@contractspec/lib.runtime-sandbox 0.11.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 (63) hide show
  1. package/dist/_virtual/rolldown_runtime.js +18 -0
  2. package/dist/adapters/pglite/adapter.js +97 -0
  3. package/dist/adapters/pglite/adapter.js.map +1 -0
  4. package/dist/adapters/pglite/index.js +3 -0
  5. package/dist/index.d.ts +23 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +24 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/ports/database.port.d.ts +70 -0
  10. package/dist/ports/database.port.d.ts.map +1 -0
  11. package/dist/types/database.types.d.ts +47 -0
  12. package/dist/types/database.types.d.ts.map +1 -0
  13. package/dist/web/database/migrations.d.ts +12 -0
  14. package/dist/web/database/migrations.d.ts.map +1 -0
  15. package/dist/web/database/migrations.js +746 -0
  16. package/dist/web/database/migrations.js.map +1 -0
  17. package/dist/web/database/schema.d.ts +7349 -0
  18. package/dist/web/database/schema.d.ts.map +1 -0
  19. package/dist/web/database/schema.js +528 -0
  20. package/dist/web/database/schema.js.map +1 -0
  21. package/dist/web/events/local-pubsub.d.ts +10 -0
  22. package/dist/web/events/local-pubsub.d.ts.map +1 -0
  23. package/dist/web/events/local-pubsub.js +24 -0
  24. package/dist/web/events/local-pubsub.js.map +1 -0
  25. package/dist/web/graphql/local-client.d.ts +20 -0
  26. package/dist/web/graphql/local-client.d.ts.map +1 -0
  27. package/dist/web/graphql/local-client.js +536 -0
  28. package/dist/web/graphql/local-client.js.map +1 -0
  29. package/dist/web/index.d.ts +15 -0
  30. package/dist/web/index.d.ts.map +1 -0
  31. package/dist/web/index.js +68 -0
  32. package/dist/web/index.js.map +1 -0
  33. package/dist/web/runtime/seeders/index.js +358 -0
  34. package/dist/web/runtime/seeders/index.js.map +1 -0
  35. package/dist/web/runtime/services.d.ts +60 -0
  36. package/dist/web/runtime/services.d.ts.map +1 -0
  37. package/dist/web/runtime/services.js +80 -0
  38. package/dist/web/runtime/services.js.map +1 -0
  39. package/dist/web/storage/indexeddb.d.ts +22 -0
  40. package/dist/web/storage/indexeddb.d.ts.map +1 -0
  41. package/dist/web/storage/indexeddb.js +85 -0
  42. package/dist/web/storage/indexeddb.js.map +1 -0
  43. package/dist/web/utils/id.d.ts +5 -0
  44. package/dist/web/utils/id.d.ts.map +1 -0
  45. package/dist/web/utils/id.js +9 -0
  46. package/dist/web/utils/id.js.map +1 -0
  47. package/package.json +70 -0
  48. package/src/adapters/pglite/adapter.ts +152 -0
  49. package/src/adapters/pglite/index.ts +1 -0
  50. package/src/index.ts +41 -0
  51. package/src/ports/database.port.ts +82 -0
  52. package/src/ports/index.ts +4 -0
  53. package/src/types/database.types.ts +55 -0
  54. package/src/types/index.ts +1 -0
  55. package/src/web/database/migrations.ts +760 -0
  56. package/src/web/database/schema.ts +596 -0
  57. package/src/web/events/local-pubsub.ts +28 -0
  58. package/src/web/graphql/local-client.ts +747 -0
  59. package/src/web/index.ts +21 -0
  60. package/src/web/runtime/seeders/index.ts +449 -0
  61. package/src/web/runtime/services.ts +132 -0
  62. package/src/web/storage/indexeddb.ts +116 -0
  63. package/src/web/utils/id.ts +7 -0
