@contractspec/lib.runtime-sandbox 0.12.0 → 0.14.0

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.
Files changed (59) hide show
  1. package/dist/adapters/pglite/adapter.d.ts +27 -0
  2. package/dist/adapters/pglite/adapter.d.ts.map +1 -0
  3. package/dist/adapters/pglite/index.d.ts +2 -0
  4. package/dist/adapters/pglite/index.d.ts.map +1 -0
  5. package/dist/browser/index.js +2443 -0
  6. package/dist/index.d.ts +10 -8
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +2436 -22
  9. package/dist/node/index.js +2438 -0
  10. package/dist/ports/database.port.d.ts +49 -53
  11. package/dist/ports/database.port.d.ts.map +1 -1
  12. package/dist/ports/index.d.ts +2 -0
  13. package/dist/ports/index.d.ts.map +1 -0
  14. package/dist/types/database.types.d.ts +21 -24
  15. package/dist/types/database.types.d.ts.map +1 -1
  16. package/dist/types/index.d.ts +2 -0
  17. package/dist/types/index.d.ts.map +1 -0
  18. package/dist/web/database/migrations.d.ts +8 -6
  19. package/dist/web/database/migrations.d.ts.map +1 -1
  20. package/dist/web/database/schema.d.ts +7297 -7302
  21. package/dist/web/database/schema.d.ts.map +1 -1
  22. package/dist/web/events/local-pubsub.d.ts +5 -7
  23. package/dist/web/events/local-pubsub.d.ts.map +1 -1
  24. package/dist/web/graphql/local-client.d.ts +13 -17
  25. package/dist/web/graphql/local-client.d.ts.map +1 -1
  26. package/dist/web/index.d.ts +7 -14
  27. package/dist/web/index.d.ts.map +1 -1
  28. package/dist/web/runtime/seeders/index.d.ts +19 -0
  29. package/dist/web/runtime/seeders/index.d.ts.map +1 -0
  30. package/dist/web/runtime/services.d.ts +51 -50
  31. package/dist/web/runtime/services.d.ts.map +1 -1
  32. package/dist/web/storage/indexeddb.d.ts +16 -19
  33. package/dist/web/storage/indexeddb.d.ts.map +1 -1
  34. package/dist/web/utils/id.d.ts +1 -4
  35. package/dist/web/utils/id.d.ts.map +1 -1
  36. package/package.json +18 -16
  37. package/dist/_virtual/_rolldown/runtime.js +0 -18
  38. package/dist/adapters/pglite/adapter.js +0 -97
  39. package/dist/adapters/pglite/adapter.js.map +0 -1
  40. package/dist/adapters/pglite/index.js +0 -3
  41. package/dist/index.js.map +0 -1
  42. package/dist/web/database/migrations.js +0 -746
  43. package/dist/web/database/migrations.js.map +0 -1
  44. package/dist/web/database/schema.js +0 -528
  45. package/dist/web/database/schema.js.map +0 -1
  46. package/dist/web/events/local-pubsub.js +0 -24
  47. package/dist/web/events/local-pubsub.js.map +0 -1
  48. package/dist/web/graphql/local-client.js +0 -536
  49. package/dist/web/graphql/local-client.js.map +0 -1
  50. package/dist/web/index.js +0 -68
  51. package/dist/web/index.js.map +0 -1
  52. package/dist/web/runtime/seeders/index.js +0 -358
  53. package/dist/web/runtime/seeders/index.js.map +0 -1
  54. package/dist/web/runtime/services.js +0 -80
  55. package/dist/web/runtime/services.js.map +0 -1
  56. package/dist/web/storage/indexeddb.js +0 -85
  57. package/dist/web/storage/indexeddb.js.map +0 -1
  58. package/dist/web/utils/id.js +0 -9
  59. package/dist/web/utils/id.js.map +0 -1
