@kurotako/parser-prisma 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.
package/dist/index.cjs ADDED
@@ -0,0 +1,879 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ PrismaInputError: () => PrismaInputError,
34
+ PrismaParserOptions: () => PrismaParserOptions,
35
+ PrismaPeerMissingError: () => PrismaPeerMissingError,
36
+ PrismaSchemaError: () => PrismaSchemaError,
37
+ prismaParser: () => prismaParser
38
+ });
39
+ module.exports = __toCommonJS(index_exports);
40
+
41
+ // src/errors.ts
42
+ var import_core = require("@kurotako/core");
43
+ var PrismaInputError = class extends import_core.TakoError {
44
+ namespace;
45
+ resolvedPath;
46
+ constructor(namespace, resolvedPath, detail) {
47
+ super(
48
+ "prisma_input",
49
+ `prisma parser (namespace '${namespace}'): ${detail} (resolved path: ${resolvedPath})`
50
+ );
51
+ this.namespace = namespace;
52
+ this.resolvedPath = resolvedPath;
53
+ }
54
+ };
55
+ var PrismaPeerMissingError = class extends import_core.TakoError {
56
+ namespace;
57
+ constructor(namespace, options) {
58
+ super(
59
+ "prisma_peer_missing",
60
+ `prisma parser (namespace '${namespace}'): '@prisma/internals' could not be resolved. Add it as a devDependency (\`bun add -d @prisma/internals\`, matching your Prisma major). In a monorepo it is resolved from the directory holding the schema, so it may be installed in the sub-project that owns the schema rather than at the repo root. Note: installing it pulls @prisma/engines, whose postinstall downloads a schema-engine binary.`,
61
+ options
62
+ );
63
+ this.namespace = namespace;
64
+ }
65
+ };
66
+ var PrismaSchemaError = class extends import_core.TakoError {
67
+ namespace;
68
+ prismaMessage;
69
+ constructor(namespace, prismaMessage, options) {
70
+ super(
71
+ "prisma_schema",
72
+ `prisma parser (namespace '${namespace}'): the Prisma schema is invalid:
73
+ ${prismaMessage}`,
74
+ options
75
+ );
76
+ this.namespace = namespace;
77
+ this.prismaMessage = prismaMessage;
78
+ }
79
+ };
80
+
81
+ // src/options.ts
82
+ var v = __toESM(require("valibot"), 1);
83
+ var PrismaParserOptions = v.strictObject({
84
+ schema: v.optional(v.string(), "./prisma/schema.prisma"),
85
+ version: v.optional(v.picklist([7, 8]))
86
+ });
87
+
88
+ // src/parser.ts
89
+ var import_node_path3 = require("path");
90
+ var import_config = require("@kurotako/config");
91
+
92
+ // src/detect.ts
93
+ var import_promises = require("fs/promises");
94
+ var import_node_path = require("path");
95
+ var PRISMA_EXT = ".prisma";
96
+ var CONTRACT_FILE = "contract.json";
97
+ function toPosix(p) {
98
+ return import_node_path.sep === "/" ? p : p.split(import_node_path.sep).join("/");
99
+ }
100
+ async function pathKind(p) {
101
+ try {
102
+ const s = await (0, import_promises.stat)(p);
103
+ return s.isDirectory() ? "dir" : "file";
104
+ } catch (err) {
105
+ if (err.code === "ENOENT") {
106
+ return "missing";
107
+ }
108
+ throw err;
109
+ }
110
+ }
111
+ async function collectPrismaFiles(dir) {
112
+ const found = [];
113
+ const entries = await (0, import_promises.readdir)(dir, { withFileTypes: true });
114
+ for (const entry of entries) {
115
+ const full = (0, import_node_path.join)(dir, entry.name);
116
+ if (entry.isFile() && entry.name.endsWith(PRISMA_EXT)) {
117
+ found.push(full);
118
+ } else if (entry.isDirectory()) {
119
+ const nested = await (0, import_promises.readdir)(full, { withFileTypes: true });
120
+ for (const child of nested) {
121
+ if (child.isFile() && child.name.endsWith(PRISMA_EXT)) {
122
+ found.push((0, import_node_path.join)(full, child.name));
123
+ }
124
+ }
125
+ }
126
+ }
127
+ return found;
128
+ }
129
+ async function dirHasContract(dir) {
130
+ return await pathKind((0, import_node_path.join)(dir, CONTRACT_FILE)) === "file";
131
+ }
132
+ async function readTuples(root, files) {
133
+ const tuples = await Promise.all(
134
+ files.map(
135
+ async (file) => [
136
+ toPosix((0, import_node_path.relative)(root, file)),
137
+ await (0, import_promises.readFile)(file, "utf8")
138
+ ]
139
+ )
140
+ );
141
+ return tuples.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
142
+ }
143
+ async function resolveMode7(namespace, resolved, kind) {
144
+ if (kind === "file") {
145
+ return {
146
+ mode: 7,
147
+ kind: "file",
148
+ files: await readTuples((0, import_node_path.dirname)(resolved), [resolved])
149
+ };
150
+ }
151
+ const files = await collectPrismaFiles(resolved);
152
+ if (files.length === 0) {
153
+ throw new PrismaInputError(
154
+ namespace,
155
+ resolved,
156
+ "the schema folder contains no .prisma file"
157
+ );
158
+ }
159
+ return { mode: 7, kind: "folder", files: await readTuples(resolved, files) };
160
+ }
161
+ async function resolveMode8(namespace, resolved, kind) {
162
+ if (kind === "dir") {
163
+ const contractPath = (0, import_node_path.join)(resolved, CONTRACT_FILE);
164
+ if (await pathKind(contractPath) !== "file") {
165
+ throw new PrismaInputError(
166
+ namespace,
167
+ resolved,
168
+ `version 8 mode expects a ${CONTRACT_FILE} in the folder`
169
+ );
170
+ }
171
+ return { mode: 8, kind: "contract", contractPath };
172
+ }
173
+ return { mode: 8, kind: "contract", contractPath: resolved };
174
+ }
175
+ async function resolveInput(cwd, o, namespace = "<unknown>") {
176
+ const resolved = (0, import_node_path.resolve)(cwd, o.schema);
177
+ const kind = await pathKind(resolved);
178
+ if (kind === "missing") {
179
+ throw new PrismaInputError(
180
+ namespace,
181
+ resolved,
182
+ "the schema path does not exist"
183
+ );
184
+ }
185
+ if (o.version === 8) {
186
+ return resolveMode8(namespace, resolved, kind);
187
+ }
188
+ if (o.version === 7) {
189
+ return resolveMode7(namespace, resolved, kind);
190
+ }
191
+ if (kind === "file") {
192
+ if ((0, import_node_path.basename)(resolved) === CONTRACT_FILE) {
193
+ return { mode: 8, kind: "contract", contractPath: resolved };
194
+ }
195
+ if (resolved.endsWith(PRISMA_EXT)) {
196
+ return resolveMode7(namespace, resolved, "file");
197
+ }
198
+ throw new PrismaInputError(
199
+ namespace,
200
+ resolved,
201
+ `not a .prisma file or a ${CONTRACT_FILE}`
202
+ );
203
+ }
204
+ if (await dirHasContract(resolved)) {
205
+ return resolveMode8(namespace, resolved, "dir");
206
+ }
207
+ return resolveMode7(namespace, resolved, "dir");
208
+ }
209
+
210
+ // src/dmmf/load.ts
211
+ var import_promises2 = require("fs/promises");
212
+ var import_node_module = require("module");
213
+ var import_node_path2 = require("path");
214
+ var import_node_url = require("url");
215
+
216
+ // src/dmmf/read.ts
217
+ function readField(f) {
218
+ const field = {
219
+ name: f.name,
220
+ type: f.type,
221
+ kind: f.kind === "enum" ? "enum" : f.kind === "unsupported" ? "unsupported" : "scalar",
222
+ isList: f.isList,
223
+ isRequired: f.isRequired,
224
+ isUnique: f.isUnique,
225
+ isUpdatedAt: f.isUpdatedAt ?? false,
226
+ hasDefaultValue: f.hasDefaultValue,
227
+ nativeType: f.nativeType ? [f.nativeType[0], [...f.nativeType[1]]] : null
228
+ };
229
+ if (f.default !== void 0) {
230
+ field.default = f.default;
231
+ }
232
+ if (f.documentation !== void 0) {
233
+ field.doc = f.documentation;
234
+ }
235
+ return field;
236
+ }
237
+ function readEdge(f) {
238
+ const edge = {
239
+ fieldName: f.name,
240
+ relationName: f.relationName ?? "",
241
+ targetEntity: f.type,
242
+ isList: f.isList,
243
+ isRequired: f.isRequired,
244
+ fromFields: [...f.relationFromFields ?? []],
245
+ toFields: [...f.relationToFields ?? []]
246
+ };
247
+ if (f.relationOnDelete !== void 0) {
248
+ edge.onDelete = f.relationOnDelete;
249
+ }
250
+ if (f.relationOnUpdate !== void 0) {
251
+ edge.onUpdate = f.relationOnUpdate;
252
+ }
253
+ return edge;
254
+ }
255
+ function readPrimaryKey(model) {
256
+ if (model.primaryKey && model.primaryKey.fields.length > 0) {
257
+ return [...model.primaryKey.fields];
258
+ }
259
+ const id = model.fields.find((f) => f.isId);
260
+ return id ? [id.name] : [];
261
+ }
262
+ function readUniques(model) {
263
+ if (model.uniqueIndexes.length > 0) {
264
+ return model.uniqueIndexes.map((u) => {
265
+ const entry = { fields: [...u.fields] };
266
+ if (u.name) {
267
+ entry.name = u.name;
268
+ }
269
+ return entry;
270
+ });
271
+ }
272
+ return model.uniqueFields.map((fields) => ({ fields: [...fields] }));
273
+ }
274
+ function readIndexes(modelName, all) {
275
+ if (!all) {
276
+ return [];
277
+ }
278
+ return all.filter((idx) => idx.model === modelName && idx.type === "normal").map((idx) => {
279
+ const entry = { fields: idx.fields.map((f) => f.name) };
280
+ if (idx.name) {
281
+ entry.name = idx.name;
282
+ }
283
+ if (idx.algorithm) {
284
+ entry.type = idx.algorithm.toLowerCase();
285
+ }
286
+ return entry;
287
+ });
288
+ }
289
+ function readEntity(model, doc) {
290
+ const fields = [];
291
+ const relationEdges = [];
292
+ for (const f of model.fields) {
293
+ if (f.kind === "object") {
294
+ relationEdges.push(readEdge(f));
295
+ } else {
296
+ fields.push(readField(f));
297
+ }
298
+ }
299
+ const entity = {
300
+ name: model.name,
301
+ fields,
302
+ relationEdges,
303
+ primaryKey: readPrimaryKey(model),
304
+ uniques: readUniques(model),
305
+ indexes: readIndexes(model.name, doc.datamodel.indexes)
306
+ };
307
+ if (model.dbName) {
308
+ entity.dbName = model.dbName;
309
+ }
310
+ if (model.documentation !== void 0) {
311
+ entity.doc = model.documentation;
312
+ }
313
+ return entity;
314
+ }
315
+ function readEnum(e) {
316
+ const def = {
317
+ name: e.name,
318
+ values: e.values.map((value) => {
319
+ const entry = { name: value.name };
320
+ if (value.dbName) {
321
+ entry.dbName = value.dbName;
322
+ }
323
+ return entry;
324
+ })
325
+ };
326
+ if (e.dbName) {
327
+ def.dbName = e.dbName;
328
+ }
329
+ if (e.documentation !== void 0) {
330
+ def.doc = e.documentation;
331
+ }
332
+ return def;
333
+ }
334
+ function toPrismaModel(doc) {
335
+ return {
336
+ entities: doc.datamodel.models.map((m) => readEntity(m, doc)),
337
+ enums: doc.datamodel.enums.map(readEnum)
338
+ };
339
+ }
340
+
341
+ // src/dmmf/load.ts
342
+ async function resolveInternals(ctx) {
343
+ const base = ctx.anchorDir ?? ctx.cwd;
344
+ const require2 = (0, import_node_module.createRequire)((0, import_node_path2.join)(base, "noop.js"));
345
+ let entry;
346
+ try {
347
+ entry = require2.resolve("@prisma/internals");
348
+ } catch (err) {
349
+ throw new PrismaPeerMissingError(ctx.namespace, { cause: err });
350
+ }
351
+ let mod;
352
+ try {
353
+ mod = await import((0, import_node_url.pathToFileURL)(entry).href);
354
+ } catch (err) {
355
+ throw new PrismaPeerMissingError(ctx.namespace, { cause: err });
356
+ }
357
+ const getDMMF = mod.default?.getDMMF ?? mod.getDMMF;
358
+ if (typeof getDMMF !== "function") {
359
+ throw new PrismaPeerMissingError(ctx.namespace);
360
+ }
361
+ let prismaVersion = "unknown";
362
+ try {
363
+ const pkg = JSON.parse(
364
+ await (0, import_promises2.readFile)(require2.resolve("@prisma/internals/package.json"), "utf8")
365
+ );
366
+ if (typeof pkg.version === "string") {
367
+ prismaVersion = pkg.version;
368
+ }
369
+ } catch {
370
+ ctx.logger.debug(
371
+ "prisma parser: could not read @prisma/internals version",
372
+ { namespace: ctx.namespace }
373
+ );
374
+ }
375
+ return { getDMMF, prismaVersion };
376
+ }
377
+ async function readDmmf(input, ctx) {
378
+ const { getDMMF, prismaVersion } = await resolveInternals(ctx);
379
+ const datamodel = input.kind === "file" ? input.files[0]?.[1] ?? "" : input.files.map(([path, content]) => [path, content]);
380
+ let doc;
381
+ try {
382
+ doc = await getDMMF({ datamodel });
383
+ } catch (err) {
384
+ const message = err instanceof Error ? err.message : String(err);
385
+ throw new PrismaSchemaError(ctx.namespace, message, { cause: err });
386
+ }
387
+ return { model: toPrismaModel(doc), prismaVersion };
388
+ }
389
+
390
+ // src/map/build.ts
391
+ var import_ir = require("@kurotako/ir");
392
+
393
+ // src/map/defaults.ts
394
+ var ID_FORMATS = {
395
+ uuid: "uuid",
396
+ cuid: "cuid",
397
+ ulid: "ulid"
398
+ };
399
+ function isCall(raw) {
400
+ return typeof raw === "object" && !Array.isArray(raw) && raw !== null;
401
+ }
402
+ function mapDefault(raw) {
403
+ if (raw === void 0) {
404
+ return {};
405
+ }
406
+ if (!isCall(raw)) {
407
+ return { default: { kind: "value", value: raw } };
408
+ }
409
+ const { name, args } = raw;
410
+ if (name === "dbgenerated") {
411
+ return { default: { kind: "expr", expr: "dbgenerated", args: [...args] } };
412
+ }
413
+ const idFormat = ID_FORMATS[name];
414
+ if (idFormat !== void 0) {
415
+ const format = name === "cuid" && args[0] === 2 ? "cuid2" : idFormat;
416
+ return { default: { kind: "expr", expr: `${name}()` }, format };
417
+ }
418
+ return { default: { kind: "expr", expr: `${name}()` } };
419
+ }
420
+
421
+ // src/map/scalars.ts
422
+ var SCALAR_TABLE = {
423
+ String: "string",
424
+ Boolean: "boolean",
425
+ Int: "int",
426
+ BigInt: "bigint",
427
+ Float: "float",
428
+ Decimal: "decimal",
429
+ DateTime: "datetime",
430
+ Json: "json",
431
+ Bytes: "bytes"
432
+ };
433
+ var LENGTH_NATIVE = /* @__PURE__ */ new Set(["VarChar", "Char", "NVarChar", "String"]);
434
+ var UUID_NATIVE = /* @__PURE__ */ new Set(["Uuid", "ObjectId"]);
435
+ var NOOP_NATIVE = /* @__PURE__ */ new Set([
436
+ "Text",
437
+ "Citext",
438
+ "Xml",
439
+ "Bit",
440
+ "VarBit",
441
+ "Inet",
442
+ "Line",
443
+ "LongText",
444
+ "MediumText",
445
+ "TinyText",
446
+ "SmallInt",
447
+ "MediumInt",
448
+ "UnsignedInt",
449
+ "UnsignedBigInt",
450
+ "Money",
451
+ "Real",
452
+ "DoublePrecision",
453
+ "Decimal",
454
+ "Numeric",
455
+ "SmallMoney",
456
+ "Timestamp",
457
+ "Timestamptz",
458
+ "DateTime2",
459
+ "DateTimeOffset"
460
+ ]);
461
+ function refineNative(native, constraints, result, field, logger) {
462
+ const [name, args] = native;
463
+ if (LENGTH_NATIVE.has(name)) {
464
+ const n = Number(args[0]);
465
+ if (Number.isFinite(n)) {
466
+ constraints.maxLength = n;
467
+ }
468
+ return;
469
+ }
470
+ if (UUID_NATIVE.has(name)) {
471
+ result.scalarOverride = "uuid";
472
+ return;
473
+ }
474
+ if (name === "Date") {
475
+ result.scalarOverride = "date";
476
+ return;
477
+ }
478
+ if (name === "Time" || name === "Timetz") {
479
+ constraints.format = "time";
480
+ return;
481
+ }
482
+ if (NOOP_NATIVE.has(name)) {
483
+ return;
484
+ }
485
+ logger?.debug(`prisma parser: ignoring unmapped native type @db.${name}`, {
486
+ field: field.name
487
+ });
488
+ }
489
+ function mapFieldType(field, logger) {
490
+ const constraints = {};
491
+ if (field.kind === "unsupported") {
492
+ return { type: { kind: "unknown", hint: field.type }, constraints };
493
+ }
494
+ if (field.kind === "enum") {
495
+ return { type: { kind: "enum", ref: field.type }, constraints };
496
+ }
497
+ const scalar = SCALAR_TABLE[field.type];
498
+ if (scalar === void 0) {
499
+ return { type: { kind: "unknown", hint: field.type }, constraints };
500
+ }
501
+ const result = {
502
+ type: { kind: "scalar", scalar },
503
+ constraints
504
+ };
505
+ if (field.nativeType) {
506
+ refineNative(field.nativeType, constraints, result, field, logger);
507
+ }
508
+ return result;
509
+ }
510
+
511
+ // src/map/relations.ts
512
+ var ACTION_MAP = {
513
+ Cascade: "cascade",
514
+ Restrict: "restrict",
515
+ SetNull: "setNull",
516
+ SetDefault: "setDefault",
517
+ NoAction: "noAction"
518
+ };
519
+ function mapAction(raw) {
520
+ return raw === void 0 ? void 0 : ACTION_MAP[raw];
521
+ }
522
+ function lcfirst(s) {
523
+ return s.length === 0 ? s : s.charAt(0).toLowerCase() + s.slice(1);
524
+ }
525
+ function isImplicitM2M(edges) {
526
+ return edges.length === 2 && edges.every(
527
+ ({ edge }) => edge.isList && edge.fromFields.length === 0 && edge.toFields.length === 0
528
+ );
529
+ }
530
+ function pkScalar(entities, entityName, logger) {
531
+ const entity = entities.get(entityName);
532
+ if (entity && entity.primaryKey.length === 1) {
533
+ const pkName = entity.primaryKey[0];
534
+ const field = entity.fields.find((f) => f.name === pkName);
535
+ if (field) {
536
+ const mapped = mapFieldType(field);
537
+ if (mapped.scalarOverride) {
538
+ return mapped.scalarOverride;
539
+ }
540
+ if (mapped.type.kind === "scalar") {
541
+ return mapped.type.scalar;
542
+ }
543
+ }
544
+ }
545
+ logger?.debug(
546
+ `prisma parser: could not resolve primary-key scalar of '${entityName}', defaulting to string`
547
+ );
548
+ return "string";
549
+ }
550
+ function normalRelation(edge, back) {
551
+ const owning = edge.fromFields.length > 0;
552
+ const relation = {
553
+ name: edge.fieldName,
554
+ target: { namespace: "", entity: edge.targetEntity },
555
+ cardinality: edge.isList ? "many" : "one",
556
+ optional: !edge.isRequired,
557
+ owning
558
+ };
559
+ if (owning) {
560
+ relation.fkFields = [...edge.fromFields];
561
+ relation.references = [...edge.toFields];
562
+ }
563
+ if (back) {
564
+ relation.backRelation = back.fieldName;
565
+ }
566
+ const onDelete = mapAction(edge.onDelete);
567
+ if (onDelete) {
568
+ relation.onDelete = onDelete;
569
+ }
570
+ const onUpdate = mapAction(edge.onUpdate);
571
+ if (onUpdate) {
572
+ relation.onUpdate = onUpdate;
573
+ }
574
+ return relation;
575
+ }
576
+ function materialiseM2M(a, b, relationName, entities, logger) {
577
+ const sorted = [a.owner, b.owner].sort(
578
+ (l, r) => l < r ? -1 : l > r ? 1 : 0
579
+ );
580
+ const x = sorted[0] ?? a.owner;
581
+ const y = sorted[1] ?? b.owner;
582
+ const defaultName = `${x}To${y}`;
583
+ const name = relationName !== "" && relationName !== defaultName ? relationName : `${x}${y}`;
584
+ const fkX = `${lcfirst(x)}Id`;
585
+ const fkY = `${lcfirst(y)}Id`;
586
+ const relX = lcfirst(x);
587
+ const relY = lcfirst(y);
588
+ const pkX = entities.get(x)?.primaryKey[0] ?? "id";
589
+ const pkY = entities.get(y)?.primaryKey[0] ?? "id";
590
+ const synthetic = {
591
+ name,
592
+ fields: [
593
+ { name: fkX, scalar: pkScalar(entities, x, logger) },
594
+ { name: fkY, scalar: pkScalar(entities, y, logger) }
595
+ ],
596
+ primaryKey: [fkX, fkY],
597
+ relations: [
598
+ {
599
+ name: relX,
600
+ target: { namespace: "", entity: x },
601
+ cardinality: "one",
602
+ optional: false,
603
+ owning: true,
604
+ fkFields: [fkX],
605
+ references: [pkX],
606
+ onDelete: "cascade"
607
+ },
608
+ {
609
+ name: relY,
610
+ target: { namespace: "", entity: y },
611
+ cardinality: "one",
612
+ optional: false,
613
+ owning: true,
614
+ fkFields: [fkY],
615
+ references: [pkY],
616
+ onDelete: "cascade"
617
+ }
618
+ ]
619
+ };
620
+ const mkRewrite = (edge, backName) => ({
621
+ owner: edge.owner,
622
+ relation: {
623
+ name: edge.edge.fieldName,
624
+ target: { namespace: "", entity: name },
625
+ cardinality: "many",
626
+ optional: false,
627
+ owning: false,
628
+ backRelation: backName
629
+ }
630
+ });
631
+ return {
632
+ synthetic,
633
+ rewrites: [
634
+ mkRewrite(a, a.owner === x ? relX : relY),
635
+ mkRewrite(b, b.owner === x ? relX : relY)
636
+ ]
637
+ };
638
+ }
639
+ function buildRelations(model, logger) {
640
+ const entities = new Map(model.entities.map((e) => [e.name, e]));
641
+ const relations = /* @__PURE__ */ new Map();
642
+ const syntheticEntities = [];
643
+ const push = (owner, relation) => {
644
+ const list = relations.get(owner) ?? [];
645
+ list.push(relation);
646
+ relations.set(owner, list);
647
+ };
648
+ const groups = /* @__PURE__ */ new Map();
649
+ for (const entity of model.entities) {
650
+ for (const edge of entity.relationEdges) {
651
+ const list = groups.get(edge.relationName) ?? [];
652
+ list.push({ owner: entity.name, edge });
653
+ groups.set(edge.relationName, list);
654
+ }
655
+ }
656
+ for (const [relationName, edges] of groups) {
657
+ if (isImplicitM2M(edges)) {
658
+ const [a, b] = edges;
659
+ const { synthetic, rewrites } = materialiseM2M(
660
+ a,
661
+ b,
662
+ relationName,
663
+ entities,
664
+ logger
665
+ );
666
+ syntheticEntities.push(synthetic);
667
+ for (const { owner, relation } of rewrites) {
668
+ push(owner, relation);
669
+ }
670
+ continue;
671
+ }
672
+ for (const { owner, edge } of edges) {
673
+ const back = edges.find((e) => e.edge !== edge)?.edge;
674
+ push(owner, normalRelation(edge, back));
675
+ }
676
+ }
677
+ return { relations, syntheticEntities };
678
+ }
679
+
680
+ // src/map/build.ts
681
+ var INDEX_TYPES = /* @__PURE__ */ new Set([
682
+ "btree",
683
+ "hash",
684
+ "gin",
685
+ "gist",
686
+ "brin",
687
+ "spgist"
688
+ ]);
689
+ function asIndexType(raw) {
690
+ return raw !== void 0 && INDEX_TYPES.has(raw) ? raw : void 0;
691
+ }
692
+ function fillEnum(eb, e) {
693
+ for (const value of e.values) {
694
+ const opts = {};
695
+ if (value.dbName !== void 0) {
696
+ opts.dbName = value.dbName;
697
+ }
698
+ if (value.doc !== void 0) {
699
+ opts.doc = value.doc;
700
+ }
701
+ eb.value(value.name, opts);
702
+ }
703
+ if (e.doc !== void 0) {
704
+ eb.doc(e.doc);
705
+ }
706
+ if (e.dbName !== void 0) {
707
+ eb.dbName(e.dbName);
708
+ }
709
+ }
710
+ function addRelation(eb, rel, namespace) {
711
+ eb.relation(rel.name, (rb) => {
712
+ rb.to(namespace, rel.target.entity);
713
+ if (rel.cardinality === "many") {
714
+ rb.many();
715
+ } else {
716
+ rb.one();
717
+ }
718
+ if (rel.optional) {
719
+ rb.optional();
720
+ }
721
+ if (rel.owning) {
722
+ rb.owning();
723
+ }
724
+ if (rel.backRelation !== void 0) {
725
+ rb.backRelation(rel.backRelation);
726
+ }
727
+ if (rel.fkFields) {
728
+ rb.fkFields(...rel.fkFields);
729
+ }
730
+ if (rel.references) {
731
+ rb.references(...rel.references);
732
+ }
733
+ if (rel.onDelete) {
734
+ rb.onDelete(rel.onDelete);
735
+ }
736
+ if (rel.onUpdate) {
737
+ rb.onUpdate(rel.onUpdate);
738
+ }
739
+ });
740
+ }
741
+ function buildSourceIR(namespace, model, parserVersion, logger) {
742
+ const b = (0, import_ir.createSourceIR)({ namespace, parser: "prisma", parserVersion });
743
+ for (const e of model.enums) {
744
+ b.addEnum(e.name, (eb) => fillEnum(eb, e));
745
+ }
746
+ const { relations, syntheticEntities } = buildRelations(model, logger);
747
+ for (const entity of model.entities) {
748
+ b.addEntity(entity.name, (eb) => {
749
+ for (const field of entity.fields) {
750
+ eb.field(field.name, (fb) => {
751
+ const mapped = mapFieldType(field, logger);
752
+ const scalar = mapped.scalarOverride ?? (mapped.type.kind === "scalar" ? mapped.type.scalar : void 0);
753
+ if (scalar !== void 0) {
754
+ fb.scalar(scalar);
755
+ } else if (mapped.type.kind === "enum") {
756
+ fb.enum(mapped.type.ref);
757
+ } else {
758
+ fb.unknown(
759
+ mapped.type.kind === "unknown" ? mapped.type.hint : void 0
760
+ );
761
+ }
762
+ const isString = scalar === "string";
763
+ const { maxLength, format: nativeFormat } = mapped.constraints;
764
+ if (maxLength !== void 0) {
765
+ fb.maxLength(maxLength);
766
+ }
767
+ const mappedDefault = mapDefault(field.default);
768
+ if (mappedDefault.default) {
769
+ fb.default(mappedDefault.default);
770
+ }
771
+ const format = mappedDefault.format ?? nativeFormat;
772
+ if (format !== void 0) {
773
+ if (isString) {
774
+ fb.format(format);
775
+ } else {
776
+ logger?.debug(
777
+ `prisma parser: dropping format '${format}' on non-string field '${entity.name}.${field.name}'`
778
+ );
779
+ }
780
+ }
781
+ if (field.isList) {
782
+ fb.list();
783
+ }
784
+ if (!field.isRequired) {
785
+ fb.nullable();
786
+ }
787
+ if (field.hasDefaultValue || field.isUpdatedAt) {
788
+ fb.optional();
789
+ }
790
+ if (field.isUnique) {
791
+ fb.unique();
792
+ }
793
+ if (field.doc !== void 0) {
794
+ fb.doc(field.doc);
795
+ }
796
+ });
797
+ }
798
+ if (entity.primaryKey.length > 0) {
799
+ eb.primaryKey(...entity.primaryKey);
800
+ }
801
+ for (const unique of entity.uniques) {
802
+ eb.unique(
803
+ unique.fields,
804
+ unique.name ? { name: unique.name } : void 0
805
+ );
806
+ }
807
+ for (const index of entity.indexes) {
808
+ const opts = {};
809
+ if (index.name) {
810
+ opts.name = index.name;
811
+ }
812
+ const type = asIndexType(index.type);
813
+ if (type) {
814
+ opts.type = type;
815
+ }
816
+ eb.index(index.fields, opts);
817
+ }
818
+ if (entity.doc !== void 0) {
819
+ eb.doc(entity.doc);
820
+ }
821
+ if (entity.dbName !== void 0) {
822
+ eb.dbName(entity.dbName);
823
+ }
824
+ for (const rel of relations.get(entity.name) ?? []) {
825
+ addRelation(eb, rel, namespace);
826
+ }
827
+ });
828
+ }
829
+ for (const synthetic of syntheticEntities) {
830
+ b.addEntity(synthetic.name, (eb) => {
831
+ for (const field of synthetic.fields) {
832
+ eb.field(field.name, (fb) => fb.scalar(field.scalar));
833
+ }
834
+ eb.primaryKey(...synthetic.primaryKey);
835
+ for (const rel of synthetic.relations) {
836
+ addRelation(eb, rel, namespace);
837
+ }
838
+ });
839
+ }
840
+ return b.build();
841
+ }
842
+
843
+ // src/parser.ts
844
+ var prismaParser = (0, import_config.defineParser)({
845
+ name: "prisma",
846
+ optionsSchema: PrismaParserOptions,
847
+ async parse(ctx, options) {
848
+ const input = await resolveInput(ctx.cwd, options, ctx.namespace);
849
+ if (input.mode === 8) {
850
+ throw new PrismaInputError(
851
+ ctx.namespace,
852
+ input.contractPath,
853
+ "the Prisma 8 contract.json mode is not implemented in kurotako v1"
854
+ );
855
+ }
856
+ const { model, prismaVersion } = await readDmmf(input, ctx);
857
+ return buildSourceIR(
858
+ ctx.namespace,
859
+ model,
860
+ `prisma@${prismaVersion}`,
861
+ ctx.logger
862
+ );
863
+ },
864
+ async watchPaths(ctx, options) {
865
+ return [(0, import_node_path3.resolve)(ctx.cwd, options.schema)];
866
+ },
867
+ anchor(rootDir, options) {
868
+ return (0, import_node_path3.dirname)((0, import_node_path3.resolve)(rootDir, options.schema));
869
+ }
870
+ });
871
+ // Annotate the CommonJS export names for ESM import in node:
872
+ 0 && (module.exports = {
873
+ PrismaInputError,
874
+ PrismaParserOptions,
875
+ PrismaPeerMissingError,
876
+ PrismaSchemaError,
877
+ prismaParser
878
+ });
879
+ //# sourceMappingURL=index.cjs.map