@prisma-next-idb/family-idb 0.1.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 (33) hide show
  1. package/LICENSE +21 -0
  2. package/dist/bin/prisma-next-idb.d.mts +1 -0
  3. package/dist/bin/prisma-next-idb.mjs +281 -0
  4. package/dist/bin/prisma-next-idb.mjs.map +1 -0
  5. package/dist/exports/config-types.d.mts +39 -0
  6. package/dist/exports/config-types.d.mts.map +1 -0
  7. package/dist/exports/config-types.mjs +66 -0
  8. package/dist/exports/config-types.mjs.map +1 -0
  9. package/dist/exports/contract-psl.d.mts +48 -0
  10. package/dist/exports/contract-psl.d.mts.map +1 -0
  11. package/dist/exports/contract-psl.mjs +463 -0
  12. package/dist/exports/contract-psl.mjs.map +1 -0
  13. package/dist/exports/contract-ts.d.mts +73 -0
  14. package/dist/exports/contract-ts.d.mts.map +1 -0
  15. package/dist/exports/contract-ts.mjs +162 -0
  16. package/dist/exports/contract-ts.mjs.map +1 -0
  17. package/dist/exports/control.d.mts +79 -0
  18. package/dist/exports/control.d.mts.map +1 -0
  19. package/dist/exports/control.mjs +566 -0
  20. package/dist/exports/control.mjs.map +1 -0
  21. package/dist/exports/pack.d.mts +10 -0
  22. package/dist/exports/pack.d.mts.map +1 -0
  23. package/dist/exports/pack.mjs +11 -0
  24. package/dist/exports/pack.mjs.map +1 -0
  25. package/dist/generate-baseline-Dg3vfBpB.mjs +130 -0
  26. package/dist/generate-baseline-Dg3vfBpB.mjs.map +1 -0
  27. package/dist/generate-migration-D8bDx9jo.mjs +150 -0
  28. package/dist/generate-migration-D8bDx9jo.mjs.map +1 -0
  29. package/dist/preflight-D8GLQXy3.mjs +112 -0
  30. package/dist/preflight-D8GLQXy3.mjs.map +1 -0
  31. package/dist/validate-DL9NthnR.mjs +48 -0
  32. package/dist/validate-DL9NthnR.mjs.map +1 -0
  33. package/package.json +63 -0
