@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 @@
1
+ {"version":3,"file":"local-pubsub.d.ts","names":[],"sources":["../../../src/web/events/local-pubsub.ts"],"sourcesContent":[],"mappings":";KAAK,+BAA+B;AAA/B,cAEQ,aAAA,CAFuB;EAEvB,QAAA,SAAa;EAGyB,IAAA,CAAA,WAAA,OAAA,CAAA,CAAA,KAAA,EAAA,MAAA,EAAA,OAAA,EAAA,QAAA,CAAA,EAAA,IAAA;EAU5B,SAAA,CAAA,WAAA,OAAA,CAAA,CAAA,KAAA,EAAA,MAAA,EAAA,QAAA,EAAT,QAAS,CAAA,QAAA,CAAA,CAAA,EAAA,GAAA,GAAA,IAAA"}
@@ -0,0 +1,24 @@
1
+ //#region src/web/events/local-pubsub.ts
2
+ var LocalEventBus = class {
3
+ listeners = /* @__PURE__ */ new Map();
4
+ emit(event, payload) {
5
+ const listeners = this.listeners.get(event);
6
+ if (!listeners) return;
7
+ for (const listener of listeners) listener(payload);
8
+ }
9
+ subscribe(event, listener) {
10
+ let listeners = this.listeners.get(event);
11
+ if (!listeners) {
12
+ listeners = /* @__PURE__ */ new Set();
13
+ this.listeners.set(event, listeners);
14
+ }
15
+ listeners.add(listener);
16
+ return () => {
17
+ listeners.delete(listener);
18
+ };
19
+ }
20
+ };
21
+
22
+ //#endregion
23
+ export { LocalEventBus };
24
+ //# sourceMappingURL=local-pubsub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-pubsub.js","names":[],"sources":["../../../src/web/events/local-pubsub.ts"],"sourcesContent":["type Listener<TPayload> = (payload: TPayload) => void;\n\nexport class LocalEventBus {\n private listeners = new Map<string, Set<Listener<unknown>>>();\n\n emit<TPayload = unknown>(event: string, payload: TPayload): void {\n const listeners = this.listeners.get(event);\n if (!listeners) return;\n for (const listener of listeners) {\n listener(payload);\n }\n }\n\n subscribe<TPayload = unknown>(\n event: string,\n listener: Listener<TPayload>\n ): () => void {\n let listeners = this.listeners.get(event);\n if (!listeners) {\n listeners = new Set();\n this.listeners.set(event, listeners);\n }\n listeners.add(listener as Listener<unknown>);\n return () => {\n listeners.delete(listener as Listener<unknown>);\n };\n }\n}\n"],"mappings":";AAEA,IAAa,gBAAb,MAA2B;CACzB,AAAQ,4BAAY,IAAI,KAAqC;CAE7D,KAAyB,OAAe,SAAyB;EAC/D,MAAM,YAAY,KAAK,UAAU,IAAI,MAAM;AAC3C,MAAI,CAAC,UAAW;AAChB,OAAK,MAAM,YAAY,UACrB,UAAS,QAAQ;;CAIrB,UACE,OACA,UACY;EACZ,IAAI,YAAY,KAAK,UAAU,IAAI,MAAM;AACzC,MAAI,CAAC,WAAW;AACd,+BAAY,IAAI,KAAK;AACrB,QAAK,UAAU,IAAI,OAAO,UAAU;;AAEtC,YAAU,IAAI,SAA8B;AAC5C,eAAa;AACX,aAAU,OAAO,SAA8B"}
@@ -0,0 +1,20 @@
1
+ import { LocalStorageService } from "../storage/indexeddb.js";
2
+ import { LocalEventBus } from "../events/local-pubsub.js";
3
+ import { ApolloClient } from "@apollo/client";
4
+ import { DatabasePort } from "@contractspec/lib.runtime-sandbox";
5
+
6
+ //#region src/web/graphql/local-client.d.ts
7
+ interface LocalGraphQLClientOptions {
8
+ db: DatabasePort;
9
+ storage: LocalStorageService;
10
+ pubsub?: LocalEventBus;
11
+ }
12
+ declare class LocalGraphQLClient {
13
+ private readonly options;
14
+ readonly apollo: InstanceType<typeof ApolloClient>;
15
+ constructor(options: LocalGraphQLClientOptions);
16
+ private createResolvers;
17
+ }
18
+ //#endregion
19
+ export { LocalGraphQLClient, LocalGraphQLClientOptions };
20
+ //# sourceMappingURL=local-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-client.d.ts","names":[],"sources":["../../../src/web/graphql/local-client.ts"],"sourcesContent":[],"mappings":";;;;;;UA8NiB,yBAAA;MACX;EADW,OAAA,EAEN,mBAF+B;EACpC,MAAA,CAAA,EAEK,aAFL;;AAEK,cAGE,kBAAA,CAHF;EAAa,iBAAA,OAAA;EAGX,SAAA,MAAA,EACM,YADY,CAAA,OACQ,YADR,CAAA;EACQ,WAAA,CAAA,OAAA,EAEC,yBAFD;EAApB,QAAA,eAAA"}
@@ -0,0 +1,536 @@
1
+ import { generateId } from "../utils/id.js";
2
+ import { LocalEventBus } from "../events/local-pubsub.js";
3
+ import { ApolloClient, InMemoryCache } from "@apollo/client";
4
+ import { SchemaLink } from "@apollo/client/link/schema";
5
+ import { makeExecutableSchema } from "@graphql-tools/schema";
6
+ import { GraphQLScalarType, Kind } from "graphql";
7
+
8
+ //#region src/web/graphql/local-client.ts
9
+ const typeDefs = `
10
+ scalar DateTime
11
+
12
+ enum TaskPriority {
13
+ LOW
14
+ MEDIUM
15
+ HIGH
16
+ URGENT
17
+ }
18
+
19
+ enum MessageStatus {
20
+ SENT
21
+ DELIVERED
22
+ READ
23
+ }
24
+
25
+ enum RecipeLocale {
26
+ EN
27
+ FR
28
+ }
29
+
30
+ type TaskCategory {
31
+ id: ID!
32
+ projectId: ID!
33
+ name: String!
34
+ color: String
35
+ createdAt: DateTime!
36
+ updatedAt: DateTime!
37
+ }
38
+
39
+ type Task {
40
+ id: ID!
41
+ projectId: ID!
42
+ categoryId: ID
43
+ title: String!
44
+ description: String
45
+ completed: Boolean!
46
+ priority: TaskPriority!
47
+ dueDate: DateTime
48
+ tags: [String!]!
49
+ createdAt: DateTime!
50
+ updatedAt: DateTime!
51
+ category: TaskCategory
52
+ }
53
+
54
+ input CreateTaskInput {
55
+ projectId: ID!
56
+ categoryId: ID
57
+ title: String!
58
+ description: String
59
+ priority: TaskPriority = MEDIUM
60
+ dueDate: DateTime
61
+ tags: [String!]
62
+ }
63
+
64
+ input UpdateTaskInput {
65
+ categoryId: ID
66
+ title: String
67
+ description: String
68
+ priority: TaskPriority
69
+ dueDate: DateTime
70
+ tags: [String!]
71
+ }
72
+
73
+ type ConversationParticipant {
74
+ id: ID!
75
+ conversationId: ID!
76
+ projectId: ID!
77
+ userId: String!
78
+ displayName: String
79
+ role: String
80
+ joinedAt: DateTime!
81
+ lastReadAt: DateTime
82
+ }
83
+
84
+ input ConversationParticipantInput {
85
+ userId: String!
86
+ displayName: String
87
+ role: String
88
+ }
89
+
90
+ type Message {
91
+ id: ID!
92
+ conversationId: ID!
93
+ projectId: ID!
94
+ senderId: String!
95
+ senderName: String
96
+ content: String!
97
+ attachments: [String!]!
98
+ status: MessageStatus!
99
+ createdAt: DateTime!
100
+ updatedAt: DateTime!
101
+ }
102
+
103
+ input SendMessageInput {
104
+ conversationId: ID!
105
+ projectId: ID!
106
+ senderId: String!
107
+ senderName: String
108
+ content: String!
109
+ }
110
+
111
+ input CreateConversationInput {
112
+ projectId: ID!
113
+ name: String
114
+ isGroup: Boolean = false
115
+ avatarUrl: String
116
+ participants: [ConversationParticipantInput!]!
117
+ }
118
+
119
+ type Conversation {
120
+ id: ID!
121
+ projectId: ID!
122
+ name: String
123
+ isGroup: Boolean!
124
+ avatarUrl: String
125
+ lastMessageId: ID
126
+ updatedAt: DateTime!
127
+ participants: [ConversationParticipant!]!
128
+ messages(limit: Int = 50): [Message!]!
129
+ }
130
+
131
+ type RecipeCategory {
132
+ id: ID!
133
+ nameEn: String!
134
+ nameFr: String!
135
+ icon: String
136
+ }
137
+
138
+ type RecipeIngredient {
139
+ id: ID!
140
+ name: String!
141
+ quantity: String!
142
+ ordering: Int!
143
+ }
144
+
145
+ type RecipeInstruction {
146
+ id: ID!
147
+ content: String!
148
+ ordering: Int!
149
+ }
150
+
151
+ type Recipe {
152
+ id: ID!
153
+ projectId: ID!
154
+ slugEn: String!
155
+ slugFr: String!
156
+ name: String!
157
+ description: String
158
+ heroImageUrl: String
159
+ prepTimeMinutes: Int
160
+ cookTimeMinutes: Int
161
+ servings: Int
162
+ isFavorite: Boolean!
163
+ locale: RecipeLocale!
164
+ category: RecipeCategory
165
+ ingredients: [RecipeIngredient!]!
166
+ instructions: [RecipeInstruction!]!
167
+ }
168
+
169
+ type Query {
170
+ taskCategories(projectId: ID!): [TaskCategory!]!
171
+ tasks(projectId: ID!): [Task!]!
172
+ conversations(projectId: ID!): [Conversation!]!
173
+ messages(conversationId: ID!, limit: Int = 50): [Message!]!
174
+ recipes(projectId: ID!, locale: RecipeLocale = EN): [Recipe!]!
175
+ recipe(id: ID!, locale: RecipeLocale = EN): Recipe
176
+ }
177
+
178
+ type Mutation {
179
+ createTask(input: CreateTaskInput!): Task!
180
+ updateTask(id: ID!, input: UpdateTaskInput!): Task!
181
+ toggleTask(id: ID!, completed: Boolean!): Task!
182
+ deleteTask(id: ID!): Boolean!
183
+ createConversation(input: CreateConversationInput!): Conversation!
184
+ sendMessage(input: SendMessageInput!): Message!
185
+ setMessagesRead(conversationId: ID!, userId: String!): Boolean!
186
+ favoriteRecipe(id: ID!, isFavorite: Boolean!): Recipe!
187
+ }
188
+ `;
189
+ const DateTimeScalar = new GraphQLScalarType({
190
+ name: "DateTime",
191
+ parseValue(value) {
192
+ return value ? new Date(value).toISOString() : null;
193
+ },
194
+ serialize(value) {
195
+ if (!value) return null;
196
+ if (typeof value === "string") return value;
197
+ return new Date(value).toISOString();
198
+ },
199
+ parseLiteral(ast) {
200
+ if (ast.kind === Kind.STRING) return new Date(ast.value).toISOString();
201
+ return null;
202
+ }
203
+ });
204
+ var LocalGraphQLClient = class {
205
+ apollo;
206
+ constructor(options) {
207
+ this.options = options;
208
+ const schema = makeExecutableSchema({
209
+ typeDefs,
210
+ resolvers: this.createResolvers()
211
+ });
212
+ this.apollo = new ApolloClient({
213
+ cache: new InMemoryCache(),
214
+ link: new SchemaLink({
215
+ schema,
216
+ context: () => ({
217
+ db: this.options.db,
218
+ storage: this.options.storage,
219
+ pubsub: this.options.pubsub ?? new LocalEventBus()
220
+ })
221
+ }),
222
+ devtools: { enabled: typeof window !== "undefined" }
223
+ });
224
+ }
225
+ createResolvers() {
226
+ return {
227
+ DateTime: DateTimeScalar,
228
+ Query: {
229
+ taskCategories: async (_, args, ctx) => {
230
+ return (await ctx.db.query(`SELECT * FROM template_task_category WHERE "projectId" = $1 ORDER BY name ASC`, [args.projectId])).rows.map(mapTaskCategory);
231
+ },
232
+ tasks: async (_, args, ctx) => {
233
+ return (await ctx.db.query(`SELECT * FROM template_task WHERE "projectId" = $1 ORDER BY "createdAt" DESC`, [args.projectId])).rows.map(mapTask);
234
+ },
235
+ conversations: async (_, args, ctx) => {
236
+ return (await ctx.db.query(`SELECT * FROM template_conversation WHERE "projectId" = $1 ORDER BY "updatedAt" DESC`, [args.projectId])).rows.map(mapConversation);
237
+ },
238
+ messages: async (_, args, ctx) => {
239
+ return (await ctx.db.query(`SELECT * FROM template_message WHERE "conversationId" = $1 ORDER BY "createdAt" DESC LIMIT $2`, [args.conversationId, args.limit])).rows.map(mapMessage);
240
+ },
241
+ recipes: async (_, args, ctx) => {
242
+ return (await ctx.db.query(`SELECT * FROM template_recipe WHERE "projectId" = $1 ORDER BY "nameEn" ASC`, [args.projectId])).rows.map((row) => mapRecipe(row, args.locale));
243
+ },
244
+ recipe: async (_, args, ctx) => {
245
+ const result = await ctx.db.query(`SELECT * FROM template_recipe WHERE id = $1 LIMIT 1`, [args.id]);
246
+ if (!result.rows.length || !result.rows[0]) return null;
247
+ return mapRecipe(result.rows[0], args.locale);
248
+ }
249
+ },
250
+ Mutation: {
251
+ createTask: async (_, args, ctx) => {
252
+ const id = generateId("task");
253
+ const now = (/* @__PURE__ */ new Date()).toISOString();
254
+ const tags = JSON.stringify(args.input.tags ?? []);
255
+ await ctx.db.execute(`INSERT INTO template_task (id, "projectId", "categoryId", title, description, completed, priority, "dueDate", tags, "createdAt", "updatedAt")
256
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`, [
257
+ id,
258
+ args.input.projectId,
259
+ args.input.categoryId ?? null,
260
+ args.input.title,
261
+ args.input.description ?? null,
262
+ 0,
263
+ args.input.priority ?? "MEDIUM",
264
+ args.input.dueDate ?? null,
265
+ tags,
266
+ now,
267
+ now
268
+ ]);
269
+ const result = await ctx.db.query(`SELECT * FROM template_task WHERE id = $1 LIMIT 1`, [id]);
270
+ if (!result.rows.length || !result.rows[0]) throw new Error("Failed to create task");
271
+ return mapTask(result.rows[0]);
272
+ },
273
+ updateTask: async (_, args, ctx) => {
274
+ const now = (/* @__PURE__ */ new Date()).toISOString();
275
+ await ctx.db.execute(`UPDATE template_task
276
+ SET "categoryId" = COALESCE($1, "categoryId"),
277
+ title = COALESCE($2, title),
278
+ description = COALESCE($3, description),
279
+ priority = COALESCE($4, priority),
280
+ "dueDate" = COALESCE($5, "dueDate"),
281
+ tags = COALESCE($6, tags),
282
+ "updatedAt" = $7
283
+ WHERE id = $8`, [
284
+ args.input.categoryId ?? null,
285
+ args.input.title ?? null,
286
+ args.input.description ?? null,
287
+ args.input.priority ?? null,
288
+ args.input.dueDate ?? null,
289
+ args.input.tags ? JSON.stringify(args.input.tags) : null,
290
+ now,
291
+ args.id
292
+ ]);
293
+ const result = await ctx.db.query(`SELECT * FROM template_task WHERE id = $1 LIMIT 1`, [args.id]);
294
+ if (!result.rows.length || !result.rows[0]) throw new Error("Task not found");
295
+ return mapTask(result.rows[0]);
296
+ },
297
+ toggleTask: async (_, args, ctx) => {
298
+ const now = (/* @__PURE__ */ new Date()).toISOString();
299
+ await ctx.db.execute(`UPDATE template_task SET completed = $1, "updatedAt" = $2 WHERE id = $3`, [
300
+ args.completed ? 1 : 0,
301
+ now,
302
+ args.id
303
+ ]);
304
+ const result = await ctx.db.query(`SELECT * FROM template_task WHERE id = $1 LIMIT 1`, [args.id]);
305
+ if (!result.rows.length || !result.rows[0]) throw new Error("Task not found");
306
+ return mapTask(result.rows[0]);
307
+ },
308
+ deleteTask: async (_, args, ctx) => {
309
+ await ctx.db.execute(`DELETE FROM template_task WHERE id = $1`, [args.id]);
310
+ return true;
311
+ },
312
+ createConversation: async (_, args, ctx) => {
313
+ const id = generateId("conversation");
314
+ const now = (/* @__PURE__ */ new Date()).toISOString();
315
+ await ctx.db.execute(`INSERT INTO template_conversation (id, "projectId", name, "isGroup", "avatarUrl", "updatedAt")
316
+ VALUES ($1, $2, $3, $4, $5, $6)`, [
317
+ id,
318
+ args.input.projectId,
319
+ args.input.name ?? null,
320
+ args.input.isGroup ? 1 : 0,
321
+ args.input.avatarUrl ?? null,
322
+ now
323
+ ]);
324
+ const participants = args.input.participants ?? [];
325
+ for (const participant of participants) await ctx.db.execute(`INSERT INTO template_conversation_participant (id, "conversationId", "projectId", "userId", "displayName", role, "joinedAt")
326
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`, [
327
+ generateId("participant"),
328
+ id,
329
+ args.input.projectId,
330
+ participant.userId,
331
+ participant.displayName ?? null,
332
+ participant.role ?? null,
333
+ now
334
+ ]);
335
+ const result = await ctx.db.query(`SELECT * FROM template_conversation WHERE id = $1 LIMIT 1`, [id]);
336
+ if (!result.rows.length || !result.rows[0]) throw new Error("Failed to create conversation");
337
+ return mapConversation(result.rows[0]);
338
+ },
339
+ sendMessage: async (_, args, ctx) => {
340
+ const id = generateId("message");
341
+ const now = (/* @__PURE__ */ new Date()).toISOString();
342
+ await ctx.db.execute(`INSERT INTO template_message (id, "conversationId", "projectId", "senderId", "senderName", content, attachments, status, "createdAt", "updatedAt")
343
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, [
344
+ id,
345
+ args.input.conversationId,
346
+ args.input.projectId,
347
+ args.input.senderId,
348
+ args.input.senderName ?? null,
349
+ args.input.content,
350
+ JSON.stringify([]),
351
+ "SENT",
352
+ now,
353
+ now
354
+ ]);
355
+ await ctx.db.execute(`UPDATE template_conversation SET "lastMessageId" = $1, "updatedAt" = $2 WHERE id = $3`, [
356
+ id,
357
+ now,
358
+ args.input.conversationId
359
+ ]);
360
+ const result = await ctx.db.query(`SELECT * FROM template_message WHERE id = $1`, [id]);
361
+ if (!result.rows.length || !result.rows[0]) throw new Error("Failed to send message");
362
+ const message = mapMessage(result.rows[0]);
363
+ ctx.pubsub.emit("message:new", message);
364
+ return message;
365
+ },
366
+ setMessagesRead: async (_, args, ctx) => {
367
+ const now = (/* @__PURE__ */ new Date()).toISOString();
368
+ await ctx.db.execute(`UPDATE template_conversation_participant
369
+ SET "lastReadAt" = $1
370
+ WHERE "conversationId" = $2 AND "userId" = $3`, [
371
+ now,
372
+ args.conversationId,
373
+ args.userId
374
+ ]);
375
+ return true;
376
+ },
377
+ favoriteRecipe: async (_, args, ctx) => {
378
+ const now = (/* @__PURE__ */ new Date()).toISOString();
379
+ await ctx.db.execute(`UPDATE template_recipe SET "isFavorite" = $1, "updatedAt" = $2 WHERE id = $3`, [
380
+ args.isFavorite ? 1 : 0,
381
+ now,
382
+ args.id
383
+ ]);
384
+ const result = await ctx.db.query(`SELECT * FROM template_recipe WHERE id = $1 LIMIT 1`, [args.id]);
385
+ if (!result.rows.length || !result.rows[0]) throw new Error("Recipe not found");
386
+ return mapRecipe(result.rows[0], "EN");
387
+ }
388
+ },
389
+ Task: { category: async (parent, _, ctx) => {
390
+ if (!parent.categoryId) return null;
391
+ const result = await ctx.db.query(`SELECT * FROM template_task_category WHERE id = $1 LIMIT 1`, [parent.categoryId]);
392
+ if (!result.rows.length || !result.rows[0]) return null;
393
+ return mapTaskCategory(result.rows[0]);
394
+ } },
395
+ Conversation: {
396
+ participants: async (parent, _, ctx) => {
397
+ return (await ctx.db.query(`SELECT * FROM template_conversation_participant WHERE "conversationId" = $1 ORDER BY "joinedAt" ASC`, [parent.id])).rows.map(mapParticipant);
398
+ },
399
+ messages: async (parent, args, ctx) => {
400
+ return (await ctx.db.query(`SELECT * FROM template_message WHERE "conversationId" = $1 ORDER BY "createdAt" DESC LIMIT $2`, [parent.id, args.limit])).rows.map(mapMessage);
401
+ }
402
+ },
403
+ Recipe: {
404
+ category: async (parent, _, ctx) => {
405
+ if (!parent.categoryId) return null;
406
+ const result = await ctx.db.query(`SELECT * FROM template_recipe_category WHERE id = $1 LIMIT 1`, [parent.categoryId]);
407
+ if (!result.rows.length || !result.rows[0]) return null;
408
+ return mapRecipeCategory(result.rows[0]);
409
+ },
410
+ ingredients: async (parent, _, ctx) => {
411
+ return (await ctx.db.query(`SELECT * FROM template_recipe_ingredient WHERE "recipeId" = $1 ORDER BY ordering ASC`, [parent.id])).rows.map((row) => mapRecipeIngredient(row, parent.locale));
412
+ },
413
+ instructions: async (parent, _, ctx) => {
414
+ return (await ctx.db.query(`SELECT * FROM template_recipe_instruction WHERE "recipeId" = $1 ORDER BY ordering ASC`, [parent.id])).rows.map((row) => mapRecipeInstruction(row, parent.locale));
415
+ }
416
+ }
417
+ };
418
+ }
419
+ };
420
+ function mapTaskCategory(row) {
421
+ return {
422
+ id: row.id,
423
+ projectId: row.projectId,
424
+ name: row.name,
425
+ color: row.color,
426
+ createdAt: row.createdAt,
427
+ updatedAt: row.updatedAt
428
+ };
429
+ }
430
+ function mapTask(row) {
431
+ return {
432
+ id: row.id,
433
+ projectId: row.projectId,
434
+ categoryId: row.categoryId,
435
+ title: row.title,
436
+ description: row.description,
437
+ completed: Boolean(row.completed),
438
+ priority: row.priority ?? "MEDIUM",
439
+ dueDate: row.dueDate,
440
+ tags: parseTags(row.tags),
441
+ createdAt: row.createdAt,
442
+ updatedAt: row.updatedAt
443
+ };
444
+ }
445
+ function parseTags(value) {
446
+ if (typeof value !== "string") return [];
447
+ try {
448
+ const parsed = JSON.parse(value);
449
+ return Array.isArray(parsed) ? parsed : [];
450
+ } catch {
451
+ return [];
452
+ }
453
+ }
454
+ function mapConversation(row) {
455
+ return {
456
+ id: row.id,
457
+ projectId: row.projectId,
458
+ name: row.name,
459
+ isGroup: Boolean(row.isGroup),
460
+ avatarUrl: row.avatarUrl,
461
+ lastMessageId: row.lastMessageId,
462
+ updatedAt: row.updatedAt
463
+ };
464
+ }
465
+ function mapParticipant(row) {
466
+ return {
467
+ id: row.id,
468
+ conversationId: row.conversationId,
469
+ projectId: row.projectId,
470
+ userId: row.userId,
471
+ displayName: row.displayName,
472
+ role: row.role,
473
+ joinedAt: row.joinedAt,
474
+ lastReadAt: row.lastReadAt
475
+ };
476
+ }
477
+ function mapMessage(row) {
478
+ return {
479
+ id: row.id,
480
+ conversationId: row.conversationId,
481
+ projectId: row.projectId,
482
+ senderId: row.senderId,
483
+ senderName: row.senderName,
484
+ content: row.content,
485
+ attachments: [],
486
+ status: row.status ?? "SENT",
487
+ createdAt: row.createdAt,
488
+ updatedAt: row.updatedAt
489
+ };
490
+ }
491
+ function mapRecipe(row, locale) {
492
+ return {
493
+ id: row.id,
494
+ projectId: row.projectId,
495
+ slugEn: row.slugEn,
496
+ slugFr: row.slugFr,
497
+ name: locale === "FR" ? row.nameFr : row.nameEn,
498
+ description: locale === "FR" ? row.descriptionFr : row.descriptionEn,
499
+ heroImageUrl: row.heroImageUrl,
500
+ prepTimeMinutes: row.prepTimeMinutes ?? null,
501
+ cookTimeMinutes: row.cookTimeMinutes ?? null,
502
+ servings: row.servings ?? null,
503
+ isFavorite: Boolean(row.isFavorite),
504
+ locale,
505
+ categoryId: row.categoryId,
506
+ createdAt: row.createdAt,
507
+ updatedAt: row.updatedAt
508
+ };
509
+ }
510
+ function mapRecipeCategory(row) {
511
+ return {
512
+ id: row.id,
513
+ nameEn: row.nameEn,
514
+ nameFr: row.nameFr,
515
+ icon: row.icon
516
+ };
517
+ }
518
+ function mapRecipeIngredient(row, locale) {
519
+ return {
520
+ id: row.id,
521
+ name: locale === "FR" ? row.nameFr : row.nameEn,
522
+ quantity: row.quantity,
523
+ ordering: row.ordering ?? 0
524
+ };
525
+ }
526
+ function mapRecipeInstruction(row, locale) {
527
+ return {
528
+ id: row.id,
529
+ content: locale === "FR" ? row.contentFr : row.contentEn,
530
+ ordering: row.ordering ?? 0
531
+ };
532
+ }
533
+
534
+ //#endregion
535
+ export { LocalGraphQLClient };
536
+ //# sourceMappingURL=local-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-client.js","names":[],"sources":["../../../src/web/graphql/local-client.ts"],"sourcesContent":["import { ApolloClient, InMemoryCache } from '@apollo/client';\nimport { SchemaLink } from '@apollo/client/link/schema';\nimport { makeExecutableSchema } from '@graphql-tools/schema';\nimport { GraphQLScalarType, Kind } from 'graphql';\n\nimport type { DatabasePort, DbRow } from '@contractspec/lib.runtime-sandbox';\nimport { LocalEventBus } from '../events/local-pubsub';\nimport { LocalStorageService } from '../storage/indexeddb';\nimport { generateId } from '../utils/id';\n\nconst typeDefs = /* GraphQL */ `\n scalar DateTime\n\n enum TaskPriority {\n LOW\n MEDIUM\n HIGH\n URGENT\n }\n\n enum MessageStatus {\n SENT\n DELIVERED\n READ\n }\n\n enum RecipeLocale {\n EN\n FR\n }\n\n type TaskCategory {\n id: ID!\n projectId: ID!\n name: String!\n color: String\n createdAt: DateTime!\n updatedAt: DateTime!\n }\n\n type Task {\n id: ID!\n projectId: ID!\n categoryId: ID\n title: String!\n description: String\n completed: Boolean!\n priority: TaskPriority!\n dueDate: DateTime\n tags: [String!]!\n createdAt: DateTime!\n updatedAt: DateTime!\n category: TaskCategory\n }\n\n input CreateTaskInput {\n projectId: ID!\n categoryId: ID\n title: String!\n description: String\n priority: TaskPriority = MEDIUM\n dueDate: DateTime\n tags: [String!]\n }\n\n input UpdateTaskInput {\n categoryId: ID\n title: String\n description: String\n priority: TaskPriority\n dueDate: DateTime\n tags: [String!]\n }\n\n type ConversationParticipant {\n id: ID!\n conversationId: ID!\n projectId: ID!\n userId: String!\n displayName: String\n role: String\n joinedAt: DateTime!\n lastReadAt: DateTime\n }\n\n input ConversationParticipantInput {\n userId: String!\n displayName: String\n role: String\n }\n\n type Message {\n id: ID!\n conversationId: ID!\n projectId: ID!\n senderId: String!\n senderName: String\n content: String!\n attachments: [String!]!\n status: MessageStatus!\n createdAt: DateTime!\n updatedAt: DateTime!\n }\n\n input SendMessageInput {\n conversationId: ID!\n projectId: ID!\n senderId: String!\n senderName: String\n content: String!\n }\n\n input CreateConversationInput {\n projectId: ID!\n name: String\n isGroup: Boolean = false\n avatarUrl: String\n participants: [ConversationParticipantInput!]!\n }\n\n type Conversation {\n id: ID!\n projectId: ID!\n name: String\n isGroup: Boolean!\n avatarUrl: String\n lastMessageId: ID\n updatedAt: DateTime!\n participants: [ConversationParticipant!]!\n messages(limit: Int = 50): [Message!]!\n }\n\n type RecipeCategory {\n id: ID!\n nameEn: String!\n nameFr: String!\n icon: String\n }\n\n type RecipeIngredient {\n id: ID!\n name: String!\n quantity: String!\n ordering: Int!\n }\n\n type RecipeInstruction {\n id: ID!\n content: String!\n ordering: Int!\n }\n\n type Recipe {\n id: ID!\n projectId: ID!\n slugEn: String!\n slugFr: String!\n name: String!\n description: String\n heroImageUrl: String\n prepTimeMinutes: Int\n cookTimeMinutes: Int\n servings: Int\n isFavorite: Boolean!\n locale: RecipeLocale!\n category: RecipeCategory\n ingredients: [RecipeIngredient!]!\n instructions: [RecipeInstruction!]!\n }\n\n type Query {\n taskCategories(projectId: ID!): [TaskCategory!]!\n tasks(projectId: ID!): [Task!]!\n conversations(projectId: ID!): [Conversation!]!\n messages(conversationId: ID!, limit: Int = 50): [Message!]!\n recipes(projectId: ID!, locale: RecipeLocale = EN): [Recipe!]!\n recipe(id: ID!, locale: RecipeLocale = EN): Recipe\n }\n\n type Mutation {\n createTask(input: CreateTaskInput!): Task!\n updateTask(id: ID!, input: UpdateTaskInput!): Task!\n toggleTask(id: ID!, completed: Boolean!): Task!\n deleteTask(id: ID!): Boolean!\n createConversation(input: CreateConversationInput!): Conversation!\n sendMessage(input: SendMessageInput!): Message!\n setMessagesRead(conversationId: ID!, userId: String!): Boolean!\n favoriteRecipe(id: ID!, isFavorite: Boolean!): Recipe!\n }\n`;\n\ninterface ResolverContext {\n db: DatabasePort;\n storage: LocalStorageService;\n pubsub: LocalEventBus;\n}\n\ntype ResolverParent = Record<string, unknown>;\n\n/**\n * Local row type for query results\n */\ntype LocalRow = DbRow;\n\nconst DateTimeScalar = new GraphQLScalarType({\n name: 'DateTime',\n parseValue(value: unknown) {\n return value ? new Date(value as string).toISOString() : null;\n },\n serialize(value: unknown) {\n if (!value) return null;\n if (typeof value === 'string') return value;\n return new Date(value as string).toISOString();\n },\n parseLiteral(ast) {\n if (ast.kind === Kind.STRING) {\n return new Date(ast.value).toISOString();\n }\n return null;\n },\n});\n\nexport interface LocalGraphQLClientOptions {\n db: DatabasePort;\n storage: LocalStorageService;\n pubsub?: LocalEventBus;\n}\n\nexport class LocalGraphQLClient {\n readonly apollo: InstanceType<typeof ApolloClient>;\n\n constructor(private readonly options: LocalGraphQLClientOptions) {\n const schema = makeExecutableSchema({\n typeDefs,\n resolvers: this.createResolvers(),\n });\n\n this.apollo = new ApolloClient({\n cache: new InMemoryCache(),\n link: new SchemaLink({\n schema,\n context: () => ({\n db: this.options.db,\n storage: this.options.storage,\n pubsub: this.options.pubsub ?? new LocalEventBus(),\n }),\n }),\n devtools: {\n enabled: typeof window !== 'undefined',\n },\n });\n }\n\n private createResolvers() {\n return {\n DateTime: DateTimeScalar,\n Query: {\n taskCategories: async (\n _: ResolverParent,\n args: { projectId: string },\n ctx: ResolverContext\n ) => {\n const result = await ctx.db.query(\n `SELECT * FROM template_task_category WHERE \"projectId\" = $1 ORDER BY name ASC`,\n [args.projectId]\n );\n return result.rows.map(mapTaskCategory);\n },\n tasks: async (\n _: ResolverParent,\n args: { projectId: string },\n ctx: ResolverContext\n ) => {\n const result = await ctx.db.query(\n `SELECT * FROM template_task WHERE \"projectId\" = $1 ORDER BY \"createdAt\" DESC`,\n [args.projectId]\n );\n return result.rows.map(mapTask);\n },\n conversations: async (\n _: ResolverParent,\n args: { projectId: string },\n ctx: ResolverContext\n ) => {\n const result = await ctx.db.query(\n `SELECT * FROM template_conversation WHERE \"projectId\" = $1 ORDER BY \"updatedAt\" DESC`,\n [args.projectId]\n );\n return result.rows.map(mapConversation);\n },\n messages: async (\n _: ResolverParent,\n args: { conversationId: string; limit: number },\n ctx: ResolverContext\n ) => {\n const result = await ctx.db.query(\n `SELECT * FROM template_message WHERE \"conversationId\" = $1 ORDER BY \"createdAt\" DESC LIMIT $2`,\n [args.conversationId, args.limit]\n );\n return result.rows.map(mapMessage);\n },\n recipes: async (\n _: ResolverParent,\n args: { projectId: string; locale: 'EN' | 'FR' },\n ctx: ResolverContext\n ) => {\n const result = await ctx.db.query(\n `SELECT * FROM template_recipe WHERE \"projectId\" = $1 ORDER BY \"nameEn\" ASC`,\n [args.projectId]\n );\n return result.rows.map((row: LocalRow) =>\n mapRecipe(row, args.locale)\n );\n },\n recipe: async (\n _: ResolverParent,\n args: { id: string; locale: 'EN' | 'FR' },\n ctx: ResolverContext\n ) => {\n const result = await ctx.db.query(\n `SELECT * FROM template_recipe WHERE id = $1 LIMIT 1`,\n [args.id]\n );\n if (!result.rows.length || !result.rows[0]) return null;\n return mapRecipe(result.rows[0], args.locale);\n },\n },\n Mutation: {\n createTask: async (\n _: ResolverParent,\n args: { input: Record<string, unknown> },\n ctx: ResolverContext\n ) => {\n const id = generateId('task');\n const now = new Date().toISOString();\n const tags = JSON.stringify(args.input.tags ?? []);\n await ctx.db.execute(\n `INSERT INTO template_task (id, \"projectId\", \"categoryId\", title, description, completed, priority, \"dueDate\", tags, \"createdAt\", \"updatedAt\")\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`,\n [\n id,\n args.input.projectId as string,\n (args.input.categoryId as string | undefined) ?? null,\n args.input.title as string,\n (args.input.description as string | undefined) ?? null,\n 0,\n (args.input.priority as string | undefined) ?? 'MEDIUM',\n (args.input.dueDate as string | undefined) ?? null,\n tags,\n now,\n now,\n ]\n );\n const result = await ctx.db.query(\n `SELECT * FROM template_task WHERE id = $1 LIMIT 1`,\n [id]\n );\n if (!result.rows.length || !result.rows[0])\n throw new Error('Failed to create task');\n return mapTask(result.rows[0]);\n },\n updateTask: async (\n _: ResolverParent,\n args: { id: string; input: Record<string, unknown> },\n ctx: ResolverContext\n ) => {\n const now = new Date().toISOString();\n await ctx.db.execute(\n `UPDATE template_task\n SET \"categoryId\" = COALESCE($1, \"categoryId\"),\n title = COALESCE($2, title),\n description = COALESCE($3, description),\n priority = COALESCE($4, priority),\n \"dueDate\" = COALESCE($5, \"dueDate\"),\n tags = COALESCE($6, tags),\n \"updatedAt\" = $7\n WHERE id = $8`,\n [\n (args.input.categoryId as string | undefined) ?? null,\n (args.input.title as string | undefined) ?? null,\n (args.input.description as string | undefined) ?? null,\n (args.input.priority as string | undefined) ?? null,\n (args.input.dueDate as string | undefined) ?? null,\n args.input.tags ? JSON.stringify(args.input.tags) : null,\n now,\n args.id,\n ]\n );\n const result = await ctx.db.query(\n `SELECT * FROM template_task WHERE id = $1 LIMIT 1`,\n [args.id]\n );\n if (!result.rows.length || !result.rows[0])\n throw new Error('Task not found');\n return mapTask(result.rows[0]);\n },\n toggleTask: async (\n _: ResolverParent,\n args: { id: string; completed: boolean },\n ctx: ResolverContext\n ) => {\n const now = new Date().toISOString();\n await ctx.db.execute(\n `UPDATE template_task SET completed = $1, \"updatedAt\" = $2 WHERE id = $3`,\n [args.completed ? 1 : 0, now, args.id]\n );\n const result = await ctx.db.query(\n `SELECT * FROM template_task WHERE id = $1 LIMIT 1`,\n [args.id]\n );\n if (!result.rows.length || !result.rows[0])\n throw new Error('Task not found');\n return mapTask(result.rows[0]);\n },\n deleteTask: async (\n _: ResolverParent,\n args: { id: string },\n ctx: ResolverContext\n ) => {\n await ctx.db.execute(`DELETE FROM template_task WHERE id = $1`, [\n args.id,\n ]);\n return true;\n },\n createConversation: async (\n _: ResolverParent,\n args: { input: Record<string, unknown> },\n ctx: ResolverContext\n ) => {\n const id = generateId('conversation');\n const now = new Date().toISOString();\n await ctx.db.execute(\n `INSERT INTO template_conversation (id, \"projectId\", name, \"isGroup\", \"avatarUrl\", \"updatedAt\")\n VALUES ($1, $2, $3, $4, $5, $6)`,\n [\n id,\n args.input.projectId as string,\n (args.input.name as string | undefined) ?? null,\n (args.input.isGroup as boolean | undefined) ? 1 : 0,\n (args.input.avatarUrl as string | undefined) ?? null,\n now,\n ]\n );\n\n const participants =\n (args.input.participants as Record<string, string>[]) ?? [];\n for (const participant of participants) {\n await ctx.db.execute(\n `INSERT INTO template_conversation_participant (id, \"conversationId\", \"projectId\", \"userId\", \"displayName\", role, \"joinedAt\")\n VALUES ($1, $2, $3, $4, $5, $6, $7)`,\n [\n generateId('participant'),\n id,\n args.input.projectId as string,\n participant.userId,\n participant.displayName ?? null,\n participant.role ?? null,\n now,\n ]\n );\n }\n\n const result = await ctx.db.query(\n `SELECT * FROM template_conversation WHERE id = $1 LIMIT 1`,\n [id]\n );\n if (!result.rows.length || !result.rows[0])\n throw new Error('Failed to create conversation');\n return mapConversation(result.rows[0]);\n },\n sendMessage: async (\n _: ResolverParent,\n args: { input: Record<string, unknown> },\n ctx: ResolverContext\n ) => {\n const id = generateId('message');\n const now = new Date().toISOString();\n await ctx.db.execute(\n `INSERT INTO template_message (id, \"conversationId\", \"projectId\", \"senderId\", \"senderName\", content, attachments, status, \"createdAt\", \"updatedAt\")\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,\n [\n id,\n args.input.conversationId as string,\n args.input.projectId as string,\n args.input.senderId as string,\n (args.input.senderName as string | undefined) ?? null,\n args.input.content as string,\n JSON.stringify([]),\n 'SENT',\n now,\n now,\n ]\n );\n await ctx.db.execute(\n `UPDATE template_conversation SET \"lastMessageId\" = $1, \"updatedAt\" = $2 WHERE id = $3`,\n [id, now, args.input.conversationId as string]\n );\n const result = await ctx.db.query(\n `SELECT * FROM template_message WHERE id = $1`,\n [id]\n );\n if (!result.rows.length || !result.rows[0])\n throw new Error('Failed to send message');\n const message = mapMessage(result.rows[0]);\n ctx.pubsub.emit('message:new', message);\n return message;\n },\n setMessagesRead: async (\n _: ResolverParent,\n args: { conversationId: string; userId: string },\n ctx: ResolverContext\n ) => {\n const now = new Date().toISOString();\n await ctx.db.execute(\n `UPDATE template_conversation_participant\n SET \"lastReadAt\" = $1\n WHERE \"conversationId\" = $2 AND \"userId\" = $3`,\n [now, args.conversationId, args.userId]\n );\n return true;\n },\n favoriteRecipe: async (\n _: ResolverParent,\n args: { id: string; isFavorite: boolean },\n ctx: ResolverContext\n ) => {\n const now = new Date().toISOString();\n await ctx.db.execute(\n `UPDATE template_recipe SET \"isFavorite\" = $1, \"updatedAt\" = $2 WHERE id = $3`,\n [args.isFavorite ? 1 : 0, now, args.id]\n );\n const result = await ctx.db.query(\n `SELECT * FROM template_recipe WHERE id = $1 LIMIT 1`,\n [args.id]\n );\n if (!result.rows.length || !result.rows[0])\n throw new Error('Recipe not found');\n const locale: 'EN' | 'FR' = 'EN';\n return mapRecipe(result.rows[0], locale);\n },\n },\n Task: {\n category: async (\n parent: LocalRow,\n _: unknown,\n ctx: ResolverContext\n ) => {\n if (!parent.categoryId) return null;\n const result = await ctx.db.query(\n `SELECT * FROM template_task_category WHERE id = $1 LIMIT 1`,\n [parent.categoryId]\n );\n if (!result.rows.length || !result.rows[0]) return null;\n return mapTaskCategory(result.rows[0]);\n },\n },\n Conversation: {\n participants: async (\n parent: LocalRow,\n _: unknown,\n ctx: ResolverContext\n ) => {\n const result = await ctx.db.query(\n `SELECT * FROM template_conversation_participant WHERE \"conversationId\" = $1 ORDER BY \"joinedAt\" ASC`,\n [parent.id]\n );\n return result.rows.map(mapParticipant);\n },\n messages: async (\n parent: LocalRow,\n args: { limit: number },\n ctx: ResolverContext\n ) => {\n const result = await ctx.db.query(\n `SELECT * FROM template_message WHERE \"conversationId\" = $1 ORDER BY \"createdAt\" DESC LIMIT $2`,\n [parent.id, args.limit]\n );\n return result.rows.map(mapMessage);\n },\n },\n Recipe: {\n category: async (\n parent: LocalRow & { categoryId?: string | null },\n _: unknown,\n ctx: ResolverContext\n ) => {\n if (!parent.categoryId) return null;\n const result = await ctx.db.query(\n `SELECT * FROM template_recipe_category WHERE id = $1 LIMIT 1`,\n [parent.categoryId]\n );\n if (!result.rows.length || !result.rows[0]) return null;\n return mapRecipeCategory(result.rows[0]);\n },\n ingredients: async (\n parent: LocalRow & { locale: 'EN' | 'FR' },\n _: unknown,\n ctx: ResolverContext\n ) => {\n const result = await ctx.db.query(\n `SELECT * FROM template_recipe_ingredient WHERE \"recipeId\" = $1 ORDER BY ordering ASC`,\n [parent.id]\n );\n return result.rows.map((row: LocalRow) =>\n mapRecipeIngredient(row, parent.locale)\n );\n },\n instructions: async (\n parent: LocalRow & { locale: 'EN' | 'FR' },\n _: unknown,\n ctx: ResolverContext\n ) => {\n const result = await ctx.db.query(\n `SELECT * FROM template_recipe_instruction WHERE \"recipeId\" = $1 ORDER BY ordering ASC`,\n [parent.id]\n );\n return result.rows.map((row: LocalRow) =>\n mapRecipeInstruction(row, parent.locale)\n );\n },\n },\n };\n }\n}\n\nfunction mapTaskCategory(row: LocalRow) {\n return {\n id: row.id,\n projectId: row.projectId,\n name: row.name,\n color: row.color,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nfunction mapTask(row: LocalRow) {\n return {\n id: row.id,\n projectId: row.projectId,\n categoryId: row.categoryId,\n title: row.title,\n description: row.description,\n completed: Boolean(row.completed),\n priority: row.priority ?? 'MEDIUM',\n dueDate: row.dueDate,\n tags: parseTags(row.tags),\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nfunction parseTags(value: LocalRow['tags']): string[] {\n if (typeof value !== 'string') return [];\n try {\n const parsed = JSON.parse(value);\n return Array.isArray(parsed) ? (parsed as string[]) : [];\n } catch {\n return [];\n }\n}\n\nfunction mapConversation(row: LocalRow) {\n return {\n id: row.id,\n projectId: row.projectId,\n name: row.name,\n isGroup: Boolean(row.isGroup),\n avatarUrl: row.avatarUrl,\n lastMessageId: row.lastMessageId,\n updatedAt: row.updatedAt,\n };\n}\n\nfunction mapParticipant(row: LocalRow) {\n return {\n id: row.id,\n conversationId: row.conversationId,\n projectId: row.projectId,\n userId: row.userId,\n displayName: row.displayName,\n role: row.role,\n joinedAt: row.joinedAt,\n lastReadAt: row.lastReadAt,\n };\n}\n\nfunction mapMessage(row: LocalRow) {\n return {\n id: row.id,\n conversationId: row.conversationId,\n projectId: row.projectId,\n senderId: row.senderId,\n senderName: row.senderName,\n content: row.content,\n attachments: [],\n status: row.status ?? 'SENT',\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nfunction mapRecipe(row: LocalRow, locale: 'EN' | 'FR') {\n return {\n id: row.id,\n projectId: row.projectId,\n slugEn: row.slugEn,\n slugFr: row.slugFr,\n name: locale === 'FR' ? row.nameFr : row.nameEn,\n description: locale === 'FR' ? row.descriptionFr : row.descriptionEn,\n heroImageUrl: row.heroImageUrl,\n prepTimeMinutes: row.prepTimeMinutes ?? null,\n cookTimeMinutes: row.cookTimeMinutes ?? null,\n servings: row.servings ?? null,\n isFavorite: Boolean(row.isFavorite),\n locale,\n categoryId: row.categoryId,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nfunction mapRecipeCategory(row: LocalRow) {\n return {\n id: row.id,\n nameEn: row.nameEn,\n nameFr: row.nameFr,\n icon: row.icon,\n };\n}\n\nfunction mapRecipeIngredient(row: LocalRow, locale: 'EN' | 'FR') {\n return {\n id: row.id,\n name: locale === 'FR' ? row.nameFr : row.nameEn,\n quantity: row.quantity,\n ordering: row.ordering ?? 0,\n };\n}\n\nfunction mapRecipeInstruction(row: LocalRow, locale: 'EN' | 'FR') {\n return {\n id: row.id,\n content: locale === 'FR' ? row.contentFr : row.contentEn,\n ordering: row.ordering ?? 0,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { SANDBOX_MIGRATIONS } from "./database/migrations.js";
2
+ import { agentDefinition, agentRun, agentRunLog, agentRunStep, agentTool, agentToolAssignment, analyticsDashboard, analyticsQuery, analyticsWidget, crmCompany, crmContact, crmDeal, crmPipeline, crmStage, integration, integrationConnection, integrationFieldMapping, integrationSyncConfig, marketplaceOrder, marketplaceOrderItem, marketplacePayout, marketplaceProduct, marketplaceReview, marketplaceStore, psaChangeCandidate, psaReviewTask, psaRule, psaRuleVersion, psaSnapshot, psaUserContext, saasProject, saasSubscription, saasUsage, templateConversation, templateConversationParticipant, templateMessage, templateRecipe, templateRecipeCategory, templateRecipeIngredient, templateRecipeInstruction, templateTask, templateTaskCategory, workflowApproval, workflowDefinition, workflowInstance, workflowStep } from "./database/schema.js";
3
+ import { LocalStorageOptions, LocalStorageService } from "./storage/indexeddb.js";
4
+ import { generateId } from "./utils/id.js";
5
+ import { LocalEventBus } from "./events/local-pubsub.js";
6
+ import { LocalGraphQLClient, LocalGraphQLClientOptions } from "./graphql/local-client.js";
7
+ import { LocalRuntimeInitOptions, LocalRuntimeServices, TemplateId, TemplateSeedOptions } from "./runtime/services.js";
8
+
9
+ //#region src/web/index.d.ts
10
+ declare namespace index_d_exports {
11
+ export { LocalEventBus, LocalGraphQLClient, LocalGraphQLClientOptions, LocalRuntimeInitOptions, LocalRuntimeServices, LocalStorageOptions, LocalStorageService, SANDBOX_MIGRATIONS, TemplateId, TemplateSeedOptions, agentDefinition, agentRun, agentRunLog, agentRunStep, agentTool, agentToolAssignment, analyticsDashboard, analyticsQuery, analyticsWidget, crmCompany, crmContact, crmDeal, crmPipeline, crmStage, generateId, integration, integrationConnection, integrationFieldMapping, integrationSyncConfig, marketplaceOrder, marketplaceOrderItem, marketplacePayout, marketplaceProduct, marketplaceReview, marketplaceStore, psaChangeCandidate, psaReviewTask, psaRule, psaRuleVersion, psaSnapshot, psaUserContext, saasProject, saasSubscription, saasUsage, templateConversation, templateConversationParticipant, templateMessage, templateRecipe, templateRecipeCategory, templateRecipeIngredient, templateRecipeInstruction, templateTask, templateTaskCategory, workflowApproval, workflowDefinition, workflowInstance, workflowStep };
12
+ }
13
+ //#endregion
14
+ export { index_d_exports };
15
+ //# sourceMappingURL=index.d.ts.map