tempest-db-js 0.4.0 → 0.5.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/README.md +8 -2
- package/dist/bin.cjs +159 -56
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +2 -2
- package/dist/{chunk-JR4MLFQN.js → chunk-5QQMVTS5.js} +881 -257
- package/dist/chunk-5QQMVTS5.js.map +1 -0
- package/dist/{chunk-43XL66JG.js → chunk-EPMLFNFK.js} +103 -30
- package/dist/chunk-EPMLFNFK.js.map +1 -0
- package/dist/index.cjs +884 -254
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +532 -29
- package/dist/index.d.ts +532 -29
- package/dist/index.js +1 -1
- package/dist/migrations/index.cjs +214 -62
- package/dist/migrations/index.cjs.map +1 -1
- package/dist/migrations/index.d.cts +12 -2
- package/dist/migrations/index.d.ts +12 -2
- package/dist/migrations/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-43XL66JG.js.map +0 -1
- package/dist/chunk-JR4MLFQN.js.map +0 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
📖 **Documentation:** [Português (BR)](https://mauriciobenjamin700.github.io/tempest-db-js/) · [English (US)](https://mauriciobenjamin700.github.io/tempest-db-js/en/)
|
|
7
7
|
|
|
8
|
-
> ✅ **Status: alpha (v0.
|
|
8
|
+
> ✅ **Status: alpha (v0.5.0), published on [npm](https://www.npmjs.com/package/tempest-db-js).** The full path works end-to-end — declarative models with **foreign keys, UNIQUE, table constraints, explicit column names and PostgreSQL arrays**, a typed query builder (aggregations, `DISTINCT`, upsert with **partial-index predicates**, **`FOR UPDATE SKIP LOCKED`**, SQL expressions in writes), **real SQLite + PostgreSQL execution**, a **MySQL** dialect, joins, relations, Alembic-style migrations (sync + **async** runner) with a `tempest-db` CLI, a typed `BaseRepository`, an opt-in active-record layer, and a `session.raw` escape hatch. The public API may still shift before v1.0.
|
|
9
9
|
|
|
10
10
|
## Why tempest-db-js
|
|
11
11
|
|
|
@@ -76,6 +76,12 @@ Typed extras, each with a [docs recipe](https://mauriciobenjamin700.github.io/te
|
|
|
76
76
|
- **Upsert** — `insert(Row).values(...).onConflictDoUpdate(["key"], { ... })` / `.onConflictDoNothing(["key"])` (portable SQLite ↔ PostgreSQL).
|
|
77
77
|
- **Active-record (opt-in)** — `activeRecord(User, session)` → `save`/`update`/`delete`/`reload` over `.data`; the plain-object default is unchanged.
|
|
78
78
|
- **Query logging & errors** — `createEngine(url, { onQuery })` traces every statement; a failed statement throws `QueryExecutionError` carrying the SQL + params.
|
|
79
|
+
- **Durable queues** — `select(Job).where(...).limit(10).forUpdate({ skipLocked: true })` inside a transaction hands each worker a disjoint batch, and `set({ attempts: sql.raw("attempts + 1") })` increments in the database instead of read-modify-write. SQLite throws rather than emitting an unlocked `SELECT`.
|
|
80
|
+
- **Partial-index upsert** — `onConflictDoNothing(["consumer", "idempotencyKey"], { where: { idempotencyKey: { isNull: false } } })` repeats the index predicate PostgreSQL requires to match a **partial** unique index as a conflict target.
|
|
81
|
+
- **Column names** — `.name("consumer_name")` per column, or `static naming = "snake_case"` per table: a `snake_case` schema behind a `camelCase` model, mapped everywhere including the migration IR (so no false drift).
|
|
82
|
+
- **PostgreSQL arrays** — `column.array(column.text())` → `text[]` typed as `string[]`, with `contains` (`@>`), `containedBy` (`<@`) and `overlaps` (`&&`) in `where`.
|
|
83
|
+
- **Case-insensitive lookups** — `{ ieq: probe }` → `lower(col) = lower($1)`: no wildcards, matches a `lower(col)` functional index. (`ilike` is pattern matching — `{ ilike: "%" }` matches every row.)
|
|
84
|
+
- **Raw SQL escape hatch** — `session.raw(sql, params, { as: Model })` for the query the builder cannot yet express, always parameterized and integrated with logging, errors and transactions.
|
|
79
85
|
|
|
80
86
|
## Migrations CLI
|
|
81
87
|
|
|
@@ -106,7 +112,7 @@ HTTP integration recipes (Hono, Express, Fastify) live in the [docs](https://mau
|
|
|
106
112
|
|
|
107
113
|
## Roadmap
|
|
108
114
|
|
|
109
|
-
See [ROADMAP.md](./ROADMAP.md). Shipped (v0.
|
|
115
|
+
See [ROADMAP.md](./ROADMAP.md). Shipped (v0.5.0): declarative schema with foreign keys / UNIQUE / table constraints / explicit column names / PostgreSQL arrays, SQLite + PostgreSQL execution (both tested in CI, Postgres against a live database), a MySQL dialect, row locking, SQL expressions in writes, partial-index upsert, `session.raw`, joins, relations, sync + async migration runners with a `tempest-db` CLI, repository, aggregations/upsert, opt-in active-record. Next: subqueries in `IN`/`EXISTS` and `HAVING`, MySQL execution in CI + `RETURNING` round-trip, async CLI wiring, then `tempest-ts-sdk`.
|
|
110
116
|
|
|
111
117
|
## Development
|
|
112
118
|
|
package/dist/bin.cjs
CHANGED
|
@@ -100,6 +100,22 @@ function makeRevisionId(label, parents) {
|
|
|
100
100
|
return hash.toString(16).padStart(8, "0");
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
// src/expressions.ts
|
|
104
|
+
function renderPortableToken(token, dialect) {
|
|
105
|
+
switch (token) {
|
|
106
|
+
case "now":
|
|
107
|
+
return dialect === "postgresql" ? "now()" : "CURRENT_TIMESTAMP";
|
|
108
|
+
case "current_date":
|
|
109
|
+
return "CURRENT_DATE";
|
|
110
|
+
case "current_time":
|
|
111
|
+
return "CURRENT_TIME";
|
|
112
|
+
case "uuidv4":
|
|
113
|
+
if (dialect === "postgresql") return "gen_random_uuid()";
|
|
114
|
+
if (dialect === "mysql") return "(UUID())";
|
|
115
|
+
return "(lower(hex(randomblob(16))))";
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
103
119
|
// src/migrations/ddl.ts
|
|
104
120
|
function quoteId(name, dialect) {
|
|
105
121
|
return dialect === "mysql" ? `\`${name.replace(/`/g, "``")}\`` : `"${name.replace(/"/g, '""')}"`;
|
|
@@ -123,6 +139,8 @@ function renderColumnType(type, dialect) {
|
|
|
123
139
|
return "NUMERIC";
|
|
124
140
|
case "blob":
|
|
125
141
|
return "BLOB";
|
|
142
|
+
case "array":
|
|
143
|
+
throw new Error(unsupportedArray("sqlite"));
|
|
126
144
|
default:
|
|
127
145
|
return "TEXT";
|
|
128
146
|
}
|
|
@@ -164,6 +182,8 @@ function renderColumnType(type, dialect) {
|
|
|
164
182
|
return "CHAR(36)";
|
|
165
183
|
case "enum":
|
|
166
184
|
return `ENUM(${(meta.values ?? []).map(quoteLiteral).join(", ")})`;
|
|
185
|
+
case "array":
|
|
186
|
+
throw new Error(unsupportedArray("mysql"));
|
|
167
187
|
}
|
|
168
188
|
}
|
|
169
189
|
switch (kind) {
|
|
@@ -202,27 +222,39 @@ function renderColumnType(type, dialect) {
|
|
|
202
222
|
return "UUID";
|
|
203
223
|
case "enum":
|
|
204
224
|
return "TEXT";
|
|
225
|
+
case "array":
|
|
226
|
+
return `${renderColumnType(arrayElement(meta.element), dialect)}[]`;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function unsupportedArray(dialect) {
|
|
230
|
+
return `column.array() is PostgreSQL-only \u2014 ${dialect} has no native array type. Model the column as JSON there, accepting that array operators will not work.`;
|
|
231
|
+
}
|
|
232
|
+
function arrayElement(element) {
|
|
233
|
+
if (!element) {
|
|
234
|
+
throw new Error(
|
|
235
|
+
"An array column has no element type \u2014 build it with column.array()."
|
|
236
|
+
);
|
|
205
237
|
}
|
|
238
|
+
return element;
|
|
206
239
|
}
|
|
207
|
-
function renderDefault(def, dialect) {
|
|
240
|
+
function renderDefault(def, dialect, type) {
|
|
208
241
|
if (def.kind === "expression") {
|
|
209
242
|
const expr = def.expression;
|
|
210
|
-
if (typeof expr === "object")
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
return "CURRENT_DATE";
|
|
216
|
-
case "current_time":
|
|
217
|
-
return "CURRENT_TIME";
|
|
218
|
-
case "uuidv4":
|
|
219
|
-
if (dialect === "postgresql") return "gen_random_uuid()";
|
|
220
|
-
if (dialect === "mysql") return "(UUID())";
|
|
221
|
-
return "(lower(hex(randomblob(16))))";
|
|
243
|
+
if (typeof expr === "object") {
|
|
244
|
+
if ("raw" in expr) return expr.raw;
|
|
245
|
+
throw new Error(
|
|
246
|
+
"sql.expr`...` binds parameters and cannot be rendered as a DEFAULT \u2014 use sql.raw()."
|
|
247
|
+
);
|
|
222
248
|
}
|
|
249
|
+
return renderPortableToken(expr, dialect);
|
|
223
250
|
}
|
|
224
251
|
const value = def.value;
|
|
225
252
|
if (value === null) return "NULL";
|
|
253
|
+
if (Array.isArray(value) && type?.kind === "array") {
|
|
254
|
+
const elementType = renderColumnType(arrayElement(type.meta.element), dialect);
|
|
255
|
+
const items = value.map((v) => renderDefault({ kind: "literal", value: v }, dialect));
|
|
256
|
+
return `ARRAY[${items.join(", ")}]::${elementType}[]`;
|
|
257
|
+
}
|
|
226
258
|
if (typeof value === "boolean") {
|
|
227
259
|
return dialect === "postgresql" ? value ? "TRUE" : "FALSE" : value ? "1" : "0";
|
|
228
260
|
}
|
|
@@ -268,7 +300,9 @@ function tableConstraintClauses(table, dialect) {
|
|
|
268
300
|
function renderColumnDef(col, dialect) {
|
|
269
301
|
let sql = `${quoteId(col.name, dialect)} ${renderColumnType(col.type, dialect)}`;
|
|
270
302
|
if (col.notNull) sql += " NOT NULL";
|
|
271
|
-
if (col.default !== null)
|
|
303
|
+
if (col.default !== null) {
|
|
304
|
+
sql += ` DEFAULT ${renderDefault(col.default, dialect, col.type)}`;
|
|
305
|
+
}
|
|
272
306
|
sql += columnConstraintSuffix(col, dialect);
|
|
273
307
|
return sql;
|
|
274
308
|
}
|
|
@@ -292,7 +326,9 @@ function renderCreateTable(table, dialect) {
|
|
|
292
326
|
typeStmts.push(`CREATE TYPE ${quoteId(typeName, dialect)} AS ENUM (${values})`);
|
|
293
327
|
let def = `${quoteId(c.name, dialect)} ${quoteId(typeName, dialect)}`;
|
|
294
328
|
if (c.notNull) def += " NOT NULL";
|
|
295
|
-
if (c.default !== null)
|
|
329
|
+
if (c.default !== null) {
|
|
330
|
+
def += ` DEFAULT ${renderDefault(c.default, dialect, c.type)}`;
|
|
331
|
+
}
|
|
296
332
|
return def + columnConstraintSuffix(c, dialect);
|
|
297
333
|
}
|
|
298
334
|
if (dialect === "postgresql" && isAutoIncrementPk(table, c)) {
|
|
@@ -436,7 +472,7 @@ function renderAlterColumn(table, to, dialect) {
|
|
|
436
472
|
to.notNull ? `ALTER TABLE ${t} ALTER COLUMN ${c} SET NOT NULL` : `ALTER TABLE ${t} ALTER COLUMN ${c} DROP NOT NULL`
|
|
437
473
|
);
|
|
438
474
|
stmts.push(
|
|
439
|
-
to.default !== null ? `ALTER TABLE ${t} ALTER COLUMN ${c} SET DEFAULT ${renderDefault(to.default, dialect)}` : `ALTER TABLE ${t} ALTER COLUMN ${c} DROP DEFAULT`
|
|
475
|
+
to.default !== null ? `ALTER TABLE ${t} ALTER COLUMN ${c} SET DEFAULT ${renderDefault(to.default, dialect, to.type)}` : `ALTER TABLE ${t} ALTER COLUMN ${c} DROP DEFAULT`
|
|
440
476
|
);
|
|
441
477
|
return stmts;
|
|
442
478
|
}
|
|
@@ -611,6 +647,9 @@ function heads(migrations) {
|
|
|
611
647
|
function isDefaultValue(value) {
|
|
612
648
|
return typeof value === "object" && value !== null && "kind" in value && (value.kind === "literal" || value.kind === "expression");
|
|
613
649
|
}
|
|
650
|
+
function bindsParameters(value) {
|
|
651
|
+
return value.kind === "expression" && typeof value.expression === "object" && "parts" in value.expression;
|
|
652
|
+
}
|
|
614
653
|
function parseReference(ref, options) {
|
|
615
654
|
const dot = ref.lastIndexOf(".");
|
|
616
655
|
if (dot <= 0 || dot === ref.length - 1) {
|
|
@@ -624,48 +663,71 @@ function parseReference(ref, options) {
|
|
|
624
663
|
};
|
|
625
664
|
}
|
|
626
665
|
var Column = class _Column {
|
|
627
|
-
constructor(type, flags, defaultValue = null, onUpdateValue = null, reference = null) {
|
|
666
|
+
constructor(type, flags, defaultValue = null, onUpdateValue = null, reference = null, dbName = null) {
|
|
628
667
|
this.type = type;
|
|
629
668
|
this.flags = flags;
|
|
630
669
|
this.defaultValue = defaultValue;
|
|
631
670
|
this.onUpdateValue = onUpdateValue;
|
|
632
671
|
this.reference = reference;
|
|
672
|
+
this.dbName = dbName;
|
|
633
673
|
}
|
|
634
674
|
type;
|
|
635
675
|
flags;
|
|
636
676
|
defaultValue;
|
|
637
677
|
onUpdateValue;
|
|
638
678
|
reference;
|
|
639
|
-
|
|
679
|
+
dbName;
|
|
680
|
+
/** Clone this column with one facet replaced, carrying every other over. */
|
|
681
|
+
derive(patch) {
|
|
640
682
|
return new _Column(
|
|
641
683
|
this.type,
|
|
642
|
-
|
|
643
|
-
this.defaultValue,
|
|
644
|
-
this.onUpdateValue,
|
|
645
|
-
this.reference
|
|
684
|
+
patch.flags ?? this.flags,
|
|
685
|
+
patch.defaultValue !== void 0 ? patch.defaultValue : this.defaultValue,
|
|
686
|
+
patch.onUpdateValue !== void 0 ? patch.onUpdateValue : this.onUpdateValue,
|
|
687
|
+
patch.reference !== void 0 ? patch.reference : this.reference,
|
|
688
|
+
patch.dbName !== void 0 ? patch.dbName : this.dbName
|
|
646
689
|
);
|
|
647
690
|
}
|
|
691
|
+
primaryKey() {
|
|
692
|
+
return this.derive({ flags: { ...this.flags, primaryKey: true, hasDefault: true } });
|
|
693
|
+
}
|
|
648
694
|
notNull() {
|
|
649
|
-
return
|
|
650
|
-
this.type,
|
|
651
|
-
{ ...this.flags, notNull: true },
|
|
652
|
-
this.defaultValue,
|
|
653
|
-
this.onUpdateValue,
|
|
654
|
-
this.reference
|
|
655
|
-
);
|
|
695
|
+
return this.derive({ flags: { ...this.flags, notNull: true } });
|
|
656
696
|
}
|
|
657
697
|
/**
|
|
658
698
|
* Add a `UNIQUE` constraint to the column (mirrors SQLAlchemy's
|
|
659
699
|
* `mapped_column(unique=True)`). DDL-only — does not change the inferred type.
|
|
660
700
|
*/
|
|
661
701
|
unique() {
|
|
662
|
-
return
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
702
|
+
return this.derive({ flags: { ...this.flags, unique: true } });
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Map this property to a differently-named database column, à la SQLAlchemy's
|
|
706
|
+
* `mapped_column("consumer_name")` (Django's `db_column`, Prisma's `@map`).
|
|
707
|
+
*
|
|
708
|
+
* The override applies everywhere the name reaches SQL — select, insert,
|
|
709
|
+
* update, delete, where, order by, group by, returning, conflict targets, the
|
|
710
|
+
* migration IR and the drift check — while the TypeScript row keeps the
|
|
711
|
+
* property name. Use it to keep a `snake_case` schema behind a `camelCase`
|
|
712
|
+
* model; {@link Model.naming} does the same for a whole table at once.
|
|
713
|
+
*
|
|
714
|
+
* @param dbName The real column name in the database.
|
|
715
|
+
* @returns A new column bound to that name.
|
|
716
|
+
* @throws Error When `dbName` is empty.
|
|
717
|
+
*
|
|
718
|
+
* @example
|
|
719
|
+
* ```ts
|
|
720
|
+
* class ApiKey extends Model {
|
|
721
|
+
* static tablename = "api_keys";
|
|
722
|
+
* consumerName = column.text().name("consumer_name").notNull();
|
|
723
|
+
* }
|
|
724
|
+
* ```
|
|
725
|
+
*/
|
|
726
|
+
name(dbName) {
|
|
727
|
+
if (dbName.length === 0) {
|
|
728
|
+
throw new Error("column.name() requires a non-empty database column name.");
|
|
729
|
+
}
|
|
730
|
+
return this.derive({ dbName });
|
|
669
731
|
}
|
|
670
732
|
/**
|
|
671
733
|
* Declare a foreign-key reference to another table's column, à la SQLAlchemy's
|
|
@@ -678,37 +740,50 @@ var Column = class _Column {
|
|
|
678
740
|
* @throws Error When `ref` is not a valid `"table.column"` string.
|
|
679
741
|
*/
|
|
680
742
|
references(ref, options) {
|
|
681
|
-
return
|
|
682
|
-
this.type,
|
|
683
|
-
this.flags,
|
|
684
|
-
this.defaultValue,
|
|
685
|
-
this.onUpdateValue,
|
|
686
|
-
parseReference(ref, options)
|
|
687
|
-
);
|
|
743
|
+
return this.derive({ reference: parseReference(ref, options) });
|
|
688
744
|
}
|
|
689
745
|
/**
|
|
690
746
|
* Set the insert-time default: a constant value of type `T`, or a portable
|
|
691
747
|
* server-side expression from {@link sql} (e.g. `sql.now()`, `sql.uuidv4()`).
|
|
748
|
+
*
|
|
749
|
+
* @param value The literal default, or a {@link sql} expression.
|
|
750
|
+
* @returns A new column carrying the default.
|
|
751
|
+
* @throws Error When given a `sql.expr` fragment — a `DEFAULT` clause has
|
|
752
|
+
* nowhere to bind parameters; use `sql.raw()` for a verbatim expression.
|
|
692
753
|
*/
|
|
693
754
|
default(value) {
|
|
694
755
|
const resolved = isDefaultValue(value) ? value : { kind: "literal", value };
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
756
|
+
if (bindsParameters(resolved)) {
|
|
757
|
+
throw new Error(
|
|
758
|
+
"sql.expr`...` binds parameters and cannot be a column default \u2014 use sql.raw() for a verbatim DEFAULT expression."
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
return this.derive({
|
|
762
|
+
flags: { ...this.flags, hasDefault: true },
|
|
763
|
+
defaultValue: resolved
|
|
764
|
+
});
|
|
702
765
|
}
|
|
703
766
|
/**
|
|
704
767
|
* Re-apply a value whenever the row is updated (e.g. an `updated_at` column
|
|
705
768
|
* with `sql.now()`). Mirrors SQLAlchemy's `onupdate`.
|
|
769
|
+
*
|
|
770
|
+
* @param value The literal value, or a {@link sql} expression.
|
|
771
|
+
* @returns A new column carrying the on-update value.
|
|
772
|
+
* @throws Error When given a `sql.expr` fragment (see {@link Column.default}).
|
|
706
773
|
*/
|
|
707
774
|
onUpdate(value) {
|
|
708
775
|
const resolved = isDefaultValue(value) ? value : { kind: "literal", value };
|
|
709
|
-
|
|
776
|
+
if (bindsParameters(resolved)) {
|
|
777
|
+
throw new Error(
|
|
778
|
+
"sql.expr`...` binds parameters and cannot be an onUpdate default \u2014 use sql.raw() for a verbatim expression."
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
return this.derive({ onUpdateValue: resolved });
|
|
710
782
|
}
|
|
711
783
|
};
|
|
784
|
+
function toSnakeCase(name) {
|
|
785
|
+
return name.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
|
|
786
|
+
}
|
|
712
787
|
var columnsCache = /* @__PURE__ */ new WeakMap();
|
|
713
788
|
function columnsOf(model) {
|
|
714
789
|
const cached = columnsCache.get(model);
|
|
@@ -723,16 +798,43 @@ function columnsOf(model) {
|
|
|
723
798
|
columnsCache.set(model, out);
|
|
724
799
|
return out;
|
|
725
800
|
}
|
|
801
|
+
var nameMapCache = /* @__PURE__ */ new WeakMap();
|
|
802
|
+
function columnNamesOf(model) {
|
|
803
|
+
const cached = nameMapCache.get(model);
|
|
804
|
+
if (cached !== void 0) return cached;
|
|
805
|
+
const strategy = model.naming ?? "preserve";
|
|
806
|
+
const map = {};
|
|
807
|
+
const seen = /* @__PURE__ */ new Map();
|
|
808
|
+
let renamed = false;
|
|
809
|
+
for (const [prop, col] of Object.entries(columnsOf(model))) {
|
|
810
|
+
const dbName = col.dbName ?? (strategy === "snake_case" ? toSnakeCase(prop) : prop);
|
|
811
|
+
const collision = seen.get(dbName);
|
|
812
|
+
if (collision !== void 0) {
|
|
813
|
+
throw new Error(
|
|
814
|
+
`${model.tablename}: properties "${collision}" and "${prop}" both map to column "${dbName}".`
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
seen.set(dbName, prop);
|
|
818
|
+
map[prop] = dbName;
|
|
819
|
+
if (dbName !== prop) renamed = true;
|
|
820
|
+
}
|
|
821
|
+
const result = renamed ? map : null;
|
|
822
|
+
nameMapCache.set(model, result);
|
|
823
|
+
return result;
|
|
824
|
+
}
|
|
726
825
|
|
|
727
826
|
// src/migrations/ir.ts
|
|
728
827
|
function constraintName(prefix, table, columns) {
|
|
729
828
|
return `${prefix}_${table}_${columns.join("_")}`;
|
|
730
829
|
}
|
|
731
830
|
function reflectTable(model) {
|
|
831
|
+
const names = columnNamesOf(model);
|
|
832
|
+
const toColumn = (prop) => names?.[prop] ?? prop;
|
|
732
833
|
const columns = {};
|
|
733
834
|
const primaryKey = [];
|
|
734
|
-
for (const [
|
|
835
|
+
for (const [prop, col] of Object.entries(columnsOf(model))) {
|
|
735
836
|
const isPk = col.flags.primaryKey;
|
|
837
|
+
const name = toColumn(prop);
|
|
736
838
|
columns[name] = {
|
|
737
839
|
name,
|
|
738
840
|
type: col.type,
|
|
@@ -747,15 +849,16 @@ function reflectTable(model) {
|
|
|
747
849
|
const uniqueConstraints = [];
|
|
748
850
|
const foreignKeys = [];
|
|
749
851
|
for (const c of model.tableArgs?.() ?? []) {
|
|
852
|
+
const cols = c.columns.map(toColumn);
|
|
750
853
|
if (c.kind === "unique") {
|
|
751
854
|
uniqueConstraints.push({
|
|
752
|
-
name: c.name ?? constraintName("uq", model.tablename,
|
|
753
|
-
columns:
|
|
855
|
+
name: c.name ?? constraintName("uq", model.tablename, cols),
|
|
856
|
+
columns: cols
|
|
754
857
|
});
|
|
755
858
|
} else {
|
|
756
859
|
foreignKeys.push({
|
|
757
|
-
name: c.name ?? constraintName("fk", model.tablename,
|
|
758
|
-
columns:
|
|
860
|
+
name: c.name ?? constraintName("fk", model.tablename, cols),
|
|
861
|
+
columns: cols,
|
|
759
862
|
refTable: c.refTable,
|
|
760
863
|
refColumns: c.refColumns,
|
|
761
864
|
onDelete: c.onDelete,
|