sqlite-zod-orm 3.9.0 → 3.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/index.js +242 -72
- package/package.json +1 -1
- package/src/builder.ts +399 -0
- package/src/context.ts +9 -0
- package/src/crud.ts +44 -3
- package/src/database.ts +53 -3
- package/src/helpers.ts +11 -0
- package/src/index.ts +1 -1
- package/src/iqo.ts +198 -0
- package/src/proxy.ts +276 -0
- package/src/query.ts +23 -736
- package/src/types.ts +26 -3
package/dist/index.js
CHANGED
|
@@ -13,65 +13,6 @@ var __export = (target, all) => {
|
|
|
13
13
|
// src/database.ts
|
|
14
14
|
import { Database as SqliteDatabase } from "bun:sqlite";
|
|
15
15
|
|
|
16
|
-
// src/ast.ts
|
|
17
|
-
var wrapNode = (val) => val !== null && typeof val === "object" && ("type" in val) ? val : { type: "literal", value: val };
|
|
18
|
-
function compileAST(node) {
|
|
19
|
-
if (node.type === "column")
|
|
20
|
-
return { sql: `"${node.name}"`, params: [] };
|
|
21
|
-
if (node.type === "literal") {
|
|
22
|
-
if (node.value instanceof Date)
|
|
23
|
-
return { sql: "?", params: [node.value.toISOString()] };
|
|
24
|
-
if (typeof node.value === "boolean")
|
|
25
|
-
return { sql: "?", params: [node.value ? 1 : 0] };
|
|
26
|
-
return { sql: "?", params: [node.value] };
|
|
27
|
-
}
|
|
28
|
-
if (node.type === "function") {
|
|
29
|
-
const compiledArgs = node.args.map(compileAST);
|
|
30
|
-
return {
|
|
31
|
-
sql: `${node.name}(${compiledArgs.map((c) => c.sql).join(", ")})`,
|
|
32
|
-
params: compiledArgs.flatMap((c) => c.params)
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
if (node.type === "operator") {
|
|
36
|
-
const left = compileAST(node.left);
|
|
37
|
-
const right = compileAST(node.right);
|
|
38
|
-
return {
|
|
39
|
-
sql: `(${left.sql} ${node.op} ${right.sql})`,
|
|
40
|
-
params: [...left.params, ...right.params]
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
throw new Error("Unknown AST node type");
|
|
44
|
-
}
|
|
45
|
-
var createColumnProxy = () => new Proxy({}, {
|
|
46
|
-
get: (_, prop) => ({ type: "column", name: prop })
|
|
47
|
-
});
|
|
48
|
-
var createFunctionProxy = () => new Proxy({}, {
|
|
49
|
-
get: (_, funcName) => (...args) => ({
|
|
50
|
-
type: "function",
|
|
51
|
-
name: funcName.toUpperCase(),
|
|
52
|
-
args: args.map(wrapNode)
|
|
53
|
-
})
|
|
54
|
-
});
|
|
55
|
-
var op = {
|
|
56
|
-
eq: (left, right) => ({ type: "operator", op: "=", left: wrapNode(left), right: wrapNode(right) }),
|
|
57
|
-
ne: (left, right) => ({ type: "operator", op: "!=", left: wrapNode(left), right: wrapNode(right) }),
|
|
58
|
-
gt: (left, right) => ({ type: "operator", op: ">", left: wrapNode(left), right: wrapNode(right) }),
|
|
59
|
-
gte: (left, right) => ({ type: "operator", op: ">=", left: wrapNode(left), right: wrapNode(right) }),
|
|
60
|
-
lt: (left, right) => ({ type: "operator", op: "<", left: wrapNode(left), right: wrapNode(right) }),
|
|
61
|
-
lte: (left, right) => ({ type: "operator", op: "<=", left: wrapNode(left), right: wrapNode(right) }),
|
|
62
|
-
and: (left, right) => ({ type: "operator", op: "AND", left: wrapNode(left), right: wrapNode(right) }),
|
|
63
|
-
or: (left, right) => ({ type: "operator", op: "OR", left: wrapNode(left), right: wrapNode(right) }),
|
|
64
|
-
like: (left, right) => ({ type: "operator", op: "LIKE", left: wrapNode(left), right: wrapNode(right) }),
|
|
65
|
-
isNull: (node) => ({ type: "operator", op: "IS", left: wrapNode(node), right: { type: "literal", value: null } }),
|
|
66
|
-
isNotNull: (node) => ({ type: "operator", op: "IS NOT", left: wrapNode(node), right: { type: "literal", value: null } }),
|
|
67
|
-
in: (left, values) => ({
|
|
68
|
-
type: "function",
|
|
69
|
-
name: `${compileAST(wrapNode(left)).sql} IN`,
|
|
70
|
-
args: values.map((v) => wrapNode(v))
|
|
71
|
-
}),
|
|
72
|
-
not: (node) => ({ type: "operator", op: "NOT", left: { type: "literal", value: "" }, right: wrapNode(node) })
|
|
73
|
-
};
|
|
74
|
-
|
|
75
16
|
// node_modules/zod/v3/external.js
|
|
76
17
|
var exports_external = {};
|
|
77
18
|
__export(exports_external, {
|
|
@@ -4139,7 +4080,66 @@ function transformFromStorage(row, schema) {
|
|
|
4139
4080
|
return transformed;
|
|
4140
4081
|
}
|
|
4141
4082
|
|
|
4142
|
-
// src/
|
|
4083
|
+
// src/ast.ts
|
|
4084
|
+
var wrapNode = (val) => val !== null && typeof val === "object" && ("type" in val) ? val : { type: "literal", value: val };
|
|
4085
|
+
function compileAST(node) {
|
|
4086
|
+
if (node.type === "column")
|
|
4087
|
+
return { sql: `"${node.name}"`, params: [] };
|
|
4088
|
+
if (node.type === "literal") {
|
|
4089
|
+
if (node.value instanceof Date)
|
|
4090
|
+
return { sql: "?", params: [node.value.toISOString()] };
|
|
4091
|
+
if (typeof node.value === "boolean")
|
|
4092
|
+
return { sql: "?", params: [node.value ? 1 : 0] };
|
|
4093
|
+
return { sql: "?", params: [node.value] };
|
|
4094
|
+
}
|
|
4095
|
+
if (node.type === "function") {
|
|
4096
|
+
const compiledArgs = node.args.map(compileAST);
|
|
4097
|
+
return {
|
|
4098
|
+
sql: `${node.name}(${compiledArgs.map((c) => c.sql).join(", ")})`,
|
|
4099
|
+
params: compiledArgs.flatMap((c) => c.params)
|
|
4100
|
+
};
|
|
4101
|
+
}
|
|
4102
|
+
if (node.type === "operator") {
|
|
4103
|
+
const left = compileAST(node.left);
|
|
4104
|
+
const right = compileAST(node.right);
|
|
4105
|
+
return {
|
|
4106
|
+
sql: `(${left.sql} ${node.op} ${right.sql})`,
|
|
4107
|
+
params: [...left.params, ...right.params]
|
|
4108
|
+
};
|
|
4109
|
+
}
|
|
4110
|
+
throw new Error("Unknown AST node type");
|
|
4111
|
+
}
|
|
4112
|
+
var createColumnProxy = () => new Proxy({}, {
|
|
4113
|
+
get: (_, prop) => ({ type: "column", name: prop })
|
|
4114
|
+
});
|
|
4115
|
+
var createFunctionProxy = () => new Proxy({}, {
|
|
4116
|
+
get: (_, funcName) => (...args) => ({
|
|
4117
|
+
type: "function",
|
|
4118
|
+
name: funcName.toUpperCase(),
|
|
4119
|
+
args: args.map(wrapNode)
|
|
4120
|
+
})
|
|
4121
|
+
});
|
|
4122
|
+
var op = {
|
|
4123
|
+
eq: (left, right) => ({ type: "operator", op: "=", left: wrapNode(left), right: wrapNode(right) }),
|
|
4124
|
+
ne: (left, right) => ({ type: "operator", op: "!=", left: wrapNode(left), right: wrapNode(right) }),
|
|
4125
|
+
gt: (left, right) => ({ type: "operator", op: ">", left: wrapNode(left), right: wrapNode(right) }),
|
|
4126
|
+
gte: (left, right) => ({ type: "operator", op: ">=", left: wrapNode(left), right: wrapNode(right) }),
|
|
4127
|
+
lt: (left, right) => ({ type: "operator", op: "<", left: wrapNode(left), right: wrapNode(right) }),
|
|
4128
|
+
lte: (left, right) => ({ type: "operator", op: "<=", left: wrapNode(left), right: wrapNode(right) }),
|
|
4129
|
+
and: (left, right) => ({ type: "operator", op: "AND", left: wrapNode(left), right: wrapNode(right) }),
|
|
4130
|
+
or: (left, right) => ({ type: "operator", op: "OR", left: wrapNode(left), right: wrapNode(right) }),
|
|
4131
|
+
like: (left, right) => ({ type: "operator", op: "LIKE", left: wrapNode(left), right: wrapNode(right) }),
|
|
4132
|
+
isNull: (node) => ({ type: "operator", op: "IS", left: wrapNode(node), right: { type: "literal", value: null } }),
|
|
4133
|
+
isNotNull: (node) => ({ type: "operator", op: "IS NOT", left: wrapNode(node), right: { type: "literal", value: null } }),
|
|
4134
|
+
in: (left, values) => ({
|
|
4135
|
+
type: "function",
|
|
4136
|
+
name: `${compileAST(wrapNode(left)).sql} IN`,
|
|
4137
|
+
args: values.map((v) => wrapNode(v))
|
|
4138
|
+
}),
|
|
4139
|
+
not: (node) => ({ type: "operator", op: "NOT", left: { type: "literal", value: "" }, right: wrapNode(node) })
|
|
4140
|
+
};
|
|
4141
|
+
|
|
4142
|
+
// src/iqo.ts
|
|
4143
4143
|
var OPERATOR_MAP = {
|
|
4144
4144
|
$gt: ">",
|
|
4145
4145
|
$gte: ">=",
|
|
@@ -4149,7 +4149,9 @@ var OPERATOR_MAP = {
|
|
|
4149
4149
|
$in: "IN",
|
|
4150
4150
|
$like: "LIKE",
|
|
4151
4151
|
$notIn: "NOT IN",
|
|
4152
|
-
$between: "BETWEEN"
|
|
4152
|
+
$between: "BETWEEN",
|
|
4153
|
+
$isNull: "IS NULL",
|
|
4154
|
+
$isNotNull: "IS NOT NULL"
|
|
4153
4155
|
};
|
|
4154
4156
|
function transformValueForStorage(value) {
|
|
4155
4157
|
if (value instanceof Date)
|
|
@@ -4173,7 +4175,7 @@ function compileIQO(tableName, iqo) {
|
|
|
4173
4175
|
selectParts.push(`${j.table}.*`);
|
|
4174
4176
|
}
|
|
4175
4177
|
}
|
|
4176
|
-
let sql = `SELECT ${selectParts.join(", ")} FROM ${tableName}`;
|
|
4178
|
+
let sql = `SELECT ${iqo.distinct ? "DISTINCT " : ""}${selectParts.join(", ")} FROM ${tableName}`;
|
|
4177
4179
|
for (const j of iqo.joins) {
|
|
4178
4180
|
sql += ` JOIN ${j.table} ON ${tableName}.${j.fromCol} = ${j.table}.${j.toCol}`;
|
|
4179
4181
|
}
|
|
@@ -4206,6 +4208,10 @@ function compileIQO(tableName, iqo) {
|
|
|
4206
4208
|
const [min, max] = w.value;
|
|
4207
4209
|
whereParts.push(`${qualify(w.field)} BETWEEN ? AND ?`);
|
|
4208
4210
|
params.push(transformValueForStorage(min), transformValueForStorage(max));
|
|
4211
|
+
} else if (w.operator === "IS NULL") {
|
|
4212
|
+
whereParts.push(`${qualify(w.field)} IS NULL`);
|
|
4213
|
+
} else if (w.operator === "IS NOT NULL") {
|
|
4214
|
+
whereParts.push(`${qualify(w.field)} IS NOT NULL`);
|
|
4209
4215
|
} else {
|
|
4210
4216
|
whereParts.push(`${qualify(w.field)} ${w.operator} ?`);
|
|
4211
4217
|
params.push(transformValueForStorage(w.value));
|
|
@@ -4241,6 +4247,22 @@ function compileIQO(tableName, iqo) {
|
|
|
4241
4247
|
if (iqo.groupBy.length > 0) {
|
|
4242
4248
|
sql += ` GROUP BY ${iqo.groupBy.join(", ")}`;
|
|
4243
4249
|
}
|
|
4250
|
+
if (iqo.having && iqo.having.length > 0) {
|
|
4251
|
+
const havingParts = [];
|
|
4252
|
+
for (const h of iqo.having) {
|
|
4253
|
+
if (h.operator === "IS NULL") {
|
|
4254
|
+
havingParts.push(`${h.field} IS NULL`);
|
|
4255
|
+
} else if (h.operator === "IS NOT NULL") {
|
|
4256
|
+
havingParts.push(`${h.field} IS NOT NULL`);
|
|
4257
|
+
} else {
|
|
4258
|
+
havingParts.push(`${h.field} ${h.operator} ?`);
|
|
4259
|
+
params.push(transformValueForStorage(h.value));
|
|
4260
|
+
}
|
|
4261
|
+
}
|
|
4262
|
+
if (havingParts.length > 0) {
|
|
4263
|
+
sql += ` HAVING ${havingParts.join(" AND ")}`;
|
|
4264
|
+
}
|
|
4265
|
+
}
|
|
4244
4266
|
if (iqo.orderBy.length > 0) {
|
|
4245
4267
|
const parts = iqo.orderBy.map((o) => `${o.field} ${o.direction.toUpperCase()}`);
|
|
4246
4268
|
sql += ` ORDER BY ${parts.join(", ")}`;
|
|
@@ -4251,7 +4273,7 @@ function compileIQO(tableName, iqo) {
|
|
|
4251
4273
|
sql += ` OFFSET ${iqo.offset}`;
|
|
4252
4274
|
return { sql, params };
|
|
4253
4275
|
}
|
|
4254
|
-
|
|
4276
|
+
// src/builder.ts
|
|
4255
4277
|
class QueryBuilder {
|
|
4256
4278
|
iqo;
|
|
4257
4279
|
tableName;
|
|
@@ -4274,11 +4296,13 @@ class QueryBuilder {
|
|
|
4274
4296
|
whereAST: null,
|
|
4275
4297
|
joins: [],
|
|
4276
4298
|
groupBy: [],
|
|
4299
|
+
having: [],
|
|
4277
4300
|
limit: null,
|
|
4278
4301
|
offset: null,
|
|
4279
4302
|
orderBy: [],
|
|
4280
4303
|
includes: [],
|
|
4281
|
-
raw: false
|
|
4304
|
+
raw: false,
|
|
4305
|
+
distinct: false
|
|
4282
4306
|
};
|
|
4283
4307
|
}
|
|
4284
4308
|
select(...cols) {
|
|
@@ -4433,6 +4457,61 @@ class QueryBuilder {
|
|
|
4433
4457
|
this.iqo.groupBy.push(...fields);
|
|
4434
4458
|
return this;
|
|
4435
4459
|
}
|
|
4460
|
+
distinct() {
|
|
4461
|
+
this.iqo.distinct = true;
|
|
4462
|
+
return this;
|
|
4463
|
+
}
|
|
4464
|
+
withTrashed() {
|
|
4465
|
+
this.iqo.wheres = this.iqo.wheres.filter((w) => !(w.field === "deletedAt" && w.operator === "IS NULL"));
|
|
4466
|
+
return this;
|
|
4467
|
+
}
|
|
4468
|
+
having(conditions) {
|
|
4469
|
+
for (const [field, value] of Object.entries(conditions)) {
|
|
4470
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
4471
|
+
for (const [opKey, operand] of Object.entries(value)) {
|
|
4472
|
+
const sqlOp = OPERATOR_MAP[opKey];
|
|
4473
|
+
if (!sqlOp)
|
|
4474
|
+
throw new Error(`Unsupported having operator: '${opKey}'`);
|
|
4475
|
+
this.iqo.having.push({ field, operator: sqlOp, value: operand });
|
|
4476
|
+
}
|
|
4477
|
+
} else {
|
|
4478
|
+
this.iqo.having.push({ field, operator: "=", value });
|
|
4479
|
+
}
|
|
4480
|
+
}
|
|
4481
|
+
return this;
|
|
4482
|
+
}
|
|
4483
|
+
sum(field) {
|
|
4484
|
+
const { sql: selectSql, params } = compileIQO(this.tableName, this.iqo);
|
|
4485
|
+
const aggSql = selectSql.replace(/^SELECT .+? FROM/, `SELECT COALESCE(SUM("${field}"), 0) as val FROM`);
|
|
4486
|
+
const results = this.executor(aggSql, params, true);
|
|
4487
|
+
return results[0]?.val ?? 0;
|
|
4488
|
+
}
|
|
4489
|
+
avg(field) {
|
|
4490
|
+
const { sql: selectSql, params } = compileIQO(this.tableName, this.iqo);
|
|
4491
|
+
const aggSql = selectSql.replace(/^SELECT .+? FROM/, `SELECT AVG("${field}") as val FROM`);
|
|
4492
|
+
const results = this.executor(aggSql, params, true);
|
|
4493
|
+
return results[0]?.val ?? 0;
|
|
4494
|
+
}
|
|
4495
|
+
min(field) {
|
|
4496
|
+
const { sql: selectSql, params } = compileIQO(this.tableName, this.iqo);
|
|
4497
|
+
const aggSql = selectSql.replace(/^SELECT .+? FROM/, `SELECT MIN("${field}") as val FROM`);
|
|
4498
|
+
const results = this.executor(aggSql, params, true);
|
|
4499
|
+
return results[0]?.val ?? null;
|
|
4500
|
+
}
|
|
4501
|
+
max(field) {
|
|
4502
|
+
const { sql: selectSql, params } = compileIQO(this.tableName, this.iqo);
|
|
4503
|
+
const aggSql = selectSql.replace(/^SELECT .+? FROM/, `SELECT MAX("${field}") as val FROM`);
|
|
4504
|
+
const results = this.executor(aggSql, params, true);
|
|
4505
|
+
return results[0]?.val ?? null;
|
|
4506
|
+
}
|
|
4507
|
+
paginate(page = 1, perPage = 20) {
|
|
4508
|
+
const total = this.count();
|
|
4509
|
+
const pages = Math.ceil(total / perPage);
|
|
4510
|
+
this.iqo.limit = perPage;
|
|
4511
|
+
this.iqo.offset = (page - 1) * perPage;
|
|
4512
|
+
const data = this.all();
|
|
4513
|
+
return { data, total, page, perPage, pages };
|
|
4514
|
+
}
|
|
4436
4515
|
then(onfulfilled, onrejected) {
|
|
4437
4516
|
try {
|
|
4438
4517
|
const result = this.all();
|
|
@@ -4442,7 +4521,7 @@ class QueryBuilder {
|
|
|
4442
4521
|
}
|
|
4443
4522
|
}
|
|
4444
4523
|
}
|
|
4445
|
-
|
|
4524
|
+
// src/proxy.ts
|
|
4446
4525
|
class ColumnNode {
|
|
4447
4526
|
table;
|
|
4448
4527
|
column;
|
|
@@ -4550,7 +4629,7 @@ function compileProxyQuery(queryResult, aliasMap) {
|
|
|
4550
4629
|
const whereParts = [];
|
|
4551
4630
|
for (const [key, value] of Object.entries(queryResult.where)) {
|
|
4552
4631
|
let fieldRef;
|
|
4553
|
-
const quotedMatch = key.match(/^"([^"]+)"
|
|
4632
|
+
const quotedMatch = key.match(/^"([^"]+)"\."([^"]+)"$/);
|
|
4554
4633
|
if (quotedMatch && tablesUsed.has(quotedMatch[1])) {
|
|
4555
4634
|
fieldRef = key;
|
|
4556
4635
|
} else {
|
|
@@ -4605,7 +4684,7 @@ function compileProxyQuery(queryResult, aliasMap) {
|
|
|
4605
4684
|
const parts = [];
|
|
4606
4685
|
for (const [key, dir] of Object.entries(queryResult.orderBy)) {
|
|
4607
4686
|
let fieldRef;
|
|
4608
|
-
const quotedMatch = key.match(/^"([^"]+)"
|
|
4687
|
+
const quotedMatch = key.match(/^"([^"]+)"\."([^"]+)"$/);
|
|
4609
4688
|
if (quotedMatch && tablesUsed.has(quotedMatch[1])) {
|
|
4610
4689
|
fieldRef = key;
|
|
4611
4690
|
} else {
|
|
@@ -4633,9 +4712,13 @@ function executeProxyQuery(schemas, callback, executor) {
|
|
|
4633
4712
|
const { sql, params } = compileProxyQuery(queryResult, aliasMap);
|
|
4634
4713
|
return executor(sql, params);
|
|
4635
4714
|
}
|
|
4715
|
+
|
|
4716
|
+
// src/query.ts
|
|
4636
4717
|
function createQueryBuilder(ctx, entityName, initialCols) {
|
|
4637
4718
|
const schema = ctx.schemas[entityName];
|
|
4638
4719
|
const executor = (sql, params, raw) => {
|
|
4720
|
+
if (ctx.debug)
|
|
4721
|
+
console.log("[satidb]", sql, params);
|
|
4639
4722
|
const rows = ctx.db.query(sql).all(...params);
|
|
4640
4723
|
if (raw)
|
|
4641
4724
|
return rows;
|
|
@@ -4705,6 +4788,9 @@ function createQueryBuilder(ctx, entityName, initialCols) {
|
|
|
4705
4788
|
const builder = new QueryBuilder(entityName, executor, singleExecutor, joinResolver, conditionResolver, eagerLoader);
|
|
4706
4789
|
if (initialCols.length > 0)
|
|
4707
4790
|
builder.select(...initialCols);
|
|
4791
|
+
if (ctx.softDeletes) {
|
|
4792
|
+
builder.where({ deletedAt: { $isNull: true } });
|
|
4793
|
+
}
|
|
4708
4794
|
return builder;
|
|
4709
4795
|
}
|
|
4710
4796
|
|
|
@@ -4769,6 +4855,14 @@ function buildWhereClause(conditions, tablePrefix) {
|
|
|
4769
4855
|
values.push(transformForStorage({ v: operand[0] }).v, transformForStorage({ v: operand[1] }).v);
|
|
4770
4856
|
continue;
|
|
4771
4857
|
}
|
|
4858
|
+
if (operator === "$isNull") {
|
|
4859
|
+
parts.push(`${fieldName} IS NULL`);
|
|
4860
|
+
continue;
|
|
4861
|
+
}
|
|
4862
|
+
if (operator === "$isNotNull") {
|
|
4863
|
+
parts.push(`${fieldName} IS NOT NULL`);
|
|
4864
|
+
continue;
|
|
4865
|
+
}
|
|
4772
4866
|
const sqlOp = { $gt: ">", $gte: ">=", $lt: "<", $lte: "<=", $ne: "!=" }[operator];
|
|
4773
4867
|
if (!sqlOp)
|
|
4774
4868
|
throw new Error(`Unsupported operator '${operator}' on '${key}'`);
|
|
@@ -4805,9 +4899,16 @@ function insert(ctx, entityName, data) {
|
|
|
4805
4899
|
const schema = ctx.schemas[entityName];
|
|
4806
4900
|
const validatedData = asZodObject(schema).passthrough().parse(data);
|
|
4807
4901
|
const transformed = transformForStorage(validatedData);
|
|
4902
|
+
if (ctx.timestamps) {
|
|
4903
|
+
const now = new Date().toISOString();
|
|
4904
|
+
transformed.createdAt = now;
|
|
4905
|
+
transformed.updatedAt = now;
|
|
4906
|
+
}
|
|
4808
4907
|
const columns = Object.keys(transformed);
|
|
4809
4908
|
const quotedCols = columns.map((c) => `"${c}"`);
|
|
4810
4909
|
const sql = columns.length === 0 ? `INSERT INTO "${entityName}" DEFAULT VALUES` : `INSERT INTO "${entityName}" (${quotedCols.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`;
|
|
4910
|
+
if (ctx.debug)
|
|
4911
|
+
console.log("[satidb]", sql, Object.values(transformed));
|
|
4811
4912
|
const result = ctx.db.query(sql).run(...Object.values(transformed));
|
|
4812
4913
|
const newEntity = getById(ctx, entityName, result.lastInsertRowid);
|
|
4813
4914
|
if (!newEntity)
|
|
@@ -4818,10 +4919,16 @@ function update(ctx, entityName, id, data) {
|
|
|
4818
4919
|
const schema = ctx.schemas[entityName];
|
|
4819
4920
|
const validatedData = asZodObject(schema).partial().parse(data);
|
|
4820
4921
|
const transformed = transformForStorage(validatedData);
|
|
4821
|
-
if (Object.keys(transformed).length === 0)
|
|
4922
|
+
if (Object.keys(transformed).length === 0 && !ctx.timestamps)
|
|
4822
4923
|
return getById(ctx, entityName, id);
|
|
4924
|
+
if (ctx.timestamps) {
|
|
4925
|
+
transformed.updatedAt = new Date().toISOString();
|
|
4926
|
+
}
|
|
4823
4927
|
const setClause = Object.keys(transformed).map((key) => `"${key}" = ?`).join(", ");
|
|
4824
|
-
|
|
4928
|
+
const sql = `UPDATE "${entityName}" SET ${setClause} WHERE id = ?`;
|
|
4929
|
+
if (ctx.debug)
|
|
4930
|
+
console.log("[satidb]", sql, [...Object.values(transformed), id]);
|
|
4931
|
+
ctx.db.query(sql).run(...Object.values(transformed), id);
|
|
4825
4932
|
return getById(ctx, entityName, id);
|
|
4826
4933
|
}
|
|
4827
4934
|
function updateWhere(ctx, entityName, data, conditions) {
|
|
@@ -4864,6 +4971,24 @@ function upsert(ctx, entityName, data, conditions = {}) {
|
|
|
4864
4971
|
function deleteEntity(ctx, entityName, id) {
|
|
4865
4972
|
ctx.db.query(`DELETE FROM "${entityName}" WHERE id = ?`).run(id);
|
|
4866
4973
|
}
|
|
4974
|
+
function deleteWhere(ctx, entityName, conditions) {
|
|
4975
|
+
const { clause, values } = ctx.buildWhereClause(conditions);
|
|
4976
|
+
if (!clause)
|
|
4977
|
+
throw new Error("delete().where() requires at least one condition");
|
|
4978
|
+
const result = ctx.db.query(`DELETE FROM "${entityName}" ${clause}`).run(...values);
|
|
4979
|
+
return result.changes ?? 0;
|
|
4980
|
+
}
|
|
4981
|
+
function createDeleteBuilder(ctx, entityName) {
|
|
4982
|
+
let _conditions = {};
|
|
4983
|
+
const builder = {
|
|
4984
|
+
where: (conditions) => {
|
|
4985
|
+
_conditions = { ..._conditions, ...conditions };
|
|
4986
|
+
return builder;
|
|
4987
|
+
},
|
|
4988
|
+
exec: () => deleteWhere(ctx, entityName, _conditions)
|
|
4989
|
+
};
|
|
4990
|
+
return builder;
|
|
4991
|
+
}
|
|
4867
4992
|
function insertMany(ctx, entityName, rows) {
|
|
4868
4993
|
if (rows.length === 0)
|
|
4869
4994
|
return [];
|
|
@@ -4874,6 +4999,11 @@ function insertMany(ctx, entityName, rows) {
|
|
|
4874
4999
|
for (const data of rows) {
|
|
4875
5000
|
const validatedData = zodSchema.parse(data);
|
|
4876
5001
|
const transformed = transformForStorage(validatedData);
|
|
5002
|
+
if (ctx.timestamps) {
|
|
5003
|
+
const now = new Date().toISOString();
|
|
5004
|
+
transformed.createdAt = now;
|
|
5005
|
+
transformed.updatedAt = now;
|
|
5006
|
+
}
|
|
4877
5007
|
const columns = Object.keys(transformed);
|
|
4878
5008
|
const quotedCols = columns.map((c) => `"${c}"`);
|
|
4879
5009
|
const sql = columns.length === 0 ? `INSERT INTO "${entityName}" DEFAULT VALUES` : `INSERT INTO "${entityName}" (${quotedCols.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})`;
|
|
@@ -4924,6 +5054,9 @@ function attachMethods(ctx, entityName, entity) {
|
|
|
4924
5054
|
class _Database {
|
|
4925
5055
|
db;
|
|
4926
5056
|
_reactive;
|
|
5057
|
+
_timestamps;
|
|
5058
|
+
_softDeletes;
|
|
5059
|
+
_debug;
|
|
4927
5060
|
schemas;
|
|
4928
5061
|
relationships;
|
|
4929
5062
|
options;
|
|
@@ -4939,6 +5072,9 @@ class _Database {
|
|
|
4939
5072
|
this.schemas = schemas;
|
|
4940
5073
|
this.options = options;
|
|
4941
5074
|
this._reactive = options.reactive !== false;
|
|
5075
|
+
this._timestamps = options.timestamps === true;
|
|
5076
|
+
this._softDeletes = options.softDeletes === true;
|
|
5077
|
+
this._debug = options.debug === true;
|
|
4942
5078
|
this._pollInterval = options.pollInterval ?? 100;
|
|
4943
5079
|
this.relationships = options.relations ? parseRelationsConfig(options.relations, schemas) : [];
|
|
4944
5080
|
this._ctx = {
|
|
@@ -4946,7 +5082,10 @@ class _Database {
|
|
|
4946
5082
|
schemas: this.schemas,
|
|
4947
5083
|
relationships: this.relationships,
|
|
4948
5084
|
attachMethods: (name, entity) => attachMethods(this._ctx, name, entity),
|
|
4949
|
-
buildWhereClause: (conds, prefix) => buildWhereClause(conds, prefix)
|
|
5085
|
+
buildWhereClause: (conds, prefix) => buildWhereClause(conds, prefix),
|
|
5086
|
+
debug: this._debug,
|
|
5087
|
+
timestamps: this._timestamps,
|
|
5088
|
+
softDeletes: this._softDeletes
|
|
4950
5089
|
};
|
|
4951
5090
|
this.initializeTables();
|
|
4952
5091
|
if (this._reactive)
|
|
@@ -4965,7 +5104,17 @@ class _Database {
|
|
|
4965
5104
|
return createUpdateBuilder(this._ctx, entityName, idOrData);
|
|
4966
5105
|
},
|
|
4967
5106
|
upsert: (conditions, data) => upsert(this._ctx, entityName, data, conditions),
|
|
4968
|
-
delete: (id) =>
|
|
5107
|
+
delete: (id) => {
|
|
5108
|
+
if (typeof id === "number") {
|
|
5109
|
+
if (this._softDeletes) {
|
|
5110
|
+
const now = new Date().toISOString();
|
|
5111
|
+
this.db.run(`UPDATE "${entityName}" SET "deletedAt" = ? WHERE id = ?`, now, id);
|
|
5112
|
+
return;
|
|
5113
|
+
}
|
|
5114
|
+
return deleteEntity(this._ctx, entityName, id);
|
|
5115
|
+
}
|
|
5116
|
+
return createDeleteBuilder(this._ctx, entityName);
|
|
5117
|
+
},
|
|
4969
5118
|
select: (...cols) => createQueryBuilder(this._ctx, entityName, cols),
|
|
4970
5119
|
on: (event, callback) => {
|
|
4971
5120
|
return this._registerListener(entityName, event, callback);
|
|
@@ -4979,6 +5128,13 @@ class _Database {
|
|
|
4979
5128
|
for (const [entityName, schema] of Object.entries(this.schemas)) {
|
|
4980
5129
|
const storableFields = getStorableFields(schema);
|
|
4981
5130
|
const columnDefs = storableFields.map((f) => `"${f.name}" ${zodTypeToSqlType(f.type)}`);
|
|
5131
|
+
if (this._timestamps) {
|
|
5132
|
+
columnDefs.push('"createdAt" TEXT');
|
|
5133
|
+
columnDefs.push('"updatedAt" TEXT');
|
|
5134
|
+
}
|
|
5135
|
+
if (this._softDeletes) {
|
|
5136
|
+
columnDefs.push('"deletedAt" TEXT');
|
|
5137
|
+
}
|
|
4982
5138
|
const constraints = [];
|
|
4983
5139
|
const belongsToRels = this.relationships.filter((rel) => rel.type === "belongs-to" && rel.from === entityName);
|
|
4984
5140
|
for (const rel of belongsToRels) {
|
|
@@ -5103,7 +5259,21 @@ class _Database {
|
|
|
5103
5259
|
this.db.close();
|
|
5104
5260
|
}
|
|
5105
5261
|
query(callback) {
|
|
5106
|
-
return executeProxyQuery(this.schemas, callback, (sql, params) =>
|
|
5262
|
+
return executeProxyQuery(this.schemas, callback, (sql, params) => {
|
|
5263
|
+
if (this._debug)
|
|
5264
|
+
console.log("[satidb]", sql, params);
|
|
5265
|
+
return this.db.query(sql).all(...params);
|
|
5266
|
+
});
|
|
5267
|
+
}
|
|
5268
|
+
raw(sql, ...params) {
|
|
5269
|
+
if (this._debug)
|
|
5270
|
+
console.log("[satidb]", sql, params);
|
|
5271
|
+
return this.db.query(sql).all(...params);
|
|
5272
|
+
}
|
|
5273
|
+
exec(sql, ...params) {
|
|
5274
|
+
if (this._debug)
|
|
5275
|
+
console.log("[satidb]", sql, params);
|
|
5276
|
+
this.db.run(sql, ...params);
|
|
5107
5277
|
}
|
|
5108
5278
|
}
|
|
5109
5279
|
var Database = _Database;
|