@mateusseiboth/ember-orm 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/LICENSE +21 -0
- package/README.md +125 -0
- package/dist/cli/bin.cjs +3265 -0
- package/dist/cli/bin.cjs.map +1 -0
- package/dist/cli/bin.d.cts +1 -0
- package/dist/cli/bin.d.ts +1 -0
- package/dist/cli/bin.js +3241 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/client/index.cjs +2434 -0
- package/dist/client/index.cjs.map +1 -0
- package/dist/client/index.d.cts +2 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +2396 -0
- package/dist/client/index.js.map +1 -0
- package/dist/editor.cjs +1113 -0
- package/dist/editor.cjs.map +1 -0
- package/dist/editor.d.cts +22 -0
- package/dist/editor.d.ts +22 -0
- package/dist/editor.js +1077 -0
- package/dist/editor.js.map +1 -0
- package/dist/index-0lWi8TMM.d.ts +72 -0
- package/dist/index-BSXZjDUd.d.ts +459 -0
- package/dist/index-CKqkQhZx.d.cts +459 -0
- package/dist/index-CMeqhmVc.d.cts +72 -0
- package/dist/index-D0xIdtCl.d.cts +145 -0
- package/dist/index-D0xIdtCl.d.ts +145 -0
- package/dist/index.cjs +5013 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +288 -0
- package/dist/index.d.ts +288 -0
- package/dist/index.js +4935 -0
- package/dist/index.js.map +1 -0
- package/package.json +75 -0
package/dist/editor.js
ADDED
|
@@ -0,0 +1,1077 @@
|
|
|
1
|
+
// src/schema/index.ts
|
|
2
|
+
import { readFileSync, existsSync } from "fs";
|
|
3
|
+
import { resolve, dirname } from "path";
|
|
4
|
+
|
|
5
|
+
// src/errors/index.ts
|
|
6
|
+
var EmberError = class extends Error {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = new.target.name;
|
|
10
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var SchemaParseError = class extends EmberError {
|
|
14
|
+
constructor(message, line, column, file) {
|
|
15
|
+
super(
|
|
16
|
+
`${message} (at ${file ? `${file}:` : ""}${line}:${column})`
|
|
17
|
+
);
|
|
18
|
+
this.line = line;
|
|
19
|
+
this.column = column;
|
|
20
|
+
this.file = file;
|
|
21
|
+
}
|
|
22
|
+
line;
|
|
23
|
+
column;
|
|
24
|
+
file;
|
|
25
|
+
};
|
|
26
|
+
var SchemaValidationError = class extends EmberError {
|
|
27
|
+
constructor(message, details = []) {
|
|
28
|
+
super(
|
|
29
|
+
details.length > 0 ? `${message}
|
|
30
|
+
- ${details.join("\n - ")}` : message
|
|
31
|
+
);
|
|
32
|
+
this.details = details;
|
|
33
|
+
}
|
|
34
|
+
details;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// src/schema/lexer.ts
|
|
38
|
+
var SINGLE_CHAR_TOKENS = {
|
|
39
|
+
"{": "lbrace",
|
|
40
|
+
"}": "rbrace",
|
|
41
|
+
"(": "lparen",
|
|
42
|
+
")": "rparen",
|
|
43
|
+
"[": "lbracket",
|
|
44
|
+
"]": "rbracket",
|
|
45
|
+
"=": "equals",
|
|
46
|
+
",": "comma",
|
|
47
|
+
":": "colon",
|
|
48
|
+
"?": "question",
|
|
49
|
+
".": "dot"
|
|
50
|
+
};
|
|
51
|
+
var IDENT_START = /[A-Za-z_]/;
|
|
52
|
+
var IDENT_PART = /[A-Za-z0-9_]/;
|
|
53
|
+
var Lexer = class {
|
|
54
|
+
constructor(source, file) {
|
|
55
|
+
this.source = source;
|
|
56
|
+
this.file = file;
|
|
57
|
+
}
|
|
58
|
+
source;
|
|
59
|
+
file;
|
|
60
|
+
pos = 0;
|
|
61
|
+
line = 1;
|
|
62
|
+
column = 1;
|
|
63
|
+
tokenize() {
|
|
64
|
+
const tokens = [];
|
|
65
|
+
let token = this.next();
|
|
66
|
+
while (token.type !== "eof") {
|
|
67
|
+
tokens.push(token);
|
|
68
|
+
token = this.next();
|
|
69
|
+
}
|
|
70
|
+
tokens.push(token);
|
|
71
|
+
return tokens;
|
|
72
|
+
}
|
|
73
|
+
next() {
|
|
74
|
+
this.skipWhitespaceAndComments();
|
|
75
|
+
if (this.pos >= this.source.length) {
|
|
76
|
+
return this.make("eof", "");
|
|
77
|
+
}
|
|
78
|
+
const startLine = this.line;
|
|
79
|
+
const startColumn = this.column;
|
|
80
|
+
const ch = this.source[this.pos];
|
|
81
|
+
if (ch === "/" && this.peek(1) === "/" && this.peek(2) === "/") {
|
|
82
|
+
return this.readDocComment(startLine, startColumn);
|
|
83
|
+
}
|
|
84
|
+
if (ch === '"') {
|
|
85
|
+
return this.readString(startLine, startColumn);
|
|
86
|
+
}
|
|
87
|
+
if (ch === "@") {
|
|
88
|
+
this.advance();
|
|
89
|
+
if (this.source[this.pos] === "@") {
|
|
90
|
+
this.advance();
|
|
91
|
+
return { type: "double_at", value: "@@", line: startLine, column: startColumn };
|
|
92
|
+
}
|
|
93
|
+
return { type: "at", value: "@", line: startLine, column: startColumn };
|
|
94
|
+
}
|
|
95
|
+
const single = SINGLE_CHAR_TOKENS[ch];
|
|
96
|
+
if (single) {
|
|
97
|
+
this.advance();
|
|
98
|
+
return { type: single, value: ch, line: startLine, column: startColumn };
|
|
99
|
+
}
|
|
100
|
+
if (ch === "-" || /[0-9]/.test(ch)) {
|
|
101
|
+
return this.readNumber(startLine, startColumn);
|
|
102
|
+
}
|
|
103
|
+
if (IDENT_START.test(ch)) {
|
|
104
|
+
return this.readIdentifier(startLine, startColumn);
|
|
105
|
+
}
|
|
106
|
+
throw new SchemaParseError(
|
|
107
|
+
`Unexpected character '${ch}'`,
|
|
108
|
+
startLine,
|
|
109
|
+
startColumn,
|
|
110
|
+
this.file
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
readDocComment(line, column) {
|
|
114
|
+
this.advance();
|
|
115
|
+
this.advance();
|
|
116
|
+
this.advance();
|
|
117
|
+
let value = "";
|
|
118
|
+
while (this.pos < this.source.length && this.source[this.pos] !== "\n") {
|
|
119
|
+
value += this.source[this.pos];
|
|
120
|
+
this.advance();
|
|
121
|
+
}
|
|
122
|
+
return { type: "doc_comment", value: value.trim(), line, column };
|
|
123
|
+
}
|
|
124
|
+
readString(line, column) {
|
|
125
|
+
this.advance();
|
|
126
|
+
let value = "";
|
|
127
|
+
while (this.pos < this.source.length && this.source[this.pos] !== '"') {
|
|
128
|
+
const c = this.source[this.pos];
|
|
129
|
+
if (c === "\\") {
|
|
130
|
+
this.advance();
|
|
131
|
+
const escaped = this.source[this.pos];
|
|
132
|
+
if (escaped === void 0) break;
|
|
133
|
+
value += escapeChar(escaped);
|
|
134
|
+
this.advance();
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (c === "\n") {
|
|
138
|
+
throw new SchemaParseError(
|
|
139
|
+
"Unterminated string literal",
|
|
140
|
+
line,
|
|
141
|
+
column,
|
|
142
|
+
this.file
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
value += c;
|
|
146
|
+
this.advance();
|
|
147
|
+
}
|
|
148
|
+
if (this.source[this.pos] !== '"') {
|
|
149
|
+
throw new SchemaParseError(
|
|
150
|
+
"Unterminated string literal",
|
|
151
|
+
line,
|
|
152
|
+
column,
|
|
153
|
+
this.file
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
this.advance();
|
|
157
|
+
return { type: "string", value, line, column };
|
|
158
|
+
}
|
|
159
|
+
readNumber(line, column) {
|
|
160
|
+
let value = "";
|
|
161
|
+
if (this.source[this.pos] === "-") {
|
|
162
|
+
value += "-";
|
|
163
|
+
this.advance();
|
|
164
|
+
}
|
|
165
|
+
while (this.pos < this.source.length && /[0-9.]/.test(this.source[this.pos])) {
|
|
166
|
+
value += this.source[this.pos];
|
|
167
|
+
this.advance();
|
|
168
|
+
}
|
|
169
|
+
return { type: "number", value, line, column };
|
|
170
|
+
}
|
|
171
|
+
readIdentifier(line, column) {
|
|
172
|
+
let value = "";
|
|
173
|
+
while (this.pos < this.source.length && IDENT_PART.test(this.source[this.pos])) {
|
|
174
|
+
value += this.source[this.pos];
|
|
175
|
+
this.advance();
|
|
176
|
+
}
|
|
177
|
+
return { type: "identifier", value, line, column };
|
|
178
|
+
}
|
|
179
|
+
skipWhitespaceAndComments() {
|
|
180
|
+
while (this.pos < this.source.length) {
|
|
181
|
+
const ch = this.source[this.pos];
|
|
182
|
+
if (ch === " " || ch === " " || ch === "\r" || ch === "\n") {
|
|
183
|
+
this.advance();
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (ch === "/" && this.peek(1) === "/" && this.peek(2) !== "/") {
|
|
187
|
+
while (this.pos < this.source.length && this.source[this.pos] !== "\n") {
|
|
188
|
+
this.advance();
|
|
189
|
+
}
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (ch === "/" && this.peek(1) === "*") {
|
|
193
|
+
this.advance();
|
|
194
|
+
this.advance();
|
|
195
|
+
while (this.pos < this.source.length && !(this.source[this.pos] === "*" && this.peek(1) === "/")) {
|
|
196
|
+
this.advance();
|
|
197
|
+
}
|
|
198
|
+
this.advance();
|
|
199
|
+
this.advance();
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
peek(offset) {
|
|
206
|
+
return this.source[this.pos + offset];
|
|
207
|
+
}
|
|
208
|
+
advance() {
|
|
209
|
+
if (this.source[this.pos] === "\n") {
|
|
210
|
+
this.line++;
|
|
211
|
+
this.column = 1;
|
|
212
|
+
} else {
|
|
213
|
+
this.column++;
|
|
214
|
+
}
|
|
215
|
+
this.pos++;
|
|
216
|
+
}
|
|
217
|
+
make(type, value) {
|
|
218
|
+
return { type, value, line: this.line, column: this.column };
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
function escapeChar(c) {
|
|
222
|
+
switch (c) {
|
|
223
|
+
case "n":
|
|
224
|
+
return "\n";
|
|
225
|
+
case "t":
|
|
226
|
+
return " ";
|
|
227
|
+
case "r":
|
|
228
|
+
return "\r";
|
|
229
|
+
case '"':
|
|
230
|
+
return '"';
|
|
231
|
+
case "\\":
|
|
232
|
+
return "\\";
|
|
233
|
+
default:
|
|
234
|
+
return c;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// src/schema/parser.ts
|
|
239
|
+
var Parser = class {
|
|
240
|
+
constructor(source, file) {
|
|
241
|
+
this.file = file;
|
|
242
|
+
this.tokens = new Lexer(source, file).tokenize();
|
|
243
|
+
}
|
|
244
|
+
file;
|
|
245
|
+
tokens;
|
|
246
|
+
pos = 0;
|
|
247
|
+
parse() {
|
|
248
|
+
const doc = { generators: [], models: [], enums: [] };
|
|
249
|
+
let pendingDoc = [];
|
|
250
|
+
while (!this.isEof()) {
|
|
251
|
+
const tok = this.peek();
|
|
252
|
+
if (tok.type === "doc_comment") {
|
|
253
|
+
pendingDoc.push(tok.value);
|
|
254
|
+
this.advance();
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (tok.type !== "identifier") {
|
|
258
|
+
throw this.error(`Unexpected token '${tok.value}'`, tok);
|
|
259
|
+
}
|
|
260
|
+
const documentation = pendingDoc.length ? pendingDoc.join("\n") : void 0;
|
|
261
|
+
pendingDoc = [];
|
|
262
|
+
switch (tok.value) {
|
|
263
|
+
case "datasource":
|
|
264
|
+
doc.datasource = this.parseDatasource();
|
|
265
|
+
break;
|
|
266
|
+
case "generator":
|
|
267
|
+
doc.generators.push(this.parseGenerator());
|
|
268
|
+
break;
|
|
269
|
+
case "model":
|
|
270
|
+
doc.models.push(this.parseModel(documentation));
|
|
271
|
+
break;
|
|
272
|
+
case "enum":
|
|
273
|
+
doc.enums.push(this.parseEnum(documentation));
|
|
274
|
+
break;
|
|
275
|
+
default:
|
|
276
|
+
throw this.error(`Unknown top-level keyword '${tok.value}'`, tok);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
resolveKinds(doc);
|
|
280
|
+
return doc;
|
|
281
|
+
}
|
|
282
|
+
// ---- Top-level blocks -------------------------------------------------
|
|
283
|
+
parseDatasource() {
|
|
284
|
+
this.expectKeyword("datasource");
|
|
285
|
+
const name = this.expect("identifier").value;
|
|
286
|
+
this.expect("lbrace");
|
|
287
|
+
const assignments = this.parseAssignments();
|
|
288
|
+
this.expect("rbrace");
|
|
289
|
+
const provider = literalString(assignments["provider"]);
|
|
290
|
+
const urlAssign = assignments["url"];
|
|
291
|
+
let url = { kind: "literal", value: "" };
|
|
292
|
+
if (urlAssign) {
|
|
293
|
+
if (urlAssign.kind === "function" && urlAssign.name === "env") {
|
|
294
|
+
url = { kind: "env", value: literalString(urlAssign.args[0]) };
|
|
295
|
+
} else if (urlAssign.kind === "string") {
|
|
296
|
+
url = { kind: "literal", value: urlAssign.value };
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return { name, provider: provider ?? "firebird", url };
|
|
300
|
+
}
|
|
301
|
+
parseGenerator() {
|
|
302
|
+
this.expectKeyword("generator");
|
|
303
|
+
const name = this.expect("identifier").value;
|
|
304
|
+
this.expect("lbrace");
|
|
305
|
+
const assignments = this.parseAssignments();
|
|
306
|
+
this.expect("rbrace");
|
|
307
|
+
const config = {};
|
|
308
|
+
for (const [key, value] of Object.entries(assignments)) {
|
|
309
|
+
if (value.kind === "string") config[key] = value.value;
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
name,
|
|
313
|
+
provider: literalString(assignments["provider"]) ?? "ember-client-js",
|
|
314
|
+
output: assignments["output"] ? literalString(assignments["output"]) : void 0,
|
|
315
|
+
config
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
parseAssignments() {
|
|
319
|
+
const out = {};
|
|
320
|
+
while (!this.check("rbrace") && !this.isEof()) {
|
|
321
|
+
if (this.check("doc_comment")) {
|
|
322
|
+
this.advance();
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
const key = this.expect("identifier").value;
|
|
326
|
+
this.expect("equals");
|
|
327
|
+
out[key] = this.parseValue();
|
|
328
|
+
}
|
|
329
|
+
return out;
|
|
330
|
+
}
|
|
331
|
+
parseModel(documentation) {
|
|
332
|
+
this.expectKeyword("model");
|
|
333
|
+
const name = this.expect("identifier").value;
|
|
334
|
+
this.expect("lbrace");
|
|
335
|
+
const model = {
|
|
336
|
+
name,
|
|
337
|
+
fields: [],
|
|
338
|
+
primaryKey: [],
|
|
339
|
+
uniqueIndexes: [],
|
|
340
|
+
indexes: [],
|
|
341
|
+
documentation
|
|
342
|
+
};
|
|
343
|
+
let pendingDoc = [];
|
|
344
|
+
while (!this.check("rbrace") && !this.isEof()) {
|
|
345
|
+
const tok = this.peek();
|
|
346
|
+
if (tok.type === "doc_comment") {
|
|
347
|
+
pendingDoc.push(tok.value);
|
|
348
|
+
this.advance();
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
if (tok.type === "double_at") {
|
|
352
|
+
this.parseBlockAttribute(model);
|
|
353
|
+
pendingDoc = [];
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
const field = this.parseField(
|
|
357
|
+
pendingDoc.length ? pendingDoc.join("\n") : void 0
|
|
358
|
+
);
|
|
359
|
+
pendingDoc = [];
|
|
360
|
+
model.fields.push(field);
|
|
361
|
+
if (field.isId && !model.primaryKey.includes(field.name)) {
|
|
362
|
+
model.primaryKey.push(field.name);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
this.expect("rbrace");
|
|
366
|
+
return model;
|
|
367
|
+
}
|
|
368
|
+
parseField(documentation) {
|
|
369
|
+
const name = this.expect("identifier").value;
|
|
370
|
+
const typeName = this.expect("identifier").value;
|
|
371
|
+
let isList = false;
|
|
372
|
+
let isRequired = true;
|
|
373
|
+
while (this.check("lbracket") || this.check("question")) {
|
|
374
|
+
if (this.check("lbracket")) {
|
|
375
|
+
this.advance();
|
|
376
|
+
this.expect("rbracket");
|
|
377
|
+
isList = true;
|
|
378
|
+
} else {
|
|
379
|
+
this.advance();
|
|
380
|
+
isRequired = false;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const field = {
|
|
384
|
+
name,
|
|
385
|
+
type: typeName,
|
|
386
|
+
kind: "scalar",
|
|
387
|
+
// resolved later
|
|
388
|
+
isList,
|
|
389
|
+
isRequired,
|
|
390
|
+
isId: false,
|
|
391
|
+
isUnique: false,
|
|
392
|
+
isUpdatedAt: false,
|
|
393
|
+
documentation
|
|
394
|
+
};
|
|
395
|
+
while (this.check("at")) {
|
|
396
|
+
const attr = this.parseAttribute();
|
|
397
|
+
this.applyFieldAttribute(field, attr);
|
|
398
|
+
}
|
|
399
|
+
return field;
|
|
400
|
+
}
|
|
401
|
+
applyFieldAttribute(field, attr) {
|
|
402
|
+
if (attr.name.startsWith("db.")) {
|
|
403
|
+
field.nativeType = toNativeType(attr);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
switch (attr.name) {
|
|
407
|
+
case "id":
|
|
408
|
+
field.isId = true;
|
|
409
|
+
break;
|
|
410
|
+
case "unique":
|
|
411
|
+
field.isUnique = true;
|
|
412
|
+
break;
|
|
413
|
+
case "updatedAt":
|
|
414
|
+
field.isUpdatedAt = true;
|
|
415
|
+
break;
|
|
416
|
+
case "default":
|
|
417
|
+
field.default = toDefaultValue(attr.args[0]);
|
|
418
|
+
break;
|
|
419
|
+
case "map":
|
|
420
|
+
field.dbName = literalString(attr.args[0]);
|
|
421
|
+
break;
|
|
422
|
+
case "relation":
|
|
423
|
+
field.relation = toRelationInfo(attr.args);
|
|
424
|
+
break;
|
|
425
|
+
default:
|
|
426
|
+
throw this.error(`Unknown field attribute '@${attr.name}'`, attr);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
parseBlockAttribute(model) {
|
|
430
|
+
const start = this.expect("double_at");
|
|
431
|
+
const name = this.expect("identifier").value;
|
|
432
|
+
const args = this.check("lparen") ? this.parseArgList() : [];
|
|
433
|
+
switch (name) {
|
|
434
|
+
case "id":
|
|
435
|
+
model.primaryKey = fieldNameList(args);
|
|
436
|
+
break;
|
|
437
|
+
case "unique":
|
|
438
|
+
model.uniqueIndexes.push({
|
|
439
|
+
fields: fieldNameList(args),
|
|
440
|
+
name: namedArg(args, "map")
|
|
441
|
+
});
|
|
442
|
+
break;
|
|
443
|
+
case "index":
|
|
444
|
+
model.indexes.push({
|
|
445
|
+
fields: fieldNameList(args),
|
|
446
|
+
name: namedArg(args, "map"),
|
|
447
|
+
unique: false
|
|
448
|
+
});
|
|
449
|
+
break;
|
|
450
|
+
case "map":
|
|
451
|
+
model.dbName = literalString(args[0]);
|
|
452
|
+
break;
|
|
453
|
+
default:
|
|
454
|
+
throw this.error(`Unknown block attribute '@@${name}'`, start);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
parseEnum(documentation) {
|
|
458
|
+
this.expectKeyword("enum");
|
|
459
|
+
const name = this.expect("identifier").value;
|
|
460
|
+
this.expect("lbrace");
|
|
461
|
+
const node = { name, values: [], documentation };
|
|
462
|
+
while (!this.check("rbrace") && !this.isEof()) {
|
|
463
|
+
if (this.check("doc_comment")) {
|
|
464
|
+
this.advance();
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
if (this.check("double_at")) {
|
|
468
|
+
this.advance();
|
|
469
|
+
const attrName = this.expect("identifier").value;
|
|
470
|
+
const args = this.check("lparen") ? this.parseArgList() : [];
|
|
471
|
+
if (attrName === "map") node.dbName = literalString(args[0]);
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
const valueName = this.expect("identifier").value;
|
|
475
|
+
let dbName;
|
|
476
|
+
while (this.check("at")) {
|
|
477
|
+
const attr = this.parseAttribute();
|
|
478
|
+
if (attr.name === "map") dbName = literalString(attr.args[0]);
|
|
479
|
+
}
|
|
480
|
+
node.values.push({ name: valueName, dbName });
|
|
481
|
+
}
|
|
482
|
+
this.expect("rbrace");
|
|
483
|
+
return node;
|
|
484
|
+
}
|
|
485
|
+
// ---- Attributes & values ---------------------------------------------
|
|
486
|
+
parseAttribute() {
|
|
487
|
+
const at = this.expect("at");
|
|
488
|
+
let name = this.expect("identifier").value;
|
|
489
|
+
if (this.check("dot")) {
|
|
490
|
+
this.advance();
|
|
491
|
+
name += "." + this.expect("identifier").value;
|
|
492
|
+
}
|
|
493
|
+
const args = this.check("lparen") ? this.parseArgList() : [];
|
|
494
|
+
return { name, args, line: at.line, column: at.column };
|
|
495
|
+
}
|
|
496
|
+
parseArgList() {
|
|
497
|
+
this.expect("lparen");
|
|
498
|
+
const args = [];
|
|
499
|
+
while (!this.check("rparen") && !this.isEof()) {
|
|
500
|
+
args.push(this.parseArg());
|
|
501
|
+
if (this.check("comma")) this.advance();
|
|
502
|
+
}
|
|
503
|
+
this.expect("rparen");
|
|
504
|
+
return args;
|
|
505
|
+
}
|
|
506
|
+
/** Handles both positional values and `name: value` named arguments. */
|
|
507
|
+
parseArg() {
|
|
508
|
+
if (this.check("identifier") && this.peek(1)?.type === "colon") {
|
|
509
|
+
const key = this.expect("identifier").value;
|
|
510
|
+
this.expect("colon");
|
|
511
|
+
const value = this.parseValue();
|
|
512
|
+
return { kind: "function", name: `__named:${key}`, args: [value] };
|
|
513
|
+
}
|
|
514
|
+
return this.parseValue();
|
|
515
|
+
}
|
|
516
|
+
parseValue() {
|
|
517
|
+
const tok = this.peek();
|
|
518
|
+
switch (tok.type) {
|
|
519
|
+
case "string":
|
|
520
|
+
this.advance();
|
|
521
|
+
return { kind: "string", value: tok.value };
|
|
522
|
+
case "number":
|
|
523
|
+
this.advance();
|
|
524
|
+
return { kind: "number", value: Number(tok.value) };
|
|
525
|
+
case "lbracket": {
|
|
526
|
+
this.advance();
|
|
527
|
+
const items = [];
|
|
528
|
+
while (!this.check("rbracket") && !this.isEof()) {
|
|
529
|
+
items.push(this.parseValue());
|
|
530
|
+
if (this.check("comma")) this.advance();
|
|
531
|
+
}
|
|
532
|
+
this.expect("rbracket");
|
|
533
|
+
return { kind: "array", items };
|
|
534
|
+
}
|
|
535
|
+
case "identifier": {
|
|
536
|
+
this.advance();
|
|
537
|
+
if (tok.value === "true" || tok.value === "false") {
|
|
538
|
+
return { kind: "boolean", value: tok.value === "true" };
|
|
539
|
+
}
|
|
540
|
+
if (this.check("lparen")) {
|
|
541
|
+
const args = this.parseArgList();
|
|
542
|
+
return { kind: "function", name: tok.value, args };
|
|
543
|
+
}
|
|
544
|
+
return { kind: "ref", value: tok.value };
|
|
545
|
+
}
|
|
546
|
+
default:
|
|
547
|
+
throw this.error(`Unexpected value token '${tok.value}'`, tok);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// ---- Token helpers ----------------------------------------------------
|
|
551
|
+
peek(offset = 0) {
|
|
552
|
+
return this.tokens[this.pos + offset] ?? this.tokens[this.tokens.length - 1];
|
|
553
|
+
}
|
|
554
|
+
advance() {
|
|
555
|
+
const tok = this.tokens[this.pos];
|
|
556
|
+
if (this.pos < this.tokens.length - 1) this.pos++;
|
|
557
|
+
return tok;
|
|
558
|
+
}
|
|
559
|
+
check(type) {
|
|
560
|
+
return this.peek().type === type;
|
|
561
|
+
}
|
|
562
|
+
isEof() {
|
|
563
|
+
return this.peek().type === "eof";
|
|
564
|
+
}
|
|
565
|
+
expect(type) {
|
|
566
|
+
const tok = this.peek();
|
|
567
|
+
if (tok.type !== type) {
|
|
568
|
+
throw this.error(`Expected ${type} but found '${tok.value}' (${tok.type})`, tok);
|
|
569
|
+
}
|
|
570
|
+
return this.advance();
|
|
571
|
+
}
|
|
572
|
+
expectKeyword(keyword) {
|
|
573
|
+
const tok = this.peek();
|
|
574
|
+
if (tok.type !== "identifier" || tok.value !== keyword) {
|
|
575
|
+
throw this.error(`Expected keyword '${keyword}'`, tok);
|
|
576
|
+
}
|
|
577
|
+
return this.advance();
|
|
578
|
+
}
|
|
579
|
+
error(message, at) {
|
|
580
|
+
return new SchemaParseError(message, at.line, at.column, this.file);
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
function literalString(v) {
|
|
584
|
+
if (!v) return "";
|
|
585
|
+
if (v.kind === "string") return v.value;
|
|
586
|
+
if (v.kind === "ref") return v.value;
|
|
587
|
+
return String("value" in v ? v.value : "");
|
|
588
|
+
}
|
|
589
|
+
function toNativeType(attr) {
|
|
590
|
+
const name = attr.name.slice("db.".length);
|
|
591
|
+
const args = attr.args.filter((a) => a.kind === "number").map((a) => a.value);
|
|
592
|
+
return { name, args };
|
|
593
|
+
}
|
|
594
|
+
function toDefaultValue(v) {
|
|
595
|
+
if (!v) return {};
|
|
596
|
+
switch (v.kind) {
|
|
597
|
+
case "function":
|
|
598
|
+
return { function: { name: v.name, args: v.args } };
|
|
599
|
+
case "string":
|
|
600
|
+
return { literal: v.value };
|
|
601
|
+
case "number":
|
|
602
|
+
return { literal: v.value };
|
|
603
|
+
case "boolean":
|
|
604
|
+
return { literal: v.value };
|
|
605
|
+
case "ref":
|
|
606
|
+
return { literal: v.value };
|
|
607
|
+
default:
|
|
608
|
+
return {};
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
function toRelationInfo(args) {
|
|
612
|
+
const info = {};
|
|
613
|
+
for (const arg of args) {
|
|
614
|
+
if (arg.kind === "string") {
|
|
615
|
+
info.name = arg.value;
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
const named = asNamed(arg);
|
|
619
|
+
if (!named) continue;
|
|
620
|
+
const [key, value] = named;
|
|
621
|
+
if (key === "name" && value.kind === "string") info.name = value.value;
|
|
622
|
+
if (key === "fields") info.fields = refArray(value);
|
|
623
|
+
if (key === "references") info.references = refArray(value);
|
|
624
|
+
if (key === "onDelete") info.onDelete = refName(value);
|
|
625
|
+
if (key === "onUpdate") info.onUpdate = refName(value);
|
|
626
|
+
}
|
|
627
|
+
return info;
|
|
628
|
+
}
|
|
629
|
+
function asNamed(arg) {
|
|
630
|
+
if (arg.kind === "function" && arg.name.startsWith("__named:")) {
|
|
631
|
+
return [arg.name.slice("__named:".length), arg.args[0]];
|
|
632
|
+
}
|
|
633
|
+
return void 0;
|
|
634
|
+
}
|
|
635
|
+
function namedArg(args, key) {
|
|
636
|
+
for (const arg of args) {
|
|
637
|
+
const named = asNamed(arg);
|
|
638
|
+
if (named && named[0] === key && named[1].kind === "string") {
|
|
639
|
+
return named[1].value;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return void 0;
|
|
643
|
+
}
|
|
644
|
+
function refArray(v) {
|
|
645
|
+
if (v.kind === "array") {
|
|
646
|
+
return v.items.map((i) => refName(i)).filter((s) => !!s);
|
|
647
|
+
}
|
|
648
|
+
const single = refName(v);
|
|
649
|
+
return single ? [single] : [];
|
|
650
|
+
}
|
|
651
|
+
function refName(v) {
|
|
652
|
+
if (v.kind === "ref") return v.value;
|
|
653
|
+
if (v.kind === "string") return v.value;
|
|
654
|
+
return void 0;
|
|
655
|
+
}
|
|
656
|
+
function fieldNameList(args) {
|
|
657
|
+
for (const arg of args) {
|
|
658
|
+
if (arg.kind === "array") return refArray(arg);
|
|
659
|
+
const named = asNamed(arg);
|
|
660
|
+
if (named && named[0] === "fields") return refArray(named[1]);
|
|
661
|
+
}
|
|
662
|
+
return args.map((a) => refName(a)).filter((s) => !!s);
|
|
663
|
+
}
|
|
664
|
+
function resolveKinds(doc) {
|
|
665
|
+
const modelNames = new Set(doc.models.map((m) => m.name));
|
|
666
|
+
const enumNames = new Set(doc.enums.map((e) => e.name));
|
|
667
|
+
for (const model of doc.models) {
|
|
668
|
+
for (const field of model.fields) {
|
|
669
|
+
if (modelNames.has(field.type)) field.kind = "object";
|
|
670
|
+
else if (enumNames.has(field.type)) field.kind = "enum";
|
|
671
|
+
else field.kind = "scalar";
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// src/ast/index.ts
|
|
677
|
+
var SCALAR_TYPES = [
|
|
678
|
+
"String",
|
|
679
|
+
"Boolean",
|
|
680
|
+
"Int",
|
|
681
|
+
"BigInt",
|
|
682
|
+
"Float",
|
|
683
|
+
"Decimal",
|
|
684
|
+
"DateTime",
|
|
685
|
+
"Bytes",
|
|
686
|
+
"Json"
|
|
687
|
+
];
|
|
688
|
+
function findModel(schema, name) {
|
|
689
|
+
return schema.models.find((m) => m.name === name);
|
|
690
|
+
}
|
|
691
|
+
function idFields(model) {
|
|
692
|
+
if (model.primaryKey.length > 0) {
|
|
693
|
+
return model.primaryKey.map((n) => model.fields.find((f) => f.name === n)).filter((f) => !!f);
|
|
694
|
+
}
|
|
695
|
+
return model.fields.filter((f) => f.isId);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// src/schema/validator.ts
|
|
699
|
+
function validateSchema(doc) {
|
|
700
|
+
const errors = [];
|
|
701
|
+
const scalar = new Set(SCALAR_TYPES);
|
|
702
|
+
const enumNames = new Set(doc.enums.map((e) => e.name));
|
|
703
|
+
const modelNames = new Set(doc.models.map((m) => m.name));
|
|
704
|
+
const duplicateModels = findDuplicates(doc.models.map((m) => m.name));
|
|
705
|
+
for (const name of duplicateModels) {
|
|
706
|
+
errors.push(`Duplicate model '${name}'.`);
|
|
707
|
+
}
|
|
708
|
+
for (const model of doc.models) {
|
|
709
|
+
const fieldNames = /* @__PURE__ */ new Set();
|
|
710
|
+
for (const field of model.fields) {
|
|
711
|
+
if (fieldNames.has(field.name)) {
|
|
712
|
+
errors.push(`Duplicate field '${model.name}.${field.name}'.`);
|
|
713
|
+
}
|
|
714
|
+
fieldNames.add(field.name);
|
|
715
|
+
const known = scalar.has(field.type) || enumNames.has(field.type) || modelNames.has(field.type);
|
|
716
|
+
if (!known) {
|
|
717
|
+
errors.push(
|
|
718
|
+
`Field '${model.name}.${field.name}' has unknown type '${field.type}'.`
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
if (field.kind === "object" && field.relation) {
|
|
722
|
+
validateRelation(doc, model.name, field.name, field.relation, errors);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
for (const pk of model.primaryKey) {
|
|
726
|
+
if (!fieldNames.has(pk)) {
|
|
727
|
+
errors.push(
|
|
728
|
+
`Primary key field '${pk}' does not exist on model '${model.name}'.`
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
const hasId = model.primaryKey.length > 0 || model.fields.some((f) => f.isId);
|
|
733
|
+
if (!hasId) {
|
|
734
|
+
errors.push(
|
|
735
|
+
`Model '${model.name}' has no @id / @@id. Every model needs a primary key.`
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
for (const enumNode of doc.enums) {
|
|
740
|
+
if (enumNode.values.length === 0) {
|
|
741
|
+
errors.push(`Enum '${enumNode.name}' has no values.`);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
if (errors.length > 0) {
|
|
745
|
+
throw new SchemaValidationError("Schema validation failed", errors);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
function validateRelation(doc, modelName, fieldName, relation, errors) {
|
|
749
|
+
const model = findModel(doc, modelName);
|
|
750
|
+
if (!model) return;
|
|
751
|
+
for (const f of relation.fields ?? []) {
|
|
752
|
+
if (!model.fields.some((mf) => mf.name === f)) {
|
|
753
|
+
errors.push(
|
|
754
|
+
`Relation '${modelName}.${fieldName}' references local field '${f}' which does not exist.`
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
function findDuplicates(values) {
|
|
760
|
+
const seen = /* @__PURE__ */ new Set();
|
|
761
|
+
const dups = /* @__PURE__ */ new Set();
|
|
762
|
+
for (const v of values) {
|
|
763
|
+
if (seen.has(v)) dups.add(v);
|
|
764
|
+
seen.add(v);
|
|
765
|
+
}
|
|
766
|
+
return [...dups];
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// src/schema/printer.ts
|
|
770
|
+
function printSchema(doc) {
|
|
771
|
+
const blocks = [];
|
|
772
|
+
if (doc.datasource) {
|
|
773
|
+
const ds = doc.datasource;
|
|
774
|
+
const url = ds.url.kind === "env" ? `env("${ds.url.value}")` : `"${ds.url.value}"`;
|
|
775
|
+
blocks.push(
|
|
776
|
+
`datasource ${ds.name} {
|
|
777
|
+
provider = "${ds.provider}"
|
|
778
|
+
url = ${url}
|
|
779
|
+
}`
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
for (const gen of doc.generators) {
|
|
783
|
+
const lines = [` provider = "${gen.provider}"`];
|
|
784
|
+
if (gen.output) lines.push(` output = "${gen.output}"`);
|
|
785
|
+
for (const [k, v] of Object.entries(gen.config)) {
|
|
786
|
+
if (k === "provider" || k === "output") continue;
|
|
787
|
+
lines.push(` ${k} = "${v}"`);
|
|
788
|
+
}
|
|
789
|
+
blocks.push(`generator ${gen.name} {
|
|
790
|
+
${lines.join("\n")}
|
|
791
|
+
}`);
|
|
792
|
+
}
|
|
793
|
+
for (const enumNode of doc.enums) {
|
|
794
|
+
blocks.push(printEnum(enumNode));
|
|
795
|
+
}
|
|
796
|
+
for (const model of doc.models) {
|
|
797
|
+
blocks.push(printModel(model));
|
|
798
|
+
}
|
|
799
|
+
return blocks.join("\n\n") + "\n";
|
|
800
|
+
}
|
|
801
|
+
function printEnum(node) {
|
|
802
|
+
const lines = [];
|
|
803
|
+
if (node.documentation) lines.push(...docLines(node.documentation));
|
|
804
|
+
lines.push(`enum ${node.name} {`);
|
|
805
|
+
for (const v of node.values) {
|
|
806
|
+
lines.push(` ${v.name}${v.dbName ? ` @map("${v.dbName}")` : ""}`);
|
|
807
|
+
}
|
|
808
|
+
if (node.dbName) lines.push(`
|
|
809
|
+
@@map("${node.dbName}")`);
|
|
810
|
+
lines.push(`}`);
|
|
811
|
+
return lines.join("\n");
|
|
812
|
+
}
|
|
813
|
+
function printModel(model) {
|
|
814
|
+
const lines = [];
|
|
815
|
+
if (model.documentation) lines.push(...docLines(model.documentation));
|
|
816
|
+
lines.push(`model ${model.name} {`);
|
|
817
|
+
const nameWidth = Math.max(...model.fields.map((f) => f.name.length), 0);
|
|
818
|
+
const typeWidth = Math.max(...model.fields.map((f) => fieldType(f).length), 0);
|
|
819
|
+
for (const field of model.fields) {
|
|
820
|
+
if (field.documentation) {
|
|
821
|
+
lines.push(...docLines(field.documentation).map((l) => ` ${l}`));
|
|
822
|
+
}
|
|
823
|
+
const attrs = fieldAttributes(field);
|
|
824
|
+
const name = field.name.padEnd(nameWidth);
|
|
825
|
+
const type = fieldType(field).padEnd(typeWidth);
|
|
826
|
+
lines.push(` ${name} ${type}${attrs ? ` ${attrs}` : ""}`.trimEnd());
|
|
827
|
+
}
|
|
828
|
+
const blockAttrs = modelBlockAttributes(model);
|
|
829
|
+
if (blockAttrs.length > 0) {
|
|
830
|
+
lines.push("");
|
|
831
|
+
for (const a of blockAttrs) lines.push(` ${a}`);
|
|
832
|
+
}
|
|
833
|
+
lines.push(`}`);
|
|
834
|
+
return lines.join("\n");
|
|
835
|
+
}
|
|
836
|
+
function fieldType(field) {
|
|
837
|
+
let t = field.type;
|
|
838
|
+
if (field.isList) t += "[]";
|
|
839
|
+
else if (!field.isRequired) t += "?";
|
|
840
|
+
return t;
|
|
841
|
+
}
|
|
842
|
+
function fieldAttributes(field) {
|
|
843
|
+
const parts = [];
|
|
844
|
+
if (field.isId) parts.push("@id");
|
|
845
|
+
if (field.isUnique) parts.push("@unique");
|
|
846
|
+
if (field.default) parts.push(`@default(${printDefault(field.default)})`);
|
|
847
|
+
if (field.isUpdatedAt) parts.push("@updatedAt");
|
|
848
|
+
if (field.relation) {
|
|
849
|
+
const rel = field.relation;
|
|
850
|
+
const args = [];
|
|
851
|
+
if (rel.name) args.push(`"${rel.name}"`);
|
|
852
|
+
if (rel.fields?.length) args.push(`fields: [${rel.fields.join(", ")}]`);
|
|
853
|
+
if (rel.references?.length)
|
|
854
|
+
args.push(`references: [${rel.references.join(", ")}]`);
|
|
855
|
+
if (rel.onDelete) args.push(`onDelete: ${rel.onDelete}`);
|
|
856
|
+
if (rel.onUpdate) args.push(`onUpdate: ${rel.onUpdate}`);
|
|
857
|
+
parts.push(args.length ? `@relation(${args.join(", ")})` : "@relation");
|
|
858
|
+
}
|
|
859
|
+
if (field.nativeType) {
|
|
860
|
+
const a = field.nativeType.args.length ? `(${field.nativeType.args.join(", ")})` : "";
|
|
861
|
+
parts.push(`@db.${field.nativeType.name}${a}`);
|
|
862
|
+
}
|
|
863
|
+
if (field.dbName) parts.push(`@map("${field.dbName}")`);
|
|
864
|
+
return parts.join(" ");
|
|
865
|
+
}
|
|
866
|
+
function printDefault(def) {
|
|
867
|
+
if (def.function) {
|
|
868
|
+
return `${def.function.name}(${def.function.args.map(printArgValue).join(", ")})`;
|
|
869
|
+
}
|
|
870
|
+
if (typeof def.literal === "string") {
|
|
871
|
+
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(def.literal) ? def.literal : `"${def.literal}"`;
|
|
872
|
+
}
|
|
873
|
+
return String(def.literal);
|
|
874
|
+
}
|
|
875
|
+
function printArgValue(v) {
|
|
876
|
+
switch (v.kind) {
|
|
877
|
+
case "string":
|
|
878
|
+
return `"${v.value}"`;
|
|
879
|
+
case "number":
|
|
880
|
+
return String(v.value);
|
|
881
|
+
case "boolean":
|
|
882
|
+
return String(v.value);
|
|
883
|
+
case "ref":
|
|
884
|
+
return v.value;
|
|
885
|
+
case "array":
|
|
886
|
+
return `[${v.items.map(printArgValue).join(", ")}]`;
|
|
887
|
+
case "function":
|
|
888
|
+
return `${v.name}(${v.args.map(printArgValue).join(", ")})`;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
function modelBlockAttributes(model) {
|
|
892
|
+
const out = [];
|
|
893
|
+
const inlineId = model.primaryKey.length === 1 && model.fields.find((f) => f.name === model.primaryKey[0])?.isId;
|
|
894
|
+
if (model.primaryKey.length > 0 && !inlineId) {
|
|
895
|
+
out.push(`@@id([${model.primaryKey.join(", ")}])`);
|
|
896
|
+
}
|
|
897
|
+
for (const u of model.uniqueIndexes) {
|
|
898
|
+
out.push(
|
|
899
|
+
`@@unique([${u.fields.join(", ")}]${u.name ? `, map: "${u.name}"` : ""})`
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
for (const i of model.indexes) {
|
|
903
|
+
out.push(
|
|
904
|
+
`@@index([${i.fields.join(", ")}]${i.name ? `, map: "${i.name}"` : ""})`
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
if (model.dbName) out.push(`@@map("${model.dbName}")`);
|
|
908
|
+
return out;
|
|
909
|
+
}
|
|
910
|
+
function docLines(documentation) {
|
|
911
|
+
return documentation.split("\n").map((l) => `/// ${l}`);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// src/utils/index.ts
|
|
915
|
+
function pascalCase(input) {
|
|
916
|
+
return input.toLowerCase().replace(
|
|
917
|
+
/[^a-zA-Z0-9]+(.)?/g,
|
|
918
|
+
(_, c) => c ? c.toUpperCase() : ""
|
|
919
|
+
).replace(/^(.)/, (_, c) => c.toUpperCase());
|
|
920
|
+
}
|
|
921
|
+
function camelCase(input) {
|
|
922
|
+
const pascal = pascalCase(input);
|
|
923
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
924
|
+
}
|
|
925
|
+
function pluralize(word) {
|
|
926
|
+
if (/[^aeiou]y$/i.test(word)) return word.replace(/y$/i, "ies");
|
|
927
|
+
if (/(s|x|z|ch|sh)$/i.test(word)) return `${word}es`;
|
|
928
|
+
return `${word}s`;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// src/schema/relations-complete.ts
|
|
932
|
+
function completeRelations(doc) {
|
|
933
|
+
const original = [];
|
|
934
|
+
for (const model of doc.models) {
|
|
935
|
+
for (const field of model.fields) {
|
|
936
|
+
if (field.kind === "object") original.push({ model, field });
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
for (const { model, field } of original) {
|
|
940
|
+
const related = findModel(doc, field.type);
|
|
941
|
+
if (!related) continue;
|
|
942
|
+
if (hasPartner(related, model, field)) continue;
|
|
943
|
+
if (field.isList) {
|
|
944
|
+
addOwningSide(related, model, field);
|
|
945
|
+
} else if (field.relation?.fields?.length) {
|
|
946
|
+
addBackRelation(related, model, field);
|
|
947
|
+
} else {
|
|
948
|
+
upgradeBareToOne(doc, model, field);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
return doc;
|
|
952
|
+
}
|
|
953
|
+
function hasPartner(related, model, field) {
|
|
954
|
+
return related.fields.some(
|
|
955
|
+
(f) => f !== field && f.kind === "object" && f.type === model.name && relationNamesMatch(f, field)
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
function relationNamesMatch(a, b) {
|
|
959
|
+
const an = a.relation?.name;
|
|
960
|
+
const bn = b.relation?.name;
|
|
961
|
+
if (an && bn) return an === bn;
|
|
962
|
+
if (!an && !bn) return true;
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
965
|
+
function addBackRelation(related, owner, owning) {
|
|
966
|
+
const fkFields = (owning.relation?.fields ?? []).map((n) => owner.fields.find((f) => f.name === n)).filter((f) => !!f);
|
|
967
|
+
const isOneToOne = fkFields.length > 0 && fkFields.every((f) => f.isUnique);
|
|
968
|
+
const baseName = isOneToOne ? camelCase(owner.name) : camelCase(pluralize(owner.name));
|
|
969
|
+
const name = uniqueFieldName(related, baseName);
|
|
970
|
+
related.fields.push({
|
|
971
|
+
name,
|
|
972
|
+
type: owner.name,
|
|
973
|
+
kind: "object",
|
|
974
|
+
isList: !isOneToOne,
|
|
975
|
+
isRequired: false,
|
|
976
|
+
isId: false,
|
|
977
|
+
isUnique: false,
|
|
978
|
+
isUpdatedAt: false,
|
|
979
|
+
// Only emit @relation when the relation is named; an unnamed back-relation
|
|
980
|
+
// carries no attribute (matches Prisma's formatter output).
|
|
981
|
+
...owning.relation?.name ? { relation: { name: owning.relation.name } } : {}
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
function addOwningSide(related, parent, listField) {
|
|
985
|
+
const ref = idFields(parent)[0];
|
|
986
|
+
if (!ref) return;
|
|
987
|
+
const relationField = uniqueFieldName(related, camelCase(parent.name));
|
|
988
|
+
const fkName = uniqueFieldName(
|
|
989
|
+
related,
|
|
990
|
+
`${camelCase(parent.name)}${pascalCase(ref.name)}`
|
|
991
|
+
);
|
|
992
|
+
related.fields.push({
|
|
993
|
+
name: fkName,
|
|
994
|
+
type: ref.type,
|
|
995
|
+
kind: "scalar",
|
|
996
|
+
isList: false,
|
|
997
|
+
isRequired: true,
|
|
998
|
+
isId: false,
|
|
999
|
+
isUnique: false,
|
|
1000
|
+
isUpdatedAt: false,
|
|
1001
|
+
...ref.nativeType ? { nativeType: ref.nativeType } : {}
|
|
1002
|
+
});
|
|
1003
|
+
related.fields.push({
|
|
1004
|
+
name: relationField,
|
|
1005
|
+
type: parent.name,
|
|
1006
|
+
kind: "object",
|
|
1007
|
+
isList: false,
|
|
1008
|
+
isRequired: true,
|
|
1009
|
+
isId: false,
|
|
1010
|
+
isUnique: false,
|
|
1011
|
+
isUpdatedAt: false,
|
|
1012
|
+
relation: {
|
|
1013
|
+
...listField.relation?.name ? { name: listField.relation.name } : {},
|
|
1014
|
+
fields: [fkName],
|
|
1015
|
+
references: [ref.name]
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
function upgradeBareToOne(doc, model, field) {
|
|
1020
|
+
const related = findModel(doc, field.type);
|
|
1021
|
+
if (!related) return;
|
|
1022
|
+
const ref = idFields(related)[0];
|
|
1023
|
+
if (!ref) return;
|
|
1024
|
+
const fkName = uniqueFieldName(model, `${field.name}${pascalCase(ref.name)}`);
|
|
1025
|
+
const idx = model.fields.indexOf(field);
|
|
1026
|
+
model.fields.splice(idx, 0, {
|
|
1027
|
+
name: fkName,
|
|
1028
|
+
type: ref.type,
|
|
1029
|
+
kind: "scalar",
|
|
1030
|
+
isList: false,
|
|
1031
|
+
isRequired: field.isRequired,
|
|
1032
|
+
isId: false,
|
|
1033
|
+
isUnique: false,
|
|
1034
|
+
isUpdatedAt: false,
|
|
1035
|
+
...ref.nativeType ? { nativeType: ref.nativeType } : {}
|
|
1036
|
+
});
|
|
1037
|
+
field.relation = {
|
|
1038
|
+
...field.relation ?? {},
|
|
1039
|
+
fields: [fkName],
|
|
1040
|
+
references: [ref.name]
|
|
1041
|
+
};
|
|
1042
|
+
addBackRelation(related, model, field);
|
|
1043
|
+
}
|
|
1044
|
+
function uniqueFieldName(model, base) {
|
|
1045
|
+
let name = base || "relation";
|
|
1046
|
+
let i = 1;
|
|
1047
|
+
while (model.fields.some((f) => f.name === name)) name = `${base}_${++i}`;
|
|
1048
|
+
return name;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// src/schema/index.ts
|
|
1052
|
+
function formatSchema(source, file) {
|
|
1053
|
+
const doc = completeRelations(parseSchema(source, file));
|
|
1054
|
+
validateSchema(doc);
|
|
1055
|
+
return printSchema(doc);
|
|
1056
|
+
}
|
|
1057
|
+
function parseSchema(source, file) {
|
|
1058
|
+
return new Parser(source, file).parse();
|
|
1059
|
+
}
|
|
1060
|
+
function parseAndValidate(source, file) {
|
|
1061
|
+
const doc = parseSchema(source, file);
|
|
1062
|
+
validateSchema(doc);
|
|
1063
|
+
return doc;
|
|
1064
|
+
}
|
|
1065
|
+
export {
|
|
1066
|
+
EmberError,
|
|
1067
|
+
SCALAR_TYPES,
|
|
1068
|
+
SchemaParseError,
|
|
1069
|
+
SchemaValidationError,
|
|
1070
|
+
completeRelations,
|
|
1071
|
+
formatSchema,
|
|
1072
|
+
parseAndValidate,
|
|
1073
|
+
parseSchema,
|
|
1074
|
+
printSchema,
|
|
1075
|
+
validateSchema
|
|
1076
|
+
};
|
|
1077
|
+
//# sourceMappingURL=editor.js.map
|