@@ -0,0 +1,596 @@
1
+ /**
2
+ * Drizzle schema for sandbox runtime database tables.
3
+ *
4
+ * This schema defines all tables used by the template examples:
5
+ * - Todos (tasks, categories)
6
+ * - Messaging (conversations, participants, messages)
7
+ * - Recipes (categories, recipes, ingredients, instructions)
8
+ * - CRM (pipelines, stages, deals, companies, contacts)
9
+ * - SaaS (projects, subscriptions, usage)
10
+ * - Agent Console (tools, definitions, runs, steps, logs)
11
+ * - Workflow System (definitions, steps, instances, approvals)
12
+ * - Marketplace (stores, products, orders, items, payouts, reviews)
13
+ * - Integration Hub (integrations, connections, syncs, mappings)
14
+ * - Analytics (dashboards, widgets, queries)
15
+ * - Policy-Safe Knowledge Assistant (contexts, rules, versions, snapshots, candidates, tasks)
16
+ */
17
+ import { integer, pgTable, real, text, timestamp } from 'drizzle-orm/pg-core';
18
+
19
+ // ============ Todos Template ============
20
+
21
+ export const templateTaskCategory = pgTable('template_task_category', {
22
+ id: text('id').primaryKey(),
23
+ projectId: text('projectId').notNull(),
24
+ name: text('name').notNull(),
25
+ color: text('color'),
26
+ createdAt: timestamp('createdAt').defaultNow(),
27
+ updatedAt: timestamp('updatedAt').defaultNow(),
28
+ });
29
+
30
+ export const templateTask = pgTable('template_task', {
31
+ id: text('id').primaryKey(),
32
+ projectId: text('projectId').notNull(),
33
+ categoryId: text('categoryId'),
34
+ title: text('title').notNull(),
35
+ description: text('description'),
36
+ completed: integer('completed').default(0),
37
+ priority: text('priority').default('MEDIUM'),
38
+ dueDate: text('dueDate'),
39
+ tags: text('tags'),
40
+ createdAt: timestamp('createdAt').defaultNow(),
41
+ updatedAt: timestamp('updatedAt').defaultNow(),
42
+ });
43
+
44
+ // ============ Messaging Template ============
45
+
46
+ export const templateConversation = pgTable('template_conversation', {
47
+ id: text('id').primaryKey(),
48
+ projectId: text('projectId').notNull(),
49
+ name: text('name'),
50
+ isGroup: integer('isGroup').default(0),
51
+ avatarUrl: text('avatarUrl'),
52
+ lastMessageId: text('lastMessageId'),
53
+ createdAt: timestamp('createdAt').defaultNow(),
54
+ updatedAt: timestamp('updatedAt').defaultNow(),
55
+ });
56
+
57
+ export const templateConversationParticipant = pgTable(
58
+ 'template_conversation_participant',
59
+ {
60
+ id: text('id').primaryKey(),
61
+ conversationId: text('conversationId').notNull(),
62
+ projectId: text('projectId').notNull(),
63
+ userId: text('userId').notNull(),
64
+ displayName: text('displayName'),
65
+ role: text('role'),
66
+ joinedAt: timestamp('joinedAt').defaultNow(),
67
+ lastReadAt: text('lastReadAt'),
68
+ }
69
+ );
70
+
71
+ export const templateMessage = pgTable('template_message', {
72
+ id: text('id').primaryKey(),
73
+ conversationId: text('conversationId').notNull(),
74
+ projectId: text('projectId').notNull(),
75
+ senderId: text('senderId').notNull(),
76
+ senderName: text('senderName'),
77
+ content: text('content').notNull(),
78
+ attachments: text('attachments'),
79
+ status: text('status').default('SENT'),
80
+ createdAt: timestamp('createdAt').defaultNow(),
81
+ updatedAt: timestamp('updatedAt').defaultNow(),
82
+ });
83
+
84
+ // ============ Recipes Template ============
85
+
86
+ export const templateRecipeCategory = pgTable('template_recipe_category', {
87
+ id: text('id').primaryKey(),
88
+ nameEn: text('nameEn').notNull(),
89
+ nameFr: text('nameFr').notNull(),
90
+ icon: text('icon'),
91
+ });
92
+
93
+ export const templateRecipe = pgTable('template_recipe', {
94
+ id: text('id').primaryKey(),
95
+ projectId: text('projectId').notNull(),
96
+ categoryId: text('categoryId'),
97
+ slugEn: text('slugEn').notNull(),
98
+ slugFr: text('slugFr').notNull(),
99
+ nameEn: text('nameEn').notNull(),
100
+ nameFr: text('nameFr').notNull(),
101
+ descriptionEn: text('descriptionEn'),
102
+ descriptionFr: text('descriptionFr'),
103
+ heroImageUrl: text('heroImageUrl'),
104
+ prepTimeMinutes: integer('prepTimeMinutes'),
105
+ cookTimeMinutes: integer('cookTimeMinutes'),
106
+ servings: integer('servings'),
107
+ isFavorite: integer('isFavorite').default(0),
108
+ createdAt: timestamp('createdAt').defaultNow(),
109
+ updatedAt: timestamp('updatedAt').defaultNow(),
110
+ });
111
+
112
+ export const templateRecipeIngredient = pgTable('template_recipe_ingredient', {
113
+ id: text('id').primaryKey(),
114
+ recipeId: text('recipeId').notNull(),
115
+ nameEn: text('nameEn').notNull(),
116
+ nameFr: text('nameFr').notNull(),
117
+ quantity: text('quantity').notNull(),
118
+ ordering: integer('ordering').default(0),
119
+ });
120
+
121
+ export const templateRecipeInstruction = pgTable(
122
+ 'template_recipe_instruction',
123
+ {
124
+ id: text('id').primaryKey(),
125
+ recipeId: text('recipeId').notNull(),
126
+ contentEn: text('contentEn').notNull(),
127
+ contentFr: text('contentFr').notNull(),
128
+ ordering: integer('ordering').default(0),
129
+ }
130
+ );
131
+
132
+ // ============ CRM Template ============
133
+
134
+ export const crmPipeline = pgTable('crm_pipeline', {
135
+ id: text('id').primaryKey(),
136
+ projectId: text('projectId').notNull(),
137
+ name: text('name').notNull(),
138
+ createdAt: timestamp('createdAt').defaultNow(),
139
+ updatedAt: timestamp('updatedAt').defaultNow(),
140
+ });
141
+
142
+ export const crmStage = pgTable('crm_stage', {
143
+ id: text('id').primaryKey(),
144
+ pipelineId: text('pipelineId').notNull(),
145
+ name: text('name').notNull(),
146
+ position: integer('position').notNull(),
147
+ createdAt: timestamp('createdAt').defaultNow(),
148
+ });
149
+
150
+ export const crmDeal = pgTable('crm_deal', {
151
+ id: text('id').primaryKey(),
152
+ projectId: text('projectId').notNull(),
153
+ pipelineId: text('pipelineId').notNull(),
154
+ stageId: text('stageId').notNull(),
155
+ name: text('name').notNull(),
156
+ value: real('value').notNull().default(0),
157
+ currency: text('currency').default('USD'),
158
+ status: text('status').default('OPEN'),
159
+ contactId: text('contactId'),
160
+ companyId: text('companyId'),
161
+ ownerId: text('ownerId').notNull(),
162
+ expectedCloseDate: text('expectedCloseDate'),
163
+ wonSource: text('wonSource'),
164
+ lostReason: text('lostReason'),
165
+ notes: text('notes'),
166
+ createdAt: timestamp('createdAt').defaultNow(),
167
+ updatedAt: timestamp('updatedAt').defaultNow(),
168
+ });
169
+
170
+ export const crmCompany = pgTable('crm_company', {
171
+ id: text('id').primaryKey(),
172
+ projectId: text('projectId').notNull(),
173
+ name: text('name').notNull(),
174
+ domain: text('domain'),
175
+ industry: text('industry'),
176
+ size: text('size'),
177
+ website: text('website'),
178
+ createdAt: timestamp('createdAt').defaultNow(),
179
+ updatedAt: timestamp('updatedAt').defaultNow(),
180
+ });
181
+
182
+ export const crmContact = pgTable('crm_contact', {
183
+ id: text('id').primaryKey(),
184
+ projectId: text('projectId').notNull(),
185
+ companyId: text('companyId'),
186
+ firstName: text('firstName').notNull(),
187
+ lastName: text('lastName'),
188
+ email: text('email'),
189
+ phone: text('phone'),
190
+ title: text('title'),
191
+ createdAt: timestamp('createdAt').defaultNow(),
192
+ updatedAt: timestamp('updatedAt').defaultNow(),
193
+ });
194
+
195
+ // ============ SaaS Template ============
196
+
197
+ export const saasProject = pgTable('saas_project', {
198
+ id: text('id').primaryKey(),
199
+ projectId: text('projectId').notNull(),
200
+ organizationId: text('organizationId').notNull(),
201
+ name: text('name').notNull(),
202
+ description: text('description'),
203
+ status: text('status').default('DRAFT'),
204
+ tier: text('tier').default('FREE'),
205
+ createdAt: timestamp('createdAt').defaultNow(),
206
+ updatedAt: timestamp('updatedAt').defaultNow(),
207
+ });
208
+
209
+ export const saasSubscription = pgTable('saas_subscription', {
210
+ id: text('id').primaryKey(),
211
+ projectId: text('projectId').notNull(),
212
+ organizationId: text('organizationId').notNull(),
213
+ plan: text('plan').notNull().default('FREE'),
214
+ status: text('status').default('ACTIVE'),
215
+ billingCycle: text('billingCycle').default('MONTHLY'),
216
+ currentPeriodStart: text('currentPeriodStart'),
217
+ currentPeriodEnd: text('currentPeriodEnd'),
218
+ cancelAtPeriodEnd: integer('cancelAtPeriodEnd').default(0),
219
+ createdAt: timestamp('createdAt').defaultNow(),
220
+ updatedAt: timestamp('updatedAt').defaultNow(),
221
+ });
222
+
223
+ export const saasUsage = pgTable('saas_usage', {
224
+ id: text('id').primaryKey(),
225
+ projectId: text('projectId').notNull(),
226
+ organizationId: text('organizationId').notNull(),
227
+ metricName: text('metricName').notNull(),
228
+ value: real('value').notNull().default(0),
229
+ periodStart: text('periodStart').notNull(),
230
+ periodEnd: text('periodEnd').notNull(),
231
+ createdAt: timestamp('createdAt').defaultNow(),
232
+ });
233
+
234
+ // ============ Agent Console Template ============
235
+
236
+ export const agentTool = pgTable('agent_tool', {
237
+ id: text('id').primaryKey(),
238
+ projectId: text('projectId').notNull(),
239
+ organizationId: text('organizationId').notNull(),
240
+ name: text('name').notNull(),
241
+ description: text('description'),
242
+ version: text('version').default('1.0.0'),
243
+ category: text('category').default('UTILITY'),
244
+ status: text('status').default('ACTIVE'),
245
+ inputSchema: text('inputSchema'),
246
+ outputSchema: text('outputSchema'),
247
+ endpoint: text('endpoint'),
248
+ createdAt: timestamp('createdAt').defaultNow(),
249
+ updatedAt: timestamp('updatedAt').defaultNow(),
250
+ });
251
+
252
+ export const agentDefinition = pgTable('agent_definition', {
253
+ id: text('id').primaryKey(),
254
+ projectId: text('projectId').notNull(),
255
+ organizationId: text('organizationId').notNull(),
256
+ name: text('name').notNull(),
257
+ description: text('description'),
258
+ modelProvider: text('modelProvider').default('openai'),
259
+ modelName: text('modelName').default('gpt-4'),
260
+ systemPrompt: text('systemPrompt'),
261
+ temperature: real('temperature').default(0.7),
262
+ maxTokens: integer('maxTokens').default(4096),
263
+ status: text('status').default('DRAFT'),
264
+ createdAt: timestamp('createdAt').defaultNow(),
265
+ updatedAt: timestamp('updatedAt').defaultNow(),
266
+ });
267
+
268
+ export const agentToolAssignment = pgTable('agent_tool_assignment', {
269
+ id: text('id').primaryKey(),
270
+ agentId: text('agentId').notNull(),
271
+ toolId: text('toolId').notNull(),
272
+ assignedAt: timestamp('assignedAt').defaultNow(),
273
+ });
274
+
275
+ export const agentRun = pgTable('agent_run', {
276
+ id: text('id').primaryKey(),
277
+ projectId: text('projectId').notNull(),
278
+ agentId: text('agentId').notNull(),
279
+ status: text('status').default('QUEUED'),
280
+ input: text('input'),
281
+ output: text('output'),
282
+ totalTokens: integer('totalTokens').default(0),
283
+ promptTokens: integer('promptTokens').default(0),
284
+ completionTokens: integer('completionTokens').default(0),
285
+ estimatedCostUsd: real('estimatedCostUsd').default(0),
286
+ durationMs: integer('durationMs'),
287
+ errorMessage: text('errorMessage'),
288
+ queuedAt: timestamp('queuedAt').defaultNow(),
289
+ startedAt: text('startedAt'),
290
+ completedAt: text('completedAt'),
291
+ });
292
+
293
+ export const agentRunStep = pgTable('agent_run_step', {
294
+ id: text('id').primaryKey(),
295
+ runId: text('runId').notNull(),
296
+ stepNumber: integer('stepNumber').notNull(),
297
+ type: text('type').notNull(),
298
+ toolId: text('toolId'),
299
+ toolInput: text('toolInput'),
300
+ toolOutput: text('toolOutput'),
301
+ reasoning: text('reasoning'),
302
+ tokensUsed: integer('tokensUsed').default(0),
303
+ durationMs: integer('durationMs'),
304
+ status: text('status').default('PENDING'),
305
+ createdAt: timestamp('createdAt').defaultNow(),
306
+ });
307
+
308
+ export const agentRunLog = pgTable('agent_run_log', {
309
+ id: text('id').primaryKey(),
310
+ runId: text('runId').notNull(),
311
+ level: text('level').default('INFO'),
312
+ message: text('message').notNull(),
313
+ metadata: text('metadata'),
314
+ timestamp: timestamp('timestamp').defaultNow(),
315
+ });
316
+
317
+ // ============ Workflow Template ============
318
+
319
+ export const workflowDefinition = pgTable('workflow_definition', {
320
+ id: text('id').primaryKey(),
321
+ projectId: text('projectId').notNull(),
322
+ organizationId: text('organizationId').notNull(),
323
+ name: text('name').notNull(),
324
+ description: text('description'),
325
+ type: text('type').default('APPROVAL'),
326
+ status: text('status').default('DRAFT'),
327
+ createdAt: timestamp('createdAt').defaultNow(),
328
+ updatedAt: timestamp('updatedAt').defaultNow(),
329
+ });
330
+
331
+ export const workflowStep = pgTable('workflow_step', {
332
+ id: text('id').primaryKey(),
333
+ definitionId: text('definitionId').notNull(),
334
+ name: text('name').notNull(),
335
+ description: text('description'),
336
+ stepOrder: integer('stepOrder').notNull(),
337
+ type: text('type').default('APPROVAL'),
338
+ requiredRoles: text('requiredRoles'),
339
+ autoApproveCondition: text('autoApproveCondition'),
340
+ timeoutHours: integer('timeoutHours'),
341
+ createdAt: timestamp('createdAt').defaultNow(),
342
+ });
343
+
344
+ export const workflowInstance = pgTable('workflow_instance', {
345
+ id: text('id').primaryKey(),
346
+ projectId: text('projectId').notNull(),
347
+ definitionId: text('definitionId').notNull(),
348
+ status: text('status').default('PENDING'),
349
+ currentStepId: text('currentStepId'),
350
+ data: text('data'),
351
+ requestedBy: text('requestedBy').notNull(),
352
+ startedAt: timestamp('startedAt').defaultNow(),
353
+ completedAt: text('completedAt'),
354
+ });
355
+
356
+ export const workflowApproval = pgTable('workflow_approval', {
357
+ id: text('id').primaryKey(),
358
+ instanceId: text('instanceId').notNull(),
359
+ stepId: text('stepId').notNull(),
360
+ status: text('status').default('PENDING'),
361
+ actorId: text('actorId'),
362
+ comment: text('comment'),
363
+ decidedAt: text('decidedAt'),
364
+ createdAt: timestamp('createdAt').defaultNow(),
365
+ });
366
+
367
+ // ============ Marketplace Template ============
368
+
369
+ export const marketplaceStore = pgTable('marketplace_store', {
370
+ id: text('id').primaryKey(),
371
+ projectId: text('projectId').notNull(),
372
+ organizationId: text('organizationId').notNull(),
373
+ name: text('name').notNull(),
374
+ description: text('description'),
375
+ status: text('status').default('PENDING'),
376
+ rating: real('rating').default(0),
377
+ reviewCount: integer('reviewCount').default(0),
378
+ createdAt: timestamp('createdAt').defaultNow(),
379
+ updatedAt: timestamp('updatedAt').defaultNow(),
380
+ });
381
+
382
+ export const marketplaceProduct = pgTable('marketplace_product', {
383
+ id: text('id').primaryKey(),
384
+ storeId: text('storeId').notNull(),
385
+ name: text('name').notNull(),
386
+ description: text('description'),
387
+ price: real('price').notNull().default(0),
388
+ currency: text('currency').default('USD'),
389
+ status: text('status').default('DRAFT'),
390
+ stock: integer('stock').default(0),
391
+ category: text('category'),
392
+ imageUrl: text('imageUrl'),
393
+ createdAt: timestamp('createdAt').defaultNow(),
394
+ updatedAt: timestamp('updatedAt').defaultNow(),
395
+ });
396
+
397
+ export const marketplaceOrder = pgTable('marketplace_order', {
398
+ id: text('id').primaryKey(),
399
+ projectId: text('projectId').notNull(),
400
+ storeId: text('storeId').notNull(),
401
+ customerId: text('customerId').notNull(),
402
+ status: text('status').default('PENDING'),
403
+ total: real('total').notNull().default(0),
404
+ currency: text('currency').default('USD'),
405
+ shippingAddress: text('shippingAddress'),
406
+ createdAt: timestamp('createdAt').defaultNow(),
407
+ updatedAt: timestamp('updatedAt').defaultNow(),
408
+ });
409
+
410
+ export const marketplaceOrderItem = pgTable('marketplace_order_item', {
411
+ id: text('id').primaryKey(),
412
+ orderId: text('orderId').notNull(),
413
+ productId: text('productId').notNull(),
414
+ quantity: integer('quantity').notNull().default(1),
415
+ price: real('price').notNull().default(0),
416
+ createdAt: timestamp('createdAt').defaultNow(),
417
+ });
418
+
419
+ export const marketplacePayout = pgTable('marketplace_payout', {
420
+ id: text('id').primaryKey(),
421
+ storeId: text('storeId').notNull(),
422
+ amount: real('amount').notNull().default(0),
423
+ currency: text('currency').default('USD'),
424
+ status: text('status').default('PENDING'),
425
+ processedAt: text('processedAt'),
426
+ createdAt: timestamp('createdAt').defaultNow(),
427
+ });
428
+
429
+ export const marketplaceReview = pgTable('marketplace_review', {
430
+ id: text('id').primaryKey(),
431
+ productId: text('productId').notNull(),
432
+ customerId: text('customerId').notNull(),
433
+ orderId: text('orderId'),
434
+ rating: integer('rating').notNull(),
435
+ comment: text('comment'),
436
+ createdAt: timestamp('createdAt').defaultNow(),
437
+ });
438
+
439
+ // ============ Integration Hub Template ============
440
+
441
+ export const integration = pgTable('integration', {
442
+ id: text('id').primaryKey(),
443
+ projectId: text('projectId').notNull(),
444
+ organizationId: text('organizationId').notNull(),
445
+ name: text('name').notNull(),
446
+ description: text('description'),
447
+ type: text('type').notNull(),
448
+ status: text('status').default('INACTIVE'),
449
+ iconUrl: text('iconUrl'),
450
+ createdAt: timestamp('createdAt').defaultNow(),
451
+ updatedAt: timestamp('updatedAt').defaultNow(),
452
+ });
453
+
454
+ export const integrationConnection = pgTable('integration_connection', {
455
+ id: text('id').primaryKey(),
456
+ integrationId: text('integrationId').notNull(),
457
+ name: text('name').notNull(),
458
+ status: text('status').default('DISCONNECTED'),
459
+ credentials: text('credentials'),
460
+ config: text('config'),
461
+ lastSyncAt: text('lastSyncAt'),
462
+ errorMessage: text('errorMessage'),
463
+ createdAt: timestamp('createdAt').defaultNow(),
464
+ updatedAt: timestamp('updatedAt').defaultNow(),
465
+ });
466
+
467
+ export const integrationSyncConfig = pgTable('integration_sync_config', {
468
+ id: text('id').primaryKey(),
469
+ connectionId: text('connectionId').notNull(),
470
+ name: text('name').notNull(),
471
+ sourceEntity: text('sourceEntity').notNull(),
472
+ targetEntity: text('targetEntity').notNull(),
473
+ frequency: text('frequency').default('DAILY'),
474
+ status: text('status').default('ACTIVE'),
475
+ lastRunAt: text('lastRunAt'),
476
+ lastRunStatus: text('lastRunStatus'),
477
+ recordsSynced: integer('recordsSynced').default(0),
478
+ createdAt: timestamp('createdAt').defaultNow(),
479
+ updatedAt: timestamp('updatedAt').defaultNow(),
480
+ });
481
+
482
+ export const integrationFieldMapping = pgTable('integration_field_mapping', {
483
+ id: text('id').primaryKey(),
484
+ syncConfigId: text('syncConfigId').notNull(),
485
+ sourceField: text('sourceField').notNull(),
486
+ targetField: text('targetField').notNull(),
487
+ transformType: text('transformType'),
488
+ transformConfig: text('transformConfig'),
489
+ createdAt: timestamp('createdAt').defaultNow(),
490
+ });
491
+
492
+ // ============ Analytics Dashboard Template ============
493
+
494
+ export const analyticsDashboard = pgTable('analytics_dashboard', {
495
+ id: text('id').primaryKey(),
496
+ projectId: text('projectId').notNull(),
497
+ organizationId: text('organizationId').notNull(),
498
+ name: text('name').notNull(),
499
+ slug: text('slug').notNull(),
500
+ description: text('description'),
501
+ status: text('status').default('DRAFT'),
502
+ refreshInterval: text('refreshInterval').default('NONE'),
503
+ isPublic: integer('isPublic').default(0),
504
+ shareToken: text('shareToken'),
505
+ createdAt: timestamp('createdAt').defaultNow(),
506
+ updatedAt: timestamp('updatedAt').defaultNow(),
507
+ });
508
+
509
+ export const analyticsWidget = pgTable('analytics_widget', {
510
+ id: text('id').primaryKey(),
511
+ dashboardId: text('dashboardId').notNull(),
512
+ name: text('name').notNull(),
513
+ type: text('type').notNull(),
514
+ gridX: integer('gridX').default(0),
515
+ gridY: integer('gridY').default(0),
516
+ gridWidth: integer('gridWidth').default(6),
517
+ gridHeight: integer('gridHeight').default(4),
518
+ queryId: text('queryId'),
519
+ config: text('config'),
520
+ createdAt: timestamp('createdAt').defaultNow(),
521
+ updatedAt: timestamp('updatedAt').defaultNow(),
522
+ });
523
+
524
+ export const analyticsQuery = pgTable('analytics_query', {
525
+ id: text('id').primaryKey(),
526
+ projectId: text('projectId').notNull(),
527
+ organizationId: text('organizationId').notNull(),
528
+ name: text('name').notNull(),
529
+ description: text('description'),
530
+ type: text('type').notNull(),
531
+ definition: text('definition').notNull(),
532
+ sql: text('sql'),
533
+ cacheTtlSeconds: integer('cacheTtlSeconds').default(300),
534
+ isShared: integer('isShared').default(0),
535
+ createdAt: timestamp('createdAt').defaultNow(),
536
+ updatedAt: timestamp('updatedAt').defaultNow(),
537
+ });
538
+
539
+ // ============ Policy-Safe Knowledge Assistant Template ============
540
+
541
+ export const psaUserContext = pgTable('psa_user_context', {
542
+ projectId: text('projectId').primaryKey(),
543
+ locale: text('locale').notNull(),
544
+ jurisdiction: text('jurisdiction').notNull(),
545
+ allowedScope: text('allowedScope').notNull(),
546
+ kbSnapshotId: text('kbSnapshotId'),
547
+ });
548
+
549
+ export const psaRule = pgTable('psa_rule', {
550
+ id: text('id').primaryKey(),
551
+ projectId: text('projectId').notNull(),
552
+ jurisdiction: text('jurisdiction').notNull(),
553
+ topicKey: text('topicKey').notNull(),
554
+ });
555
+
556
+ export const psaRuleVersion = pgTable('psa_rule_version', {
557
+ id: text('id').primaryKey(),
558
+ ruleId: text('ruleId').notNull(),
559
+ jurisdiction: text('jurisdiction').notNull(),
560
+ topicKey: text('topicKey').notNull(),
561
+ version: integer('version').notNull(),
562
+ content: text('content').notNull(),
563
+ status: text('status').notNull(),
564
+ sourceRefsJson: text('sourceRefsJson').notNull(),
565
+ approvedBy: text('approvedBy'),
566
+ approvedAt: text('approvedAt'),
567
+ createdAt: timestamp('createdAt').defaultNow(),
568
+ });
569
+
570
+ export const psaSnapshot = pgTable('psa_snapshot', {
571
+ id: text('id').primaryKey(),
572
+ jurisdiction: text('jurisdiction').notNull(),
573
+ asOfDate: text('asOfDate').notNull(),
574
+ includedRuleVersionIdsJson: text('includedRuleVersionIdsJson').notNull(),
575
+ publishedAt: text('publishedAt').notNull(),
576
+ });
577
+
578
+ export const psaChangeCandidate = pgTable('psa_change_candidate', {
579
+ id: text('id').primaryKey(),
580
+ projectId: text('projectId').notNull(),
581
+ jurisdiction: text('jurisdiction').notNull(),
582
+ detectedAt: text('detectedAt').notNull(),
583
+ diffSummary: text('diffSummary').notNull(),
584
+ riskLevel: text('riskLevel').notNull(),
585
+ proposedRuleVersionIdsJson: text('proposedRuleVersionIdsJson').notNull(),
586
+ });
587
+
588
+ export const psaReviewTask = pgTable('psa_review_task', {
589
+ id: text('id').primaryKey(),
590
+ changeCandidateId: text('changeCandidateId').notNull(),
591
+ status: text('status').notNull(),
592
+ assignedRole: text('assignedRole').notNull(),
593
+ decision: text('decision'),
594
+ decidedAt: text('decidedAt'),
595
+ decidedBy: text('decidedBy'),
596
+ });
@@ -0,0 +1,28 @@
1
+ type Listener<TPayload> = (payload: TPayload) => void;
2
+
3
+ export class LocalEventBus {
4
+ private listeners = new Map<string, Set<Listener<unknown>>>();
5
+
6
+ emit<TPayload = unknown>(event: string, payload: TPayload): void {
7
+ const listeners = this.listeners.get(event);
8
+ if (!listeners) return;
9
+ for (const listener of listeners) {
10
+ listener(payload);
11
+ }
12
+ }
13
+
14
+ subscribe<TPayload = unknown>(
15
+ event: string,
16
+ listener: Listener<TPayload>
17
+ ): () => void {
18
+ let listeners = this.listeners.get(event);
19
+ if (!listeners) {
20
+ listeners = new Set();
21
+ this.listeners.set(event, listeners);
22
+ }
23
+ listeners.add(listener as Listener<unknown>);
24
+ return () => {
25
+ listeners.delete(listener as Listener<unknown>);
26
+ };
27
+ }
28
+ }