@@ -0,0 +1,2443 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, {
5
+ get: all[name],
6
+ enumerable: true,
7
+ configurable: true,
8
+ set: (newValue) => all[name] = () => newValue
9
+ });
10
+ };
11
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
12
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
13
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
14
+ }) : x)(function(x) {
15
+ if (typeof require !== "undefined")
16
+ return require.apply(this, arguments);
17
+ throw Error('Dynamic require of "' + x + '" is not supported');
18
+ });
19
+
20
+ // src/web/runtime/seeders/index.ts
21
+ var exports_seeders = {};
22
+ __export(exports_seeders, {
23
+ seedTemplate: () => seedTemplate
24
+ });
25
+ async function seedTemplate(params) {
26
+ const { templateId, projectId, db } = params;
27
+ switch (templateId) {
28
+ case "todos-app":
29
+ return seedTodos({ projectId, db });
30
+ case "messaging-app":
31
+ return seedMessaging({ projectId, db });
32
+ case "recipe-app-i18n":
33
+ return seedRecipes({ projectId, db });
34
+ case "crm-pipeline":
35
+ return seedCrmPipeline({ projectId, db });
36
+ case "saas-boilerplate":
37
+ return seedSaasBoilerplate({ projectId, db });
38
+ case "agent-console":
39
+ return seedAgentConsole({ projectId, db });
40
+ case "workflow-system":
41
+ return seedWorkflowSystem({ projectId, db });
42
+ case "marketplace":
43
+ return seedMarketplace({ projectId, db });
44
+ case "integration-hub":
45
+ return seedIntegrationHub({ projectId, db });
46
+ case "analytics-dashboard":
47
+ return seedAnalyticsDashboard({ projectId, db });
48
+ case "policy-safe-knowledge-assistant":
49
+ return seedPolicyKnowledgeAssistant({ projectId, db });
50
+ default:
51
+ return;
52
+ }
53
+ }
54
+ async function seedTodos(params) {
55
+ const { projectId, db } = params;
56
+ const existing = await db.query(`SELECT COUNT(*) as count FROM template_task WHERE "projectId" = $1`, [projectId]);
57
+ if (existing.rows[0]?.count > 0)
58
+ return;
59
+ const workCategoryId = "todo_category_ops";
60
+ const homeCategoryId = "todo_category_home";
61
+ await db.execute(`INSERT INTO template_task_category (id, "projectId", name, color) VALUES ($1, $2, $3, $4)`, [workCategoryId, projectId, "Operations", "#8b5cf6"]);
62
+ await db.execute(`INSERT INTO template_task_category (id, "projectId", name, color) VALUES ($1, $2, $3, $4)`, [homeCategoryId, projectId, "Home", "#f472b6"]);
63
+ const tasks = [
64
+ {
65
+ id: "todo_task_1",
66
+ title: "Review intent signals",
67
+ description: "Scan yesterday's signals and flag the ones to promote.",
68
+ categoryId: workCategoryId,
69
+ priority: "HIGH"
70
+ },
71
+ {
72
+ id: "todo_task_2",
73
+ title: "Schedule studio walkthrough",
74
+ description: "Prep the sandbox before tomorrow's ceremony.",
75
+ categoryId: workCategoryId,
76
+ priority: "MEDIUM"
77
+ },
78
+ {
79
+ id: "todo_task_3",
80
+ title: "Collect testimonials",
81
+ description: "Ask last week's pilot crew for quotes.",
82
+ categoryId: homeCategoryId,
83
+ priority: "LOW"
84
+ }
85
+ ];
86
+ for (const task of tasks) {
87
+ await db.execute(`INSERT INTO template_task (id, "projectId", "categoryId", title, description, completed, priority, tags)
88
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, [
89
+ task.id,
90
+ projectId,
91
+ task.categoryId,
92
+ task.title,
93
+ task.description,
94
+ 0,
95
+ task.priority,
96
+ JSON.stringify(["demo"])
97
+ ]);
98
+ }
99
+ }
100
+ async function seedMessaging(params) {
101
+ const { projectId, db } = params;
102
+ const existing = await db.query(`SELECT COUNT(*) as count FROM template_conversation WHERE "projectId" = $1`, [projectId]);
103
+ if (existing.rows[0]?.count > 0)
104
+ return;
105
+ const conversationId = "conv_demo_1";
106
+ const now = new Date().toISOString();
107
+ await db.execute(`INSERT INTO template_conversation (id, "projectId", name, "isGroup", "avatarUrl", "updatedAt")
108
+ VALUES ($1, $2, $3, $4, $5, $6)`, [conversationId, projectId, "Team Chat", 1, null, now]);
109
+ await db.execute(`INSERT INTO template_conversation_participant (id, "conversationId", "projectId", "userId", "displayName", role, "joinedAt")
110
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`, ["part_1", conversationId, projectId, "user_alice", "Alice", "member", now]);
111
+ await db.execute(`INSERT INTO template_conversation_participant (id, "conversationId", "projectId", "userId", "displayName", role, "joinedAt")
112
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`, ["part_2", conversationId, projectId, "user_bob", "Bob", "member", now]);
113
+ await db.execute(`INSERT INTO template_message (id, "conversationId", "projectId", "senderId", "senderName", content, attachments, status, "createdAt", "updatedAt")
114
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, [
115
+ "msg_1",
116
+ conversationId,
117
+ projectId,
118
+ "user_alice",
119
+ "Alice",
120
+ "Hey team! Ready for the demo?",
121
+ JSON.stringify([]),
122
+ "DELIVERED",
123
+ now,
124
+ now
125
+ ]);
126
+ }
127
+ async function seedRecipes(params) {
128
+ const { projectId, db } = params;
129
+ const existing = await db.query(`SELECT COUNT(*) as count FROM template_recipe WHERE "projectId" = $1`, [projectId]);
130
+ if (existing.rows[0]?.count > 0)
131
+ return;
132
+ await db.execute(`INSERT INTO template_recipe_category (id, "nameEn", "nameFr", icon) VALUES ($1, $2, $3, $4)`, ["cat_main", "Main Courses", "Plats Principaux", "\uD83C\uDF7D️"]);
133
+ const recipeId = "recipe_1";
134
+ await db.execute(`INSERT INTO template_recipe (id, "projectId", "categoryId", "slugEn", "slugFr", "nameEn", "nameFr", "descriptionEn", "descriptionFr", "prepTimeMinutes", "cookTimeMinutes", servings, "isFavorite")
135
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`, [
136
+ recipeId,
137
+ projectId,
138
+ "cat_main",
139
+ "grilled-salmon",
140
+ "saumon-grille",
141
+ "Grilled Salmon",
142
+ "Saumon Grillé",
143
+ "Delicious grilled salmon with lemon",
144
+ "Délicieux saumon grillé au citron",
145
+ 10,
146
+ 15,
147
+ 4,
148
+ 0
149
+ ]);
150
+ await db.execute(`INSERT INTO template_recipe_ingredient (id, "recipeId", "nameEn", "nameFr", quantity, ordering)
151
+ VALUES ($1, $2, $3, $4, $5, $6)`, ["ing_1", recipeId, "Salmon fillet", "Filet de saumon", "4 pieces", 1]);
152
+ await db.execute(`INSERT INTO template_recipe_ingredient (id, "recipeId", "nameEn", "nameFr", quantity, ordering)
153
+ VALUES ($1, $2, $3, $4, $5, $6)`, ["ing_2", recipeId, "Lemon", "Citron", "1 whole", 2]);
154
+ await db.execute(`INSERT INTO template_recipe_instruction (id, "recipeId", "contentEn", "contentFr", ordering)
155
+ VALUES ($1, $2, $3, $4, $5)`, [
156
+ "inst_1",
157
+ recipeId,
158
+ "Preheat grill to medium-high heat.",
159
+ "Préchauffer le grill à feu moyen-vif.",
160
+ 1
161
+ ]);
162
+ await db.execute(`INSERT INTO template_recipe_instruction (id, "recipeId", "contentEn", "contentFr", ordering)
163
+ VALUES ($1, $2, $3, $4, $5)`, [
164
+ "inst_2",
165
+ recipeId,
166
+ "Season salmon and grill for 4-5 minutes per side.",
167
+ "Assaisonner le saumon et griller 4-5 minutes de chaque côté.",
168
+ 2
169
+ ]);
170
+ }
171
+ async function seedCrmPipeline(params) {
172
+ const { projectId, db } = params;
173
+ const existing = await db.query(`SELECT COUNT(*) as count FROM crm_pipeline WHERE "projectId" = $1`, [projectId]);
174
+ if (existing.rows[0]?.count > 0)
175
+ return;
176
+ const pipelineId = "pipeline_sales";
177
+ await db.execute(`INSERT INTO crm_pipeline (id, "projectId", name) VALUES ($1, $2, $3)`, [pipelineId, projectId, "Sales Pipeline"]);
178
+ const stages = [
179
+ { id: "stage_lead", name: "Lead", position: 1 },
180
+ { id: "stage_contact", name: "Contact Made", position: 2 },
181
+ { id: "stage_proposal", name: "Proposal", position: 3 },
182
+ { id: "stage_negotiation", name: "Negotiation", position: 4 },
183
+ { id: "stage_closed", name: "Closed", position: 5 }
184
+ ];
185
+ for (const stage of stages) {
186
+ await db.execute(`INSERT INTO crm_stage (id, "pipelineId", name, position) VALUES ($1, $2, $3, $4)`, [stage.id, pipelineId, stage.name, stage.position]);
187
+ }
188
+ }
189
+ async function seedSaasBoilerplate(params) {
190
+ const { projectId, db } = params;
191
+ const existing = await db.query(`SELECT COUNT(*) as count FROM saas_project WHERE "projectId" = $1`, [projectId]);
192
+ if (existing.rows[0]?.count > 0)
193
+ return;
194
+ await db.execute(`INSERT INTO saas_project (id, "projectId", "organizationId", name, description, status, tier)
195
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`, [
196
+ "saas_proj_1",
197
+ projectId,
198
+ "org_demo",
199
+ "Demo Project",
200
+ "A demo SaaS project",
201
+ "ACTIVE",
202
+ "PRO"
203
+ ]);
204
+ }
205
+ async function seedAgentConsole(params) {
206
+ const { projectId, db } = params;
207
+ const existing = await db.query(`SELECT COUNT(*) as count FROM agent_definition WHERE "projectId" = $1`, [projectId]);
208
+ if (existing.rows[0]?.count > 0)
209
+ return;
210
+ await db.execute(`INSERT INTO agent_definition (id, "projectId", "organizationId", name, description, "modelProvider", "modelName", status)
211
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, [
212
+ "agent_1",
213
+ projectId,
214
+ "org_demo",
215
+ "Demo Agent",
216
+ "A demo AI agent",
217
+ "openai",
218
+ "gpt-4",
219
+ "ACTIVE"
220
+ ]);
221
+ }
222
+ async function seedWorkflowSystem(params) {
223
+ const { projectId, db } = params;
224
+ const existing = await db.query(`SELECT COUNT(*) as count FROM workflow_definition WHERE "projectId" = $1`, [projectId]);
225
+ if (existing.rows[0]?.count > 0)
226
+ return;
227
+ await db.execute(`INSERT INTO workflow_definition (id, "projectId", "organizationId", name, description, type, status)
228
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`, [
229
+ "wf_1",
230
+ projectId,
231
+ "org_demo",
232
+ "Approval Workflow",
233
+ "Demo approval workflow",
234
+ "APPROVAL",
235
+ "ACTIVE"
236
+ ]);
237
+ }
238
+ async function seedMarketplace(params) {
239
+ const { projectId, db } = params;
240
+ const existing = await db.query(`SELECT COUNT(*) as count FROM marketplace_store WHERE "projectId" = $1`, [projectId]);
241
+ if (existing.rows[0]?.count > 0)
242
+ return;
243
+ await db.execute(`INSERT INTO marketplace_store (id, "projectId", "organizationId", name, description, status)
244
+ VALUES ($1, $2, $3, $4, $5, $6)`, ["store_1", projectId, "org_demo", "Demo Store", "A demo store", "ACTIVE"]);
245
+ }
246
+ async function seedIntegrationHub(params) {
247
+ const { projectId, db } = params;
248
+ const existing = await db.query(`SELECT COUNT(*) as count FROM integration WHERE "projectId" = $1`, [projectId]);
249
+ if (existing.rows[0]?.count > 0)
250
+ return;
251
+ await db.execute(`INSERT INTO integration (id, "projectId", "organizationId", name, description, type, status)
252
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`, [
253
+ "int_1",
254
+ projectId,
255
+ "org_demo",
256
+ "Salesforce",
257
+ "Salesforce CRM integration",
258
+ "CRM",
259
+ "ACTIVE"
260
+ ]);
261
+ }
262
+ async function seedAnalyticsDashboard(params) {
263
+ const { projectId, db } = params;
264
+ const existing = await db.query(`SELECT COUNT(*) as count FROM analytics_dashboard WHERE "projectId" = $1`, [projectId]);
265
+ if (existing.rows[0]?.count > 0)
266
+ return;
267
+ await db.execute(`INSERT INTO analytics_dashboard (id, "projectId", "organizationId", name, slug, description, status)
268
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`, [
269
+ "dash_1",
270
+ projectId,
271
+ "org_demo",
272
+ "Sales Overview",
273
+ "sales-overview",
274
+ "Sales performance dashboard",
275
+ "PUBLISHED"
276
+ ]);
277
+ }
278
+ async function seedPolicyKnowledgeAssistant(params) {
279
+ const { projectId, db } = params;
280
+ const existing = await db.query(`SELECT COUNT(*) as count FROM psa_user_context WHERE "projectId" = $1`, [projectId]);
281
+ if (existing.rows[0]?.count > 0)
282
+ return;
283
+ await db.execute(`INSERT INTO psa_user_context ("projectId", locale, jurisdiction, "allowedScope")
284
+ VALUES ($1, $2, $3, $4)`, [projectId, "en-GB", "EU", "education_only"]);
285
+ }
286
+
287
+ // src/adapters/pglite/adapter.ts
288
+ import { PGlite } from "@electric-sql/pglite";
289
+
290
+ class PGLiteDatabaseAdapter {
291
+ client = null;
292
+ initialized = false;
293
+ async init(options) {
294
+ if (this.initialized)
295
+ return;
296
+ const dataDir = options?.dataDir;
297
+ this.client = dataDir ? new PGlite(`idb://${dataDir}`) : new PGlite;
298
+ await this.client.waitReady;
299
+ this.initialized = true;
300
+ }
301
+ async close() {
302
+ if (this.client) {
303
+ await this.client.close();
304
+ this.client = null;
305
+ this.initialized = false;
306
+ }
307
+ }
308
+ isInitialized() {
309
+ return this.initialized;
310
+ }
311
+ async query(sql, params) {
312
+ const client = this.getClient();
313
+ const normalizedParams = this.normalizeParams(params);
314
+ const result = await client.query(sql, normalizedParams);
315
+ return {
316
+ rows: result.rows,
317
+ rowCount: result.rows.length
318
+ };
319
+ }
320
+ async execute(sql, params) {
321
+ const client = this.getClient();
322
+ const normalizedParams = this.normalizeParams(params);
323
+ await client.query(sql, normalizedParams);
324
+ }
325
+ async transaction(callback) {
326
+ const client = this.getClient();
327
+ await client.query("BEGIN");
328
+ try {
329
+ const ctx = {
330
+ execute: async (sql, params) => {
331
+ const normalizedParams = this.normalizeParams(params);
332
+ await client.query(sql, normalizedParams);
333
+ }
334
+ };
335
+ const result = await callback(ctx);
336
+ await client.query("COMMIT");
337
+ return result;
338
+ } catch (error) {
339
+ await client.query("ROLLBACK");
340
+ throw error;
341
+ }
342
+ }
343
+ async migrate(migrations2) {
344
+ const client = this.getClient();
345
+ await client.query(`
346
+ CREATE TABLE IF NOT EXISTS _sandbox_migrations (
347
+ id TEXT PRIMARY KEY,
348
+ applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
349
+ )
350
+ `);
351
+ for (const migration of migrations2) {
352
+ const existing = await client.query("SELECT id FROM _sandbox_migrations WHERE id = $1", [migration.id]);
353
+ if (existing.rows.length === 0) {
354
+ await client.query(migration.sql);
355
+ await client.query("INSERT INTO _sandbox_migrations (id) VALUES ($1)", [
356
+ migration.id
357
+ ]);
358
+ }
359
+ }
360
+ }
361
+ async export() {
362
+ this.getClient();
363
+ return new Uint8Array;
364
+ }
365
+ getClient() {
366
+ if (!this.client || !this.initialized) {
367
+ throw new Error("PGLiteDatabaseAdapter not initialized. Call init() first.");
368
+ }
369
+ return this.client;
370
+ }
371
+ normalizeParams(params) {
372
+ if (!params)
373
+ return [];
374
+ return params.map((value) => {
375
+ if (typeof value === "boolean") {
376
+ return value;
377
+ }
378
+ if (value instanceof Date) {
379
+ return value.toISOString();
380
+ }
381
+ if (value === undefined) {
382
+ return null;
383
+ }
384
+ return value;
385
+ });
386
+ }
387
+ }
388
+ var init_adapter = () => {};
389
+
390
+ // src/adapters/pglite/index.ts
391
+ var exports_pglite = {};
392
+ __export(exports_pglite, {
393
+ PGLiteDatabaseAdapter: () => PGLiteDatabaseAdapter
394
+ });
395
+ var init_pglite = __esm(() => {
396
+ init_adapter();
397
+ });
398
+
399
+ // src/web/index.ts
400
+ var exports_web = {};
401
+ __export(exports_web, {
402
+ workflowStep: () => workflowStep,
403
+ workflowInstance: () => workflowInstance,
404
+ workflowDefinition: () => workflowDefinition,
405
+ workflowApproval: () => workflowApproval,
406
+ templateTaskCategory: () => templateTaskCategory,
407
+ templateTask: () => templateTask,
408
+ templateRecipeInstruction: () => templateRecipeInstruction,
409
+ templateRecipeIngredient: () => templateRecipeIngredient,
410
+ templateRecipeCategory: () => templateRecipeCategory,
411
+ templateRecipe: () => templateRecipe,
412
+ templateMessage: () => templateMessage,
413
+ templateConversationParticipant: () => templateConversationParticipant,
414
+ templateConversation: () => templateConversation,
415
+ saasUsage: () => saasUsage,
416
+ saasSubscription: () => saasSubscription,
417
+ saasProject: () => saasProject,
418
+ psaUserContext: () => psaUserContext,
419
+ psaSnapshot: () => psaSnapshot,
420
+ psaRuleVersion: () => psaRuleVersion,
421
+ psaRule: () => psaRule,
422
+ psaReviewTask: () => psaReviewTask,
423
+ psaChangeCandidate: () => psaChangeCandidate,
424
+ marketplaceStore: () => marketplaceStore,
425
+ marketplaceReview: () => marketplaceReview,
426
+ marketplaceProduct: () => marketplaceProduct,
427
+ marketplacePayout: () => marketplacePayout,
428
+ marketplaceOrderItem: () => marketplaceOrderItem,
429
+ marketplaceOrder: () => marketplaceOrder,
430
+ integrationSyncConfig: () => integrationSyncConfig,
431
+ integrationFieldMapping: () => integrationFieldMapping,
432
+ integrationConnection: () => integrationConnection,
433
+ integration: () => integration,
434
+ generateId: () => generateId,
435
+ crmStage: () => crmStage,
436
+ crmPipeline: () => crmPipeline,
437
+ crmDeal: () => crmDeal,
438
+ crmContact: () => crmContact,
439
+ crmCompany: () => crmCompany,
440
+ analyticsWidget: () => analyticsWidget,
441
+ analyticsQuery: () => analyticsQuery,
442
+ analyticsDashboard: () => analyticsDashboard,
443
+ agentToolAssignment: () => agentToolAssignment,
444
+ agentTool: () => agentTool,
445
+ agentRunStep: () => agentRunStep,
446
+ agentRunLog: () => agentRunLog,
447
+ agentRun: () => agentRun,
448
+ agentDefinition: () => agentDefinition,
449
+ SANDBOX_MIGRATIONS: () => SANDBOX_MIGRATIONS,
450
+ LocalStorageService: () => LocalStorageService,
451
+ LocalRuntimeServices: () => LocalRuntimeServices,
452
+ LocalGraphQLClient: () => LocalGraphQLClient,
453
+ LocalEventBus: () => LocalEventBus
454
+ });
455
+
456
+ // src/web/database/migrations.ts
457
+ var SANDBOX_MIGRATIONS = [
458
+ {
459
+ id: "001_template_task_category",
460
+ sql: `
461
+ CREATE TABLE IF NOT EXISTS template_task_category (
462
+ id TEXT PRIMARY KEY,
463
+ "projectId" TEXT NOT NULL,
464
+ name TEXT NOT NULL,
465
+ color TEXT,
466
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
467
+ "updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
468
+ );
469
+ `
470
+ },
471
+ {
472
+ id: "002_template_task",
473
+ sql: `
474
+ CREATE TABLE IF NOT EXISTS template_task (
475
+ id TEXT PRIMARY KEY,
476
+ "projectId" TEXT NOT NULL,
477
+ "categoryId" TEXT,
478
+ title TEXT NOT NULL,
479
+ description TEXT,
480
+ completed INTEGER DEFAULT 0,
481
+ priority TEXT DEFAULT 'MEDIUM',
482
+ "dueDate" TEXT,
483
+ tags TEXT,
484
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
485
+ "updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
486
+ );
487
+ `
488
+ },
489
+ {
490
+ id: "003_template_conversation",
491
+ sql: `
492
+ CREATE TABLE IF NOT EXISTS template_conversation (
493
+ id TEXT PRIMARY KEY,
494
+ "projectId" TEXT NOT NULL,
495
+ name TEXT,
496
+ "isGroup" INTEGER DEFAULT 0,
497
+ "avatarUrl" TEXT,
498
+ "lastMessageId" TEXT,
499
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
500
+ "updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
501
+ );
502
+ `
503
+ },
504
+ {
505
+ id: "004_template_conversation_participant",
506
+ sql: `
507
+ CREATE TABLE IF NOT EXISTS template_conversation_participant (
508
+ id TEXT PRIMARY KEY,
509
+ "conversationId" TEXT NOT NULL,
510
+ "projectId" TEXT NOT NULL,
511
+ "userId" TEXT NOT NULL,
512
+ "displayName" TEXT,
513
+ role TEXT,
514
+ "joinedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
515
+ "lastReadAt" TEXT
516
+ );
517
+ `
518
+ },
519
+ {
520
+ id: "005_template_message",
521
+ sql: `
522
+ CREATE TABLE IF NOT EXISTS template_message (
523
+ id TEXT PRIMARY KEY,
524
+ "conversationId" TEXT NOT NULL,
525
+ "projectId" TEXT NOT NULL,
526
+ "senderId" TEXT NOT NULL,
527
+ "senderName" TEXT,
528
+ content TEXT NOT NULL,
529
+ attachments TEXT,
530
+ status TEXT DEFAULT 'SENT',
531
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
532
+ "updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
533
+ );
534
+ `
535
+ },
536
+ {
537
+ id: "006_template_recipe_category",
538
+ sql: `
539
+ CREATE TABLE IF NOT EXISTS template_recipe_category (
540
+ id TEXT PRIMARY KEY,
541
+ "nameEn" TEXT NOT NULL,
542
+ "nameFr" TEXT NOT NULL,
543
+ icon TEXT
544
+ );
545
+ `
546
+ },
547
+ {
548
+ id: "007_template_recipe",
549
+ sql: `
550
+ CREATE TABLE IF NOT EXISTS template_recipe (
551
+ id TEXT PRIMARY KEY,
552
+ "projectId" TEXT NOT NULL,
553
+ "categoryId" TEXT,
554
+ "slugEn" TEXT NOT NULL,
555
+ "slugFr" TEXT NOT NULL,
556
+ "nameEn" TEXT NOT NULL,
557
+ "nameFr" TEXT NOT NULL,
558
+ "descriptionEn" TEXT,
559
+ "descriptionFr" TEXT,
560
+ "heroImageUrl" TEXT,
561
+ "prepTimeMinutes" INTEGER,
562
+ "cookTimeMinutes" INTEGER,
563
+ servings INTEGER,
564
+ "isFavorite" INTEGER DEFAULT 0,
565
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
566
+ "updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
567
+ );
568
+ `
569
+ },
570
+ {
571
+ id: "008_template_recipe_ingredient",
572
+ sql: `
573
+ CREATE TABLE IF NOT EXISTS template_recipe_ingredient (
574
+ id TEXT PRIMARY KEY,
575
+ "recipeId" TEXT NOT NULL,
576
+ "nameEn" TEXT NOT NULL,
577
+ "nameFr" TEXT NOT NULL,
578
+ quantity TEXT NOT NULL,
579
+ ordering INTEGER DEFAULT 0
580
+ );
581
+ `
582
+ },
583
+ {
584
+ id: "009_template_recipe_instruction",
585
+ sql: `
586
+ CREATE TABLE IF NOT EXISTS template_recipe_instruction (
587
+ id TEXT PRIMARY KEY,
588
+ "recipeId" TEXT NOT NULL,
589
+ "contentEn" TEXT NOT NULL,
590
+ "contentFr" TEXT NOT NULL,
591
+ ordering INTEGER DEFAULT 0
592
+ );
593
+ `
594
+ },
595
+ {
596
+ id: "010_crm_pipeline",
597
+ sql: `
598
+ CREATE TABLE IF NOT EXISTS crm_pipeline (
599
+ id TEXT PRIMARY KEY,
600
+ "projectId" TEXT NOT NULL,
601
+ name TEXT NOT NULL,
602
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
603
+ "updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
604
+ );
605
+ `
606
+ },
607
+ {
608
+ id: "011_crm_stage",
609
+ sql: `
610
+ CREATE TABLE IF NOT EXISTS crm_stage (
611
+ id TEXT PRIMARY KEY,
612
+ "pipelineId" TEXT NOT NULL,
613
+ name TEXT NOT NULL,
614
+ position INTEGER NOT NULL,
615
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
616
+ );
617
+ `
618
+ },
619
+ {
620
+ id: "012_crm_deal",
621
+ sql: `
622
+ CREATE TABLE IF NOT EXISTS crm_deal (
623
+ id TEXT PRIMARY KEY,
624
+ "projectId" TEXT NOT NULL,
625
+ "pipelineId" TEXT NOT NULL,
626
+ "stageId" TEXT NOT NULL,
627
+ name TEXT NOT NULL,
628
+ value REAL NOT NULL DEFAULT 0,
629
+ currency TEXT DEFAULT 'USD',
630
+ status TEXT DEFAULT 'OPEN',
631
+ "contactId" TEXT,
632
+ "companyId" TEXT,
633
+ "ownerId" TEXT NOT NULL,
634
+ "expectedCloseDate" TEXT,
635
+ "wonSource" TEXT,
636
+ "lostReason" TEXT,
637
+ notes TEXT,
638
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
639
+ "updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
640
+ );
641
+ `
642
+ },
643
+ {
644
+ id: "013_crm_company",
645
+ sql: `
646
+ CREATE TABLE IF NOT EXISTS crm_company (
647
+ id TEXT PRIMARY KEY,
648
+ "projectId" TEXT NOT NULL,
649
+ name TEXT NOT NULL,
650
+ domain TEXT,
651
+ industry TEXT,
652
+ size TEXT,
653
+ website TEXT,
654
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
655
+ "updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
656
+ );
657
+ `
658
+ },
659
+ {
660
+ id: "014_crm_contact",
661
+ sql: `
662
+ CREATE TABLE IF NOT EXISTS crm_contact (
663
+ id TEXT PRIMARY KEY,
664
+ "projectId" TEXT NOT NULL,
665
+ "companyId" TEXT,
666
+ "firstName" TEXT NOT NULL,
667
+ "lastName" TEXT,
668
+ email TEXT,
669
+ phone TEXT,
670
+ title TEXT,
671
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
672
+ "updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
673
+ );
674
+ `
675
+ },
676
+ {
677
+ id: "015_saas_project",
678
+ sql: `
679
+ CREATE TABLE IF NOT EXISTS saas_project (
680
+ id TEXT PRIMARY KEY,
681
+ "projectId" TEXT NOT NULL,
682
+ "organizationId" TEXT NOT NULL,
683
+ name TEXT NOT NULL,
684
+ description TEXT,
685
+ status TEXT DEFAULT 'DRAFT',
686
+ tier TEXT DEFAULT 'FREE',
687
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
688
+ "updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
689
+ );
690
+ `
691
+ },
692
+ {
693
+ id: "016_saas_subscription",
694
+ sql: `
695
+ CREATE TABLE IF NOT EXISTS saas_subscription (
696
+ id TEXT PRIMARY KEY,
697
+ "projectId" TEXT NOT NULL,
698
+ "organizationId" TEXT NOT NULL,
699
+ plan TEXT NOT NULL DEFAULT 'FREE',
700
+ status TEXT DEFAULT 'ACTIVE',
701
+ "billingCycle" TEXT DEFAULT 'MONTHLY',
702
+ "currentPeriodStart" TEXT,
703
+ "currentPeriodEnd" TEXT,
704
+ "cancelAtPeriodEnd" INTEGER DEFAULT 0,
705
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
706
+ "updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
707
+ );
708
+ `
709
+ },
710
+ {
711
+ id: "017_saas_usage",
712
+ sql: `
713
+ CREATE TABLE IF NOT EXISTS saas_usage (
714
+ id TEXT PRIMARY KEY,
715
+ "projectId" TEXT NOT NULL,
716
+ "organizationId" TEXT NOT NULL,
717
+ "metricName" TEXT NOT NULL,
718
+ value REAL NOT NULL DEFAULT 0,
719
+ "periodStart" TEXT NOT NULL,
720
+ "periodEnd" TEXT NOT NULL,
721
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
722
+ );
723
+ `
724
+ },
725
+ {
726
+ id: "018_agent_tool",
727
+ sql: `
728
+ CREATE TABLE IF NOT EXISTS agent_tool (
729
+ id TEXT PRIMARY KEY,
730
+ "projectId" TEXT NOT NULL,
731
+ "organizationId" TEXT NOT NULL,
732
+ name TEXT NOT NULL,
733
+ description TEXT,
734
+ version TEXT DEFAULT '1.0.0',
735
+ category TEXT DEFAULT 'UTILITY',
736
+ status TEXT DEFAULT 'ACTIVE',
737
+ "inputSchema" TEXT,
738
+ "outputSchema" TEXT,
739
+ endpoint TEXT,
740
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
741
+ "updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
742
+ );
743
+ `
744
+ },
745
+ {
746
+ id: "019_agent_definition",
747
+ sql: `
748
+ CREATE TABLE IF NOT EXISTS agent_definition (
749
+ id TEXT PRIMARY KEY,
750
+ "projectId" TEXT NOT NULL,
751
+ "organizationId" TEXT NOT NULL,
752
+ name TEXT NOT NULL,
753
+ description TEXT,
754
+ "modelProvider" TEXT DEFAULT 'openai',
755
+ "modelName" TEXT DEFAULT 'gpt-4',
756
+ "systemPrompt" TEXT,
757
+ temperature REAL DEFAULT 0.7,
758
+ "maxTokens" INTEGER DEFAULT 4096,
759
+ status TEXT DEFAULT 'DRAFT',
760
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
761
+ "updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
762
+ );
763
+ `
764
+ },
765
+ {
766
+ id: "020_agent_tool_assignment",
767
+ sql: `
768
+ CREATE TABLE IF NOT EXISTS agent_tool_assignment (
769
+ id TEXT PRIMARY KEY,
770
+ "agentId" TEXT NOT NULL,
771
+ "toolId" TEXT NOT NULL,
772
+ "assignedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
773
+ );
774
+ `
775
+ },
776
+ {
777
+ id: "021_agent_run",
778
+ sql: `
779
+ CREATE TABLE IF NOT EXISTS agent_run (
780
+ id TEXT PRIMARY KEY,
781
+ "projectId" TEXT NOT NULL,
782
+ "agentId" TEXT NOT NULL,
783
+ status TEXT DEFAULT 'QUEUED',
784
+ input TEXT,
785
+ output TEXT,
786
+ "totalTokens" INTEGER DEFAULT 0,
787
+ "promptTokens" INTEGER DEFAULT 0,
788
+ "completionTokens" INTEGER DEFAULT 0,
789
+ "estimatedCostUsd" REAL DEFAULT 0,
790
+ "durationMs" INTEGER,
791
+ "errorMessage" TEXT,
792
+ "queuedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
793
+ "startedAt" TEXT,
794
+ "completedAt" TEXT
795
+ );
796
+ `
797
+ },
798
+ {
799
+ id: "022_agent_run_step",
800
+ sql: `
801
+ CREATE TABLE IF NOT EXISTS agent_run_step (
802
+ id TEXT PRIMARY KEY,
803
+ "runId" TEXT NOT NULL,
804
+ "stepNumber" INTEGER NOT NULL,
805
+ type TEXT NOT NULL,
806
+ "toolId" TEXT,
807
+ "toolInput" TEXT,
808
+ "toolOutput" TEXT,
809
+ reasoning TEXT,
810
+ "tokensUsed" INTEGER DEFAULT 0,
811
+ "durationMs" INTEGER,
812
+ status TEXT DEFAULT 'PENDING',
813
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
814
+ );
815
+ `
816
+ },
817
+ {
818
+ id: "023_agent_run_log",
819
+ sql: `
820
+ CREATE TABLE IF NOT EXISTS agent_run_log (
821
+ id TEXT PRIMARY KEY,
822
+ "runId" TEXT NOT NULL,
823
+ level TEXT DEFAULT 'INFO',
824
+ message TEXT NOT NULL,
825
+ metadata TEXT,
826
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
827
+ );
828
+ `
829
+ },
830
+ {
831
+ id: "024_workflow_definition",
832
+ sql: `
833
+ CREATE TABLE IF NOT EXISTS workflow_definition (
834
+ id TEXT PRIMARY KEY,
835
+ "projectId" TEXT NOT NULL,
836
+ "organizationId" TEXT NOT NULL,
837
+ name TEXT NOT NULL,
838
+ description TEXT,
839
+ type TEXT DEFAULT 'APPROVAL',
840
+ status TEXT DEFAULT 'DRAFT',
841
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
842
+ "updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
843
+ );
844
+ `
845
+ },
846
+ {
847
+ id: "025_workflow_step",
848
+ sql: `
849
+ CREATE TABLE IF NOT EXISTS workflow_step (
850
+ id TEXT PRIMARY KEY,
851
+ "definitionId" TEXT NOT NULL,
852
+ name TEXT NOT NULL,
853
+ description TEXT,
854
+ "stepOrder" INTEGER NOT NULL,
855
+ type TEXT DEFAULT 'APPROVAL',
856
+ "requiredRoles" TEXT,
857
+ "autoApproveCondition" TEXT,
858
+ "timeoutHours" INTEGER,
859
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
860
+ );
861
+ `
862
+ },
863
+ {
864
+ id: "026_workflow_instance",
865
+ sql: `
866
+ CREATE TABLE IF NOT EXISTS workflow_instance (
867
+ id TEXT PRIMARY KEY,
868
+ "projectId" TEXT NOT NULL,
869
+ "definitionId" TEXT NOT NULL,
870
+ status TEXT DEFAULT 'PENDING',
871
+ "currentStepId" TEXT,
872
+ data TEXT,
873
+ "requestedBy" TEXT NOT NULL,
874
+ "startedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
875
+ "completedAt" TEXT
876
+ );
877
+ `
878
+ },
879
+ {
880
+ id: "027_workflow_approval",
881
+ sql: `
882
+ CREATE TABLE IF NOT EXISTS workflow_approval (
883
+ id TEXT PRIMARY KEY,
884
+ "instanceId" TEXT NOT NULL,
885
+ "stepId" TEXT NOT NULL,
886
+ status TEXT DEFAULT 'PENDING',
887
+ "actorId" TEXT,
888
+ comment TEXT,
889
+ "decidedAt" TEXT,
890
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
891
+ );
892
+ `
893
+ },
894
+ {
895
+ id: "028_marketplace_store",
896
+ sql: `
897
+ CREATE TABLE IF NOT EXISTS marketplace_store (
898
+ id TEXT PRIMARY KEY,
899
+ "projectId" TEXT NOT NULL,
900
+ "organizationId" TEXT NOT NULL,
901
+ name TEXT NOT NULL,
902
+ description TEXT,
903
+ status TEXT DEFAULT 'PENDING',
904
+ rating REAL DEFAULT 0,
905
+ "reviewCount" INTEGER DEFAULT 0,
906
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
907
+ "updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
908
+ );
909
+ `
910
+ },
911
+ {
912
+ id: "029_marketplace_product",
913
+ sql: `
914
+ CREATE TABLE IF NOT EXISTS marketplace_product (
915
+ id TEXT PRIMARY KEY,
916
+ "storeId" TEXT NOT NULL,
917
+ name TEXT NOT NULL,
918
+ description TEXT,
919
+ price REAL NOT NULL DEFAULT 0,
920
+ currency TEXT DEFAULT 'USD',
921
+ status TEXT DEFAULT 'DRAFT',
922
+ stock INTEGER DEFAULT 0,
923
+ category TEXT,
924
+ "imageUrl" TEXT,
925
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
926
+ "updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
927
+ );
928
+ `
929
+ },
930
+ {
931
+ id: "030_marketplace_order",
932
+ sql: `
933
+ CREATE TABLE IF NOT EXISTS marketplace_order (
934
+ id TEXT PRIMARY KEY,
935
+ "projectId" TEXT NOT NULL,
936
+ "storeId" TEXT NOT NULL,
937
+ "customerId" TEXT NOT NULL,
938
+ status TEXT DEFAULT 'PENDING',
939
+ total REAL NOT NULL DEFAULT 0,
940
+ currency TEXT DEFAULT 'USD',
941
+ "shippingAddress" TEXT,
942
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
943
+ "updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
944
+ );
945
+ `
946
+ },
947
+ {
948
+ id: "031_marketplace_order_item",
949
+ sql: `
950
+ CREATE TABLE IF NOT EXISTS marketplace_order_item (
951
+ id TEXT PRIMARY KEY,
952
+ "orderId" TEXT NOT NULL,
953
+ "productId" TEXT NOT NULL,
954
+ quantity INTEGER NOT NULL DEFAULT 1,
955
+ price REAL NOT NULL DEFAULT 0,
956
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
957
+ );
958
+ `
959
+ },
960
+ {
961
+ id: "032_marketplace_payout",
962
+ sql: `
963
+ CREATE TABLE IF NOT EXISTS marketplace_payout (
964
+ id TEXT PRIMARY KEY,
965
+ "storeId" TEXT NOT NULL,
966
+ amount REAL NOT NULL DEFAULT 0,
967
+ currency TEXT DEFAULT 'USD',
968
+ status TEXT DEFAULT 'PENDING',
969
+ "processedAt" TEXT,
970
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
971
+ );
972
+ `
973
+ },
974
+ {
975
+ id: "033_marketplace_review",
976
+ sql: `
977
+ CREATE TABLE IF NOT EXISTS marketplace_review (
978
+ id TEXT PRIMARY KEY,
979
+ "productId" TEXT NOT NULL,
980
+ "customerId" TEXT NOT NULL,
981
+ "orderId" TEXT,
982
+ rating INTEGER NOT NULL,
983
+ comment TEXT,
984
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
985
+ );
986
+ `
987
+ },
988
+ {
989
+ id: "034_integration",
990
+ sql: `
991
+ CREATE TABLE IF NOT EXISTS integration (
992
+ id TEXT PRIMARY KEY,
993
+ "projectId" TEXT NOT NULL,
994
+ "organizationId" TEXT NOT NULL,
995
+ name TEXT NOT NULL,
996
+ description TEXT,
997
+ type TEXT NOT NULL,
998
+ status TEXT DEFAULT 'INACTIVE',
999
+ "iconUrl" TEXT,
1000
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1001
+ "updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1002
+ );
1003
+ `
1004
+ },
1005
+ {
1006
+ id: "035_integration_connection",
1007
+ sql: `
1008
+ CREATE TABLE IF NOT EXISTS integration_connection (
1009
+ id TEXT PRIMARY KEY,
1010
+ "integrationId" TEXT NOT NULL,
1011
+ name TEXT NOT NULL,
1012
+ status TEXT DEFAULT 'DISCONNECTED',
1013
+ credentials TEXT,
1014
+ config TEXT,
1015
+ "lastSyncAt" TEXT,
1016
+ "errorMessage" TEXT,
1017
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1018
+ "updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1019
+ );
1020
+ `
1021
+ },
1022
+ {
1023
+ id: "036_integration_sync_config",
1024
+ sql: `
1025
+ CREATE TABLE IF NOT EXISTS integration_sync_config (
1026
+ id TEXT PRIMARY KEY,
1027
+ "connectionId" TEXT NOT NULL,
1028
+ name TEXT NOT NULL,
1029
+ "sourceEntity" TEXT NOT NULL,
1030
+ "targetEntity" TEXT NOT NULL,
1031
+ frequency TEXT DEFAULT 'DAILY',
1032
+ status TEXT DEFAULT 'ACTIVE',
1033
+ "lastRunAt" TEXT,
1034
+ "lastRunStatus" TEXT,
1035
+ "recordsSynced" INTEGER DEFAULT 0,
1036
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1037
+ "updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1038
+ );
1039
+ `
1040
+ },
1041
+ {
1042
+ id: "037_integration_field_mapping",
1043
+ sql: `
1044
+ CREATE TABLE IF NOT EXISTS integration_field_mapping (
1045
+ id TEXT PRIMARY KEY,
1046
+ "syncConfigId" TEXT NOT NULL,
1047
+ "sourceField" TEXT NOT NULL,
1048
+ "targetField" TEXT NOT NULL,
1049
+ "transformType" TEXT,
1050
+ "transformConfig" TEXT,
1051
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1052
+ );
1053
+ `
1054
+ },
1055
+ {
1056
+ id: "038_analytics_dashboard",
1057
+ sql: `
1058
+ CREATE TABLE IF NOT EXISTS analytics_dashboard (
1059
+ id TEXT PRIMARY KEY,
1060
+ "projectId" TEXT NOT NULL,
1061
+ "organizationId" TEXT NOT NULL,
1062
+ name TEXT NOT NULL,
1063
+ slug TEXT NOT NULL,
1064
+ description TEXT,
1065
+ status TEXT DEFAULT 'DRAFT',
1066
+ "refreshInterval" TEXT DEFAULT 'NONE',
1067
+ "isPublic" INTEGER DEFAULT 0,
1068
+ "shareToken" TEXT,
1069
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1070
+ "updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1071
+ );
1072
+ `
1073
+ },
1074
+ {
1075
+ id: "039_analytics_widget",
1076
+ sql: `
1077
+ CREATE TABLE IF NOT EXISTS analytics_widget (
1078
+ id TEXT PRIMARY KEY,
1079
+ "dashboardId" TEXT NOT NULL,
1080
+ name TEXT NOT NULL,
1081
+ type TEXT NOT NULL,
1082
+ "gridX" INTEGER DEFAULT 0,
1083
+ "gridY" INTEGER DEFAULT 0,
1084
+ "gridWidth" INTEGER DEFAULT 6,
1085
+ "gridHeight" INTEGER DEFAULT 4,
1086
+ "queryId" TEXT,
1087
+ config TEXT,
1088
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1089
+ "updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1090
+ );
1091
+ `
1092
+ },
1093
+ {
1094
+ id: "040_analytics_query",
1095
+ sql: `
1096
+ CREATE TABLE IF NOT EXISTS analytics_query (
1097
+ id TEXT PRIMARY KEY,
1098
+ "projectId" TEXT NOT NULL,
1099
+ "organizationId" TEXT NOT NULL,
1100
+ name TEXT NOT NULL,
1101
+ description TEXT,
1102
+ type TEXT NOT NULL,
1103
+ definition TEXT NOT NULL,
1104
+ sql TEXT,
1105
+ "cacheTtlSeconds" INTEGER DEFAULT 300,
1106
+ "isShared" INTEGER DEFAULT 0,
1107
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1108
+ "updatedAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1109
+ );
1110
+ `
1111
+ },
1112
+ {
1113
+ id: "041_psa_user_context",
1114
+ sql: `
1115
+ CREATE TABLE IF NOT EXISTS psa_user_context (
1116
+ "projectId" TEXT PRIMARY KEY,
1117
+ locale TEXT NOT NULL,
1118
+ jurisdiction TEXT NOT NULL,
1119
+ "allowedScope" TEXT NOT NULL,
1120
+ "kbSnapshotId" TEXT
1121
+ );
1122
+ `
1123
+ },
1124
+ {
1125
+ id: "042_psa_rule",
1126
+ sql: `
1127
+ CREATE TABLE IF NOT EXISTS psa_rule (
1128
+ id TEXT PRIMARY KEY,
1129
+ "projectId" TEXT NOT NULL,
1130
+ jurisdiction TEXT NOT NULL,
1131
+ "topicKey" TEXT NOT NULL
1132
+ );
1133
+ `
1134
+ },
1135
+ {
1136
+ id: "043_psa_rule_version",
1137
+ sql: `
1138
+ CREATE TABLE IF NOT EXISTS psa_rule_version (
1139
+ id TEXT PRIMARY KEY,
1140
+ "ruleId" TEXT NOT NULL,
1141
+ jurisdiction TEXT NOT NULL,
1142
+ "topicKey" TEXT NOT NULL,
1143
+ version INTEGER NOT NULL,
1144
+ content TEXT NOT NULL,
1145
+ status TEXT NOT NULL,
1146
+ "sourceRefsJson" TEXT NOT NULL,
1147
+ "approvedBy" TEXT,
1148
+ "approvedAt" TEXT,
1149
+ "createdAt" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1150
+ );
1151
+ `
1152
+ },
1153
+ {
1154
+ id: "044_psa_snapshot",
1155
+ sql: `
1156
+ CREATE TABLE IF NOT EXISTS psa_snapshot (
1157
+ id TEXT PRIMARY KEY,
1158
+ jurisdiction TEXT NOT NULL,
1159
+ "asOfDate" TEXT NOT NULL,
1160
+ "includedRuleVersionIdsJson" TEXT NOT NULL,
1161
+ "publishedAt" TEXT NOT NULL
1162
+ );
1163
+ `
1164
+ },
1165
+ {
1166
+ id: "045_psa_change_candidate",
1167
+ sql: `
1168
+ CREATE TABLE IF NOT EXISTS psa_change_candidate (
1169
+ id TEXT PRIMARY KEY,
1170
+ "projectId" TEXT NOT NULL,
1171
+ jurisdiction TEXT NOT NULL,
1172
+ "detectedAt" TEXT NOT NULL,
1173
+ "diffSummary" TEXT NOT NULL,
1174
+ "riskLevel" TEXT NOT NULL,
1175
+ "proposedRuleVersionIdsJson" TEXT NOT NULL
1176
+ );
1177
+ `
1178
+ },
1179
+ {
1180
+ id: "046_psa_review_task",
1181
+ sql: `
1182
+ CREATE TABLE IF NOT EXISTS psa_review_task (
1183
+ id TEXT PRIMARY KEY,
1184
+ "changeCandidateId" TEXT NOT NULL,
1185
+ status TEXT NOT NULL,
1186
+ "assignedRole" TEXT NOT NULL,
1187
+ decision TEXT,
1188
+ "decidedAt" TEXT,
1189
+ "decidedBy" TEXT
1190
+ );
1191
+ `
1192
+ }
1193
+ ];
1194
+ // src/web/database/schema.ts
1195
+ import { integer, pgTable, real, text, timestamp } from "drizzle-orm/pg-core";
1196
+ var templateTaskCategory = pgTable("template_task_category", {
1197
+ id: text("id").primaryKey(),
1198
+ projectId: text("projectId").notNull(),
1199
+ name: text("name").notNull(),
1200
+ color: text("color"),
1201
+ createdAt: timestamp("createdAt").defaultNow(),
1202
+ updatedAt: timestamp("updatedAt").defaultNow()
1203
+ });
1204
+ var templateTask = pgTable("template_task", {
1205
+ id: text("id").primaryKey(),
1206
+ projectId: text("projectId").notNull(),
1207
+ categoryId: text("categoryId"),
1208
+ title: text("title").notNull(),
1209
+ description: text("description"),
1210
+ completed: integer("completed").default(0),
1211
+ priority: text("priority").default("MEDIUM"),
1212
+ dueDate: text("dueDate"),
1213
+ tags: text("tags"),
1214
+ createdAt: timestamp("createdAt").defaultNow(),
1215
+ updatedAt: timestamp("updatedAt").defaultNow()
1216
+ });
1217
+ var templateConversation = pgTable("template_conversation", {
1218
+ id: text("id").primaryKey(),
1219
+ projectId: text("projectId").notNull(),
1220
+ name: text("name"),
1221
+ isGroup: integer("isGroup").default(0),
1222
+ avatarUrl: text("avatarUrl"),
1223
+ lastMessageId: text("lastMessageId"),
1224
+ createdAt: timestamp("createdAt").defaultNow(),
1225
+ updatedAt: timestamp("updatedAt").defaultNow()
1226
+ });
1227
+ var templateConversationParticipant = pgTable("template_conversation_participant", {
1228
+ id: text("id").primaryKey(),
1229
+ conversationId: text("conversationId").notNull(),
1230
+ projectId: text("projectId").notNull(),
1231
+ userId: text("userId").notNull(),
1232
+ displayName: text("displayName"),
1233
+ role: text("role"),
1234
+ joinedAt: timestamp("joinedAt").defaultNow(),
1235
+ lastReadAt: text("lastReadAt")
1236
+ });
1237
+ var templateMessage = pgTable("template_message", {
1238
+ id: text("id").primaryKey(),
1239
+ conversationId: text("conversationId").notNull(),
1240
+ projectId: text("projectId").notNull(),
1241
+ senderId: text("senderId").notNull(),
1242
+ senderName: text("senderName"),
1243
+ content: text("content").notNull(),
1244
+ attachments: text("attachments"),
1245
+ status: text("status").default("SENT"),
1246
+ createdAt: timestamp("createdAt").defaultNow(),
1247
+ updatedAt: timestamp("updatedAt").defaultNow()
1248
+ });
1249
+ var templateRecipeCategory = pgTable("template_recipe_category", {
1250
+ id: text("id").primaryKey(),
1251
+ nameEn: text("nameEn").notNull(),
1252
+ nameFr: text("nameFr").notNull(),
1253
+ icon: text("icon")
1254
+ });
1255
+ var templateRecipe = pgTable("template_recipe", {
1256
+ id: text("id").primaryKey(),
1257
+ projectId: text("projectId").notNull(),
1258
+ categoryId: text("categoryId"),
1259
+ slugEn: text("slugEn").notNull(),
1260
+ slugFr: text("slugFr").notNull(),
1261
+ nameEn: text("nameEn").notNull(),
1262
+ nameFr: text("nameFr").notNull(),
1263
+ descriptionEn: text("descriptionEn"),
1264
+ descriptionFr: text("descriptionFr"),
1265
+ heroImageUrl: text("heroImageUrl"),
1266
+ prepTimeMinutes: integer("prepTimeMinutes"),
1267
+ cookTimeMinutes: integer("cookTimeMinutes"),
1268
+ servings: integer("servings"),
1269
+ isFavorite: integer("isFavorite").default(0),
1270
+ createdAt: timestamp("createdAt").defaultNow(),
1271
+ updatedAt: timestamp("updatedAt").defaultNow()
1272
+ });
1273
+ var templateRecipeIngredient = pgTable("template_recipe_ingredient", {
1274
+ id: text("id").primaryKey(),
1275
+ recipeId: text("recipeId").notNull(),
1276
+ nameEn: text("nameEn").notNull(),
1277
+ nameFr: text("nameFr").notNull(),
1278
+ quantity: text("quantity").notNull(),
1279
+ ordering: integer("ordering").default(0)
1280
+ });
1281
+ var templateRecipeInstruction = pgTable("template_recipe_instruction", {
1282
+ id: text("id").primaryKey(),
1283
+ recipeId: text("recipeId").notNull(),
1284
+ contentEn: text("contentEn").notNull(),
1285
+ contentFr: text("contentFr").notNull(),
1286
+ ordering: integer("ordering").default(0)
1287
+ });
1288
+ var crmPipeline = pgTable("crm_pipeline", {
1289
+ id: text("id").primaryKey(),
1290
+ projectId: text("projectId").notNull(),
1291
+ name: text("name").notNull(),
1292
+ createdAt: timestamp("createdAt").defaultNow(),
1293
+ updatedAt: timestamp("updatedAt").defaultNow()
1294
+ });
1295
+ var crmStage = pgTable("crm_stage", {
1296
+ id: text("id").primaryKey(),
1297
+ pipelineId: text("pipelineId").notNull(),
1298
+ name: text("name").notNull(),
1299
+ position: integer("position").notNull(),
1300
+ createdAt: timestamp("createdAt").defaultNow()
1301
+ });
1302
+ var crmDeal = pgTable("crm_deal", {
1303
+ id: text("id").primaryKey(),
1304
+ projectId: text("projectId").notNull(),
1305
+ pipelineId: text("pipelineId").notNull(),
1306
+ stageId: text("stageId").notNull(),
1307
+ name: text("name").notNull(),
1308
+ value: real("value").notNull().default(0),
1309
+ currency: text("currency").default("USD"),
1310
+ status: text("status").default("OPEN"),
1311
+ contactId: text("contactId"),
1312
+ companyId: text("companyId"),
1313
+ ownerId: text("ownerId").notNull(),
1314
+ expectedCloseDate: text("expectedCloseDate"),
1315
+ wonSource: text("wonSource"),
1316
+ lostReason: text("lostReason"),
1317
+ notes: text("notes"),
1318
+ createdAt: timestamp("createdAt").defaultNow(),
1319
+ updatedAt: timestamp("updatedAt").defaultNow()
1320
+ });
1321
+ var crmCompany = pgTable("crm_company", {
1322
+ id: text("id").primaryKey(),
1323
+ projectId: text("projectId").notNull(),
1324
+ name: text("name").notNull(),
1325
+ domain: text("domain"),
1326
+ industry: text("industry"),
1327
+ size: text("size"),
1328
+ website: text("website"),
1329
+ createdAt: timestamp("createdAt").defaultNow(),
1330
+ updatedAt: timestamp("updatedAt").defaultNow()
1331
+ });
1332
+ var crmContact = pgTable("crm_contact", {
1333
+ id: text("id").primaryKey(),
1334
+ projectId: text("projectId").notNull(),
1335
+ companyId: text("companyId"),
1336
+ firstName: text("firstName").notNull(),
1337
+ lastName: text("lastName"),
1338
+ email: text("email"),
1339
+ phone: text("phone"),
1340
+ title: text("title"),
1341
+ createdAt: timestamp("createdAt").defaultNow(),
1342
+ updatedAt: timestamp("updatedAt").defaultNow()
1343
+ });
1344
+ var saasProject = pgTable("saas_project", {
1345
+ id: text("id").primaryKey(),
1346
+ projectId: text("projectId").notNull(),
1347
+ organizationId: text("organizationId").notNull(),
1348
+ name: text("name").notNull(),
1349
+ description: text("description"),
1350
+ status: text("status").default("DRAFT"),
1351
+ tier: text("tier").default("FREE"),
1352
+ createdAt: timestamp("createdAt").defaultNow(),
1353
+ updatedAt: timestamp("updatedAt").defaultNow()
1354
+ });
1355
+ var saasSubscription = pgTable("saas_subscription", {
1356
+ id: text("id").primaryKey(),
1357
+ projectId: text("projectId").notNull(),
1358
+ organizationId: text("organizationId").notNull(),
1359
+ plan: text("plan").notNull().default("FREE"),
1360
+ status: text("status").default("ACTIVE"),
1361
+ billingCycle: text("billingCycle").default("MONTHLY"),
1362
+ currentPeriodStart: text("currentPeriodStart"),
1363
+ currentPeriodEnd: text("currentPeriodEnd"),
1364
+ cancelAtPeriodEnd: integer("cancelAtPeriodEnd").default(0),
1365
+ createdAt: timestamp("createdAt").defaultNow(),
1366
+ updatedAt: timestamp("updatedAt").defaultNow()
1367
+ });
1368
+ var saasUsage = pgTable("saas_usage", {
1369
+ id: text("id").primaryKey(),
1370
+ projectId: text("projectId").notNull(),
1371
+ organizationId: text("organizationId").notNull(),
1372
+ metricName: text("metricName").notNull(),
1373
+ value: real("value").notNull().default(0),
1374
+ periodStart: text("periodStart").notNull(),
1375
+ periodEnd: text("periodEnd").notNull(),
1376
+ createdAt: timestamp("createdAt").defaultNow()
1377
+ });
1378
+ var agentTool = pgTable("agent_tool", {
1379
+ id: text("id").primaryKey(),
1380
+ projectId: text("projectId").notNull(),
1381
+ organizationId: text("organizationId").notNull(),
1382
+ name: text("name").notNull(),
1383
+ description: text("description"),
1384
+ version: text("version").default("1.0.0"),
1385
+ category: text("category").default("UTILITY"),
1386
+ status: text("status").default("ACTIVE"),
1387
+ inputSchema: text("inputSchema"),
1388
+ outputSchema: text("outputSchema"),
1389
+ endpoint: text("endpoint"),
1390
+ createdAt: timestamp("createdAt").defaultNow(),
1391
+ updatedAt: timestamp("updatedAt").defaultNow()
1392
+ });
1393
+ var agentDefinition = pgTable("agent_definition", {
1394
+ id: text("id").primaryKey(),
1395
+ projectId: text("projectId").notNull(),
1396
+ organizationId: text("organizationId").notNull(),
1397
+ name: text("name").notNull(),
1398
+ description: text("description"),
1399
+ modelProvider: text("modelProvider").default("openai"),
1400
+ modelName: text("modelName").default("gpt-4"),
1401
+ systemPrompt: text("systemPrompt"),
1402
+ temperature: real("temperature").default(0.7),
1403
+ maxTokens: integer("maxTokens").default(4096),
1404
+ status: text("status").default("DRAFT"),
1405
+ createdAt: timestamp("createdAt").defaultNow(),
1406
+ updatedAt: timestamp("updatedAt").defaultNow()
1407
+ });
1408
+ var agentToolAssignment = pgTable("agent_tool_assignment", {
1409
+ id: text("id").primaryKey(),
1410
+ agentId: text("agentId").notNull(),
1411
+ toolId: text("toolId").notNull(),
1412
+ assignedAt: timestamp("assignedAt").defaultNow()
1413
+ });
1414
+ var agentRun = pgTable("agent_run", {
1415
+ id: text("id").primaryKey(),
1416
+ projectId: text("projectId").notNull(),
1417
+ agentId: text("agentId").notNull(),
1418
+ status: text("status").default("QUEUED"),
1419
+ input: text("input"),
1420
+ output: text("output"),
1421
+ totalTokens: integer("totalTokens").default(0),
1422
+ promptTokens: integer("promptTokens").default(0),
1423
+ completionTokens: integer("completionTokens").default(0),
1424
+ estimatedCostUsd: real("estimatedCostUsd").default(0),
1425
+ durationMs: integer("durationMs"),
1426
+ errorMessage: text("errorMessage"),
1427
+ queuedAt: timestamp("queuedAt").defaultNow(),
1428
+ startedAt: text("startedAt"),
1429
+ completedAt: text("completedAt")
1430
+ });
1431
+ var agentRunStep = pgTable("agent_run_step", {
1432
+ id: text("id").primaryKey(),
1433
+ runId: text("runId").notNull(),
1434
+ stepNumber: integer("stepNumber").notNull(),
1435
+ type: text("type").notNull(),
1436
+ toolId: text("toolId"),
1437
+ toolInput: text("toolInput"),
1438
+ toolOutput: text("toolOutput"),
1439
+ reasoning: text("reasoning"),
1440
+ tokensUsed: integer("tokensUsed").default(0),
1441
+ durationMs: integer("durationMs"),
1442
+ status: text("status").default("PENDING"),
1443
+ createdAt: timestamp("createdAt").defaultNow()
1444
+ });
1445
+ var agentRunLog = pgTable("agent_run_log", {
1446
+ id: text("id").primaryKey(),
1447
+ runId: text("runId").notNull(),
1448
+ level: text("level").default("INFO"),
1449
+ message: text("message").notNull(),
1450
+ metadata: text("metadata"),
1451
+ timestamp: timestamp("timestamp").defaultNow()
1452
+ });
1453
+ var workflowDefinition = pgTable("workflow_definition", {
1454
+ id: text("id").primaryKey(),
1455
+ projectId: text("projectId").notNull(),
1456
+ organizationId: text("organizationId").notNull(),
1457
+ name: text("name").notNull(),
1458
+ description: text("description"),
1459
+ type: text("type").default("APPROVAL"),
1460
+ status: text("status").default("DRAFT"),
1461
+ createdAt: timestamp("createdAt").defaultNow(),
1462
+ updatedAt: timestamp("updatedAt").defaultNow()
1463
+ });
1464
+ var workflowStep = pgTable("workflow_step", {
1465
+ id: text("id").primaryKey(),
1466
+ definitionId: text("definitionId").notNull(),
1467
+ name: text("name").notNull(),
1468
+ description: text("description"),
1469
+ stepOrder: integer("stepOrder").notNull(),
1470
+ type: text("type").default("APPROVAL"),
1471
+ requiredRoles: text("requiredRoles"),
1472
+ autoApproveCondition: text("autoApproveCondition"),
1473
+ timeoutHours: integer("timeoutHours"),
1474
+ createdAt: timestamp("createdAt").defaultNow()
1475
+ });
1476
+ var workflowInstance = pgTable("workflow_instance", {
1477
+ id: text("id").primaryKey(),
1478
+ projectId: text("projectId").notNull(),
1479
+ definitionId: text("definitionId").notNull(),
1480
+ status: text("status").default("PENDING"),
1481
+ currentStepId: text("currentStepId"),
1482
+ data: text("data"),
1483
+ requestedBy: text("requestedBy").notNull(),
1484
+ startedAt: timestamp("startedAt").defaultNow(),
1485
+ completedAt: text("completedAt")
1486
+ });
1487
+ var workflowApproval = pgTable("workflow_approval", {
1488
+ id: text("id").primaryKey(),
1489
+ instanceId: text("instanceId").notNull(),
1490
+ stepId: text("stepId").notNull(),
1491
+ status: text("status").default("PENDING"),
1492
+ actorId: text("actorId"),
1493
+ comment: text("comment"),
1494
+ decidedAt: text("decidedAt"),
1495
+ createdAt: timestamp("createdAt").defaultNow()
1496
+ });
1497
+ var marketplaceStore = pgTable("marketplace_store", {
1498
+ id: text("id").primaryKey(),
1499
+ projectId: text("projectId").notNull(),
1500
+ organizationId: text("organizationId").notNull(),
1501
+ name: text("name").notNull(),
1502
+ description: text("description"),
1503
+ status: text("status").default("PENDING"),
1504
+ rating: real("rating").default(0),
1505
+ reviewCount: integer("reviewCount").default(0),
1506
+ createdAt: timestamp("createdAt").defaultNow(),
1507
+ updatedAt: timestamp("updatedAt").defaultNow()
1508
+ });
1509
+ var marketplaceProduct = pgTable("marketplace_product", {
1510
+ id: text("id").primaryKey(),
1511
+ storeId: text("storeId").notNull(),
1512
+ name: text("name").notNull(),
1513
+ description: text("description"),
1514
+ price: real("price").notNull().default(0),
1515
+ currency: text("currency").default("USD"),
1516
+ status: text("status").default("DRAFT"),
1517
+ stock: integer("stock").default(0),
1518
+ category: text("category"),
1519
+ imageUrl: text("imageUrl"),
1520
+ createdAt: timestamp("createdAt").defaultNow(),
1521
+ updatedAt: timestamp("updatedAt").defaultNow()
1522
+ });
1523
+ var marketplaceOrder = pgTable("marketplace_order", {
1524
+ id: text("id").primaryKey(),
1525
+ projectId: text("projectId").notNull(),
1526
+ storeId: text("storeId").notNull(),
1527
+ customerId: text("customerId").notNull(),
1528
+ status: text("status").default("PENDING"),
1529
+ total: real("total").notNull().default(0),
1530
+ currency: text("currency").default("USD"),
1531
+ shippingAddress: text("shippingAddress"),
1532
+ createdAt: timestamp("createdAt").defaultNow(),
1533
+ updatedAt: timestamp("updatedAt").defaultNow()
1534
+ });
1535
+ var marketplaceOrderItem = pgTable("marketplace_order_item", {
1536
+ id: text("id").primaryKey(),
1537
+ orderId: text("orderId").notNull(),
1538
+ productId: text("productId").notNull(),
1539
+ quantity: integer("quantity").notNull().default(1),
1540
+ price: real("price").notNull().default(0),
1541
+ createdAt: timestamp("createdAt").defaultNow()
1542
+ });
1543
+ var marketplacePayout = pgTable("marketplace_payout", {
1544
+ id: text("id").primaryKey(),
1545
+ storeId: text("storeId").notNull(),
1546
+ amount: real("amount").notNull().default(0),
1547
+ currency: text("currency").default("USD"),
1548
+ status: text("status").default("PENDING"),
1549
+ processedAt: text("processedAt"),
1550
+ createdAt: timestamp("createdAt").defaultNow()
1551
+ });
1552
+ var marketplaceReview = pgTable("marketplace_review", {
1553
+ id: text("id").primaryKey(),
1554
+ productId: text("productId").notNull(),
1555
+ customerId: text("customerId").notNull(),
1556
+ orderId: text("orderId"),
1557
+ rating: integer("rating").notNull(),
1558
+ comment: text("comment"),
1559
+ createdAt: timestamp("createdAt").defaultNow()
1560
+ });
1561
+ var integration = pgTable("integration", {
1562
+ id: text("id").primaryKey(),
1563
+ projectId: text("projectId").notNull(),
1564
+ organizationId: text("organizationId").notNull(),
1565
+ name: text("name").notNull(),
1566
+ description: text("description"),
1567
+ type: text("type").notNull(),
1568
+ status: text("status").default("INACTIVE"),
1569
+ iconUrl: text("iconUrl"),
1570
+ createdAt: timestamp("createdAt").defaultNow(),
1571
+ updatedAt: timestamp("updatedAt").defaultNow()
1572
+ });
1573
+ var integrationConnection = pgTable("integration_connection", {
1574
+ id: text("id").primaryKey(),
1575
+ integrationId: text("integrationId").notNull(),
1576
+ name: text("name").notNull(),
1577
+ status: text("status").default("DISCONNECTED"),
1578
+ credentials: text("credentials"),
1579
+ config: text("config"),
1580
+ lastSyncAt: text("lastSyncAt"),
1581
+ errorMessage: text("errorMessage"),
1582
+ createdAt: timestamp("createdAt").defaultNow(),
1583
+ updatedAt: timestamp("updatedAt").defaultNow()
1584
+ });
1585
+ var integrationSyncConfig = pgTable("integration_sync_config", {
1586
+ id: text("id").primaryKey(),
1587
+ connectionId: text("connectionId").notNull(),
1588
+ name: text("name").notNull(),
1589
+ sourceEntity: text("sourceEntity").notNull(),
1590
+ targetEntity: text("targetEntity").notNull(),
1591
+ frequency: text("frequency").default("DAILY"),
1592
+ status: text("status").default("ACTIVE"),
1593
+ lastRunAt: text("lastRunAt"),
1594
+ lastRunStatus: text("lastRunStatus"),
1595
+ recordsSynced: integer("recordsSynced").default(0),
1596
+ createdAt: timestamp("createdAt").defaultNow(),
1597
+ updatedAt: timestamp("updatedAt").defaultNow()
1598
+ });
1599
+ var integrationFieldMapping = pgTable("integration_field_mapping", {
1600
+ id: text("id").primaryKey(),
1601
+ syncConfigId: text("syncConfigId").notNull(),
1602
+ sourceField: text("sourceField").notNull(),
1603
+ targetField: text("targetField").notNull(),
1604
+ transformType: text("transformType"),
1605
+ transformConfig: text("transformConfig"),
1606
+ createdAt: timestamp("createdAt").defaultNow()
1607
+ });
1608
+ var analyticsDashboard = pgTable("analytics_dashboard", {
1609
+ id: text("id").primaryKey(),
1610
+ projectId: text("projectId").notNull(),
1611
+ organizationId: text("organizationId").notNull(),
1612
+ name: text("name").notNull(),
1613
+ slug: text("slug").notNull(),
1614
+ description: text("description"),
1615
+ status: text("status").default("DRAFT"),
1616
+ refreshInterval: text("refreshInterval").default("NONE"),
1617
+ isPublic: integer("isPublic").default(0),
1618
+ shareToken: text("shareToken"),
1619
+ createdAt: timestamp("createdAt").defaultNow(),
1620
+ updatedAt: timestamp("updatedAt").defaultNow()
1621
+ });
1622
+ var analyticsWidget = pgTable("analytics_widget", {
1623
+ id: text("id").primaryKey(),
1624
+ dashboardId: text("dashboardId").notNull(),
1625
+ name: text("name").notNull(),
1626
+ type: text("type").notNull(),
1627
+ gridX: integer("gridX").default(0),
1628
+ gridY: integer("gridY").default(0),
1629
+ gridWidth: integer("gridWidth").default(6),
1630
+ gridHeight: integer("gridHeight").default(4),
1631
+ queryId: text("queryId"),
1632
+ config: text("config"),
1633
+ createdAt: timestamp("createdAt").defaultNow(),
1634
+ updatedAt: timestamp("updatedAt").defaultNow()
1635
+ });
1636
+ var analyticsQuery = pgTable("analytics_query", {
1637
+ id: text("id").primaryKey(),
1638
+ projectId: text("projectId").notNull(),
1639
+ organizationId: text("organizationId").notNull(),
1640
+ name: text("name").notNull(),
1641
+ description: text("description"),
1642
+ type: text("type").notNull(),
1643
+ definition: text("definition").notNull(),
1644
+ sql: text("sql"),
1645
+ cacheTtlSeconds: integer("cacheTtlSeconds").default(300),
1646
+ isShared: integer("isShared").default(0),
1647
+ createdAt: timestamp("createdAt").defaultNow(),
1648
+ updatedAt: timestamp("updatedAt").defaultNow()
1649
+ });
1650
+ var psaUserContext = pgTable("psa_user_context", {
1651
+ projectId: text("projectId").primaryKey(),
1652
+ locale: text("locale").notNull(),
1653
+ jurisdiction: text("jurisdiction").notNull(),
1654
+ allowedScope: text("allowedScope").notNull(),
1655
+ kbSnapshotId: text("kbSnapshotId")
1656
+ });
1657
+ var psaRule = pgTable("psa_rule", {
1658
+ id: text("id").primaryKey(),
1659
+ projectId: text("projectId").notNull(),
1660
+ jurisdiction: text("jurisdiction").notNull(),
1661
+ topicKey: text("topicKey").notNull()
1662
+ });
1663
+ var psaRuleVersion = pgTable("psa_rule_version", {
1664
+ id: text("id").primaryKey(),
1665
+ ruleId: text("ruleId").notNull(),
1666
+ jurisdiction: text("jurisdiction").notNull(),
1667
+ topicKey: text("topicKey").notNull(),
1668
+ version: integer("version").notNull(),
1669
+ content: text("content").notNull(),
1670
+ status: text("status").notNull(),
1671
+ sourceRefsJson: text("sourceRefsJson").notNull(),
1672
+ approvedBy: text("approvedBy"),
1673
+ approvedAt: text("approvedAt"),
1674
+ createdAt: timestamp("createdAt").defaultNow()
1675
+ });
1676
+ var psaSnapshot = pgTable("psa_snapshot", {
1677
+ id: text("id").primaryKey(),
1678
+ jurisdiction: text("jurisdiction").notNull(),
1679
+ asOfDate: text("asOfDate").notNull(),
1680
+ includedRuleVersionIdsJson: text("includedRuleVersionIdsJson").notNull(),
1681
+ publishedAt: text("publishedAt").notNull()
1682
+ });
1683
+ var psaChangeCandidate = pgTable("psa_change_candidate", {
1684
+ id: text("id").primaryKey(),
1685
+ projectId: text("projectId").notNull(),
1686
+ jurisdiction: text("jurisdiction").notNull(),
1687
+ detectedAt: text("detectedAt").notNull(),
1688
+ diffSummary: text("diffSummary").notNull(),
1689
+ riskLevel: text("riskLevel").notNull(),
1690
+ proposedRuleVersionIdsJson: text("proposedRuleVersionIdsJson").notNull()
1691
+ });
1692
+ var psaReviewTask = pgTable("psa_review_task", {
1693
+ id: text("id").primaryKey(),
1694
+ changeCandidateId: text("changeCandidateId").notNull(),
1695
+ status: text("status").notNull(),
1696
+ assignedRole: text("assignedRole").notNull(),
1697
+ decision: text("decision"),
1698
+ decidedAt: text("decidedAt"),
1699
+ decidedBy: text("decidedBy")
1700
+ });
1701
+ // src/web/storage/indexeddb.ts
1702
+ var DEFAULT_DB_NAME = "contractspec-runtime";
1703
+ var DEFAULT_STORE = "kv";
1704
+ var FALLBACK_STORE = new Map;
1705
+
1706
+ class LocalStorageService {
1707
+ options;
1708
+ dbPromise;
1709
+ constructor(options = {}) {
1710
+ this.options = options;
1711
+ }
1712
+ async init() {
1713
+ await this.getDb();
1714
+ }
1715
+ async get(key, fallback) {
1716
+ const db = await this.getDb();
1717
+ if (!db) {
1718
+ return FALLBACK_STORE.get(key) ?? fallback;
1719
+ }
1720
+ return new Promise((resolve, reject) => {
1721
+ const tx = db.transaction(this.storeName, "readonly");
1722
+ const store = tx.objectStore(this.storeName);
1723
+ const request = store.get(key);
1724
+ request.onsuccess = () => {
1725
+ resolve(request.result ?? fallback);
1726
+ };
1727
+ request.onerror = () => reject(request.error);
1728
+ });
1729
+ }
1730
+ async set(key, value) {
1731
+ const db = await this.getDb();
1732
+ if (!db) {
1733
+ FALLBACK_STORE.set(key, value);
1734
+ return;
1735
+ }
1736
+ await new Promise((resolve, reject) => {
1737
+ const tx = db.transaction(this.storeName, "readwrite");
1738
+ const store = tx.objectStore(this.storeName);
1739
+ const request = store.put(value, key);
1740
+ request.onsuccess = () => resolve();
1741
+ request.onerror = () => reject(request.error);
1742
+ });
1743
+ }
1744
+ async delete(key) {
1745
+ const db = await this.getDb();
1746
+ if (!db) {
1747
+ FALLBACK_STORE.delete(key);
1748
+ return;
1749
+ }
1750
+ await new Promise((resolve, reject) => {
1751
+ const tx = db.transaction(this.storeName, "readwrite");
1752
+ const store = tx.objectStore(this.storeName);
1753
+ const request = store.delete(key);
1754
+ request.onsuccess = () => resolve();
1755
+ request.onerror = () => reject(request.error);
1756
+ });
1757
+ }
1758
+ async clear() {
1759
+ const db = await this.getDb();
1760
+ if (!db) {
1761
+ FALLBACK_STORE.clear();
1762
+ return;
1763
+ }
1764
+ await new Promise((resolve, reject) => {
1765
+ const tx = db.transaction(this.storeName, "readwrite");
1766
+ const store = tx.objectStore(this.storeName);
1767
+ const request = store.clear();
1768
+ request.onsuccess = () => resolve();
1769
+ request.onerror = () => reject(request.error);
1770
+ });
1771
+ }
1772
+ get storeName() {
1773
+ return this.options.storeName ?? DEFAULT_STORE;
1774
+ }
1775
+ async getDb() {
1776
+ if (typeof indexedDB === "undefined") {
1777
+ return null;
1778
+ }
1779
+ if (!this.dbPromise) {
1780
+ this.dbPromise = this.openDb();
1781
+ }
1782
+ return this.dbPromise;
1783
+ }
1784
+ openDb() {
1785
+ return new Promise((resolve, reject) => {
1786
+ const request = indexedDB.open(this.options.dbName ?? DEFAULT_DB_NAME, this.options.version ?? 1);
1787
+ request.onerror = () => reject(request.error);
1788
+ request.onsuccess = () => {
1789
+ resolve(request.result);
1790
+ };
1791
+ request.onupgradeneeded = () => {
1792
+ const db = request.result;
1793
+ if (!db.objectStoreNames.contains(this.storeName)) {
1794
+ db.createObjectStore(this.storeName);
1795
+ }
1796
+ };
1797
+ });
1798
+ }
1799
+ }
1800
+ // src/web/utils/id.ts
1801
+ function generateId(prefix) {
1802
+ const base = typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : Math.random().toString(36).slice(2, 10);
1803
+ return prefix ? `${prefix}_${base}` : base;
1804
+ }
1805
+ // src/web/graphql/local-client.ts
1806
+ import { ApolloClient, InMemoryCache } from "@apollo/client";
1807
+ import { SchemaLink } from "@apollo/client/link/schema";
1808
+ import { makeExecutableSchema } from "@graphql-tools/schema";
1809
+ import { GraphQLScalarType, Kind } from "graphql";
1810
+
1811
+ // src/web/events/local-pubsub.ts
1812
+ class LocalEventBus {
1813
+ listeners = new Map;
1814
+ emit(event, payload) {
1815
+ const listeners = this.listeners.get(event);
1816
+ if (!listeners)
1817
+ return;
1818
+ for (const listener of listeners) {
1819
+ listener(payload);
1820
+ }
1821
+ }
1822
+ subscribe(event, listener) {
1823
+ let listeners = this.listeners.get(event);
1824
+ if (!listeners) {
1825
+ listeners = new Set;
1826
+ this.listeners.set(event, listeners);
1827
+ }
1828
+ listeners.add(listener);
1829
+ return () => {
1830
+ listeners.delete(listener);
1831
+ };
1832
+ }
1833
+ }
1834
+
1835
+ // src/web/graphql/local-client.ts
1836
+ var typeDefs = `
1837
+ scalar DateTime
1838
+
1839
+ enum TaskPriority {
1840
+ LOW
1841
+ MEDIUM
1842
+ HIGH
1843
+ URGENT
1844
+ }
1845
+
1846
+ enum MessageStatus {
1847
+ SENT
1848
+ DELIVERED
1849
+ READ
1850
+ }
1851
+
1852
+ enum RecipeLocale {
1853
+ EN
1854
+ FR
1855
+ }
1856
+
1857
+ type TaskCategory {
1858
+ id: ID!
1859
+ projectId: ID!
1860
+ name: String!
1861
+ color: String
1862
+ createdAt: DateTime!
1863
+ updatedAt: DateTime!
1864
+ }
1865
+
1866
+ type Task {
1867
+ id: ID!
1868
+ projectId: ID!
1869
+ categoryId: ID
1870
+ title: String!
1871
+ description: String
1872
+ completed: Boolean!
1873
+ priority: TaskPriority!
1874
+ dueDate: DateTime
1875
+ tags: [String!]!
1876
+ createdAt: DateTime!
1877
+ updatedAt: DateTime!
1878
+ category: TaskCategory
1879
+ }
1880
+
1881
+ input CreateTaskInput {
1882
+ projectId: ID!
1883
+ categoryId: ID
1884
+ title: String!
1885
+ description: String
1886
+ priority: TaskPriority = MEDIUM
1887
+ dueDate: DateTime
1888
+ tags: [String!]
1889
+ }
1890
+
1891
+ input UpdateTaskInput {
1892
+ categoryId: ID
1893
+ title: String
1894
+ description: String
1895
+ priority: TaskPriority
1896
+ dueDate: DateTime
1897
+ tags: [String!]
1898
+ }
1899
+
1900
+ type ConversationParticipant {
1901
+ id: ID!
1902
+ conversationId: ID!
1903
+ projectId: ID!
1904
+ userId: String!
1905
+ displayName: String
1906
+ role: String
1907
+ joinedAt: DateTime!
1908
+ lastReadAt: DateTime
1909
+ }
1910
+
1911
+ input ConversationParticipantInput {
1912
+ userId: String!
1913
+ displayName: String
1914
+ role: String
1915
+ }
1916
+
1917
+ type Message {
1918
+ id: ID!
1919
+ conversationId: ID!
1920
+ projectId: ID!
1921
+ senderId: String!
1922
+ senderName: String
1923
+ content: String!
1924
+ attachments: [String!]!
1925
+ status: MessageStatus!
1926
+ createdAt: DateTime!
1927
+ updatedAt: DateTime!
1928
+ }
1929
+
1930
+ input SendMessageInput {
1931
+ conversationId: ID!
1932
+ projectId: ID!
1933
+ senderId: String!
1934
+ senderName: String
1935
+ content: String!
1936
+ }
1937
+
1938
+ input CreateConversationInput {
1939
+ projectId: ID!
1940
+ name: String
1941
+ isGroup: Boolean = false
1942
+ avatarUrl: String
1943
+ participants: [ConversationParticipantInput!]!
1944
+ }
1945
+
1946
+ type Conversation {
1947
+ id: ID!
1948
+ projectId: ID!
1949
+ name: String
1950
+ isGroup: Boolean!
1951
+ avatarUrl: String
1952
+ lastMessageId: ID
1953
+ updatedAt: DateTime!
1954
+ participants: [ConversationParticipant!]!
1955
+ messages(limit: Int = 50): [Message!]!
1956
+ }
1957
+
1958
+ type RecipeCategory {
1959
+ id: ID!
1960
+ nameEn: String!
1961
+ nameFr: String!
1962
+ icon: String
1963
+ }
1964
+
1965
+ type RecipeIngredient {
1966
+ id: ID!
1967
+ name: String!
1968
+ quantity: String!
1969
+ ordering: Int!
1970
+ }
1971
+
1972
+ type RecipeInstruction {
1973
+ id: ID!
1974
+ content: String!
1975
+ ordering: Int!
1976
+ }
1977
+
1978
+ type Recipe {
1979
+ id: ID!
1980
+ projectId: ID!
1981
+ slugEn: String!
1982
+ slugFr: String!
1983
+ name: String!
1984
+ description: String
1985
+ heroImageUrl: String
1986
+ prepTimeMinutes: Int
1987
+ cookTimeMinutes: Int
1988
+ servings: Int
1989
+ isFavorite: Boolean!
1990
+ locale: RecipeLocale!
1991
+ category: RecipeCategory
1992
+ ingredients: [RecipeIngredient!]!
1993
+ instructions: [RecipeInstruction!]!
1994
+ }
1995
+
1996
+ type Query {
1997
+ taskCategories(projectId: ID!): [TaskCategory!]!
1998
+ tasks(projectId: ID!): [Task!]!
1999
+ conversations(projectId: ID!): [Conversation!]!
2000
+ messages(conversationId: ID!, limit: Int = 50): [Message!]!
2001
+ recipes(projectId: ID!, locale: RecipeLocale = EN): [Recipe!]!
2002
+ recipe(id: ID!, locale: RecipeLocale = EN): Recipe
2003
+ }
2004
+
2005
+ type Mutation {
2006
+ createTask(input: CreateTaskInput!): Task!
2007
+ updateTask(id: ID!, input: UpdateTaskInput!): Task!
2008
+ toggleTask(id: ID!, completed: Boolean!): Task!
2009
+ deleteTask(id: ID!): Boolean!
2010
+ createConversation(input: CreateConversationInput!): Conversation!
2011
+ sendMessage(input: SendMessageInput!): Message!
2012
+ setMessagesRead(conversationId: ID!, userId: String!): Boolean!
2013
+ favoriteRecipe(id: ID!, isFavorite: Boolean!): Recipe!
2014
+ }
2015
+ `;
2016
+ var DateTimeScalar = new GraphQLScalarType({
2017
+ name: "DateTime",
2018
+ parseValue(value) {
2019
+ return value ? new Date(value).toISOString() : null;
2020
+ },
2021
+ serialize(value) {
2022
+ if (!value)
2023
+ return null;
2024
+ if (typeof value === "string")
2025
+ return value;
2026
+ return new Date(value).toISOString();
2027
+ },
2028
+ parseLiteral(ast) {
2029
+ if (ast.kind === Kind.STRING) {
2030
+ return new Date(ast.value).toISOString();
2031
+ }
2032
+ return null;
2033
+ }
2034
+ });
2035
+
2036
+ class LocalGraphQLClient {
2037
+ options;
2038
+ apollo;
2039
+ constructor(options) {
2040
+ this.options = options;
2041
+ const schema = makeExecutableSchema({
2042
+ typeDefs,
2043
+ resolvers: this.createResolvers()
2044
+ });
2045
+ this.apollo = new ApolloClient({
2046
+ cache: new InMemoryCache,
2047
+ link: new SchemaLink({
2048
+ schema,
2049
+ context: () => ({
2050
+ db: this.options.db,
2051
+ storage: this.options.storage,
2052
+ pubsub: this.options.pubsub ?? new LocalEventBus
2053
+ })
2054
+ }),
2055
+ devtools: {
2056
+ enabled: typeof window !== "undefined"
2057
+ }
2058
+ });
2059
+ }
2060
+ createResolvers() {
2061
+ return {
2062
+ DateTime: DateTimeScalar,
2063
+ Query: {
2064
+ taskCategories: async (_, args, ctx) => {
2065
+ const result = await ctx.db.query(`SELECT * FROM template_task_category WHERE "projectId" = $1 ORDER BY name ASC`, [args.projectId]);
2066
+ return result.rows.map(mapTaskCategory);
2067
+ },
2068
+ tasks: async (_, args, ctx) => {
2069
+ const result = await ctx.db.query(`SELECT * FROM template_task WHERE "projectId" = $1 ORDER BY "createdAt" DESC`, [args.projectId]);
2070
+ return result.rows.map(mapTask);
2071
+ },
2072
+ conversations: async (_, args, ctx) => {
2073
+ const result = await ctx.db.query(`SELECT * FROM template_conversation WHERE "projectId" = $1 ORDER BY "updatedAt" DESC`, [args.projectId]);
2074
+ return result.rows.map(mapConversation);
2075
+ },
2076
+ messages: async (_, args, ctx) => {
2077
+ const result = await ctx.db.query(`SELECT * FROM template_message WHERE "conversationId" = $1 ORDER BY "createdAt" DESC LIMIT $2`, [args.conversationId, args.limit]);
2078
+ return result.rows.map(mapMessage);
2079
+ },
2080
+ recipes: async (_, args, ctx) => {
2081
+ const result = await ctx.db.query(`SELECT * FROM template_recipe WHERE "projectId" = $1 ORDER BY "nameEn" ASC`, [args.projectId]);
2082
+ return result.rows.map((row) => mapRecipe(row, args.locale));
2083
+ },
2084
+ recipe: async (_, args, ctx) => {
2085
+ const result = await ctx.db.query(`SELECT * FROM template_recipe WHERE id = $1 LIMIT 1`, [args.id]);
2086
+ if (!result.rows.length || !result.rows[0])
2087
+ return null;
2088
+ return mapRecipe(result.rows[0], args.locale);
2089
+ }
2090
+ },
2091
+ Mutation: {
2092
+ createTask: async (_, args, ctx) => {
2093
+ const id = generateId("task");
2094
+ const now = new Date().toISOString();
2095
+ const tags = JSON.stringify(args.input.tags ?? []);
2096
+ await ctx.db.execute(`INSERT INTO template_task (id, "projectId", "categoryId", title, description, completed, priority, "dueDate", tags, "createdAt", "updatedAt")
2097
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`, [
2098
+ id,
2099
+ args.input.projectId,
2100
+ args.input.categoryId ?? null,
2101
+ args.input.title,
2102
+ args.input.description ?? null,
2103
+ 0,
2104
+ args.input.priority ?? "MEDIUM",
2105
+ args.input.dueDate ?? null,
2106
+ tags,
2107
+ now,
2108
+ now
2109
+ ]);
2110
+ const result = await ctx.db.query(`SELECT * FROM template_task WHERE id = $1 LIMIT 1`, [id]);
2111
+ if (!result.rows.length || !result.rows[0])
2112
+ throw new Error("Failed to create task");
2113
+ return mapTask(result.rows[0]);
2114
+ },
2115
+ updateTask: async (_, args, ctx) => {
2116
+ const now = new Date().toISOString();
2117
+ await ctx.db.execute(`UPDATE template_task
2118
+ SET "categoryId" = COALESCE($1, "categoryId"),
2119
+ title = COALESCE($2, title),
2120
+ description = COALESCE($3, description),
2121
+ priority = COALESCE($4, priority),
2122
+ "dueDate" = COALESCE($5, "dueDate"),
2123
+ tags = COALESCE($6, tags),
2124
+ "updatedAt" = $7
2125
+ WHERE id = $8`, [
2126
+ args.input.categoryId ?? null,
2127
+ args.input.title ?? null,
2128
+ args.input.description ?? null,
2129
+ args.input.priority ?? null,
2130
+ args.input.dueDate ?? null,
2131
+ args.input.tags ? JSON.stringify(args.input.tags) : null,
2132
+ now,
2133
+ args.id
2134
+ ]);
2135
+ const result = await ctx.db.query(`SELECT * FROM template_task WHERE id = $1 LIMIT 1`, [args.id]);
2136
+ if (!result.rows.length || !result.rows[0])
2137
+ throw new Error("Task not found");
2138
+ return mapTask(result.rows[0]);
2139
+ },
2140
+ toggleTask: async (_, args, ctx) => {
2141
+ const now = new Date().toISOString();
2142
+ await ctx.db.execute(`UPDATE template_task SET completed = $1, "updatedAt" = $2 WHERE id = $3`, [args.completed ? 1 : 0, now, args.id]);
2143
+ const result = await ctx.db.query(`SELECT * FROM template_task WHERE id = $1 LIMIT 1`, [args.id]);
2144
+ if (!result.rows.length || !result.rows[0])
2145
+ throw new Error("Task not found");
2146
+ return mapTask(result.rows[0]);
2147
+ },
2148
+ deleteTask: async (_, args, ctx) => {
2149
+ await ctx.db.execute(`DELETE FROM template_task WHERE id = $1`, [
2150
+ args.id
2151
+ ]);
2152
+ return true;
2153
+ },
2154
+ createConversation: async (_, args, ctx) => {
2155
+ const id = generateId("conversation");
2156
+ const now = new Date().toISOString();
2157
+ await ctx.db.execute(`INSERT INTO template_conversation (id, "projectId", name, "isGroup", "avatarUrl", "updatedAt")
2158
+ VALUES ($1, $2, $3, $4, $5, $6)`, [
2159
+ id,
2160
+ args.input.projectId,
2161
+ args.input.name ?? null,
2162
+ args.input.isGroup ? 1 : 0,
2163
+ args.input.avatarUrl ?? null,
2164
+ now
2165
+ ]);
2166
+ const participants = args.input.participants ?? [];
2167
+ for (const participant of participants) {
2168
+ await ctx.db.execute(`INSERT INTO template_conversation_participant (id, "conversationId", "projectId", "userId", "displayName", role, "joinedAt")
2169
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`, [
2170
+ generateId("participant"),
2171
+ id,
2172
+ args.input.projectId,
2173
+ participant.userId,
2174
+ participant.displayName ?? null,
2175
+ participant.role ?? null,
2176
+ now
2177
+ ]);
2178
+ }
2179
+ const result = await ctx.db.query(`SELECT * FROM template_conversation WHERE id = $1 LIMIT 1`, [id]);
2180
+ if (!result.rows.length || !result.rows[0])
2181
+ throw new Error("Failed to create conversation");
2182
+ return mapConversation(result.rows[0]);
2183
+ },
2184
+ sendMessage: async (_, args, ctx) => {
2185
+ const id = generateId("message");
2186
+ const now = new Date().toISOString();
2187
+ await ctx.db.execute(`INSERT INTO template_message (id, "conversationId", "projectId", "senderId", "senderName", content, attachments, status, "createdAt", "updatedAt")
2188
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, [
2189
+ id,
2190
+ args.input.conversationId,
2191
+ args.input.projectId,
2192
+ args.input.senderId,
2193
+ args.input.senderName ?? null,
2194
+ args.input.content,
2195
+ JSON.stringify([]),
2196
+ "SENT",
2197
+ now,
2198
+ now
2199
+ ]);
2200
+ await ctx.db.execute(`UPDATE template_conversation SET "lastMessageId" = $1, "updatedAt" = $2 WHERE id = $3`, [id, now, args.input.conversationId]);
2201
+ const result = await ctx.db.query(`SELECT * FROM template_message WHERE id = $1`, [id]);
2202
+ if (!result.rows.length || !result.rows[0])
2203
+ throw new Error("Failed to send message");
2204
+ const message = mapMessage(result.rows[0]);
2205
+ ctx.pubsub.emit("message:new", message);
2206
+ return message;
2207
+ },
2208
+ setMessagesRead: async (_, args, ctx) => {
2209
+ const now = new Date().toISOString();
2210
+ await ctx.db.execute(`UPDATE template_conversation_participant
2211
+ SET "lastReadAt" = $1
2212
+ WHERE "conversationId" = $2 AND "userId" = $3`, [now, args.conversationId, args.userId]);
2213
+ return true;
2214
+ },
2215
+ favoriteRecipe: async (_, args, ctx) => {
2216
+ const now = new Date().toISOString();
2217
+ await ctx.db.execute(`UPDATE template_recipe SET "isFavorite" = $1, "updatedAt" = $2 WHERE id = $3`, [args.isFavorite ? 1 : 0, now, args.id]);
2218
+ const result = await ctx.db.query(`SELECT * FROM template_recipe WHERE id = $1 LIMIT 1`, [args.id]);
2219
+ if (!result.rows.length || !result.rows[0])
2220
+ throw new Error("Recipe not found");
2221
+ const locale = "EN";
2222
+ return mapRecipe(result.rows[0], locale);
2223
+ }
2224
+ },
2225
+ Task: {
2226
+ category: async (parent, _, ctx) => {
2227
+ if (!parent.categoryId)
2228
+ return null;
2229
+ const result = await ctx.db.query(`SELECT * FROM template_task_category WHERE id = $1 LIMIT 1`, [parent.categoryId]);
2230
+ if (!result.rows.length || !result.rows[0])
2231
+ return null;
2232
+ return mapTaskCategory(result.rows[0]);
2233
+ }
2234
+ },
2235
+ Conversation: {
2236
+ participants: async (parent, _, ctx) => {
2237
+ const result = await ctx.db.query(`SELECT * FROM template_conversation_participant WHERE "conversationId" = $1 ORDER BY "joinedAt" ASC`, [parent.id]);
2238
+ return result.rows.map(mapParticipant);
2239
+ },
2240
+ messages: async (parent, args, ctx) => {
2241
+ const result = await ctx.db.query(`SELECT * FROM template_message WHERE "conversationId" = $1 ORDER BY "createdAt" DESC LIMIT $2`, [parent.id, args.limit]);
2242
+ return result.rows.map(mapMessage);
2243
+ }
2244
+ },
2245
+ Recipe: {
2246
+ category: async (parent, _, ctx) => {
2247
+ if (!parent.categoryId)
2248
+ return null;
2249
+ const result = await ctx.db.query(`SELECT * FROM template_recipe_category WHERE id = $1 LIMIT 1`, [parent.categoryId]);
2250
+ if (!result.rows.length || !result.rows[0])
2251
+ return null;
2252
+ return mapRecipeCategory(result.rows[0]);
2253
+ },
2254
+ ingredients: async (parent, _, ctx) => {
2255
+ const result = await ctx.db.query(`SELECT * FROM template_recipe_ingredient WHERE "recipeId" = $1 ORDER BY ordering ASC`, [parent.id]);
2256
+ return result.rows.map((row) => mapRecipeIngredient(row, parent.locale));
2257
+ },
2258
+ instructions: async (parent, _, ctx) => {
2259
+ const result = await ctx.db.query(`SELECT * FROM template_recipe_instruction WHERE "recipeId" = $1 ORDER BY ordering ASC`, [parent.id]);
2260
+ return result.rows.map((row) => mapRecipeInstruction(row, parent.locale));
2261
+ }
2262
+ }
2263
+ };
2264
+ }
2265
+ }
2266
+ function mapTaskCategory(row) {
2267
+ return {
2268
+ id: row.id,
2269
+ projectId: row.projectId,
2270
+ name: row.name,
2271
+ color: row.color,
2272
+ createdAt: row.createdAt,
2273
+ updatedAt: row.updatedAt
2274
+ };
2275
+ }
2276
+ function mapTask(row) {
2277
+ return {
2278
+ id: row.id,
2279
+ projectId: row.projectId,
2280
+ categoryId: row.categoryId,
2281
+ title: row.title,
2282
+ description: row.description,
2283
+ completed: Boolean(row.completed),
2284
+ priority: row.priority ?? "MEDIUM",
2285
+ dueDate: row.dueDate,
2286
+ tags: parseTags(row.tags),
2287
+ createdAt: row.createdAt,
2288
+ updatedAt: row.updatedAt
2289
+ };
2290
+ }
2291
+ function parseTags(value) {
2292
+ if (typeof value !== "string")
2293
+ return [];
2294
+ try {
2295
+ const parsed = JSON.parse(value);
2296
+ return Array.isArray(parsed) ? parsed : [];
2297
+ } catch {
2298
+ return [];
2299
+ }
2300
+ }
2301
+ function mapConversation(row) {
2302
+ return {
2303
+ id: row.id,
2304
+ projectId: row.projectId,
2305
+ name: row.name,
2306
+ isGroup: Boolean(row.isGroup),
2307
+ avatarUrl: row.avatarUrl,
2308
+ lastMessageId: row.lastMessageId,
2309
+ updatedAt: row.updatedAt
2310
+ };
2311
+ }
2312
+ function mapParticipant(row) {
2313
+ return {
2314
+ id: row.id,
2315
+ conversationId: row.conversationId,
2316
+ projectId: row.projectId,
2317
+ userId: row.userId,
2318
+ displayName: row.displayName,
2319
+ role: row.role,
2320
+ joinedAt: row.joinedAt,
2321
+ lastReadAt: row.lastReadAt
2322
+ };
2323
+ }
2324
+ function mapMessage(row) {
2325
+ return {
2326
+ id: row.id,
2327
+ conversationId: row.conversationId,
2328
+ projectId: row.projectId,
2329
+ senderId: row.senderId,
2330
+ senderName: row.senderName,
2331
+ content: row.content,
2332
+ attachments: [],
2333
+ status: row.status ?? "SENT",
2334
+ createdAt: row.createdAt,
2335
+ updatedAt: row.updatedAt
2336
+ };
2337
+ }
2338
+ function mapRecipe(row, locale) {
2339
+ return {
2340
+ id: row.id,
2341
+ projectId: row.projectId,
2342
+ slugEn: row.slugEn,
2343
+ slugFr: row.slugFr,
2344
+ name: locale === "FR" ? row.nameFr : row.nameEn,
2345
+ description: locale === "FR" ? row.descriptionFr : row.descriptionEn,
2346
+ heroImageUrl: row.heroImageUrl,
2347
+ prepTimeMinutes: row.prepTimeMinutes ?? null,
2348
+ cookTimeMinutes: row.cookTimeMinutes ?? null,
2349
+ servings: row.servings ?? null,
2350
+ isFavorite: Boolean(row.isFavorite),
2351
+ locale,
2352
+ categoryId: row.categoryId,
2353
+ createdAt: row.createdAt,
2354
+ updatedAt: row.updatedAt
2355
+ };
2356
+ }
2357
+ function mapRecipeCategory(row) {
2358
+ return {
2359
+ id: row.id,
2360
+ nameEn: row.nameEn,
2361
+ nameFr: row.nameFr,
2362
+ icon: row.icon
2363
+ };
2364
+ }
2365
+ function mapRecipeIngredient(row, locale) {
2366
+ return {
2367
+ id: row.id,
2368
+ name: locale === "FR" ? row.nameFr : row.nameEn,
2369
+ quantity: row.quantity,
2370
+ ordering: row.ordering ?? 0
2371
+ };
2372
+ }
2373
+ function mapRecipeInstruction(row, locale) {
2374
+ return {
2375
+ id: row.id,
2376
+ content: locale === "FR" ? row.contentFr : row.contentEn,
2377
+ ordering: row.ordering ?? 0
2378
+ };
2379
+ }
2380
+ // src/web/runtime/services.ts
2381
+ var DEFAULT_PROJECT_ID = "local-project";
2382
+
2383
+ class LocalRuntimeServices {
2384
+ storage = new LocalStorageService;
2385
+ pubsub = new LocalEventBus;
2386
+ #initialized = false;
2387
+ _db;
2388
+ get db() {
2389
+ if (!this._db) {
2390
+ throw new Error("LocalRuntimeServices not initialized. Call init() first.");
2391
+ }
2392
+ return this._db;
2393
+ }
2394
+ _graphql;
2395
+ get graphql() {
2396
+ if (!this._graphql) {
2397
+ throw new Error("LocalRuntimeServices not initialized. Call init() first.");
2398
+ }
2399
+ return this._graphql;
2400
+ }
2401
+ async init(options = {}) {
2402
+ if (this.#initialized)
2403
+ return;
2404
+ const { createPGLiteAdapter } = await import("@contractspec/lib.runtime-sandbox");
2405
+ this._db = await createPGLiteAdapter();
2406
+ await this._db.init({ dataDir: options.dataDir });
2407
+ await this._db.migrate(SANDBOX_MIGRATIONS);
2408
+ await this.storage.init();
2409
+ this._graphql = new LocalGraphQLClient({
2410
+ db: this._db,
2411
+ storage: this.storage,
2412
+ pubsub: this.pubsub
2413
+ });
2414
+ this.#initialized = true;
2415
+ }
2416
+ isInitialized() {
2417
+ return this.#initialized;
2418
+ }
2419
+ async seedTemplate(options) {
2420
+ if (!this.#initialized) {
2421
+ throw new Error("Call init() before seeding templates.");
2422
+ }
2423
+ const projectId = options.projectId ?? DEFAULT_PROJECT_ID;
2424
+ if (!this._db) {
2425
+ throw new Error("Database not initialized");
2426
+ }
2427
+ const { seedTemplate: seedTemplate2 } = await Promise.resolve().then(() => exports_seeders);
2428
+ await seedTemplate2({
2429
+ templateId: options.templateId,
2430
+ projectId,
2431
+ db: this._db
2432
+ });
2433
+ }
2434
+ }
2435
+ // src/index.ts
2436
+ async function createPGLiteAdapter() {
2437
+ const { PGLiteDatabaseAdapter: PGLiteDatabaseAdapter2 } = await Promise.resolve().then(() => (init_pglite(), exports_pglite));
2438
+ return new PGLiteDatabaseAdapter2;
2439
+ }
2440
+ export {
2441
+ exports_web as web,
2442
+ createPGLiteAdapter
2443
+ };