@ailnaf/zoddb 0.0.0-stage → 0.2.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.
Files changed (93) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/LICENSE +15 -0
  3. package/README.md +440 -2
  4. package/dist/aggregate.d.ts +42 -0
  5. package/dist/aggregate.d.ts.map +1 -0
  6. package/dist/aggregate.js +77 -0
  7. package/dist/aggregate.js.map +1 -0
  8. package/dist/auth-schema.d.ts +26 -0
  9. package/dist/auth-schema.d.ts.map +1 -0
  10. package/dist/auth-schema.js +139 -0
  11. package/dist/auth-schema.js.map +1 -0
  12. package/dist/better-auth-adapter.d.ts +21 -0
  13. package/dist/better-auth-adapter.d.ts.map +1 -0
  14. package/dist/better-auth-adapter.js +175 -0
  15. package/dist/better-auth-adapter.js.map +1 -0
  16. package/dist/cli.d.ts +3 -0
  17. package/dist/cli.d.ts.map +1 -0
  18. package/dist/cli.js +341 -0
  19. package/dist/cli.js.map +1 -0
  20. package/dist/config.d.ts +22 -0
  21. package/dist/config.d.ts.map +1 -0
  22. package/dist/config.js +52 -0
  23. package/dist/config.js.map +1 -0
  24. package/dist/db.d.ts +183 -0
  25. package/dist/db.d.ts.map +1 -0
  26. package/dist/db.js +1063 -0
  27. package/dist/db.js.map +1 -0
  28. package/dist/ddl.d.ts +21 -0
  29. package/dist/ddl.d.ts.map +1 -0
  30. package/dist/ddl.js +127 -0
  31. package/dist/ddl.js.map +1 -0
  32. package/dist/dev.d.ts +9 -0
  33. package/dist/dev.d.ts.map +1 -0
  34. package/dist/dev.js +21 -0
  35. package/dist/dev.js.map +1 -0
  36. package/dist/errors.d.ts +63 -0
  37. package/dist/errors.d.ts.map +1 -0
  38. package/dist/errors.js +122 -0
  39. package/dist/errors.js.map +1 -0
  40. package/dist/filter.d.ts +67 -0
  41. package/dist/filter.d.ts.map +1 -0
  42. package/dist/filter.js +230 -0
  43. package/dist/filter.js.map +1 -0
  44. package/dist/graph.d.ts +54 -0
  45. package/dist/graph.d.ts.map +1 -0
  46. package/dist/graph.js +52 -0
  47. package/dist/graph.js.map +1 -0
  48. package/dist/index.d.ts +8 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +6 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/init.d.ts +12 -0
  53. package/dist/init.d.ts.map +1 -0
  54. package/dist/init.js +62 -0
  55. package/dist/init.js.map +1 -0
  56. package/dist/migrate.d.ts +58 -0
  57. package/dist/migrate.d.ts.map +1 -0
  58. package/dist/migrate.js +447 -0
  59. package/dist/migrate.js.map +1 -0
  60. package/dist/migrations.d.ts +80 -0
  61. package/dist/migrations.d.ts.map +1 -0
  62. package/dist/migrations.js +281 -0
  63. package/dist/migrations.js.map +1 -0
  64. package/dist/relation-graph.d.ts +50 -0
  65. package/dist/relation-graph.d.ts.map +1 -0
  66. package/dist/relation-graph.js +93 -0
  67. package/dist/relation-graph.js.map +1 -0
  68. package/dist/relations.d.ts +128 -0
  69. package/dist/relations.d.ts.map +1 -0
  70. package/dist/relations.js +21 -0
  71. package/dist/relations.js.map +1 -0
  72. package/dist/schema.d.ts +247 -0
  73. package/dist/schema.d.ts.map +1 -0
  74. package/dist/schema.js +584 -0
  75. package/dist/schema.js.map +1 -0
  76. package/dist/studio/page.d.ts +3 -0
  77. package/dist/studio/page.d.ts.map +1 -0
  78. package/dist/studio/page.js +708 -0
  79. package/dist/studio/page.js.map +1 -0
  80. package/dist/studio/server.d.ts +26 -0
  81. package/dist/studio/server.d.ts.map +1 -0
  82. package/dist/studio/server.js +415 -0
  83. package/dist/studio/server.js.map +1 -0
  84. package/dist/table.d.ts +156 -0
  85. package/dist/table.d.ts.map +1 -0
  86. package/dist/table.js +165 -0
  87. package/dist/table.js.map +1 -0
  88. package/dist/where.d.ts +18 -0
  89. package/dist/where.d.ts.map +1 -0
  90. package/dist/where.js +76 -0
  91. package/dist/where.js.map +1 -0
  92. package/docs/api.md +476 -0
  93. package/package.json +70 -4
