wizards 0.2.11 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/runtime/dist/authoring/guide.js +8 -2
- package/apps/runtime/dist/db/migrations/tenant/0007_space_data.sql +43 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0007_snapshot.json +2012 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/_journal.json +7 -0
- package/apps/runtime/dist/db/schema.js +55 -0
- package/apps/runtime/dist/engine/runner.js +1 -1
- package/apps/runtime/dist/routes/api.js +14 -1
- package/apps/runtime/dist/routes/runs.js +13 -7
- package/apps/runtime/dist/routes/studio.js +89 -1
- package/apps/runtime/dist/services/cloud.js +62 -2
- package/apps/runtime/dist/services/runs.js +69 -1
- package/apps/runtime/dist/services/space-data.js +361 -0
- package/apps/runtime/dist/services/spaces.js +95 -1
- package/apps/runtime/dist/store/errors.js +3 -0
- package/apps/runtime/dist/store/index.js +19 -5
- package/apps/runtime/dist/store/shared-lists.js +124 -0
- package/apps/runtime/dist/tools/index.js +4 -0
- package/apps/runtime/dist/tools/pages.js +51 -0
- package/apps/runtime/dist/tools/store.js +3 -3
- package/apps/web/dist/assets/Account-ulB5WeOT.js +1 -0
- package/apps/web/dist/assets/ChangelogDialog-BVR5814e.js +1 -0
- package/apps/web/dist/assets/ChatPanel-C7uql4_d.js +1 -0
- package/apps/web/dist/assets/{CreditsDialog-DSjsgLoo.js → CreditsDialog-o9h0Nrui.js} +1 -1
- package/apps/web/dist/assets/EditorPage-CskikVRK.js +2 -0
- package/apps/web/dist/assets/FlowPreview-YaqUirty.js +1 -0
- package/apps/web/dist/assets/{Harness-OlRaoHGd.js → Harness-ut5xGR_o.js} +2 -2
- package/apps/web/dist/assets/HomePage-Drys3BPU.js +1 -0
- package/apps/web/dist/assets/{ImportWizard-BGs5dv94.js → ImportWizard-BGqkJFeZ.js} +1 -1
- package/apps/web/dist/assets/LocalRuntime-DwPJjv3H.js +1 -0
- package/apps/web/dist/assets/NewWizardPage-CrBSpeIw.js +2 -0
- package/apps/web/dist/assets/ProjectPage-CAdSHd0Y.js +4 -0
- package/apps/web/dist/assets/PublicRunner-JleX2stQ.js +1 -0
- package/apps/web/dist/assets/RunnerView-BWG4Odbt.js +70 -0
- package/apps/web/dist/assets/SettingsPage-CYOdJ5FL.js +1 -0
- package/apps/web/dist/assets/SetupPage-CMVwnLQm.js +1 -0
- package/apps/web/dist/assets/SharePage-CKppWAtr.js +1 -0
- package/apps/web/dist/assets/{api-u3nydnnp.js → api-DTLkC0ge.js} +2 -2
- package/apps/web/dist/assets/changelog-dI1t4l0T.js +1 -0
- package/apps/web/dist/assets/circle-alert-D2SCwFT-.js +1 -0
- package/apps/web/dist/assets/{definition-Co2Mg1qo.js → definition-Dp0DR2D6.js} +1 -1
- package/apps/web/dist/assets/{esm-CyCiCaFy.js → esm-B6hLaDRL.js} +1 -1
- package/apps/web/dist/assets/index-BWxUOTc4.js +10 -0
- package/apps/web/dist/assets/index-DMwk_afK.css +1 -0
- package/apps/web/dist/assets/lock-DZL2N67v.js +1 -0
- package/apps/web/dist/assets/meta-CZWUthoQ.js +1 -0
- package/apps/web/dist/assets/refresh-cw-BNtHis1x.js +1 -0
- package/apps/web/dist/assets/settings-menu-Iz6kyoDe.js +1 -0
- package/apps/web/dist/assets/{type-BqbppSsp.js → table-2-BdDuBcF2.js} +1 -1
- package/apps/web/dist/assets/ui-CHX2gEQl.js +1 -0
- package/apps/web/dist/assets/video-9fDPRPCv.js +1 -0
- package/apps/web/dist/index.html +4 -4
- package/apps/web/dist/mcp/flow.html +5 -5
- package/node_modules/@engenty-wizards/plugin-sdk/dist/studio.d.ts +12 -4
- package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +6 -1
- package/node_modules/@engenty-wizards/shared/dist/definition.js +36 -2
- package/node_modules/@engenty-wizards/shared/dist/space-data.d.ts +69 -0
- package/node_modules/@engenty-wizards/shared/dist/space-data.js +46 -0
- package/node_modules/@engenty-wizards/shared/dist/store.d.ts +2 -0
- package/package.json +1 -1
- package/apps/web/dist/assets/Account-BJPPu___.js +0 -1
- package/apps/web/dist/assets/ChangelogDialog-D5Jc3poI.js +0 -1
- package/apps/web/dist/assets/ChatPanel-n7GEZxTX.js +0 -1
- package/apps/web/dist/assets/EditorPage-teTTDbnX.js +0 -2
- package/apps/web/dist/assets/FlowPreview-4cRsWwO8.js +0 -1
- package/apps/web/dist/assets/HomePage-BGQMVqEr.js +0 -1
- package/apps/web/dist/assets/LocalRuntime-WHeeZvB4.js +0 -1
- package/apps/web/dist/assets/NewWizardPage-h3dWMUgw.js +0 -2
- package/apps/web/dist/assets/ProjectPage-Cfnwi-pK.js +0 -2
- package/apps/web/dist/assets/PublicRunner-Oa4uTkvr.js +0 -1
- package/apps/web/dist/assets/ReadOnly-B0-ReU_0.js +0 -1
- package/apps/web/dist/assets/RunnerView-njFeqF1A.js +0 -2
- package/apps/web/dist/assets/SettingsPage-D4xJ8tEu.js +0 -1
- package/apps/web/dist/assets/SetupPage-DUyvWSdi.js +0 -1
- package/apps/web/dist/assets/SharePage-Bikt8bn4.js +0 -1
- package/apps/web/dist/assets/arrow-left-hTN1VoH1.js +0 -1
- package/apps/web/dist/assets/changelog-CT8yK95a.js +0 -1
- package/apps/web/dist/assets/circle-alert-B76z9TWZ.js +0 -1
- package/apps/web/dist/assets/index-CQQD8QNR.css +0 -1
- package/apps/web/dist/assets/index-Z0fbfnW7.js +0 -10
- package/apps/web/dist/assets/lock-CzNXAYdi.js +0 -1
- package/apps/web/dist/assets/meta-DsCJykuf.js +0 -1
- package/apps/web/dist/assets/mic-Bvz4mA2j.js +0 -1
- package/apps/web/dist/assets/outputs-Ba-Tblsj.js +0 -69
- package/apps/web/dist/assets/refresh-cw-Cmk7VCe0.js +0 -1
- package/apps/web/dist/assets/ui-gGAdXx-P.js +0 -1
- package/apps/web/dist/assets/video-DBmwF1Jm.js +0 -1
|
@@ -225,6 +225,61 @@ export const projectChunk = sqliteTable("project_chunk", {
|
|
|
225
225
|
index("project_chunk_file").on(t.fileId),
|
|
226
226
|
index("project_chunk_ref").on(t.projectId, t.plugin, t.ref),
|
|
227
227
|
]);
|
|
228
|
+
// --- The space's data -------------------------------------------------------
|
|
229
|
+
// Tables and pages a space keeps, its own or one of its wizards'. Shared by everyone who works
|
|
230
|
+
// in the space, unlike the wizard's store below, which belongs to one person.
|
|
231
|
+
/** A table: its columns are engenty data-table columns, its rows in `space_table_row`. */
|
|
232
|
+
export const spaceTable = sqliteTable("space_table", {
|
|
233
|
+
id: text("id").primaryKey(),
|
|
234
|
+
tenantId: text("tenant_id").notNull(),
|
|
235
|
+
projectId: text("project_id")
|
|
236
|
+
.notNull()
|
|
237
|
+
.references(() => project.id, { onDelete: "cascade" }),
|
|
238
|
+
/** The wizard it belongs to; null: the space's own. */
|
|
239
|
+
wizardId: text("wizard_id").references(() => wizard.id, { onDelete: "cascade" }),
|
|
240
|
+
/**
|
|
241
|
+
* The wizard's shared list it keeps: its title, columns and key come from the wizard's
|
|
242
|
+
* definition, its rows from every run. Null: a table made in the studio.
|
|
243
|
+
*/
|
|
244
|
+
list: text("list"),
|
|
245
|
+
/** The column a row is matched on, as the list says. */
|
|
246
|
+
keyColumn: text("key_column"),
|
|
247
|
+
title: text("title").notNull(),
|
|
248
|
+
columns: text("columns", { mode: "json" }).$type().notNull(),
|
|
249
|
+
createdAt: createdAt(),
|
|
250
|
+
updatedAt: updatedAt(),
|
|
251
|
+
}, (t) => [
|
|
252
|
+
index("space_table_project").on(t.projectId, t.wizardId),
|
|
253
|
+
uniqueIndex("space_table_list").on(t.wizardId, t.list),
|
|
254
|
+
]);
|
|
255
|
+
/** A row of a table: cells by column id. Rows stand in the order they were added. */
|
|
256
|
+
export const spaceTableRow = sqliteTable("space_table_row", {
|
|
257
|
+
id: text("id").primaryKey(),
|
|
258
|
+
tableId: text("table_id")
|
|
259
|
+
.notNull()
|
|
260
|
+
.references(() => spaceTable.id, { onDelete: "cascade" }),
|
|
261
|
+
/** The key column's value, normalised: saving a row with a known key updates that row. */
|
|
262
|
+
key: text("key"),
|
|
263
|
+
cells: text("cells", { mode: "json" }).$type().notNull(),
|
|
264
|
+
createdAt: createdAt(),
|
|
265
|
+
updatedAt: updatedAt(),
|
|
266
|
+
}, (t) => [
|
|
267
|
+
index("space_table_row_table").on(t.tableId, t.createdAt),
|
|
268
|
+
index("space_table_row_key").on(t.tableId, t.key),
|
|
269
|
+
]);
|
|
270
|
+
/** A page: a title and markdown, what the editor shows and an agent reads and writes. */
|
|
271
|
+
export const spacePage = sqliteTable("space_page", {
|
|
272
|
+
id: text("id").primaryKey(),
|
|
273
|
+
tenantId: text("tenant_id").notNull(),
|
|
274
|
+
projectId: text("project_id")
|
|
275
|
+
.notNull()
|
|
276
|
+
.references(() => project.id, { onDelete: "cascade" }),
|
|
277
|
+
wizardId: text("wizard_id").references(() => wizard.id, { onDelete: "cascade" }),
|
|
278
|
+
title: text("title").notNull(),
|
|
279
|
+
markdown: text("markdown").notNull().default(""),
|
|
280
|
+
createdAt: createdAt(),
|
|
281
|
+
updatedAt: updatedAt(),
|
|
282
|
+
}, (t) => [index("space_page_project").on(t.projectId, t.wizardId)]);
|
|
228
283
|
// --- The wizard's store ------------------------------------------------------
|
|
229
284
|
// What a wizard keeps between runs for one person. `holder` is that person: "u:<userId>" for a
|
|
230
285
|
// signed-in admin, "v:<visitorId>" for an end user on a shared link.
|
|
@@ -226,7 +226,7 @@ async function storedLists(run) {
|
|
|
226
226
|
const scope = scopeOf(run);
|
|
227
227
|
const out = {};
|
|
228
228
|
for (const def of run.definition.lists ?? []) {
|
|
229
|
-
out[def.id] = { def, rows: await listRows(scope, def
|
|
229
|
+
out[def.id] = { def, rows: await listRows(scope, def) };
|
|
230
230
|
}
|
|
231
231
|
return out;
|
|
232
232
|
}
|
|
@@ -6,7 +6,7 @@ import { allowed } from "../limits.js";
|
|
|
6
6
|
import { authenticate, principalOf } from "../mcp/auth.js";
|
|
7
7
|
import { ServiceError } from "../services/errors.js";
|
|
8
8
|
import { deleteFile, draftFiles, writeFile } from "../services/files.js";
|
|
9
|
-
import { checkOnServer, checkSchema, listSyncedSpaces, patchSyncedWizard, removeSyncedSpace, removeSyncedWizard, rotateSyncedLink, syncSettingsSchema, syncWizard, syncWizardSchema, } from "../services/spaces.js";
|
|
9
|
+
import { checkOnServer, checkSchema, listSyncedSpaces, patchSyncedWizard, removeSyncedSpace, removeSyncedWizard, rotateSyncedLink, syncDataSchema, syncSettingsSchema, syncSpaceData, syncWizard, syncWizardSchema, } from "../services/spaces.js";
|
|
10
10
|
import { createWizard, ownedWizard, publishWizard, writeDraft } from "../services/wizards.js";
|
|
11
11
|
import { codeOf, tenantStatus } from "../tenants/control.js";
|
|
12
12
|
const importSchema = z.object({
|
|
@@ -83,6 +83,19 @@ export const apiRoutes = new Hono()
|
|
|
83
83
|
}
|
|
84
84
|
const body = syncWizardSchema.parse(await c.req.json());
|
|
85
85
|
return withTenant(who.tenantId, async () => c.json(await syncWizard(c.req.param("spaceId"), c.req.param("wizardId"), body)));
|
|
86
|
+
})
|
|
87
|
+
// The space's own tables and pages, sent with a wizard when they changed.
|
|
88
|
+
.put("/spaces/:spaceId/data", tooLarge, async (c) => {
|
|
89
|
+
const who = await caller(c, ["wizards:write"]);
|
|
90
|
+
if (who instanceof Response) {
|
|
91
|
+
return who;
|
|
92
|
+
}
|
|
93
|
+
const limited = withinSyncLimit(c, who);
|
|
94
|
+
if (limited) {
|
|
95
|
+
return limited;
|
|
96
|
+
}
|
|
97
|
+
const body = syncDataSchema.parse(await c.req.json());
|
|
98
|
+
return withTenant(who.tenantId, async () => c.json(await syncSpaceData(c.req.param("spaceId"), body)));
|
|
86
99
|
})
|
|
87
100
|
.patch("/spaces/:spaceId/wizards/:wizardId", async (c) => {
|
|
88
101
|
const who = await caller(c, ["wizards:publish"]);
|
|
@@ -594,7 +594,8 @@ export const runRoutes = new Hono()
|
|
|
594
594
|
// --- the wizard's lists and files for this person -------------------------------
|
|
595
595
|
.post("/:id/lists/:list/rows", async (c) => {
|
|
596
596
|
const run = await accessibleRun(c, c.req.param("id"));
|
|
597
|
-
|
|
597
|
+
// A shared list is every run's: the person never sees it.
|
|
598
|
+
const def = run?.definition.lists?.find((l) => l.id === c.req.param("list") && !l.shared);
|
|
598
599
|
if (!run || !def) {
|
|
599
600
|
return c.json({ error: "not found" }, 404);
|
|
600
601
|
}
|
|
@@ -612,7 +613,8 @@ export const runRoutes = new Hono()
|
|
|
612
613
|
})
|
|
613
614
|
.patch("/:id/lists/:list/rows/:rowId", async (c) => {
|
|
614
615
|
const run = await accessibleRun(c, c.req.param("id"));
|
|
615
|
-
|
|
616
|
+
// A shared list is every run's: the person never sees it.
|
|
617
|
+
const def = run?.definition.lists?.find((l) => l.id === c.req.param("list") && !l.shared);
|
|
616
618
|
if (!run || !def) {
|
|
617
619
|
return c.json({ error: "not found" }, 404);
|
|
618
620
|
}
|
|
@@ -632,7 +634,8 @@ export const runRoutes = new Hono()
|
|
|
632
634
|
})
|
|
633
635
|
.delete("/:id/lists/:list/rows/:rowId", async (c) => {
|
|
634
636
|
const run = await accessibleRun(c, c.req.param("id"));
|
|
635
|
-
|
|
637
|
+
// A shared list is every run's: the person never sees it.
|
|
638
|
+
const def = run?.definition.lists?.find((l) => l.id === c.req.param("list") && !l.shared);
|
|
636
639
|
if (!run || !def) {
|
|
637
640
|
return c.json({ error: "not found" }, 404);
|
|
638
641
|
}
|
|
@@ -642,13 +645,14 @@ export const runRoutes = new Hono()
|
|
|
642
645
|
})
|
|
643
646
|
.get("/:id/lists/:list/download", async (c) => {
|
|
644
647
|
const run = await accessibleRun(c, c.req.param("id"));
|
|
645
|
-
|
|
648
|
+
// A shared list is every run's: the person never sees it.
|
|
649
|
+
const def = run?.definition.lists?.find((l) => l.id === c.req.param("list") && !l.shared);
|
|
646
650
|
const format = z.enum(FORMATS).safeParse(c.req.query("format"));
|
|
647
651
|
if (!run || !def || !format.success || !LIST_FORMATS.includes(format.data)) {
|
|
648
652
|
return c.notFound();
|
|
649
653
|
}
|
|
650
654
|
const scope = scopeOf(run);
|
|
651
|
-
const download = await listDownload(def, await listRows(scope, def
|
|
655
|
+
const download = await listDownload(def, await listRows(scope, def), format.data, `${run.definition.title} ${def.title}`, async (path) => {
|
|
652
656
|
const found = await readStoreFile(scope, path).catch(() => null);
|
|
653
657
|
return found ? new Uint8Array(found.data) : null;
|
|
654
658
|
});
|
|
@@ -660,10 +664,12 @@ export const runRoutes = new Hono()
|
|
|
660
664
|
return c.json({ error: "not found" }, 404);
|
|
661
665
|
}
|
|
662
666
|
const scope = scopeOf(run);
|
|
663
|
-
const lists = await Promise.all((run.definition.lists ?? [])
|
|
667
|
+
const lists = await Promise.all((run.definition.lists ?? [])
|
|
668
|
+
.filter((def) => !def.shared)
|
|
669
|
+
.map(async (def) => ({
|
|
664
670
|
id: def.id,
|
|
665
671
|
title: def.title,
|
|
666
|
-
rows: (await listRows(scope, def
|
|
672
|
+
rows: (await listRows(scope, def)).length,
|
|
667
673
|
})));
|
|
668
674
|
const secrets = await listSecrets(scope);
|
|
669
675
|
return c.json({
|
|
@@ -37,7 +37,8 @@ import { profileSchema, saveProfile, userProfile } from "../services/profile.js"
|
|
|
37
37
|
import { addProjectFile, fileView, orderProjectFiles, projectFile, projectFileContent, projectFiles, reindexProjectFile, removeProjectFile, updateProjectFile, } from "../services/project-files.js";
|
|
38
38
|
import { documentText, searchProject } from "../services/project-index.js";
|
|
39
39
|
import { createProject, deleteProject, listProjects, maskedServers, ownedProject, projectLimit, projectPatchSchema, updateProject, } from "../services/projects.js";
|
|
40
|
-
import { listRuns, startTestRun } from "../services/runs.js";
|
|
40
|
+
import { listResults, listRuns, startTestRun } from "../services/runs.js";
|
|
41
|
+
import { addTableRow, createPage, createTable, deletePage, deleteTable, deleteTableRows, getPage, getTable, listSpaceData, updatePage, updateTable, updateTableRow, } from "../services/space-data.js";
|
|
41
42
|
import { draftWidgetPreview } from "../services/widgets.js";
|
|
42
43
|
import { createWizard, deleteWizard, duplicateWizard, listWizards, ownedWizard, rotateShareLink, updateWizardSettings, wizardMessages, wizardState, writableWizard, writeDraft, } from "../services/wizards.js";
|
|
43
44
|
import { readSetting, writeSetting } from "../settings.js";
|
|
@@ -447,6 +448,93 @@ export const studio = new Hono()
|
|
|
447
448
|
const project = await ownedProject(c.get("user").id, c.req.param("id"));
|
|
448
449
|
await removeConnector(project.id, c.req.param("connectorId"));
|
|
449
450
|
return c.json({ ok: true });
|
|
451
|
+
})
|
|
452
|
+
// The runs of the space's wizards that reached their result: of one wizard, or of all.
|
|
453
|
+
.get("/projects/:id/results", async (c) => {
|
|
454
|
+
const project = await ownedProject(c.get("user").id, c.req.param("id"));
|
|
455
|
+
const query = z
|
|
456
|
+
.object({
|
|
457
|
+
wizard: z.string().optional(),
|
|
458
|
+
q: z.string().max(200).optional(),
|
|
459
|
+
mode: z.enum(["live", "test"]).optional(),
|
|
460
|
+
days: z.coerce.number().int().min(1).max(3650).optional(),
|
|
461
|
+
})
|
|
462
|
+
.parse(c.req.query());
|
|
463
|
+
return c.json(await listResults(project.id, {
|
|
464
|
+
wizardId: query.wizard || undefined,
|
|
465
|
+
q: query.q,
|
|
466
|
+
mode: query.mode,
|
|
467
|
+
days: query.days,
|
|
468
|
+
}));
|
|
469
|
+
})
|
|
470
|
+
// --- the space's tables and pages --------------------------------------------
|
|
471
|
+
.get("/projects/:id/data", async (c) => c.json(await listSpaceData(c.get("user").id, c.req.param("id"))))
|
|
472
|
+
.post("/projects/:id/tables", async (c) => {
|
|
473
|
+
const body = z
|
|
474
|
+
.object({
|
|
475
|
+
title: z.string().max(120),
|
|
476
|
+
wizardId: z.string().nullish(),
|
|
477
|
+
columns: z.unknown().optional(),
|
|
478
|
+
})
|
|
479
|
+
.parse(await c.req.json());
|
|
480
|
+
return c.json(await createTable(c.get("user").id, c.req.param("id"), body));
|
|
481
|
+
})
|
|
482
|
+
.get("/tables/:id", async (c) => c.json(await getTable(c.get("user").id, c.req.param("id"))))
|
|
483
|
+
.patch("/tables/:id", async (c) => {
|
|
484
|
+
const body = z
|
|
485
|
+
.object({
|
|
486
|
+
title: z.string().max(120).optional(),
|
|
487
|
+
wizardId: z.string().nullish(),
|
|
488
|
+
columns: z.unknown().optional(),
|
|
489
|
+
})
|
|
490
|
+
.parse(await c.req.json());
|
|
491
|
+
await updateTable(c.get("user").id, c.req.param("id"), body);
|
|
492
|
+
return c.json({ ok: true });
|
|
493
|
+
})
|
|
494
|
+
.delete("/tables/:id", async (c) => {
|
|
495
|
+
await deleteTable(c.get("user").id, c.req.param("id"));
|
|
496
|
+
return c.json({ ok: true });
|
|
497
|
+
})
|
|
498
|
+
.post("/tables/:id/rows", async (c) => {
|
|
499
|
+
const body = z
|
|
500
|
+
.object({ cells: z.record(z.string(), z.unknown()).optional() })
|
|
501
|
+
.parse(await c.req.json());
|
|
502
|
+
return c.json(await addTableRow(c.get("user").id, c.req.param("id"), body.cells));
|
|
503
|
+
})
|
|
504
|
+
.patch("/tables/:id/rows/:rowId", async (c) => {
|
|
505
|
+
const body = z.object({ cells: z.record(z.string(), z.unknown()) }).parse(await c.req.json());
|
|
506
|
+
return c.json(await updateTableRow(c.get("user").id, c.req.param("id"), c.req.param("rowId"), body.cells));
|
|
507
|
+
})
|
|
508
|
+
.post("/tables/:id/rows/delete", async (c) => {
|
|
509
|
+
const body = z.object({ ids: z.array(z.string()).max(5000) }).parse(await c.req.json());
|
|
510
|
+
await deleteTableRows(c.get("user").id, c.req.param("id"), body.ids);
|
|
511
|
+
return c.json({ ok: true });
|
|
512
|
+
})
|
|
513
|
+
.post("/projects/:id/pages", async (c) => {
|
|
514
|
+
const body = z
|
|
515
|
+
.object({
|
|
516
|
+
title: z.string().max(200),
|
|
517
|
+
wizardId: z.string().nullish(),
|
|
518
|
+
markdown: z.string().optional(),
|
|
519
|
+
})
|
|
520
|
+
.parse(await c.req.json());
|
|
521
|
+
return c.json(await createPage(c.get("user").id, c.req.param("id"), body));
|
|
522
|
+
})
|
|
523
|
+
.get("/pages/:id", async (c) => c.json(await getPage(c.get("user").id, c.req.param("id"))))
|
|
524
|
+
.patch("/pages/:id", async (c) => {
|
|
525
|
+
const body = z
|
|
526
|
+
.object({
|
|
527
|
+
title: z.string().max(200).optional(),
|
|
528
|
+
wizardId: z.string().nullish(),
|
|
529
|
+
markdown: z.string().optional(),
|
|
530
|
+
})
|
|
531
|
+
.parse(await c.req.json());
|
|
532
|
+
await updatePage(c.get("user").id, c.req.param("id"), body);
|
|
533
|
+
return c.json({ ok: true });
|
|
534
|
+
})
|
|
535
|
+
.delete("/pages/:id", async (c) => {
|
|
536
|
+
await deletePage(c.get("user").id, c.req.param("id"));
|
|
537
|
+
return c.json({ ok: true });
|
|
450
538
|
})
|
|
451
539
|
// --- wizards ---------------------------------------------------------------
|
|
452
540
|
.get("/projects/:id/wizards", async (c) => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { and, asc, eq, inArray, isNotNull, isNull, like } from "drizzle-orm";
|
|
2
3
|
import { accountToken, linkedAccount } from "../auth/account.js";
|
|
3
4
|
import { control, controlDb, db, schema } from "../db/client.js";
|
|
4
5
|
import { env } from "../env.js";
|
|
@@ -94,10 +95,61 @@ async function spaceOf(userId, projectId) {
|
|
|
94
95
|
: null,
|
|
95
96
|
};
|
|
96
97
|
}
|
|
98
|
+
/** The space's own tables and pages, as they are sent: not what a wizard's runs keep. */
|
|
99
|
+
async function spaceDataOf(projectId) {
|
|
100
|
+
const own = (table) => and(eq(table.projectId, projectId), isNull(table.wizardId));
|
|
101
|
+
const tables = await db.query.spaceTable.findMany({
|
|
102
|
+
where: own(schema.spaceTable),
|
|
103
|
+
orderBy: [asc(schema.spaceTable.id)],
|
|
104
|
+
});
|
|
105
|
+
const rows = tables.length
|
|
106
|
+
? await db.query.spaceTableRow.findMany({
|
|
107
|
+
where: inArray(schema.spaceTableRow.tableId, tables.map((t) => t.id)),
|
|
108
|
+
orderBy: [asc(schema.spaceTableRow.createdAt), asc(schema.spaceTableRow.id)],
|
|
109
|
+
})
|
|
110
|
+
: [];
|
|
111
|
+
const pages = await db.query.spacePage.findMany({
|
|
112
|
+
where: own(schema.spacePage),
|
|
113
|
+
orderBy: [asc(schema.spacePage.id)],
|
|
114
|
+
});
|
|
115
|
+
return {
|
|
116
|
+
tables: tables.map((t) => ({
|
|
117
|
+
id: t.id,
|
|
118
|
+
title: t.title,
|
|
119
|
+
columns: t.columns,
|
|
120
|
+
rows: rows.filter((r) => r.tableId === t.id).map((r) => ({ id: r.id, cells: r.cells })),
|
|
121
|
+
})),
|
|
122
|
+
pages: pages.map((p) => ({ id: p.id, title: p.title, markdown: p.markdown })),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Sends the space's own tables and pages along with a wizard, when they changed since they last
|
|
127
|
+
* arrived. A sending that fails is tried with the next wizard; the wizard is there either way.
|
|
128
|
+
*/
|
|
129
|
+
async function syncSpaceDataToCloud(projectId) {
|
|
130
|
+
const data = await spaceDataOf(projectId);
|
|
131
|
+
const hash = createHash("sha256").update(JSON.stringify(data)).digest("hex");
|
|
132
|
+
const sent = `cloud-data:${projectId}`;
|
|
133
|
+
if ((await readSetting(sent)) === hash) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const answer = await call("PUT", `/spaces/${projectId}/data`, data, 120_000);
|
|
138
|
+
if (answer.ok) {
|
|
139
|
+
await writeSetting(sent, hash);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
console.error("[cloud] the space's data was not taken:", answer.status, answer.body);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
console.error("[cloud] sending the space's data failed:", err);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
97
149
|
/**
|
|
98
150
|
* Sends a wizard's published version to the cloud: the same wizard again updates its copy
|
|
99
151
|
* there, whose link stays. What came back is kept; a try that failed is kept as such beside
|
|
100
|
-
* the copy from before.
|
|
152
|
+
* the copy from before. The space's own tables and pages follow it.
|
|
101
153
|
*/
|
|
102
154
|
export async function syncToCloud(userId, wizardId) {
|
|
103
155
|
const w = await ownedWizard(userId, wizardId);
|
|
@@ -136,6 +188,7 @@ export async function syncToCloud(userId, wizardId) {
|
|
|
136
188
|
error: null,
|
|
137
189
|
};
|
|
138
190
|
await writeSetting(key(w.id), state);
|
|
191
|
+
await syncSpaceDataToCloud(w.projectId);
|
|
139
192
|
return state;
|
|
140
193
|
}
|
|
141
194
|
catch (err) {
|
|
@@ -253,9 +306,16 @@ export async function retryFailedSends(userId) {
|
|
|
253
306
|
/** The account was unlinked: what this runtime kept of its cloud belongs to no account now. */
|
|
254
307
|
export async function forgetCloud() {
|
|
255
308
|
await controlDb.delete(control.setting).where(like(control.setting.key, "cloud:%"));
|
|
309
|
+
await forgetSentData();
|
|
310
|
+
}
|
|
311
|
+
/** The space's data counts as not sent: the next wizard takes it along again. */
|
|
312
|
+
async function forgetSentData() {
|
|
313
|
+
await controlDb.delete(control.setting).where(like(control.setting.key, "cloud-data:%"));
|
|
256
314
|
}
|
|
257
315
|
/** Sends every published wizard: once after an account was linked. */
|
|
258
316
|
export async function syncAllToCloud(userId) {
|
|
317
|
+
// A cloud that was linked anew, or whose spaces were replaced, has none of it.
|
|
318
|
+
await forgetSentData();
|
|
259
319
|
const wizards = await db
|
|
260
320
|
.select({ id: schema.wizard.id })
|
|
261
321
|
.from(schema.wizard)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { formatsFor } from "@engenty-wizards/shared/definition";
|
|
2
|
-
import { and, count, desc, eq, inArray, or } from "drizzle-orm";
|
|
2
|
+
import { and, count, desc, eq, gte, inArray, max, or, sql } from "drizzle-orm";
|
|
3
3
|
import { canSpend, MICROS_PER_CREDIT } from "../credits/credits.js";
|
|
4
4
|
import { db, schema } from "../db/client.js";
|
|
5
5
|
import { answerAsk, unattended } from "../engine/asks.js";
|
|
@@ -384,3 +384,71 @@ export async function listRuns(userId, wizardId) {
|
|
|
384
384
|
stepTitle: r.definition.steps.find((s) => s.id === r.cursor)?.title ?? null,
|
|
385
385
|
}));
|
|
386
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* The results of a space: its wizards' runs that reached their result, newest first, narrowed by
|
|
389
|
+
* `filter`; and each wizard with how many it has and when its last one came, the latest first.
|
|
390
|
+
*/
|
|
391
|
+
export async function listResults(projectId, filter = {}) {
|
|
392
|
+
const wizards = await db
|
|
393
|
+
.select({
|
|
394
|
+
id: schema.wizard.id,
|
|
395
|
+
title: schema.wizard.title,
|
|
396
|
+
results: count(schema.run.id),
|
|
397
|
+
lastAt: max(schema.run.updatedAt),
|
|
398
|
+
})
|
|
399
|
+
.from(schema.wizard)
|
|
400
|
+
.leftJoin(schema.run, and(eq(schema.run.wizardId, schema.wizard.id), eq(schema.run.status, "done")))
|
|
401
|
+
.where(and(eq(schema.wizard.projectId, projectId), eq(schema.wizard.tenantId, currentTenant())))
|
|
402
|
+
.groupBy(schema.wizard.id)
|
|
403
|
+
// Wizards without a result last: SQLite sorts null below every time.
|
|
404
|
+
.orderBy(desc(max(schema.run.updatedAt)), desc(schema.wizard.updatedAt));
|
|
405
|
+
const q = filter.q?.trim().toLowerCase();
|
|
406
|
+
const like = q ? `%${q.replace(/[\\%_]/g, (c) => `\\${c}`)}%` : null;
|
|
407
|
+
const titled = like
|
|
408
|
+
? new Set(wizards.filter((w) => w.title.toLowerCase().includes(q)).map((w) => w.id))
|
|
409
|
+
: null;
|
|
410
|
+
const ids = wizards.map((w) => w.id).filter((id) => !filter.wizardId || id === filter.wizardId);
|
|
411
|
+
const rows = ids.length
|
|
412
|
+
? await db
|
|
413
|
+
.select({
|
|
414
|
+
id: schema.run.id,
|
|
415
|
+
wizardId: schema.run.wizardId,
|
|
416
|
+
mode: schema.run.mode,
|
|
417
|
+
version: schema.run.version,
|
|
418
|
+
costMicros: schema.run.costMicros,
|
|
419
|
+
updatedAt: schema.run.updatedAt,
|
|
420
|
+
})
|
|
421
|
+
.from(schema.run)
|
|
422
|
+
.where(and(inArray(schema.run.wizardId, ids), eq(schema.run.status, "done"), filter.mode ? eq(schema.run.mode, filter.mode) : undefined, filter.days
|
|
423
|
+
? gte(schema.run.updatedAt, new Date(Date.now() - filter.days * 86_400_000))
|
|
424
|
+
: undefined, like
|
|
425
|
+
? or(titled?.size ? inArray(schema.run.wizardId, [...titled]) : undefined, sql `lower(${schema.run.state}) like ${like} escape '\\'`)
|
|
426
|
+
: undefined))
|
|
427
|
+
.orderBy(desc(schema.run.updatedAt))
|
|
428
|
+
.limit(100)
|
|
429
|
+
: [];
|
|
430
|
+
const assetCounts = rows.length
|
|
431
|
+
? await db
|
|
432
|
+
.select({ runId: schema.asset.runId, n: count() })
|
|
433
|
+
.from(schema.asset)
|
|
434
|
+
.where(inArray(schema.asset.runId, rows.map((r) => r.id)))
|
|
435
|
+
.groupBy(schema.asset.runId)
|
|
436
|
+
: [];
|
|
437
|
+
return {
|
|
438
|
+
wizards: wizards.map((w) => ({
|
|
439
|
+
id: w.id,
|
|
440
|
+
title: w.title,
|
|
441
|
+
results: w.results,
|
|
442
|
+
lastAt: w.lastAt ? new Date(w.lastAt).toISOString() : null,
|
|
443
|
+
})),
|
|
444
|
+
runs: rows.map((r) => ({
|
|
445
|
+
id: r.id,
|
|
446
|
+
wizardId: r.wizardId,
|
|
447
|
+
mode: r.mode,
|
|
448
|
+
version: r.version,
|
|
449
|
+
doneAt: r.updatedAt.toISOString(),
|
|
450
|
+
credits: Math.ceil(r.costMicros / MICROS_PER_CREDIT),
|
|
451
|
+
assets: assetCounts.find((a) => a.runId === r.id)?.n ?? 0,
|
|
452
|
+
})),
|
|
453
|
+
};
|
|
454
|
+
}
|