arkormx 2.10.1 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +541 -21
- package/dist/{index-GhBV8Kyw.d.cts → index-DggXDBiD.d.cts} +524 -13
- package/dist/{index-ufenXVzx.d.mts → index-DoqUdah-.d.mts} +524 -13
- package/dist/index.cjs +539 -62
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +520 -63
- package/dist/relationship/index.cjs +1 -1
- package/dist/relationship/index.d.cts +1 -1
- package/dist/relationship/index.d.mts +1 -1
- package/dist/relationship/index.mjs +1 -1
- package/dist/{relationship-CmhzOlEo.mjs → relationship-CP1xbMOa.mjs} +507 -5
- package/dist/{relationship-DGOpcWA0.cjs → relationship-IC-TAFyG.cjs} +626 -4
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -363,6 +363,419 @@ var PrimaryKeyGenerationPlanner = class {
|
|
|
363
363
|
}
|
|
364
364
|
};
|
|
365
365
|
|
|
366
|
+
//#endregion
|
|
367
|
+
//#region src/Expression.ts
|
|
368
|
+
/**
|
|
369
|
+
* A composable SQL expression. Instances are immutable — every operator returns a
|
|
370
|
+
* new expression — and are accepted by `select`, `where`, `groupBy`, `orderBy`,
|
|
371
|
+
* `having`, and the aggregate helpers. Adapters compile the underlying node tree.
|
|
372
|
+
*/
|
|
373
|
+
var Expression = class Expression {
|
|
374
|
+
/**
|
|
375
|
+
* Type guard for values that came out of the expression builder.
|
|
376
|
+
*/
|
|
377
|
+
static isExpression(value) {
|
|
378
|
+
return value instanceof Expression;
|
|
379
|
+
}
|
|
380
|
+
eq(value) {
|
|
381
|
+
return binary("=", this, coerceValue(value));
|
|
382
|
+
}
|
|
383
|
+
ne(value) {
|
|
384
|
+
return binary("!=", this, coerceValue(value));
|
|
385
|
+
}
|
|
386
|
+
gt(value) {
|
|
387
|
+
return binary(">", this, coerceValue(value));
|
|
388
|
+
}
|
|
389
|
+
gte(value) {
|
|
390
|
+
return binary(">=", this, coerceValue(value));
|
|
391
|
+
}
|
|
392
|
+
lt(value) {
|
|
393
|
+
return binary("<", this, coerceValue(value));
|
|
394
|
+
}
|
|
395
|
+
lte(value) {
|
|
396
|
+
return binary("<=", this, coerceValue(value));
|
|
397
|
+
}
|
|
398
|
+
like(value) {
|
|
399
|
+
return binary("like", this, coerceValue(value));
|
|
400
|
+
}
|
|
401
|
+
ilike(value) {
|
|
402
|
+
return binary("ilike", this, coerceValue(value));
|
|
403
|
+
}
|
|
404
|
+
notLike(value) {
|
|
405
|
+
return binary("not-like", this, coerceValue(value));
|
|
406
|
+
}
|
|
407
|
+
notIlike(value) {
|
|
408
|
+
return binary("not-ilike", this, coerceValue(value));
|
|
409
|
+
}
|
|
410
|
+
in(values) {
|
|
411
|
+
return new NodeExpression({
|
|
412
|
+
kind: "in",
|
|
413
|
+
operand: this.toExpressionNode(),
|
|
414
|
+
values: values.map((value) => coerceValue(value).toExpressionNode()),
|
|
415
|
+
not: false
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
notIn(values) {
|
|
419
|
+
return new NodeExpression({
|
|
420
|
+
kind: "in",
|
|
421
|
+
operand: this.toExpressionNode(),
|
|
422
|
+
values: values.map((value) => coerceValue(value).toExpressionNode()),
|
|
423
|
+
not: true
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
isNull() {
|
|
427
|
+
return new NodeExpression({
|
|
428
|
+
kind: "null-check",
|
|
429
|
+
operand: this.toExpressionNode(),
|
|
430
|
+
not: false
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
isNotNull() {
|
|
434
|
+
return new NodeExpression({
|
|
435
|
+
kind: "null-check",
|
|
436
|
+
operand: this.toExpressionNode(),
|
|
437
|
+
not: true
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
and(other) {
|
|
441
|
+
return binary("and", this, other);
|
|
442
|
+
}
|
|
443
|
+
or(other) {
|
|
444
|
+
return binary("or", this, other);
|
|
445
|
+
}
|
|
446
|
+
plus(value) {
|
|
447
|
+
return binary("+", this, coerceValue(value));
|
|
448
|
+
}
|
|
449
|
+
minus(value) {
|
|
450
|
+
return binary("-", this, coerceValue(value));
|
|
451
|
+
}
|
|
452
|
+
times(value) {
|
|
453
|
+
return binary("*", this, coerceValue(value));
|
|
454
|
+
}
|
|
455
|
+
dividedBy(value) {
|
|
456
|
+
return binary("/", this, coerceValue(value));
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
/**
|
|
460
|
+
* Concrete expression backed by a pre-built node.
|
|
461
|
+
*/
|
|
462
|
+
var NodeExpression = class extends Expression {
|
|
463
|
+
constructor(node) {
|
|
464
|
+
super();
|
|
465
|
+
this.node = node;
|
|
466
|
+
}
|
|
467
|
+
toExpressionNode() {
|
|
468
|
+
return this.node;
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
/**
|
|
472
|
+
* Fluent `CASE … WHEN … THEN … ELSE … END` builder. Immutable.
|
|
473
|
+
*/
|
|
474
|
+
var CaseExpression = class CaseExpression extends Expression {
|
|
475
|
+
constructor(branches, elseExpr) {
|
|
476
|
+
super();
|
|
477
|
+
this.branches = branches;
|
|
478
|
+
this.elseExpr = elseExpr;
|
|
479
|
+
}
|
|
480
|
+
when(condition, result) {
|
|
481
|
+
return new CaseExpression([...this.branches, {
|
|
482
|
+
when: condition,
|
|
483
|
+
then: coerceValue(result)
|
|
484
|
+
}], this.elseExpr);
|
|
485
|
+
}
|
|
486
|
+
else(result) {
|
|
487
|
+
return new CaseExpression(this.branches, coerceValue(result));
|
|
488
|
+
}
|
|
489
|
+
toExpressionNode() {
|
|
490
|
+
return {
|
|
491
|
+
kind: "case",
|
|
492
|
+
cases: this.branches.map((branch) => ({
|
|
493
|
+
when: branch.when.toExpressionNode(),
|
|
494
|
+
then: branch.then.toExpressionNode()
|
|
495
|
+
})),
|
|
496
|
+
else: this.elseExpr?.toExpressionNode()
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
/**
|
|
501
|
+
* JSON-path value extraction (`metadata ->> 'billType'`), with optional casts.
|
|
502
|
+
*/
|
|
503
|
+
var JsonExpression = class JsonExpression extends Expression {
|
|
504
|
+
constructor(column, path, castTo) {
|
|
505
|
+
super();
|
|
506
|
+
this.column = column;
|
|
507
|
+
this.path = path;
|
|
508
|
+
this.castTo = castTo;
|
|
509
|
+
}
|
|
510
|
+
asText() {
|
|
511
|
+
return new JsonExpression(this.column, this.path, "text");
|
|
512
|
+
}
|
|
513
|
+
asNumber() {
|
|
514
|
+
return new JsonExpression(this.column, this.path, "number");
|
|
515
|
+
}
|
|
516
|
+
asBoolean() {
|
|
517
|
+
return new JsonExpression(this.column, this.path, "boolean");
|
|
518
|
+
}
|
|
519
|
+
toExpressionNode() {
|
|
520
|
+
return {
|
|
521
|
+
kind: "json",
|
|
522
|
+
column: this.column,
|
|
523
|
+
path: this.path,
|
|
524
|
+
cast: this.castTo
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
/**
|
|
529
|
+
* Aggregate expression (`sum`, `count`, `avg`, `min`, `max`) with optional filter.
|
|
530
|
+
*/
|
|
531
|
+
var AggregateExpression = class AggregateExpression extends Expression {
|
|
532
|
+
constructor(fn, arg, options = {}) {
|
|
533
|
+
super();
|
|
534
|
+
this.fn = fn;
|
|
535
|
+
this.arg = arg;
|
|
536
|
+
this.options = options;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Restricts the aggregate to rows matching `predicate` (`FILTER (WHERE …)`).
|
|
540
|
+
*/
|
|
541
|
+
filter(predicate) {
|
|
542
|
+
return new AggregateExpression(this.fn, this.arg, {
|
|
543
|
+
...this.options,
|
|
544
|
+
filterExpr: predicate
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
distinct() {
|
|
548
|
+
return new AggregateExpression(this.fn, this.arg, {
|
|
549
|
+
...this.options,
|
|
550
|
+
distinct: true
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
toExpressionNode() {
|
|
554
|
+
return {
|
|
555
|
+
kind: "aggregate",
|
|
556
|
+
fn: this.fn,
|
|
557
|
+
arg: this.arg?.toExpressionNode(),
|
|
558
|
+
distinct: this.options.distinct,
|
|
559
|
+
filter: this.options.filterExpr?.toExpressionNode()
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
const binary = (operator, left, right) => new NodeExpression({
|
|
564
|
+
kind: "binary",
|
|
565
|
+
operator,
|
|
566
|
+
left: left.toExpressionNode(),
|
|
567
|
+
right: right.toExpressionNode()
|
|
568
|
+
});
|
|
569
|
+
/**
|
|
570
|
+
* Coerces a raw value into a bound-literal expression; passes expressions through.
|
|
571
|
+
*/
|
|
572
|
+
const coerceValue = (value) => {
|
|
573
|
+
if (value instanceof Expression) return value;
|
|
574
|
+
return new NodeExpression({
|
|
575
|
+
kind: "value",
|
|
576
|
+
value
|
|
577
|
+
});
|
|
578
|
+
};
|
|
579
|
+
/**
|
|
580
|
+
* Coerces a bare string into a column reference; passes expressions through.
|
|
581
|
+
*/
|
|
582
|
+
const coerceColumn = (value) => {
|
|
583
|
+
if (value instanceof Expression) return value;
|
|
584
|
+
if (typeof value === "string") return col(value);
|
|
585
|
+
return coerceValue(value);
|
|
586
|
+
};
|
|
587
|
+
/**
|
|
588
|
+
* A typed column reference. Supports joined `table.column` syntax.
|
|
589
|
+
*/
|
|
590
|
+
const col = (name) => new NodeExpression({
|
|
591
|
+
kind: "column",
|
|
592
|
+
name
|
|
593
|
+
});
|
|
594
|
+
/**
|
|
595
|
+
* A bound literal value (parameterized, never interpolated).
|
|
596
|
+
*/
|
|
597
|
+
const val = (value) => new NodeExpression({
|
|
598
|
+
kind: "value",
|
|
599
|
+
value
|
|
600
|
+
});
|
|
601
|
+
/**
|
|
602
|
+
* Raw SQL escape hatch with positional `?` bindings.
|
|
603
|
+
*/
|
|
604
|
+
const raw = (sql, bindings = []) => new NodeExpression({
|
|
605
|
+
kind: "raw",
|
|
606
|
+
sql,
|
|
607
|
+
bindings
|
|
608
|
+
});
|
|
609
|
+
/**
|
|
610
|
+
* Starts a `CASE WHEN condition THEN result` expression.
|
|
611
|
+
*/
|
|
612
|
+
const caseWhen = (condition, result) => new CaseExpression([{
|
|
613
|
+
when: condition,
|
|
614
|
+
then: coerceValue(result)
|
|
615
|
+
}]);
|
|
616
|
+
/**
|
|
617
|
+
* `COALESCE(a, b, …)` — first non-null argument. Bare strings are columns.
|
|
618
|
+
*/
|
|
619
|
+
const coalesce = (...args) => new NodeExpression({
|
|
620
|
+
kind: "function",
|
|
621
|
+
name: "coalesce",
|
|
622
|
+
args: args.map((arg) => coerceColumn(arg).toExpressionNode())
|
|
623
|
+
});
|
|
624
|
+
/**
|
|
625
|
+
* An arbitrary SQL function call. Bare-string arguments are treated as columns.
|
|
626
|
+
*/
|
|
627
|
+
const fn = (name, ...args) => new NodeExpression({
|
|
628
|
+
kind: "function",
|
|
629
|
+
name,
|
|
630
|
+
args: args.map((arg) => coerceColumn(arg).toExpressionNode())
|
|
631
|
+
});
|
|
632
|
+
/**
|
|
633
|
+
* JSON value extraction: `json('metadata', 'billType')` => `metadata ->> 'billType'`.
|
|
634
|
+
*/
|
|
635
|
+
const json = (column, ...path) => new JsonExpression(column, path.map(String));
|
|
636
|
+
/**
|
|
637
|
+
* `SUM(expr)`; a bare-string argument is treated as a column.
|
|
638
|
+
*/
|
|
639
|
+
const sum = (arg) => new AggregateExpression("sum", coerceColumn(arg));
|
|
640
|
+
/**
|
|
641
|
+
* `AVG(expr)`; a bare-string argument is treated as a column.
|
|
642
|
+
*/
|
|
643
|
+
const avg = (arg) => new AggregateExpression("avg", coerceColumn(arg));
|
|
644
|
+
/**
|
|
645
|
+
* `MIN(expr)`; a bare-string argument is treated as a column.
|
|
646
|
+
*/
|
|
647
|
+
const min = (arg) => new AggregateExpression("min", coerceColumn(arg));
|
|
648
|
+
/**
|
|
649
|
+
* `MAX(expr)`; a bare-string argument is treated as a column.
|
|
650
|
+
*/
|
|
651
|
+
const max = (arg) => new AggregateExpression("max", coerceColumn(arg));
|
|
652
|
+
/**
|
|
653
|
+
* `COUNT(expr)` — or `COUNT(*)` when called without an argument.
|
|
654
|
+
*/
|
|
655
|
+
const count = (arg) => new AggregateExpression("count", arg === void 0 ? void 0 : coerceColumn(arg));
|
|
656
|
+
const EXPRESSION_OPERATORS = {
|
|
657
|
+
"=": "=",
|
|
658
|
+
"==": "=",
|
|
659
|
+
"!=": "!=",
|
|
660
|
+
"<>": "!=",
|
|
661
|
+
">": ">",
|
|
662
|
+
">=": ">=",
|
|
663
|
+
"<": "<",
|
|
664
|
+
"<=": "<=",
|
|
665
|
+
like: "like",
|
|
666
|
+
ilike: "ilike",
|
|
667
|
+
"not like": "not-like",
|
|
668
|
+
"not ilike": "not-ilike"
|
|
669
|
+
};
|
|
670
|
+
/**
|
|
671
|
+
* Builds a comparison predicate: `where('createdAt', '>=', boundary)`. Handy as an
|
|
672
|
+
* inline predicate for `caseWhen`, `having`, and aggregate `.filter(…)`.
|
|
673
|
+
*/
|
|
674
|
+
const where = (column, operator, value) => {
|
|
675
|
+
const normalized = EXPRESSION_OPERATORS[operator];
|
|
676
|
+
if (!normalized) throw new Error(`Unsupported expression operator [${operator}].`);
|
|
677
|
+
return binary(normalized, col(column), coerceValue(value));
|
|
678
|
+
};
|
|
679
|
+
/**
|
|
680
|
+
* The expression-builder namespace passed to `static computed` factories, so a
|
|
681
|
+
* model can declare a virtual attribute as `category: (e) => e.coalesce(…)`.
|
|
682
|
+
*/
|
|
683
|
+
const expressionBuilder = {
|
|
684
|
+
col,
|
|
685
|
+
val,
|
|
686
|
+
raw,
|
|
687
|
+
caseWhen,
|
|
688
|
+
coalesce,
|
|
689
|
+
fn,
|
|
690
|
+
json,
|
|
691
|
+
sum,
|
|
692
|
+
avg,
|
|
693
|
+
min,
|
|
694
|
+
max,
|
|
695
|
+
count,
|
|
696
|
+
where
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
//#endregion
|
|
700
|
+
//#region src/helpers/generated-column.ts
|
|
701
|
+
/**
|
|
702
|
+
* Resolves a generated-column expression into raw Postgres SQL. Generated columns
|
|
703
|
+
* cannot carry bind parameters (they must be immutable), so literal values are
|
|
704
|
+
* inlined and aggregates are rejected.
|
|
705
|
+
*/
|
|
706
|
+
const resolveGeneratedExpression = (expression) => {
|
|
707
|
+
if (typeof expression === "string") return expression;
|
|
708
|
+
return expressionNodeToSql(expression(expressionBuilder).toExpressionNode());
|
|
709
|
+
};
|
|
710
|
+
const quoteIdentifier = (name) => name.split(".").map((part) => `"${part.replace(/"/g, "\"\"")}"`).join(".");
|
|
711
|
+
const quoteLiteral = (value) => {
|
|
712
|
+
if (value === null || value === void 0) return "null";
|
|
713
|
+
if (typeof value === "number" || typeof value === "bigint") return String(value);
|
|
714
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
715
|
+
if (value instanceof Date) return `'${value.toISOString()}'`;
|
|
716
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
717
|
+
};
|
|
718
|
+
const BINARY_OPERATORS = {
|
|
719
|
+
"=": "=",
|
|
720
|
+
"!=": "!=",
|
|
721
|
+
">": ">",
|
|
722
|
+
">=": ">=",
|
|
723
|
+
"<": "<",
|
|
724
|
+
"<=": "<=",
|
|
725
|
+
like: "like",
|
|
726
|
+
ilike: "ilike",
|
|
727
|
+
"not-like": "not like",
|
|
728
|
+
"not-ilike": "not ilike",
|
|
729
|
+
and: "and",
|
|
730
|
+
or: "or",
|
|
731
|
+
"+": "+",
|
|
732
|
+
"-": "-",
|
|
733
|
+
"*": "*",
|
|
734
|
+
"/": "/"
|
|
735
|
+
};
|
|
736
|
+
const FUNCTION_NAME = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
737
|
+
const jsonAccessorSql = (node) => {
|
|
738
|
+
const base = `${quoteIdentifier(node.column)}::jsonb`;
|
|
739
|
+
let accessor;
|
|
740
|
+
if (node.path.length === 0) accessor = base;
|
|
741
|
+
else if (node.path.length === 1) accessor = `(${base} ->> ${quoteLiteral(node.path[0])})`;
|
|
742
|
+
else accessor = `(${base} #>> '{${node.path.join(",")}}'::text[])`;
|
|
743
|
+
if (node.cast === "number") return `(${accessor})::numeric`;
|
|
744
|
+
if (node.cast === "boolean") return `(${accessor})::boolean`;
|
|
745
|
+
return accessor;
|
|
746
|
+
};
|
|
747
|
+
const expressionNodeToSql = (node) => {
|
|
748
|
+
switch (node.kind) {
|
|
749
|
+
case "column": return quoteIdentifier(node.name);
|
|
750
|
+
case "value": return quoteLiteral(node.value);
|
|
751
|
+
case "raw": return inlineRawSql(node.sql, node.bindings);
|
|
752
|
+
case "json": return jsonAccessorSql(node);
|
|
753
|
+
case "function":
|
|
754
|
+
if (!FUNCTION_NAME.test(node.name)) throw new ArkormException(`Unsupported SQL function name [${node.name}].`);
|
|
755
|
+
return `${node.name}(${node.args.map(expressionNodeToSql).join(", ")})`;
|
|
756
|
+
case "case": return `case ${node.cases.map((branch) => `when ${expressionNodeToSql(branch.when)} then ${expressionNodeToSql(branch.then)}`).join(" ")}${node.else ? ` else ${expressionNodeToSql(node.else)}` : ""} end`;
|
|
757
|
+
case "binary": {
|
|
758
|
+
const operator = BINARY_OPERATORS[node.operator];
|
|
759
|
+
return `(${expressionNodeToSql(node.left)} ${operator} ${expressionNodeToSql(node.right)})`;
|
|
760
|
+
}
|
|
761
|
+
case "in": {
|
|
762
|
+
const values = node.values.map(expressionNodeToSql).join(", ");
|
|
763
|
+
return `(${expressionNodeToSql(node.operand)} ${node.not ? "not in" : "in"} (${values}))`;
|
|
764
|
+
}
|
|
765
|
+
case "null-check": return `(${expressionNodeToSql(node.operand)} is ${node.not ? "not null" : "null"})`;
|
|
766
|
+
case "aggregate": throw new ArkormException("Aggregate expressions are not allowed in generated columns.");
|
|
767
|
+
default: throw new ArkormException(`Unsupported expression node [${node.kind}].`);
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
const inlineRawSql = (sql, bindings) => {
|
|
771
|
+
const segments = sql.split("?");
|
|
772
|
+
if (segments.length !== bindings.length + 1) throw new ArkormException("Raw expression bindings do not match the number of placeholders.");
|
|
773
|
+
return segments.reduce((accumulator, segment, index) => {
|
|
774
|
+
const binding = index < bindings.length ? quoteLiteral(bindings[index]) : "";
|
|
775
|
+
return accumulator + segment + binding;
|
|
776
|
+
}, "");
|
|
777
|
+
};
|
|
778
|
+
|
|
366
779
|
//#endregion
|
|
367
780
|
//#region src/database/TableBuilder.ts
|
|
368
781
|
const PRISMA_ENUM_MEMBER_REGEX$1 = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
@@ -455,6 +868,15 @@ var EnumBuilder = class {
|
|
|
455
868
|
this.tableBuilder.map(name, this.columnName);
|
|
456
869
|
return this;
|
|
457
870
|
}
|
|
871
|
+
/**
|
|
872
|
+
* Marks the enum column as an in-place change to an existing column.
|
|
873
|
+
*
|
|
874
|
+
* @returns
|
|
875
|
+
*/
|
|
876
|
+
change() {
|
|
877
|
+
this.tableBuilder.change(this.columnName);
|
|
878
|
+
return this;
|
|
879
|
+
}
|
|
458
880
|
};
|
|
459
881
|
/**
|
|
460
882
|
* The TableBuilder class provides a fluent interface for defining
|
|
@@ -467,6 +889,7 @@ var TableBuilder = class {
|
|
|
467
889
|
constructor() {
|
|
468
890
|
this.columns = [];
|
|
469
891
|
this.dropColumnNames = [];
|
|
892
|
+
this.changeColumnNames = /* @__PURE__ */ new Set();
|
|
470
893
|
this.indexes = [];
|
|
471
894
|
this.foreignKeys = [];
|
|
472
895
|
this.compositeUniqueConstraints = [];
|
|
@@ -656,6 +1079,30 @@ var TableBuilder = class {
|
|
|
656
1079
|
return this.column(name, "dateTime", options);
|
|
657
1080
|
}
|
|
658
1081
|
/**
|
|
1082
|
+
* Defines a database-computed column (`GENERATED ALWAYS AS (…) STORED`). The
|
|
1083
|
+
* expression may be a raw SQL string or an expression-builder factory, and must
|
|
1084
|
+
* be immutable and reference only the row's own columns. Combine with
|
|
1085
|
+
* {@link index} to index the generated value.
|
|
1086
|
+
*
|
|
1087
|
+
* @example
|
|
1088
|
+
* table.generated('category', "case when metadata->>'kind' = 'a' then 'x' else 'y' end", {
|
|
1089
|
+
* type: 'text',
|
|
1090
|
+
* })
|
|
1091
|
+
* table.generated('total', (e) => e.col('price').times(e.col('quantity')), { type: 'integer' })
|
|
1092
|
+
*
|
|
1093
|
+
* @param name The generated column name.
|
|
1094
|
+
* @param expression The SQL expression string, or a builder factory.
|
|
1095
|
+
* @param options Column type (default `text`), `stored` flag, and nullability.
|
|
1096
|
+
* @returns
|
|
1097
|
+
*/
|
|
1098
|
+
generated(name, expression, options = {}) {
|
|
1099
|
+
return this.column(name, options.type ?? "text", {
|
|
1100
|
+
generatedExpression: resolveGeneratedExpression(expression),
|
|
1101
|
+
generatedStored: options.stored ?? true,
|
|
1102
|
+
nullable: options.nullable
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
659
1106
|
* Defines colonns for a polymorphic relationship in the table.
|
|
660
1107
|
*
|
|
661
1108
|
* @param name The base name for the polymorphic relationship columns.
|
|
@@ -780,6 +1227,24 @@ var TableBuilder = class {
|
|
|
780
1227
|
return this;
|
|
781
1228
|
}
|
|
782
1229
|
/**
|
|
1230
|
+
* Marks a (re)defined column as a change to an existing column rather than an
|
|
1231
|
+
* addition. Use it at the end of a normal column chain inside `alterTable` to
|
|
1232
|
+
* redefine the column's type, nullability, default, or enum values in place:
|
|
1233
|
+
*
|
|
1234
|
+
* ```ts
|
|
1235
|
+
* table.string('status').default('active').change()
|
|
1236
|
+
* table.enum('role', ['admin', 'user', 'guest']).change()
|
|
1237
|
+
* ```
|
|
1238
|
+
*
|
|
1239
|
+
* @param columnName Optional explicit column name. When omitted, applies to the latest defined column.
|
|
1240
|
+
* @returns The current TableBuilder instance for chaining.
|
|
1241
|
+
*/
|
|
1242
|
+
change(columnName) {
|
|
1243
|
+
const column = this.resolveColumn(columnName);
|
|
1244
|
+
this.changeColumnNames.add(column.name);
|
|
1245
|
+
return this;
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
783
1248
|
* Marks a column as nullable.
|
|
784
1249
|
*
|
|
785
1250
|
* @param columnName Optional explicit column name. When omitted, applies to the latest defined column.
|
|
@@ -888,7 +1353,18 @@ var TableBuilder = class {
|
|
|
888
1353
|
* @returns
|
|
889
1354
|
*/
|
|
890
1355
|
getColumns() {
|
|
891
|
-
return this.columns.map((column) => ({
|
|
1356
|
+
return this.columns.filter((column) => !this.changeColumnNames.has(column.name)).map((column) => ({
|
|
1357
|
+
...column,
|
|
1358
|
+
enumValues: column.enumValues ? [...column.enumValues] : void 0
|
|
1359
|
+
}));
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* Returns a deep copy of the columns flagged for in-place change via `change()`.
|
|
1363
|
+
*
|
|
1364
|
+
* @returns
|
|
1365
|
+
*/
|
|
1366
|
+
getChangeColumns() {
|
|
1367
|
+
return this.columns.filter((column) => this.changeColumnNames.has(column.name)).map((column) => ({
|
|
892
1368
|
...column,
|
|
893
1369
|
enumValues: column.enumValues ? [...column.enumValues] : void 0
|
|
894
1370
|
}));
|
|
@@ -962,6 +1438,8 @@ var TableBuilder = class {
|
|
|
962
1438
|
updatedAt: options.updatedAt,
|
|
963
1439
|
precision: options.precision,
|
|
964
1440
|
scale: options.scale,
|
|
1441
|
+
generatedExpression: options.generatedExpression,
|
|
1442
|
+
generatedStored: options.generatedStored,
|
|
965
1443
|
primaryKeyGeneration: options.primaryKeyGeneration
|
|
966
1444
|
});
|
|
967
1445
|
const column = this.columns[this.columns.length - 1];
|
|
@@ -1110,6 +1588,7 @@ var SchemaBuilder = class SchemaBuilder {
|
|
|
1110
1588
|
type: "alterTable",
|
|
1111
1589
|
table,
|
|
1112
1590
|
addColumns: builder.getColumns(),
|
|
1591
|
+
changeColumns: builder.getChangeColumns(),
|
|
1113
1592
|
dropColumns: builder.getDropColumns(),
|
|
1114
1593
|
addIndexes: builder.getIndexes(),
|
|
1115
1594
|
addForeignKeys: builder.getForeignKeys(),
|
|
@@ -1164,6 +1643,10 @@ var SchemaBuilder = class SchemaBuilder {
|
|
|
1164
1643
|
...column,
|
|
1165
1644
|
enumValues: column.enumValues ? [...column.enumValues] : void 0
|
|
1166
1645
|
})),
|
|
1646
|
+
changeColumns: operation.changeColumns?.map((column) => ({
|
|
1647
|
+
...column,
|
|
1648
|
+
enumValues: column.enumValues ? [...column.enumValues] : void 0
|
|
1649
|
+
})),
|
|
1167
1650
|
dropColumns: [...operation.dropColumns],
|
|
1168
1651
|
addIndexes: operation.addIndexes.map((index) => ({
|
|
1169
1652
|
...index,
|
|
@@ -1340,6 +1823,10 @@ const buildFieldLine = (column) => {
|
|
|
1340
1823
|
const mapped = typeof column.map === "string" && column.map.trim().length > 0 ? ` @map("${column.map.replace(/"/g, "\\\"")}")` : "";
|
|
1341
1824
|
const updatedAt = column.updatedAt ? " @updatedAt" : "";
|
|
1342
1825
|
const nativeType = column.type === "decimal" ? ` @db.Decimal(${column.precision ?? 8}, ${column.scale ?? 2})` : "";
|
|
1826
|
+
if (column.generatedExpression) {
|
|
1827
|
+
const escaped = column.generatedExpression.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
1828
|
+
return ` ${column.name} ${scalar}${nullable}${unique} @default(dbgenerated("${escaped}"))${mapped}${nativeType}`;
|
|
1829
|
+
}
|
|
1343
1830
|
const defaultValue = column.type === "enum" ? formatEnumDefaultValue(column.default) : column.primaryKeyGeneration?.prismaDefault ?? formatDefaultValue(column.default);
|
|
1344
1831
|
const defaultSuffix = defaultValue ? ` ${defaultValue}` : "";
|
|
1345
1832
|
return ` ${column.name} ${scalar}${nullable}${primary}${unique}${defaultSuffix}${updatedAt}${mapped}${nativeType}`;
|
|
@@ -1667,11 +2154,18 @@ const applyCreateTableOperation = (schema, operation) => {
|
|
|
1667
2154
|
const applyAlterTableOperation = (schema, operation) => {
|
|
1668
2155
|
const model = findModelBlock(schema, operation.table);
|
|
1669
2156
|
if (!model) throw new ArkormException(`Prisma model for table [${operation.table}] was not found.`);
|
|
1670
|
-
const schemaWithEnums = ensureEnumBlocks(schema, operation.addColumns);
|
|
2157
|
+
const schemaWithEnums = ensureEnumBlocks(schema, [...operation.addColumns, ...operation.changeColumns ?? []]);
|
|
1671
2158
|
const refreshedModel = findModelBlock(schemaWithEnums, operation.table);
|
|
1672
2159
|
if (!refreshedModel) throw new ArkormException(`Prisma model for table [${operation.table}] was not found.`);
|
|
1673
2160
|
let block = refreshedModel.block;
|
|
1674
2161
|
const bodyLines = block.split("\n");
|
|
2162
|
+
(operation.changeColumns ?? []).forEach((column) => {
|
|
2163
|
+
const fieldLine = buildFieldLine(column);
|
|
2164
|
+
const columnRegex = new RegExp(`^\\s*${escapeRegex(column.name)}\\s+`);
|
|
2165
|
+
const index = bodyLines.findIndex((line) => columnRegex.test(line));
|
|
2166
|
+
if (index >= 0) bodyLines.splice(index, 1, fieldLine);
|
|
2167
|
+
else bodyLines.splice(Math.max(1, bodyLines.length - 1), 0, fieldLine);
|
|
2168
|
+
});
|
|
1675
2169
|
operation.dropColumns.forEach((column) => {
|
|
1676
2170
|
const columnRegex = new RegExp(`^\\s*${escapeRegex(column)}\\s+`);
|
|
1677
2171
|
for (let index = 0; index < bodyLines.length; index += 1) if (columnRegex.test(bodyLines[index])) {
|
|
@@ -1896,14 +2390,18 @@ const stripPrismaSchemaModelsAndEnums = (schema) => {
|
|
|
1896
2390
|
};
|
|
1897
2391
|
const applyMigrationToDatabase = async (adapter, migration) => {
|
|
1898
2392
|
if (!supportsDatabaseMigrationExecution(adapter)) throw new ArkormException("The configured adapter does not support database-backed migration execution.");
|
|
1899
|
-
const
|
|
2393
|
+
const instance = typeof migration === "function" ? new migration() : migration;
|
|
2394
|
+
const operations = await getMigrationPlan(instance, "up");
|
|
1900
2395
|
await adapter.executeSchemaOperations(operations);
|
|
2396
|
+
await instance.done?.("up");
|
|
1901
2397
|
return { operations };
|
|
1902
2398
|
};
|
|
1903
2399
|
const applyMigrationRollbackToDatabase = async (adapter, migration) => {
|
|
1904
2400
|
if (!supportsDatabaseMigrationExecution(adapter)) throw new ArkormException("The configured adapter does not support database-backed migration execution.");
|
|
1905
|
-
const
|
|
2401
|
+
const instance = typeof migration === "function" ? new migration() : migration;
|
|
2402
|
+
const operations = await getMigrationPlan(instance, "down");
|
|
1906
2403
|
await adapter.executeSchemaOperations(operations);
|
|
2404
|
+
await instance.done?.("down");
|
|
1907
2405
|
return { operations };
|
|
1908
2406
|
};
|
|
1909
2407
|
/**
|
|
@@ -2761,6 +3259,7 @@ var PrismaDatabaseAdapter = class PrismaDatabaseAdapter {
|
|
|
2761
3259
|
rawWhere: false,
|
|
2762
3260
|
distinct: false,
|
|
2763
3261
|
groupBy: false,
|
|
3262
|
+
expressions: false,
|
|
2764
3263
|
returning: false
|
|
2765
3264
|
};
|
|
2766
3265
|
}
|
|
@@ -2796,6 +3295,14 @@ var PrismaDatabaseAdapter = class PrismaDatabaseAdapter {
|
|
|
2796
3295
|
}
|
|
2797
3296
|
toQuerySelect(columns) {
|
|
2798
3297
|
if (!columns || columns.length === 0) return void 0;
|
|
3298
|
+
const expressionColumn = columns.find((column) => column.expression);
|
|
3299
|
+
if (expressionColumn) throw new UnsupportedAdapterFeatureException("Expression select columns are not supported by the Prisma compatibility adapter; use a SQL-backed adapter.", {
|
|
3300
|
+
operation: "adapter.select",
|
|
3301
|
+
meta: {
|
|
3302
|
+
feature: "expressions",
|
|
3303
|
+
alias: expressionColumn.alias
|
|
3304
|
+
}
|
|
3305
|
+
});
|
|
2799
3306
|
const rawColumn = columns.find((column) => column.raw);
|
|
2800
3307
|
if (rawColumn) throw new UnsupportedAdapterFeatureException("Raw select expressions are not supported by the Prisma compatibility adapter; use a SQL-backed adapter or DB.raw().", {
|
|
2801
3308
|
operation: "adapter.select",
|
|
@@ -2813,6 +3320,10 @@ var PrismaDatabaseAdapter = class PrismaDatabaseAdapter {
|
|
|
2813
3320
|
}
|
|
2814
3321
|
toQueryOrderBy(orderBy) {
|
|
2815
3322
|
if (!orderBy || orderBy.length === 0) return void 0;
|
|
3323
|
+
if (orderBy.some((entry) => entry.expression)) throw new UnsupportedAdapterFeatureException("Order-by expressions are not supported by the Prisma compatibility adapter; use a SQL-backed adapter.", {
|
|
3324
|
+
operation: "adapter.select",
|
|
3325
|
+
meta: { feature: "expressions" }
|
|
3326
|
+
});
|
|
2816
3327
|
return orderBy.map((entry) => ({ [entry.column]: entry.direction }));
|
|
2817
3328
|
}
|
|
2818
3329
|
toComparisonWhere(condition) {
|
|
@@ -4173,13 +4684,22 @@ var Migration = class {
|
|
|
4173
4684
|
static {
|
|
4174
4685
|
this[MIGRATION_BRAND] = true;
|
|
4175
4686
|
}
|
|
4687
|
+
/**
|
|
4688
|
+
* Optional lifecycle hook invoked after the migration's schema operations
|
|
4689
|
+
* have been applied to the database, for either direction. Override it to run
|
|
4690
|
+
* extra logic such as seeding or data backfills once the schema is in place.
|
|
4691
|
+
*
|
|
4692
|
+
* @param direction The direction that just ran (`'up'` or `'down'`).
|
|
4693
|
+
*/
|
|
4694
|
+
done(_direction) {}
|
|
4176
4695
|
};
|
|
4177
4696
|
|
|
4178
4697
|
//#endregion
|
|
4179
4698
|
//#region src/cli/commands/MigrateCommand.ts
|
|
4180
4699
|
/**
|
|
4181
4700
|
* The MigrateCommand class implements the CLI command for applying migration
|
|
4182
|
-
* classes to the Prisma schema and running the Prisma workflow
|
|
4701
|
+
* classes to the database or Prisma schema and running the Prisma workflow when
|
|
4702
|
+
* using the Prisma compatibility driver.
|
|
4183
4703
|
*
|
|
4184
4704
|
* @author Legacy (3m1n3nc3)
|
|
4185
4705
|
* @since 0.1.0
|
|
@@ -4190,22 +4710,22 @@ var MigrateCommand = class extends Command {
|
|
|
4190
4710
|
this.signature = `migrate
|
|
4191
4711
|
{name? : Migration class or file name}
|
|
4192
4712
|
{--all : Run all migrations from the configured migrations directory}
|
|
4193
|
-
{--deploy : Use prisma migrate deploy instead of migrate dev}
|
|
4194
|
-
{--skip-generate : Skip prisma generate}
|
|
4713
|
+
{--deploy : Use prisma migrate deploy instead of migrate dev (Prisma compatibility driver only)}
|
|
4714
|
+
{--skip-generate : Skip prisma generate (Prisma compatibility driver only)}
|
|
4195
4715
|
{--skip-migrate : Skip prisma migrate command}
|
|
4196
4716
|
{--state-file= : Path to applied migration state file}
|
|
4197
|
-
{--schema= : Explicit prisma schema path}
|
|
4198
|
-
{--migration-name= : Name for prisma migrate dev}
|
|
4717
|
+
{--schema= : Explicit prisma schema path (Prisma compatibility driver only)}
|
|
4718
|
+
{--migration-name= : Name for prisma migrate dev (Prisma compatibility driver only)}
|
|
4199
4719
|
{--create-database : Create the configured database without prompting}
|
|
4200
4720
|
`;
|
|
4201
|
-
this.description = "Apply migration classes to schema.prisma and run Prisma workflow";
|
|
4721
|
+
this.description = "Apply migration classes to the database or schema.prisma and run Prisma workflow when using the Prisma compatibility driver";
|
|
4202
4722
|
}
|
|
4203
4723
|
/**
|
|
4204
4724
|
* Command handler for the migrate command.
|
|
4205
4725
|
* This method is responsible for orchestrating the migration
|
|
4206
4726
|
* process, including loading migration classes, applying them to
|
|
4207
|
-
* the Prisma schema, and running the appropriate Prisma commands
|
|
4208
|
-
* based on the provided options.
|
|
4727
|
+
* the the database or Prisma schema, and running the appropriate Prisma commands
|
|
4728
|
+
* when using the Prisma compatibility driver based on the provided options.
|
|
4209
4729
|
*
|
|
4210
4730
|
* @returns
|
|
4211
4731
|
*/
|
|
@@ -4414,10 +4934,10 @@ var MigrateFreshCommand = class extends Command {
|
|
|
4414
4934
|
constructor(..._args) {
|
|
4415
4935
|
super(..._args);
|
|
4416
4936
|
this.signature = `migrate:fresh
|
|
4417
|
-
{--skip-generate : Skip prisma generate}
|
|
4418
|
-
{--skip-migrate : Skip prisma database sync}
|
|
4937
|
+
{--skip-generate : Skip prisma generate (Prisma compatibility driver only)}
|
|
4938
|
+
{--skip-migrate : Skip prisma database sync (Prisma compatibility driver only)}
|
|
4419
4939
|
{--state-file= : Path to applied migration state file}
|
|
4420
|
-
{--schema= : Explicit prisma schema path}
|
|
4940
|
+
{--schema= : Explicit prisma schema path (Prisma compatibility driver only)}
|
|
4421
4941
|
{--create-database : Create the configured database without prompting}
|
|
4422
4942
|
`;
|
|
4423
4943
|
this.description = "Reset the database and rerun all migration classes";
|
|
@@ -4572,12 +5092,12 @@ var MigrateRollbackCommand = class extends Command {
|
|
|
4572
5092
|
this.signature = `migrate:rollback
|
|
4573
5093
|
{--step= : Number of latest applied migration classes to rollback}
|
|
4574
5094
|
{--dry-run : Preview rollback targets without applying changes}
|
|
4575
|
-
{--deploy : Use prisma migrate deploy instead of migrate dev}
|
|
4576
|
-
{--skip-generate : Skip prisma generate}
|
|
4577
|
-
{--skip-migrate : Skip prisma migrate command}
|
|
5095
|
+
{--deploy : Use prisma migrate deploy instead of migrate dev (Prisma compatibility driver only)}
|
|
5096
|
+
{--skip-generate : Skip prisma generate (Prisma compatibility driver only)}
|
|
5097
|
+
{--skip-migrate : Skip prisma migrate command (Prisma compatibility driver only)}
|
|
4578
5098
|
{--state-file= : Path to applied migration state file}
|
|
4579
|
-
{--schema= : Explicit prisma schema path}
|
|
4580
|
-
{--migration-name= : Name for prisma migrate dev}
|
|
5099
|
+
{--schema= : Explicit prisma schema path (Prisma compatibility driver only)}
|
|
5100
|
+
{--migration-name= : Name for prisma migrate dev (Prisma compatibility driver only)}
|
|
4581
5101
|
`;
|
|
4582
5102
|
this.description = "Rollback migration classes from schema.prisma and run Prisma workflow";
|
|
4583
5103
|
}
|
|
@@ -4592,7 +5112,7 @@ var MigrateRollbackCommand = class extends Command {
|
|
|
4592
5112
|
const useDatabaseMigrations = supportsDatabaseMigrationExecution(adapter);
|
|
4593
5113
|
const persistedFeatures = resolvePersistedMetadataFeatures(this.app.getConfig("features"));
|
|
4594
5114
|
let appliedState = await readAppliedMigrationsStateFromStore(adapter, stateFilePath);
|
|
4595
|
-
const stepOption = this.option("step");
|
|
5115
|
+
const stepOption = this.option("step", 1);
|
|
4596
5116
|
const stepCount = stepOption == null ? void 0 : Number(stepOption);
|
|
4597
5117
|
if (stepCount != null && (!Number.isFinite(stepCount) || stepCount <= 0 || !Number.isInteger(stepCount))) return void this.error("Error: --step must be a positive integer.");
|
|
4598
5118
|
const targets = stepCount ? getLatestAppliedMigrations(appliedState, stepCount) : (() => {
|