@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.
- package/dist/_virtual/rolldown_runtime.js +18 -0
- package/dist/adapters/pglite/adapter.js +97 -0
- package/dist/adapters/pglite/adapter.js.map +1 -0
- package/dist/adapters/pglite/index.js +3 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/ports/database.port.d.ts +70 -0
- package/dist/ports/database.port.d.ts.map +1 -0
- package/dist/types/database.types.d.ts +47 -0
- package/dist/types/database.types.d.ts.map +1 -0
- package/dist/web/database/migrations.d.ts +12 -0
- package/dist/web/database/migrations.d.ts.map +1 -0
- package/dist/web/database/migrations.js +746 -0
- package/dist/web/database/migrations.js.map +1 -0
- package/dist/web/database/schema.d.ts +7349 -0
- package/dist/web/database/schema.d.ts.map +1 -0
- package/dist/web/database/schema.js +528 -0
- package/dist/web/database/schema.js.map +1 -0
- package/dist/web/events/local-pubsub.d.ts +10 -0
- package/dist/web/events/local-pubsub.d.ts.map +1 -0
- package/dist/web/events/local-pubsub.js +24 -0
- package/dist/web/events/local-pubsub.js.map +1 -0
- package/dist/web/graphql/local-client.d.ts +20 -0
- package/dist/web/graphql/local-client.d.ts.map +1 -0
- package/dist/web/graphql/local-client.js +536 -0
- package/dist/web/graphql/local-client.js.map +1 -0
- package/dist/web/index.d.ts +15 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +68 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/runtime/seeders/index.js +358 -0
- package/dist/web/runtime/seeders/index.js.map +1 -0
- package/dist/web/runtime/services.d.ts +60 -0
- package/dist/web/runtime/services.d.ts.map +1 -0
- package/dist/web/runtime/services.js +80 -0
- package/dist/web/runtime/services.js.map +1 -0
- package/dist/web/storage/indexeddb.d.ts +22 -0
- package/dist/web/storage/indexeddb.d.ts.map +1 -0
- package/dist/web/storage/indexeddb.js +85 -0
- package/dist/web/storage/indexeddb.js.map +1 -0
- package/dist/web/utils/id.d.ts +5 -0
- package/dist/web/utils/id.d.ts.map +1 -0
- package/dist/web/utils/id.js +9 -0
- package/dist/web/utils/id.js.map +1 -0
- package/package.json +70 -0
- package/src/adapters/pglite/adapter.ts +152 -0
- package/src/adapters/pglite/index.ts +1 -0
- package/src/index.ts +41 -0
- package/src/ports/database.port.ts +82 -0
- package/src/ports/index.ts +4 -0
- package/src/types/database.types.ts +55 -0
- package/src/types/index.ts +1 -0
- package/src/web/database/migrations.ts +760 -0
- package/src/web/database/schema.ts +596 -0
- package/src/web/events/local-pubsub.ts +28 -0
- package/src/web/graphql/local-client.ts +747 -0
- package/src/web/index.ts +21 -0
- package/src/web/runtime/seeders/index.ts +449 -0
- package/src/web/runtime/services.ts +132 -0
- package/src/web/storage/indexeddb.ts +116 -0
- package/src/web/utils/id.ts +7 -0
package/src/web/index.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Re-export database schema and types
|
|
2
|
+
export * from './database/migrations';
|
|
3
|
+
export * from './database/schema';
|
|
4
|
+
|
|
5
|
+
// Storage
|
|
6
|
+
export * from './storage/indexeddb';
|
|
7
|
+
|
|
8
|
+
// Utils
|
|
9
|
+
export * from './utils/id';
|
|
10
|
+
|
|
11
|
+
// GraphQL client
|
|
12
|
+
export * from './graphql/local-client';
|
|
13
|
+
|
|
14
|
+
// Event bus
|
|
15
|
+
export * from './events/local-pubsub';
|
|
16
|
+
|
|
17
|
+
// Runtime services
|
|
18
|
+
export * from './runtime/services';
|
|
19
|
+
|
|
20
|
+
// Handlers (Deprecated/Removed)
|
|
21
|
+
// export * from './handlers';
|
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seed template data into the sandbox database.
|
|
3
|
+
*
|
|
4
|
+
* Lazy-loads individual seeders to avoid bundle bloat.
|
|
5
|
+
*/
|
|
6
|
+
import type { DatabasePort } from '@contractspec/lib.runtime-sandbox';
|
|
7
|
+
import type { TemplateId } from '../services';
|
|
8
|
+
|
|
9
|
+
export interface SeedTemplateParams {
|
|
10
|
+
templateId: TemplateId;
|
|
11
|
+
projectId: string;
|
|
12
|
+
db: DatabasePort;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Seed the database with template-specific data.
|
|
17
|
+
*
|
|
18
|
+
* Unknown templates are a no-op (safe default).
|
|
19
|
+
*/
|
|
20
|
+
export async function seedTemplate(params: SeedTemplateParams): Promise<void> {
|
|
21
|
+
const { templateId, projectId, db } = params;
|
|
22
|
+
|
|
23
|
+
switch (templateId) {
|
|
24
|
+
case 'todos-app':
|
|
25
|
+
return seedTodos({ projectId, db });
|
|
26
|
+
case 'messaging-app':
|
|
27
|
+
return seedMessaging({ projectId, db });
|
|
28
|
+
case 'recipe-app-i18n':
|
|
29
|
+
return seedRecipes({ projectId, db });
|
|
30
|
+
case 'crm-pipeline':
|
|
31
|
+
return seedCrmPipeline({ projectId, db });
|
|
32
|
+
case 'saas-boilerplate':
|
|
33
|
+
return seedSaasBoilerplate({ projectId, db });
|
|
34
|
+
case 'agent-console':
|
|
35
|
+
return seedAgentConsole({ projectId, db });
|
|
36
|
+
case 'workflow-system':
|
|
37
|
+
return seedWorkflowSystem({ projectId, db });
|
|
38
|
+
case 'marketplace':
|
|
39
|
+
return seedMarketplace({ projectId, db });
|
|
40
|
+
case 'integration-hub':
|
|
41
|
+
return seedIntegrationHub({ projectId, db });
|
|
42
|
+
case 'analytics-dashboard':
|
|
43
|
+
return seedAnalyticsDashboard({ projectId, db });
|
|
44
|
+
case 'policy-safe-knowledge-assistant':
|
|
45
|
+
return seedPolicyKnowledgeAssistant({ projectId, db });
|
|
46
|
+
default:
|
|
47
|
+
// Unknown template - no-op
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// --------------------------------------------------------------------------
|
|
53
|
+
// Individual seeder functions with PostgreSQL syntax
|
|
54
|
+
// --------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
async function seedTodos(params: { projectId: string; db: DatabasePort }) {
|
|
57
|
+
const { projectId, db } = params;
|
|
58
|
+
|
|
59
|
+
// Check if already seeded
|
|
60
|
+
const existing = await db.query(
|
|
61
|
+
`SELECT COUNT(*) as count FROM template_task WHERE "projectId" = $1`,
|
|
62
|
+
[projectId]
|
|
63
|
+
);
|
|
64
|
+
if ((existing.rows[0]?.count as number) > 0) return;
|
|
65
|
+
|
|
66
|
+
const workCategoryId = 'todo_category_ops';
|
|
67
|
+
const homeCategoryId = 'todo_category_home';
|
|
68
|
+
|
|
69
|
+
await db.execute(
|
|
70
|
+
`INSERT INTO template_task_category (id, "projectId", name, color) VALUES ($1, $2, $3, $4)`,
|
|
71
|
+
[workCategoryId, projectId, 'Operations', '#8b5cf6']
|
|
72
|
+
);
|
|
73
|
+
await db.execute(
|
|
74
|
+
`INSERT INTO template_task_category (id, "projectId", name, color) VALUES ($1, $2, $3, $4)`,
|
|
75
|
+
[homeCategoryId, projectId, 'Home', '#f472b6']
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const tasks = [
|
|
79
|
+
{
|
|
80
|
+
id: 'todo_task_1',
|
|
81
|
+
title: 'Review intent signals',
|
|
82
|
+
description: "Scan yesterday's signals and flag the ones to promote.",
|
|
83
|
+
categoryId: workCategoryId,
|
|
84
|
+
priority: 'HIGH',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 'todo_task_2',
|
|
88
|
+
title: 'Schedule studio walkthrough',
|
|
89
|
+
description: "Prep the sandbox before tomorrow's ceremony.",
|
|
90
|
+
categoryId: workCategoryId,
|
|
91
|
+
priority: 'MEDIUM',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'todo_task_3',
|
|
95
|
+
title: 'Collect testimonials',
|
|
96
|
+
description: "Ask last week's pilot crew for quotes.",
|
|
97
|
+
categoryId: homeCategoryId,
|
|
98
|
+
priority: 'LOW',
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
for (const task of tasks) {
|
|
103
|
+
await db.execute(
|
|
104
|
+
`INSERT INTO template_task (id, "projectId", "categoryId", title, description, completed, priority, tags)
|
|
105
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
|
|
106
|
+
[
|
|
107
|
+
task.id,
|
|
108
|
+
projectId,
|
|
109
|
+
task.categoryId,
|
|
110
|
+
task.title,
|
|
111
|
+
task.description,
|
|
112
|
+
0,
|
|
113
|
+
task.priority,
|
|
114
|
+
JSON.stringify(['demo']),
|
|
115
|
+
]
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function seedMessaging(params: { projectId: string; db: DatabasePort }) {
|
|
121
|
+
const { projectId, db } = params;
|
|
122
|
+
|
|
123
|
+
const existing = await db.query(
|
|
124
|
+
`SELECT COUNT(*) as count FROM template_conversation WHERE "projectId" = $1`,
|
|
125
|
+
[projectId]
|
|
126
|
+
);
|
|
127
|
+
if ((existing.rows[0]?.count as number) > 0) return;
|
|
128
|
+
|
|
129
|
+
const conversationId = 'conv_demo_1';
|
|
130
|
+
const now = new Date().toISOString();
|
|
131
|
+
|
|
132
|
+
await db.execute(
|
|
133
|
+
`INSERT INTO template_conversation (id, "projectId", name, "isGroup", "avatarUrl", "updatedAt")
|
|
134
|
+
VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
135
|
+
[conversationId, projectId, 'Team Chat', 1, null, now]
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// Add participants
|
|
139
|
+
await db.execute(
|
|
140
|
+
`INSERT INTO template_conversation_participant (id, "conversationId", "projectId", "userId", "displayName", role, "joinedAt")
|
|
141
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
142
|
+
['part_1', conversationId, projectId, 'user_alice', 'Alice', 'member', now]
|
|
143
|
+
);
|
|
144
|
+
await db.execute(
|
|
145
|
+
`INSERT INTO template_conversation_participant (id, "conversationId", "projectId", "userId", "displayName", role, "joinedAt")
|
|
146
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
147
|
+
['part_2', conversationId, projectId, 'user_bob', 'Bob', 'member', now]
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
// Add messages
|
|
151
|
+
await db.execute(
|
|
152
|
+
`INSERT INTO template_message (id, "conversationId", "projectId", "senderId", "senderName", content, attachments, status, "createdAt", "updatedAt")
|
|
153
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
|
|
154
|
+
[
|
|
155
|
+
'msg_1',
|
|
156
|
+
conversationId,
|
|
157
|
+
projectId,
|
|
158
|
+
'user_alice',
|
|
159
|
+
'Alice',
|
|
160
|
+
'Hey team! Ready for the demo?',
|
|
161
|
+
JSON.stringify([]),
|
|
162
|
+
'DELIVERED',
|
|
163
|
+
now,
|
|
164
|
+
now,
|
|
165
|
+
]
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function seedRecipes(params: { projectId: string; db: DatabasePort }) {
|
|
170
|
+
const { projectId, db } = params;
|
|
171
|
+
|
|
172
|
+
const existing = await db.query(
|
|
173
|
+
`SELECT COUNT(*) as count FROM template_recipe WHERE "projectId" = $1`,
|
|
174
|
+
[projectId]
|
|
175
|
+
);
|
|
176
|
+
if ((existing.rows[0]?.count as number) > 0) return;
|
|
177
|
+
|
|
178
|
+
// Add category
|
|
179
|
+
await db.execute(
|
|
180
|
+
`INSERT INTO template_recipe_category (id, "nameEn", "nameFr", icon) VALUES ($1, $2, $3, $4)`,
|
|
181
|
+
['cat_main', 'Main Courses', 'Plats Principaux', '🍽️']
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
// Add recipe
|
|
185
|
+
const recipeId = 'recipe_1';
|
|
186
|
+
await db.execute(
|
|
187
|
+
`INSERT INTO template_recipe (id, "projectId", "categoryId", "slugEn", "slugFr", "nameEn", "nameFr", "descriptionEn", "descriptionFr", "prepTimeMinutes", "cookTimeMinutes", servings, "isFavorite")
|
|
188
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`,
|
|
189
|
+
[
|
|
190
|
+
recipeId,
|
|
191
|
+
projectId,
|
|
192
|
+
'cat_main',
|
|
193
|
+
'grilled-salmon',
|
|
194
|
+
'saumon-grille',
|
|
195
|
+
'Grilled Salmon',
|
|
196
|
+
'Saumon Grillé',
|
|
197
|
+
'Delicious grilled salmon with lemon',
|
|
198
|
+
'Délicieux saumon grillé au citron',
|
|
199
|
+
10,
|
|
200
|
+
15,
|
|
201
|
+
4,
|
|
202
|
+
0,
|
|
203
|
+
]
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// Add ingredients
|
|
207
|
+
await db.execute(
|
|
208
|
+
`INSERT INTO template_recipe_ingredient (id, "recipeId", "nameEn", "nameFr", quantity, ordering)
|
|
209
|
+
VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
210
|
+
['ing_1', recipeId, 'Salmon fillet', 'Filet de saumon', '4 pieces', 1]
|
|
211
|
+
);
|
|
212
|
+
await db.execute(
|
|
213
|
+
`INSERT INTO template_recipe_ingredient (id, "recipeId", "nameEn", "nameFr", quantity, ordering)
|
|
214
|
+
VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
215
|
+
['ing_2', recipeId, 'Lemon', 'Citron', '1 whole', 2]
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
// Add instructions
|
|
219
|
+
await db.execute(
|
|
220
|
+
`INSERT INTO template_recipe_instruction (id, "recipeId", "contentEn", "contentFr", ordering)
|
|
221
|
+
VALUES ($1, $2, $3, $4, $5)`,
|
|
222
|
+
[
|
|
223
|
+
'inst_1',
|
|
224
|
+
recipeId,
|
|
225
|
+
'Preheat grill to medium-high heat.',
|
|
226
|
+
'Préchauffer le grill à feu moyen-vif.',
|
|
227
|
+
1,
|
|
228
|
+
]
|
|
229
|
+
);
|
|
230
|
+
await db.execute(
|
|
231
|
+
`INSERT INTO template_recipe_instruction (id, "recipeId", "contentEn", "contentFr", ordering)
|
|
232
|
+
VALUES ($1, $2, $3, $4, $5)`,
|
|
233
|
+
[
|
|
234
|
+
'inst_2',
|
|
235
|
+
recipeId,
|
|
236
|
+
'Season salmon and grill for 4-5 minutes per side.',
|
|
237
|
+
'Assaisonner le saumon et griller 4-5 minutes de chaque côté.',
|
|
238
|
+
2,
|
|
239
|
+
]
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function seedCrmPipeline(params: {
|
|
244
|
+
projectId: string;
|
|
245
|
+
db: DatabasePort;
|
|
246
|
+
}) {
|
|
247
|
+
const { projectId, db } = params;
|
|
248
|
+
|
|
249
|
+
const existing = await db.query(
|
|
250
|
+
`SELECT COUNT(*) as count FROM crm_pipeline WHERE "projectId" = $1`,
|
|
251
|
+
[projectId]
|
|
252
|
+
);
|
|
253
|
+
if ((existing.rows[0]?.count as number) > 0) return;
|
|
254
|
+
|
|
255
|
+
const pipelineId = 'pipeline_sales';
|
|
256
|
+
await db.execute(
|
|
257
|
+
`INSERT INTO crm_pipeline (id, "projectId", name) VALUES ($1, $2, $3)`,
|
|
258
|
+
[pipelineId, projectId, 'Sales Pipeline']
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const stages = [
|
|
262
|
+
{ id: 'stage_lead', name: 'Lead', position: 1 },
|
|
263
|
+
{ id: 'stage_contact', name: 'Contact Made', position: 2 },
|
|
264
|
+
{ id: 'stage_proposal', name: 'Proposal', position: 3 },
|
|
265
|
+
{ id: 'stage_negotiation', name: 'Negotiation', position: 4 },
|
|
266
|
+
{ id: 'stage_closed', name: 'Closed', position: 5 },
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
for (const stage of stages) {
|
|
270
|
+
await db.execute(
|
|
271
|
+
`INSERT INTO crm_stage (id, "pipelineId", name, position) VALUES ($1, $2, $3, $4)`,
|
|
272
|
+
[stage.id, pipelineId, stage.name, stage.position]
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function seedSaasBoilerplate(params: {
|
|
278
|
+
projectId: string;
|
|
279
|
+
db: DatabasePort;
|
|
280
|
+
}) {
|
|
281
|
+
const { projectId, db } = params;
|
|
282
|
+
|
|
283
|
+
const existing = await db.query(
|
|
284
|
+
`SELECT COUNT(*) as count FROM saas_project WHERE "projectId" = $1`,
|
|
285
|
+
[projectId]
|
|
286
|
+
);
|
|
287
|
+
if ((existing.rows[0]?.count as number) > 0) return;
|
|
288
|
+
|
|
289
|
+
await db.execute(
|
|
290
|
+
`INSERT INTO saas_project (id, "projectId", "organizationId", name, description, status, tier)
|
|
291
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
292
|
+
[
|
|
293
|
+
'saas_proj_1',
|
|
294
|
+
projectId,
|
|
295
|
+
'org_demo',
|
|
296
|
+
'Demo Project',
|
|
297
|
+
'A demo SaaS project',
|
|
298
|
+
'ACTIVE',
|
|
299
|
+
'PRO',
|
|
300
|
+
]
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function seedAgentConsole(params: {
|
|
305
|
+
projectId: string;
|
|
306
|
+
db: DatabasePort;
|
|
307
|
+
}) {
|
|
308
|
+
const { projectId, db } = params;
|
|
309
|
+
|
|
310
|
+
const existing = await db.query(
|
|
311
|
+
`SELECT COUNT(*) as count FROM agent_definition WHERE "projectId" = $1`,
|
|
312
|
+
[projectId]
|
|
313
|
+
);
|
|
314
|
+
if ((existing.rows[0]?.count as number) > 0) return;
|
|
315
|
+
|
|
316
|
+
await db.execute(
|
|
317
|
+
`INSERT INTO agent_definition (id, "projectId", "organizationId", name, description, "modelProvider", "modelName", status)
|
|
318
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
|
|
319
|
+
[
|
|
320
|
+
'agent_1',
|
|
321
|
+
projectId,
|
|
322
|
+
'org_demo',
|
|
323
|
+
'Demo Agent',
|
|
324
|
+
'A demo AI agent',
|
|
325
|
+
'openai',
|
|
326
|
+
'gpt-4',
|
|
327
|
+
'ACTIVE',
|
|
328
|
+
]
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async function seedWorkflowSystem(params: {
|
|
333
|
+
projectId: string;
|
|
334
|
+
db: DatabasePort;
|
|
335
|
+
}) {
|
|
336
|
+
const { projectId, db } = params;
|
|
337
|
+
|
|
338
|
+
const existing = await db.query(
|
|
339
|
+
`SELECT COUNT(*) as count FROM workflow_definition WHERE "projectId" = $1`,
|
|
340
|
+
[projectId]
|
|
341
|
+
);
|
|
342
|
+
if ((existing.rows[0]?.count as number) > 0) return;
|
|
343
|
+
|
|
344
|
+
await db.execute(
|
|
345
|
+
`INSERT INTO workflow_definition (id, "projectId", "organizationId", name, description, type, status)
|
|
346
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
347
|
+
[
|
|
348
|
+
'wf_1',
|
|
349
|
+
projectId,
|
|
350
|
+
'org_demo',
|
|
351
|
+
'Approval Workflow',
|
|
352
|
+
'Demo approval workflow',
|
|
353
|
+
'APPROVAL',
|
|
354
|
+
'ACTIVE',
|
|
355
|
+
]
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function seedMarketplace(params: {
|
|
360
|
+
projectId: string;
|
|
361
|
+
db: DatabasePort;
|
|
362
|
+
}) {
|
|
363
|
+
const { projectId, db } = params;
|
|
364
|
+
|
|
365
|
+
const existing = await db.query(
|
|
366
|
+
`SELECT COUNT(*) as count FROM marketplace_store WHERE "projectId" = $1`,
|
|
367
|
+
[projectId]
|
|
368
|
+
);
|
|
369
|
+
if ((existing.rows[0]?.count as number) > 0) return;
|
|
370
|
+
|
|
371
|
+
await db.execute(
|
|
372
|
+
`INSERT INTO marketplace_store (id, "projectId", "organizationId", name, description, status)
|
|
373
|
+
VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
374
|
+
['store_1', projectId, 'org_demo', 'Demo Store', 'A demo store', 'ACTIVE']
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async function seedIntegrationHub(params: {
|
|
379
|
+
projectId: string;
|
|
380
|
+
db: DatabasePort;
|
|
381
|
+
}) {
|
|
382
|
+
const { projectId, db } = params;
|
|
383
|
+
|
|
384
|
+
const existing = await db.query(
|
|
385
|
+
`SELECT COUNT(*) as count FROM integration WHERE "projectId" = $1`,
|
|
386
|
+
[projectId]
|
|
387
|
+
);
|
|
388
|
+
if ((existing.rows[0]?.count as number) > 0) return;
|
|
389
|
+
|
|
390
|
+
await db.execute(
|
|
391
|
+
`INSERT INTO integration (id, "projectId", "organizationId", name, description, type, status)
|
|
392
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
393
|
+
[
|
|
394
|
+
'int_1',
|
|
395
|
+
projectId,
|
|
396
|
+
'org_demo',
|
|
397
|
+
'Salesforce',
|
|
398
|
+
'Salesforce CRM integration',
|
|
399
|
+
'CRM',
|
|
400
|
+
'ACTIVE',
|
|
401
|
+
]
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async function seedAnalyticsDashboard(params: {
|
|
406
|
+
projectId: string;
|
|
407
|
+
db: DatabasePort;
|
|
408
|
+
}) {
|
|
409
|
+
const { projectId, db } = params;
|
|
410
|
+
|
|
411
|
+
const existing = await db.query(
|
|
412
|
+
`SELECT COUNT(*) as count FROM analytics_dashboard WHERE "projectId" = $1`,
|
|
413
|
+
[projectId]
|
|
414
|
+
);
|
|
415
|
+
if ((existing.rows[0]?.count as number) > 0) return;
|
|
416
|
+
|
|
417
|
+
await db.execute(
|
|
418
|
+
`INSERT INTO analytics_dashboard (id, "projectId", "organizationId", name, slug, description, status)
|
|
419
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
420
|
+
[
|
|
421
|
+
'dash_1',
|
|
422
|
+
projectId,
|
|
423
|
+
'org_demo',
|
|
424
|
+
'Sales Overview',
|
|
425
|
+
'sales-overview',
|
|
426
|
+
'Sales performance dashboard',
|
|
427
|
+
'PUBLISHED',
|
|
428
|
+
]
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async function seedPolicyKnowledgeAssistant(params: {
|
|
433
|
+
projectId: string;
|
|
434
|
+
db: DatabasePort;
|
|
435
|
+
}) {
|
|
436
|
+
const { projectId, db } = params;
|
|
437
|
+
|
|
438
|
+
const existing = await db.query(
|
|
439
|
+
`SELECT COUNT(*) as count FROM psa_user_context WHERE "projectId" = $1`,
|
|
440
|
+
[projectId]
|
|
441
|
+
);
|
|
442
|
+
if ((existing.rows[0]?.count as number) > 0) return;
|
|
443
|
+
|
|
444
|
+
await db.execute(
|
|
445
|
+
`INSERT INTO psa_user_context ("projectId", locale, jurisdiction, "allowedScope")
|
|
446
|
+
VALUES ($1, $2, $3, $4)`,
|
|
447
|
+
[projectId, 'en-GB', 'EU', 'education_only']
|
|
448
|
+
);
|
|
449
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime services for local (browser) sandbox environment.
|
|
3
|
+
*
|
|
4
|
+
* Uses lazy-loading for PGLite to avoid bundle bloat.
|
|
5
|
+
*/
|
|
6
|
+
import type { DatabasePort } from '@contractspec/lib.runtime-sandbox';
|
|
7
|
+
|
|
8
|
+
import { LocalEventBus } from '../events/local-pubsub';
|
|
9
|
+
import { LocalGraphQLClient } from '../graphql/local-client';
|
|
10
|
+
import { LocalStorageService } from '../storage/indexeddb';
|
|
11
|
+
|
|
12
|
+
import { SANDBOX_MIGRATIONS } from '../database/migrations';
|
|
13
|
+
|
|
14
|
+
export type TemplateId = string;
|
|
15
|
+
|
|
16
|
+
export interface LocalRuntimeInitOptions {
|
|
17
|
+
/**
|
|
18
|
+
* Data directory for IndexedDB persistence (optional).
|
|
19
|
+
* If omitted, uses in-memory database.
|
|
20
|
+
*/
|
|
21
|
+
dataDir?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface TemplateSeedOptions {
|
|
25
|
+
templateId: TemplateId;
|
|
26
|
+
projectId?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const DEFAULT_PROJECT_ID = 'local-project' as const;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Local runtime services for sandbox environment.
|
|
33
|
+
*
|
|
34
|
+
* Provides lazy-loaded database access via DatabasePort interface.
|
|
35
|
+
*/
|
|
36
|
+
export class LocalRuntimeServices {
|
|
37
|
+
readonly storage = new LocalStorageService();
|
|
38
|
+
readonly pubsub = new LocalEventBus();
|
|
39
|
+
#initialized = false;
|
|
40
|
+
|
|
41
|
+
private _db?: DatabasePort;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get the database port (must be initialized first).
|
|
45
|
+
*/
|
|
46
|
+
get db(): DatabasePort {
|
|
47
|
+
if (!this._db) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
'LocalRuntimeServices not initialized. Call init() first.'
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return this._db;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private _graphql?: LocalGraphQLClient;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get the GraphQL client (must be initialized first).
|
|
59
|
+
*/
|
|
60
|
+
get graphql(): LocalGraphQLClient {
|
|
61
|
+
if (!this._graphql) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
'LocalRuntimeServices not initialized. Call init() first.'
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return this._graphql;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Initialize the runtime services.
|
|
71
|
+
*
|
|
72
|
+
* Lazy-loads PGLite adapter to avoid bundle bloat.
|
|
73
|
+
*/
|
|
74
|
+
async init(options: LocalRuntimeInitOptions = {}): Promise<void> {
|
|
75
|
+
if (this.#initialized) return;
|
|
76
|
+
|
|
77
|
+
// Lazy-load PGLite adapter
|
|
78
|
+
const { createPGLiteAdapter } =
|
|
79
|
+
await import('@contractspec/lib.runtime-sandbox');
|
|
80
|
+
this._db = await createPGLiteAdapter();
|
|
81
|
+
await this._db.init({ dataDir: options.dataDir });
|
|
82
|
+
|
|
83
|
+
// Run migrations
|
|
84
|
+
await this._db.migrate(SANDBOX_MIGRATIONS);
|
|
85
|
+
|
|
86
|
+
// Initialize storage
|
|
87
|
+
await this.storage.init();
|
|
88
|
+
|
|
89
|
+
// Initialize GraphQL client with the new database port
|
|
90
|
+
this._graphql = new LocalGraphQLClient({
|
|
91
|
+
db: this._db,
|
|
92
|
+
storage: this.storage,
|
|
93
|
+
pubsub: this.pubsub,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
this.#initialized = true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Check if runtime is initialized.
|
|
101
|
+
*/
|
|
102
|
+
isInitialized(): boolean {
|
|
103
|
+
return this.#initialized;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Seed the database with deterministic defaults for a template.
|
|
108
|
+
*
|
|
109
|
+
* - No randomness
|
|
110
|
+
* - No wall-clock timestamps
|
|
111
|
+
* - Unknown templates are a no-op (safe default)
|
|
112
|
+
*/
|
|
113
|
+
async seedTemplate(options: TemplateSeedOptions): Promise<void> {
|
|
114
|
+
if (!this.#initialized) {
|
|
115
|
+
throw new Error('Call init() before seeding templates.');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const projectId = options.projectId ?? DEFAULT_PROJECT_ID;
|
|
119
|
+
|
|
120
|
+
if (!this._db) {
|
|
121
|
+
throw new Error('Database not initialized');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Lazy-load seeders to avoid bundle bloat
|
|
125
|
+
const { seedTemplate } = await import('./seeders');
|
|
126
|
+
await seedTemplate({
|
|
127
|
+
templateId: options.templateId,
|
|
128
|
+
projectId,
|
|
129
|
+
db: this._db,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|