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