package/dist/db.js ADDED
@@ -0,0 +1,1063 @@
1
+ import postgres from 'postgres';
2
+ import { PgError, IncludeAliasError, NoRowsError, RowValidationError, UndefinedParamError, UniqueViolationError, ForeignKeyViolationError, NotNullViolationError, isPgError, uniqueColumns, } from './errors.js';
3
+ import { compilePlanMappers, isSqlFragment } from './table.js';
4
+ import { compileCursor, compileOrderBy, compileWhere } from './filter.js';
5
+ import { aggregateImpl } from './aggregate.js';
6
+ import { resolveRelationColumn } from './relations.js';
7
+ const ISOLATION_LEVELS = ['read committed', 'repeatable read', 'serializable'];
8
+ export function many(table, opts) {
9
+ return opts ? { _kind: 'many', table, ...opts } : { _rel: 'many', table };
10
+ }
11
+ export function one(table, opts) {
12
+ return opts ? { _kind: 'one', table, ...opts } : { _rel: 'one', table };
13
+ }
14
+ function emitQuery(ctx, event) {
15
+ try {
16
+ ctx.hooks?.onQuery?.(event);
17
+ }
18
+ catch { /* 日志不能反噬业务 */ }
19
+ if (event.slow) {
20
+ try {
21
+ ctx.hooks?.onSlowQuery?.(event);
22
+ }
23
+ catch { /* 同上 */ }
24
+ }
25
+ }
26
+ async function trackQuery(ctx, meta, fn, countRows) {
27
+ const start = performance.now();
28
+ try {
29
+ const result = await fn();
30
+ const durationMs = performance.now() - start;
31
+ emitQuery(ctx, {
32
+ ...meta,
33
+ durationMs,
34
+ slow: ctx.slowQueryMs !== undefined && durationMs >= ctx.slowQueryMs,
35
+ replica: ctx.onReplica === true,
36
+ rows: countRows ? countRows(result) : (Array.isArray(result) ? result.length : undefined),
37
+ });
38
+ return result;
39
+ }
40
+ catch (error) {
41
+ const durationMs = performance.now() - start;
42
+ emitQuery(ctx, {
43
+ ...meta,
44
+ durationMs,
45
+ error,
46
+ slow: ctx.slowQueryMs !== undefined && durationMs >= ctx.slowQueryMs,
47
+ replica: ctx.onReplica === true,
48
+ });
49
+ throw error;
50
+ }
51
+ }
52
+ async function exec(ctx, text, params = [], meta = { operation: 'query' }) {
53
+ return trackQuery(ctx, { ...meta, sql: text, params }, () => ctx.raw.unsafe(text, params));
54
+ }
55
+ const readCtx = (ctx) => ctx.replica ? { ...ctx, raw: ctx.replica, onReplica: true } : ctx;
56
+ const quoteIdent = (s) => `"${s.replace(/"/g, '""')}"`;
57
+ /** 顶层用 begin;已在事务里时用 savepoint(postgres.js 的 TransactionSql 没有 begin) */
58
+ function beginOrSavepoint(conn, cb) {
59
+ const c = conn;
60
+ if (typeof c.begin === 'function')
61
+ return c.begin.call(conn, cb);
62
+ if (typeof c.savepoint === 'function')
63
+ return c.savepoint.call(conn, cb);
64
+ throw new Error('create/seed: 当前连接既不支持 begin 也不支持 savepoint');
65
+ }
66
+ const isSelectFragment = (v) => typeof v === 'object' && v !== null && v._tag === 'select';
67
+ function assertParams(params) {
68
+ for (let i = 0; i < params.length; i++) {
69
+ const p = params[i];
70
+ if (p === undefined)
71
+ throw new UndefinedParamError(i);
72
+ if (Array.isArray(p) && p.some(v => v === undefined))
73
+ throw new UndefinedParamError(i, 'array-element');
74
+ }
75
+ }
76
+ function mapError(e, table) {
77
+ if (!isPgError(e))
78
+ return e;
79
+ const cols = table ? table.__plan.map(p => p.dbKey) : [];
80
+ switch (e.code) {
81
+ case '23505':
82
+ return new UniqueViolationError(e, uniqueColumns(e.constraint_name, table?.name ?? e.table_name, cols));
83
+ case '23502':
84
+ return new NotNullViolationError(e);
85
+ case '23503':
86
+ return new ForeignKeyViolationError(e);
87
+ default:
88
+ return new PgError(e);
89
+ }
90
+ }
91
+ function readInclude(v, spec, validate, index, alias = '') {
92
+ if (spec._rel === 'one') {
93
+ if (v === null || v === undefined)
94
+ return null;
95
+ return mapRow(v, spec.table, undefined, validate, index);
96
+ }
97
+ if (v === null || v === undefined)
98
+ return [];
99
+ if (!Array.isArray(v))
100
+ throw new RowValidationError(spec.table.name, index, [], alias);
101
+ return v.map(el => mapRow(el, spec.table, undefined, validate, index));
102
+ }
103
+ function mapRow(row, table, include, validate, index) {
104
+ const out = validate ? table.__mapValid(row, index) : table.__mapFast(row);
105
+ if (include) {
106
+ for (const key of Object.keys(include)) {
107
+ if (validate && !(key in row))
108
+ throw new IncludeAliasError(table.name, key, index);
109
+ out[key] = readInclude(row[key], include[key], validate, index, key);
110
+ }
111
+ }
112
+ return out;
113
+ }
114
+ const dbKeyOf = (table, key) => table.__plan.find(p => p.outKey === key)?.dbKey ?? key;
115
+ function buildWhere(table, where, offset = 0, relations = []) {
116
+ if (isSqlFragment(where)) {
117
+ let text = '';
118
+ for (let i = 0; i < where.strings.length; i++) {
119
+ text += where.strings[i];
120
+ if (i < where.params.length)
121
+ text += `$${offset + i + 1}`;
122
+ }
123
+ return { text, params: [...where.params] };
124
+ }
125
+ return compileWhere(table, where, offset, relations);
126
+ }
127
+ function rawTextFragment(ctx, text) {
128
+ const strings = [text];
129
+ strings.raw = [text];
130
+ return ctx.raw(strings);
131
+ }
132
+ function makeSelect(ctx, table, keys, alias) {
133
+ for (const k of keys) {
134
+ if (!table.__plan.some(p => p.outKey === k))
135
+ throw new Error(`select: 未知列 ${table.name}.${k}`);
136
+ }
137
+ const prefix = alias ? `${quoteIdent(alias)}.` : '';
138
+ const text = keys.map(k => prefix + quoteIdent(dbKeyOf(table, k))).join(', ');
139
+ const plan = table.__plan.filter(p => keys.includes(p.outKey));
140
+ const compiled = compilePlanMappers(table.name, plan);
141
+ const parsers = plan.map(p => p.parse);
142
+ return {
143
+ _tag: 'select',
144
+ table,
145
+ keys,
146
+ __fragment: rawTextFragment(ctx, text),
147
+ __text: text,
148
+ __mapFast: compiled.fast,
149
+ __mapValid: (row, i) => compiled.valid(row, i, parsers, table.name, RowValidationError),
150
+ $row: undefined,
151
+ as: alias2 => makeSelect(ctx, table, keys, alias2),
152
+ toSQL: () => text,
153
+ };
154
+ }
155
+ // ---------------------------------------------------------------- Query
156
+ export class Query {
157
+ #pending;
158
+ #ctx;
159
+ #factory;
160
+ #sql;
161
+ #params;
162
+ constructor(ctx, factory, sql = '', params = []) {
163
+ this.#ctx = ctx;
164
+ this.#factory = factory;
165
+ this.#sql = sql;
166
+ this.#params = params;
167
+ }
168
+ pending() {
169
+ return (this.#pending ??= this.#factory());
170
+ }
171
+ async run(table) {
172
+ try {
173
+ return await trackQuery(this.#ctx, { sql: this.#sql, params: this.#params, operation: 'sql' }, () => this.pending(), result => result.length);
174
+ }
175
+ catch (e) {
176
+ throw mapError(e, table);
177
+ }
178
+ }
179
+ then(onfulfilled, onrejected) {
180
+ return this.run().then(onfulfilled, onrejected);
181
+ }
182
+ catch(onrejected) {
183
+ return this.run().catch(onrejected);
184
+ }
185
+ finally(onfinally) {
186
+ return this.run().finally(onfinally);
187
+ }
188
+ /** 原始行:不映射、不校验(逃生舱) */
189
+ execute() {
190
+ return this.run();
191
+ }
192
+ async rows(arg, opts) {
193
+ if (arg._tag === 'select') {
194
+ const rows = (await this.run(arg.table));
195
+ return rows.map((row, i) => this.#ctx.shouldValidate ? arg.__mapValid(row, i) : arg.__mapFast(row));
196
+ }
197
+ const rows = (await this.run(arg));
198
+ return rows.map((row, i) => mapRow(row, arg, opts?.include, this.#ctx.shouldValidate, i));
199
+ }
200
+ async one(arg, opts) {
201
+ const rows = await this.rows(arg, opts);
202
+ if (rows.length === 0)
203
+ throw new NoRowsError(arg._tag === 'select' ? arg.table.name : arg.name);
204
+ return rows[0];
205
+ }
206
+ async maybeOne(arg, opts) {
207
+ const rows = await this.rows(arg, opts);
208
+ return rows[0] ?? null;
209
+ }
210
+ }
211
+ async function insertImpl(ctx, table, values) {
212
+ const parsed = ctx.shouldValidate ? table.insertSchema.parse(values) : values;
213
+ const entries = Object.entries(parsed).filter(([, v]) => v !== undefined);
214
+ const text = entries.length === 0
215
+ ? `insert into ${quoteIdent(table.name)} default values returning *`
216
+ : `insert into ${quoteIdent(table.name)} (${entries.map(([k]) => quoteIdent(dbKeyOf(table, k))).join(', ')}) `
217
+ + `values (${entries.map((_, i) => `$${i + 1}`).join(', ')}) returning *`;
218
+ const params = entries.map(([, v]) => v);
219
+ try {
220
+ const res = await exec(ctx, text, params, { operation: 'insert', table: table.name });
221
+ return res.map((row, i) => mapRow(row, table, undefined, ctx.shouldValidate, i));
222
+ }
223
+ catch (e) {
224
+ throw mapError(e, table);
225
+ }
226
+ }
227
+ async function insertManyImpl(ctx, table, values, chunkSize = 1000) {
228
+ if (values.length === 0)
229
+ return [];
230
+ const parsed = values.map(v => ctx.shouldValidate ? table.insertSchema.parse(v) : v);
231
+ const first = parsed[0];
232
+ const keys = Object.keys(first).filter(k => first[k] !== undefined);
233
+ const width = keys.length;
234
+ const prefix = `insert into ${quoteIdent(table.name)} (${keys.map(k => quoteIdent(dbKeyOf(table, k))).join(', ')}) values `;
235
+ const out = [];
236
+ for (let start = 0; start < parsed.length; start += chunkSize) {
237
+ const chunk = parsed.slice(start, start + chunkSize);
238
+ const params = new Array(chunk.length * width);
239
+ let tuples = '';
240
+ let at = 0;
241
+ for (let r = 0; r < chunk.length; r++) {
242
+ const row = chunk[r];
243
+ if (r > 0)
244
+ tuples += ', ';
245
+ tuples += '(';
246
+ let c = 0;
247
+ // 单趟:校验列序/列集合 与 收集参数合并,避免额外分配
248
+ for (const k in row) {
249
+ const v = row[k];
250
+ if (v === undefined)
251
+ continue;
252
+ if (k !== keys[c]) {
253
+ throw new Error(`insertMany 要求统一列集合:第 ${start + r} 行出现列 "${k}"(期望 "${keys[c]}")`);
254
+ }
255
+ if (c > 0)
256
+ tuples += ', ';
257
+ params[at] = v;
258
+ at += 1;
259
+ tuples += `$${at}`;
260
+ c += 1;
261
+ }
262
+ if (c !== width)
263
+ throw new Error(`insertMany 要求统一列集合:第 ${start + r} 行的列数与首行不同`);
264
+ tuples += ')';
265
+ }
266
+ try {
267
+ const res = await exec(ctx, `${prefix}${tuples} returning *`, params, { operation: 'insertMany', table: table.name });
268
+ for (const row of res) {
269
+ out.push(mapRow(row, table, undefined, ctx.shouldValidate, out.length));
270
+ }
271
+ }
272
+ catch (e) {
273
+ throw mapError(e, table);
274
+ }
275
+ }
276
+ return out;
277
+ }
278
+ function linkPrimaryKey(def, operation) {
279
+ const pk = def.table.__plan.find(p => p.pk);
280
+ if (!pk)
281
+ throw new Error(`${operation}: ${def.table.name} 没有主键`);
282
+ return pk;
283
+ }
284
+ function parseLinkSpec(value, allowed, operation) {
285
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
286
+ throw new Error(`${operation} 需要对象`);
287
+ }
288
+ const spec = value;
289
+ for (const key of Object.keys(spec)) {
290
+ if (!allowed.includes(key)) {
291
+ throw new Error(`${operation} 只支持 ${allowed.join('/')}(收到 ${key})`);
292
+ }
293
+ }
294
+ return spec;
295
+ }
296
+ function linkIds(def, refs, operation) {
297
+ if (!Array.isArray(refs))
298
+ throw new Error(`${operation} 必须是数组`);
299
+ const pk = linkPrimaryKey(def, operation);
300
+ const ids = [];
301
+ const seen = new Set();
302
+ for (const ref of refs) {
303
+ if (typeof ref !== 'object' || ref === null || Array.isArray(ref)) {
304
+ throw new Error(`${operation} 的元素必须是对象`);
305
+ }
306
+ const id = ref[pk.outKey];
307
+ if (id === undefined || id === null)
308
+ throw new Error(`${operation} 需要 ${def.table.name}.${pk.outKey}`);
309
+ if (!seen.has(id)) {
310
+ seen.add(id);
311
+ ids.push(id);
312
+ }
313
+ }
314
+ return ids;
315
+ }
316
+ async function loadLinkTargets(ctx, def, ids, operation, requireAll) {
317
+ if (ids.length === 0)
318
+ return [];
319
+ const pk = linkPrimaryKey(def, operation);
320
+ const rows = await findManyImpl(ctx, def.table, {
321
+ where: { [pk.outKey]: { in: ids } },
322
+ });
323
+ const byId = new Map();
324
+ for (const row of rows)
325
+ byId.set(row[pk.outKey], row);
326
+ const out = [];
327
+ for (const id of ids) {
328
+ const row = byId.get(id);
329
+ if (!row) {
330
+ if (requireAll)
331
+ throw new Error(`${operation}: 找不到 ${def.table.name}.${String(id)}`);
332
+ continue;
333
+ }
334
+ out.push(row);
335
+ }
336
+ return out;
337
+ }
338
+ async function insertLinkRows(ctx, def, sourceValue, targets, operation) {
339
+ if (targets.length === 0)
340
+ return;
341
+ const from = resolveRelationColumn(def.from);
342
+ const to = resolveRelationColumn(def.to);
343
+ const throughFrom = resolveRelationColumn(def.through.from);
344
+ const throughTo = resolveRelationColumn(def.through.to);
345
+ if (sourceValue === undefined || sourceValue === null) {
346
+ throw new Error(`${operation}: ${from.table.name}.${from.outKey} 不能为空`);
347
+ }
348
+ for (const target of targets) {
349
+ const targetValue = target[to.outKey];
350
+ if (targetValue === undefined || targetValue === null) {
351
+ throw new Error(`${operation}: ${def.table.name}.${to.outKey} 不能为空`);
352
+ }
353
+ const text = `insert into ${quoteIdent(def.through.table.name)}`
354
+ + ` (${quoteIdent(throughFrom.dbKey)}, ${quoteIdent(throughTo.dbKey)})`
355
+ + ' values ($1, $2) on conflict do nothing';
356
+ await exec(ctx, text, [sourceValue, targetValue], {
357
+ operation: 'insert',
358
+ table: def.through.table.name,
359
+ });
360
+ }
361
+ }
362
+ async function deleteLinkRows(ctx, def, sourceValue, targetValues, operation) {
363
+ if (targetValues !== undefined && targetValues.length === 0)
364
+ return;
365
+ const from = resolveRelationColumn(def.from);
366
+ const to = resolveRelationColumn(def.to);
367
+ const throughFrom = resolveRelationColumn(def.through.from);
368
+ const throughTo = resolveRelationColumn(def.through.to);
369
+ if (sourceValue === undefined || sourceValue === null) {
370
+ throw new Error(`${operation}: ${from.table.name}.${from.outKey} 不能为空`);
371
+ }
372
+ let text = `delete from ${quoteIdent(def.through.table.name)}`
373
+ + ` where ${quoteIdent(throughFrom.dbKey)} = $1`;
374
+ const params = [sourceValue];
375
+ if (targetValues !== undefined) {
376
+ text += ` and ${quoteIdent(throughTo.dbKey)} in (${targetValues.map((_, i) => `$${i + 2}`).join(', ')})`;
377
+ params.push(...targetValues);
378
+ }
379
+ await exec(ctx, text, params, { operation: 'delete', table: def.through.table.name });
380
+ }
381
+ async function applyLinkWrite(ctx, parent, table, key, def, value) {
382
+ const operation = `update ${table.name}.${key}`;
383
+ const spec = parseLinkSpec(value, ['connect', 'disconnect', 'set'], operation);
384
+ if (spec.connect === undefined && spec.disconnect === undefined && spec.set === undefined) {
385
+ throw new Error(`${operation} 需要 connect/disconnect/set 之一`);
386
+ }
387
+ if (spec.set !== undefined && (spec.connect !== undefined || spec.disconnect !== undefined)) {
388
+ throw new Error(`${operation} 的 set 不能与 connect/disconnect 同时使用`);
389
+ }
390
+ const from = resolveRelationColumn(def.from);
391
+ const to = resolveRelationColumn(def.to);
392
+ const sourceValue = parent[from.outKey];
393
+ if (sourceValue === undefined || sourceValue === null) {
394
+ throw new Error(`${operation}: ${table.name}.${from.outKey} 不能为空`);
395
+ }
396
+ if (spec.set !== undefined) {
397
+ const ids = linkIds(def, spec.set, `${operation}.set`);
398
+ const targets = await loadLinkTargets(ctx, def, ids, `${operation}.set`, true);
399
+ await deleteLinkRows(ctx, def, sourceValue, undefined, operation);
400
+ await insertLinkRows(ctx, def, sourceValue, targets, operation);
401
+ return;
402
+ }
403
+ if (spec.disconnect !== undefined) {
404
+ const ids = linkIds(def, spec.disconnect, `${operation}.disconnect`);
405
+ const found = await loadLinkTargets(ctx, def, ids, `${operation}.disconnect`, false);
406
+ const targetValues = found.map(target => target[to.outKey]).filter(v => v !== undefined && v !== null);
407
+ await deleteLinkRows(ctx, def, sourceValue, targetValues, `${operation}.disconnect`);
408
+ }
409
+ if (spec.connect !== undefined) {
410
+ const ids = linkIds(def, spec.connect, `${operation}.connect`);
411
+ const targets = await loadLinkTargets(ctx, def, ids, `${operation}.connect`, true);
412
+ await insertLinkRows(ctx, def, sourceValue, targets, `${operation}.connect`);
413
+ }
414
+ }
415
+ async function createImpl(ctx, table, input) {
416
+ const rels = ctx.relations.find(r => r.table === table);
417
+ const columns = {};
418
+ const nested = [];
419
+ for (const [k, v] of Object.entries(input)) {
420
+ if (table.__plan.some(p => p.outKey === k))
421
+ columns[k] = v;
422
+ else if (rels?.map[k]) {
423
+ const def = rels.map[k];
424
+ if (def._write === false && !def.through)
425
+ throw new Error(`create: ${table.name}.${k} 是只读关系,不支持 nested create`);
426
+ nested.push({ key: k, def, value: v });
427
+ }
428
+ else
429
+ throw new Error(`create: 未知字段 ${table.name}.${k}`);
430
+ }
431
+ if (nested.length > 0 && table.__primaryKey.length > 0) {
432
+ throw new Error(`create: ${table.name} 使用复合主键,暂不支持 nested create 父行`);
433
+ }
434
+ return beginOrSavepoint(ctx.raw, async (txSql) => {
435
+ const tx = {
436
+ raw: txSql,
437
+ validate: ctx.validate,
438
+ shouldValidate: ctx.shouldValidate,
439
+ relations: ctx.relations,
440
+ };
441
+ const inserted = await insertImpl(tx, table, columns);
442
+ let parent = inserted[0];
443
+ const parentPk = table.__plan.find(p => p.pk);
444
+ for (const { key, def, value } of nested) {
445
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
446
+ throw new Error(`create: ${table.name}.${key} 需要 { create } 或 { connect }`);
447
+ }
448
+ const spec = value;
449
+ const from = resolveRelationColumn(def.from);
450
+ const to = resolveRelationColumn(def.to);
451
+ const childPk = def.table.__plan.find(p => p.pk);
452
+ if (def.through) {
453
+ const linkSpec = parseLinkSpec(value, ['connect'], `create ${table.name}.${key} M2M through`);
454
+ if (linkSpec.connect === undefined) {
455
+ parent[key] = [];
456
+ continue;
457
+ }
458
+ const ids = linkIds(def, linkSpec.connect, `create ${table.name}.${key}.connect`);
459
+ const targets = await loadLinkTargets(tx, def, ids, `create ${table.name}.${key}.connect`, true);
460
+ const sourceValue = parent[from.outKey];
461
+ await insertLinkRows(tx, def, sourceValue, targets, `create ${table.name}.${key}.connect`);
462
+ parent[key] = targets;
463
+ continue;
464
+ }
465
+ if (def._kind === 'many') {
466
+ const out = [];
467
+ if (spec.create) {
468
+ const childValues = spec.create.map(v => ({ ...v, [to.outKey]: parent[from.outKey] }));
469
+ out.push(...await insertManyImpl(tx, def.table, childValues));
470
+ }
471
+ if (spec.connect !== undefined) {
472
+ if (!Array.isArray(spec.connect))
473
+ throw new Error(`create: ${table.name}.${key} 的 connect 必须是数组`);
474
+ if (!childPk)
475
+ throw new Error(`create connect: ${def.table.name} 没有主键`);
476
+ for (const v of spec.connect) {
477
+ const id = v[childPk.outKey];
478
+ if (id === undefined || id === null)
479
+ throw new Error(`create connect: 需要 ${def.table.name}.${childPk.outKey}`);
480
+ const rows = await updateImpl(tx, def.table, { [to.outKey]: parent[from.outKey] }, { [childPk.outKey]: id });
481
+ if (rows.length === 0)
482
+ throw new Error(`create connect: 找不到 ${def.table.name}.${String(id)}`);
483
+ out.push(...rows);
484
+ }
485
+ }
486
+ parent[key] = out;
487
+ continue;
488
+ }
489
+ if (spec.create)
490
+ throw new Error(`create: ${table.name}.${key} 是 one 关系,只支持 connect`);
491
+ if (spec.connect === undefined) {
492
+ parent[key] = null;
493
+ continue;
494
+ }
495
+ if (typeof spec.connect !== 'object' || Array.isArray(spec.connect))
496
+ throw new Error(`create: ${table.name}.${key} 的 connect 必须是对象`);
497
+ if (!childPk || !parentPk)
498
+ throw new Error(`create connect: ${def.table.name} 或 ${table.name} 没有主键`);
499
+ const connect = spec.connect;
500
+ const id = connect[childPk.outKey];
501
+ if (id === undefined || id === null)
502
+ throw new Error(`create connect: 需要 ${def.table.name}.${childPk.outKey}`);
503
+ const target = await findOneImpl(tx, def.table, { where: { [childPk.outKey]: id } });
504
+ if (!target)
505
+ throw new Error(`create connect: 找不到 ${def.table.name}.${String(id)}`);
506
+ const rows = await updateImpl(tx, table, { [from.outKey]: target[to.outKey] }, { [parentPk.outKey]: parent[parentPk.outKey] });
507
+ if (rows.length === 0)
508
+ throw new Error(`create connect: 更新 ${table.name} 失败`);
509
+ parent = rows[0];
510
+ parent[key] = target;
511
+ }
512
+ return parent;
513
+ });
514
+ }
515
+ async function seedImpl(ctx, table, rows, opts) {
516
+ if (rows.length === 0)
517
+ return [];
518
+ if (!opts.conflict || opts.conflict.length === 0)
519
+ throw new Error('seed: conflict 不能为空');
520
+ // seed 默认只插入缺失行;要覆盖已有行必须显式传 update
521
+ const upsertOpts = { conflict: opts.conflict, update: opts.update ?? [] };
522
+ return beginOrSavepoint(ctx.raw, async (txSql) => {
523
+ const tx = { ...ctx, raw: txSql };
524
+ const out = [];
525
+ for (const row of rows)
526
+ out.push(...await upsertImpl(tx, table, row, upsertOpts));
527
+ return out;
528
+ });
529
+ }
530
+ async function updateColumnsImpl(ctx, table, values, where) {
531
+ const parsed = ctx.shouldValidate ? table.updateSchema.parse(values) : values;
532
+ const entries = Object.entries(parsed).filter(([, v]) => v !== undefined);
533
+ if (entries.length === 0)
534
+ throw new Error('update: 没有要更新的列');
535
+ const sets = entries.map(([k], i) => `${quoteIdent(dbKeyOf(table, k))} = $${i + 1}`);
536
+ const w = buildWhere(table, where, entries.length);
537
+ if (!w.text)
538
+ throw new Error('where 不能为空(拒绝全表操作)');
539
+ const text = `update ${quoteIdent(table.name)} set ${sets.join(', ')} where ${w.text} returning *`;
540
+ const params = [...entries.map(([, v]) => v), ...w.params];
541
+ try {
542
+ const res = await exec(ctx, text, params, { operation: 'insert', table: table.name });
543
+ return res.map((row, i) => mapRow(row, table, undefined, ctx.shouldValidate, i));
544
+ }
545
+ catch (e) {
546
+ throw mapError(e, table);
547
+ }
548
+ }
549
+ async function updateImpl(ctx, table, values, where) {
550
+ const rels = ctx.relations.find(r => r.table === table);
551
+ const columns = {};
552
+ const links = [];
553
+ const unknown = [];
554
+ for (const [k, v] of Object.entries(values)) {
555
+ if (v === undefined)
556
+ continue;
557
+ if (table.__plan.some(p => p.outKey === k)) {
558
+ columns[k] = v;
559
+ continue;
560
+ }
561
+ const def = rels?.map[k];
562
+ if (def?.through) {
563
+ links.push({ key: k, def, value: v });
564
+ continue;
565
+ }
566
+ if (def)
567
+ throw new Error(`update: ${table.name}.${k} 不支持关系写入(只有 M2M through 支持)`);
568
+ unknown.push(k);
569
+ }
570
+ // 保留既有 update 的 ZodError 语义:未知列先交给严格 schema 报错。
571
+ if (unknown.length > 0) {
572
+ if (ctx.shouldValidate)
573
+ table.updateSchema.parse(values);
574
+ throw new Error(`update: 未知字段 ${table.name}.${unknown[0]}`);
575
+ }
576
+ if (links.length === 0)
577
+ return updateColumnsImpl(ctx, table, columns, where);
578
+ return beginOrSavepoint(ctx.raw, async (txSql) => {
579
+ const tx = { ...ctx, raw: txSql };
580
+ let rows;
581
+ if (Object.keys(columns).length > 0) {
582
+ rows = await updateColumnsImpl(tx, table, columns, where);
583
+ }
584
+ else {
585
+ rows = await findManyImpl(tx, table, { where: where, limit: 2 });
586
+ }
587
+ if (rows.length !== 1) {
588
+ throw new Error(`update: M2M 关系写入要求 where 命中恰好 1 行(命中 ${rows.length} 行)`);
589
+ }
590
+ const parent = rows[0];
591
+ for (const { key, def, value } of links) {
592
+ await applyLinkWrite(tx, parent, table, key, def, value);
593
+ }
594
+ return rows;
595
+ });
596
+ }
597
+ async function deleteImpl(ctx, table, where) {
598
+ const w = buildWhere(table, where);
599
+ if (!w.text)
600
+ throw new Error('where 不能为空(拒绝全表操作)');
601
+ try {
602
+ const res = await exec(ctx, `delete from ${quoteIdent(table.name)} where ${w.text}`, w.params, { operation: 'delete', table: table.name });
603
+ return res.count;
604
+ }
605
+ catch (e) {
606
+ throw mapError(e, table);
607
+ }
608
+ }
609
+ async function upsertImpl(ctx, table, values, opts) {
610
+ const parsed = ctx.shouldValidate ? table.insertSchema.parse(values) : values;
611
+ const entries = Object.entries(parsed).filter(([, v]) => v !== undefined);
612
+ if (entries.length === 0)
613
+ throw new Error('upsert: 没有要写入的列');
614
+ const dbKeyStrict = (k, kind) => {
615
+ const plan = table.__plan.find(p => p.outKey === k);
616
+ if (!plan)
617
+ throw new Error(`upsert: 未知${kind} ${table.name}.${k}`);
618
+ return plan.dbKey;
619
+ };
620
+ const conflict = opts.conflict.map(k => dbKeyStrict(k, '冲突列'));
621
+ const updateKeys = opts.update ?? Object.keys(parsed).filter(k => {
622
+ if (opts.conflict.includes(k))
623
+ return false;
624
+ return !table.__plan.find(p => p.outKey === k)?.pk; // 默认不覆盖主键
625
+ });
626
+ const updateCols = updateKeys.filter(k => parsed[k] !== undefined).map(k => dbKeyStrict(k, '更新列'));
627
+ const text = `insert into ${quoteIdent(table.name)} (${entries.map(([k]) => quoteIdent(dbKeyOf(table, k))).join(', ')}) `
628
+ + `values (${entries.map((_, i) => `$${i + 1}`).join(', ')}) `
629
+ + `on conflict (${conflict.map(quoteIdent).join(', ')}) `
630
+ + (updateCols.length === 0
631
+ ? 'do nothing'
632
+ : `do update set ${updateCols.map(c => `${quoteIdent(c)} = excluded.${quoteIdent(c)}`).join(', ')}`)
633
+ + ' returning *';
634
+ try {
635
+ const res = await exec(ctx, text, entries.map(([, v]) => v), { operation: 'upsert', table: table.name });
636
+ return res.map((row, i) => mapRow(row, table, undefined, ctx.shouldValidate, i));
637
+ }
638
+ catch (e) {
639
+ throw mapError(e, table);
640
+ }
641
+ }
642
+ async function countImpl(ctx, table, where) {
643
+ const w = where === undefined ? undefined : buildWhere(table, where, 0, ctx.relations);
644
+ const text = `select count(*)::int as n from ${quoteIdent(table.name)}` + (w ? ` where ${w.text}` : '');
645
+ const rows = await exec(ctx, text, w?.params ?? [], { operation: 'count', table: table.name });
646
+ return rows[0]?.n ?? 0;
647
+ }
648
+ const AGGREGATE_NUMERIC = /^(int|int2|int4|int8|smallint|integer|bigint|numeric|decimal|real|double|float)/;
649
+ function isPlainObject(value) {
650
+ return typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date);
651
+ }
652
+ function parseAggregate(table, raw) {
653
+ if (!isPlainObject(raw))
654
+ throw new Error(`with: ${table.name}.aggregate 必须是对象`);
655
+ const allowed = new Set(['count', 'sum', 'max']);
656
+ for (const key of Object.keys(raw)) {
657
+ if (!allowed.has(key))
658
+ throw new Error(`with: ${table.name}.aggregate 不支持 ${key}`);
659
+ }
660
+ if (raw.count !== undefined && raw.count !== true)
661
+ throw new Error(`with: ${table.name}.aggregate.count 必须是 true`);
662
+ const count = raw.count === true;
663
+ const parseColumns = (name) => {
664
+ const value = raw[name];
665
+ if (value === undefined)
666
+ return [];
667
+ if (!isPlainObject(value))
668
+ throw new Error(`with: ${table.name}.aggregate.${name} 必须是对象`);
669
+ const result = [];
670
+ for (const [outKey, enabled] of Object.entries(value)) {
671
+ if (enabled !== true)
672
+ throw new Error(`with: ${table.name}.aggregate.${name}.${outKey} 必须是 true`);
673
+ const plan = table.__plan.find(p => p.outKey === outKey);
674
+ if (!plan)
675
+ throw new Error(`with: ${table.name}.aggregate.${name} 未知列 ${outKey}`);
676
+ if (name === 'sum' && !AGGREGATE_NUMERIC.test(plan.sqlType)) {
677
+ throw new Error(`with: ${table.name}.aggregate.sum.${outKey} 必须是数值列(${plan.sqlType})`);
678
+ }
679
+ result.push({ outKey, dbKey: plan.dbKey, parse: value => plan.parse.parse(value) });
680
+ }
681
+ return result;
682
+ };
683
+ const sum = parseColumns('sum');
684
+ const max = parseColumns('max');
685
+ if (!count && sum.length === 0 && max.length === 0) {
686
+ throw new Error(`with: ${table.name}.aggregate 至少需要 count/sum/max 之一`);
687
+ }
688
+ return { count, sum, max };
689
+ }
690
+ const MAX_WITH_DEPTH = 3;
691
+ function resolveWith(ctx, table, withMap, depth = 1) {
692
+ if (depth > MAX_WITH_DEPTH)
693
+ throw new Error(`with: 关系加载超过最大深度 ${MAX_WITH_DEPTH}`);
694
+ const entries = Object.entries(withMap).filter(([, v]) => v !== undefined && v !== false);
695
+ if (entries.length === 0)
696
+ return [];
697
+ const rels = ctx.relations.find(r => r.table === table);
698
+ if (!rels)
699
+ throw new Error(`with: 表 ${table.name} 没有定义 relations()`);
700
+ return entries.map(([key, enabled]) => {
701
+ const def = rels.map[key];
702
+ if (!def)
703
+ throw new Error(`with: 未知关系 ${table.name}.${key}`);
704
+ if (enabled === true)
705
+ return { key, def };
706
+ if (typeof enabled !== 'object' || enabled === null || Array.isArray(enabled)) {
707
+ throw new Error(`with: ${table.name}.${key} 只支持 true/false 或关系选项对象`);
708
+ }
709
+ const opts = enabled;
710
+ const allowed = new Set(['where', 'orderBy', 'limit', 'offset', 'with', 'aggregate']);
711
+ for (const k of Object.keys(opts)) {
712
+ if (!allowed.has(k))
713
+ throw new Error(`with: ${table.name}.${key} 不支持选项 ${k}`);
714
+ }
715
+ if (Object.keys(opts).length === 0)
716
+ throw new Error(`with: ${table.name}.${key} 需要 where/orderBy/limit/offset/with/aggregate 之一`);
717
+ if (def.through && (opts.where !== undefined || opts.orderBy !== undefined || opts.limit !== undefined || opts.offset !== undefined)) {
718
+ throw new Error(`with: M2M through 关系 ${table.name}.${key} 暂不支持 where/orderBy/limit/offset`);
719
+ }
720
+ if (opts.where !== undefined && (typeof opts.where !== 'object' || opts.where === null || Array.isArray(opts.where))) {
721
+ throw new Error(`with: ${table.name}.${key}.where 必须是对象`);
722
+ }
723
+ if (opts.with !== undefined && (typeof opts.with !== 'object' || opts.with === null || Array.isArray(opts.with))) {
724
+ throw new Error(`with: ${table.name}.${key}.with 必须是对象`);
725
+ }
726
+ const aggregate = opts.aggregate === undefined ? undefined : parseAggregate(def.table, opts.aggregate);
727
+ if (aggregate && table.__plan.some(p => p.outKey === `${key}Aggregate`)) {
728
+ throw new Error(`with: 聚合别名 ${key}Aggregate 与 ${table.name} 的真实列冲突`);
729
+ }
730
+ return {
731
+ key,
732
+ def,
733
+ where: opts.where,
734
+ orderBy: opts.orderBy,
735
+ limit: opts.limit,
736
+ offset: opts.offset,
737
+ with: opts.with,
738
+ aggregate,
739
+ };
740
+ });
741
+ }
742
+ async function attachThroughRelation(ctx, rows, key, def, through, nested, depth = 1) {
743
+ const from = resolveRelationColumn(def.from);
744
+ const to = resolveRelationColumn(def.to);
745
+ const throughFrom = resolveRelationColumn(through.from);
746
+ const throughTo = resolveRelationColumn(through.to);
747
+ const values = [...new Set(rows.map(r => r[from.outKey]).filter(v => v !== null && v !== undefined))];
748
+ const links = new Map();
749
+ if (values.length > 0) {
750
+ const linkRows = await exec(ctx, `select ${quoteIdent(throughFrom.dbKey)}, ${quoteIdent(throughTo.dbKey)} from ${quoteIdent(through.table.name)} where ${quoteIdent(throughFrom.dbKey)} = any($1)`, [values], { operation: 'with', table: through.table.name });
751
+ for (const row of linkRows) {
752
+ const parent = row[throughFrom.dbKey];
753
+ const target = row[throughTo.dbKey];
754
+ if (parent === null || parent === undefined || target === null || target === undefined)
755
+ continue;
756
+ const bucket = links.get(parent);
757
+ if (bucket)
758
+ bucket.push(target);
759
+ else
760
+ links.set(parent, [target]);
761
+ }
762
+ }
763
+ const targetValues = [...new Set([...links.values()].flat())];
764
+ let children = [];
765
+ if (targetValues.length > 0) {
766
+ const childRows = await exec(ctx, `select * from ${quoteIdent(def.table.name)} where ${quoteIdent(to.dbKey)} = any($1)`, [targetValues], { operation: 'with', table: def.table.name });
767
+ children = childRows.map((row, i) => mapRow(row, def.table, undefined, ctx.shouldValidate, i));
768
+ }
769
+ const byTarget = new Map();
770
+ for (const child of children)
771
+ byTarget.set(child[to.outKey], child);
772
+ for (const row of rows) {
773
+ const ids = links.get(row[from.outKey]);
774
+ const bucket = ids
775
+ ? [...new Set(ids)].map(id => byTarget.get(id)).filter((child) => child !== undefined)
776
+ : [];
777
+ row[key] = bucket;
778
+ }
779
+ if (nested.length > 0 && children.length > 0)
780
+ await attachRelations(ctx, children, nested, depth);
781
+ }
782
+ function validateWithNumber(name, value) {
783
+ if (value === undefined)
784
+ return;
785
+ if (!Number.isInteger(value) || value < 0)
786
+ throw new Error(`with: ${name} 必须是非负整数`);
787
+ }
788
+ async function attachAggregate(ctx, rows, key, def, aggregate, where) {
789
+ const from = resolveRelationColumn(def.from);
790
+ const to = resolveRelationColumn(def.to);
791
+ const through = def.through;
792
+ const values = [...new Set(rows.map(r => r[from.outKey]).filter(v => v !== null && v !== undefined))];
793
+ const targetAliasName = '__zd_agg_target';
794
+ const targetAlias = quoteIdent(targetAliasName);
795
+ let parentRef;
796
+ let fromClause;
797
+ if (through) {
798
+ const throughFrom = resolveRelationColumn(through.from);
799
+ const throughTo = resolveRelationColumn(through.to);
800
+ const throughAlias = quoteIdent('__zd_agg_through');
801
+ parentRef = `${throughAlias}.${quoteIdent(throughFrom.dbKey)}`;
802
+ fromClause = `from ${quoteIdent(through.table.name)} as ${throughAlias}`
803
+ + ` join ${quoteIdent(def.table.name)} as ${targetAlias}`
804
+ + ` on ${targetAlias}.${quoteIdent(to.dbKey)} = ${throughAlias}.${quoteIdent(throughTo.dbKey)}`;
805
+ }
806
+ else {
807
+ parentRef = `${targetAlias}.${quoteIdent(to.dbKey)}`;
808
+ fromClause = `from ${quoteIdent(def.table.name)} as ${targetAlias}`;
809
+ }
810
+ const sumFields = aggregate.sum.map((p, i) => ({ ...p, alias: `__zd_sum_${i}` }));
811
+ const maxFields = aggregate.max.map((p, i) => ({ ...p, alias: `__zd_max_${i}` }));
812
+ const select = [`${parentRef} as "__zd_parent"`];
813
+ if (aggregate.count)
814
+ select.push('count(*)::int as "__zd_count"');
815
+ for (const p of sumFields)
816
+ select.push(`sum(${targetAlias}.${quoteIdent(p.dbKey)})::float8 as ${quoteIdent(p.alias)}`);
817
+ for (const p of maxFields)
818
+ select.push(`max(${targetAlias}.${quoteIdent(p.dbKey)}) as ${quoteIdent(p.alias)}`);
819
+ const wherePart = where === undefined
820
+ ? { text: '', params: [] }
821
+ : compileWhere(def.table, where, 1, [], targetAliasName);
822
+ const params = [values, ...wherePart.params];
823
+ const predicate = wherePart.text ? ` and (${wherePart.text})` : '';
824
+ const text = `select ${select.join(', ')} ${fromClause}`
825
+ + ` where ${parentRef} = any($1)${predicate}`
826
+ + ` group by ${parentRef}`;
827
+ const aggregateRows = values.length === 0
828
+ ? []
829
+ : await exec(ctx, text, params, { operation: 'with', table: def.table.name });
830
+ const byParent = new Map();
831
+ for (const row of aggregateRows) {
832
+ const parent = row.__zd_parent;
833
+ if (parent !== null && parent !== undefined)
834
+ byParent.set(parent, row);
835
+ }
836
+ for (const row of rows) {
837
+ const parent = row[from.outKey];
838
+ const raw = parent === null || parent === undefined ? undefined : byParent.get(parent);
839
+ const result = {};
840
+ if (aggregate.count)
841
+ result.count = Number(raw?.__zd_count ?? 0);
842
+ if (sumFields.length > 0) {
843
+ result.sum = Object.fromEntries(sumFields.map(p => [p.outKey, raw?.[p.alias] == null ? null : Number(raw[p.alias])]));
844
+ }
845
+ if (maxFields.length > 0) {
846
+ result.max = Object.fromEntries(maxFields.map(p => [p.outKey, raw?.[p.alias] == null ? null : p.parse(raw[p.alias])]));
847
+ }
848
+ row[`${key}Aggregate`] = result;
849
+ }
850
+ }
851
+ async function attachRelations(ctx, rows, resolved, depth = 1) {
852
+ for (const { key, def, where, orderBy, limit, offset, with: nestedInput, aggregate } of resolved) {
853
+ const nested = nestedInput !== undefined ? resolveWith(ctx, def.table, nestedInput, depth + 1) : [];
854
+ if (def.through) {
855
+ await attachThroughRelation(ctx, rows, key, def, def.through, nested, depth + 1);
856
+ if (aggregate)
857
+ await attachAggregate(ctx, rows, key, def, aggregate, where);
858
+ continue;
859
+ }
860
+ const from = resolveRelationColumn(def.from);
861
+ const to = resolveRelationColumn(def.to);
862
+ if (def._kind === 'one' && (limit !== undefined || offset !== undefined)) {
863
+ throw new Error(`with: one 关系 ${def.table.name}.${key} 不支持 limit/offset`);
864
+ }
865
+ if (offset !== undefined && limit === undefined) {
866
+ throw new Error(`with: ${def.table.name}.${key} 使用 offset 时必须同时提供 limit`);
867
+ }
868
+ validateWithNumber(`${def.table.name}.${key}.limit`, limit);
869
+ validateWithNumber(`${def.table.name}.${key}.offset`, offset);
870
+ if ((limit !== undefined || offset !== undefined) && orderBy === undefined) {
871
+ throw new Error(`with: ${def.table.name}.${key} 使用 limit/offset 时必须提供 orderBy`);
872
+ }
873
+ const values = [...new Set(rows.map(r => r[from.outKey]).filter(v => v !== null && v !== undefined))];
874
+ let children = [];
875
+ if (values.length > 0) {
876
+ let text;
877
+ let params;
878
+ if (limit !== undefined || offset !== undefined) {
879
+ const alias = '__zd_rel';
880
+ const order = compileOrderBy(def.table, orderBy);
881
+ if (!order)
882
+ throw new Error(`with: ${def.table.name}.${key} 的 orderBy 不能为空`);
883
+ const wherePart = where === undefined
884
+ ? { text: '', params: [] }
885
+ : compileWhere(def.table, where, 1, [], alias);
886
+ params = [values, ...wherePart.params];
887
+ const predicate = wherePart.text ? ` and (${wherePart.text})` : '';
888
+ const lo = offset ?? 0;
889
+ const hi = lo + limit;
890
+ text = `select * from (`
891
+ + `select ${quoteIdent(alias)}.*, row_number() over (partition by ${quoteIdent(alias)}.${quoteIdent(to.dbKey)} order by ${order}) as "__zd_rn"`
892
+ + ` from ${quoteIdent(def.table.name)} as ${quoteIdent(alias)}`
893
+ + ` where ${quoteIdent(alias)}.${quoteIdent(to.dbKey)} = any($1)${predicate}`
894
+ + `) as "__zd_rel_rows" where "__zd_rel_rows"."__zd_rn" > ${lo} and "__zd_rel_rows"."__zd_rn" <= ${hi}`;
895
+ }
896
+ else {
897
+ const wherePart = where === undefined
898
+ ? { text: '', params: [] }
899
+ : compileWhere(def.table, where, 1, []);
900
+ params = [values, ...wherePart.params];
901
+ const predicate = wherePart.text ? ` and (${wherePart.text})` : '';
902
+ const order = orderBy === undefined ? '' : compileOrderBy(def.table, orderBy);
903
+ text = `select * from ${quoteIdent(def.table.name)} where ${quoteIdent(to.dbKey)} = any($1)${predicate}`;
904
+ if (order)
905
+ text += ` order by ${order}`;
906
+ }
907
+ const childRows = await exec(ctx, text, params, { operation: 'with', table: def.table.name });
908
+ children = childRows.map((row, i) => mapRow(row, def.table, undefined, ctx.shouldValidate, i));
909
+ }
910
+ const buckets = new Map();
911
+ for (const child of children) {
912
+ const k = child[to.outKey];
913
+ const bucket = buckets.get(k);
914
+ if (bucket)
915
+ bucket.push(child);
916
+ else
917
+ buckets.set(k, [child]);
918
+ }
919
+ for (const row of rows) {
920
+ const k = row[from.outKey];
921
+ const bucket = k === null || k === undefined ? undefined : buckets.get(k);
922
+ row[key] = def._kind === 'many' ? (bucket ?? []) : (bucket?.[0] ?? null);
923
+ }
924
+ if (nested.length > 0 && children.length > 0)
925
+ await attachRelations(ctx, children, nested, depth + 1);
926
+ if (aggregate)
927
+ await attachAggregate(ctx, rows, key, def, aggregate, where);
928
+ }
929
+ }
930
+ async function findManyImpl(ctx, table, opts = {}) {
931
+ const resolved = opts.with ? resolveWith(ctx, table, opts.with, 1) : [];
932
+ const selectEntries = opts.select ? Object.entries(opts.select).filter(([, v]) => v) : null;
933
+ if (selectEntries && selectEntries.length === 0)
934
+ throw new Error('select 不能为空');
935
+ const selectKeys = selectEntries ? selectEntries.map(([k]) => k) : null;
936
+ const hidden = new Set();
937
+ if (selectKeys) {
938
+ for (const { def } of resolved) {
939
+ const from = resolveRelationColumn(def.from);
940
+ if (!selectKeys.includes(from.outKey)) {
941
+ selectKeys.push(from.outKey);
942
+ hidden.add(from.outKey);
943
+ }
944
+ }
945
+ }
946
+ const sel = selectKeys ? makeSelect(ctx, table, selectKeys) : null;
947
+ const where = opts.where === undefined ? { text: '', params: [] } : compileWhere(table, opts.where, 0, ctx.relations);
948
+ const order = opts.orderBy === undefined ? '' : compileOrderBy(table, opts.orderBy);
949
+ const params = [...where.params];
950
+ const conditions = [];
951
+ if (where.text)
952
+ conditions.push(where.text);
953
+ if (opts.cursor !== undefined) {
954
+ const c = compileCursor(table, opts.orderBy, opts.cursor, params.length + 1);
955
+ conditions.push(c.text);
956
+ params.push(...c.params);
957
+ }
958
+ let text = `select ${sel ? sel.toSQL() : '*'} from ${quoteIdent(table.name)}`;
959
+ if (conditions.length > 0)
960
+ text += ` where ${conditions.join(' and ')}`;
961
+ if (order)
962
+ text += ` order by ${order}`;
963
+ if (opts.limit !== undefined) {
964
+ if (!Number.isInteger(opts.limit) || opts.limit < 0)
965
+ throw new Error('limit 必须是非负整数');
966
+ params.push(opts.limit);
967
+ text += ` limit $${params.length}`;
968
+ }
969
+ if (opts.offset !== undefined) {
970
+ if (!Number.isInteger(opts.offset) || opts.offset < 0)
971
+ throw new Error('offset 必须是非负整数');
972
+ params.push(opts.offset);
973
+ text += ` offset $${params.length}`;
974
+ }
975
+ const rows = await exec(ctx, text, params, { operation: 'find', table: table.name });
976
+ const mapped = sel
977
+ ? rows.map((row, i) => (ctx.shouldValidate ? sel.__mapValid(row, i) : sel.__mapFast(row)))
978
+ : rows.map((row, i) => mapRow(row, table, undefined, ctx.shouldValidate, i));
979
+ if (resolved.length > 0)
980
+ await attachRelations(ctx, mapped, resolved);
981
+ if (hidden.size > 0) {
982
+ for (const row of mapped)
983
+ for (const k of hidden)
984
+ delete row[k];
985
+ }
986
+ return mapped;
987
+ }
988
+ async function findOneImpl(ctx, table, opts = {}) {
989
+ const rows = await findManyImpl(ctx, table, { ...opts, limit: 1 });
990
+ return rows[0] ?? null;
991
+ }
992
+ export function createDb(conn, validate = 'always', relations = [], opts = {}) {
993
+ const seen = new Set();
994
+ for (const r of relations) {
995
+ if (seen.has(r.table))
996
+ throw new Error(`relations: 表 ${r.table.name} 定义了多组 relations()`);
997
+ seen.add(r.table);
998
+ }
999
+ const ctx = {
1000
+ raw: conn,
1001
+ validate,
1002
+ shouldValidate: validate === 'always',
1003
+ relations,
1004
+ hooks: opts.hooks,
1005
+ slowQueryMs: opts.slowQueryMs,
1006
+ replica: opts.replica,
1007
+ };
1008
+ const db = {
1009
+ raw: conn,
1010
+ sql: ((strings, ...params) => {
1011
+ const args = params.map(p => isSelectFragment(p) ? p.__fragment : p);
1012
+ assertParams(args);
1013
+ const c = ctx.raw;
1014
+ const text = strings.reduce((out, part, i) => out + part + (i < args.length ? `$${i + 1}` : ''), '');
1015
+ return new Query(ctx, () => c(strings, ...args), text, args);
1016
+ }),
1017
+ select: ((table, columns) => makeSelect(ctx, table, columns ? Object.keys(columns) : Object.keys(table.columns))),
1018
+ insert: (table, values) => insertImpl(ctx, table, values),
1019
+ insertMany: (table, values, opts) => insertManyImpl(ctx, table, values, opts?.chunkSize),
1020
+ create: ((table, input) => createImpl(ctx, table, input)),
1021
+ seed: ((table, rows, opts) => seedImpl(ctx, table, rows, opts)),
1022
+ update: (table, values, where) => updateImpl(ctx, table, values, where),
1023
+ delete: (table, where) => deleteImpl(ctx, table, where),
1024
+ upsert: (table, values, opts) => upsertImpl(ctx, table, values, opts),
1025
+ count: (table, where) => countImpl(readCtx(ctx), table, where),
1026
+ aggregate: (async (table, opts) => {
1027
+ const rc = readCtx(ctx);
1028
+ return (await aggregateImpl(rc.raw, table, opts, false, (sql, params, meta) => exec(rc, sql, params, meta)))[0];
1029
+ }),
1030
+ groupBy: ((table, opts) => {
1031
+ const rc = readCtx(ctx);
1032
+ return aggregateImpl(rc.raw, table, opts, true, (sql, params, meta) => exec(rc, sql, params, meta));
1033
+ }),
1034
+ findMany: ((table, opts) => findManyImpl(readCtx(ctx), table, opts)),
1035
+ findOne: ((table, opts) => findOneImpl(readCtx(ctx), table, opts)),
1036
+ begin(fn, opts = {}) {
1037
+ if (opts.isolation !== undefined && !ISOLATION_LEVELS.includes(opts.isolation)) {
1038
+ throw new Error(`begin: 不支持的隔离级别 ${String(opts.isolation)}(可选:${ISOLATION_LEVELS.join(' / ')})`);
1039
+ }
1040
+ const options = opts.isolation ? `isolation level ${opts.isolation}` : '';
1041
+ const tx = conn.begin(options, (txSql) => fn(createDb(txSql, validate, ctx.relations, { hooks: ctx.hooks, slowQueryMs: ctx.slowQueryMs })));
1042
+ return tx;
1043
+ },
1044
+ close: async () => {
1045
+ const targets = new Set([conn]);
1046
+ if (ctx.replica)
1047
+ targets.add(ctx.replica);
1048
+ await Promise.all([...targets].map(c => c.end({ timeout: 5 })));
1049
+ },
1050
+ };
1051
+ return db;
1052
+ }
1053
+ export function defineDb(opts) {
1054
+ // notice 默认静音:`drop table if exists` 之类的 NOTICE 不该刷屏
1055
+ const conn = opts.connection ?? postgres(opts.url ?? process.env.DATABASE_URL ?? '', { onnotice: () => { } });
1056
+ const replica = opts.replica ?? (opts.replicaUrl ? postgres(opts.replicaUrl, { onnotice: () => { } }) : undefined);
1057
+ return createDb(conn, opts.validate ?? 'always', (opts.relations ?? []), {
1058
+ hooks: opts.hooks,
1059
+ slowQueryMs: opts.slowQueryMs,
1060
+ replica,
1061
+ });
1062
+ }
1063
+ //# sourceMappingURL=db.js.map