oakbun 0.2.2 → 0.4.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/{db-YSUNURBB.js → chunk-6GT2LJZI.js} +55 -23
- package/dist/{executor-BM3A6AGL.js → chunk-E7H3SYJM.js} +1 -1
- package/dist/cli/bin.js +49 -38
- package/dist/cli/config/types.d.ts +7 -2
- package/dist/cli/config/types.d.ts.map +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/sql.d.ts.map +1 -1
- package/dist/db-5FWOAQJO.js +24 -0
- package/dist/executor-CRT6GKGQ.js +8 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +242 -188
- package/dist/index.js.map +1 -1
- package/dist/schema/column.d.ts +11 -0
- package/dist/schema/column.d.ts.map +1 -1
- package/dist/schema/table.d.ts +11 -0
- package/dist/schema/table.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -20,6 +20,188 @@ import {
|
|
|
20
20
|
__toCommonJS
|
|
21
21
|
} from "./chunk-Z6ZWNWWR.js";
|
|
22
22
|
|
|
23
|
+
// src/schema/table.ts
|
|
24
|
+
function defineTable(name, schema) {
|
|
25
|
+
return new TableBuilder(name, schema);
|
|
26
|
+
}
|
|
27
|
+
function sqlColName(jsKey, col) {
|
|
28
|
+
return col.def.columnName ?? jsKey;
|
|
29
|
+
}
|
|
30
|
+
function toCreateTableSql(table) {
|
|
31
|
+
const cols = Object.entries(table.schema).map(([jsKey, col]) => {
|
|
32
|
+
const c = col;
|
|
33
|
+
const sqlName = c.def.columnName ?? jsKey;
|
|
34
|
+
let def = `"${sqlName}" `;
|
|
35
|
+
switch (c.def.type) {
|
|
36
|
+
case "INTEGER":
|
|
37
|
+
def += "INTEGER";
|
|
38
|
+
break;
|
|
39
|
+
case "TEXT":
|
|
40
|
+
case "UUID":
|
|
41
|
+
def += "TEXT";
|
|
42
|
+
break;
|
|
43
|
+
case "REAL":
|
|
44
|
+
def += "REAL";
|
|
45
|
+
break;
|
|
46
|
+
case "BOOLEAN":
|
|
47
|
+
def += "INTEGER";
|
|
48
|
+
break;
|
|
49
|
+
// SQLite has no BOOLEAN
|
|
50
|
+
case "TIMESTAMP":
|
|
51
|
+
def += "TEXT";
|
|
52
|
+
break;
|
|
53
|
+
// ISO string in SQLite
|
|
54
|
+
case "JSON":
|
|
55
|
+
def += "TEXT";
|
|
56
|
+
break;
|
|
57
|
+
case "BLOB":
|
|
58
|
+
def += "BLOB";
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
if (c.def.primaryKey) def += " PRIMARY KEY";
|
|
62
|
+
if (c.def.autoIncrement && c.def.type === "INTEGER") def += " AUTOINCREMENT";
|
|
63
|
+
if (!c.def.nullable && !c.def.primaryKey) def += " NOT NULL";
|
|
64
|
+
if (c.def.unique) def += " UNIQUE";
|
|
65
|
+
return def;
|
|
66
|
+
});
|
|
67
|
+
return `CREATE TABLE IF NOT EXISTS "${table.name}" (${cols.join(", ")})`;
|
|
68
|
+
}
|
|
69
|
+
var TableBuilder;
|
|
70
|
+
var init_table = __esm({
|
|
71
|
+
"src/schema/table.ts"() {
|
|
72
|
+
"use strict";
|
|
73
|
+
TableBuilder = class _TableBuilder {
|
|
74
|
+
constructor(_name, _schema) {
|
|
75
|
+
this._name = _name;
|
|
76
|
+
this._schema = _schema;
|
|
77
|
+
}
|
|
78
|
+
_name;
|
|
79
|
+
_schema;
|
|
80
|
+
_hooks = [];
|
|
81
|
+
_events = {};
|
|
82
|
+
_relations = {};
|
|
83
|
+
_softDeleteColumn = null;
|
|
84
|
+
// Register table-level hook (no ctx)
|
|
85
|
+
hook(handlers) {
|
|
86
|
+
this._hooks.push(handlers);
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Designate a column as the soft-delete timestamp.
|
|
91
|
+
* Once set, all SELECTs automatically add `WHERE "col" IS NULL`.
|
|
92
|
+
* Use `.withDeleted()` on the query to opt out.
|
|
93
|
+
*
|
|
94
|
+
* The column must exist in the schema (validated in `build()`).
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* const usersTable = defineTable('users', {
|
|
98
|
+
* id: column.integer().primaryKey(),
|
|
99
|
+
* deletedAt: column.timestamp().nullable(),
|
|
100
|
+
* }).withSoftDelete('deletedAt').build()
|
|
101
|
+
*/
|
|
102
|
+
withSoftDelete(col) {
|
|
103
|
+
this._softDeleteColumn = col;
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
emits(map) {
|
|
107
|
+
const next = new _TableBuilder(this._name, this._schema);
|
|
108
|
+
for (const h of this._hooks) next._hooks.push(h);
|
|
109
|
+
next._events = map;
|
|
110
|
+
for (const [k, v] of Object.entries(this._relations)) {
|
|
111
|
+
;
|
|
112
|
+
next._relations[k] = v;
|
|
113
|
+
}
|
|
114
|
+
;
|
|
115
|
+
next._softDeleteColumn = this._softDeleteColumn;
|
|
116
|
+
return next;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Declare a belongs-to relation — FK lives on this table.
|
|
120
|
+
* Returns a new builder with the relation type added to TRelations.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* const postsTable = defineTable('posts', { authorId: column.integer() })
|
|
124
|
+
* .belongsTo('author', () => usersTable, 'authorId')
|
|
125
|
+
* .build()
|
|
126
|
+
*/
|
|
127
|
+
belongsTo(name, getTable, foreignKey) {
|
|
128
|
+
if (name in this._relations) {
|
|
129
|
+
throw new Error(`Relation '${name}' is already defined on table '${this._name}'`);
|
|
130
|
+
}
|
|
131
|
+
const rel = { kind: "belongsTo", name, getTable, foreignKey };
|
|
132
|
+
this._relations[name] = rel;
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Declare a has-many relation — FK lives on the foreign table.
|
|
137
|
+
* Returns a new builder with the relation type added to TRelations.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* const usersTable = defineTable('users', { id: column.integer().primaryKey() })
|
|
141
|
+
* .hasMany('posts', () => postsTable, 'authorId')
|
|
142
|
+
* .build()
|
|
143
|
+
*/
|
|
144
|
+
hasMany(name, getTable, foreignKey) {
|
|
145
|
+
if (name in this._relations) {
|
|
146
|
+
throw new Error(`Relation '${name}' is already defined on table '${this._name}'`);
|
|
147
|
+
}
|
|
148
|
+
const rel = { kind: "hasMany", name, getTable, foreignKey };
|
|
149
|
+
this._relations[name] = rel;
|
|
150
|
+
return this;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Declare a many-to-many relation via a pivot table.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* const postsTable = defineTable('posts', { ... })
|
|
157
|
+
* .manyToMany('tags', () => tagsTable, postTagsTable, 'postId', 'tagId')
|
|
158
|
+
* .build()
|
|
159
|
+
*/
|
|
160
|
+
manyToMany(name, getTable, pivotTable, localKey, foreignKey) {
|
|
161
|
+
if (name in this._relations) {
|
|
162
|
+
throw new Error(`Relation '${name}' is already defined on table '${this._name}'`);
|
|
163
|
+
}
|
|
164
|
+
this._relations[name] = {
|
|
165
|
+
kind: "manyToMany",
|
|
166
|
+
name,
|
|
167
|
+
getTable,
|
|
168
|
+
foreignKey,
|
|
169
|
+
pivot: { table: pivotTable, localKey, foreignKey }
|
|
170
|
+
};
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
build() {
|
|
174
|
+
if (this._softDeleteColumn !== null && !(this._softDeleteColumn in this._schema)) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`withSoftDelete: column '${this._softDeleteColumn}' is not defined in table '${this._name}'. Add it to the schema: column.timestamp().nullable()`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
name: this._name,
|
|
181
|
+
schema: this._schema,
|
|
182
|
+
primaryKey: this._findPrimaryKey(),
|
|
183
|
+
hooks: [...this._hooks],
|
|
184
|
+
// copy — immutable after build
|
|
185
|
+
events: { ...this._events },
|
|
186
|
+
// _eventMap is typed as InferTableEvents<T, TEvents> — the concrete shape.
|
|
187
|
+
// At runtime it's an empty object (events hold only the string names, not payloads).
|
|
188
|
+
// The field exists solely so TypeScript can infer TMap in onEvent() without
|
|
189
|
+
// recomputing the conditional InferTableEvents each time.
|
|
190
|
+
_eventMap: {},
|
|
191
|
+
relations: { ...this._relations },
|
|
192
|
+
softDeleteColumn: this._softDeleteColumn
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
_findPrimaryKey() {
|
|
196
|
+
for (const [key, col] of Object.entries(this._schema)) {
|
|
197
|
+
if (col.def.primaryKey) return key;
|
|
198
|
+
}
|
|
199
|
+
return "id";
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
23
205
|
// src/events/index.ts
|
|
24
206
|
var events_exports = {};
|
|
25
207
|
__export(events_exports, {
|
|
@@ -517,13 +699,14 @@ function buildDelete(tableName, pk, pkValue) {
|
|
|
517
699
|
}
|
|
518
700
|
function deserializeRow(table, row) {
|
|
519
701
|
const result = {};
|
|
520
|
-
for (const [
|
|
702
|
+
for (const [jsKey, col] of Object.entries(table.schema)) {
|
|
521
703
|
const c = col;
|
|
522
|
-
const
|
|
704
|
+
const sqlName = sqlColName(jsKey, c);
|
|
705
|
+
const raw = row[sqlName] !== void 0 ? row[sqlName] : row[jsKey];
|
|
523
706
|
if (c.def.type === "TIMESTAMP" && raw !== null && raw !== void 0) {
|
|
524
|
-
result[
|
|
707
|
+
result[jsKey] = new Date(raw);
|
|
525
708
|
} else {
|
|
526
|
-
result[
|
|
709
|
+
result[jsKey] = raw;
|
|
527
710
|
}
|
|
528
711
|
}
|
|
529
712
|
return result;
|
|
@@ -621,6 +804,7 @@ var ON_CLAUSE_PATTERN;
|
|
|
621
804
|
var init_sql = __esm({
|
|
622
805
|
"src/db/sql.ts"() {
|
|
623
806
|
"use strict";
|
|
807
|
+
init_table();
|
|
624
808
|
init_errors();
|
|
625
809
|
ON_CLAUSE_PATTERN = /^([\w]+)\.([\w]+)\s*=\s*([\w]+)\.([\w]+)$/;
|
|
626
810
|
}
|
|
@@ -638,6 +822,27 @@ __export(db_exports, {
|
|
|
638
822
|
UnionBuilder: () => UnionBuilder,
|
|
639
823
|
VelnDB: () => VelnDB
|
|
640
824
|
});
|
|
825
|
+
function mapWhere(conditions, schema) {
|
|
826
|
+
if (typeof conditions !== "object" || conditions === null || Array.isArray(conditions)) {
|
|
827
|
+
return conditions;
|
|
828
|
+
}
|
|
829
|
+
const result = {};
|
|
830
|
+
for (const [jsKey, val] of Object.entries(conditions)) {
|
|
831
|
+
const col = schema[jsKey];
|
|
832
|
+
const sqlName = col?.def.columnName ?? jsKey;
|
|
833
|
+
result[sqlName] = val;
|
|
834
|
+
}
|
|
835
|
+
return result;
|
|
836
|
+
}
|
|
837
|
+
function mapDataToSql(data, schema) {
|
|
838
|
+
const result = {};
|
|
839
|
+
for (const [jsKey, val] of Object.entries(data)) {
|
|
840
|
+
const col = schema[jsKey];
|
|
841
|
+
const sqlName = col?.def.columnName ?? jsKey;
|
|
842
|
+
result[sqlName] = val;
|
|
843
|
+
}
|
|
844
|
+
return result;
|
|
845
|
+
}
|
|
641
846
|
function mergeWhereAnd(a, b) {
|
|
642
847
|
const aIsPlain = !("OR" in a) && !("AND" in a);
|
|
643
848
|
const bIsPlain = !("OR" in b) && !("AND" in b);
|
|
@@ -1055,7 +1260,7 @@ var init_db = __esm({
|
|
|
1055
1260
|
return mergeWhereAnd(this.conditions, softFilter);
|
|
1056
1261
|
}
|
|
1057
1262
|
_buildSelectSQL() {
|
|
1058
|
-
const conditions = this._effectiveConditions();
|
|
1263
|
+
const conditions = mapWhere(this._effectiveConditions(), this.table.schema);
|
|
1059
1264
|
if (this._rawWhere.length === 0) {
|
|
1060
1265
|
return buildSelect(
|
|
1061
1266
|
this.table.name,
|
|
@@ -1167,7 +1372,7 @@ var init_db = __esm({
|
|
|
1167
1372
|
const alias = "_agg";
|
|
1168
1373
|
const colExpr = col ? `"${col}"` : "*";
|
|
1169
1374
|
const { sql: whereSql, params } = buildWhere(
|
|
1170
|
-
this._effectiveConditions(),
|
|
1375
|
+
mapWhere(this._effectiveConditions(), this.table.schema),
|
|
1171
1376
|
this._dialect
|
|
1172
1377
|
);
|
|
1173
1378
|
let sqlStr;
|
|
@@ -1193,7 +1398,7 @@ var init_db = __esm({
|
|
|
1193
1398
|
async select() {
|
|
1194
1399
|
let finalSql;
|
|
1195
1400
|
let finalParams;
|
|
1196
|
-
const effectiveConditions = this._effectiveConditions();
|
|
1401
|
+
const effectiveConditions = mapWhere(this._effectiveConditions(), this.table.schema);
|
|
1197
1402
|
if (this._rawWhere.length === 0) {
|
|
1198
1403
|
const { sql, params } = buildSelect(
|
|
1199
1404
|
this.table.name,
|
|
@@ -1353,10 +1558,11 @@ var init_db = __esm({
|
|
|
1353
1558
|
const finalPatch = await this.hooks.runBeforeUpdate(this.table, this.ctx, current, patch);
|
|
1354
1559
|
const pk = this.table.primaryKey;
|
|
1355
1560
|
const pkValue = current[pk];
|
|
1561
|
+
const pkSqlName = this.table.schema[pk]?.def.columnName ?? pk;
|
|
1356
1562
|
const { sql, params } = buildUpdate(
|
|
1357
1563
|
this.table.name,
|
|
1358
|
-
finalPatch,
|
|
1359
|
-
|
|
1564
|
+
mapDataToSql(finalPatch, this.table.schema),
|
|
1565
|
+
pkSqlName,
|
|
1360
1566
|
pkValue
|
|
1361
1567
|
);
|
|
1362
1568
|
await this.adapter.execute(sql, params);
|
|
@@ -1392,7 +1598,8 @@ var init_db = __esm({
|
|
|
1392
1598
|
`updateMany: row is missing primary key "${pk}" \u2014 every row must include the PK`
|
|
1393
1599
|
);
|
|
1394
1600
|
}
|
|
1395
|
-
const
|
|
1601
|
+
const pkSqlName = this.table.schema[pk]?.def.columnName ?? pk;
|
|
1602
|
+
const selectSql = `SELECT * FROM "${this.table.name}" WHERE "${pkSqlName}" = ?`;
|
|
1396
1603
|
const currentRows = await txAdapter.query(selectSql, [pkValue]);
|
|
1397
1604
|
if (currentRows.length === 0) {
|
|
1398
1605
|
throw new Error(`updateMany: record with ${pk}=${String(pkValue)} not found`);
|
|
@@ -1403,7 +1610,7 @@ var init_db = __esm({
|
|
|
1403
1610
|
const finalPatch = await this.hooks.runBeforeUpdate(this.table, this.ctx, current, patch);
|
|
1404
1611
|
const { sql, params } = buildUpdate(
|
|
1405
1612
|
this.table.name,
|
|
1406
|
-
finalPatch,
|
|
1613
|
+
mapDataToSql(finalPatch, this.table.schema),
|
|
1407
1614
|
pk,
|
|
1408
1615
|
pkValue
|
|
1409
1616
|
);
|
|
@@ -1432,7 +1639,8 @@ var init_db = __esm({
|
|
|
1432
1639
|
await this.hooks.runBeforeDelete(this.table, this.ctx, current);
|
|
1433
1640
|
const pk = this.table.primaryKey;
|
|
1434
1641
|
const pkValue = current[pk];
|
|
1435
|
-
const
|
|
1642
|
+
const pkSqlName = this.table.schema[pk]?.def.columnName ?? pk;
|
|
1643
|
+
const { sql, params } = buildDelete(this.table.name, pkSqlName, pkValue);
|
|
1436
1644
|
await this.adapter.execute(sql, params);
|
|
1437
1645
|
await this.hooks.runAfterDelete(this.table, this.ctx, current, this.queue);
|
|
1438
1646
|
return current;
|
|
@@ -1571,11 +1779,12 @@ var init_db = __esm({
|
|
|
1571
1779
|
`softDelete() called on table '${this.table.name}' which has no soft delete column. Add .withSoftDelete('deletedAt') to the table definition.`
|
|
1572
1780
|
);
|
|
1573
1781
|
}
|
|
1782
|
+
const colSqlName = this.table.schema[col]?.def.columnName ?? col;
|
|
1574
1783
|
const { sql, params } = buildSoftDeleteUpdate(
|
|
1575
1784
|
this.table.name,
|
|
1576
|
-
|
|
1785
|
+
colSqlName,
|
|
1577
1786
|
this._value,
|
|
1578
|
-
this._conditions,
|
|
1787
|
+
mapWhere(this._conditions, this.table.schema),
|
|
1579
1788
|
this._dialect
|
|
1580
1789
|
);
|
|
1581
1790
|
await this.adapter.execute(sql, params);
|
|
@@ -1741,12 +1950,14 @@ var init_db = __esm({
|
|
|
1741
1950
|
}
|
|
1742
1951
|
return results;
|
|
1743
1952
|
}
|
|
1744
|
-
/** Serialize values for storage. Date → ISO string. Drops undefined
|
|
1953
|
+
/** Serialize values for storage. Maps JS keys → SQL column names. Date → ISO string. Drops undefined. */
|
|
1745
1954
|
_serializeForInsert(data) {
|
|
1746
1955
|
const result = {};
|
|
1747
|
-
for (const [
|
|
1956
|
+
for (const [jsKey, val] of Object.entries(data)) {
|
|
1748
1957
|
if (val === void 0) continue;
|
|
1749
|
-
|
|
1958
|
+
const col = this.table.schema[jsKey];
|
|
1959
|
+
const sqlName = col?.def.columnName ?? jsKey;
|
|
1960
|
+
result[sqlName] = val instanceof Date ? val.toISOString() : val;
|
|
1750
1961
|
}
|
|
1751
1962
|
return result;
|
|
1752
1963
|
}
|
|
@@ -1921,6 +2132,17 @@ var Column = class _Column {
|
|
|
1921
2132
|
defaultFn(fn) {
|
|
1922
2133
|
return new _Column({ ...this.def, defaultFn: fn });
|
|
1923
2134
|
}
|
|
2135
|
+
/**
|
|
2136
|
+
* Set an explicit SQL column name, independent of the JS property key.
|
|
2137
|
+
* Use this to map camelCase TypeScript keys to snake_case SQL columns.
|
|
2138
|
+
*
|
|
2139
|
+
* @example
|
|
2140
|
+
* passwordHash: column.text().name('password_hash')
|
|
2141
|
+
* // INSERT uses "password_hash", SELECT returns { passwordHash: ... }
|
|
2142
|
+
*/
|
|
2143
|
+
name(columnName) {
|
|
2144
|
+
return new _Column({ ...this.def, columnName });
|
|
2145
|
+
}
|
|
1924
2146
|
};
|
|
1925
2147
|
var base = (type) => ({
|
|
1926
2148
|
type,
|
|
@@ -1940,179 +2162,11 @@ var column = {
|
|
|
1940
2162
|
json: () => new Column({ ...base("JSON") })
|
|
1941
2163
|
};
|
|
1942
2164
|
|
|
1943
|
-
// src/
|
|
1944
|
-
|
|
1945
|
-
constructor(_name, _schema) {
|
|
1946
|
-
this._name = _name;
|
|
1947
|
-
this._schema = _schema;
|
|
1948
|
-
}
|
|
1949
|
-
_name;
|
|
1950
|
-
_schema;
|
|
1951
|
-
_hooks = [];
|
|
1952
|
-
_events = {};
|
|
1953
|
-
_relations = {};
|
|
1954
|
-
_softDeleteColumn = null;
|
|
1955
|
-
// Register table-level hook (no ctx)
|
|
1956
|
-
hook(handlers) {
|
|
1957
|
-
this._hooks.push(handlers);
|
|
1958
|
-
return this;
|
|
1959
|
-
}
|
|
1960
|
-
/**
|
|
1961
|
-
* Designate a column as the soft-delete timestamp.
|
|
1962
|
-
* Once set, all SELECTs automatically add `WHERE "col" IS NULL`.
|
|
1963
|
-
* Use `.withDeleted()` on the query to opt out.
|
|
1964
|
-
*
|
|
1965
|
-
* The column must exist in the schema (validated in `build()`).
|
|
1966
|
-
*
|
|
1967
|
-
* @example
|
|
1968
|
-
* const usersTable = defineTable('users', {
|
|
1969
|
-
* id: column.integer().primaryKey(),
|
|
1970
|
-
* deletedAt: column.timestamp().nullable(),
|
|
1971
|
-
* }).withSoftDelete('deletedAt').build()
|
|
1972
|
-
*/
|
|
1973
|
-
withSoftDelete(col) {
|
|
1974
|
-
this._softDeleteColumn = col;
|
|
1975
|
-
return this;
|
|
1976
|
-
}
|
|
1977
|
-
emits(map) {
|
|
1978
|
-
const next = new _TableBuilder(this._name, this._schema);
|
|
1979
|
-
for (const h of this._hooks) next._hooks.push(h);
|
|
1980
|
-
next._events = map;
|
|
1981
|
-
for (const [k, v] of Object.entries(this._relations)) {
|
|
1982
|
-
;
|
|
1983
|
-
next._relations[k] = v;
|
|
1984
|
-
}
|
|
1985
|
-
;
|
|
1986
|
-
next._softDeleteColumn = this._softDeleteColumn;
|
|
1987
|
-
return next;
|
|
1988
|
-
}
|
|
1989
|
-
/**
|
|
1990
|
-
* Declare a belongs-to relation — FK lives on this table.
|
|
1991
|
-
* Returns a new builder with the relation type added to TRelations.
|
|
1992
|
-
*
|
|
1993
|
-
* @example
|
|
1994
|
-
* const postsTable = defineTable('posts', { authorId: column.integer() })
|
|
1995
|
-
* .belongsTo('author', () => usersTable, 'authorId')
|
|
1996
|
-
* .build()
|
|
1997
|
-
*/
|
|
1998
|
-
belongsTo(name, getTable, foreignKey) {
|
|
1999
|
-
if (name in this._relations) {
|
|
2000
|
-
throw new Error(`Relation '${name}' is already defined on table '${this._name}'`);
|
|
2001
|
-
}
|
|
2002
|
-
const rel = { kind: "belongsTo", name, getTable, foreignKey };
|
|
2003
|
-
this._relations[name] = rel;
|
|
2004
|
-
return this;
|
|
2005
|
-
}
|
|
2006
|
-
/**
|
|
2007
|
-
* Declare a has-many relation — FK lives on the foreign table.
|
|
2008
|
-
* Returns a new builder with the relation type added to TRelations.
|
|
2009
|
-
*
|
|
2010
|
-
* @example
|
|
2011
|
-
* const usersTable = defineTable('users', { id: column.integer().primaryKey() })
|
|
2012
|
-
* .hasMany('posts', () => postsTable, 'authorId')
|
|
2013
|
-
* .build()
|
|
2014
|
-
*/
|
|
2015
|
-
hasMany(name, getTable, foreignKey) {
|
|
2016
|
-
if (name in this._relations) {
|
|
2017
|
-
throw new Error(`Relation '${name}' is already defined on table '${this._name}'`);
|
|
2018
|
-
}
|
|
2019
|
-
const rel = { kind: "hasMany", name, getTable, foreignKey };
|
|
2020
|
-
this._relations[name] = rel;
|
|
2021
|
-
return this;
|
|
2022
|
-
}
|
|
2023
|
-
/**
|
|
2024
|
-
* Declare a many-to-many relation via a pivot table.
|
|
2025
|
-
*
|
|
2026
|
-
* @example
|
|
2027
|
-
* const postsTable = defineTable('posts', { ... })
|
|
2028
|
-
* .manyToMany('tags', () => tagsTable, postTagsTable, 'postId', 'tagId')
|
|
2029
|
-
* .build()
|
|
2030
|
-
*/
|
|
2031
|
-
manyToMany(name, getTable, pivotTable, localKey, foreignKey) {
|
|
2032
|
-
if (name in this._relations) {
|
|
2033
|
-
throw new Error(`Relation '${name}' is already defined on table '${this._name}'`);
|
|
2034
|
-
}
|
|
2035
|
-
this._relations[name] = {
|
|
2036
|
-
kind: "manyToMany",
|
|
2037
|
-
name,
|
|
2038
|
-
getTable,
|
|
2039
|
-
foreignKey,
|
|
2040
|
-
pivot: { table: pivotTable, localKey, foreignKey }
|
|
2041
|
-
};
|
|
2042
|
-
return this;
|
|
2043
|
-
}
|
|
2044
|
-
build() {
|
|
2045
|
-
if (this._softDeleteColumn !== null && !(this._softDeleteColumn in this._schema)) {
|
|
2046
|
-
throw new Error(
|
|
2047
|
-
`withSoftDelete: column '${this._softDeleteColumn}' is not defined in table '${this._name}'. Add it to the schema: column.timestamp().nullable()`
|
|
2048
|
-
);
|
|
2049
|
-
}
|
|
2050
|
-
return {
|
|
2051
|
-
name: this._name,
|
|
2052
|
-
schema: this._schema,
|
|
2053
|
-
primaryKey: this._findPrimaryKey(),
|
|
2054
|
-
hooks: [...this._hooks],
|
|
2055
|
-
// copy — immutable after build
|
|
2056
|
-
events: { ...this._events },
|
|
2057
|
-
// _eventMap is typed as InferTableEvents<T, TEvents> — the concrete shape.
|
|
2058
|
-
// At runtime it's an empty object (events hold only the string names, not payloads).
|
|
2059
|
-
// The field exists solely so TypeScript can infer TMap in onEvent() without
|
|
2060
|
-
// recomputing the conditional InferTableEvents each time.
|
|
2061
|
-
_eventMap: {},
|
|
2062
|
-
relations: { ...this._relations },
|
|
2063
|
-
softDeleteColumn: this._softDeleteColumn
|
|
2064
|
-
};
|
|
2065
|
-
}
|
|
2066
|
-
_findPrimaryKey() {
|
|
2067
|
-
for (const [key, col] of Object.entries(this._schema)) {
|
|
2068
|
-
if (col.def.primaryKey) return key;
|
|
2069
|
-
}
|
|
2070
|
-
return "id";
|
|
2071
|
-
}
|
|
2072
|
-
};
|
|
2073
|
-
function defineTable(name, schema) {
|
|
2074
|
-
return new TableBuilder(name, schema);
|
|
2075
|
-
}
|
|
2076
|
-
function toCreateTableSql(table) {
|
|
2077
|
-
const cols = Object.entries(table.schema).map(([name, col]) => {
|
|
2078
|
-
const c = col;
|
|
2079
|
-
let def = `"${name}" `;
|
|
2080
|
-
switch (c.def.type) {
|
|
2081
|
-
case "INTEGER":
|
|
2082
|
-
def += "INTEGER";
|
|
2083
|
-
break;
|
|
2084
|
-
case "TEXT":
|
|
2085
|
-
case "UUID":
|
|
2086
|
-
def += "TEXT";
|
|
2087
|
-
break;
|
|
2088
|
-
case "REAL":
|
|
2089
|
-
def += "REAL";
|
|
2090
|
-
break;
|
|
2091
|
-
case "BOOLEAN":
|
|
2092
|
-
def += "INTEGER";
|
|
2093
|
-
break;
|
|
2094
|
-
// SQLite has no BOOLEAN
|
|
2095
|
-
case "TIMESTAMP":
|
|
2096
|
-
def += "TEXT";
|
|
2097
|
-
break;
|
|
2098
|
-
// ISO string in SQLite
|
|
2099
|
-
case "JSON":
|
|
2100
|
-
def += "TEXT";
|
|
2101
|
-
break;
|
|
2102
|
-
case "BLOB":
|
|
2103
|
-
def += "BLOB";
|
|
2104
|
-
break;
|
|
2105
|
-
}
|
|
2106
|
-
if (c.def.primaryKey) def += " PRIMARY KEY";
|
|
2107
|
-
if (c.def.autoIncrement && c.def.type === "INTEGER") def += " AUTOINCREMENT";
|
|
2108
|
-
if (!c.def.nullable && !c.def.primaryKey) def += " NOT NULL";
|
|
2109
|
-
if (c.def.unique) def += " UNIQUE";
|
|
2110
|
-
return def;
|
|
2111
|
-
});
|
|
2112
|
-
return `CREATE TABLE IF NOT EXISTS "${table.name}" (${cols.join(", ")})`;
|
|
2113
|
-
}
|
|
2165
|
+
// src/index.ts
|
|
2166
|
+
init_table();
|
|
2114
2167
|
|
|
2115
2168
|
// src/schema/audit.ts
|
|
2169
|
+
init_table();
|
|
2116
2170
|
var _baseAuditFields = {
|
|
2117
2171
|
id: column.integer().primaryKey(),
|
|
2118
2172
|
tableName: column.text(),
|