@@ -0,0 +1,463 @@
1
+ import { t as validateContract } from "../validate-DL9NthnR.mjs";
2
+ import { UNBOUND_DOMAIN_NAMESPACE_ID, crossRef } from "@prisma-next/contract/types";
3
+ import { computeProfileHash, computeStorageHash } from "@prisma-next/contract/hashing";
4
+ import { flatPslModels } from "@prisma-next/framework-components/psl-ast";
5
+ import { notOk, ok } from "@prisma-next/utils/result";
6
+ import { readFile } from "node:fs/promises";
7
+ import { parsePslDocument } from "@prisma-next/psl-parser";
8
+ import { basename, extname } from "pathe";
9
+ //#region src/core/psl-interpreter.ts
10
+ const SCALAR_TO_CODEC_ID = {
11
+ String: "idb/string@1",
12
+ Int: "idb/int32@1",
13
+ Float: "idb/double@1",
14
+ Boolean: "idb/bool@1",
15
+ DateTime: "idb/date@1",
16
+ BigInt: "idb/bigint@1",
17
+ Decimal: "idb/decimal@1",
18
+ Json: "idb/json@1",
19
+ Bytes: "idb/bytes@1"
20
+ };
21
+ const REFERENTIAL_ACTION_MAP = {
22
+ Cascade: "cascade",
23
+ SetNull: "setNull",
24
+ SetDefault: "setDefault",
25
+ Restrict: "restrict",
26
+ NoAction: "noAction"
27
+ };
28
+ function findPositionalArg(args) {
29
+ return args.find((a) => a.kind === "positional")?.value;
30
+ }
31
+ function findNamedArg(args, name) {
32
+ return args.find((a) => a.kind === "named" && a.name === name)?.value;
33
+ }
34
+ function parseStringArg(raw) {
35
+ if (raw === void 0) return void 0;
36
+ const t = raw.trim();
37
+ if (t.startsWith("\"") && t.endsWith("\"") || t.startsWith("'") && t.endsWith("'")) return t.slice(1, -1);
38
+ }
39
+ function parseFieldList(raw) {
40
+ if (raw === void 0) return void 0;
41
+ const t = raw.trim();
42
+ if (!t.startsWith("[") || !t.endsWith("]")) return void 0;
43
+ const inner = t.slice(1, -1).trim();
44
+ if (inner === "") return [];
45
+ return inner.split(",").map((s) => s.trim()).filter(Boolean);
46
+ }
47
+ function lowerFirst(s) {
48
+ return s.charAt(0).toLowerCase() + s.slice(1);
49
+ }
50
+ function hasFieldAttribute(field, name) {
51
+ return field.attributes.some((a) => a.name === name);
52
+ }
53
+ function getFieldAttribute(field, name) {
54
+ return field.attributes.find((a) => a.name === name);
55
+ }
56
+ function interpretModel(model, modelNames, sourceId, diagnostics) {
57
+ const storeName = parseStringArg(findPositionalArg(model.attributes.find((a) => a.name === "map")?.args ?? [])) ?? lowerFirst(model.name);
58
+ let keyPath;
59
+ let idFieldName;
60
+ const idModelAttr = model.attributes.find((a) => a.name === "id");
61
+ if (idModelAttr) {
62
+ const fields = parseFieldList(findPositionalArg(idModelAttr.args));
63
+ if (!fields || fields.length === 0) {
64
+ diagnostics.push({
65
+ code: "IDB_INVALID_ID",
66
+ message: `Model "${model.name}" @@id([…]) is missing a field list.`,
67
+ sourceId,
68
+ span: idModelAttr.span
69
+ });
70
+ return;
71
+ }
72
+ if (fields.length > 1) {
73
+ diagnostics.push({
74
+ code: "IDB_NO_COMPOUND_KEY",
75
+ message: `Model "${model.name}" @@id([${fields.join(", ")}]) declares a compound key. IDB does not support compound primary keys — use a single @id field instead.`,
76
+ sourceId,
77
+ span: idModelAttr.span
78
+ });
79
+ return;
80
+ }
81
+ idFieldName = fields[0];
82
+ keyPath = fields[0];
83
+ }
84
+ const idFields = model.fields.filter((f) => hasFieldAttribute(f, "id"));
85
+ if (idFields.length > 1) {
86
+ diagnostics.push({
87
+ code: "IDB_MULTIPLE_ID_FIELDS",
88
+ message: `Model "${model.name}" declares @id on multiple fields (${idFields.map((f) => f.name).join(", ")}). Only one @id field is allowed.`,
89
+ sourceId,
90
+ span: model.span
91
+ });
92
+ return;
93
+ }
94
+ if (idFields.length === 1) {
95
+ if (keyPath !== void 0) {
96
+ diagnostics.push({
97
+ code: "IDB_INVALID_ID",
98
+ message: `Model "${model.name}" cannot declare both a field-level @id and a model-level @@id.`,
99
+ sourceId,
100
+ span: model.span
101
+ });
102
+ return;
103
+ }
104
+ idFieldName = idFields[0].name;
105
+ keyPath = idFields[0].name;
106
+ }
107
+ if (keyPath === void 0) {
108
+ diagnostics.push({
109
+ code: "IDB_MISSING_ID",
110
+ message: `Model "${model.name}" has no @id field. Add @id to exactly one scalar field.`,
111
+ sourceId,
112
+ span: model.span
113
+ });
114
+ return;
115
+ }
116
+ const indexes = {};
117
+ for (const attr of model.attributes) {
118
+ if (attr.name !== "index" && attr.name !== "unique") continue;
119
+ const isUnique = attr.name === "unique";
120
+ const fields = parseFieldList(findPositionalArg(attr.args));
121
+ if (!fields || fields.length === 0) {
122
+ diagnostics.push({
123
+ code: "IDB_INVALID_INDEX",
124
+ message: `Model "${model.name}" @@${attr.name} is missing a field list.`,
125
+ sourceId,
126
+ span: attr.span
127
+ });
128
+ continue;
129
+ }
130
+ if (fields.length > 1) {
131
+ diagnostics.push({
132
+ code: "IDB_COMPOUND_INDEX_UNSUPPORTED",
133
+ message: `Model "${model.name}" @@${attr.name}([${fields.join(", ")}]) declares a compound index. IDB compound indexes are not yet supported — use a single-field index.`,
134
+ sourceId,
135
+ span: attr.span
136
+ });
137
+ continue;
138
+ }
139
+ const field = fields[0];
140
+ const indexName = parseStringArg(findNamedArg(attr.args, "name") ?? findNamedArg(attr.args, "map")) ?? (isUnique ? `${field}_unique` : field);
141
+ indexes[indexName] = {
142
+ keyPath: field,
143
+ unique: isUnique
144
+ };
145
+ }
146
+ const contractFields = {};
147
+ const relations = {};
148
+ const relationsStorage = {};
149
+ const fksByTarget = /* @__PURE__ */ new Map();
150
+ for (const field of model.fields) {
151
+ if (field.name === idFieldName && field.optional) diagnostics.push({
152
+ code: "IDB_NULLABLE_ID",
153
+ message: `Field "${model.name}.${field.name}" is marked as @id but is optional (?). The primary key cannot be nullable.`,
154
+ sourceId,
155
+ span: field.span
156
+ });
157
+ if (field.list && modelNames.has(field.typeName)) continue;
158
+ const relationAttr = getFieldAttribute(field, "relation");
159
+ if (!field.list && modelNames.has(field.typeName) && relationAttr) {
160
+ const args = relationAttr.args;
161
+ const localFieldsRaw = findNamedArg(args, "fields");
162
+ const targetFieldsRaw = findNamedArg(args, "references");
163
+ const localFields = parseFieldList(localFieldsRaw);
164
+ const targetFields = parseFieldList(targetFieldsRaw);
165
+ if (!localFields || localFields.length === 0 || !targetFields || targetFields.length === 0) {
166
+ diagnostics.push({
167
+ code: "IDB_INVALID_RELATION",
168
+ message: `Relation field "${model.name}.${field.name}" must declare both fields and references in @relation.`,
169
+ sourceId,
170
+ span: field.span
171
+ });
172
+ continue;
173
+ }
174
+ if (localFields.length !== targetFields.length) {
175
+ diagnostics.push({
176
+ code: "IDB_INVALID_RELATION",
177
+ message: `Relation field "${model.name}.${field.name}" must have the same number of fields and references.`,
178
+ sourceId,
179
+ span: field.span
180
+ });
181
+ continue;
182
+ }
183
+ const onDeleteRaw = findNamedArg(args, "onDelete");
184
+ const onDelete = onDeleteRaw ? REFERENTIAL_ACTION_MAP[onDeleteRaw.trim()] : void 0;
185
+ if (onDeleteRaw && onDelete === void 0) diagnostics.push({
186
+ code: "IDB_UNKNOWN_REFERENTIAL_ACTION",
187
+ message: `Relation field "${model.name}.${field.name}" has unknown onDelete value "${onDeleteRaw}". Valid values: ${Object.keys(REFERENTIAL_ACTION_MAP).join(", ")}.`,
188
+ sourceId,
189
+ span: field.span
190
+ });
191
+ relations[field.name] = {
192
+ to: crossRef(field.typeName),
193
+ cardinality: "N:1",
194
+ on: {
195
+ localFields,
196
+ targetFields
197
+ }
198
+ };
199
+ if (onDelete !== void 0) relationsStorage[field.name] = { onDelete };
200
+ fksByTarget.set(field.typeName, {
201
+ fieldName: field.name,
202
+ localFields,
203
+ targetFields
204
+ });
205
+ for (const fkField of localFields) if (!(fkField in indexes)) indexes[fkField] = {
206
+ keyPath: fkField,
207
+ unique: false
208
+ };
209
+ continue;
210
+ }
211
+ if (!field.list && modelNames.has(field.typeName) && !relationAttr) {
212
+ diagnostics.push({
213
+ code: "IDB_MISSING_RELATION_ATTRIBUTE",
214
+ message: `Field "${model.name}.${field.name}" has model type "${field.typeName}" but no @relation attribute. Add @relation(fields: [...], references: [...]).`,
215
+ sourceId,
216
+ span: field.span
217
+ });
218
+ continue;
219
+ }
220
+ if (field.list) continue;
221
+ const codecId = SCALAR_TO_CODEC_ID[field.typeName];
222
+ if (codecId === void 0) {
223
+ diagnostics.push({
224
+ code: "IDB_UNSUPPORTED_FIELD_TYPE",
225
+ message: `Field "${model.name}.${field.name}" has unsupported type "${field.typeName}". Supported types: ${Object.keys(SCALAR_TO_CODEC_ID).join(", ")}.`,
226
+ sourceId,
227
+ span: field.span
228
+ });
229
+ continue;
230
+ }
231
+ contractFields[field.name] = {
232
+ nullable: field.optional,
233
+ type: {
234
+ kind: "scalar",
235
+ codecId
236
+ }
237
+ };
238
+ if (hasFieldAttribute(field, "unique")) {
239
+ if (!Object.entries(indexes).find(([, idx]) => idx.keyPath === field.name)?.[0]) indexes[`${field.name}_unique`] = {
240
+ keyPath: field.name,
241
+ unique: true
242
+ };
243
+ }
244
+ }
245
+ return {
246
+ modelName: model.name,
247
+ storeName,
248
+ keyPath,
249
+ indexes,
250
+ fields: contractFields,
251
+ relations,
252
+ relationsStorage,
253
+ fksByTarget
254
+ };
255
+ }
256
+ /**
257
+ * Interprets a parsed PSL document AST and produces an IDB `Contract`.
258
+ *
259
+ * This is the IDB equivalent of `interpretPslDocumentToSqlContract` from the
260
+ * SQL family. It handles IDB-specific constraints:
261
+ *
262
+ * - No namespace blocks (IDB has a single implicit `__unbound__` namespace)
263
+ * - No compound primary keys (IDB `keyPath` must be a single field)
264
+ * - Relations are FK-side (`@relation`) + backrelation list fields
265
+ * - Indexes map directly to `IDBObjectStore.createIndex()` calls
266
+ */
267
+ function interpretPslDocumentToIdbContract(ast, sourceId) {
268
+ const diagnostics = [];
269
+ const explicitNamespaces = ast.namespaces.filter((ns) => ns.name !== "__unspecified__");
270
+ for (const ns of explicitNamespaces) diagnostics.push({
271
+ code: "IDB_UNSUPPORTED_NAMESPACE_BLOCK",
272
+ message: `IDB does not support \`namespace ${ns.name} { … }\` blocks. All models must be declared at the top level.`,
273
+ sourceId,
274
+ span: ns.span
275
+ });
276
+ const allModels = flatPslModels(ast);
277
+ const modelNames = new Set(allModels.map((m) => m.name));
278
+ const interpretedByName = /* @__PURE__ */ new Map();
279
+ for (const model of allModels) {
280
+ const result = interpretModel(model, modelNames, sourceId, diagnostics);
281
+ if (result) interpretedByName.set(model.name, result);
282
+ }
283
+ for (const model of allModels) {
284
+ const interp = interpretedByName.get(model.name);
285
+ if (!interp) continue;
286
+ for (const field of model.fields) {
287
+ if (!field.list || !modelNames.has(field.typeName)) continue;
288
+ const targetInterp = interpretedByName.get(field.typeName);
289
+ if (!targetInterp) continue;
290
+ const fk = targetInterp.fksByTarget.get(model.name);
291
+ if (!fk) {
292
+ diagnostics.push({
293
+ code: "IDB_UNRESOLVED_BACKRELATION",
294
+ message: `Backrelation field "${model.name}.${field.name}" (list of ${field.typeName}) has no matching @relation in "${field.typeName}" pointing to "${model.name}". Add @relation(fields: [...], references: [...]) to the FK field in "${field.typeName}".`,
295
+ sourceId,
296
+ span: field.span
297
+ });
298
+ continue;
299
+ }
300
+ const mutableInterp = interp;
301
+ mutableInterp.relations[field.name] = {
302
+ to: crossRef(field.typeName),
303
+ cardinality: "1:N",
304
+ on: {
305
+ localFields: fk.targetFields,
306
+ targetFields: fk.localFields
307
+ }
308
+ };
309
+ }
310
+ }
311
+ if (diagnostics.length > 0) return notOk({
312
+ summary: "PSL to IDB contract interpretation failed",
313
+ diagnostics
314
+ });
315
+ const ns = UNBOUND_DOMAIN_NAMESPACE_ID;
316
+ const stores = {};
317
+ const roots = {};
318
+ const domainModels = {};
319
+ for (const [modelName, interp] of interpretedByName) {
320
+ stores[interp.storeName] = {
321
+ keyPath: interp.keyPath,
322
+ ...Object.keys(interp.indexes).length > 0 ? { indexes: interp.indexes } : {}
323
+ };
324
+ roots[interp.storeName] = crossRef(modelName);
325
+ const modelStorage = Object.keys(interp.relationsStorage).length > 0 ? {
326
+ storeName: interp.storeName,
327
+ keyPath: interp.keyPath,
328
+ relations: interp.relationsStorage
329
+ } : {
330
+ storeName: interp.storeName,
331
+ keyPath: interp.keyPath
332
+ };
333
+ domainModels[modelName] = {
334
+ fields: interp.fields,
335
+ relations: interp.relations,
336
+ storage: modelStorage
337
+ };
338
+ }
339
+ const storageBlock = {
340
+ stores,
341
+ namespaces: { [ns]: {
342
+ id: ns,
343
+ entries: {}
344
+ } }
345
+ };
346
+ const capabilities = { idb: {
347
+ ddlOnlyInUpgrade: true,
348
+ transactionalDDL: true
349
+ } };
350
+ const storageHash = computeStorageHash({
351
+ target: "idb",
352
+ targetFamily: "idb",
353
+ storage: storageBlock
354
+ });
355
+ const profileHash = computeProfileHash({
356
+ target: "idb",
357
+ targetFamily: "idb",
358
+ capabilities
359
+ });
360
+ const storage = {
361
+ ...storageBlock,
362
+ storageHash
363
+ };
364
+ const contract = {
365
+ target: "idb",
366
+ targetFamily: "idb",
367
+ roots,
368
+ domain: { namespaces: { [ns]: { models: domainModels } } },
369
+ storage,
370
+ capabilities,
371
+ extensionPacks: {},
372
+ meta: {},
373
+ profileHash
374
+ };
375
+ validateContract(contract);
376
+ return ok(contract);
377
+ }
378
+ //#endregion
379
+ //#region src/core/psl-provider.ts
380
+ function defaultOutputFromSchemaPath(schemaPath) {
381
+ const ext = extname(schemaPath);
382
+ if (ext.length === 0) return `${schemaPath}.json`;
383
+ const base = schemaPath.slice(0, -ext.length);
384
+ if (basename(base) === "schema") return `${base.slice(0, -6)}contract.json`;
385
+ return `${base}.json`;
386
+ }
387
+ function mapPslDiagnostics(diagnostics, sourceId) {
388
+ return diagnostics.map((d) => ({
389
+ code: d.code,
390
+ message: d.message,
391
+ sourceId,
392
+ ...d.span !== void 0 ? { span: d.span } : {}
393
+ }));
394
+ }
395
+ /**
396
+ * Creates a `ContractConfig` that reads an IDB schema from a `.prisma` file.
397
+ *
398
+ * Use this in `prisma-next.config.ts` as the `contract:` value when you prefer
399
+ * PSL authoring over the TypeScript-first `defineContract()` helper.
400
+ *
401
+ * @example
402
+ * ```ts
403
+ * import { defineConfig } from '@prisma-next-idb/family-idb/config-types';
404
+ * import { prismaIdbContract } from '@prisma-next-idb/family-idb/contract-psl';
405
+ *
406
+ * export default defineConfig({
407
+ * // ...
408
+ * contract: prismaIdbContract('./src/prisma/schema.prisma'),
409
+ * });
410
+ * ```
411
+ *
412
+ * The emitted `contract.json` lands next to the schema file by default
413
+ * (`schema.prisma` → `contract.json`). Override with `options.output`.
414
+ */
415
+ function prismaIdbContract(schemaPath, options) {
416
+ return {
417
+ source: {
418
+ inputs: [schemaPath],
419
+ load: async (context) => {
420
+ const [absoluteSchemaPath] = context.resolvedInputs;
421
+ if (absoluteSchemaPath === void 0) throw new Error("prismaIdbContract: context.resolvedInputs is empty. The CLI config loader should populate it positional-matched with source.inputs.");
422
+ let schema;
423
+ try {
424
+ schema = await readFile(absoluteSchemaPath, "utf-8");
425
+ } catch (error) {
426
+ const message = String(error);
427
+ return notOk({
428
+ summary: `Failed to read Prisma schema at "${schemaPath}"`,
429
+ diagnostics: [{
430
+ code: "PSL_SCHEMA_READ_FAILED",
431
+ message,
432
+ sourceId: schemaPath
433
+ }]
434
+ });
435
+ }
436
+ const { ast, diagnostics: parseDiagnostics, ok: parseOk } = parsePslDocument({
437
+ schema,
438
+ sourceId: schemaPath
439
+ });
440
+ const seedDiagnostics = mapPslDiagnostics(parseDiagnostics, schemaPath);
441
+ if (!parseOk) return notOk({
442
+ summary: `Failed to parse Prisma schema at "${schemaPath}"`,
443
+ diagnostics: seedDiagnostics
444
+ });
445
+ const interpreted = interpretPslDocumentToIdbContract(ast, schemaPath);
446
+ if (!interpreted.ok) return notOk({
447
+ ...interpreted.failure,
448
+ diagnostics: [...seedDiagnostics, ...interpreted.failure.diagnostics]
449
+ });
450
+ if (seedDiagnostics.length > 0) return notOk({
451
+ summary: `PSL parse warnings in "${schemaPath}"`,
452
+ diagnostics: seedDiagnostics
453
+ });
454
+ return ok(interpreted.value);
455
+ }
456
+ },
457
+ output: options?.output ?? defaultOutputFromSchemaPath(schemaPath)
458
+ };
459
+ }
460
+ //#endregion
461
+ export { interpretPslDocumentToIdbContract, prismaIdbContract };
462
+
463
+ //# sourceMappingURL=contract-psl.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-psl.mjs","names":[],"sources":["../../src/core/psl-interpreter.ts","../../src/core/psl-provider.ts"],"sourcesContent":["import type { ContractSourceDiagnostic, ContractSourceDiagnostics } from \"@prisma-next/config/config-types\";\nimport { computeProfileHash, computeStorageHash } from \"@prisma-next/contract/hashing\";\nimport type { ApplicationDomain, Contract, ContractField } from \"@prisma-next/contract/types\";\nimport { UNBOUND_DOMAIN_NAMESPACE_ID, crossRef } from \"@prisma-next/contract/types\";\nimport type { PslDocumentAst, PslField, PslModel } from \"@prisma-next/framework-components/psl-ast\";\nimport { flatPslModels } from \"@prisma-next/framework-components/psl-ast\";\nimport type {\n IdbIndexDefinition,\n IdbModelStorage,\n IdbReferentialAction,\n IdbStorage,\n IdbStoreDefinition,\n} from \"@prisma-next-idb/target-idb/pack\";\nimport { notOk, ok } from \"@prisma-next/utils/result\";\nimport type { Result } from \"@prisma-next/utils/result\";\nimport { validateContract } from \"./validate\";\n\n// ── Scalar type → codec ID mapping ────────────────────────────────────────────\n\nconst SCALAR_TO_CODEC_ID: Record<string, string> = {\n String: \"idb/string@1\",\n Int: \"idb/int32@1\",\n Float: \"idb/double@1\",\n Boolean: \"idb/bool@1\",\n DateTime: \"idb/date@1\",\n BigInt: \"idb/bigint@1\",\n Decimal: \"idb/decimal@1\",\n Json: \"idb/json@1\",\n Bytes: \"idb/bytes@1\",\n};\n\n// PSL PascalCase referential actions → IDB lowercase\nconst REFERENTIAL_ACTION_MAP: Record<string, IdbReferentialAction> = {\n Cascade: \"cascade\",\n SetNull: \"setNull\",\n SetDefault: \"setDefault\",\n Restrict: \"restrict\",\n NoAction: \"noAction\",\n};\n\n// ── Attribute arg helpers ──────────────────────────────────────────────────────\n\ntype AttributeArg = { kind: string; name?: string; value: string };\n\nfunction findPositionalArg(args: readonly AttributeArg[]): string | undefined {\n return args.find((a) => a.kind === \"positional\")?.value;\n}\n\nfunction findNamedArg(args: readonly AttributeArg[], name: string): string | undefined {\n return (args.find((a) => a.kind === \"named\" && a.name === name) as AttributeArg | undefined)?.value;\n}\n\nfunction parseStringArg(raw: string | undefined): string | undefined {\n if (raw === undefined) return undefined;\n const t = raw.trim();\n if ((t.startsWith('\"') && t.endsWith('\"')) || (t.startsWith(\"'\") && t.endsWith(\"'\"))) {\n return t.slice(1, -1);\n }\n return undefined;\n}\n\nfunction parseFieldList(raw: string | undefined): string[] | undefined {\n if (raw === undefined) return undefined;\n const t = raw.trim();\n if (!t.startsWith(\"[\") || !t.endsWith(\"]\")) return undefined;\n const inner = t.slice(1, -1).trim();\n if (inner === \"\") return [];\n return inner\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n}\n\nfunction lowerFirst(s: string): string {\n return s.charAt(0).toLowerCase() + s.slice(1);\n}\n\n// ── Field helpers ──────────────────────────────────────────────────────────────\n\nfunction hasFieldAttribute(field: PslField, name: string): boolean {\n return field.attributes.some((a) => a.name === name);\n}\n\nfunction getFieldAttribute(field: PslField, name: string) {\n return field.attributes.find((a) => a.name === name);\n}\n\n// ── Per-model interpretation result ───────────────────────────────────────────\n\ninterface InterpretedModel {\n readonly modelName: string;\n readonly storeName: string;\n readonly keyPath: string;\n readonly indexes: Record<string, IdbIndexDefinition>;\n readonly fields: Record<string, ContractField>;\n readonly relations: Record<\n string,\n {\n readonly to: ReturnType<typeof crossRef>;\n readonly cardinality: \"1:1\" | \"1:N\" | \"N:1\";\n readonly on: { readonly localFields: readonly string[]; readonly targetFields: readonly string[] };\n }\n >;\n readonly relationsStorage: Record<string, { onDelete?: IdbReferentialAction }>;\n /** FK-side declarations keyed by targetModelName for back-relation resolution. */\n readonly fksByTarget: ReadonlyMap<string, { fieldName: string; localFields: string[]; targetFields: string[] }>;\n}\n\n// ── Core interpreter ───────────────────────────────────────────────────────────\n\nfunction interpretModel(\n model: PslModel,\n modelNames: ReadonlySet<string>,\n sourceId: string,\n diagnostics: ContractSourceDiagnostic[]\n): InterpretedModel | undefined {\n // Derive store name from @@map or lowerFirst(modelName)\n const mapAttr = model.attributes.find((a) => a.name === \"map\");\n const storeName = parseStringArg(findPositionalArg(mapAttr?.args ?? [])) ?? lowerFirst(model.name);\n\n // Find the keyPath: @id field-level attribute OR @@id([field]) model-level\n let keyPath: string | undefined;\n let idFieldName: string | undefined;\n\n const idModelAttr = model.attributes.find((a) => a.name === \"id\");\n if (idModelAttr) {\n const fields = parseFieldList(findPositionalArg(idModelAttr.args));\n if (!fields || fields.length === 0) {\n diagnostics.push({\n code: \"IDB_INVALID_ID\",\n message: `Model \"${model.name}\" @@id([…]) is missing a field list.`,\n sourceId,\n span: idModelAttr.span,\n });\n return undefined;\n }\n if (fields.length > 1) {\n diagnostics.push({\n code: \"IDB_NO_COMPOUND_KEY\",\n message: `Model \"${model.name}\" @@id([${fields.join(\", \")}]) declares a compound key. IDB does not support compound primary keys — use a single @id field instead.`,\n sourceId,\n span: idModelAttr.span,\n });\n return undefined;\n }\n idFieldName = fields[0];\n keyPath = fields[0];\n }\n\n const idFields = model.fields.filter((f) => hasFieldAttribute(f, \"id\"));\n if (idFields.length > 1) {\n diagnostics.push({\n code: \"IDB_MULTIPLE_ID_FIELDS\",\n message: `Model \"${model.name}\" declares @id on multiple fields (${idFields.map((f) => f.name).join(\", \")}). Only one @id field is allowed.`,\n sourceId,\n span: model.span,\n });\n return undefined;\n }\n if (idFields.length === 1) {\n if (keyPath !== undefined) {\n diagnostics.push({\n code: \"IDB_INVALID_ID\",\n message: `Model \"${model.name}\" cannot declare both a field-level @id and a model-level @@id.`,\n sourceId,\n span: model.span,\n });\n return undefined;\n }\n idFieldName = idFields[0]!.name;\n keyPath = idFields[0]!.name;\n }\n\n if (keyPath === undefined) {\n diagnostics.push({\n code: \"IDB_MISSING_ID\",\n message: `Model \"${model.name}\" has no @id field. Add @id to exactly one scalar field.`,\n sourceId,\n span: model.span,\n });\n return undefined;\n }\n\n // ── Build indexes ────────────────────────────────────────────────────────────\n const indexes: Record<string, IdbIndexDefinition> = {};\n\n // @@index([fields]) model attribute\n for (const attr of model.attributes) {\n if (attr.name !== \"index\" && attr.name !== \"unique\") continue;\n const isUnique = attr.name === \"unique\";\n const fieldListRaw = findPositionalArg(attr.args);\n const fields = parseFieldList(fieldListRaw);\n if (!fields || fields.length === 0) {\n diagnostics.push({\n code: \"IDB_INVALID_INDEX\",\n message: `Model \"${model.name}\" @@${attr.name} is missing a field list.`,\n sourceId,\n span: attr.span,\n });\n continue;\n }\n if (fields.length > 1) {\n diagnostics.push({\n code: \"IDB_COMPOUND_INDEX_UNSUPPORTED\",\n message: `Model \"${model.name}\" @@${attr.name}([${fields.join(\", \")}]) declares a compound index. IDB compound indexes are not yet supported — use a single-field index.`,\n sourceId,\n span: attr.span,\n });\n continue;\n }\n const field = fields[0]!;\n const nameRaw = findNamedArg(attr.args, \"name\") ?? findNamedArg(attr.args, \"map\");\n const indexName = parseStringArg(nameRaw) ?? (isUnique ? `${field}_unique` : field);\n indexes[indexName] = { keyPath: field, unique: isUnique };\n }\n\n // ── Walk fields ──────────────────────────────────────────────────────────────\n const contractFields: Record<string, ContractField> = {};\n const relations: InterpretedModel[\"relations\"] = {};\n const relationsStorage: Record<string, { onDelete?: IdbReferentialAction }> = {};\n const fksByTarget = new Map<string, { fieldName: string; localFields: string[]; targetFields: string[] }>();\n\n for (const field of model.fields) {\n // Skip the @id field's optional marker — keyPath fields cannot be nullable in IDB\n if (field.name === idFieldName && field.optional) {\n diagnostics.push({\n code: \"IDB_NULLABLE_ID\",\n message: `Field \"${model.name}.${field.name}\" is marked as @id but is optional (?). The primary key cannot be nullable.`,\n sourceId,\n span: field.span,\n });\n }\n\n // Relation list field (backrelation) — skip for now, resolved in second pass\n if (field.list && modelNames.has(field.typeName)) {\n continue;\n }\n\n // FK-side relation field: non-list, type is a model, has @relation\n const relationAttr = getFieldAttribute(field, \"relation\");\n if (!field.list && modelNames.has(field.typeName) && relationAttr) {\n const args = relationAttr.args;\n const localFieldsRaw = findNamedArg(args, \"fields\");\n const targetFieldsRaw = findNamedArg(args, \"references\");\n const localFields = parseFieldList(localFieldsRaw);\n const targetFields = parseFieldList(targetFieldsRaw);\n\n if (!localFields || localFields.length === 0 || !targetFields || targetFields.length === 0) {\n diagnostics.push({\n code: \"IDB_INVALID_RELATION\",\n message: `Relation field \"${model.name}.${field.name}\" must declare both fields and references in @relation.`,\n sourceId,\n span: field.span,\n });\n continue;\n }\n if (localFields.length !== targetFields.length) {\n diagnostics.push({\n code: \"IDB_INVALID_RELATION\",\n message: `Relation field \"${model.name}.${field.name}\" must have the same number of fields and references.`,\n sourceId,\n span: field.span,\n });\n continue;\n }\n\n const onDeleteRaw = findNamedArg(args, \"onDelete\");\n const onDelete = onDeleteRaw ? REFERENTIAL_ACTION_MAP[onDeleteRaw.trim()] : undefined;\n\n if (onDeleteRaw && onDelete === undefined) {\n diagnostics.push({\n code: \"IDB_UNKNOWN_REFERENTIAL_ACTION\",\n message: `Relation field \"${model.name}.${field.name}\" has unknown onDelete value \"${onDeleteRaw}\". Valid values: ${Object.keys(REFERENTIAL_ACTION_MAP).join(\", \")}.`,\n sourceId,\n span: field.span,\n });\n }\n\n relations[field.name] = {\n to: crossRef(field.typeName),\n cardinality: \"N:1\",\n on: { localFields, targetFields },\n };\n if (onDelete !== undefined) {\n relationsStorage[field.name] = { onDelete };\n }\n fksByTarget.set(field.typeName, {\n fieldName: field.name,\n localFields,\n targetFields,\n });\n\n // Also create a default index on the FK field(s) if not already indexed\n for (const fkField of localFields) {\n if (!(fkField in indexes)) {\n indexes[fkField] = { keyPath: fkField, unique: false };\n }\n }\n continue;\n }\n\n // Non-model relation field without @relation — not a scalar, skip with error\n if (!field.list && modelNames.has(field.typeName) && !relationAttr) {\n diagnostics.push({\n code: \"IDB_MISSING_RELATION_ATTRIBUTE\",\n message: `Field \"${model.name}.${field.name}\" has model type \"${field.typeName}\" but no @relation attribute. Add @relation(fields: [...], references: [...]).`,\n sourceId,\n span: field.span,\n });\n continue;\n }\n\n // Skip list fields of non-model types (JSON arrays etc. are handled as Json codec)\n if (field.list) {\n // Only model-type lists are backrelations; non-model lists are not supported in IDB.\n continue;\n }\n\n // Scalar field\n const codecId = SCALAR_TO_CODEC_ID[field.typeName];\n if (codecId === undefined) {\n diagnostics.push({\n code: \"IDB_UNSUPPORTED_FIELD_TYPE\",\n message: `Field \"${model.name}.${field.name}\" has unsupported type \"${field.typeName}\". Supported types: ${Object.keys(SCALAR_TO_CODEC_ID).join(\", \")}.`,\n sourceId,\n span: field.span,\n });\n continue;\n }\n\n contractFields[field.name] = {\n nullable: field.optional,\n type: { kind: \"scalar\", codecId },\n };\n\n // @unique field attribute → unique index\n if (hasFieldAttribute(field, \"unique\")) {\n const existingKey = Object.entries(indexes).find(([, idx]) => idx.keyPath === field.name)?.[0];\n if (!existingKey) {\n indexes[`${field.name}_unique`] = { keyPath: field.name, unique: true };\n }\n }\n }\n\n return {\n modelName: model.name,\n storeName,\n keyPath,\n indexes,\n fields: contractFields,\n relations,\n relationsStorage,\n fksByTarget,\n };\n}\n\n// ── Main export ────────────────────────────────────────────────────────────────\n\n/**\n * Interprets a parsed PSL document AST and produces an IDB `Contract`.\n *\n * This is the IDB equivalent of `interpretPslDocumentToSqlContract` from the\n * SQL family. It handles IDB-specific constraints:\n *\n * - No namespace blocks (IDB has a single implicit `__unbound__` namespace)\n * - No compound primary keys (IDB `keyPath` must be a single field)\n * - Relations are FK-side (`@relation`) + backrelation list fields\n * - Indexes map directly to `IDBObjectStore.createIndex()` calls\n */\nexport function interpretPslDocumentToIdbContract(\n ast: PslDocumentAst,\n sourceId: string\n): Result<Contract<IdbStorage>, ContractSourceDiagnostics> {\n const diagnostics: ContractSourceDiagnostic[] = [];\n\n // IDB does not support namespace blocks\n const explicitNamespaces = ast.namespaces.filter((ns) => ns.name !== \"__unspecified__\");\n for (const ns of explicitNamespaces) {\n diagnostics.push({\n code: \"IDB_UNSUPPORTED_NAMESPACE_BLOCK\",\n message: `IDB does not support \\`namespace ${ns.name} { … }\\` blocks. All models must be declared at the top level.`,\n sourceId,\n span: ns.span,\n });\n }\n\n const allModels = flatPslModels(ast);\n const modelNames = new Set(allModels.map((m) => m.name));\n const interpretedByName = new Map<string, InterpretedModel>();\n\n // First pass: interpret each model individually\n for (const model of allModels) {\n const result = interpretModel(model, modelNames, sourceId, diagnostics);\n if (result) {\n interpretedByName.set(model.name, result);\n }\n }\n\n // Second pass: resolve backrelation list fields\n for (const model of allModels) {\n const interp = interpretedByName.get(model.name);\n if (!interp) continue;\n\n for (const field of model.fields) {\n if (!field.list || !modelNames.has(field.typeName)) continue;\n\n const targetInterp = interpretedByName.get(field.typeName);\n if (!targetInterp) continue;\n\n // Find the FK in the target model that points back to this model\n const fk = targetInterp.fksByTarget.get(model.name);\n if (!fk) {\n diagnostics.push({\n code: \"IDB_UNRESOLVED_BACKRELATION\",\n message: `Backrelation field \"${model.name}.${field.name}\" (list of ${field.typeName}) has no matching @relation in \"${field.typeName}\" pointing to \"${model.name}\". Add @relation(fields: [...], references: [...]) to the FK field in \"${field.typeName}\".`,\n sourceId,\n span: field.span,\n });\n continue;\n }\n\n // The 1:N side: localFields are the PK fields of this model, targetFields are the FK fields in the target\n const mutableInterp = interp as { relations: Record<string, unknown> };\n mutableInterp.relations[field.name] = {\n to: crossRef(field.typeName),\n cardinality: \"1:N\",\n on: { localFields: fk.targetFields, targetFields: fk.localFields },\n };\n }\n }\n\n if (diagnostics.length > 0) {\n return notOk({\n summary: \"PSL to IDB contract interpretation failed\",\n diagnostics,\n });\n }\n\n // ── Build the contract ────────────────────────────────────────────────────────\n\n const ns = UNBOUND_DOMAIN_NAMESPACE_ID;\n const stores: Record<string, IdbStoreDefinition> = {};\n const roots: Record<string, ReturnType<typeof crossRef>> = {};\n const domainModels: Record<string, unknown> = {};\n\n for (const [modelName, interp] of interpretedByName) {\n stores[interp.storeName] = {\n keyPath: interp.keyPath,\n ...(Object.keys(interp.indexes).length > 0 ? { indexes: interp.indexes } : {}),\n };\n\n roots[interp.storeName] = crossRef(modelName);\n\n const modelStorage: IdbModelStorage =\n Object.keys(interp.relationsStorage).length > 0\n ? { storeName: interp.storeName, keyPath: interp.keyPath, relations: interp.relationsStorage }\n : { storeName: interp.storeName, keyPath: interp.keyPath };\n\n domainModels[modelName] = {\n fields: interp.fields,\n relations: interp.relations,\n storage: modelStorage,\n };\n }\n\n const storageBlock = {\n stores,\n namespaces: { [ns]: { id: ns, entries: {} } },\n };\n\n const capabilities = {\n idb: { ddlOnlyInUpgrade: true, transactionalDDL: true },\n };\n\n const storageHash = computeStorageHash({\n target: \"idb\",\n targetFamily: \"idb\",\n storage: storageBlock,\n });\n\n const profileHash = computeProfileHash({\n target: \"idb\",\n targetFamily: \"idb\",\n capabilities,\n });\n\n const storage: IdbStorage = { ...storageBlock, storageHash };\n\n const domain = {\n namespaces: { [ns]: { models: domainModels } },\n } as unknown as ApplicationDomain;\n\n const contract: Contract<IdbStorage> = {\n target: \"idb\",\n targetFamily: \"idb\",\n roots,\n domain,\n storage,\n capabilities,\n extensionPacks: {},\n meta: {},\n profileHash,\n };\n\n validateContract(contract);\n return ok(contract);\n}\n","import { readFile } from \"node:fs/promises\";\nimport type { ContractConfig, ContractSourceDiagnostic } from \"@prisma-next/config/config-types\";\nimport type { PslDiagnostic } from \"@prisma-next/framework-components/psl-ast\";\nimport { parsePslDocument } from \"@prisma-next/psl-parser\";\nimport { notOk, ok } from \"@prisma-next/utils/result\";\nimport { extname, basename } from \"pathe\";\nimport { interpretPslDocumentToIdbContract } from \"./psl-interpreter\";\n\nfunction defaultOutputFromSchemaPath(schemaPath: string): string {\n const ext = extname(schemaPath);\n if (ext.length === 0) return `${schemaPath}.json`;\n const base = schemaPath.slice(0, -ext.length);\n if (basename(base) === \"schema\") {\n return `${base.slice(0, -\"schema\".length)}contract.json`;\n }\n return `${base}.json`;\n}\n\nfunction mapPslDiagnostics(diagnostics: readonly PslDiagnostic[], sourceId: string): ContractSourceDiagnostic[] {\n return diagnostics.map((d) => ({\n code: d.code as string,\n message: d.message,\n sourceId,\n ...(d.span !== undefined ? { span: d.span } : {}),\n }));\n}\n\nexport interface PrismaIdbContractOptions {\n readonly output?: string;\n}\n\n/**\n * Creates a `ContractConfig` that reads an IDB schema from a `.prisma` file.\n *\n * Use this in `prisma-next.config.ts` as the `contract:` value when you prefer\n * PSL authoring over the TypeScript-first `defineContract()` helper.\n *\n * @example\n * ```ts\n * import { defineConfig } from '@prisma-next-idb/family-idb/config-types';\n * import { prismaIdbContract } from '@prisma-next-idb/family-idb/contract-psl';\n *\n * export default defineConfig({\n * // ...\n * contract: prismaIdbContract('./src/prisma/schema.prisma'),\n * });\n * ```\n *\n * The emitted `contract.json` lands next to the schema file by default\n * (`schema.prisma` → `contract.json`). Override with `options.output`.\n */\nexport function prismaIdbContract(schemaPath: string, options?: PrismaIdbContractOptions): ContractConfig {\n return {\n source: {\n inputs: [schemaPath],\n load: async (context) => {\n const [absoluteSchemaPath] = context.resolvedInputs;\n if (absoluteSchemaPath === undefined) {\n throw new Error(\n \"prismaIdbContract: context.resolvedInputs is empty. The CLI config loader should populate it positional-matched with source.inputs.\"\n );\n }\n\n let schema: string;\n try {\n schema = await readFile(absoluteSchemaPath, \"utf-8\");\n } catch (error) {\n const message = String(error);\n return notOk({\n summary: `Failed to read Prisma schema at \"${schemaPath}\"`,\n diagnostics: [\n {\n code: \"PSL_SCHEMA_READ_FAILED\",\n message,\n sourceId: schemaPath,\n },\n ],\n });\n }\n\n const {\n ast,\n diagnostics: parseDiagnostics,\n ok: parseOk,\n } = parsePslDocument({\n schema,\n sourceId: schemaPath,\n });\n\n const seedDiagnostics = mapPslDiagnostics(parseDiagnostics, schemaPath);\n\n if (!parseOk) {\n return notOk({\n summary: `Failed to parse Prisma schema at \"${schemaPath}\"`,\n diagnostics: seedDiagnostics,\n });\n }\n\n const interpreted = interpretPslDocumentToIdbContract(ast, schemaPath);\n if (!interpreted.ok) {\n return notOk({\n ...interpreted.failure,\n diagnostics: [...seedDiagnostics, ...interpreted.failure.diagnostics],\n });\n }\n\n if (seedDiagnostics.length > 0) {\n return notOk({\n summary: `PSL parse warnings in \"${schemaPath}\"`,\n diagnostics: seedDiagnostics,\n });\n }\n\n return ok(interpreted.value);\n },\n },\n output: options?.output ?? defaultOutputFromSchemaPath(schemaPath),\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ import { Contract } from "@prisma-next/contract/types";
2
+ import { IdbReferentialAction, IdbStorage } from "@prisma-next-idb/target-idb/pack";
3
+
4
+ //#region src/core/contract-builder.d.ts
5
+ type PrismaScalarType = "String" | "Int" | "Float" | "Boolean" | "DateTime" | "BigInt" | "Decimal" | "Json" | "Bytes";
6
+ /**
7
+ * A field spec string: the Prisma scalar type name, optionally suffixed with
8
+ * `?` to indicate the field is nullable (e.g. `"String"`, `"Int?"`, `"DateTime?"`).
9
+ */
10
+ type FieldSpec = PrismaScalarType | `${PrismaScalarType}?`;
11
+ type RelationDef = {
12
+ readonly to: string;
13
+ readonly cardinality: "1:1" | "1:N" | "N:1";
14
+ readonly on: {
15
+ readonly local: readonly string[];
16
+ readonly target: readonly string[];
17
+ };
18
+ readonly onDelete?: IdbReferentialAction;
19
+ };
20
+ type IndexDef = {
21
+ readonly keyPath: string;
22
+ readonly unique?: boolean;
23
+ readonly multiEntry?: boolean;
24
+ };
25
+ type ModelDef = {
26
+ readonly store: string;
27
+ readonly key: string; /** All scalar fields on the model. Use `"Type"` for non-nullable, `"Type?"` for nullable. */
28
+ readonly fields: Record<string, FieldSpec>;
29
+ readonly indexes?: Record<string, IndexDef>;
30
+ readonly relations?: Record<string, RelationDef>;
31
+ };
32
+ type DefineContractInput = {
33
+ /** Pass the default export of `@prisma-next-idb/family-idb/pack`. */readonly family: {
34
+ readonly familyId: "idb";
35
+ readonly id: string;
36
+ }; /** Pass the default export of `@prisma-next-idb/target-idb/pack`. */
37
+ readonly target: {
38
+ readonly targetId: string;
39
+ readonly id: string;
40
+ };
41
+ readonly models: Record<string, ModelDef>;
42
+ };
43
+ /**
44
+ * Builds a typed IDB contract from a developer-friendly model definition.
45
+ *
46
+ * This is the TypeScript-first (no-emit) authoring path per ADR 006. The
47
+ * returned contract object can be passed directly to `createIdbClient()` or
48
+ * to `typescriptContract()` for config-file usage.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * import { defineContract } from '@prisma-next-idb/family-idb/contract-ts';
53
+ * import idbFamily from '@prisma-next-idb/family-idb/pack';
54
+ * import idbTarget from '@prisma-next-idb/target-idb/pack';
55
+ *
56
+ * export default defineContract({
57
+ * family: idbFamily,
58
+ * target: idbTarget,
59
+ * models: {
60
+ * User: {
61
+ * store: 'users',
62
+ * key: 'id',
63
+ * fields: { id: 'String', name: 'String?', email: 'String' },
64
+ * indexes: { byEmail: { keyPath: 'email', unique: true } },
65
+ * },
66
+ * },
67
+ * });
68
+ * ```
69
+ */
70
+ declare function defineContract(input: DefineContractInput): Contract<IdbStorage>;
71
+ //#endregion
72
+ export { type DefineContractInput, type FieldSpec, type IndexDef, type ModelDef, type RelationDef, defineContract };
73
+ //# sourceMappingURL=contract-ts.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-ts.d.mts","names":[],"sources":["../../src/core/contract-builder.ts"],"mappings":";;;;KAcK,gBAAA;;AALqC;;;KAW9B,SAAA,GAAY,gBAAA,MAAsB,gBAAgB;AAAA,KAgBlD,WAAA;EAAA,SACD,EAAA;EAAA,SACA,WAAA;EAAA,SACA,EAAA;IAAA,SACE,KAAA;IAAA,SACA,MAAA;EAAA;EAAA,SAEF,QAAA,GAAW,oBAAoB;AAAA;AAAA,KAG9B,QAAA;EAAA,SACD,OAAA;EAAA,SACA,MAAA;EAAA,SACA,UAAA;AAAA;AAAA,KAGC,QAAA;EAAA,SACD,KAAA;EAAA,SACA,GAAA,UAX+B;EAAA,SAa/B,MAAA,EAAQ,MAAA,SAAe,SAAA;EAAA,SACvB,OAAA,GAAU,MAAA,SAAe,QAAA;EAAA,SACzB,SAAA,GAAY,MAAA,SAAe,WAAA;AAAA;AAAA,KAG1B,mBAAA;EAdD,8EAgBA,MAAA;IAAA,SAAmB,QAAA;IAAA,SAA0B,EAAA;EAAA,GAX5C;EAAA,SAaD,MAAA;IAAA,SAAmB,QAAA;IAAA,SAA2B,EAAA;EAAA;EAAA,SAC9C,MAAA,EAAQ,MAAM,SAAS,QAAA;AAAA;;;;;;;;;;;;;;;;;AARe;AAGjD;;;;;;;;;;iBA+HgB,cAAA,CAAe,KAAA,EAAO,mBAAA,GAAsB,QAAA,CAAS,UAAA"}