@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.
@@ -0,0 +1,3241 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/bin.ts
4
+ import { readFileSync as readFileSync4 } from "fs";
5
+ import { fileURLToPath } from "url";
6
+
7
+ // src/errors/index.ts
8
+ var EmberError = class extends Error {
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = new.target.name;
12
+ Object.setPrototypeOf(this, new.target.prototype);
13
+ }
14
+ };
15
+ var SchemaParseError = class extends EmberError {
16
+ constructor(message, line, column, file) {
17
+ super(
18
+ `${message} (at ${file ? `${file}:` : ""}${line}:${column})`
19
+ );
20
+ this.line = line;
21
+ this.column = column;
22
+ this.file = file;
23
+ }
24
+ line;
25
+ column;
26
+ file;
27
+ };
28
+ var SchemaValidationError = class extends EmberError {
29
+ constructor(message, details = []) {
30
+ super(
31
+ details.length > 0 ? `${message}
32
+ - ${details.join("\n - ")}` : message
33
+ );
34
+ this.details = details;
35
+ }
36
+ details;
37
+ };
38
+ var DatabaseError = class extends EmberError {
39
+ constructor(message, cause, sql) {
40
+ super(message);
41
+ this.cause = cause;
42
+ this.sql = sql;
43
+ }
44
+ cause;
45
+ sql;
46
+ };
47
+
48
+ // src/cli/commands.ts
49
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
50
+ import { dirname as dirname3, resolve as resolve3 } from "path";
51
+
52
+ // src/schema/index.ts
53
+ import { readFileSync, existsSync } from "fs";
54
+ import { resolve, dirname } from "path";
55
+
56
+ // src/schema/lexer.ts
57
+ var SINGLE_CHAR_TOKENS = {
58
+ "{": "lbrace",
59
+ "}": "rbrace",
60
+ "(": "lparen",
61
+ ")": "rparen",
62
+ "[": "lbracket",
63
+ "]": "rbracket",
64
+ "=": "equals",
65
+ ",": "comma",
66
+ ":": "colon",
67
+ "?": "question",
68
+ ".": "dot"
69
+ };
70
+ var IDENT_START = /[A-Za-z_]/;
71
+ var IDENT_PART = /[A-Za-z0-9_]/;
72
+ var Lexer = class {
73
+ constructor(source, file) {
74
+ this.source = source;
75
+ this.file = file;
76
+ }
77
+ source;
78
+ file;
79
+ pos = 0;
80
+ line = 1;
81
+ column = 1;
82
+ tokenize() {
83
+ const tokens = [];
84
+ let token = this.next();
85
+ while (token.type !== "eof") {
86
+ tokens.push(token);
87
+ token = this.next();
88
+ }
89
+ tokens.push(token);
90
+ return tokens;
91
+ }
92
+ next() {
93
+ this.skipWhitespaceAndComments();
94
+ if (this.pos >= this.source.length) {
95
+ return this.make("eof", "");
96
+ }
97
+ const startLine = this.line;
98
+ const startColumn = this.column;
99
+ const ch = this.source[this.pos];
100
+ if (ch === "/" && this.peek(1) === "/" && this.peek(2) === "/") {
101
+ return this.readDocComment(startLine, startColumn);
102
+ }
103
+ if (ch === '"') {
104
+ return this.readString(startLine, startColumn);
105
+ }
106
+ if (ch === "@") {
107
+ this.advance();
108
+ if (this.source[this.pos] === "@") {
109
+ this.advance();
110
+ return { type: "double_at", value: "@@", line: startLine, column: startColumn };
111
+ }
112
+ return { type: "at", value: "@", line: startLine, column: startColumn };
113
+ }
114
+ const single = SINGLE_CHAR_TOKENS[ch];
115
+ if (single) {
116
+ this.advance();
117
+ return { type: single, value: ch, line: startLine, column: startColumn };
118
+ }
119
+ if (ch === "-" || /[0-9]/.test(ch)) {
120
+ return this.readNumber(startLine, startColumn);
121
+ }
122
+ if (IDENT_START.test(ch)) {
123
+ return this.readIdentifier(startLine, startColumn);
124
+ }
125
+ throw new SchemaParseError(
126
+ `Unexpected character '${ch}'`,
127
+ startLine,
128
+ startColumn,
129
+ this.file
130
+ );
131
+ }
132
+ readDocComment(line, column) {
133
+ this.advance();
134
+ this.advance();
135
+ this.advance();
136
+ let value = "";
137
+ while (this.pos < this.source.length && this.source[this.pos] !== "\n") {
138
+ value += this.source[this.pos];
139
+ this.advance();
140
+ }
141
+ return { type: "doc_comment", value: value.trim(), line, column };
142
+ }
143
+ readString(line, column) {
144
+ this.advance();
145
+ let value = "";
146
+ while (this.pos < this.source.length && this.source[this.pos] !== '"') {
147
+ const c = this.source[this.pos];
148
+ if (c === "\\") {
149
+ this.advance();
150
+ const escaped = this.source[this.pos];
151
+ if (escaped === void 0) break;
152
+ value += escapeChar(escaped);
153
+ this.advance();
154
+ continue;
155
+ }
156
+ if (c === "\n") {
157
+ throw new SchemaParseError(
158
+ "Unterminated string literal",
159
+ line,
160
+ column,
161
+ this.file
162
+ );
163
+ }
164
+ value += c;
165
+ this.advance();
166
+ }
167
+ if (this.source[this.pos] !== '"') {
168
+ throw new SchemaParseError(
169
+ "Unterminated string literal",
170
+ line,
171
+ column,
172
+ this.file
173
+ );
174
+ }
175
+ this.advance();
176
+ return { type: "string", value, line, column };
177
+ }
178
+ readNumber(line, column) {
179
+ let value = "";
180
+ if (this.source[this.pos] === "-") {
181
+ value += "-";
182
+ this.advance();
183
+ }
184
+ while (this.pos < this.source.length && /[0-9.]/.test(this.source[this.pos])) {
185
+ value += this.source[this.pos];
186
+ this.advance();
187
+ }
188
+ return { type: "number", value, line, column };
189
+ }
190
+ readIdentifier(line, column) {
191
+ let value = "";
192
+ while (this.pos < this.source.length && IDENT_PART.test(this.source[this.pos])) {
193
+ value += this.source[this.pos];
194
+ this.advance();
195
+ }
196
+ return { type: "identifier", value, line, column };
197
+ }
198
+ skipWhitespaceAndComments() {
199
+ while (this.pos < this.source.length) {
200
+ const ch = this.source[this.pos];
201
+ if (ch === " " || ch === " " || ch === "\r" || ch === "\n") {
202
+ this.advance();
203
+ continue;
204
+ }
205
+ if (ch === "/" && this.peek(1) === "/" && this.peek(2) !== "/") {
206
+ while (this.pos < this.source.length && this.source[this.pos] !== "\n") {
207
+ this.advance();
208
+ }
209
+ continue;
210
+ }
211
+ if (ch === "/" && this.peek(1) === "*") {
212
+ this.advance();
213
+ this.advance();
214
+ while (this.pos < this.source.length && !(this.source[this.pos] === "*" && this.peek(1) === "/")) {
215
+ this.advance();
216
+ }
217
+ this.advance();
218
+ this.advance();
219
+ continue;
220
+ }
221
+ break;
222
+ }
223
+ }
224
+ peek(offset) {
225
+ return this.source[this.pos + offset];
226
+ }
227
+ advance() {
228
+ if (this.source[this.pos] === "\n") {
229
+ this.line++;
230
+ this.column = 1;
231
+ } else {
232
+ this.column++;
233
+ }
234
+ this.pos++;
235
+ }
236
+ make(type, value) {
237
+ return { type, value, line: this.line, column: this.column };
238
+ }
239
+ };
240
+ function escapeChar(c) {
241
+ switch (c) {
242
+ case "n":
243
+ return "\n";
244
+ case "t":
245
+ return " ";
246
+ case "r":
247
+ return "\r";
248
+ case '"':
249
+ return '"';
250
+ case "\\":
251
+ return "\\";
252
+ default:
253
+ return c;
254
+ }
255
+ }
256
+
257
+ // src/schema/parser.ts
258
+ var Parser = class {
259
+ constructor(source, file) {
260
+ this.file = file;
261
+ this.tokens = new Lexer(source, file).tokenize();
262
+ }
263
+ file;
264
+ tokens;
265
+ pos = 0;
266
+ parse() {
267
+ const doc = { generators: [], models: [], enums: [] };
268
+ let pendingDoc = [];
269
+ while (!this.isEof()) {
270
+ const tok = this.peek();
271
+ if (tok.type === "doc_comment") {
272
+ pendingDoc.push(tok.value);
273
+ this.advance();
274
+ continue;
275
+ }
276
+ if (tok.type !== "identifier") {
277
+ throw this.error(`Unexpected token '${tok.value}'`, tok);
278
+ }
279
+ const documentation = pendingDoc.length ? pendingDoc.join("\n") : void 0;
280
+ pendingDoc = [];
281
+ switch (tok.value) {
282
+ case "datasource":
283
+ doc.datasource = this.parseDatasource();
284
+ break;
285
+ case "generator":
286
+ doc.generators.push(this.parseGenerator());
287
+ break;
288
+ case "model":
289
+ doc.models.push(this.parseModel(documentation));
290
+ break;
291
+ case "enum":
292
+ doc.enums.push(this.parseEnum(documentation));
293
+ break;
294
+ default:
295
+ throw this.error(`Unknown top-level keyword '${tok.value}'`, tok);
296
+ }
297
+ }
298
+ resolveKinds(doc);
299
+ return doc;
300
+ }
301
+ // ---- Top-level blocks -------------------------------------------------
302
+ parseDatasource() {
303
+ this.expectKeyword("datasource");
304
+ const name = this.expect("identifier").value;
305
+ this.expect("lbrace");
306
+ const assignments = this.parseAssignments();
307
+ this.expect("rbrace");
308
+ const provider = literalString(assignments["provider"]);
309
+ const urlAssign = assignments["url"];
310
+ let url = { kind: "literal", value: "" };
311
+ if (urlAssign) {
312
+ if (urlAssign.kind === "function" && urlAssign.name === "env") {
313
+ url = { kind: "env", value: literalString(urlAssign.args[0]) };
314
+ } else if (urlAssign.kind === "string") {
315
+ url = { kind: "literal", value: urlAssign.value };
316
+ }
317
+ }
318
+ return { name, provider: provider ?? "firebird", url };
319
+ }
320
+ parseGenerator() {
321
+ this.expectKeyword("generator");
322
+ const name = this.expect("identifier").value;
323
+ this.expect("lbrace");
324
+ const assignments = this.parseAssignments();
325
+ this.expect("rbrace");
326
+ const config = {};
327
+ for (const [key, value] of Object.entries(assignments)) {
328
+ if (value.kind === "string") config[key] = value.value;
329
+ }
330
+ return {
331
+ name,
332
+ provider: literalString(assignments["provider"]) ?? "ember-client-js",
333
+ output: assignments["output"] ? literalString(assignments["output"]) : void 0,
334
+ config
335
+ };
336
+ }
337
+ parseAssignments() {
338
+ const out = {};
339
+ while (!this.check("rbrace") && !this.isEof()) {
340
+ if (this.check("doc_comment")) {
341
+ this.advance();
342
+ continue;
343
+ }
344
+ const key = this.expect("identifier").value;
345
+ this.expect("equals");
346
+ out[key] = this.parseValue();
347
+ }
348
+ return out;
349
+ }
350
+ parseModel(documentation) {
351
+ this.expectKeyword("model");
352
+ const name = this.expect("identifier").value;
353
+ this.expect("lbrace");
354
+ const model = {
355
+ name,
356
+ fields: [],
357
+ primaryKey: [],
358
+ uniqueIndexes: [],
359
+ indexes: [],
360
+ documentation
361
+ };
362
+ let pendingDoc = [];
363
+ while (!this.check("rbrace") && !this.isEof()) {
364
+ const tok = this.peek();
365
+ if (tok.type === "doc_comment") {
366
+ pendingDoc.push(tok.value);
367
+ this.advance();
368
+ continue;
369
+ }
370
+ if (tok.type === "double_at") {
371
+ this.parseBlockAttribute(model);
372
+ pendingDoc = [];
373
+ continue;
374
+ }
375
+ const field = this.parseField(
376
+ pendingDoc.length ? pendingDoc.join("\n") : void 0
377
+ );
378
+ pendingDoc = [];
379
+ model.fields.push(field);
380
+ if (field.isId && !model.primaryKey.includes(field.name)) {
381
+ model.primaryKey.push(field.name);
382
+ }
383
+ }
384
+ this.expect("rbrace");
385
+ return model;
386
+ }
387
+ parseField(documentation) {
388
+ const name = this.expect("identifier").value;
389
+ const typeName = this.expect("identifier").value;
390
+ let isList = false;
391
+ let isRequired = true;
392
+ while (this.check("lbracket") || this.check("question")) {
393
+ if (this.check("lbracket")) {
394
+ this.advance();
395
+ this.expect("rbracket");
396
+ isList = true;
397
+ } else {
398
+ this.advance();
399
+ isRequired = false;
400
+ }
401
+ }
402
+ const field = {
403
+ name,
404
+ type: typeName,
405
+ kind: "scalar",
406
+ // resolved later
407
+ isList,
408
+ isRequired,
409
+ isId: false,
410
+ isUnique: false,
411
+ isUpdatedAt: false,
412
+ documentation
413
+ };
414
+ while (this.check("at")) {
415
+ const attr = this.parseAttribute();
416
+ this.applyFieldAttribute(field, attr);
417
+ }
418
+ return field;
419
+ }
420
+ applyFieldAttribute(field, attr) {
421
+ if (attr.name.startsWith("db.")) {
422
+ field.nativeType = toNativeType(attr);
423
+ return;
424
+ }
425
+ switch (attr.name) {
426
+ case "id":
427
+ field.isId = true;
428
+ break;
429
+ case "unique":
430
+ field.isUnique = true;
431
+ break;
432
+ case "updatedAt":
433
+ field.isUpdatedAt = true;
434
+ break;
435
+ case "default":
436
+ field.default = toDefaultValue(attr.args[0]);
437
+ break;
438
+ case "map":
439
+ field.dbName = literalString(attr.args[0]);
440
+ break;
441
+ case "relation":
442
+ field.relation = toRelationInfo(attr.args);
443
+ break;
444
+ default:
445
+ throw this.error(`Unknown field attribute '@${attr.name}'`, attr);
446
+ }
447
+ }
448
+ parseBlockAttribute(model) {
449
+ const start = this.expect("double_at");
450
+ const name = this.expect("identifier").value;
451
+ const args = this.check("lparen") ? this.parseArgList() : [];
452
+ switch (name) {
453
+ case "id":
454
+ model.primaryKey = fieldNameList(args);
455
+ break;
456
+ case "unique":
457
+ model.uniqueIndexes.push({
458
+ fields: fieldNameList(args),
459
+ name: namedArg(args, "map")
460
+ });
461
+ break;
462
+ case "index":
463
+ model.indexes.push({
464
+ fields: fieldNameList(args),
465
+ name: namedArg(args, "map"),
466
+ unique: false
467
+ });
468
+ break;
469
+ case "map":
470
+ model.dbName = literalString(args[0]);
471
+ break;
472
+ default:
473
+ throw this.error(`Unknown block attribute '@@${name}'`, start);
474
+ }
475
+ }
476
+ parseEnum(documentation) {
477
+ this.expectKeyword("enum");
478
+ const name = this.expect("identifier").value;
479
+ this.expect("lbrace");
480
+ const node = { name, values: [], documentation };
481
+ while (!this.check("rbrace") && !this.isEof()) {
482
+ if (this.check("doc_comment")) {
483
+ this.advance();
484
+ continue;
485
+ }
486
+ if (this.check("double_at")) {
487
+ this.advance();
488
+ const attrName = this.expect("identifier").value;
489
+ const args = this.check("lparen") ? this.parseArgList() : [];
490
+ if (attrName === "map") node.dbName = literalString(args[0]);
491
+ continue;
492
+ }
493
+ const valueName = this.expect("identifier").value;
494
+ let dbName;
495
+ while (this.check("at")) {
496
+ const attr = this.parseAttribute();
497
+ if (attr.name === "map") dbName = literalString(attr.args[0]);
498
+ }
499
+ node.values.push({ name: valueName, dbName });
500
+ }
501
+ this.expect("rbrace");
502
+ return node;
503
+ }
504
+ // ---- Attributes & values ---------------------------------------------
505
+ parseAttribute() {
506
+ const at = this.expect("at");
507
+ let name = this.expect("identifier").value;
508
+ if (this.check("dot")) {
509
+ this.advance();
510
+ name += "." + this.expect("identifier").value;
511
+ }
512
+ const args = this.check("lparen") ? this.parseArgList() : [];
513
+ return { name, args, line: at.line, column: at.column };
514
+ }
515
+ parseArgList() {
516
+ this.expect("lparen");
517
+ const args = [];
518
+ while (!this.check("rparen") && !this.isEof()) {
519
+ args.push(this.parseArg());
520
+ if (this.check("comma")) this.advance();
521
+ }
522
+ this.expect("rparen");
523
+ return args;
524
+ }
525
+ /** Handles both positional values and `name: value` named arguments. */
526
+ parseArg() {
527
+ if (this.check("identifier") && this.peek(1)?.type === "colon") {
528
+ const key = this.expect("identifier").value;
529
+ this.expect("colon");
530
+ const value = this.parseValue();
531
+ return { kind: "function", name: `__named:${key}`, args: [value] };
532
+ }
533
+ return this.parseValue();
534
+ }
535
+ parseValue() {
536
+ const tok = this.peek();
537
+ switch (tok.type) {
538
+ case "string":
539
+ this.advance();
540
+ return { kind: "string", value: tok.value };
541
+ case "number":
542
+ this.advance();
543
+ return { kind: "number", value: Number(tok.value) };
544
+ case "lbracket": {
545
+ this.advance();
546
+ const items = [];
547
+ while (!this.check("rbracket") && !this.isEof()) {
548
+ items.push(this.parseValue());
549
+ if (this.check("comma")) this.advance();
550
+ }
551
+ this.expect("rbracket");
552
+ return { kind: "array", items };
553
+ }
554
+ case "identifier": {
555
+ this.advance();
556
+ if (tok.value === "true" || tok.value === "false") {
557
+ return { kind: "boolean", value: tok.value === "true" };
558
+ }
559
+ if (this.check("lparen")) {
560
+ const args = this.parseArgList();
561
+ return { kind: "function", name: tok.value, args };
562
+ }
563
+ return { kind: "ref", value: tok.value };
564
+ }
565
+ default:
566
+ throw this.error(`Unexpected value token '${tok.value}'`, tok);
567
+ }
568
+ }
569
+ // ---- Token helpers ----------------------------------------------------
570
+ peek(offset = 0) {
571
+ return this.tokens[this.pos + offset] ?? this.tokens[this.tokens.length - 1];
572
+ }
573
+ advance() {
574
+ const tok = this.tokens[this.pos];
575
+ if (this.pos < this.tokens.length - 1) this.pos++;
576
+ return tok;
577
+ }
578
+ check(type) {
579
+ return this.peek().type === type;
580
+ }
581
+ isEof() {
582
+ return this.peek().type === "eof";
583
+ }
584
+ expect(type) {
585
+ const tok = this.peek();
586
+ if (tok.type !== type) {
587
+ throw this.error(`Expected ${type} but found '${tok.value}' (${tok.type})`, tok);
588
+ }
589
+ return this.advance();
590
+ }
591
+ expectKeyword(keyword) {
592
+ const tok = this.peek();
593
+ if (tok.type !== "identifier" || tok.value !== keyword) {
594
+ throw this.error(`Expected keyword '${keyword}'`, tok);
595
+ }
596
+ return this.advance();
597
+ }
598
+ error(message, at) {
599
+ return new SchemaParseError(message, at.line, at.column, this.file);
600
+ }
601
+ };
602
+ function literalString(v) {
603
+ if (!v) return "";
604
+ if (v.kind === "string") return v.value;
605
+ if (v.kind === "ref") return v.value;
606
+ return String("value" in v ? v.value : "");
607
+ }
608
+ function toNativeType(attr) {
609
+ const name = attr.name.slice("db.".length);
610
+ const args = attr.args.filter((a) => a.kind === "number").map((a) => a.value);
611
+ return { name, args };
612
+ }
613
+ function toDefaultValue(v) {
614
+ if (!v) return {};
615
+ switch (v.kind) {
616
+ case "function":
617
+ return { function: { name: v.name, args: v.args } };
618
+ case "string":
619
+ return { literal: v.value };
620
+ case "number":
621
+ return { literal: v.value };
622
+ case "boolean":
623
+ return { literal: v.value };
624
+ case "ref":
625
+ return { literal: v.value };
626
+ default:
627
+ return {};
628
+ }
629
+ }
630
+ function toRelationInfo(args) {
631
+ const info = {};
632
+ for (const arg of args) {
633
+ if (arg.kind === "string") {
634
+ info.name = arg.value;
635
+ continue;
636
+ }
637
+ const named = asNamed(arg);
638
+ if (!named) continue;
639
+ const [key, value] = named;
640
+ if (key === "name" && value.kind === "string") info.name = value.value;
641
+ if (key === "fields") info.fields = refArray(value);
642
+ if (key === "references") info.references = refArray(value);
643
+ if (key === "onDelete") info.onDelete = refName(value);
644
+ if (key === "onUpdate") info.onUpdate = refName(value);
645
+ }
646
+ return info;
647
+ }
648
+ function asNamed(arg) {
649
+ if (arg.kind === "function" && arg.name.startsWith("__named:")) {
650
+ return [arg.name.slice("__named:".length), arg.args[0]];
651
+ }
652
+ return void 0;
653
+ }
654
+ function namedArg(args, key) {
655
+ for (const arg of args) {
656
+ const named = asNamed(arg);
657
+ if (named && named[0] === key && named[1].kind === "string") {
658
+ return named[1].value;
659
+ }
660
+ }
661
+ return void 0;
662
+ }
663
+ function refArray(v) {
664
+ if (v.kind === "array") {
665
+ return v.items.map((i) => refName(i)).filter((s) => !!s);
666
+ }
667
+ const single = refName(v);
668
+ return single ? [single] : [];
669
+ }
670
+ function refName(v) {
671
+ if (v.kind === "ref") return v.value;
672
+ if (v.kind === "string") return v.value;
673
+ return void 0;
674
+ }
675
+ function fieldNameList(args) {
676
+ for (const arg of args) {
677
+ if (arg.kind === "array") return refArray(arg);
678
+ const named = asNamed(arg);
679
+ if (named && named[0] === "fields") return refArray(named[1]);
680
+ }
681
+ return args.map((a) => refName(a)).filter((s) => !!s);
682
+ }
683
+ function resolveKinds(doc) {
684
+ const modelNames = new Set(doc.models.map((m) => m.name));
685
+ const enumNames = new Set(doc.enums.map((e) => e.name));
686
+ for (const model of doc.models) {
687
+ for (const field of model.fields) {
688
+ if (modelNames.has(field.type)) field.kind = "object";
689
+ else if (enumNames.has(field.type)) field.kind = "enum";
690
+ else field.kind = "scalar";
691
+ }
692
+ }
693
+ }
694
+
695
+ // src/ast/index.ts
696
+ var SCALAR_TYPES = [
697
+ "String",
698
+ "Boolean",
699
+ "Int",
700
+ "BigInt",
701
+ "Float",
702
+ "Decimal",
703
+ "DateTime",
704
+ "Bytes",
705
+ "Json"
706
+ ];
707
+ function findModel(schema, name) {
708
+ return schema.models.find((m) => m.name === name);
709
+ }
710
+ function fieldColumn(field) {
711
+ return field.dbName ?? field.name.toUpperCase();
712
+ }
713
+ function modelTable(model) {
714
+ return model.dbName ?? model.name.toUpperCase();
715
+ }
716
+ function scalarFields(model) {
717
+ return model.fields.filter((f) => f.kind !== "object");
718
+ }
719
+ function relationFields(model) {
720
+ return model.fields.filter((f) => f.kind === "object");
721
+ }
722
+ function idFields(model) {
723
+ if (model.primaryKey.length > 0) {
724
+ return model.primaryKey.map((n) => model.fields.find((f) => f.name === n)).filter((f) => !!f);
725
+ }
726
+ return model.fields.filter((f) => f.isId);
727
+ }
728
+
729
+ // src/schema/validator.ts
730
+ function validateSchema(doc) {
731
+ const errors = [];
732
+ const scalar = new Set(SCALAR_TYPES);
733
+ const enumNames = new Set(doc.enums.map((e) => e.name));
734
+ const modelNames = new Set(doc.models.map((m) => m.name));
735
+ const duplicateModels = findDuplicates(doc.models.map((m) => m.name));
736
+ for (const name of duplicateModels) {
737
+ errors.push(`Duplicate model '${name}'.`);
738
+ }
739
+ for (const model of doc.models) {
740
+ const fieldNames = /* @__PURE__ */ new Set();
741
+ for (const field of model.fields) {
742
+ if (fieldNames.has(field.name)) {
743
+ errors.push(`Duplicate field '${model.name}.${field.name}'.`);
744
+ }
745
+ fieldNames.add(field.name);
746
+ const known = scalar.has(field.type) || enumNames.has(field.type) || modelNames.has(field.type);
747
+ if (!known) {
748
+ errors.push(
749
+ `Field '${model.name}.${field.name}' has unknown type '${field.type}'.`
750
+ );
751
+ }
752
+ if (field.kind === "object" && field.relation) {
753
+ validateRelation(doc, model.name, field.name, field.relation, errors);
754
+ }
755
+ }
756
+ for (const pk of model.primaryKey) {
757
+ if (!fieldNames.has(pk)) {
758
+ errors.push(
759
+ `Primary key field '${pk}' does not exist on model '${model.name}'.`
760
+ );
761
+ }
762
+ }
763
+ const hasId = model.primaryKey.length > 0 || model.fields.some((f) => f.isId);
764
+ if (!hasId) {
765
+ errors.push(
766
+ `Model '${model.name}' has no @id / @@id. Every model needs a primary key.`
767
+ );
768
+ }
769
+ }
770
+ for (const enumNode of doc.enums) {
771
+ if (enumNode.values.length === 0) {
772
+ errors.push(`Enum '${enumNode.name}' has no values.`);
773
+ }
774
+ }
775
+ if (errors.length > 0) {
776
+ throw new SchemaValidationError("Schema validation failed", errors);
777
+ }
778
+ }
779
+ function validateRelation(doc, modelName, fieldName, relation, errors) {
780
+ const model = findModel(doc, modelName);
781
+ if (!model) return;
782
+ for (const f of relation.fields ?? []) {
783
+ if (!model.fields.some((mf) => mf.name === f)) {
784
+ errors.push(
785
+ `Relation '${modelName}.${fieldName}' references local field '${f}' which does not exist.`
786
+ );
787
+ }
788
+ }
789
+ }
790
+ function findDuplicates(values) {
791
+ const seen = /* @__PURE__ */ new Set();
792
+ const dups = /* @__PURE__ */ new Set();
793
+ for (const v of values) {
794
+ if (seen.has(v)) dups.add(v);
795
+ seen.add(v);
796
+ }
797
+ return [...dups];
798
+ }
799
+
800
+ // src/schema/printer.ts
801
+ function printSchema(doc) {
802
+ const blocks = [];
803
+ if (doc.datasource) {
804
+ const ds = doc.datasource;
805
+ const url = ds.url.kind === "env" ? `env("${ds.url.value}")` : `"${ds.url.value}"`;
806
+ blocks.push(
807
+ `datasource ${ds.name} {
808
+ provider = "${ds.provider}"
809
+ url = ${url}
810
+ }`
811
+ );
812
+ }
813
+ for (const gen of doc.generators) {
814
+ const lines = [` provider = "${gen.provider}"`];
815
+ if (gen.output) lines.push(` output = "${gen.output}"`);
816
+ for (const [k, v] of Object.entries(gen.config)) {
817
+ if (k === "provider" || k === "output") continue;
818
+ lines.push(` ${k} = "${v}"`);
819
+ }
820
+ blocks.push(`generator ${gen.name} {
821
+ ${lines.join("\n")}
822
+ }`);
823
+ }
824
+ for (const enumNode of doc.enums) {
825
+ blocks.push(printEnum(enumNode));
826
+ }
827
+ for (const model of doc.models) {
828
+ blocks.push(printModel(model));
829
+ }
830
+ return blocks.join("\n\n") + "\n";
831
+ }
832
+ function printEnum(node) {
833
+ const lines = [];
834
+ if (node.documentation) lines.push(...docLines(node.documentation));
835
+ lines.push(`enum ${node.name} {`);
836
+ for (const v of node.values) {
837
+ lines.push(` ${v.name}${v.dbName ? ` @map("${v.dbName}")` : ""}`);
838
+ }
839
+ if (node.dbName) lines.push(`
840
+ @@map("${node.dbName}")`);
841
+ lines.push(`}`);
842
+ return lines.join("\n");
843
+ }
844
+ function printModel(model) {
845
+ const lines = [];
846
+ if (model.documentation) lines.push(...docLines(model.documentation));
847
+ lines.push(`model ${model.name} {`);
848
+ const nameWidth = Math.max(...model.fields.map((f) => f.name.length), 0);
849
+ const typeWidth = Math.max(...model.fields.map((f) => fieldType(f).length), 0);
850
+ for (const field of model.fields) {
851
+ if (field.documentation) {
852
+ lines.push(...docLines(field.documentation).map((l) => ` ${l}`));
853
+ }
854
+ const attrs = fieldAttributes(field);
855
+ const name = field.name.padEnd(nameWidth);
856
+ const type = fieldType(field).padEnd(typeWidth);
857
+ lines.push(` ${name} ${type}${attrs ? ` ${attrs}` : ""}`.trimEnd());
858
+ }
859
+ const blockAttrs = modelBlockAttributes(model);
860
+ if (blockAttrs.length > 0) {
861
+ lines.push("");
862
+ for (const a of blockAttrs) lines.push(` ${a}`);
863
+ }
864
+ lines.push(`}`);
865
+ return lines.join("\n");
866
+ }
867
+ function fieldType(field) {
868
+ let t = field.type;
869
+ if (field.isList) t += "[]";
870
+ else if (!field.isRequired) t += "?";
871
+ return t;
872
+ }
873
+ function fieldAttributes(field) {
874
+ const parts = [];
875
+ if (field.isId) parts.push("@id");
876
+ if (field.isUnique) parts.push("@unique");
877
+ if (field.default) parts.push(`@default(${printDefault(field.default)})`);
878
+ if (field.isUpdatedAt) parts.push("@updatedAt");
879
+ if (field.relation) {
880
+ const rel2 = field.relation;
881
+ const args = [];
882
+ if (rel2.name) args.push(`"${rel2.name}"`);
883
+ if (rel2.fields?.length) args.push(`fields: [${rel2.fields.join(", ")}]`);
884
+ if (rel2.references?.length)
885
+ args.push(`references: [${rel2.references.join(", ")}]`);
886
+ if (rel2.onDelete) args.push(`onDelete: ${rel2.onDelete}`);
887
+ if (rel2.onUpdate) args.push(`onUpdate: ${rel2.onUpdate}`);
888
+ parts.push(args.length ? `@relation(${args.join(", ")})` : "@relation");
889
+ }
890
+ if (field.nativeType) {
891
+ const a = field.nativeType.args.length ? `(${field.nativeType.args.join(", ")})` : "";
892
+ parts.push(`@db.${field.nativeType.name}${a}`);
893
+ }
894
+ if (field.dbName) parts.push(`@map("${field.dbName}")`);
895
+ return parts.join(" ");
896
+ }
897
+ function printDefault(def) {
898
+ if (def.function) {
899
+ return `${def.function.name}(${def.function.args.map(printArgValue).join(", ")})`;
900
+ }
901
+ if (typeof def.literal === "string") {
902
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(def.literal) ? def.literal : `"${def.literal}"`;
903
+ }
904
+ return String(def.literal);
905
+ }
906
+ function printArgValue(v) {
907
+ switch (v.kind) {
908
+ case "string":
909
+ return `"${v.value}"`;
910
+ case "number":
911
+ return String(v.value);
912
+ case "boolean":
913
+ return String(v.value);
914
+ case "ref":
915
+ return v.value;
916
+ case "array":
917
+ return `[${v.items.map(printArgValue).join(", ")}]`;
918
+ case "function":
919
+ return `${v.name}(${v.args.map(printArgValue).join(", ")})`;
920
+ }
921
+ }
922
+ function modelBlockAttributes(model) {
923
+ const out = [];
924
+ const inlineId = model.primaryKey.length === 1 && model.fields.find((f) => f.name === model.primaryKey[0])?.isId;
925
+ if (model.primaryKey.length > 0 && !inlineId) {
926
+ out.push(`@@id([${model.primaryKey.join(", ")}])`);
927
+ }
928
+ for (const u of model.uniqueIndexes) {
929
+ out.push(
930
+ `@@unique([${u.fields.join(", ")}]${u.name ? `, map: "${u.name}"` : ""})`
931
+ );
932
+ }
933
+ for (const i of model.indexes) {
934
+ out.push(
935
+ `@@index([${i.fields.join(", ")}]${i.name ? `, map: "${i.name}"` : ""})`
936
+ );
937
+ }
938
+ if (model.dbName) out.push(`@@map("${model.dbName}")`);
939
+ return out;
940
+ }
941
+ function docLines(documentation) {
942
+ return documentation.split("\n").map((l) => `/// ${l}`);
943
+ }
944
+
945
+ // src/utils/index.ts
946
+ function uniq(items) {
947
+ return [...new Set(items)];
948
+ }
949
+ function pascalCase(input) {
950
+ return input.toLowerCase().replace(
951
+ /[^a-zA-Z0-9]+(.)?/g,
952
+ (_, c) => c ? c.toUpperCase() : ""
953
+ ).replace(/^(.)/, (_, c) => c.toUpperCase());
954
+ }
955
+ function camelCase(input) {
956
+ const pascal = pascalCase(input);
957
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
958
+ }
959
+ function pluralize(word) {
960
+ if (/[^aeiou]y$/i.test(word)) return word.replace(/y$/i, "ies");
961
+ if (/(s|x|z|ch|sh)$/i.test(word)) return `${word}es`;
962
+ return `${word}s`;
963
+ }
964
+ function lowerFirst(input) {
965
+ return input.charAt(0).toLowerCase() + input.slice(1);
966
+ }
967
+
968
+ // src/schema/relations-complete.ts
969
+ function completeRelations(doc) {
970
+ const original = [];
971
+ for (const model of doc.models) {
972
+ for (const field of model.fields) {
973
+ if (field.kind === "object") original.push({ model, field });
974
+ }
975
+ }
976
+ for (const { model, field } of original) {
977
+ const related = findModel(doc, field.type);
978
+ if (!related) continue;
979
+ if (hasPartner(related, model, field)) continue;
980
+ if (field.isList) {
981
+ addOwningSide(related, model, field);
982
+ } else if (field.relation?.fields?.length) {
983
+ addBackRelation(related, model, field);
984
+ } else {
985
+ upgradeBareToOne(doc, model, field);
986
+ }
987
+ }
988
+ return doc;
989
+ }
990
+ function hasPartner(related, model, field) {
991
+ return related.fields.some(
992
+ (f) => f !== field && f.kind === "object" && f.type === model.name && relationNamesMatch(f, field)
993
+ );
994
+ }
995
+ function relationNamesMatch(a, b) {
996
+ const an = a.relation?.name;
997
+ const bn = b.relation?.name;
998
+ if (an && bn) return an === bn;
999
+ if (!an && !bn) return true;
1000
+ return false;
1001
+ }
1002
+ function addBackRelation(related, owner, owning) {
1003
+ const fkFields = (owning.relation?.fields ?? []).map((n) => owner.fields.find((f) => f.name === n)).filter((f) => !!f);
1004
+ const isOneToOne = fkFields.length > 0 && fkFields.every((f) => f.isUnique);
1005
+ const baseName = isOneToOne ? camelCase(owner.name) : camelCase(pluralize(owner.name));
1006
+ const name = uniqueFieldName(related, baseName);
1007
+ related.fields.push({
1008
+ name,
1009
+ type: owner.name,
1010
+ kind: "object",
1011
+ isList: !isOneToOne,
1012
+ isRequired: false,
1013
+ isId: false,
1014
+ isUnique: false,
1015
+ isUpdatedAt: false,
1016
+ // Only emit @relation when the relation is named; an unnamed back-relation
1017
+ // carries no attribute (matches Prisma's formatter output).
1018
+ ...owning.relation?.name ? { relation: { name: owning.relation.name } } : {}
1019
+ });
1020
+ }
1021
+ function addOwningSide(related, parent, listField) {
1022
+ const ref = idFields(parent)[0];
1023
+ if (!ref) return;
1024
+ const relationField = uniqueFieldName(related, camelCase(parent.name));
1025
+ const fkName = uniqueFieldName(
1026
+ related,
1027
+ `${camelCase(parent.name)}${pascalCase(ref.name)}`
1028
+ );
1029
+ related.fields.push({
1030
+ name: fkName,
1031
+ type: ref.type,
1032
+ kind: "scalar",
1033
+ isList: false,
1034
+ isRequired: true,
1035
+ isId: false,
1036
+ isUnique: false,
1037
+ isUpdatedAt: false,
1038
+ ...ref.nativeType ? { nativeType: ref.nativeType } : {}
1039
+ });
1040
+ related.fields.push({
1041
+ name: relationField,
1042
+ type: parent.name,
1043
+ kind: "object",
1044
+ isList: false,
1045
+ isRequired: true,
1046
+ isId: false,
1047
+ isUnique: false,
1048
+ isUpdatedAt: false,
1049
+ relation: {
1050
+ ...listField.relation?.name ? { name: listField.relation.name } : {},
1051
+ fields: [fkName],
1052
+ references: [ref.name]
1053
+ }
1054
+ });
1055
+ }
1056
+ function upgradeBareToOne(doc, model, field) {
1057
+ const related = findModel(doc, field.type);
1058
+ if (!related) return;
1059
+ const ref = idFields(related)[0];
1060
+ if (!ref) return;
1061
+ const fkName = uniqueFieldName(model, `${field.name}${pascalCase(ref.name)}`);
1062
+ const idx = model.fields.indexOf(field);
1063
+ model.fields.splice(idx, 0, {
1064
+ name: fkName,
1065
+ type: ref.type,
1066
+ kind: "scalar",
1067
+ isList: false,
1068
+ isRequired: field.isRequired,
1069
+ isId: false,
1070
+ isUnique: false,
1071
+ isUpdatedAt: false,
1072
+ ...ref.nativeType ? { nativeType: ref.nativeType } : {}
1073
+ });
1074
+ field.relation = {
1075
+ ...field.relation ?? {},
1076
+ fields: [fkName],
1077
+ references: [ref.name]
1078
+ };
1079
+ addBackRelation(related, model, field);
1080
+ }
1081
+ function uniqueFieldName(model, base) {
1082
+ let name = base || "relation";
1083
+ let i = 1;
1084
+ while (model.fields.some((f) => f.name === name)) name = `${base}_${++i}`;
1085
+ return name;
1086
+ }
1087
+
1088
+ // src/schema/index.ts
1089
+ function formatSchema(source, file) {
1090
+ const doc = completeRelations(parseSchema(source, file));
1091
+ validateSchema(doc);
1092
+ return printSchema(doc);
1093
+ }
1094
+ function parseSchema(source, file) {
1095
+ return new Parser(source, file).parse();
1096
+ }
1097
+ function parseAndValidate(source, file) {
1098
+ const doc = parseSchema(source, file);
1099
+ validateSchema(doc);
1100
+ return doc;
1101
+ }
1102
+ var DEFAULT_SCHEMA_PATHS = [
1103
+ "ember/schema.ember",
1104
+ "schema.ember",
1105
+ "prisma/schema.ember"
1106
+ ];
1107
+ function findSchemaPath(base = process.cwd(), explicit) {
1108
+ if (explicit) {
1109
+ const p = resolve(base, explicit);
1110
+ return existsSync(p) ? p : void 0;
1111
+ }
1112
+ for (const candidate of DEFAULT_SCHEMA_PATHS) {
1113
+ const p = resolve(base, candidate);
1114
+ if (existsSync(p)) return p;
1115
+ }
1116
+ return void 0;
1117
+ }
1118
+ function loadSchema(path) {
1119
+ const source = readFileSync(path, "utf8");
1120
+ const document = parseAndValidate(source, path);
1121
+ return {
1122
+ document,
1123
+ path,
1124
+ databaseUrl: resolveDatasourceUrl(document, dirname(path))
1125
+ };
1126
+ }
1127
+ function resolveDatasourceUrl(doc, _base) {
1128
+ const ds = doc.datasource;
1129
+ if (!ds) return void 0;
1130
+ if (ds.url.kind === "literal") return ds.url.value;
1131
+ return process.env[ds.url.value];
1132
+ }
1133
+
1134
+ // src/driver/firebird-driver.ts
1135
+ import { AsyncLocalStorage } from "async_hooks";
1136
+ import Firebird from "node-firebird";
1137
+ var fb = Firebird;
1138
+ function isolationConstant(level) {
1139
+ switch (level) {
1140
+ case "READ_COMMITTED_READ_ONLY":
1141
+ return fb.ISOLATION_READ_COMMITTED_READ_ONLY;
1142
+ case "REPEATABLE_READ":
1143
+ return fb.ISOLATION_REPEATABLE_READ;
1144
+ case "SERIALIZABLE":
1145
+ return fb.ISOLATION_SERIALIZABLE;
1146
+ case "READ_COMMITTED":
1147
+ default:
1148
+ return fb.ISOLATION_READ_COMMITTED;
1149
+ }
1150
+ }
1151
+ var FirebirdDriver = class {
1152
+ pool = null;
1153
+ options;
1154
+ poolMax;
1155
+ onQuery;
1156
+ activeTx = new AsyncLocalStorage();
1157
+ constructor(config, driverOptions) {
1158
+ this.poolMax = config.poolMax ?? 5;
1159
+ this.onQuery = driverOptions?.onQuery;
1160
+ this.options = {
1161
+ host: config.host,
1162
+ port: config.port,
1163
+ database: config.database,
1164
+ user: config.user,
1165
+ password: config.password,
1166
+ role: config.role ?? "",
1167
+ pageSize: config.pageSize ?? 4096,
1168
+ encoding: config.encoding ?? "UTF8",
1169
+ blobAsText: config.blobAsText ?? true,
1170
+ lowercase_keys: config.lowercaseKeys ?? false,
1171
+ retryConnectionInterval: 1e3,
1172
+ // FB3+ secure auth (Srp) is negotiated by default; set explicitly to force
1173
+ // a plugin, or "Legacy_Auth" for Firebird 2.1/2.5 servers.
1174
+ ...config.authPlugin ? { pluginName: config.authPlugin } : {},
1175
+ ...config.wireCompression != null ? { wireCompression: config.wireCompression } : {}
1176
+ };
1177
+ }
1178
+ async connect() {
1179
+ if (this.pool) return;
1180
+ this.pool = fb.pool(this.poolMax, this.options);
1181
+ }
1182
+ async disconnect() {
1183
+ if (!this.pool) return;
1184
+ this.pool.destroy();
1185
+ this.pool = null;
1186
+ }
1187
+ async transaction(fn, options) {
1188
+ const existing = this.activeTx.getStore();
1189
+ if (existing) {
1190
+ return fn(existing);
1191
+ }
1192
+ await this.connect();
1193
+ const db = await this.acquire();
1194
+ const tr = await this.begin(db, options?.isolation);
1195
+ const ctx = {
1196
+ query: (sql, params) => this.runOnTransaction(tr, sql, params)
1197
+ };
1198
+ try {
1199
+ const result = await this.activeTx.run(ctx, () => fn(ctx));
1200
+ await this.commit(tr);
1201
+ return result;
1202
+ } catch (err) {
1203
+ await this.safeRollback(tr);
1204
+ throw err;
1205
+ } finally {
1206
+ db.detach();
1207
+ }
1208
+ }
1209
+ // ---- promise wrappers over node-firebird -------------------------------
1210
+ acquire() {
1211
+ return new Promise((resolve4, reject) => {
1212
+ this.pool.get((err, db) => {
1213
+ if (err) return reject(wrap(err, "Failed to acquire connection"));
1214
+ resolve4(db);
1215
+ });
1216
+ });
1217
+ }
1218
+ begin(db, isolation) {
1219
+ return new Promise((resolve4, reject) => {
1220
+ db.transaction(isolationConstant(isolation), (err, tr) => {
1221
+ if (err) return reject(wrap(err, "Failed to start transaction"));
1222
+ resolve4(tr);
1223
+ });
1224
+ });
1225
+ }
1226
+ runOnTransaction(tr, sql, params = []) {
1227
+ const start = this.onQuery ? performance.now() : 0;
1228
+ return new Promise((resolve4, reject) => {
1229
+ tr.query(sql, [...params], (err, result) => {
1230
+ if (err) return reject(wrap(err, "Query failed", sql));
1231
+ const rows = normalizeRows(result);
1232
+ if (this.onQuery) {
1233
+ this.onQuery({
1234
+ sql,
1235
+ params,
1236
+ durationMs: performance.now() - start,
1237
+ rowCount: rows.length
1238
+ });
1239
+ }
1240
+ resolve4(rows);
1241
+ });
1242
+ });
1243
+ }
1244
+ commit(tr) {
1245
+ return new Promise((resolve4, reject) => {
1246
+ tr.commit((err) => {
1247
+ if (err) return reject(wrap(err, "Failed to commit transaction"));
1248
+ resolve4();
1249
+ });
1250
+ });
1251
+ }
1252
+ safeRollback(tr) {
1253
+ return new Promise((resolve4) => {
1254
+ tr.rollback(() => resolve4());
1255
+ });
1256
+ }
1257
+ };
1258
+ function normalizeRows(result) {
1259
+ if (Array.isArray(result)) return result;
1260
+ if (result === void 0 || result === null) return [];
1261
+ return [result];
1262
+ }
1263
+ function wrap(err, message, sql) {
1264
+ const detail = err && typeof err === "object" && "message" in err ? String(err.message) : String(err);
1265
+ return new DatabaseError(`${message}: ${detail}`, err, sql);
1266
+ }
1267
+
1268
+ // src/driver/url.ts
1269
+ function parseConnectionUrl(url) {
1270
+ let parsed;
1271
+ try {
1272
+ parsed = new URL(url);
1273
+ } catch {
1274
+ throw new EmberError(`Invalid Firebird connection URL: ${url}`);
1275
+ }
1276
+ if (!/^firebird:?$/.test(parsed.protocol.replace(":", "") + ":")) {
1277
+ if (parsed.protocol !== "firebird:") {
1278
+ throw new EmberError(
1279
+ `Unsupported protocol '${parsed.protocol}'. Expected 'firebird:'.`
1280
+ );
1281
+ }
1282
+ }
1283
+ const database = normalizeDatabasePath(parsed.pathname);
1284
+ if (!database) {
1285
+ throw new EmberError(`Connection URL is missing a database path: ${url}`);
1286
+ }
1287
+ const params = parsed.searchParams;
1288
+ const config = {
1289
+ host: parsed.hostname || "127.0.0.1",
1290
+ port: parsed.port ? Number(parsed.port) : 3050,
1291
+ database,
1292
+ user: decodeURIComponent(parsed.username || "SYSDBA"),
1293
+ password: decodeURIComponent(parsed.password || "masterkey"),
1294
+ encoding: params.get("encoding") ?? "UTF8"
1295
+ };
1296
+ const role = params.get("role");
1297
+ if (role) config.role = role;
1298
+ const poolMax = params.get("poolMax") ?? params.get("connection_limit");
1299
+ if (poolMax) config.poolMax = Number(poolMax);
1300
+ const pageSize = params.get("pageSize");
1301
+ if (pageSize) config.pageSize = Number(pageSize);
1302
+ const auth = (params.get("authPlugin") ?? params.get("auth"))?.toLowerCase();
1303
+ if (auth === "legacy" || auth === "legacy_auth") config.authPlugin = "Legacy_Auth";
1304
+ else if (auth === "srp") config.authPlugin = "Srp";
1305
+ const wireCompression = params.get("wireCompression");
1306
+ if (wireCompression != null) {
1307
+ config.wireCompression = wireCompression !== "false" && wireCompression !== "0";
1308
+ }
1309
+ const version = params.get("version");
1310
+ if (version) config.version = normalizeVersion(version);
1311
+ return config;
1312
+ }
1313
+ var VERSIONS = /* @__PURE__ */ new Set(["2.1", "2.5", "3", "4", "5"]);
1314
+ function normalizeVersion(raw) {
1315
+ const trimmed = raw.trim();
1316
+ if (VERSIONS.has(trimmed)) return trimmed;
1317
+ if (/^2\.1/.test(trimmed)) return "2.1";
1318
+ if (/^2\.5/.test(trimmed)) return "2.5";
1319
+ const major = trimmed.split(".")[0];
1320
+ if (major && VERSIONS.has(major)) return major;
1321
+ return void 0;
1322
+ }
1323
+ function normalizeDatabasePath(pathname) {
1324
+ let p = decodeURIComponent(pathname);
1325
+ if (p.startsWith("//")) p = p.slice(1);
1326
+ if (/^\/[A-Za-z]:\//.test(p)) p = p.slice(1);
1327
+ return p;
1328
+ }
1329
+
1330
+ // src/driver/index.ts
1331
+ function createDriver(source, options) {
1332
+ const config = typeof source === "string" ? parseConnectionUrl(source) : source;
1333
+ return new FirebirdDriver(config, options);
1334
+ }
1335
+
1336
+ // src/sql/dialect.ts
1337
+ var FirebirdDialect = class {
1338
+ supportsReturning = true;
1339
+ version;
1340
+ supportsBooleanType;
1341
+ supportsIdentity;
1342
+ supportsWindowFunctions;
1343
+ constructor(options = {}) {
1344
+ this.version = options.version ?? "3";
1345
+ const rank = versionRank(this.version);
1346
+ this.supportsBooleanType = rank >= 30;
1347
+ this.supportsIdentity = rank >= 30;
1348
+ this.supportsWindowFunctions = rank >= 30;
1349
+ }
1350
+ booleanColumnType() {
1351
+ return this.supportsBooleanType ? "BOOLEAN" : "SMALLINT";
1352
+ }
1353
+ quoteId(name) {
1354
+ return `"${name.replace(/"/g, '""')}"`;
1355
+ }
1356
+ quoteRef(table, column) {
1357
+ return `${this.quoteId(table)}.${this.quoteId(column)}`;
1358
+ }
1359
+ paginationClause(take, skip) {
1360
+ const parts = [];
1361
+ if (typeof take === "number" && take >= 0) {
1362
+ parts.push(`FIRST ${Math.trunc(take)}`);
1363
+ }
1364
+ if (typeof skip === "number" && skip > 0) {
1365
+ parts.push(`SKIP ${Math.trunc(skip)}`);
1366
+ }
1367
+ return parts.join(" ");
1368
+ }
1369
+ caseInsensitive(expr) {
1370
+ return `UPPER(${expr})`;
1371
+ }
1372
+ defaultFunctionSql(name) {
1373
+ switch (name) {
1374
+ case "now":
1375
+ return "CURRENT_TIMESTAMP";
1376
+ case "uuid":
1377
+ case "cuid":
1378
+ return "UUID_TO_CHAR(GEN_UUID())";
1379
+ case "autoincrement":
1380
+ return null;
1381
+ // handled via generators/identity, not an inline default
1382
+ default:
1383
+ return null;
1384
+ }
1385
+ }
1386
+ coerceValue(value) {
1387
+ if (value === null || value === void 0) return null;
1388
+ if (value instanceof Date) return value;
1389
+ if (Buffer.isBuffer(value)) return value;
1390
+ if (typeof value === "boolean") {
1391
+ return this.supportsBooleanType ? value : value ? 1 : 0;
1392
+ }
1393
+ if (typeof value === "bigint") return value;
1394
+ if (typeof value === "number") return value;
1395
+ if (typeof value === "string") return value;
1396
+ return JSON.stringify(value);
1397
+ }
1398
+ };
1399
+ function versionRank(version) {
1400
+ switch (version) {
1401
+ case "2.1":
1402
+ return 21;
1403
+ case "2.5":
1404
+ return 25;
1405
+ case "3":
1406
+ return 30;
1407
+ case "4":
1408
+ return 40;
1409
+ case "5":
1410
+ return 50;
1411
+ default:
1412
+ return 30;
1413
+ }
1414
+ }
1415
+
1416
+ // src/introspect/firebird-meta.ts
1417
+ var trim = (v) => v == null ? "" : String(v).trim();
1418
+ var numOrNull = (v) => v == null ? null : Number(v);
1419
+ var FirebirdMetadataReader = class {
1420
+ constructor(tx) {
1421
+ this.tx = tx;
1422
+ }
1423
+ tx;
1424
+ async tables() {
1425
+ const rows = await this.tx.query(
1426
+ `SELECT RDB$RELATION_NAME
1427
+ FROM RDB$RELATIONS
1428
+ WHERE RDB$VIEW_BLR IS NULL
1429
+ AND (RDB$SYSTEM_FLAG IS NULL OR RDB$SYSTEM_FLAG = 0)
1430
+ ORDER BY RDB$RELATION_NAME`
1431
+ );
1432
+ return rows.map((r) => trim(r["RDB$RELATION_NAME"]));
1433
+ }
1434
+ async columns() {
1435
+ const rows = await this.tx.query(
1436
+ `SELECT
1437
+ rf.RDB$RELATION_NAME AS TABLE_NAME,
1438
+ rf.RDB$FIELD_NAME AS FIELD_NAME,
1439
+ rf.RDB$FIELD_POSITION AS FIELD_POSITION,
1440
+ rf.RDB$NULL_FLAG AS NULL_FLAG,
1441
+ rf.RDB$DEFAULT_SOURCE AS DEFAULT_SOURCE,
1442
+ rf.RDB$IDENTITY_TYPE AS IDENTITY_TYPE,
1443
+ f.RDB$FIELD_TYPE AS FIELD_TYPE,
1444
+ f.RDB$FIELD_SUB_TYPE AS FIELD_SUB_TYPE,
1445
+ f.RDB$FIELD_LENGTH AS FIELD_LENGTH,
1446
+ f.RDB$CHARACTER_LENGTH AS CHAR_LENGTH,
1447
+ f.RDB$FIELD_PRECISION AS FIELD_PRECISION,
1448
+ f.RDB$FIELD_SCALE AS FIELD_SCALE
1449
+ FROM RDB$RELATION_FIELDS rf
1450
+ JOIN RDB$FIELDS f ON f.RDB$FIELD_NAME = rf.RDB$FIELD_SOURCE
1451
+ JOIN RDB$RELATIONS r ON r.RDB$RELATION_NAME = rf.RDB$RELATION_NAME
1452
+ WHERE r.RDB$VIEW_BLR IS NULL
1453
+ AND (r.RDB$SYSTEM_FLAG IS NULL OR r.RDB$SYSTEM_FLAG = 0)
1454
+ ORDER BY rf.RDB$RELATION_NAME, rf.RDB$FIELD_POSITION`
1455
+ );
1456
+ return rows.map((r) => ({
1457
+ table: trim(r.TABLE_NAME),
1458
+ name: trim(r.FIELD_NAME),
1459
+ position: Number(r.FIELD_POSITION ?? 0),
1460
+ fieldType: Number(r.FIELD_TYPE ?? 0),
1461
+ fieldSubType: numOrNull(r.FIELD_SUB_TYPE),
1462
+ length: numOrNull(r.FIELD_LENGTH),
1463
+ charLength: numOrNull(r.CHAR_LENGTH),
1464
+ precision: numOrNull(r.FIELD_PRECISION),
1465
+ scale: numOrNull(r.FIELD_SCALE),
1466
+ notNull: r.NULL_FLAG != null && Number(r.NULL_FLAG) === 1,
1467
+ defaultSource: r.DEFAULT_SOURCE == null ? null : trim(r.DEFAULT_SOURCE),
1468
+ isIdentity: r.IDENTITY_TYPE != null
1469
+ }));
1470
+ }
1471
+ async constraints() {
1472
+ const rows = await this.tx.query(
1473
+ `SELECT
1474
+ rc.RDB$CONSTRAINT_NAME AS CONSTRAINT_NAME,
1475
+ rc.RDB$CONSTRAINT_TYPE AS CONSTRAINT_TYPE,
1476
+ rc.RDB$RELATION_NAME AS TABLE_NAME,
1477
+ rc.RDB$INDEX_NAME AS INDEX_NAME,
1478
+ seg.RDB$FIELD_NAME AS FIELD_NAME,
1479
+ seg.RDB$FIELD_POSITION AS SEG_POSITION,
1480
+ refc.RDB$UPDATE_RULE AS UPDATE_RULE,
1481
+ refc.RDB$DELETE_RULE AS DELETE_RULE,
1482
+ refc.RDB$CONST_NAME_UQ AS UQ_NAME
1483
+ FROM RDB$RELATION_CONSTRAINTS rc
1484
+ JOIN RDB$INDEX_SEGMENTS seg ON seg.RDB$INDEX_NAME = rc.RDB$INDEX_NAME
1485
+ LEFT JOIN RDB$REF_CONSTRAINTS refc ON refc.RDB$CONSTRAINT_NAME = rc.RDB$CONSTRAINT_NAME
1486
+ WHERE rc.RDB$CONSTRAINT_TYPE IN ('PRIMARY KEY', 'UNIQUE', 'FOREIGN KEY')
1487
+ ORDER BY rc.RDB$CONSTRAINT_NAME, seg.RDB$FIELD_POSITION`
1488
+ );
1489
+ const byName = /* @__PURE__ */ new Map();
1490
+ const uqByName = /* @__PURE__ */ new Map();
1491
+ for (const r of rows) {
1492
+ const name = trim(r.CONSTRAINT_NAME);
1493
+ let c = byName.get(name);
1494
+ if (!c) {
1495
+ c = {
1496
+ table: trim(r.TABLE_NAME),
1497
+ type: trim(r.CONSTRAINT_TYPE),
1498
+ name,
1499
+ indexName: r.INDEX_NAME == null ? null : trim(r.INDEX_NAME),
1500
+ columns: []
1501
+ };
1502
+ if (c.type === "FOREIGN KEY") {
1503
+ c.updateRule = trim(r.UPDATE_RULE) || void 0;
1504
+ c.deleteRule = trim(r.DELETE_RULE) || void 0;
1505
+ uqByName.set(name, trim(r.UQ_NAME));
1506
+ }
1507
+ byName.set(name, c);
1508
+ }
1509
+ const col = trim(r.FIELD_NAME);
1510
+ if (col && !c.columns.includes(col)) c.columns.push(col);
1511
+ }
1512
+ for (const c of byName.values()) {
1513
+ if (c.type !== "FOREIGN KEY") continue;
1514
+ const uqName = uqByName.get(c.name);
1515
+ if (!uqName) continue;
1516
+ const target = byName.get(uqName);
1517
+ if (target) {
1518
+ c.references = { table: target.table, columns: [...target.columns] };
1519
+ } else {
1520
+ const resolved = await this.resolveConstraintColumns(uqName);
1521
+ if (resolved) c.references = resolved;
1522
+ }
1523
+ }
1524
+ return [...byName.values()];
1525
+ }
1526
+ async resolveConstraintColumns(constraintName2) {
1527
+ const rows = await this.tx.query(
1528
+ `SELECT rc.RDB$RELATION_NAME AS TABLE_NAME, seg.RDB$FIELD_NAME AS FIELD_NAME
1529
+ FROM RDB$RELATION_CONSTRAINTS rc
1530
+ JOIN RDB$INDEX_SEGMENTS seg ON seg.RDB$INDEX_NAME = rc.RDB$INDEX_NAME
1531
+ WHERE rc.RDB$CONSTRAINT_NAME = ?
1532
+ ORDER BY seg.RDB$FIELD_POSITION`,
1533
+ [constraintName2]
1534
+ );
1535
+ if (rows.length === 0) return null;
1536
+ return {
1537
+ table: trim(rows[0].TABLE_NAME),
1538
+ columns: rows.map((r) => trim(r.FIELD_NAME))
1539
+ };
1540
+ }
1541
+ };
1542
+
1543
+ // src/introspect/type-map.ts
1544
+ var FB = {
1545
+ SMALLINT: 7,
1546
+ INTEGER: 8,
1547
+ QUAD: 9,
1548
+ FLOAT: 10,
1549
+ DATE_LEGACY: 11,
1550
+ DATE: 12,
1551
+ TIME: 13,
1552
+ CHAR: 14,
1553
+ INT64: 16,
1554
+ // BIGINT / numeric
1555
+ BOOLEAN: 23,
1556
+ DOUBLE: 27,
1557
+ TIMESTAMP: 35,
1558
+ VARCHAR: 37,
1559
+ BLOB: 261
1560
+ };
1561
+ function mapColumnType(col) {
1562
+ const scale = col.scale ?? 0;
1563
+ switch (col.fieldType) {
1564
+ case FB.SMALLINT:
1565
+ return scale < 0 ? decimal(col) : { scalar: "Int", native: { name: "SmallInt", args: [] } };
1566
+ case FB.INTEGER:
1567
+ return scale < 0 ? decimal(col) : { scalar: "Int", native: { name: "Integer", args: [] } };
1568
+ case FB.INT64:
1569
+ return scale < 0 ? decimal(col) : { scalar: "BigInt", native: { name: "BigInt", args: [] } };
1570
+ case FB.FLOAT:
1571
+ return { scalar: "Float", native: { name: "Float", args: [] } };
1572
+ case FB.DOUBLE:
1573
+ return { scalar: "Float", native: { name: "DoublePrecision", args: [] } };
1574
+ case FB.BOOLEAN:
1575
+ return { scalar: "Boolean", native: { name: "Boolean", args: [] } };
1576
+ case FB.CHAR:
1577
+ return {
1578
+ scalar: "String",
1579
+ native: { name: "Char", args: col.charLength ? [col.charLength] : [] }
1580
+ };
1581
+ case FB.VARCHAR:
1582
+ return {
1583
+ scalar: "String",
1584
+ native: { name: "VarChar", args: col.charLength ? [col.charLength] : [] }
1585
+ };
1586
+ case FB.DATE:
1587
+ case FB.DATE_LEGACY:
1588
+ return { scalar: "DateTime", native: { name: "Date", args: [] } };
1589
+ case FB.TIME:
1590
+ return { scalar: "DateTime", native: { name: "Time", args: [] } };
1591
+ case FB.TIMESTAMP:
1592
+ return { scalar: "DateTime", native: { name: "Timestamp", args: [] } };
1593
+ case FB.BLOB:
1594
+ return col.fieldSubType === 1 ? { scalar: "String", native: { name: "Text", args: [] } } : { scalar: "Bytes", native: { name: "Blob", args: [] } };
1595
+ default:
1596
+ return { scalar: "String" };
1597
+ }
1598
+ }
1599
+ function decimal(col) {
1600
+ const precision = col.precision ?? 18;
1601
+ const scaleAbs = Math.abs(col.scale ?? 0);
1602
+ return {
1603
+ scalar: "Decimal",
1604
+ native: { name: "Decimal", args: [precision, scaleAbs] }
1605
+ };
1606
+ }
1607
+
1608
+ // src/introspect/index.ts
1609
+ var Introspector = class {
1610
+ constructor(driver) {
1611
+ this.driver = driver;
1612
+ }
1613
+ driver;
1614
+ async introspect(options = {}) {
1615
+ const { tables, columns, constraints } = await this.driver.transaction(
1616
+ async (tx) => {
1617
+ const reader = new FirebirdMetadataReader(tx);
1618
+ return {
1619
+ tables: await reader.tables(),
1620
+ columns: await reader.columns(),
1621
+ constraints: await reader.constraints()
1622
+ };
1623
+ },
1624
+ { isolation: "READ_COMMITTED_READ_ONLY" }
1625
+ );
1626
+ const columnsByTable = groupBy(columns, (c) => c.table);
1627
+ const constraintsByTable = groupBy(constraints, (c) => c.table);
1628
+ const modelNames = buildNameMap(tables);
1629
+ const models = tables.map(
1630
+ (table) => this.buildModel(
1631
+ table,
1632
+ modelNames,
1633
+ columnsByTable.get(table) ?? [],
1634
+ constraintsByTable.get(table) ?? []
1635
+ )
1636
+ );
1637
+ addRelations(models, modelNames, constraints);
1638
+ const enums = [];
1639
+ const doc = {
1640
+ generators: [
1641
+ { name: "client", provider: "ember-client-js", output: "../generated", config: {} }
1642
+ ],
1643
+ models,
1644
+ enums
1645
+ };
1646
+ if (options.datasource) {
1647
+ doc.datasource = {
1648
+ name: options.datasource.name,
1649
+ provider: options.datasource.provider,
1650
+ url: options.datasource.envVar ? { kind: "env", value: options.datasource.envVar } : { kind: "literal", value: options.datasource.url ?? "" }
1651
+ };
1652
+ }
1653
+ return doc;
1654
+ }
1655
+ buildModel(table, modelNames, columns, constraints) {
1656
+ const modelName = modelNames.get(table);
1657
+ const pk = constraints.find((c) => c.type === "PRIMARY KEY");
1658
+ const uniques = constraints.filter((c) => c.type === "UNIQUE");
1659
+ const fieldNameMap = buildNameMap(columns.map((c) => c.name), camelCase);
1660
+ const fields = columns.map(
1661
+ (col) => buildField(col, fieldNameMap, pk, uniques)
1662
+ );
1663
+ const model = {
1664
+ name: modelName,
1665
+ dbName: modelName !== table ? table : void 0,
1666
+ fields,
1667
+ primaryKey: pk && pk.columns.length > 1 ? pk.columns.map((c) => fieldNameMap.get(c)) : [],
1668
+ uniqueIndexes: uniques.filter((u) => u.columns.length > 1).map((u) => ({ fields: u.columns.map((c) => fieldNameMap.get(c)) })),
1669
+ indexes: []
1670
+ };
1671
+ return model;
1672
+ }
1673
+ };
1674
+ function buildField(col, fieldNameMap, pk, uniques) {
1675
+ const fieldName = fieldNameMap.get(col.name);
1676
+ const mapped = mapColumnType(col);
1677
+ const isSingleIdCol = !!pk && pk.columns.length === 1 && pk.columns[0] === col.name;
1678
+ const isSingleUnique = uniques.some(
1679
+ (u) => u.columns.length === 1 && u.columns[0] === col.name
1680
+ );
1681
+ const field = {
1682
+ name: fieldName,
1683
+ type: mapped.scalar,
1684
+ kind: "scalar",
1685
+ isList: false,
1686
+ isRequired: col.notNull || isSingleIdCol,
1687
+ isId: isSingleIdCol,
1688
+ isUnique: isSingleUnique,
1689
+ isUpdatedAt: false,
1690
+ dbName: fieldName !== col.name ? col.name : void 0,
1691
+ nativeType: mapped.native,
1692
+ default: parseDefault(col)
1693
+ };
1694
+ return field;
1695
+ }
1696
+ function parseDefault(col) {
1697
+ if (col.isIdentity) return { function: { name: "autoincrement", args: [] } };
1698
+ if (!col.defaultSource) return void 0;
1699
+ const src = col.defaultSource.replace(/^DEFAULT\s+/i, "").trim();
1700
+ if (/^CURRENT_TIMESTAMP/i.test(src)) return { function: { name: "now", args: [] } };
1701
+ if (/^(TRUE|FALSE)$/i.test(src)) return { literal: /^TRUE$/i.test(src) };
1702
+ if (/^-?\d+(\.\d+)?$/.test(src)) return { literal: Number(src) };
1703
+ const str = /^'(.*)'$/.exec(src);
1704
+ if (str) return { literal: str[1] };
1705
+ return void 0;
1706
+ }
1707
+ function addRelations(models, modelNames, constraints) {
1708
+ const byName = new Map(models.map((m) => [m.name, m]));
1709
+ for (const fk of constraints) {
1710
+ if (fk.type !== "FOREIGN KEY" || !fk.references) continue;
1711
+ const childModel = byName.get(modelNames.get(fk.table));
1712
+ const parentModel = byName.get(modelNames.get(fk.references.table));
1713
+ if (!childModel || !parentModel) continue;
1714
+ const localFieldNames = fk.columns.map((c) => columnToField(childModel, c));
1715
+ const refFieldNames = fk.references.columns.map(
1716
+ (c) => columnToField(parentModel, c)
1717
+ );
1718
+ const childFieldName = uniqueFieldName2(childModel, lowerFirst(parentModel.name));
1719
+ childModel.fields.push({
1720
+ name: childFieldName,
1721
+ type: parentModel.name,
1722
+ kind: "object",
1723
+ isList: false,
1724
+ isRequired: localFieldNames.every(
1725
+ (n) => childModel.fields.find((f) => f.name === n)?.isRequired
1726
+ ),
1727
+ isId: false,
1728
+ isUnique: false,
1729
+ isUpdatedAt: false,
1730
+ relation: {
1731
+ fields: localFieldNames,
1732
+ references: refFieldNames,
1733
+ onDelete: mapRule(fk.deleteRule),
1734
+ onUpdate: mapRule(fk.updateRule)
1735
+ }
1736
+ });
1737
+ const backName = uniqueFieldName2(
1738
+ parentModel,
1739
+ lowerFirst(pluralize(childModel.name))
1740
+ );
1741
+ parentModel.fields.push({
1742
+ name: backName,
1743
+ type: childModel.name,
1744
+ kind: "object",
1745
+ isList: true,
1746
+ isRequired: false,
1747
+ isId: false,
1748
+ isUnique: false,
1749
+ isUpdatedAt: false
1750
+ });
1751
+ }
1752
+ }
1753
+ function columnToField(model, column) {
1754
+ const f = model.fields.find((x) => (x.dbName ?? x.name) === column);
1755
+ return f ? f.name : camelCase(column);
1756
+ }
1757
+ function uniqueFieldName2(model, base) {
1758
+ let name = base;
1759
+ let i = 1;
1760
+ while (model.fields.some((f) => f.name === name)) name = `${base}_${++i}`;
1761
+ return name;
1762
+ }
1763
+ function mapRule(rule) {
1764
+ switch ((rule ?? "").toUpperCase()) {
1765
+ case "CASCADE":
1766
+ return "Cascade";
1767
+ case "SET NULL":
1768
+ return "SetNull";
1769
+ case "SET DEFAULT":
1770
+ return "SetDefault";
1771
+ case "NO ACTION":
1772
+ return "NoAction";
1773
+ case "RESTRICT":
1774
+ return "Restrict";
1775
+ default:
1776
+ return void 0;
1777
+ }
1778
+ }
1779
+ function buildNameMap(dbNames, transform = pascalCase) {
1780
+ const map = /* @__PURE__ */ new Map();
1781
+ const used = /* @__PURE__ */ new Set();
1782
+ for (const dbName of uniq(dbNames)) {
1783
+ let name = transform(dbName);
1784
+ if (!name) name = dbName;
1785
+ let candidate = name;
1786
+ let i = 1;
1787
+ while (used.has(candidate)) candidate = `${name}_${++i}`;
1788
+ used.add(candidate);
1789
+ map.set(dbName, candidate);
1790
+ }
1791
+ return map;
1792
+ }
1793
+ function groupBy(items, key) {
1794
+ const map = /* @__PURE__ */ new Map();
1795
+ for (const item of items) {
1796
+ const k = key(item);
1797
+ const list = map.get(k) ?? [];
1798
+ list.push(item);
1799
+ map.set(k, list);
1800
+ }
1801
+ return map;
1802
+ }
1803
+
1804
+ // src/generator/index.ts
1805
+ import { mkdirSync, writeFileSync } from "fs";
1806
+ import { dirname as dirname2, resolve as resolve2 } from "path";
1807
+
1808
+ // src/generator/ts-types.ts
1809
+ var SCALAR_TS = {
1810
+ String: "string",
1811
+ Boolean: "boolean",
1812
+ Int: "number",
1813
+ BigInt: "bigint",
1814
+ Float: "number",
1815
+ Decimal: "number",
1816
+ DateTime: "Date",
1817
+ Bytes: "Buffer",
1818
+ Json: "JsonValue"
1819
+ };
1820
+ function baseTsType(field, schema) {
1821
+ if (field.kind === "enum") return field.type;
1822
+ if (field.kind === "object") return field.type;
1823
+ return SCALAR_TS[field.type] ?? "unknown";
1824
+ }
1825
+
1826
+ // src/generator/index.ts
1827
+ var ClientGenerator = class {
1828
+ constructor(schema) {
1829
+ this.schema = schema;
1830
+ }
1831
+ schema;
1832
+ generate() {
1833
+ const parts = [];
1834
+ parts.push(this.header());
1835
+ parts.push(this.enums());
1836
+ parts.push(this.modelTypes());
1837
+ parts.push(this.registry());
1838
+ parts.push(PAYLOAD_RESOLVER);
1839
+ parts.push(this.namespace());
1840
+ parts.push(this.clientClass());
1841
+ return parts.filter(Boolean).join("\n\n") + "\n";
1842
+ }
1843
+ // ---- header & shared ----------------------------------------------------
1844
+ header() {
1845
+ return `// AUTO-GENERATED by EmberORM. Do not edit by hand.
1846
+ /* eslint-disable */
1847
+ import { EmberClientBase } from "ember-orm/client";
1848
+ import type { ClientOptions } from "ember-orm/client";
1849
+
1850
+ export type JsonValue =
1851
+ | string
1852
+ | number
1853
+ | boolean
1854
+ | null
1855
+ | { [key: string]: JsonValue }
1856
+ | JsonValue[];
1857
+
1858
+ export type SortOrder = "asc" | "desc";
1859
+ export type QueryMode = "default" | "insensitive";
1860
+
1861
+ const schemaDocument = ${JSON.stringify(this.schema)} as unknown as ClientOptions["schema"];`;
1862
+ }
1863
+ enums() {
1864
+ return this.schema.enums.map((e) => this.enumType(e)).join("\n\n");
1865
+ }
1866
+ enumType(node) {
1867
+ const values = node.values.map((v) => ` ${v.name}: "${v.name}"`).join(",\n");
1868
+ return `export const ${node.name} = {
1869
+ ${values}
1870
+ } as const;
1871
+ export type ${node.name} = (typeof ${node.name})[keyof typeof ${node.name}];`;
1872
+ }
1873
+ modelTypes() {
1874
+ return this.schema.models.map((m) => this.modelType(m)).join("\n\n");
1875
+ }
1876
+ modelType(model) {
1877
+ const scalars = scalarFields(model).map((f) => ` ${f.name}: ${this.scalarType(f)};`).join("\n");
1878
+ const lines = [];
1879
+ lines.push(`export type ${model.name} = {
1880
+ ${scalars}
1881
+ };`);
1882
+ lines.push(`export type ${model.name}GetPayload<A> = $Payload<"${model.name}", A>;`);
1883
+ lines.push(this.fluentType(model));
1884
+ return lines.join("\n");
1885
+ }
1886
+ /**
1887
+ * Fluent-API return type for single-record reads: a Promise of the payload
1888
+ * that also exposes a method per relation (to-many -> array; to-one ->
1889
+ * chainable fluent).
1890
+ */
1891
+ fluentType(model) {
1892
+ const n = model.name;
1893
+ const methods = relationFields(model).map((f) => {
1894
+ if (f.isList) {
1895
+ return ` ${f.name}<S extends Prisma.${f.type}FindManyArgs = {}>(args?: S): Promise<${f.type}GetPayload<S>[]>;`;
1896
+ }
1897
+ return ` ${f.name}<S extends Prisma.${f.type}FindFirstArgs = {}>(args?: S): ${f.type}Fluent<S, null>;`;
1898
+ });
1899
+ return `export type ${n}Fluent<A, Null = null> = Promise<${n}GetPayload<A> | Null> & {
1900
+ ${methods.join("\n")}
1901
+ };`;
1902
+ }
1903
+ /**
1904
+ * Type-level registry powering recursive payload resolution: a model-name
1905
+ * union, a scalar-payload map, and a relation map carrying each relation's
1906
+ * target model, list-ness and nullability.
1907
+ */
1908
+ registry() {
1909
+ const names = this.schema.models.map((m) => `"${m.name}"`).join(" | ");
1910
+ const scalarEntries = this.schema.models.map((m) => ` ${m.name}: ${m.name};`).join("\n");
1911
+ const relationEntries = this.schema.models.map((m) => {
1912
+ const rels = relationFields(m).map((f) => {
1913
+ const nullable = !f.isList && !f.isRequired ? "; isNullable: true" : "";
1914
+ return ` ${f.name}: { model: "${f.type}"; isList: ${f.isList}${nullable} }`;
1915
+ }).join(";\n");
1916
+ return ` ${m.name}: {${rels ? `
1917
+ ${rels}
1918
+ ` : ""}};`;
1919
+ }).join("\n");
1920
+ return `export type $ModelName = ${names || "never"};
1921
+
1922
+ export interface $ScalarPayload {
1923
+ ${scalarEntries}
1924
+ }
1925
+
1926
+ export interface $RelationMap {
1927
+ ${relationEntries}
1928
+ }`;
1929
+ }
1930
+ scalarType(field) {
1931
+ const base = baseTsType(field, this.schema);
1932
+ if (field.isList) return `${base}[]`;
1933
+ return field.isRequired ? base : `${base} | null`;
1934
+ }
1935
+ // ---- input types (Prisma namespace) -------------------------------------
1936
+ namespace() {
1937
+ const blocks = [SHARED_FILTERS];
1938
+ for (const model of this.schema.models) {
1939
+ blocks.push(this.whereInput(model));
1940
+ blocks.push(this.orderByInput(model));
1941
+ blocks.push(this.selectInclude(model));
1942
+ blocks.push(this.dataInputs(model));
1943
+ blocks.push(this.argsTypes(model));
1944
+ blocks.push(this.delegateInterface(model));
1945
+ }
1946
+ return `export namespace Prisma {
1947
+ ${indentBlock(blocks.join("\n\n"))}
1948
+ }`;
1949
+ }
1950
+ whereInput(model) {
1951
+ const fields = [];
1952
+ for (const f of model.fields) {
1953
+ if (f.kind === "object") {
1954
+ fields.push(` ${f.name}?: ${this.relationFilter(f)};`);
1955
+ } else {
1956
+ fields.push(` ${f.name}?: ${this.fieldFilter(f)};`);
1957
+ }
1958
+ }
1959
+ return `export type ${model.name}WhereInput = {
1960
+ AND?: ${model.name}WhereInput | ${model.name}WhereInput[];
1961
+ OR?: ${model.name}WhereInput[];
1962
+ NOT?: ${model.name}WhereInput | ${model.name}WhereInput[];
1963
+ ${fields.join("\n")}
1964
+ };
1965
+
1966
+ export type ${model.name}WhereUniqueInput = ${this.whereUnique(model)};`;
1967
+ }
1968
+ whereUnique(model) {
1969
+ const uniques = /* @__PURE__ */ new Set();
1970
+ for (const f of model.fields) if (f.isId || f.isUnique) uniques.add(f.name);
1971
+ for (const f of model.primaryKey) uniques.add(f);
1972
+ const parts = [...uniques].map((name) => {
1973
+ const field = model.fields.find((x) => x.name === name);
1974
+ return `{ ${name}: ${baseTsType(field, this.schema)} }`;
1975
+ });
1976
+ const base = `Partial<${model.name}WhereInput>`;
1977
+ return parts.length ? `(${parts.join(" | ")}) & ${base}` : base;
1978
+ }
1979
+ fieldFilter(field) {
1980
+ const base = baseTsType(field, this.schema);
1981
+ const filterName = FILTER_FOR_SCALAR[field.type] ?? null;
1982
+ if (field.kind === "enum") {
1983
+ return `${base} | { equals?: ${base}; in?: ${base}[]; notIn?: ${base}[]; not?: ${base} }`;
1984
+ }
1985
+ if (filterName) return `${base} | ${filterName}`;
1986
+ return `${base}`;
1987
+ }
1988
+ relationFilter(field) {
1989
+ if (field.isList) {
1990
+ return `{ some?: ${field.type}WhereInput; every?: ${field.type}WhereInput; none?: ${field.type}WhereInput }`;
1991
+ }
1992
+ return `${field.type}WhereInput | { is?: ${field.type}WhereInput | null; isNot?: ${field.type}WhereInput | null } | null`;
1993
+ }
1994
+ orderByInput(model) {
1995
+ const fields = scalarFields(model).map((f) => ` ${f.name}?: SortOrder;`).join("\n");
1996
+ return `export type ${model.name}OrderByInput = {
1997
+ ${fields}
1998
+ };`;
1999
+ }
2000
+ selectInclude(model) {
2001
+ const scalarSel = scalarFields(model).map((f) => ` ${f.name}?: boolean;`).join("\n");
2002
+ const relSel = relationFields(model).map((f) => ` ${f.name}?: boolean | ${f.type}FindManyArgs;`).join("\n");
2003
+ const include = relationFields(model).map((f) => ` ${f.name}?: boolean | ${f.type}FindManyArgs;`).join("\n");
2004
+ const listRelations = relationFields(model).filter((f) => f.isList);
2005
+ const countLine = listRelations.length ? `
2006
+ _count?: boolean | { select?: { ${listRelations.map((f) => `${f.name}?: boolean`).join("; ")} } };` : "";
2007
+ return `export type ${model.name}Select = {
2008
+ ${scalarSel}${relSel ? "\n" + relSel : ""}${countLine}
2009
+ };
2010
+
2011
+ export type ${model.name}Include = {
2012
+ ${include || " [k: string]: never;"}${countLine}
2013
+ };`;
2014
+ }
2015
+ dataInputs(model) {
2016
+ const foreignKeys2 = /* @__PURE__ */ new Set();
2017
+ for (const rel2 of relationFields(model)) {
2018
+ for (const fk of rel2.relation?.fields ?? []) foreignKeys2.add(fk);
2019
+ }
2020
+ const createScalars = scalarFields(model).filter((f) => !isGeneratedOnCreate(f)).map((f) => {
2021
+ const optional = isOptionalOnCreate(f) || foreignKeys2.has(f.name) ? "?" : "";
2022
+ return ` ${f.name}${optional}: ${this.inputScalarType(f)};`;
2023
+ });
2024
+ const createRelations = relationFields(model).map(
2025
+ (f) => ` ${f.name}?: ${this.nestedCreate(f)};`
2026
+ );
2027
+ const updateScalars = scalarFields(model).map(
2028
+ (f) => ` ${f.name}?: ${this.updateScalarType(f)};`
2029
+ );
2030
+ const updateRelations = relationFields(model).map(
2031
+ (f) => ` ${f.name}?: ${this.nestedUpdate(f)};`
2032
+ );
2033
+ return `export type ${model.name}CreateInput = {
2034
+ ${[...createScalars, ...createRelations].join("\n")}
2035
+ };
2036
+
2037
+ export type ${model.name}UpdateInput = {
2038
+ ${[...updateScalars, ...updateRelations].join("\n")}
2039
+ };`;
2040
+ }
2041
+ inputScalarType(field) {
2042
+ const base = baseTsType(field, this.schema);
2043
+ if (field.isList) return `${base}[]`;
2044
+ return field.isRequired ? base : `${base} | null`;
2045
+ }
2046
+ /**
2047
+ * Update value for a scalar field: a bare value, `{ set }`, and — for numeric
2048
+ * fields — the atomic operators increment/decrement/multiply/divide.
2049
+ */
2050
+ updateScalarType(field) {
2051
+ const value = this.inputScalarType(field);
2052
+ if (field.isList) return value;
2053
+ const base = baseTsType(field, this.schema);
2054
+ if (NUMERIC_SCALARS.has(field.type)) {
2055
+ const nullable = field.isRequired ? "" : " | null";
2056
+ return `${value} | { set?: ${base}${nullable}; increment?: ${base}; decrement?: ${base}; multiply?: ${base}; divide?: ${base} }`;
2057
+ }
2058
+ return `${value} | { set?: ${value} }`;
2059
+ }
2060
+ nestedCreate(field) {
2061
+ const t = field.type;
2062
+ if (field.isList) {
2063
+ return `{ create?: ${t}CreateInput | ${t}CreateInput[]; connect?: ${t}WhereUniqueInput | ${t}WhereUniqueInput[]; connectOrCreate?: { where: ${t}WhereUniqueInput; create: ${t}CreateInput } | { where: ${t}WhereUniqueInput; create: ${t}CreateInput }[] }`;
2064
+ }
2065
+ return `{ create?: ${t}CreateInput; connect?: ${t}WhereUniqueInput; connectOrCreate?: { where: ${t}WhereUniqueInput; create: ${t}CreateInput } }`;
2066
+ }
2067
+ nestedUpdate(field) {
2068
+ const t = field.type;
2069
+ if (field.isList) {
2070
+ return `{ create?: ${t}CreateInput | ${t}CreateInput[]; connect?: ${t}WhereUniqueInput | ${t}WhereUniqueInput[]; disconnect?: ${t}WhereUniqueInput | ${t}WhereUniqueInput[]; set?: ${t}WhereUniqueInput | ${t}WhereUniqueInput[]; delete?: ${t}WhereUniqueInput | ${t}WhereUniqueInput[] }`;
2071
+ }
2072
+ return `{ create?: ${t}CreateInput; connect?: ${t}WhereUniqueInput; disconnect?: boolean }`;
2073
+ }
2074
+ argsTypes(model) {
2075
+ const n = model.name;
2076
+ return `export type ${n}Omit = Partial<Record<keyof ${n}, boolean>>;
2077
+
2078
+ export type ${n}FindManyArgs = {
2079
+ where?: ${n}WhereInput;
2080
+ orderBy?: ${n}OrderByInput | ${n}OrderByInput[];
2081
+ select?: ${n}Select;
2082
+ include?: ${n}Include;
2083
+ omit?: ${n}Omit;
2084
+ take?: number;
2085
+ skip?: number;
2086
+ cursor?: ${n}WhereUniqueInput;
2087
+ distinct?: (keyof ${n})[];
2088
+ };
2089
+
2090
+ export type ${n}FindFirstArgs = ${n}FindManyArgs;
2091
+
2092
+ export type ${n}FindUniqueArgs = {
2093
+ where: ${n}WhereUniqueInput;
2094
+ select?: ${n}Select;
2095
+ include?: ${n}Include;
2096
+ omit?: ${n}Omit;
2097
+ };
2098
+
2099
+ export type ${n}CreateArgs = {
2100
+ data: ${n}CreateInput;
2101
+ select?: ${n}Select;
2102
+ include?: ${n}Include;
2103
+ omit?: ${n}Omit;
2104
+ };
2105
+
2106
+ export type ${n}CreateManyArgs = { data: ${n}CreateInput[]; skipDuplicates?: boolean };
2107
+
2108
+ export type ${n}CreateManyAndReturnArgs = {
2109
+ data: ${n}CreateInput[];
2110
+ select?: ${n}Select;
2111
+ omit?: ${n}Omit;
2112
+ skipDuplicates?: boolean;
2113
+ };
2114
+
2115
+ export type ${n}UpdateArgs = {
2116
+ where: ${n}WhereUniqueInput;
2117
+ data: ${n}UpdateInput;
2118
+ select?: ${n}Select;
2119
+ include?: ${n}Include;
2120
+ omit?: ${n}Omit;
2121
+ };
2122
+
2123
+ export type ${n}UpdateManyArgs = { where?: ${n}WhereInput; data: ${n}UpdateInput };
2124
+
2125
+ export type ${n}UpsertArgs = {
2126
+ where: ${n}WhereUniqueInput;
2127
+ create: ${n}CreateInput;
2128
+ update: ${n}UpdateInput;
2129
+ select?: ${n}Select;
2130
+ include?: ${n}Include;
2131
+ omit?: ${n}Omit;
2132
+ };
2133
+
2134
+ export type ${n}DeleteArgs = { where: ${n}WhereUniqueInput; select?: ${n}Select; include?: ${n}Include };
2135
+ export type ${n}DeleteManyArgs = { where?: ${n}WhereInput };
2136
+
2137
+ export type ${n}CountArgs = { where?: ${n}WhereInput; take?: number; skip?: number };
2138
+
2139
+ export type ${n}AggregateArgs = {
2140
+ where?: ${n}WhereInput;
2141
+ _count?: true | Partial<Record<keyof ${n}, boolean>>;
2142
+ _avg?: Partial<Record<keyof ${n}, boolean>>;
2143
+ _sum?: Partial<Record<keyof ${n}, boolean>>;
2144
+ _min?: Partial<Record<keyof ${n}, boolean>>;
2145
+ _max?: Partial<Record<keyof ${n}, boolean>>;
2146
+ };
2147
+
2148
+ export type ${n}GroupByArgs = ${n}AggregateArgs & {
2149
+ by: (keyof ${n})[];
2150
+ having?: ${n}WhereInput;
2151
+ orderBy?: ${n}OrderByInput | ${n}OrderByInput[];
2152
+ };`;
2153
+ }
2154
+ delegateInterface(model) {
2155
+ const n = model.name;
2156
+ return `export interface ${n}Delegate {
2157
+ findMany<A extends ${n}FindManyArgs>(args?: A): Promise<${n}GetPayload<A>[]>;
2158
+ findFirst<A extends ${n}FindFirstArgs>(args?: A): ${n}Fluent<A, null>;
2159
+ findFirstOrThrow<A extends ${n}FindFirstArgs>(args?: A): ${n}Fluent<A, never>;
2160
+ findUnique<A extends ${n}FindUniqueArgs>(args: A): ${n}Fluent<A, null>;
2161
+ findUniqueOrThrow<A extends ${n}FindUniqueArgs>(args: A): ${n}Fluent<A, never>;
2162
+ create<A extends ${n}CreateArgs>(args: A): Promise<${n}GetPayload<A>>;
2163
+ createMany(args: ${n}CreateManyArgs): Promise<{ count: number }>;
2164
+ createManyAndReturn<A extends ${n}CreateManyAndReturnArgs>(args: A): Promise<${n}GetPayload<A>[]>;
2165
+ update<A extends ${n}UpdateArgs>(args: A): Promise<${n}GetPayload<A>>;
2166
+ updateMany(args: ${n}UpdateManyArgs): Promise<{ count: number }>;
2167
+ upsert<A extends ${n}UpsertArgs>(args: A): Promise<${n}GetPayload<A>>;
2168
+ delete<A extends ${n}DeleteArgs>(args: A): Promise<${n}GetPayload<A>>;
2169
+ deleteMany(args?: ${n}DeleteManyArgs): Promise<{ count: number }>;
2170
+ count(args?: ${n}CountArgs): Promise<number>;
2171
+ aggregate(args?: ${n}AggregateArgs): Promise<Record<string, any>>;
2172
+ groupBy(args: ${n}GroupByArgs): Promise<Record<string, any>[]>;
2173
+ }`;
2174
+ }
2175
+ // ---- client class -------------------------------------------------------
2176
+ clientClass() {
2177
+ const delegates = this.schema.models.map((m) => ` declare readonly ${lowerFirst(m.name)}: Prisma.${m.name}Delegate;`).join("\n");
2178
+ return `export type EmberClientOptions = {
2179
+ datasourceUrl?: string;
2180
+ datasource?: ClientOptions["datasource"];
2181
+ log?: ClientOptions["log"];
2182
+ };
2183
+
2184
+ export class EmberClient extends EmberClientBase {
2185
+ constructor(options: EmberClientOptions = {}) {
2186
+ super({ schema: schemaDocument, ...options });
2187
+ }
2188
+
2189
+ ${delegates}
2190
+ }`;
2191
+ }
2192
+ };
2193
+ function generateClientSource(schema) {
2194
+ return new ClientGenerator(schema).generate();
2195
+ }
2196
+ function writeClient(schema, outDir) {
2197
+ const source = generateClientSource(schema);
2198
+ const file = resolve2(outDir, "index.ts");
2199
+ mkdirSync(dirname2(file), { recursive: true });
2200
+ writeFileSync(file, source, "utf8");
2201
+ return file;
2202
+ }
2203
+ var PAYLOAD_RESOLVER = `type $Scalar<M extends $ModelName> = $ScalarPayload[M];
2204
+ type $Relations<M extends $ModelName> = M extends keyof $RelationMap
2205
+ ? $RelationMap[M]
2206
+ : {};
2207
+
2208
+ // A relation sub-arg may be \`true\` (full payload) or a nested args object.
2209
+ type $NormalizeArgs<Sub> = Sub extends object ? Sub : {};
2210
+
2211
+ type $RelationResult<Info, Sub> = Info extends {
2212
+ model: infer RM extends $ModelName;
2213
+ isList: infer L;
2214
+ }
2215
+ ? L extends true
2216
+ ? $Payload<RM, $NormalizeArgs<Sub>>[]
2217
+ : Info extends { isNullable: true }
2218
+ ? $Payload<RM, $NormalizeArgs<Sub>> | null
2219
+ : $Payload<RM, $NormalizeArgs<Sub>>
2220
+ : never;
2221
+
2222
+ // To-many relation keys of a model (eligible for _count).
2223
+ type $ListRelations<M extends $ModelName> = {
2224
+ [K in keyof $Relations<M>]: $Relations<M>[K] extends { isList: true }
2225
+ ? K
2226
+ : never;
2227
+ }[keyof $Relations<M>];
2228
+
2229
+ type $CountResult<M extends $ModelName, C> = C extends { select: infer SC }
2230
+ ? { [K in keyof SC & $ListRelations<M>]: number }
2231
+ : { [K in $ListRelations<M> & string]: number };
2232
+
2233
+ type $CountPart<M extends $ModelName, A> = A extends { _count: infer C }
2234
+ ? { _count: $CountResult<M, C> }
2235
+ : {};
2236
+
2237
+ type $SelectResult<M extends $ModelName, S> = {
2238
+ [K in keyof S & keyof $Scalar<M>]: $Scalar<M>[K];
2239
+ } & {
2240
+ [K in keyof S & keyof $Relations<M>]: $RelationResult<$Relations<M>[K], S[K]>;
2241
+ } & $CountPart<M, S>;
2242
+
2243
+ type $IncludeResult<M extends $ModelName, I> = {
2244
+ [K in keyof I & keyof $Relations<M>]: $RelationResult<$Relations<M>[K], I[K]>;
2245
+ } & $CountPart<M, I>;
2246
+
2247
+ export type $Payload<M extends $ModelName, A> = A extends { select: infer S }
2248
+ ? $SelectResult<M, S>
2249
+ : A extends { include: infer I }
2250
+ ? $Scalar<M> & $IncludeResult<M, I>
2251
+ : $Scalar<M>;`;
2252
+ var NUMERIC_SCALARS = /* @__PURE__ */ new Set(["Int", "BigInt", "Float", "Decimal"]);
2253
+ var FILTER_FOR_SCALAR = {
2254
+ String: "StringFilter",
2255
+ Int: "IntFilter",
2256
+ BigInt: "BigIntFilter",
2257
+ Float: "FloatFilter",
2258
+ Decimal: "FloatFilter",
2259
+ Boolean: "BoolFilter",
2260
+ DateTime: "DateTimeFilter",
2261
+ Bytes: "BytesFilter",
2262
+ Json: "JsonFilter"
2263
+ };
2264
+ var SHARED_FILTERS = `export type StringFilter = {
2265
+ equals?: string;
2266
+ not?: string | StringFilter;
2267
+ in?: string[];
2268
+ notIn?: string[];
2269
+ lt?: string;
2270
+ lte?: string;
2271
+ gt?: string;
2272
+ gte?: string;
2273
+ contains?: string;
2274
+ startsWith?: string;
2275
+ endsWith?: string;
2276
+ mode?: QueryMode;
2277
+ };
2278
+
2279
+ export type IntFilter = {
2280
+ equals?: number;
2281
+ not?: number | IntFilter;
2282
+ in?: number[];
2283
+ notIn?: number[];
2284
+ lt?: number;
2285
+ lte?: number;
2286
+ gt?: number;
2287
+ gte?: number;
2288
+ };
2289
+
2290
+ export type FloatFilter = IntFilter;
2291
+ export type BigIntFilter = {
2292
+ equals?: bigint;
2293
+ not?: bigint | BigIntFilter;
2294
+ in?: bigint[];
2295
+ notIn?: bigint[];
2296
+ lt?: bigint;
2297
+ lte?: bigint;
2298
+ gt?: bigint;
2299
+ gte?: bigint;
2300
+ };
2301
+
2302
+ export type BoolFilter = { equals?: boolean; not?: boolean };
2303
+
2304
+ export type DateTimeFilter = {
2305
+ equals?: Date;
2306
+ not?: Date | DateTimeFilter;
2307
+ in?: Date[];
2308
+ notIn?: Date[];
2309
+ lt?: Date;
2310
+ lte?: Date;
2311
+ gt?: Date;
2312
+ gte?: Date;
2313
+ };
2314
+
2315
+ export type BytesFilter = { equals?: Buffer; not?: Buffer };
2316
+
2317
+ // Firebird has no JSON SQL functions: filters operate on the serialized text.
2318
+ // 'path' is accepted by the type but rejected at runtime on Firebird.
2319
+ export type JsonFilter = {
2320
+ equals?: JsonValue;
2321
+ not?: JsonValue;
2322
+ string_contains?: string;
2323
+ string_starts_with?: string;
2324
+ string_ends_with?: string;
2325
+ path?: string[];
2326
+ };`;
2327
+ function isGeneratedOnCreate(field) {
2328
+ return field.default?.function?.name === "autoincrement";
2329
+ }
2330
+ function isOptionalOnCreate(field) {
2331
+ return !field.isRequired || !!field.default || field.isUpdatedAt || field.isList;
2332
+ }
2333
+ function indentBlock(text) {
2334
+ return text.split("\n").map((l) => l ? " " + l : l).join("\n");
2335
+ }
2336
+
2337
+ // src/migrate/index.ts
2338
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
2339
+ import { join as join2 } from "path";
2340
+
2341
+ // src/migrate/diff.ts
2342
+ function diffSchemas(desired, current) {
2343
+ const currentByTable = new Map(
2344
+ current.models.map((m) => [modelTable(m), m])
2345
+ );
2346
+ const desiredTables = new Set(desired.models.map((m) => modelTable(m)));
2347
+ const createdModels = [];
2348
+ const modelChanges = [];
2349
+ for (const model of desired.models) {
2350
+ const table = modelTable(model);
2351
+ const currentModel = currentByTable.get(table);
2352
+ if (!currentModel) {
2353
+ createdModels.push(model);
2354
+ continue;
2355
+ }
2356
+ const change = diffModel(desired, model, currentModel, table);
2357
+ if (hasModelChanges(change)) modelChanges.push(change);
2358
+ }
2359
+ const droppedTables = current.models.map((m) => modelTable(m)).filter((t) => !desiredTables.has(t));
2360
+ return { createdModels, droppedTables, modelChanges };
2361
+ }
2362
+ function diffModel(desired, model, current, table) {
2363
+ const desiredCols = new Map(
2364
+ scalarFields(model).map((f) => [fieldColumn(f), f])
2365
+ );
2366
+ const currentCols = new Map(
2367
+ scalarFields(current).map((f) => [fieldColumn(f), f])
2368
+ );
2369
+ const addedColumns = [];
2370
+ const changedColumns = [];
2371
+ for (const [col, field] of desiredCols) {
2372
+ const existing = currentCols.get(col);
2373
+ if (!existing) {
2374
+ addedColumns.push(field);
2375
+ continue;
2376
+ }
2377
+ const typeChanged = !sameColumnType(field, existing);
2378
+ const nullabilityChanged = field.isRequired !== existing.isRequired;
2379
+ if (typeChanged || nullabilityChanged) {
2380
+ changedColumns.push({ field, table, typeChanged, nullabilityChanged });
2381
+ }
2382
+ }
2383
+ const droppedColumns = [...currentCols.keys()].filter(
2384
+ (c) => !desiredCols.has(c)
2385
+ );
2386
+ const currentUniqueSets = uniqueColumnSets(current).map(setKey);
2387
+ const addedUniques = uniqueColumnSets(model).filter((cols) => !currentUniqueSets.includes(setKey(cols))).map((cols) => ({ name: constraintName("UQ", table, cols), columns: cols }));
2388
+ const currentFkSets = foreignKeys(desired, current).map(fkKey);
2389
+ const addedForeignKeys = foreignKeys(desired, model).filter((fk) => !currentFkSets.includes(fkKey(fk))).map((fk) => ({ ...fk, name: constraintName("FK", table, fk.columns) }));
2390
+ return {
2391
+ model,
2392
+ table,
2393
+ addedColumns,
2394
+ droppedColumns,
2395
+ changedColumns,
2396
+ addedUniques,
2397
+ addedForeignKeys
2398
+ };
2399
+ }
2400
+ function hasModelChanges(c) {
2401
+ return c.addedColumns.length > 0 || c.droppedColumns.length > 0 || c.changedColumns.length > 0 || c.addedUniques.length > 0 || c.addedForeignKeys.length > 0;
2402
+ }
2403
+ function uniqueColumnSets(model) {
2404
+ const sets = [];
2405
+ for (const f of scalarFields(model)) {
2406
+ if (f.isUnique) sets.push([fieldColumn(f)]);
2407
+ }
2408
+ for (const u of model.uniqueIndexes) {
2409
+ sets.push(u.fields.map((name) => columnFor(model, name)));
2410
+ }
2411
+ return sets;
2412
+ }
2413
+ function indexSpecs(model) {
2414
+ return model.indexes.map((i) => ({
2415
+ name: i.name ?? constraintName("IDX", modelTable(model), i.fields.map((n) => columnFor(model, n))),
2416
+ columns: i.fields.map((name) => columnFor(model, name)),
2417
+ unique: i.unique
2418
+ }));
2419
+ }
2420
+ function foreignKeys(schema, model) {
2421
+ const out = [];
2422
+ for (const f of relationFields(model)) {
2423
+ const rel2 = f.relation;
2424
+ if (!rel2?.fields?.length) continue;
2425
+ const refModel = schema.models.find((m) => m.name === f.type);
2426
+ if (!refModel) continue;
2427
+ out.push({
2428
+ columns: rel2.fields.map((name) => columnFor(model, name)),
2429
+ refTable: modelTable(refModel),
2430
+ refColumns: (rel2.references ?? []).map((name) => columnFor(refModel, name)),
2431
+ onDelete: rel2.onDelete,
2432
+ onUpdate: rel2.onUpdate
2433
+ });
2434
+ }
2435
+ return out;
2436
+ }
2437
+ function columnFor(model, fieldName) {
2438
+ const f = model.fields.find((x) => x.name === fieldName);
2439
+ return f ? fieldColumn(f) : fieldName.toUpperCase();
2440
+ }
2441
+ function sameColumnType(a, b) {
2442
+ if (a.type !== b.type) return false;
2443
+ return sameNative(a.nativeType, b.nativeType);
2444
+ }
2445
+ function sameNative(a, b) {
2446
+ if (!a && !b) return true;
2447
+ if (!a || !b) return true;
2448
+ return a.name === b.name && a.args.join(",") === b.args.join(",");
2449
+ }
2450
+ function setKey(cols) {
2451
+ return [...cols].sort().join(",");
2452
+ }
2453
+ function fkKey(fk) {
2454
+ return `${setKey(fk.columns)}->${fk.refTable}(${setKey(fk.refColumns)})`;
2455
+ }
2456
+ function constraintName(prefix, table, columns) {
2457
+ const base = `${prefix}_${table}_${columns.join("_")}`.replace(/[^A-Za-z0-9_]/g, "_");
2458
+ if (base.length <= 31) return base;
2459
+ let hash = 0;
2460
+ for (let i = 0; i < base.length; i++) hash = hash * 31 + base.charCodeAt(i) | 0;
2461
+ const suffix = Math.abs(hash).toString(36).slice(0, 6);
2462
+ return `${base.slice(0, 24)}_${suffix}`;
2463
+ }
2464
+
2465
+ // src/migrate/ddl.ts
2466
+ var FirebirdDdl = class {
2467
+ constructor(d) {
2468
+ this.d = d;
2469
+ }
2470
+ d;
2471
+ // ---- column types -------------------------------------------------------
2472
+ /** Firebird column type for a field, honoring its `@db.*` native type. */
2473
+ columnType(field) {
2474
+ const native = field.nativeType;
2475
+ if (native) {
2476
+ const args = native.args.length ? `(${native.args.join(", ")})` : "";
2477
+ switch (native.name) {
2478
+ case "VarChar":
2479
+ return `VARCHAR${args || "(255)"}`;
2480
+ case "Char":
2481
+ return `CHAR${args || "(1)"}`;
2482
+ case "Text":
2483
+ return "BLOB SUB_TYPE TEXT";
2484
+ case "SmallInt":
2485
+ return "SMALLINT";
2486
+ case "Integer":
2487
+ return "INTEGER";
2488
+ case "BigInt":
2489
+ return "BIGINT";
2490
+ case "Float":
2491
+ return "FLOAT";
2492
+ case "DoublePrecision":
2493
+ return "DOUBLE PRECISION";
2494
+ case "Decimal":
2495
+ return `DECIMAL${args || "(18, 4)"}`;
2496
+ case "Boolean":
2497
+ return this.d.booleanColumnType();
2498
+ case "Date":
2499
+ return "DATE";
2500
+ case "Time":
2501
+ return "TIME";
2502
+ case "Timestamp":
2503
+ return "TIMESTAMP";
2504
+ case "Blob":
2505
+ return "BLOB SUB_TYPE BINARY";
2506
+ }
2507
+ }
2508
+ if (field.type === "Boolean") return this.d.booleanColumnType();
2509
+ return DEFAULT_DDL_TYPE[field.type] ?? "VARCHAR(255)";
2510
+ }
2511
+ /** `"COL" <type> [GENERATED ...] [DEFAULT ...] [NOT NULL]`. */
2512
+ columnDefinition(field) {
2513
+ const parts = [this.d.quoteId(fieldColumn(field)), this.columnType(field)];
2514
+ if (isIdentity(field) && this.d.supportsIdentity) {
2515
+ parts.push("GENERATED BY DEFAULT AS IDENTITY");
2516
+ } else if (!isIdentity(field)) {
2517
+ const def = defaultClause(field.default);
2518
+ if (def) parts.push(def);
2519
+ }
2520
+ if (field.isRequired) parts.push("NOT NULL");
2521
+ return parts.join(" ");
2522
+ }
2523
+ /**
2524
+ * On Firebird 2.1/2.5 (no IDENTITY), emulate autoincrement with a SEQUENCE
2525
+ * and a BEFORE INSERT trigger. Returns the extra DDL objects to create after
2526
+ * the table; an empty array when IDENTITY is supported.
2527
+ */
2528
+ autoIncrementObjects(model) {
2529
+ if (this.d.supportsIdentity) return [];
2530
+ const table = modelTable(model);
2531
+ const out = [];
2532
+ for (const field of scalarFields(model)) {
2533
+ if (isIdentity(field)) {
2534
+ out.push(...this.autoIncrementForColumn(table, fieldColumn(field)));
2535
+ }
2536
+ }
2537
+ return out;
2538
+ }
2539
+ autoIncrementForColumn(table, column) {
2540
+ if (this.d.supportsIdentity) return [];
2541
+ const seq = capName(`GEN_${table}_${column}`);
2542
+ const trig = capName(`${table}_BI_${column}`);
2543
+ const qTable = this.d.quoteId(table);
2544
+ const qCol = this.d.quoteId(column);
2545
+ const qSeq = this.d.quoteId(seq);
2546
+ return [
2547
+ `CREATE SEQUENCE ${qSeq}`,
2548
+ `CREATE TRIGGER ${this.d.quoteId(trig)} FOR ${qTable} ACTIVE BEFORE INSERT POSITION 0 AS
2549
+ BEGIN
2550
+ IF (NEW.${qCol} IS NULL) THEN NEW.${qCol} = GEN_ID(${qSeq}, 1);
2551
+ END`
2552
+ ];
2553
+ }
2554
+ // ---- table-level --------------------------------------------------------
2555
+ createTable(model) {
2556
+ const cols = scalarFields(model).map((f) => " " + this.columnDefinition(f));
2557
+ const pk = idFields(model);
2558
+ const lines = [...cols];
2559
+ if (pk.length > 0) {
2560
+ lines.push(
2561
+ ` PRIMARY KEY (${pk.map((f) => this.d.quoteId(fieldColumn(f))).join(", ")})`
2562
+ );
2563
+ }
2564
+ return `CREATE TABLE ${this.d.quoteId(modelTable(model))} (
2565
+ ${lines.join(",\n")}
2566
+ )`;
2567
+ }
2568
+ dropTable(table) {
2569
+ return `DROP TABLE ${this.d.quoteId(table)}`;
2570
+ }
2571
+ addColumn(table, field) {
2572
+ return `ALTER TABLE ${this.d.quoteId(table)} ADD ${this.columnDefinition(field)}`;
2573
+ }
2574
+ dropColumn(table, column) {
2575
+ return `ALTER TABLE ${this.d.quoteId(table)} DROP ${this.d.quoteId(column)}`;
2576
+ }
2577
+ /** Change a column's data type (Firebird: ALTER COLUMN ... TYPE ...). */
2578
+ alterColumnType(table, field) {
2579
+ return `ALTER TABLE ${this.d.quoteId(table)} ALTER COLUMN ${this.d.quoteId(
2580
+ fieldColumn(field)
2581
+ )} TYPE ${this.columnType(field)}`;
2582
+ }
2583
+ setNotNull(table, column, notNull) {
2584
+ const action = notNull ? "SET NOT NULL" : "DROP NOT NULL";
2585
+ return `ALTER TABLE ${this.d.quoteId(table)} ALTER COLUMN ${this.d.quoteId(
2586
+ column
2587
+ )} ${action}`;
2588
+ }
2589
+ // ---- constraints & indexes ---------------------------------------------
2590
+ addUnique(table, name, columns) {
2591
+ return `ALTER TABLE ${this.d.quoteId(table)} ADD CONSTRAINT ${this.d.quoteId(
2592
+ name
2593
+ )} UNIQUE (${columns.map((c) => this.d.quoteId(c)).join(", ")})`;
2594
+ }
2595
+ dropConstraint(table, name) {
2596
+ return `ALTER TABLE ${this.d.quoteId(table)} DROP CONSTRAINT ${this.d.quoteId(name)}`;
2597
+ }
2598
+ addForeignKey(table, name, columns, refTable, refColumns, onDelete, onUpdate) {
2599
+ let sql = `ALTER TABLE ${this.d.quoteId(table)} ADD CONSTRAINT ${this.d.quoteId(
2600
+ name
2601
+ )} FOREIGN KEY (${columns.map((c) => this.d.quoteId(c)).join(", ")}) REFERENCES ${this.d.quoteId(
2602
+ refTable
2603
+ )} (${refColumns.map((c) => this.d.quoteId(c)).join(", ")})`;
2604
+ if (onUpdate) sql += ` ON UPDATE ${referentialSql(onUpdate)}`;
2605
+ if (onDelete) sql += ` ON DELETE ${referentialSql(onDelete)}`;
2606
+ return sql;
2607
+ }
2608
+ createIndex(table, name, columns, unique) {
2609
+ return `CREATE ${unique ? "UNIQUE " : ""}INDEX ${this.d.quoteId(name)} ON ${this.d.quoteId(
2610
+ table
2611
+ )} (${columns.map((c) => this.d.quoteId(c)).join(", ")})`;
2612
+ }
2613
+ dropIndex(name) {
2614
+ return `DROP INDEX ${this.d.quoteId(name)}`;
2615
+ }
2616
+ };
2617
+ var DEFAULT_DDL_TYPE = {
2618
+ String: "VARCHAR(255)",
2619
+ Boolean: "BOOLEAN",
2620
+ Int: "INTEGER",
2621
+ BigInt: "BIGINT",
2622
+ Float: "DOUBLE PRECISION",
2623
+ Decimal: "DECIMAL(18, 4)",
2624
+ DateTime: "TIMESTAMP",
2625
+ Bytes: "BLOB SUB_TYPE BINARY",
2626
+ Json: "BLOB SUB_TYPE TEXT"
2627
+ };
2628
+ function isIdentity(field) {
2629
+ return field.default?.function?.name === "autoincrement";
2630
+ }
2631
+ function capName(name) {
2632
+ const safe = name.replace(/[^A-Za-z0-9_]/g, "_");
2633
+ if (safe.length <= 31) return safe;
2634
+ let hash = 0;
2635
+ for (let i = 0; i < safe.length; i++) hash = hash * 31 + safe.charCodeAt(i) | 0;
2636
+ return `${safe.slice(0, 24)}_${Math.abs(hash).toString(36).slice(0, 6)}`;
2637
+ }
2638
+ function defaultClause(def) {
2639
+ if (!def) return null;
2640
+ if (def.function) {
2641
+ switch (def.function.name) {
2642
+ case "now":
2643
+ return "DEFAULT CURRENT_TIMESTAMP";
2644
+ case "uuid":
2645
+ case "cuid":
2646
+ return null;
2647
+ // generated app-side
2648
+ default:
2649
+ return null;
2650
+ }
2651
+ }
2652
+ if (def.literal === void 0) return null;
2653
+ if (typeof def.literal === "boolean") {
2654
+ return `DEFAULT ${def.literal ? "TRUE" : "FALSE"}`;
2655
+ }
2656
+ if (typeof def.literal === "number") return `DEFAULT ${def.literal}`;
2657
+ return `DEFAULT '${String(def.literal).replace(/'/g, "''")}'`;
2658
+ }
2659
+ function referentialSql(action) {
2660
+ switch (action) {
2661
+ case "Cascade":
2662
+ return "CASCADE";
2663
+ case "SetNull":
2664
+ return "SET NULL";
2665
+ case "SetDefault":
2666
+ return "SET DEFAULT";
2667
+ case "NoAction":
2668
+ return "NO ACTION";
2669
+ case "Restrict":
2670
+ return "NO ACTION";
2671
+ // Firebird has no RESTRICT; NO ACTION is closest
2672
+ default:
2673
+ return "NO ACTION";
2674
+ }
2675
+ }
2676
+
2677
+ // src/migrate/planner.ts
2678
+ function planMigration(diff, desired, dialect) {
2679
+ const ddl = new FirebirdDdl(dialect);
2680
+ const drops = [];
2681
+ const creates = [];
2682
+ const alters = [];
2683
+ const constraints = [];
2684
+ const indexes = [];
2685
+ const foreignKeyStmts = [];
2686
+ for (const change of diff.modelChanges) {
2687
+ for (const col of change.droppedColumns) {
2688
+ drops.push(ddl.dropColumn(change.table, col));
2689
+ }
2690
+ }
2691
+ for (const table of diff.droppedTables) {
2692
+ drops.push(ddl.dropTable(table));
2693
+ }
2694
+ for (const model of diff.createdModels) {
2695
+ const table = modelTable(model);
2696
+ creates.push(ddl.createTable(model));
2697
+ creates.push(...ddl.autoIncrementObjects(model));
2698
+ for (const cols of uniqueColumnSets(model)) {
2699
+ if (cols.length === 1) continue;
2700
+ constraints.push(
2701
+ ddl.addUnique(table, constraintName("UQ", table, cols), cols)
2702
+ );
2703
+ }
2704
+ for (const idx of indexSpecs(model)) {
2705
+ indexes.push(ddl.createIndex(table, idx.name, idx.columns, idx.unique));
2706
+ }
2707
+ for (const fk of foreignKeys(desired, model)) {
2708
+ foreignKeyStmts.push(
2709
+ ddl.addForeignKey(
2710
+ table,
2711
+ constraintName("FK", table, fk.columns),
2712
+ fk.columns,
2713
+ fk.refTable,
2714
+ fk.refColumns,
2715
+ fk.onDelete,
2716
+ fk.onUpdate
2717
+ )
2718
+ );
2719
+ }
2720
+ }
2721
+ for (const change of diff.modelChanges) {
2722
+ for (const field of change.addedColumns) {
2723
+ alters.push(ddl.addColumn(change.table, field));
2724
+ if (isIdentity(field)) {
2725
+ creates.push(
2726
+ ...ddl.autoIncrementForColumn(change.table, fieldColumn(field))
2727
+ );
2728
+ }
2729
+ }
2730
+ for (const cc of change.changedColumns) {
2731
+ if (cc.typeChanged) alters.push(ddl.alterColumnType(cc.table, cc.field));
2732
+ if (cc.nullabilityChanged) {
2733
+ alters.push(
2734
+ ddl.setNotNull(cc.table, columnName(cc), cc.field.isRequired)
2735
+ );
2736
+ }
2737
+ }
2738
+ for (const uq of change.addedUniques) {
2739
+ if (uq.columns.length === 1) {
2740
+ constraints.push(ddl.addUnique(change.table, uq.name, uq.columns));
2741
+ } else {
2742
+ constraints.push(ddl.addUnique(change.table, uq.name, uq.columns));
2743
+ }
2744
+ }
2745
+ for (const fk of change.addedForeignKeys) {
2746
+ foreignKeyStmts.push(
2747
+ ddl.addForeignKey(
2748
+ change.table,
2749
+ fk.name,
2750
+ fk.columns,
2751
+ fk.refTable,
2752
+ fk.refColumns,
2753
+ fk.onDelete,
2754
+ fk.onUpdate
2755
+ )
2756
+ );
2757
+ }
2758
+ }
2759
+ return [
2760
+ ...drops,
2761
+ ...creates,
2762
+ ...alters,
2763
+ ...constraints,
2764
+ ...indexes,
2765
+ ...foreignKeyStmts
2766
+ ];
2767
+ }
2768
+ var BREAKPOINT = "--> statement-breakpoint";
2769
+ function renderMigrationSql(statements) {
2770
+ if (statements.length === 0) return "-- This migration is empty.\n";
2771
+ return statements.join(`
2772
+ ${BREAKPOINT}
2773
+ `) + "\n";
2774
+ }
2775
+ function splitStatements(sql) {
2776
+ return sql.split(BREAKPOINT).map((s) => stripComments(s).trim().replace(/;\s*$/, "").trim()).filter((s) => s.length > 0);
2777
+ }
2778
+ function stripComments(sql) {
2779
+ return sql.split("\n").filter((line) => !line.trim().startsWith("--")).join("\n");
2780
+ }
2781
+ function columnName(cc) {
2782
+ return cc.field.dbName ?? cc.field.name.toUpperCase();
2783
+ }
2784
+
2785
+ // src/migrate/history.ts
2786
+ import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync } from "fs";
2787
+ import { join } from "path";
2788
+ var HISTORY_TABLE = "_EMBER_MIGRATIONS";
2789
+ async function ensureHistoryTable(tx, dialect) {
2790
+ const exists = await tx.query(
2791
+ `SELECT COUNT(*) AS N FROM RDB$RELATIONS WHERE RDB$RELATION_NAME = ?`,
2792
+ [HISTORY_TABLE]
2793
+ );
2794
+ if (Number(exists[0]?.N ?? 0) > 0) return;
2795
+ const t = dialect.quoteId(HISTORY_TABLE);
2796
+ await tx.query(
2797
+ `CREATE TABLE ${t} (
2798
+ ${dialect.quoteId("ID")} VARCHAR(128) NOT NULL PRIMARY KEY,
2799
+ ${dialect.quoteId("CHECKSUM")} VARCHAR(64),
2800
+ ${dialect.quoteId("STEPS")} INTEGER,
2801
+ ${dialect.quoteId("APPLIED_AT")} TIMESTAMP DEFAULT CURRENT_TIMESTAMP
2802
+ )`
2803
+ );
2804
+ }
2805
+ async function appliedMigrations(tx, dialect) {
2806
+ const t = dialect.quoteId(HISTORY_TABLE);
2807
+ const rows = await tx.query(
2808
+ `SELECT ${dialect.quoteId("ID")} AS "id", ${dialect.quoteId("CHECKSUM")} AS "checksum", ${dialect.quoteId("STEPS")} AS "steps" FROM ${t} ORDER BY ${dialect.quoteId("ID")}`
2809
+ );
2810
+ return rows.map((r) => ({
2811
+ id: String(r.id).trim(),
2812
+ checksum: r.checksum == null ? "" : String(r.checksum).trim(),
2813
+ steps: Number(r.steps ?? 0)
2814
+ }));
2815
+ }
2816
+ async function recordMigration(tx, dialect, migration) {
2817
+ const t = dialect.quoteId(HISTORY_TABLE);
2818
+ await tx.query(
2819
+ `INSERT INTO ${t} (${dialect.quoteId("ID")}, ${dialect.quoteId("CHECKSUM")}, ${dialect.quoteId("STEPS")}) VALUES (?, ?, ?)`,
2820
+ [migration.id, migration.checksum, migration.steps]
2821
+ );
2822
+ }
2823
+ function listLocalMigrations(migrationsDir) {
2824
+ if (!existsSync2(migrationsDir)) return [];
2825
+ return readdirSync(migrationsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort().map((id) => {
2826
+ const dir = join(migrationsDir, id);
2827
+ const file = join(dir, "migration.sql");
2828
+ return {
2829
+ id,
2830
+ dir,
2831
+ sql: existsSync2(file) ? readFileSync2(file, "utf8") : ""
2832
+ };
2833
+ }).filter((m) => m.sql.length > 0);
2834
+ }
2835
+ function checksum(sql) {
2836
+ let h = 2166136261;
2837
+ for (let i = 0; i < sql.length; i++) {
2838
+ h ^= sql.charCodeAt(i);
2839
+ h = Math.imul(h, 16777619);
2840
+ }
2841
+ return (h >>> 0).toString(16).padStart(8, "0");
2842
+ }
2843
+
2844
+ // src/migrate/index.ts
2845
+ var Migrator = class {
2846
+ constructor(driver, desired, migrationsDir, dialect) {
2847
+ this.driver = driver;
2848
+ this.desired = desired;
2849
+ this.migrationsDir = migrationsDir;
2850
+ this.dialect = dialect ?? new FirebirdDialect();
2851
+ }
2852
+ driver;
2853
+ desired;
2854
+ migrationsDir;
2855
+ dialect;
2856
+ /** Compute the diff between the desired schema and the live database. */
2857
+ async diff() {
2858
+ const current = await this.currentSchema();
2859
+ return diffSchemas(this.desired, current);
2860
+ }
2861
+ /** Plan (but do not apply) the DDL needed to reach the desired schema. */
2862
+ async plan() {
2863
+ const diff = await this.diff();
2864
+ return planMigration(diff, this.desired, this.dialect);
2865
+ }
2866
+ /**
2867
+ * `migrate dev`: create a timestamped migration from the current diff, apply
2868
+ * it, and record it in history.
2869
+ */
2870
+ async dev(name = "migration") {
2871
+ const statements = await this.plan();
2872
+ if (statements.length === 0) return { empty: true, statements: [] };
2873
+ const id = `${timestamp()}_${slug(name)}`;
2874
+ const dir = join2(this.migrationsDir, id);
2875
+ const body = renderMigrationSql(statements);
2876
+ mkdirSync2(dir, { recursive: true });
2877
+ writeFileSync2(join2(dir, "migration.sql"), body, "utf8");
2878
+ await this.driver.transaction(async (tx) => {
2879
+ await ensureHistoryTable(tx, this.dialect);
2880
+ for (const stmt of statements) await tx.query(stmt);
2881
+ await recordMigration(tx, this.dialect, {
2882
+ id,
2883
+ checksum: checksum(body),
2884
+ steps: statements.length
2885
+ });
2886
+ });
2887
+ return { empty: false, id, dir, statements };
2888
+ }
2889
+ /**
2890
+ * `db push`: apply the diff directly to the database without writing a
2891
+ * migration file (prototyping flow).
2892
+ */
2893
+ async push() {
2894
+ const statements = await this.plan();
2895
+ if (statements.length === 0) return { statements: [] };
2896
+ await this.driver.transaction(async (tx) => {
2897
+ for (const stmt of statements) await tx.query(stmt);
2898
+ });
2899
+ return { statements };
2900
+ }
2901
+ /** `migrate deploy`: apply every on-disk migration not yet recorded. */
2902
+ async deploy() {
2903
+ const local = listLocalMigrations(this.migrationsDir);
2904
+ const applied = [];
2905
+ const known = await this.driver.transaction(async (tx) => {
2906
+ await ensureHistoryTable(tx, this.dialect);
2907
+ return new Set((await appliedMigrations(tx, this.dialect)).map((m) => m.id));
2908
+ });
2909
+ for (const migration of local) {
2910
+ if (known.has(migration.id)) continue;
2911
+ const statements = splitStatements(migration.sql);
2912
+ await this.driver.transaction(async (tx) => {
2913
+ for (const stmt of statements) await tx.query(stmt);
2914
+ await recordMigration(tx, this.dialect, {
2915
+ id: migration.id,
2916
+ checksum: checksum(migration.sql),
2917
+ steps: statements.length
2918
+ });
2919
+ });
2920
+ applied.push({ id: migration.id, steps: statements.length });
2921
+ }
2922
+ return { applied };
2923
+ }
2924
+ /** `migrate status`: list applied vs pending migrations. */
2925
+ async status() {
2926
+ const local = listLocalMigrations(this.migrationsDir).map((m) => m.id);
2927
+ const applied = await this.driver.transaction(async (tx) => {
2928
+ await ensureHistoryTable(tx, this.dialect);
2929
+ return (await appliedMigrations(tx, this.dialect)).map((m) => m.id);
2930
+ });
2931
+ const appliedSet = new Set(applied);
2932
+ return {
2933
+ applied,
2934
+ pending: local.filter((id) => !appliedSet.has(id))
2935
+ };
2936
+ }
2937
+ async currentSchema() {
2938
+ const introspector = new Introspector(this.driver);
2939
+ const current = await introspector.introspect();
2940
+ current.models = current.models.filter(
2941
+ (m) => modelTable(m) !== HISTORY_TABLE
2942
+ );
2943
+ return current;
2944
+ }
2945
+ };
2946
+ function timestamp() {
2947
+ const d = /* @__PURE__ */ new Date();
2948
+ const p = (n, w = 2) => String(n).padStart(w, "0");
2949
+ return `${d.getUTCFullYear()}${p(d.getUTCMonth() + 1)}${p(d.getUTCDate())}${p(d.getUTCHours())}${p(d.getUTCMinutes())}${p(d.getUTCSeconds())}`;
2950
+ }
2951
+ function slug(name) {
2952
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "") || "migration";
2953
+ }
2954
+
2955
+ // src/cli/commands.ts
2956
+ import { readFileSync as readFileSync3 } from "fs";
2957
+ var DEFAULT_SCHEMA = "ember/schema.ember";
2958
+ var STARTER_SCHEMA = `datasource db {
2959
+ provider = "firebird"
2960
+ url = env("DATABASE_URL")
2961
+ }
2962
+
2963
+ generator client {
2964
+ provider = "ember-client-js"
2965
+ output = "../generated"
2966
+ }
2967
+
2968
+ /// Example model \u2014 replace with your own or run \`ember db pull\`.
2969
+ model User {
2970
+ id Int @id @default(autoincrement())
2971
+ email String @unique
2972
+ name String?
2973
+ createdAt DateTime @default(now())
2974
+ }
2975
+ `;
2976
+ function init(ctx) {
2977
+ const target = resolve3(ctx.cwd, DEFAULT_SCHEMA);
2978
+ if (existsSync3(target)) {
2979
+ ctx.error(`Schema already exists at ${target}`);
2980
+ return 1;
2981
+ }
2982
+ mkdirSync3(dirname3(target), { recursive: true });
2983
+ writeFileSync3(target, STARTER_SCHEMA, "utf8");
2984
+ ctx.log(`Created ${rel(ctx.cwd, target)}`);
2985
+ ctx.log("Set DATABASE_URL and run `ember db pull` or `ember generate`.");
2986
+ return 0;
2987
+ }
2988
+ function validate(ctx, schemaPath) {
2989
+ const path = requireSchema(ctx, schemaPath);
2990
+ if (!path) return 1;
2991
+ loadSchema(path);
2992
+ ctx.log(`Schema at ${rel(ctx.cwd, path)} is valid.`);
2993
+ return 0;
2994
+ }
2995
+ function format(ctx, schemaPath) {
2996
+ const path = requireSchema(ctx, schemaPath);
2997
+ if (!path) return 1;
2998
+ const source = readFileSync3(path, "utf8");
2999
+ writeFileSync3(path, formatSchema(source, path), "utf8");
3000
+ ctx.log(`Formatted ${rel(ctx.cwd, path)}`);
3001
+ return 0;
3002
+ }
3003
+ function generate(ctx, schemaPath) {
3004
+ const path = requireSchema(ctx, schemaPath);
3005
+ if (!path) return 1;
3006
+ const { document } = loadSchema(path);
3007
+ const gen = document.generators[0];
3008
+ const out = resolve3(dirname3(path), gen?.output ?? "../generated");
3009
+ const file = writeClient(document, out);
3010
+ ctx.log(`Generated client at ${rel(ctx.cwd, file)}`);
3011
+ return 0;
3012
+ }
3013
+ async function dbPull(ctx, options) {
3014
+ const path = findSchemaPath(ctx.cwd, options.schemaPath) ?? resolve3(ctx.cwd, DEFAULT_SCHEMA);
3015
+ let url = options.url;
3016
+ let envVar = "DATABASE_URL";
3017
+ if (!url && existsSync3(path)) {
3018
+ const { document } = loadSchema(path);
3019
+ url = resolveDatasourceUrl(document, dirname3(path));
3020
+ if (document.datasource?.url.kind === "env") {
3021
+ envVar = document.datasource.url.value;
3022
+ }
3023
+ }
3024
+ url ??= process.env.DATABASE_URL;
3025
+ if (!url) {
3026
+ ctx.error(
3027
+ "No database URL. Set DATABASE_URL, pass --url, or add a datasource block."
3028
+ );
3029
+ return 1;
3030
+ }
3031
+ const driver = createDriver(url);
3032
+ try {
3033
+ await driver.connect();
3034
+ const introspector = new Introspector(driver);
3035
+ const document = await introspector.introspect({
3036
+ datasource: { name: "db", provider: "firebird", envVar }
3037
+ });
3038
+ mkdirSync3(dirname3(path), { recursive: true });
3039
+ writeFileSync3(path, printSchema(document), "utf8");
3040
+ ctx.log(
3041
+ `Introspected ${document.models.length} model(s) into ${rel(ctx.cwd, path)}`
3042
+ );
3043
+ return 0;
3044
+ } finally {
3045
+ await driver.disconnect();
3046
+ }
3047
+ }
3048
+ async function migrateDev(ctx, options) {
3049
+ return withMigrator(ctx, options, async (migrator) => {
3050
+ const result = await migrator.dev(options.name ?? "migration");
3051
+ if (result.empty) {
3052
+ ctx.log("No schema changes \u2014 database already in sync.");
3053
+ return 0;
3054
+ }
3055
+ ctx.log(`Created and applied migration ${result.id} (${result.statements.length} step(s)).`);
3056
+ return 0;
3057
+ });
3058
+ }
3059
+ async function migrateDeploy(ctx, options) {
3060
+ return withMigrator(ctx, options, async (migrator) => {
3061
+ const { applied } = await migrator.deploy();
3062
+ if (applied.length === 0) {
3063
+ ctx.log("No pending migrations.");
3064
+ return 0;
3065
+ }
3066
+ for (const m of applied) ctx.log(`Applied ${m.id} (${m.steps} step(s)).`);
3067
+ return 0;
3068
+ });
3069
+ }
3070
+ async function migrateStatus(ctx, options) {
3071
+ return withMigrator(ctx, options, async (migrator) => {
3072
+ const { applied, pending } = await migrator.status();
3073
+ ctx.log(`Applied (${applied.length}):`);
3074
+ for (const id of applied) ctx.log(` \u2713 ${id}`);
3075
+ ctx.log(`Pending (${pending.length}):`);
3076
+ for (const id of pending) ctx.log(` \u2022 ${id}`);
3077
+ return 0;
3078
+ });
3079
+ }
3080
+ async function dbPush(ctx, options) {
3081
+ return withMigrator(ctx, options, async (migrator) => {
3082
+ const { statements } = await migrator.push();
3083
+ if (statements.length === 0) {
3084
+ ctx.log("No schema changes \u2014 database already in sync.");
3085
+ return 0;
3086
+ }
3087
+ ctx.log(`Applied ${statements.length} statement(s) to the database.`);
3088
+ return 0;
3089
+ });
3090
+ }
3091
+ async function withMigrator(ctx, options, fn) {
3092
+ const path = requireSchema(ctx, options.schemaPath);
3093
+ if (!path) return 1;
3094
+ const { document } = loadSchema(path);
3095
+ const url = options.url ?? resolveDatasourceUrl(document, dirname3(path)) ?? process.env.DATABASE_URL;
3096
+ if (!url) {
3097
+ ctx.error(
3098
+ "No database URL. Set DATABASE_URL, pass --url, or add a datasource block."
3099
+ );
3100
+ return 1;
3101
+ }
3102
+ const migrationsDir = resolve3(dirname3(path), "migrations");
3103
+ const config = parseConnectionUrl(url);
3104
+ const dialect = new FirebirdDialect({ version: config.version });
3105
+ const driver = createDriver(config);
3106
+ try {
3107
+ await driver.connect();
3108
+ const migrator = new Migrator(driver, document, migrationsDir, dialect);
3109
+ return await fn(migrator);
3110
+ } finally {
3111
+ await driver.disconnect();
3112
+ }
3113
+ }
3114
+ function requireSchema(ctx, schemaPath) {
3115
+ const path = findSchemaPath(ctx.cwd, schemaPath);
3116
+ if (!path) {
3117
+ ctx.error(
3118
+ `Schema not found. Expected ${DEFAULT_SCHEMA} (or pass --schema). Run \`ember init\` to create one.`
3119
+ );
3120
+ return null;
3121
+ }
3122
+ return path;
3123
+ }
3124
+ function rel(cwd, target) {
3125
+ return target.startsWith(cwd) ? target.slice(cwd.length + 1) : target;
3126
+ }
3127
+
3128
+ // src/cli/bin.ts
3129
+ var HELP = `EmberORM \u2014 Prisma-like ORM for Firebird
3130
+
3131
+ Usage: ember <command> [options]
3132
+
3133
+ Commands:
3134
+ init Scaffold ember/schema.ember
3135
+ db pull Introspect the database into your schema
3136
+ db push Apply schema changes directly (no migration file)
3137
+ generate Generate the typed client from the schema
3138
+ migrate dev Diff, create a migration file, and apply it
3139
+ migrate deploy Apply all pending migration files
3140
+ migrate status Show applied vs pending migrations
3141
+ format Re-print the schema with canonical formatting
3142
+ validate Parse and validate the schema
3143
+
3144
+ Options:
3145
+ --schema <path> Path to the schema file
3146
+ --url <url> Firebird connection URL (overrides datasource/env)
3147
+ --name <name> Migration name (migrate dev)
3148
+ -h, --help Show this help
3149
+ -v, --version Show version
3150
+ `;
3151
+ function parseArgs(argv) {
3152
+ const command = [];
3153
+ const flags = {};
3154
+ for (let i = 0; i < argv.length; i++) {
3155
+ const arg = argv[i];
3156
+ if (arg.startsWith("--")) {
3157
+ const key = arg.slice(2);
3158
+ const next = argv[i + 1];
3159
+ if (next && !next.startsWith("-")) {
3160
+ flags[key] = next;
3161
+ i++;
3162
+ } else {
3163
+ flags[key] = true;
3164
+ }
3165
+ } else if (arg === "-h") {
3166
+ flags.help = true;
3167
+ } else if (arg === "-v") {
3168
+ flags.version = true;
3169
+ } else {
3170
+ command.push(arg);
3171
+ }
3172
+ }
3173
+ return { command, flags };
3174
+ }
3175
+ async function main() {
3176
+ const { command, flags } = parseArgs(process.argv.slice(2));
3177
+ const ctx = {
3178
+ cwd: process.cwd(),
3179
+ log: (m) => process.stdout.write(m + "\n"),
3180
+ error: (m) => process.stderr.write(`error: ${m}
3181
+ `)
3182
+ };
3183
+ if (flags.version) {
3184
+ ctx.log(readVersion());
3185
+ return 0;
3186
+ }
3187
+ if (flags.help || command.length === 0) {
3188
+ ctx.log(HELP);
3189
+ return command.length === 0 ? 1 : 0;
3190
+ }
3191
+ const schemaPath = typeof flags.schema === "string" ? flags.schema : void 0;
3192
+ const url = typeof flags.url === "string" ? flags.url : void 0;
3193
+ const name = typeof flags.name === "string" ? flags.name : void 0;
3194
+ const [first, second] = command;
3195
+ switch (first) {
3196
+ case "init":
3197
+ return init(ctx);
3198
+ case "validate":
3199
+ return validate(ctx, schemaPath);
3200
+ case "format":
3201
+ return format(ctx, schemaPath);
3202
+ case "generate":
3203
+ return generate(ctx, schemaPath);
3204
+ case "db":
3205
+ if (second === "pull") return dbPull(ctx, { schemaPath, url });
3206
+ if (second === "push") return dbPush(ctx, { schemaPath, url });
3207
+ ctx.error(`Unknown db subcommand '${second ?? ""}'.`);
3208
+ return 1;
3209
+ case "migrate":
3210
+ if (second === "dev") return migrateDev(ctx, { schemaPath, url, name });
3211
+ if (second === "deploy") return migrateDeploy(ctx, { schemaPath, url });
3212
+ if (second === "status") return migrateStatus(ctx, { schemaPath, url });
3213
+ ctx.error(`Unknown migrate subcommand '${second ?? ""}'.`);
3214
+ return 1;
3215
+ default:
3216
+ ctx.error(`Unknown command '${first}'. Run 'ember --help'.`);
3217
+ return 1;
3218
+ }
3219
+ }
3220
+ function readVersion() {
3221
+ for (const candidate of ["../../package.json", "../package.json"]) {
3222
+ try {
3223
+ const url = new URL(candidate, import.meta.url);
3224
+ const pkg = JSON.parse(readFileSync4(fileURLToPath(url), "utf8"));
3225
+ if (pkg?.version) return `ember ${pkg.version}`;
3226
+ } catch {
3227
+ }
3228
+ }
3229
+ return "ember (unknown version)";
3230
+ }
3231
+ main().then((code) => process.exit(code)).catch((err) => {
3232
+ if (err instanceof EmberError) {
3233
+ process.stderr.write(`error: ${err.message}
3234
+ `);
3235
+ } else {
3236
+ process.stderr.write(`unexpected error: ${err.stack ?? err}
3237
+ `);
3238
+ }
3239
+ process.exit(1);
3240
+ });
3241
+ //# sourceMappingURL=bin.js.map