Changed how data is stored quite a bit with nice query capabilities
parent
9a6a86f8b5
commit
dfd820cc25
|
@ -1,3 +1,4 @@
|
||||||
|
import type { Query } from "@plugos/plugos-syscall/store";
|
||||||
import { syscall } from "./syscall";
|
import { syscall } from "./syscall";
|
||||||
|
|
||||||
export type KV = {
|
export type KV = {
|
||||||
|
@ -25,17 +26,16 @@ export async function del(page: string, key: string): Promise<void> {
|
||||||
return syscall("index.delete", page, key);
|
return syscall("index.delete", page, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function scanPrefixForPage(
|
export async function queryPrefix(
|
||||||
page: string,
|
|
||||||
prefix: string
|
prefix: string
|
||||||
): Promise<{ key: string; page: string; value: any }[]> {
|
): Promise<{ key: string; page: string; value: any }[]> {
|
||||||
return syscall("index.scanPrefixForPage", page, prefix);
|
return syscall("index.queryPrefix", prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function scanPrefixGlobal(
|
export async function query(
|
||||||
prefix: string
|
query: Query
|
||||||
): Promise<{ key: string; page: string; value: any }[]> {
|
): Promise<{ key: string; page: string; value: any }[]> {
|
||||||
return syscall("index.scanPrefixGlobal", prefix);
|
return syscall("index.query", query);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function clearPageIndexForPage(page: string): Promise<void> {
|
export async function clearPageIndexForPage(page: string): Promise<void> {
|
||||||
|
|
|
@ -5,6 +5,20 @@ export type KV = {
|
||||||
value: any;
|
value: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Query = {
|
||||||
|
filter?: Filter[];
|
||||||
|
orderBy?: string;
|
||||||
|
orderDesc?: boolean;
|
||||||
|
limit?: number;
|
||||||
|
select?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Filter = {
|
||||||
|
op: string;
|
||||||
|
prop: string;
|
||||||
|
value: any;
|
||||||
|
};
|
||||||
|
|
||||||
export async function set(key: string, value: any): Promise<void> {
|
export async function set(key: string, value: any): Promise<void> {
|
||||||
return syscall("store.set", key, value);
|
return syscall("store.set", key, value);
|
||||||
}
|
}
|
||||||
|
@ -28,7 +42,13 @@ export async function batchDel(keys: string[]): Promise<void> {
|
||||||
export async function queryPrefix(
|
export async function queryPrefix(
|
||||||
prefix: string
|
prefix: string
|
||||||
): Promise<{ key: string; value: any }[]> {
|
): Promise<{ key: string; value: any }[]> {
|
||||||
return syscall("store.scanPrefix", prefix);
|
return syscall("store.queryPrefix", prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function query(
|
||||||
|
query: Query
|
||||||
|
): Promise<{ key: string; value: any }[]> {
|
||||||
|
return syscall("store.query", query);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deletePrefix(prefix: string): Promise<void> {
|
export async function deletePrefix(prefix: string): Promise<void> {
|
||||||
|
|
|
@ -93,19 +93,19 @@ test("Run a Node sandbox", async () => {
|
||||||
await plug.invoke("errorOut", []);
|
await plug.invoke("errorOut", []);
|
||||||
expect(true).toBe(false);
|
expect(true).toBe(false);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
expect(e.message).toBe("BOOM");
|
expect(e.message).toContain("BOOM");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await plug.invoke("errorOutSys", []);
|
await plug.invoke("errorOutSys", []);
|
||||||
expect(true).toBe(false);
|
expect(true).toBe(false);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
expect(e.message).toBe("#fail");
|
expect(e.message).toContain("#fail");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await plug.invoke("restrictedTest", []);
|
await plug.invoke("restrictedTest", []);
|
||||||
expect(true).toBe(false);
|
expect(true).toBe(false);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
expect(e.message).toBe(
|
expect(e.message).toContain(
|
||||||
"Missing permission 'restricted' for syscall restrictedSyscall"
|
"Missing permission 'restricted' for syscall restrictedSyscall"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,8 @@ test("Test store", async () => {
|
||||||
});
|
});
|
||||||
await ensureTable(db, "test_table");
|
await ensureTable(db, "test_table");
|
||||||
let system = new System("server");
|
let system = new System("server");
|
||||||
system.registerSyscalls([], storeSyscalls(db, "test_table"));
|
let syscalls = storeSyscalls(db, "test_table");
|
||||||
|
system.registerSyscalls([], syscalls);
|
||||||
let plug = await system.load(
|
let plug = await system.load(
|
||||||
{
|
{
|
||||||
name: "test",
|
name: "test",
|
||||||
|
@ -36,5 +37,79 @@ test("Test store", async () => {
|
||||||
);
|
);
|
||||||
expect(await plug.invoke("test1", [])).toBe("Pete");
|
expect(await plug.invoke("test1", [])).toBe("Pete");
|
||||||
await system.unloadAll();
|
await system.unloadAll();
|
||||||
|
|
||||||
|
let dummyCtx: any = {};
|
||||||
|
|
||||||
|
await syscalls["store.deleteAll"](dummyCtx);
|
||||||
|
await syscalls["store.batchSet"](dummyCtx, [
|
||||||
|
{
|
||||||
|
key: "pete",
|
||||||
|
value: {
|
||||||
|
age: 20,
|
||||||
|
firstName: "Pete",
|
||||||
|
lastName: "Roberts",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "petejr",
|
||||||
|
value: {
|
||||||
|
age: 8,
|
||||||
|
firstName: "Pete Jr",
|
||||||
|
lastName: "Roberts",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "petesr",
|
||||||
|
value: {
|
||||||
|
age: 78,
|
||||||
|
firstName: "Pete Sr",
|
||||||
|
lastName: "Roberts",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
let allRoberts = await syscalls["store.query"](dummyCtx, {
|
||||||
|
filter: [{ op: "=", prop: "lastName", value: "Roberts" }],
|
||||||
|
orderBy: "age",
|
||||||
|
orderDesc: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(allRoberts.length).toBe(3);
|
||||||
|
expect(allRoberts[0].key).toBe("petesr");
|
||||||
|
|
||||||
|
allRoberts = await syscalls["store.query"](dummyCtx, {
|
||||||
|
filter: [{ op: "=", prop: "lastName", value: "Roberts" }],
|
||||||
|
orderBy: "age",
|
||||||
|
limit: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(allRoberts.length).toBe(1);
|
||||||
|
expect(allRoberts[0].key).toBe("petejr");
|
||||||
|
|
||||||
|
allRoberts = await syscalls["store.query"](dummyCtx, {
|
||||||
|
filter: [
|
||||||
|
{ op: ">", prop: "age", value: 10 },
|
||||||
|
{ op: "<", prop: "age", value: 30 },
|
||||||
|
],
|
||||||
|
orderBy: "age",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(allRoberts.length).toBe(1);
|
||||||
|
expect(allRoberts[0].key).toBe("pete");
|
||||||
|
|
||||||
|
// Delete the middle one
|
||||||
|
|
||||||
|
await syscalls["store.deleteQuery"](dummyCtx, {
|
||||||
|
filter: [
|
||||||
|
{ op: ">", prop: "age", value: 10 },
|
||||||
|
{ op: "<", prop: "age", value: 30 },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
allRoberts = await syscalls["store.query"](dummyCtx, {});
|
||||||
|
expect(allRoberts.length).toBe(2);
|
||||||
|
|
||||||
|
await db.destroy();
|
||||||
|
|
||||||
await fs.unlink("test.db");
|
await fs.unlink("test.db");
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,6 +24,45 @@ export async function ensureTable(db: Knex<any, unknown>, tableName: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Query = {
|
||||||
|
filter?: Filter[];
|
||||||
|
orderBy?: string;
|
||||||
|
orderDesc?: boolean;
|
||||||
|
limit?: number;
|
||||||
|
select?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Filter = {
|
||||||
|
op: string;
|
||||||
|
prop: string;
|
||||||
|
value: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function queryToKnex(
|
||||||
|
queryBuilder: Knex.QueryBuilder<Item, any>,
|
||||||
|
query: Query
|
||||||
|
): Knex.QueryBuilder<Item, any> {
|
||||||
|
if (query.filter) {
|
||||||
|
for (let filter of query.filter) {
|
||||||
|
queryBuilder = queryBuilder.andWhereRaw(
|
||||||
|
`json_extract(value, '$.${filter.prop}') ${filter.op} ?`,
|
||||||
|
[filter.value]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (query.limit) {
|
||||||
|
queryBuilder = queryBuilder.limit(query.limit);
|
||||||
|
}
|
||||||
|
if (query.orderBy) {
|
||||||
|
queryBuilder = queryBuilder.orderByRaw(
|
||||||
|
`json_extract(value, '$.${query.orderBy}') ${
|
||||||
|
query.orderDesc ? "desc" : "asc"
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return queryBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
export function storeSyscalls(
|
export function storeSyscalls(
|
||||||
db: Knex<any, unknown>,
|
db: Knex<any, unknown>,
|
||||||
tableName: string
|
tableName: string
|
||||||
|
@ -35,6 +74,9 @@ export function storeSyscalls(
|
||||||
"store.deletePrefix": async (ctx, prefix: string) => {
|
"store.deletePrefix": async (ctx, prefix: string) => {
|
||||||
return db<Item>(tableName).andWhereLike("key", `${prefix}%`).del();
|
return db<Item>(tableName).andWhereLike("key", `${prefix}%`).del();
|
||||||
},
|
},
|
||||||
|
"store.deleteQuery": async (ctx, query: Query) => {
|
||||||
|
await queryToKnex(db<Item>(tableName), query).del();
|
||||||
|
},
|
||||||
"store.deleteAll": async (ctx) => {
|
"store.deleteAll": async (ctx) => {
|
||||||
await db<Item>(tableName).del();
|
await db<Item>(tableName).del();
|
||||||
},
|
},
|
||||||
|
@ -78,6 +120,14 @@ export function storeSyscalls(
|
||||||
value: JSON.parse(value),
|
value: JSON.parse(value),
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
"store.query": async (ctx, query: Query) => {
|
||||||
|
return (
|
||||||
|
await queryToKnex(db<Item>(tableName), query).select("key", "value")
|
||||||
|
).map(({ key, value }: { key: string; value: string }) => ({
|
||||||
|
key,
|
||||||
|
value: JSON.parse(value),
|
||||||
|
}));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
return apiObj;
|
return apiObj;
|
||||||
}
|
}
|
||||||
|
|
|
@ -422,7 +422,6 @@ function $9072202279b76d33$export$5884dae03c64f759(parsedQuery, records) {
|
||||||
}
|
}
|
||||||
async function $9072202279b76d33$export$b3c659c1456e61b0(parsedQuery, data) {
|
async function $9072202279b76d33$export$b3c659c1456e61b0(parsedQuery, data) {
|
||||||
if (parsedQuery.render) {
|
if (parsedQuery.render) {
|
||||||
console.log("Handlebars", ($parcel$interopDefault($hVExJ$handlebars)));
|
|
||||||
($parcel$interopDefault($hVExJ$handlebars)).registerHelper("json", (v)=>JSON.stringify(v)
|
($parcel$interopDefault($hVExJ$handlebars)).registerHelper("json", (v)=>JSON.stringify(v)
|
||||||
);
|
);
|
||||||
($parcel$interopDefault($hVExJ$handlebars)).registerHelper("niceDate", (ts)=>$c3893eec0c49ec96$export$5dc1410f87262ed6(new Date(ts))
|
($parcel$interopDefault($hVExJ$handlebars)).registerHelper("niceDate", (ts)=>$c3893eec0c49ec96$export$5dc1410f87262ed6(new Date(ts))
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -2,7 +2,7 @@ import type { IndexTreeEvent } from "@silverbulletmd/web/app_event";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
batchSet,
|
batchSet,
|
||||||
scanPrefixGlobal,
|
queryPrefix,
|
||||||
} from "@silverbulletmd/plugos-silverbullet-syscall/index";
|
} from "@silverbulletmd/plugos-silverbullet-syscall/index";
|
||||||
import {
|
import {
|
||||||
collectNodesOfType,
|
collectNodesOfType,
|
||||||
|
@ -61,7 +61,7 @@ export async function queryProvider({
|
||||||
query,
|
query,
|
||||||
}: QueryProviderEvent): Promise<any[]> {
|
}: QueryProviderEvent): Promise<any[]> {
|
||||||
let allItems: Item[] = [];
|
let allItems: Item[] = [];
|
||||||
for (let { key, page, value } of await scanPrefixGlobal("it:")) {
|
for (let { key, page, value } of await queryPrefix("it:")) {
|
||||||
let [, pos] = key.split(":");
|
let [, pos] = key.split(":");
|
||||||
allItems.push({
|
allItems.push({
|
||||||
...value,
|
...value,
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {
|
||||||
batchSet,
|
batchSet,
|
||||||
clearPageIndex as clearPageIndexSyscall,
|
clearPageIndex as clearPageIndexSyscall,
|
||||||
clearPageIndexForPage,
|
clearPageIndexForPage,
|
||||||
scanPrefixGlobal,
|
queryPrefix,
|
||||||
set,
|
set,
|
||||||
} from "@silverbulletmd/plugos-silverbullet-syscall/index";
|
} from "@silverbulletmd/plugos-silverbullet-syscall/index";
|
||||||
import {
|
import {
|
||||||
|
@ -73,7 +73,7 @@ export async function pageQueryProvider({
|
||||||
let allPageMap: Map<string, any> = new Map(
|
let allPageMap: Map<string, any> = new Map(
|
||||||
allPages.map((pm) => [pm.name, pm])
|
allPages.map((pm) => [pm.name, pm])
|
||||||
);
|
);
|
||||||
for (let { page, value } of await scanPrefixGlobal("meta:")) {
|
for (let { page, value } of await queryPrefix("meta:")) {
|
||||||
let p = allPageMap.get(page);
|
let p = allPageMap.get(page);
|
||||||
if (p) {
|
if (p) {
|
||||||
for (let [k, v] of Object.entries(value)) {
|
for (let [k, v] of Object.entries(value)) {
|
||||||
|
@ -90,7 +90,7 @@ export async function linkQueryProvider({
|
||||||
pageName,
|
pageName,
|
||||||
}: QueryProviderEvent): Promise<any[]> {
|
}: QueryProviderEvent): Promise<any[]> {
|
||||||
let uniqueLinks = new Set<string>();
|
let uniqueLinks = new Set<string>();
|
||||||
for (let { value: name } of await scanPrefixGlobal(`pl:${pageName}:`)) {
|
for (let { value: name } of await queryPrefix(`pl:${pageName}:`)) {
|
||||||
uniqueLinks.add(name);
|
uniqueLinks.add(name);
|
||||||
}
|
}
|
||||||
return applyQuery(
|
return applyQuery(
|
||||||
|
@ -176,7 +176,7 @@ type BackLink = {
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getBackLinks(pageName: string): Promise<BackLink[]> {
|
async function getBackLinks(pageName: string): Promise<BackLink[]> {
|
||||||
let allBackLinks = await scanPrefixGlobal(`pl:${pageName}:`);
|
let allBackLinks = await queryPrefix(`pl:${pageName}:`);
|
||||||
let pagesToUpdate: BackLink[] = [];
|
let pagesToUpdate: BackLink[] = [];
|
||||||
for (let { key, value } of allBackLinks) {
|
for (let { key, value } of allBackLinks) {
|
||||||
let keyParts = key.split(":");
|
let keyParts = key.split(":");
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
import type { IndexTreeEvent } from "@silverbulletmd/web/app_event";
|
import type { IndexTreeEvent } from "@silverbulletmd/web/app_event";
|
||||||
import {
|
import {
|
||||||
batchSet,
|
batchSet,
|
||||||
scanPrefixGlobal,
|
queryPrefix,
|
||||||
} from "@silverbulletmd/plugos-silverbullet-syscall";
|
} from "@silverbulletmd/plugos-silverbullet-syscall";
|
||||||
import {
|
import {
|
||||||
collectNodesOfType,
|
collectNodesOfType,
|
||||||
|
@ -102,7 +102,7 @@ export async function queryProvider({
|
||||||
query,
|
query,
|
||||||
}: QueryProviderEvent): Promise<any[]> {
|
}: QueryProviderEvent): Promise<any[]> {
|
||||||
let allData: any[] = [];
|
let allData: any[] = [];
|
||||||
for (let { key, page, value } of await scanPrefixGlobal("data:")) {
|
for (let { key, page, value } of await queryPrefix("data:")) {
|
||||||
let [, pos] = key.split("@");
|
let [, pos] = key.split("@");
|
||||||
allData.push({
|
allData.push({
|
||||||
...value,
|
...value,
|
||||||
|
|
|
@ -221,7 +221,6 @@ export async function renderQuery(
|
||||||
data: any[]
|
data: any[]
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
if (parsedQuery.render) {
|
if (parsedQuery.render) {
|
||||||
console.log("Handlebars", Handlebars);
|
|
||||||
Handlebars.registerHelper("json", (v) => JSON.stringify(v));
|
Handlebars.registerHelper("json", (v) => JSON.stringify(v));
|
||||||
Handlebars.registerHelper("niceDate", (ts) => niceDate(new Date(ts)));
|
Handlebars.registerHelper("niceDate", (ts) => niceDate(new Date(ts)));
|
||||||
Handlebars.registerHelper("yaml", (v, prefix) => {
|
Handlebars.registerHelper("yaml", (v, prefix) => {
|
||||||
|
|
|
@ -22,25 +22,24 @@ export async function updateMaterializedQueriesCommand() {
|
||||||
const currentPage = await getCurrentPage();
|
const currentPage = await getCurrentPage();
|
||||||
await save();
|
await save();
|
||||||
await flashNotification("Updating materialized queries...");
|
await flashNotification("Updating materialized queries...");
|
||||||
await invokeFunction(
|
if (
|
||||||
"server",
|
await invokeFunction(
|
||||||
"updateMaterializedQueriesOnPage",
|
"server",
|
||||||
currentPage
|
"updateMaterializedQueriesOnPage",
|
||||||
);
|
currentPage
|
||||||
await reloadPage();
|
)
|
||||||
}
|
) {
|
||||||
|
await reloadPage();
|
||||||
export async function whiteOutQueriesCommand() {
|
}
|
||||||
const text = await getText();
|
|
||||||
const parsed = await parseMarkdown(text);
|
|
||||||
console.log(removeQueries(parsed));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called from client, running on server
|
// Called from client, running on server
|
||||||
export async function updateMaterializedQueriesOnPage(pageName: string) {
|
export async function updateMaterializedQueriesOnPage(
|
||||||
|
pageName: string
|
||||||
|
): Promise<boolean> {
|
||||||
let { text } = await readPage(pageName);
|
let { text } = await readPage(pageName);
|
||||||
|
|
||||||
text = await replaceAsync(
|
let newText = await replaceAsync(
|
||||||
text,
|
text,
|
||||||
queryRegex,
|
queryRegex,
|
||||||
async (fullMatch, startQuery, query, body, endQuery) => {
|
async (fullMatch, startQuery, query, body, endQuery) => {
|
||||||
|
@ -68,6 +67,9 @@ export async function updateMaterializedQueriesOnPage(pageName: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
// console.log("New text", text);
|
if (text !== newText) {
|
||||||
await writePage(pageName, text);
|
await writePage(pageName, newText);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,8 @@ functions:
|
||||||
key: "Alt-q"
|
key: "Alt-q"
|
||||||
button:
|
button:
|
||||||
label: "🔄"
|
label: "🔄"
|
||||||
whiteOutQueriesCommand:
|
events:
|
||||||
path: ./materialized_queries.ts:whiteOutQueriesCommand
|
- editor:pageLoaded
|
||||||
command:
|
|
||||||
name: "Debug: Whiteout Queries"
|
|
||||||
indexData:
|
indexData:
|
||||||
path: ./data.ts:indexData
|
path: ./data.ts:indexData
|
||||||
events:
|
events:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { fullTextIndex, fullTextSearch } from "@plugos/plugos-syscall/fulltext";
|
import { fullTextIndex, fullTextSearch } from "@plugos/plugos-syscall/fulltext";
|
||||||
import { renderToText } from "@silverbulletmd/common/tree";
|
import { renderToText } from "@silverbulletmd/common/tree";
|
||||||
import { PageMeta } from "@silverbulletmd/common/types";
|
import { PageMeta } from "@silverbulletmd/common/types";
|
||||||
import { scanPrefixGlobal } from "@silverbulletmd/plugos-silverbullet-syscall";
|
import { queryPrefix } from "@silverbulletmd/plugos-silverbullet-syscall";
|
||||||
import {
|
import {
|
||||||
navigate,
|
navigate,
|
||||||
prompt,
|
prompt,
|
||||||
|
@ -28,7 +28,7 @@ export async function queryProvider({
|
||||||
let allPageMap: Map<string, any> = new Map(
|
let allPageMap: Map<string, any> = new Map(
|
||||||
results.map((r: any) => [r.name, r])
|
results.map((r: any) => [r.name, r])
|
||||||
);
|
);
|
||||||
for (let { page, value } of await scanPrefixGlobal("meta:")) {
|
for (let { page, value } of await queryPrefix("meta:")) {
|
||||||
let p = allPageMap.get(page);
|
let p = allPageMap.get(page);
|
||||||
if (p) {
|
if (p) {
|
||||||
for (let [k, v] of Object.entries(value)) {
|
for (let [k, v] of Object.entries(value)) {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type { ClickEvent, IndexTreeEvent } from "@silverbulletmd/web/app_event";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
batchSet,
|
batchSet,
|
||||||
scanPrefixGlobal,
|
queryPrefix,
|
||||||
} from "@silverbulletmd/plugos-silverbullet-syscall/index";
|
} from "@silverbulletmd/plugos-silverbullet-syscall/index";
|
||||||
import {
|
import {
|
||||||
readPage,
|
readPage,
|
||||||
|
@ -197,7 +197,7 @@ export async function queryProvider({
|
||||||
query,
|
query,
|
||||||
}: QueryProviderEvent): Promise<Task[]> {
|
}: QueryProviderEvent): Promise<Task[]> {
|
||||||
let allTasks: Task[] = [];
|
let allTasks: Task[] = [];
|
||||||
for (let { key, page, value } of await scanPrefixGlobal("task:")) {
|
for (let { key, page, value } of await queryPrefix("task:")) {
|
||||||
let [, pos] = key.split(":");
|
let [, pos] = key.split(":");
|
||||||
allTasks.push({
|
allTasks.push({
|
||||||
...value,
|
...value,
|
||||||
|
|
|
@ -10,7 +10,7 @@ import bodyParser from "body-parser";
|
||||||
import { EventHook } from "@plugos/plugos/hooks/event";
|
import { EventHook } from "@plugos/plugos/hooks/event";
|
||||||
import spaceSyscalls from "./syscalls/space";
|
import spaceSyscalls from "./syscalls/space";
|
||||||
import { eventSyscalls } from "@plugos/plugos/syscalls/event";
|
import { eventSyscalls } from "@plugos/plugos/syscalls/event";
|
||||||
import { ensurePageIndexTable, pageIndexSyscalls } from "./syscalls";
|
import { ensureTable, pageIndexSyscalls } from "./syscalls";
|
||||||
import knex, { Knex } from "knex";
|
import knex, { Knex } from "knex";
|
||||||
import shellSyscalls from "@plugos/plugos/syscalls/shell.node";
|
import shellSyscalls from "@plugos/plugos/syscalls/shell.node";
|
||||||
import { NodeCronHook } from "@plugos/plugos/hooks/node_cron";
|
import { NodeCronHook } from "@plugos/plugos/hooks/node_cron";
|
||||||
|
@ -207,7 +207,7 @@ export class ExpressServer {
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
|
|
||||||
await ensurePageIndexTable(this.db);
|
await ensureTable(this.db);
|
||||||
await ensureFTSTable(this.db, "fts");
|
await ensureFTSTable(this.db, "fts");
|
||||||
console.log("Setting up router");
|
console.log("Setting up router");
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
import { SysCallMapping } from "@plugos/plugos/system";
|
import { SysCallMapping } from "@plugos/plugos/system";
|
||||||
|
import { Query, queryToKnex } from "@plugos/plugos/syscalls/store.knex_node";
|
||||||
|
|
||||||
import {
|
type Item = {
|
||||||
ensureTable,
|
|
||||||
storeSyscalls,
|
|
||||||
} from "@plugos/plugos/syscalls/store.knex_node";
|
|
||||||
|
|
||||||
type IndexItem = {
|
|
||||||
page: string;
|
page: string;
|
||||||
key: string;
|
key: string;
|
||||||
value: any;
|
value: any;
|
||||||
|
@ -17,45 +13,35 @@ export type KV = {
|
||||||
value: any;
|
value: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
const tableName = "page_index";
|
||||||
Keyspace design:
|
|
||||||
|
|
||||||
for page lookups:
|
export async function ensureTable(db: Knex<any, unknown>) {
|
||||||
p~page~key
|
if (!(await db.schema.hasTable(tableName))) {
|
||||||
|
await db.schema.createTable(tableName, (table) => {
|
||||||
|
table.string("page");
|
||||||
|
table.string("key");
|
||||||
|
table.text("value");
|
||||||
|
table.primary(["page", "key"]);
|
||||||
|
table.index(["key"]);
|
||||||
|
});
|
||||||
|
|
||||||
for global lookups:
|
console.log(`Created table ${tableName}`);
|
||||||
k~key~page
|
}
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
function pageKey(page: string, key: string) {
|
|
||||||
return `p~${page}~${key}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function unpackPageKey(dbKey: string): { page: string; key: string } {
|
|
||||||
const [, page, key] = dbKey.split("~");
|
|
||||||
return { page, key };
|
|
||||||
}
|
|
||||||
|
|
||||||
function globalKey(page: string, key: string) {
|
|
||||||
return `k~${key}~${page}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function unpackGlobalKey(dbKey: string): { page: string; key: string } {
|
|
||||||
const [, key, page] = dbKey.split("~");
|
|
||||||
return { page, key };
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function ensurePageIndexTable(db: Knex<any, unknown>) {
|
|
||||||
await ensureTable(db, "page_index");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pageIndexSyscalls(db: Knex<any, unknown>): SysCallMapping {
|
export function pageIndexSyscalls(db: Knex<any, unknown>): SysCallMapping {
|
||||||
const storeCalls = storeSyscalls(db, "page_index");
|
|
||||||
const apiObj: SysCallMapping = {
|
const apiObj: SysCallMapping = {
|
||||||
"index.set": async (ctx, page: string, key: string, value: any) => {
|
"index.set": async (ctx, page: string, key: string, value: any) => {
|
||||||
await storeCalls["store.set"](ctx, pageKey(page, key), value);
|
let changed = await db<Item>(tableName)
|
||||||
await storeCalls["store.set"](ctx, globalKey(page, key), value);
|
.where({ key, page })
|
||||||
|
.update("value", JSON.stringify(value));
|
||||||
|
if (changed === 0) {
|
||||||
|
await db<Item>(tableName).insert({
|
||||||
|
key,
|
||||||
|
page,
|
||||||
|
value: JSON.stringify(value),
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"index.batchSet": async (ctx, page: string, kvs: KV[]) => {
|
"index.batchSet": async (ctx, page: string, kvs: KV[]) => {
|
||||||
for (let { key, value } of kvs) {
|
for (let { key, value } of kvs) {
|
||||||
|
@ -63,53 +49,53 @@ export function pageIndexSyscalls(db: Knex<any, unknown>): SysCallMapping {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"index.delete": async (ctx, page: string, key: string) => {
|
"index.delete": async (ctx, page: string, key: string) => {
|
||||||
await storeCalls["store.delete"](ctx, pageKey(page, key));
|
await db<Item>(tableName).where({ key, page }).del();
|
||||||
await storeCalls["store.delete"](ctx, globalKey(page, key));
|
|
||||||
},
|
},
|
||||||
"index.get": async (ctx, page: string, key: string) => {
|
"index.get": async (ctx, page: string, key: string) => {
|
||||||
return storeCalls["store.get"](ctx, pageKey(page, key));
|
let result = await db<Item>(tableName)
|
||||||
|
.where({ key, page })
|
||||||
|
.select("value");
|
||||||
|
if (result.length) {
|
||||||
|
return JSON.parse(result[0].value);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"index.scanPrefixForPage": async (ctx, page: string, prefix: string) => {
|
"index.queryPrefix": async (ctx, prefix: string) => {
|
||||||
return (
|
return (
|
||||||
await storeCalls["store.queryPrefix"](ctx, pageKey(page, prefix))
|
await db<Item>(tableName)
|
||||||
).map(({ key, value }: { key: string; value: any }) => {
|
.andWhereLike("key", `${prefix}%`)
|
||||||
const { key: pageKey } = unpackPageKey(key);
|
.select("key", "value", "page")
|
||||||
return {
|
).map(({ key, value, page }) => ({
|
||||||
page,
|
key,
|
||||||
key: pageKey,
|
page,
|
||||||
value,
|
value: JSON.parse(value),
|
||||||
};
|
}));
|
||||||
});
|
|
||||||
},
|
},
|
||||||
"index.scanPrefixGlobal": async (ctx, prefix: string) => {
|
"index.query": async (ctx, query: Query) => {
|
||||||
return (await storeCalls["store.queryPrefix"](ctx, `k~${prefix}`)).map(
|
return (
|
||||||
({ key, value }: { key: string; value: any }) => {
|
await queryToKnex(db<Item>(tableName), query).select(
|
||||||
const { page, key: pageKey } = unpackGlobalKey(key);
|
"key",
|
||||||
return {
|
"value",
|
||||||
page,
|
"page"
|
||||||
key: pageKey,
|
)
|
||||||
value,
|
).map(({ key, value, page }: any) => ({
|
||||||
};
|
key,
|
||||||
}
|
page,
|
||||||
);
|
value: JSON.parse(value),
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
"index.clearPageIndexForPage": async (ctx, page: string) => {
|
"index.clearPageIndexForPage": async (ctx, page: string) => {
|
||||||
await apiObj["index.deletePrefixForPage"](ctx, page, "");
|
await apiObj["index.deletePrefixForPage"](ctx, page, "");
|
||||||
},
|
},
|
||||||
"index.deletePrefixForPage": async (ctx, page: string, prefix: string) => {
|
"index.deletePrefixForPage": async (ctx, page: string, prefix: string) => {
|
||||||
// Collect all global keys for this page to delete
|
return db<Item>(tableName)
|
||||||
let keysToDelete = (
|
.where({ page })
|
||||||
await storeCalls["store.queryPrefix"](ctx, pageKey(page, prefix))
|
.andWhereLike("key", `${prefix}%`)
|
||||||
).map(({ key }: { key: string; value: string }) =>
|
.del();
|
||||||
globalKey(page, unpackPageKey(key).key)
|
|
||||||
);
|
|
||||||
// Delete all page keys
|
|
||||||
await storeCalls["store.deletePrefix"](ctx, pageKey(page, prefix));
|
|
||||||
// console.log("Deleting keys", keysToDelete);
|
|
||||||
await storeCalls["store.batchDelete"](ctx, keysToDelete);
|
|
||||||
},
|
},
|
||||||
"index.clearPageIndex": async (ctx) => {
|
"index.clearPageIndex": async (ctx) => {
|
||||||
await storeCalls["store.deleteAll"](ctx);
|
await db<Item>(tableName).del();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return apiObj;
|
return apiObj;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { ParseTree } from "@silverbulletmd/common/tree";
|
import type { ParseTree } from "@silverbulletmd/common/tree";
|
||||||
|
|
||||||
export type AppEvent = "page:click" | "page:complete";
|
export type AppEvent = "page:click" | "page:complete" | "page:load";
|
||||||
|
|
||||||
export type ClickEvent = {
|
export type ClickEvent = {
|
||||||
page: string;
|
page: string;
|
||||||
|
|
|
@ -509,6 +509,7 @@ export class Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadPage(pageName: string) {
|
async loadPage(pageName: string) {
|
||||||
|
const loadingDifferentPage = pageName !== this.currentPage;
|
||||||
const editorView = this.editorView;
|
const editorView = this.editorView;
|
||||||
if (!editorView) {
|
if (!editorView) {
|
||||||
return;
|
return;
|
||||||
|
@ -547,7 +548,9 @@ export class Editor {
|
||||||
meta: doc.meta,
|
meta: doc.meta,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.eventHook.dispatchEvent("editor:pageSwitched");
|
if (loadingDifferentPage) {
|
||||||
|
await this.eventHook.dispatchEvent("editor:pageLoaded", pageName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tweakEditorDOM(contentDOM: HTMLElement, readOnly: boolean) {
|
tweakEditorDOM(contentDOM: HTMLElement, readOnly: boolean) {
|
||||||
|
|
|
@ -31,13 +31,6 @@ export function editorSyscalls(editor: Editor): SysCallMapping {
|
||||||
"editor.getCurrentPage": (): string => {
|
"editor.getCurrentPage": (): string => {
|
||||||
return editor.currentPage!;
|
return editor.currentPage!;
|
||||||
},
|
},
|
||||||
// sets the current page name, without changing the content
|
|
||||||
"editor.setPage": (ctx, newName: string) => {
|
|
||||||
return editor.viewDispatch({
|
|
||||||
type: "page-loaded",
|
|
||||||
name: newName,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
"editor.getText": () => {
|
"editor.getText": () => {
|
||||||
return editor.editorView?.state.sliceDoc();
|
return editor.editorView?.state.sliceDoc();
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,8 +5,7 @@ import { Space } from "@silverbulletmd/common/spaces/space";
|
||||||
export function indexerSyscalls(space: Space): SysCallMapping {
|
export function indexerSyscalls(space: Space): SysCallMapping {
|
||||||
return proxySyscalls(
|
return proxySyscalls(
|
||||||
[
|
[
|
||||||
"index.scanPrefixForPage",
|
"index.queryPrefix",
|
||||||
"index.scanPrefixGlobal",
|
|
||||||
"index.get",
|
"index.get",
|
||||||
"index.set",
|
"index.set",
|
||||||
"index.batchSet",
|
"index.batchSet",
|
||||||
|
|
Loading…
Reference in New Issue