ormbridge 0.0.0-stage → 0.3.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 (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +459 -2
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +20 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/cliRunner.d.ts +20 -0
  7. package/dist/cliRunner.js +363 -0
  8. package/dist/cliRunner.js.map +1 -0
  9. package/dist/config.d.ts +44 -0
  10. package/dist/config.js +220 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/convert.d.ts +20 -0
  13. package/dist/convert.js +61 -0
  14. package/dist/convert.js.map +1 -0
  15. package/dist/emitters/django.d.ts +4 -0
  16. package/dist/emitters/django.js +501 -0
  17. package/dist/emitters/django.js.map +1 -0
  18. package/dist/emitters/doctrine.d.ts +20 -0
  19. package/dist/emitters/doctrine.js +1134 -0
  20. package/dist/emitters/doctrine.js.map +1 -0
  21. package/dist/emitters/gorm.d.ts +38 -0
  22. package/dist/emitters/gorm.js +1507 -0
  23. package/dist/emitters/gorm.js.map +1 -0
  24. package/dist/emitters/graphene.d.ts +4 -0
  25. package/dist/emitters/graphene.js +431 -0
  26. package/dist/emitters/graphene.js.map +1 -0
  27. package/dist/emitters/laravel.d.ts +33 -0
  28. package/dist/emitters/laravel.js +1695 -0
  29. package/dist/emitters/laravel.js.map +1 -0
  30. package/dist/emitters/prisma.d.ts +21 -0
  31. package/dist/emitters/prisma.js +1058 -0
  32. package/dist/emitters/prisma.js.map +1 -0
  33. package/dist/emitters/typeorm.d.ts +9 -0
  34. package/dist/emitters/typeorm.js +838 -0
  35. package/dist/emitters/typeorm.js.map +1 -0
  36. package/dist/emitters/typescriptInterfaces.d.ts +15 -0
  37. package/dist/emitters/typescriptInterfaces.js +350 -0
  38. package/dist/emitters/typescriptInterfaces.js.map +1 -0
  39. package/dist/formats.d.ts +82 -0
  40. package/dist/formats.js +238 -0
  41. package/dist/formats.js.map +1 -0
  42. package/dist/index.d.ts +37 -0
  43. package/dist/index.js +20 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/io.d.ts +48 -0
  46. package/dist/io.js +343 -0
  47. package/dist/io.js.map +1 -0
  48. package/dist/ir.d.ts +197 -0
  49. package/dist/ir.js +10 -0
  50. package/dist/ir.js.map +1 -0
  51. package/dist/naming.d.ts +11 -0
  52. package/dist/naming.js +88 -0
  53. package/dist/naming.js.map +1 -0
  54. package/dist/parsers/django.d.ts +14 -0
  55. package/dist/parsers/django.js +1161 -0
  56. package/dist/parsers/django.js.map +1 -0
  57. package/dist/parsers/doctrine.d.ts +12 -0
  58. package/dist/parsers/doctrine.js +1984 -0
  59. package/dist/parsers/doctrine.js.map +1 -0
  60. package/dist/parsers/goSyntax.d.ts +105 -0
  61. package/dist/parsers/goSyntax.js +435 -0
  62. package/dist/parsers/goSyntax.js.map +1 -0
  63. package/dist/parsers/gorm.d.ts +16 -0
  64. package/dist/parsers/gorm.js +1857 -0
  65. package/dist/parsers/gorm.js.map +1 -0
  66. package/dist/parsers/laravel.d.ts +17 -0
  67. package/dist/parsers/laravel.js +2824 -0
  68. package/dist/parsers/laravel.js.map +1 -0
  69. package/dist/parsers/phpSyntax.d.ts +124 -0
  70. package/dist/parsers/phpSyntax.js +429 -0
  71. package/dist/parsers/phpSyntax.js.map +1 -0
  72. package/dist/parsers/prisma.d.ts +12 -0
  73. package/dist/parsers/prisma.js +964 -0
  74. package/dist/parsers/prisma.js.map +1 -0
  75. package/dist/parsers/pythonSyntax.d.ts +38 -0
  76. package/dist/parsers/pythonSyntax.js +120 -0
  77. package/dist/parsers/pythonSyntax.js.map +1 -0
  78. package/dist/parsers/typeorm.d.ts +12 -0
  79. package/dist/parsers/typeorm.js +1614 -0
  80. package/dist/parsers/typeorm.js.map +1 -0
  81. package/dist/parsers/typescriptSyntax.d.ts +57 -0
  82. package/dist/parsers/typescriptSyntax.js +200 -0
  83. package/dist/parsers/typescriptSyntax.js.map +1 -0
  84. package/dist/prismaOnlyConstructs.d.ts +7 -0
  85. package/dist/prismaOnlyConstructs.js +76 -0
  86. package/dist/prismaOnlyConstructs.js.map +1 -0
  87. package/dist/result.d.ts +20 -0
  88. package/dist/result.js +18 -0
  89. package/dist/result.js.map +1 -0
  90. package/dist/transforms.d.ts +13 -0
  91. package/dist/transforms.js +142 -0
  92. package/dist/transforms.js.map +1 -0
  93. package/package.json +83 -4
@@ -0,0 +1,964 @@
1
+ import { err, ok } from '../result.js';
2
+ /** Postgres range types that Prisma can only express as Unsupported("..."), mapped to the IR range subtype. */
3
+ const RANGE_TYPES = {
4
+ int4range: 'int',
5
+ int8range: 'bigInt',
6
+ numrange: 'decimal',
7
+ daterange: 'date',
8
+ tstzrange: 'dateTime',
9
+ tsrange: 'dateTime',
10
+ };
11
+ /** Native types whose single argument is a character length rather than a bit or byte count. */
12
+ const LENGTH_TYPES = new Set([
13
+ 'VarChar',
14
+ 'Char',
15
+ 'NVarChar',
16
+ 'NChar',
17
+ 'String',
18
+ ]);
19
+ const TEXT_TYPES = new Set([
20
+ 'Text',
21
+ 'TinyText',
22
+ 'MediumText',
23
+ 'LongText',
24
+ 'NText',
25
+ ]);
26
+ const SCALAR_TYPES = new Set([
27
+ 'String',
28
+ 'Int',
29
+ 'BigInt',
30
+ 'Float',
31
+ 'Decimal',
32
+ 'Boolean',
33
+ 'DateTime',
34
+ 'Json',
35
+ 'Bytes',
36
+ ]);
37
+ const ON_DELETE_VALUES = {
38
+ Cascade: 'cascade',
39
+ SetNull: 'setNull',
40
+ Restrict: 'restrict',
41
+ NoAction: 'noAction',
42
+ SetDefault: 'setDefault',
43
+ };
44
+ /** Parses a Prisma schema (hand-written tokenizer, no Prisma engine required) into the shared IR. */
45
+ export function parsePrisma(sources, options) {
46
+ const warnings = [];
47
+ const combinedText = sources
48
+ .map((source) => source.text)
49
+ .join('\n');
50
+ const strippedText = stripComments(combinedText);
51
+ const { models, enums, provider } = readBlocks(strippedText, warnings);
52
+ if (models.length === 0) {
53
+ const checkedPaths = sources
54
+ .map((source) => source.path)
55
+ .join(', ');
56
+ return err('NO_MODELS_FOUND', `No "model" blocks were found in: ${checkedPaths}.`);
57
+ }
58
+ const enumNames = new Set(enums.map((enumDefinition) => enumDefinition.name));
59
+ const modelByName = new Map(models.map((model) => [model.name, model]));
60
+ const manyToManyOwners = findManyToMany(models, modelByName);
61
+ const irModels = models.map((model) => buildModel(model, modelByName, enumNames, enums, manyToManyOwners, provider, options, warnings));
62
+ return ok({ models: irModels, enums, warnings });
63
+ }
64
+ // ---------------------------------------------------------------------------
65
+ // Tokenizing
66
+ // ---------------------------------------------------------------------------
67
+ function stripComments(text) {
68
+ let result = '';
69
+ let inString = false;
70
+ for (let index = 0; index < text.length; index += 1) {
71
+ const character = text[index] ?? '';
72
+ const next = text[index + 1] ?? '';
73
+ if (inString) {
74
+ result += character;
75
+ if (character === '\\') {
76
+ result += next;
77
+ index += 1;
78
+ }
79
+ else if (character === '"') {
80
+ inString = false;
81
+ }
82
+ continue;
83
+ }
84
+ if (character === '"') {
85
+ inString = true;
86
+ result += character;
87
+ }
88
+ else if (character === '/' && next === '/') {
89
+ while (index < text.length && text[index] !== '\n') {
90
+ index += 1;
91
+ }
92
+ result += '\n';
93
+ }
94
+ else {
95
+ result += character;
96
+ }
97
+ }
98
+ return result;
99
+ }
100
+ function readBlocks(text, warnings) {
101
+ const models = [];
102
+ const enums = [];
103
+ let provider;
104
+ const lines = text.split('\n');
105
+ const blockStart = /^\s*(model|enum|type|view|generator|datasource)\s+(\w+)\s*\{\s*(.*)$/;
106
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
107
+ const match = blockStart.exec(lines[lineIndex] ?? '');
108
+ if (match === null) {
109
+ continue;
110
+ }
111
+ const kind = match[1] ?? '';
112
+ const name = match[2] ?? '';
113
+ const body = [];
114
+ const remainder = (match[3] ?? '').trim();
115
+ let closed = false;
116
+ if (remainder.endsWith('}')) {
117
+ const inner = remainder.slice(0, -1).trim();
118
+ if (inner.length > 0) {
119
+ body.push(inner);
120
+ }
121
+ closed = true;
122
+ }
123
+ else if (remainder.length > 0) {
124
+ body.push(remainder);
125
+ }
126
+ while (!closed && lineIndex + 1 < lines.length) {
127
+ lineIndex += 1;
128
+ const bodyLine = (lines[lineIndex] ?? '').trim();
129
+ if (bodyLine === '}') {
130
+ closed = true;
131
+ break;
132
+ }
133
+ if (bodyLine.length > 0) {
134
+ body.push(bodyLine);
135
+ }
136
+ }
137
+ if (!closed) {
138
+ warnings.push(`The ${kind} block "${name}" is missing its closing brace; parsing continued with what was found.`);
139
+ }
140
+ if (kind === 'datasource') {
141
+ for (const bodyLine of body) {
142
+ const providerMatch = /^provider\s*=\s*"([^"]*)"/.exec(bodyLine);
143
+ if (providerMatch !== null) {
144
+ provider = providerMatch[1];
145
+ }
146
+ }
147
+ }
148
+ else if (kind === 'model' || kind === 'view') {
149
+ models.push(parseModelBlock(name, body, kind === 'view', warnings));
150
+ }
151
+ else if (kind === 'enum') {
152
+ enums.push(parseEnumBlock(name, body, warnings));
153
+ }
154
+ else if (kind === 'type') {
155
+ warnings.push(`The composite type "${name}" is not supported and was skipped; fields that use it are skipped too.`);
156
+ }
157
+ }
158
+ return { models, enums, provider };
159
+ }
160
+ function parseEnumBlock(name, body, warnings) {
161
+ const values = [];
162
+ let dbName;
163
+ let schemaName;
164
+ for (const line of body) {
165
+ if (line.startsWith('@@')) {
166
+ for (const attribute of parseAttributes(line)) {
167
+ if (attribute.name === 'map') {
168
+ dbName = firstStringArgument(attribute);
169
+ }
170
+ else if (attribute.name === 'schema') {
171
+ schemaName = firstStringArgument(attribute);
172
+ }
173
+ else {
174
+ warnings.push(`enum ${name}: @@${attribute.name} is not supported and was ignored.`);
175
+ }
176
+ }
177
+ continue;
178
+ }
179
+ const memberMatch = /^(\w+)\s*(.*)$/.exec(line);
180
+ if (memberMatch === null) {
181
+ continue;
182
+ }
183
+ const memberName = memberMatch[1] ?? '';
184
+ const mapAttribute = parseAttributes(memberMatch[2] ?? '').find((attribute) => attribute.name === 'map');
185
+ const mappedValue = mapAttribute === undefined
186
+ ? undefined
187
+ : firstStringArgument(mapAttribute);
188
+ values.push({ name: memberName, dbValue: mappedValue ?? memberName });
189
+ }
190
+ return {
191
+ name,
192
+ values,
193
+ ...(dbName === undefined ? {} : { dbName }),
194
+ ...(schemaName === undefined ? {} : { schema: schemaName }),
195
+ };
196
+ }
197
+ function parseModelBlock(name, body, isView, warnings) {
198
+ const fields = [];
199
+ const blockAttributes = [];
200
+ for (const line of body) {
201
+ if (line.startsWith('@@')) {
202
+ blockAttributes.push(...parseAttributes(line));
203
+ continue;
204
+ }
205
+ const fieldMatch = /^(\w+)\s+(Unsupported\(\s*"(?:[^"\\]|\\.)*"\s*\)|\w+)(\[\])?(\?)?\s*(.*)$/.exec(line);
206
+ if (fieldMatch === null) {
207
+ warnings.push(`${isView ? 'View' : 'Model'} ${name}: the line "${line}" could not be understood and was skipped.`);
208
+ continue;
209
+ }
210
+ const rawType = fieldMatch[2] ?? '';
211
+ const isUnsupported = rawType.startsWith('Unsupported(');
212
+ const unsupportedText = rawType.slice(rawType.indexOf('(') + 1, rawType.lastIndexOf(')'));
213
+ fields.push({
214
+ name: fieldMatch[1] ?? '',
215
+ typeName: isUnsupported ? 'Unsupported' : rawType,
216
+ isList: fieldMatch[3] !== undefined,
217
+ isOptional: fieldMatch[4] !== undefined,
218
+ attributes: parseAttributes(fieldMatch[5] ?? ''),
219
+ ...(isUnsupported ? { unsupportedType: unquote(unsupportedText) } : {}),
220
+ });
221
+ }
222
+ return { name, fields, blockAttributes, isView };
223
+ }
224
+ function findClosingParenthesis(text, openIndex) {
225
+ let depth = 0;
226
+ let inString = false;
227
+ for (let index = openIndex; index < text.length; index += 1) {
228
+ const character = text[index] ?? '';
229
+ if (inString) {
230
+ if (character === '\\') {
231
+ index += 1;
232
+ }
233
+ else if (character === '"') {
234
+ inString = false;
235
+ }
236
+ continue;
237
+ }
238
+ if (character === '"') {
239
+ inString = true;
240
+ }
241
+ else if (character === '(') {
242
+ depth += 1;
243
+ }
244
+ else if (character === ')') {
245
+ depth -= 1;
246
+ if (depth === 0) {
247
+ return index;
248
+ }
249
+ }
250
+ }
251
+ return text.length;
252
+ }
253
+ function parseAttributes(text) {
254
+ const attributes = [];
255
+ let index = 0;
256
+ while (index < text.length) {
257
+ const atIndex = text.indexOf('@', index);
258
+ if (atIndex === -1) {
259
+ break;
260
+ }
261
+ let cursor = atIndex + 1;
262
+ if (text[cursor] === '@') {
263
+ cursor += 1;
264
+ }
265
+ const nameStart = cursor;
266
+ while (cursor < text.length && /[\w.]/.test(text[cursor] ?? '')) {
267
+ cursor += 1;
268
+ }
269
+ const name = text.slice(nameStart, cursor);
270
+ let args;
271
+ if (text[cursor] === '(') {
272
+ const closeIndex = findClosingParenthesis(text, cursor);
273
+ args = text.slice(cursor + 1, closeIndex);
274
+ cursor = closeIndex + 1;
275
+ }
276
+ attributes.push({ name, args });
277
+ index = Math.max(cursor, atIndex + 1);
278
+ }
279
+ return attributes;
280
+ }
281
+ function splitTopLevel(text) {
282
+ const parts = [];
283
+ let depth = 0;
284
+ let inString = false;
285
+ let current = '';
286
+ for (let index = 0; index < text.length; index += 1) {
287
+ const character = text[index] ?? '';
288
+ if (inString) {
289
+ current += character;
290
+ if (character === '\\') {
291
+ current += text[index + 1] ?? '';
292
+ index += 1;
293
+ }
294
+ else if (character === '"') {
295
+ inString = false;
296
+ }
297
+ continue;
298
+ }
299
+ if (character === '"') {
300
+ inString = true;
301
+ current += character;
302
+ }
303
+ else if (character === '(' || character === '[' || character === '{') {
304
+ depth += 1;
305
+ current += character;
306
+ }
307
+ else if (character === ')' || character === ']' || character === '}') {
308
+ depth -= 1;
309
+ current += character;
310
+ }
311
+ else if (character === ',' && depth === 0) {
312
+ parts.push(current.trim());
313
+ current = '';
314
+ }
315
+ else {
316
+ current += character;
317
+ }
318
+ }
319
+ if (current.trim().length > 0) {
320
+ parts.push(current.trim());
321
+ }
322
+ return parts;
323
+ }
324
+ function unquote(value) {
325
+ const trimmed = value.trim();
326
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') && trimmed.length >= 2) {
327
+ try {
328
+ const parsed = JSON.parse(trimmed);
329
+ if (typeof parsed === 'string') {
330
+ return parsed;
331
+ }
332
+ }
333
+ catch {
334
+ return trimmed.slice(1, -1);
335
+ }
336
+ return trimmed.slice(1, -1);
337
+ }
338
+ return trimmed;
339
+ }
340
+ /** Returns the argument supplied under a keyword (e.g. fields: [a, b]) as raw text. */
341
+ function namedArgument(attribute, key) {
342
+ if (attribute === undefined || attribute.args === undefined) {
343
+ return undefined;
344
+ }
345
+ for (const part of splitTopLevel(attribute.args)) {
346
+ const match = /^(\w+)\s*:\s*([\s\S]*)$/.exec(part);
347
+ if (match !== null && match[1] === key) {
348
+ return (match[2] ?? '').trim();
349
+ }
350
+ }
351
+ return undefined;
352
+ }
353
+ /** Returns the first argument when it is positional (not key: value). */
354
+ function positionalArgument(attribute) {
355
+ if (attribute === undefined || attribute.args === undefined) {
356
+ return undefined;
357
+ }
358
+ const first = splitTopLevel(attribute.args)[0];
359
+ if (first === undefined || /^\w+\s*:/.test(first)) {
360
+ return undefined;
361
+ }
362
+ return first;
363
+ }
364
+ function firstStringArgument(attribute) {
365
+ const positional = positionalArgument(attribute);
366
+ return positional === undefined ? undefined : unquote(positional);
367
+ }
368
+ function parseNameList(rawList) {
369
+ if (rawList === undefined) {
370
+ return [];
371
+ }
372
+ const inner = rawList.trim().replace(/^\[/, '').replace(/\]$/, '');
373
+ return splitTopLevel(inner).map((item) => item.replace(/\(.*\)$/, '').trim());
374
+ }
375
+ function attributeNamed(attributes, name) {
376
+ return attributes.find((attribute) => attribute.name === name);
377
+ }
378
+ /** Name given to a relation through @relation("Name") or @relation(name: "Name"). */
379
+ function relationLabel(field) {
380
+ const relationAttribute = attributeNamed(field.attributes, 'relation');
381
+ if (relationAttribute === undefined) {
382
+ return undefined;
383
+ }
384
+ const named = namedArgument(relationAttribute, 'name');
385
+ if (named !== undefined) {
386
+ return unquote(named);
387
+ }
388
+ const positional = positionalArgument(relationAttribute);
389
+ return positional === undefined ? undefined : unquote(positional);
390
+ }
391
+ function ownsForeignKey(field) {
392
+ const relationAttribute = attributeNamed(field.attributes, 'relation');
393
+ return namedArgument(relationAttribute, 'fields') !== undefined;
394
+ }
395
+ // ---------------------------------------------------------------------------
396
+ // Relations
397
+ // ---------------------------------------------------------------------------
398
+ /**
399
+ * Finds implicit many-to-many pairs (a list field on both models, neither owning a foreign key).
400
+ * Returns, per owning model, the list fields that should become ManyToManyField definitions.
401
+ */
402
+ function findManyToMany(models, modelByName) {
403
+ const owners = new Map();
404
+ for (const model of models) {
405
+ for (const field of model.fields) {
406
+ if (!field.isList ||
407
+ !modelByName.has(field.typeName) ||
408
+ ownsForeignKey(field)) {
409
+ continue;
410
+ }
411
+ const target = modelByName.get(field.typeName);
412
+ const counterpart = target?.fields.find((candidate) => candidate !== field &&
413
+ candidate.isList &&
414
+ candidate.typeName === model.name &&
415
+ !ownsForeignKey(candidate) &&
416
+ relationLabel(candidate) === relationLabel(field));
417
+ if (counterpart === undefined || target === undefined) {
418
+ continue;
419
+ }
420
+ const isOwner = `${model.name}.${field.name}` < `${target.name}.${counterpart.name}`;
421
+ if (isOwner) {
422
+ const ownerFields = owners.get(model.name) ?? new Map();
423
+ ownerFields.set(field.name, counterpart);
424
+ owners.set(model.name, ownerFields);
425
+ }
426
+ }
427
+ }
428
+ return owners;
429
+ }
430
+ function findInverseField(owner, field, target) {
431
+ const label = relationLabel(field);
432
+ return target.fields.find((candidate) => candidate.typeName === owner.name &&
433
+ !ownsForeignKey(candidate) &&
434
+ relationLabel(candidate) === label);
435
+ }
436
+ /** Reads `[a, b(sort: Desc, length: 10)]` into field names plus per-field options. */
437
+ function parseIndexEntries(location, rawList, warnings) {
438
+ if (rawList === undefined) {
439
+ return [];
440
+ }
441
+ const inner = rawList.trim().replace(/^\[/, '').replace(/\]$/, '');
442
+ const entries = [];
443
+ for (const item of splitTopLevel(inner)) {
444
+ const match = /^(\w+)\s*(?:\(([\s\S]*)\))?$/.exec(item);
445
+ if (match === null) {
446
+ warnings.push(`${location}: the index entry "${item}" could not be understood and was skipped.`);
447
+ continue;
448
+ }
449
+ const entryName = match[1] ?? '';
450
+ const options = {};
451
+ for (const part of splitTopLevel(match[2] ?? '')) {
452
+ const optionMatch = /^(\w+)\s*:\s*([\s\S]*)$/.exec(part);
453
+ const key = optionMatch?.[1] ?? '';
454
+ const value = (optionMatch?.[2] ?? '').trim();
455
+ if (key === 'sort' && (value === 'Asc' || value === 'Desc')) {
456
+ options.sort = value === 'Asc' ? 'asc' : 'desc';
457
+ }
458
+ else if (key === 'length' && /^\d+$/.test(value)) {
459
+ options.length = Number(value);
460
+ }
461
+ else if (key === 'ops' && value.length > 0) {
462
+ options.ops = value;
463
+ }
464
+ else {
465
+ warnings.push(`${location}: the option "${part}" on index entry "${entryName}" is not supported and was dropped.`);
466
+ }
467
+ }
468
+ entries.push({ name: entryName, options });
469
+ }
470
+ return entries;
471
+ }
472
+ function booleanArgument(attribute, key) {
473
+ const raw = namedArgument(attribute, key);
474
+ if (raw === 'true') {
475
+ return true;
476
+ }
477
+ return raw === 'false' ? false : undefined;
478
+ }
479
+ function buildModel(model, modelByName, enumNames, enums, manyToManyOwners, provider, options, warnings) {
480
+ const consumedScalars = new Map();
481
+ const relations = [];
482
+ const compositeForeignKeys = [];
483
+ for (const field of model.fields) {
484
+ if (!modelByName.has(field.typeName)) {
485
+ continue;
486
+ }
487
+ const target = modelByName.get(field.typeName);
488
+ if (target === undefined) {
489
+ continue;
490
+ }
491
+ if (field.isList) {
492
+ const counterpart = manyToManyOwners
493
+ .get(model.name)
494
+ ?.get(field.name);
495
+ if (counterpart !== undefined) {
496
+ relations.push({
497
+ name: field.name,
498
+ kind: 'manyToMany',
499
+ targetModel: target.name,
500
+ columnName: '',
501
+ isNullable: false,
502
+ onDelete: 'cascade',
503
+ relatedName: counterpart.name,
504
+ });
505
+ warnings.push(`${model.name}.${field.name}: Prisma's implicit many-to-many table ("_${model.name}To${target.name}" with columns A and B) ` +
506
+ `differs from the join table Django will create; migrate data or define an explicit through model.`);
507
+ }
508
+ continue;
509
+ }
510
+ if (!ownsForeignKey(field)) {
511
+ continue;
512
+ }
513
+ const keyColumns = parseNameList(namedArgument(attributeNamed(field.attributes, 'relation'), 'fields'));
514
+ if (keyColumns.length > 1) {
515
+ const compositeKey = buildCompositeForeignKey(model, field, target, keyColumns, warnings);
516
+ if (compositeKey !== undefined) {
517
+ compositeForeignKeys.push(compositeKey);
518
+ }
519
+ continue;
520
+ }
521
+ const relation = buildForeignKey(model, field, target, consumedScalars, warnings);
522
+ if (relation !== undefined) {
523
+ relations.push(relation);
524
+ }
525
+ }
526
+ const fields = [];
527
+ for (const field of model.fields) {
528
+ if (modelByName.has(field.typeName) || consumedScalars.has(field.name)) {
529
+ continue;
530
+ }
531
+ const scalarField = buildScalarField(model, field, enumNames, enums, provider, warnings);
532
+ if (scalarField !== undefined) {
533
+ fields.push(scalarField);
534
+ }
535
+ }
536
+ const resolveName = (name) => consumedScalars.get(name) ?? name;
537
+ const indexes = [];
538
+ let compositePrimaryKey;
539
+ let primaryKeyName;
540
+ let isIgnored = false;
541
+ let schemaName;
542
+ for (const attribute of model.blockAttributes) {
543
+ const location = `${model.name} @@${attribute.name}`;
544
+ const rawFields = positionalArgument(attribute) ?? namedArgument(attribute, 'fields');
545
+ if (attribute.name === 'id') {
546
+ const entries = parseIndexEntries(location, rawFields, warnings);
547
+ compositePrimaryKey = entries.map((entry) => resolveName(entry.name));
548
+ if (entries.some((entry) => Object.keys(entry.options).length > 0)) {
549
+ warnings.push(`${location}: sort and length options on primary key fields are not kept.`);
550
+ }
551
+ const mapArgument = namedArgument(attribute, 'map');
552
+ if (mapArgument !== undefined) {
553
+ primaryKeyName = unquote(mapArgument);
554
+ }
555
+ if (namedArgument(attribute, 'name') !== undefined) {
556
+ warnings.push(`${location}: the client-side key name (name: ...) is not kept; Prisma Client will use the default compound key name.`);
557
+ }
558
+ }
559
+ else if (attribute.name === 'unique' ||
560
+ attribute.name === 'index' ||
561
+ attribute.name === 'fulltext') {
562
+ const entries = parseIndexEntries(location, rawFields, warnings).map((entry) => ({
563
+ ...entry,
564
+ name: resolveName(entry.name),
565
+ }));
566
+ if (entries.length === 0) {
567
+ continue;
568
+ }
569
+ indexes.push(buildIndex(location, attribute, entries, warnings));
570
+ }
571
+ else if (attribute.name === 'ignore') {
572
+ isIgnored = true;
573
+ }
574
+ else if (attribute.name === 'schema') {
575
+ schemaName = firstStringArgument(attribute);
576
+ }
577
+ else if (attribute.name !== 'map') {
578
+ warnings.push(`${location} is not supported and was ignored.`);
579
+ }
580
+ }
581
+ for (const field of model.fields) {
582
+ const idAttribute = attributeNamed(field.attributes, 'id');
583
+ const idMap = namedArgument(idAttribute, 'map');
584
+ if (idMap !== undefined) {
585
+ primaryKeyName = unquote(idMap);
586
+ }
587
+ }
588
+ const mapAttribute = attributeNamed(model.blockAttributes, 'map');
589
+ const tableName = (mapAttribute === undefined
590
+ ? undefined
591
+ : firstStringArgument(mapAttribute)) ?? model.name;
592
+ return {
593
+ name: model.name,
594
+ tableName,
595
+ appLabel: options.appLabel,
596
+ fields,
597
+ relations,
598
+ indexes,
599
+ ...(compositePrimaryKey === undefined ? {} : { compositePrimaryKey }),
600
+ ...(model.isView ? { isView: true } : {}),
601
+ ...(isIgnored ? { isIgnored: true } : {}),
602
+ ...(schemaName === undefined ? {} : { schema: schemaName }),
603
+ ...(primaryKeyName === undefined ? {} : { primaryKeyName }),
604
+ ...(compositeForeignKeys.length === 0 ? {} : { compositeForeignKeys }),
605
+ };
606
+ }
607
+ function buildIndex(location, attribute, entries, warnings) {
608
+ const mapArgument = namedArgument(attribute, 'map');
609
+ const typeArgument = namedArgument(attribute, 'type');
610
+ const fieldOptions = {};
611
+ for (const entry of entries) {
612
+ if (Object.keys(entry.options).length > 0) {
613
+ fieldOptions[entry.name] = entry.options;
614
+ }
615
+ }
616
+ const clustered = booleanArgument(attribute, 'clustered');
617
+ if (attribute.name === 'unique' &&
618
+ namedArgument(attribute, 'name') !== undefined) {
619
+ warnings.push(`${location}: the client-side key name (name: ...) is not kept; Prisma Client will use the default compound key name.`);
620
+ }
621
+ if (namedArgument(attribute, 'where') !== undefined) {
622
+ warnings.push(`${location}: the partial index condition (where: ...) is not supported and was dropped.`);
623
+ }
624
+ return {
625
+ fields: entries.map((entry) => entry.name),
626
+ isUnique: attribute.name === 'unique',
627
+ ...(mapArgument === undefined ? {} : { name: unquote(mapArgument) }),
628
+ ...(attribute.name === 'fulltext' ? { kind: 'fulltext' } : {}),
629
+ ...(typeArgument === undefined ? {} : { method: typeArgument }),
630
+ ...(clustered === undefined ? {} : { clustered }),
631
+ ...(Object.keys(fieldOptions).length === 0 ? {} : { fieldOptions }),
632
+ };
633
+ }
634
+ function scalarColumnName(field) {
635
+ const mapAttribute = attributeNamed(field.attributes, 'map');
636
+ return ((mapAttribute === undefined
637
+ ? undefined
638
+ : firstStringArgument(mapAttribute)) ?? field.name);
639
+ }
640
+ function referentialAction(relationAttribute, key) {
641
+ const raw = namedArgument(relationAttribute, key);
642
+ return raw === undefined ? undefined : ON_DELETE_VALUES[raw];
643
+ }
644
+ /** True when the listed scalar fields are exactly a unique constraint or the primary key. */
645
+ function isUniqueSet(model, names) {
646
+ const wanted = [...names].sort().join();
647
+ const sameSet = (candidate) => [...candidate].sort().join() === wanted;
648
+ if (names.length === 1) {
649
+ const only = model.fields.find((candidate) => candidate.name === names[0]);
650
+ if (only !== undefined &&
651
+ (attributeNamed(only.attributes, 'id') !== undefined ||
652
+ attributeNamed(only.attributes, 'unique') !== undefined)) {
653
+ return true;
654
+ }
655
+ }
656
+ return model.blockAttributes.some((attribute) => (attribute.name === 'unique' || attribute.name === 'id') &&
657
+ sameSet(parseNameList(positionalArgument(attribute) ?? namedArgument(attribute, 'fields'))));
658
+ }
659
+ function buildCompositeForeignKey(model, field, target, keyColumns, warnings) {
660
+ const relationAttribute = attributeNamed(field.attributes, 'relation');
661
+ const references = parseNameList(namedArgument(relationAttribute, 'references'));
662
+ const location = `${model.name}.${field.name}`;
663
+ if (references.length !== keyColumns.length) {
664
+ warnings.push(`${location}: the composite foreign key lists ${keyColumns.length} fields but ${references.length} references; the relation was skipped.`);
665
+ return undefined;
666
+ }
667
+ const missingLocal = keyColumns.find((name) => !model.fields.some((candidate) => candidate.name === name));
668
+ if (missingLocal !== undefined) {
669
+ warnings.push(`${location}: the foreign key field "${missingLocal}" does not exist on ${model.name}; the relation was skipped.`);
670
+ return undefined;
671
+ }
672
+ const missingTarget = references.find((name) => !target.fields.some((candidate) => candidate.name === name));
673
+ if (missingTarget !== undefined) {
674
+ warnings.push(`${location}: the referenced field "${missingTarget}" does not exist on ${target.name}; the relation was skipped.`);
675
+ return undefined;
676
+ }
677
+ const onDelete = referentialAction(relationAttribute, 'onDelete') ??
678
+ (field.isOptional ? 'setNull' : 'restrict');
679
+ const onUpdate = referentialAction(relationAttribute, 'onUpdate');
680
+ const constraint = namedArgument(relationAttribute, 'map');
681
+ const inverse = findInverseField(model, field, target);
682
+ return {
683
+ name: field.name,
684
+ targetModel: target.name,
685
+ fields: keyColumns,
686
+ references,
687
+ kind: isUniqueSet(model, keyColumns) ? 'oneToOne' : 'foreignKey',
688
+ isNullable: field.isOptional,
689
+ onDelete,
690
+ ...(onUpdate === undefined ? {} : { onUpdate }),
691
+ ...(inverse === undefined ? {} : { relatedName: inverse.name }),
692
+ ...(constraint === undefined
693
+ ? {}
694
+ : { constraintName: unquote(constraint) }),
695
+ };
696
+ }
697
+ function buildForeignKey(model, field, target, consumedScalars, warnings) {
698
+ const relationAttribute = attributeNamed(field.attributes, 'relation');
699
+ const foreignKeyNames = parseNameList(namedArgument(relationAttribute, 'fields'));
700
+ const referenceNames = parseNameList(namedArgument(relationAttribute, 'references'));
701
+ const location = `${model.name}.${field.name}`;
702
+ const scalarName = foreignKeyNames[0] ?? '';
703
+ const scalarField = model.fields.find((candidate) => candidate.name === scalarName);
704
+ if (scalarField === undefined) {
705
+ warnings.push(`${location}: the foreign key field "${scalarName}" does not exist on ${model.name}; the relation was skipped.`);
706
+ return undefined;
707
+ }
708
+ consumedScalars.set(scalarName, field.name);
709
+ const isPrimaryKey = attributeNamed(scalarField.attributes, 'id') !== undefined;
710
+ const isUnique = isUniqueSet(model, [scalarName]);
711
+ const onDelete = referentialAction(relationAttribute, 'onDelete') ??
712
+ (field.isOptional ? 'setNull' : 'restrict');
713
+ const onUpdate = referentialAction(relationAttribute, 'onUpdate');
714
+ const constraint = namedArgument(relationAttribute, 'map');
715
+ const inverse = findInverseField(model, field, target);
716
+ const referencedField = referenceNames[0];
717
+ const targetKeyField = target.fields.find((candidate) => attributeNamed(candidate.attributes, 'id') !== undefined);
718
+ const needsToField = referencedField !== undefined &&
719
+ (targetKeyField === undefined || referencedField !== targetKeyField.name);
720
+ return {
721
+ name: field.name,
722
+ kind: isPrimaryKey || isUnique ? 'oneToOne' : 'foreignKey',
723
+ targetModel: target.name,
724
+ columnName: scalarColumnName(scalarField),
725
+ isNullable: field.isOptional,
726
+ onDelete,
727
+ ...(onUpdate === undefined ? {} : { onUpdate }),
728
+ ...(inverse === undefined ? {} : { relatedName: inverse.name }),
729
+ ...(needsToField && referencedField !== undefined
730
+ ? { toField: referencedField }
731
+ : {}),
732
+ ...(isPrimaryKey ? { isPrimaryKey: true } : {}),
733
+ ...(constraint === undefined
734
+ ? {}
735
+ : { constraintName: unquote(constraint) }),
736
+ };
737
+ }
738
+ function numericArgument(value) {
739
+ return value !== undefined && /^\d+$/.test(value) ? Number(value) : undefined;
740
+ }
741
+ /** Prisma's default Decimal precision and scale when no @db.Decimal(p, s) is given. */
742
+ function defaultDecimal(provider) {
743
+ return provider === 'sqlserver' ? [32, 16] : [65, 30];
744
+ }
745
+ function buildScalarField(model, field, enumNames, enums, provider, warnings) {
746
+ const location = `${model.name}.${field.name}`;
747
+ const isEnum = enumNames.has(field.typeName);
748
+ const isUnsupported = field.typeName === 'Unsupported';
749
+ if (!isUnsupported && !isEnum && !SCALAR_TYPES.has(field.typeName)) {
750
+ warnings.push(`${location}: the type "${field.typeName}" is not a known scalar, enum or model; the field was skipped.`);
751
+ return undefined;
752
+ }
753
+ const nativeAttribute = field.attributes.find((attribute) => attribute.name.startsWith('db.'));
754
+ const nativeName = nativeAttribute === undefined ? '' : nativeAttribute.name.slice(3);
755
+ const nativeArguments = nativeAttribute?.args === undefined
756
+ ? []
757
+ : splitTopLevel(nativeAttribute.args);
758
+ let type;
759
+ let maxLength;
760
+ let maxDigits;
761
+ let decimalPlaces;
762
+ let rangeOf;
763
+ let unsupportedType;
764
+ if (isUnsupported) {
765
+ const rawType = field.unsupportedType ?? '';
766
+ const rangeSubtype = field.isList
767
+ ? undefined
768
+ : RANGE_TYPES[rawType.toLowerCase()];
769
+ if (rangeSubtype !== undefined) {
770
+ type = 'range';
771
+ rangeOf = rangeSubtype;
772
+ }
773
+ else {
774
+ type = 'unsupported';
775
+ unsupportedType = rawType;
776
+ }
777
+ }
778
+ else if (isEnum) {
779
+ type = 'string';
780
+ }
781
+ else {
782
+ switch (field.typeName) {
783
+ case 'String':
784
+ if (TEXT_TYPES.has(nativeName)) {
785
+ type = 'text';
786
+ }
787
+ else if (nativeName === 'Uuid' || nativeName === 'UniqueIdentifier') {
788
+ type = 'uuid';
789
+ }
790
+ else if (nativeName === 'Inet') {
791
+ type = 'ipAddress';
792
+ }
793
+ else {
794
+ type = 'string';
795
+ if (LENGTH_TYPES.has(nativeName)) {
796
+ maxLength = numericArgument(nativeArguments[0]);
797
+ }
798
+ }
799
+ break;
800
+ case 'Int':
801
+ type = 'int';
802
+ break;
803
+ case 'BigInt':
804
+ type = 'bigInt';
805
+ break;
806
+ case 'Float':
807
+ type = 'float';
808
+ break;
809
+ case 'Decimal': {
810
+ type = 'decimal';
811
+ if (nativeName !== 'Money' && nativeName !== 'SmallMoney') {
812
+ const [defaultDigits, defaultPlaces] = defaultDecimal(provider);
813
+ maxDigits = numericArgument(nativeArguments[0]) ?? defaultDigits;
814
+ decimalPlaces = numericArgument(nativeArguments[1]) ?? defaultPlaces;
815
+ }
816
+ break;
817
+ }
818
+ case 'Boolean':
819
+ type = 'boolean';
820
+ break;
821
+ case 'DateTime':
822
+ type =
823
+ nativeName === 'Date'
824
+ ? 'date'
825
+ : nativeName.startsWith('Time') &&
826
+ !nativeName.startsWith('Timestamp')
827
+ ? 'time'
828
+ : 'dateTime';
829
+ break;
830
+ case 'Json':
831
+ type = 'json';
832
+ break;
833
+ case 'Bytes':
834
+ type = 'bytes';
835
+ break;
836
+ default:
837
+ type = 'string';
838
+ }
839
+ }
840
+ const irField = {
841
+ name: field.name,
842
+ columnName: scalarColumnName(field),
843
+ type,
844
+ isPrimaryKey: attributeNamed(field.attributes, 'id') !== undefined,
845
+ isUnique: attributeNamed(field.attributes, 'unique') !== undefined,
846
+ isNullable: field.isOptional,
847
+ isAutoUpdated: attributeNamed(field.attributes, 'updatedAt') !== undefined,
848
+ };
849
+ if (field.isList) {
850
+ irField.arrayDepth = 1;
851
+ }
852
+ if (isEnum) {
853
+ irField.enumName = field.typeName;
854
+ }
855
+ if (rangeOf !== undefined) {
856
+ irField.rangeOf = rangeOf;
857
+ }
858
+ if (unsupportedType !== undefined) {
859
+ irField.unsupportedType = unsupportedType;
860
+ }
861
+ if (maxLength !== undefined) {
862
+ irField.maxLength = maxLength;
863
+ }
864
+ if (maxDigits !== undefined) {
865
+ irField.maxDigits = maxDigits;
866
+ }
867
+ if (decimalPlaces !== undefined) {
868
+ irField.decimalPlaces = decimalPlaces;
869
+ }
870
+ if (nativeAttribute !== undefined) {
871
+ const nativeType = {
872
+ name: nativeName,
873
+ args: nativeArguments,
874
+ };
875
+ irField.nativeType = nativeType;
876
+ }
877
+ if (attributeNamed(field.attributes, 'ignore') !== undefined) {
878
+ irField.isIgnored = true;
879
+ }
880
+ const uniqueAttribute = attributeNamed(field.attributes, 'unique');
881
+ const uniqueName = namedArgument(uniqueAttribute, 'map');
882
+ if (uniqueName !== undefined) {
883
+ irField.uniqueName = unquote(uniqueName);
884
+ }
885
+ for (const option of ['sort', 'length', 'clustered']) {
886
+ if (namedArgument(uniqueAttribute, option) !== undefined) {
887
+ warnings.push(`${location}: the @unique option "${option}" is not supported and was dropped.`);
888
+ }
889
+ }
890
+ const defaultAttribute = attributeNamed(field.attributes, 'default');
891
+ if (defaultAttribute !== undefined) {
892
+ const parsedDefault = parseDefault(location, defaultAttribute, field.isList, isEnum
893
+ ? enums.find((candidate) => candidate.name === field.typeName)
894
+ : undefined, warnings);
895
+ if (parsedDefault !== undefined) {
896
+ irField.default = parsedDefault;
897
+ }
898
+ }
899
+ return irField;
900
+ }
901
+ function parseDefault(location, attribute, isList, enumDefinition, warnings) {
902
+ const raw = positionalArgument(attribute) ?? namedArgument(attribute, 'value');
903
+ if (raw === undefined) {
904
+ return undefined;
905
+ }
906
+ const value = raw.trim();
907
+ if (isList) {
908
+ if (/^\[\s*\]$/.test(value)) {
909
+ return { kind: 'literal', value: '[]' };
910
+ }
911
+ warnings.push(`${location}: the list default @default(${value}) could not be converted and was dropped.`);
912
+ return undefined;
913
+ }
914
+ if (/^autoincrement\(\s*\)$/.test(value)) {
915
+ return { kind: 'autoIncrement' };
916
+ }
917
+ if (/^now\(\s*\)$/.test(value)) {
918
+ return { kind: 'now' };
919
+ }
920
+ const uuidMatch = /^uuid\(\s*(\d*)\s*\)$/.exec(value);
921
+ if (uuidMatch !== null) {
922
+ const version = numericArgument(uuidMatch[1]);
923
+ return version === undefined ? { kind: 'uuid' } : { kind: 'uuid', version };
924
+ }
925
+ const generatorMatch = /^(cuid|nanoid|ulid)\(\s*(\d*)\s*\)$/.exec(value);
926
+ if (generatorMatch !== null) {
927
+ const generatorArgs = generatorMatch[2] ?? '';
928
+ return {
929
+ kind: 'clientGenerated',
930
+ generator: generatorMatch[1] ?? '',
931
+ ...(generatorArgs === '' ? {} : { args: generatorArgs }),
932
+ };
933
+ }
934
+ const dbGeneratedMatch = /^dbgenerated\(\s*("(?:[^"\\]|\\.)*")\s*\)$/.exec(value);
935
+ if (dbGeneratedMatch !== null) {
936
+ return {
937
+ kind: 'dbExpression',
938
+ expression: unquote(dbGeneratedMatch[1] ?? '""'),
939
+ };
940
+ }
941
+ if (/^(auto|sequence)\(.*\)$/.test(value)) {
942
+ return { kind: 'dbExpression', expression: value, isFunction: true };
943
+ }
944
+ if (/^dbgenerated\(.*\)$/.test(value)) {
945
+ warnings.push(`${location}: @default(${value}) has no expression to carry over and was dropped.`);
946
+ return undefined;
947
+ }
948
+ if (value === 'true' || value === 'false') {
949
+ return { kind: 'literal', value: value === 'true' };
950
+ }
951
+ if (/^-?\d+(\.\d+)?$/.test(value)) {
952
+ return { kind: 'literal', value: Number(value) };
953
+ }
954
+ if (value.startsWith('"')) {
955
+ return { kind: 'literal', value: unquote(value) };
956
+ }
957
+ if (enumDefinition !== undefined &&
958
+ enumDefinition.values.some((member) => member.name === value)) {
959
+ return { kind: 'enumValue', value };
960
+ }
961
+ warnings.push(`${location}: @default(${value}) could not be converted and was dropped.`);
962
+ return undefined;
963
+ }
964
+ //# sourceMappingURL=prisma.js.map