typion 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +4 -2
- package/bin/typion.js +7 -0
- package/dist/auth/better-auth.d.ts +13 -0
- package/dist/auth/better-auth.js +11 -0
- package/dist/auth/better-auth.js.map +1 -0
- package/dist/cli/index.d.ts +14 -0
- package/dist/cli/index.js +883 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/column-kinds-Bkm4NJI2.js +47 -0
- package/dist/column-kinds-Bkm4NJI2.js.map +1 -0
- package/dist/context-B4e2eREP.d.ts +2100 -0
- package/dist/contract/index.d.ts +137 -0
- package/dist/contract/index.js +42 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/db/pglite.d.ts +12 -0
- package/dist/db/pglite.js +27 -0
- package/dist/db/pglite.js.map +1 -0
- package/dist/descriptor-Cc3mGp73.js +12 -0
- package/dist/descriptor-Cc3mGp73.js.map +1 -0
- package/dist/errors-ChDBxeS-.js +17 -0
- package/dist/errors-ChDBxeS-.js.map +1 -0
- package/dist/errors-D-CKO-px.js +114 -0
- package/dist/errors-D-CKO-px.js.map +1 -0
- package/dist/handler-BWVU8TCR.d.ts +23 -0
- package/dist/handler-_87bFkbP.js +565 -0
- package/dist/handler-_87bFkbP.js.map +1 -0
- package/dist/hono/index.d.ts +17 -0
- package/dist/hono/index.js +26 -0
- package/dist/hono/index.js.map +1 -0
- package/dist/http/index.d.ts +13 -0
- package/dist/http/index.js +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1324 -0
- package/dist/index.js.map +1 -0
- package/dist/issues-DLqCBQT1.js +42 -0
- package/dist/issues-DLqCBQT1.js.map +1 -0
- package/dist/next/index.d.ts +8 -0
- package/dist/next/index.js +18 -0
- package/dist/next/index.js.map +1 -0
- package/dist/operations-D3rOacLX.js +1811 -0
- package/dist/operations-D3rOacLX.js.map +1 -0
- package/dist/schema/index.d.ts +18 -0
- package/dist/schema/index.js +3 -0
- package/dist/schema-Rzzq97cK.js +68 -0
- package/dist/schema-Rzzq97cK.js.map +1 -0
- package/dist/seed/index.d.ts +19 -0
- package/dist/seed/index.js +85 -0
- package/dist/seed/index.js.map +1 -0
- package/dist/structural-BZnjFmCs.js +247 -0
- package/dist/structural-BZnjFmCs.js.map +1 -0
- package/dist/testing/index.d.ts +26 -0
- package/dist/testing/index.js +92 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/typion-Dh2BCSRz.js +1187 -0
- package/dist/typion-Dh2BCSRz.js.map +1 -0
- package/dist/validation/zod.d.ts +21 -0
- package/dist/validation/zod.js +84 -0
- package/dist/validation/zod.js.map +1 -0
- package/docs/README.md +145 -0
- package/docs/access.md +114 -0
- package/docs/auth.md +195 -0
- package/docs/collections.md +172 -0
- package/docs/database.md +76 -0
- package/docs/features/localization.md +112 -0
- package/docs/features/multi-tenant.md +166 -0
- package/docs/features/soft-delete.md +85 -0
- package/docs/features/versions.md +106 -0
- package/docs/hooks.md +106 -0
- package/docs/http.md +188 -0
- package/docs/queries.md +200 -0
- package/docs/testing.md +85 -0
- package/package.json +125 -3
|
@@ -0,0 +1,1187 @@
|
|
|
1
|
+
import { a as InternalError, c as PluginError, d as ValidationError, i as ForbiddenError, n as ConfigError, r as ConflictError, s as NotFoundError, t as BadRequestError, u as UnsupportedError } from "./errors-D-CKO-px.js";
|
|
2
|
+
import { A as evaluateRule, B as pluginOptions, C as afterOperation, D as NO_TRAIT_OPTIONS, E as runOperation, F as partitionFilter, G as FALSE_FILTER, H as readStates, I as pluginFilter, J as guard, K as andFilters, L as traitFilter, M as fieldWriteCheck, N as filterToSQL, O as READING, P as forbidden, Q as operationMeta, R as kindExpression, S as afterLink, T as queueAfterCommit, U as traitOptions, V as projectCells, W as writeStates, X as CommitQueue, Y as normalizeError, Z as buildContext, _ as updateDocument, a as runEntityOperation, b as assertData, c as create, d as lockRow, f as mergedOptions, g as update, h as remove, i as operationMethods, j as fieldReadDenied, k as WRITING, l as deleteDocument, m as referenceOptionKeys, o as assertId, ot as traitOperations, p as partitionAllowance, q as isPlainObject, r as operationInput, s as assertOptions, u as deleteOptionKeys, v as writeOptionKeys, w as beforeOperation, x as assertScalars, y as writeRule, z as kindFilterSQL } from "./operations-D3rOacLX.js";
|
|
3
|
+
import { t as descriptor } from "./descriptor-Cc3mGp73.js";
|
|
4
|
+
import { n as structuralValidation } from "./structural-BZnjFmCs.js";
|
|
5
|
+
import { PgTable, getTableConfig } from "drizzle-orm/pg-core";
|
|
6
|
+
import { and, asc, eq, getTableColumns, inArray, is, sql } from "drizzle-orm";
|
|
7
|
+
//#region \0rolldown/runtime.js
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __exportAll = (all, no_symbols) => {
|
|
10
|
+
let target = {};
|
|
11
|
+
for (var name in all) __defProp(target, name, {
|
|
12
|
+
get: all[name],
|
|
13
|
+
enumerable: true
|
|
14
|
+
});
|
|
15
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
16
|
+
return target;
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/runtime/dialect/pg.ts
|
|
20
|
+
function keyLiteral(key) {
|
|
21
|
+
return sql.raw(`'${key.replace(/'/g, "''")}'`);
|
|
22
|
+
}
|
|
23
|
+
const CAST_NAME = /^[a-z_][a-z0-9_ ]*(\([0-9, ]*\))?$/i;
|
|
24
|
+
const SQLSTATE = /^[0-9A-Z]{5}$/;
|
|
25
|
+
function findPgError(error) {
|
|
26
|
+
let current = error;
|
|
27
|
+
for (let depth = 0; depth < 8 && typeof current === "object" && current !== null; depth++) {
|
|
28
|
+
const code = current.code;
|
|
29
|
+
if (typeof code === "string" && SQLSTATE.test(code)) return current;
|
|
30
|
+
current = current.cause;
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
function isQueryError(error) {
|
|
35
|
+
return error instanceof Error && typeof error.query === "string" && Array.isArray(error.params);
|
|
36
|
+
}
|
|
37
|
+
function mapSqlState(pg, cause) {
|
|
38
|
+
const message = pg.message ?? "Database error";
|
|
39
|
+
const details = {
|
|
40
|
+
sqlstate: pg.code,
|
|
41
|
+
table: pg.table,
|
|
42
|
+
constraint: pg.constraint
|
|
43
|
+
};
|
|
44
|
+
switch (pg.code) {
|
|
45
|
+
case "23505": return new ConflictError(message, "unique", {
|
|
46
|
+
constraint: pg.constraint,
|
|
47
|
+
details,
|
|
48
|
+
cause
|
|
49
|
+
});
|
|
50
|
+
case "23503": return new ConflictError(message, "foreign_key", {
|
|
51
|
+
constraint: pg.constraint,
|
|
52
|
+
details,
|
|
53
|
+
cause
|
|
54
|
+
});
|
|
55
|
+
case "23514": return new ConflictError(message, "check", {
|
|
56
|
+
constraint: pg.constraint,
|
|
57
|
+
details,
|
|
58
|
+
cause
|
|
59
|
+
});
|
|
60
|
+
case "23502": return new ValidationError(message, [{
|
|
61
|
+
path: pg.column === void 0 ? [] : [pg.column],
|
|
62
|
+
message: "must not be null"
|
|
63
|
+
}], {
|
|
64
|
+
details,
|
|
65
|
+
cause
|
|
66
|
+
});
|
|
67
|
+
case "40001": return new ConflictError(message, "serialization", {
|
|
68
|
+
details,
|
|
69
|
+
cause
|
|
70
|
+
});
|
|
71
|
+
case "40P01": return new ConflictError(message, "deadlock", {
|
|
72
|
+
details,
|
|
73
|
+
cause
|
|
74
|
+
});
|
|
75
|
+
case "22P02":
|
|
76
|
+
case "22007": return new BadRequestError(message, {
|
|
77
|
+
details,
|
|
78
|
+
cause
|
|
79
|
+
});
|
|
80
|
+
default: return new InternalError("Database error", {
|
|
81
|
+
details,
|
|
82
|
+
cause
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function qualifiedName(table) {
|
|
87
|
+
const config = getTableConfig(table);
|
|
88
|
+
const schema = config.schema ?? "public";
|
|
89
|
+
return {
|
|
90
|
+
schema,
|
|
91
|
+
name: config.name,
|
|
92
|
+
label: schema === "public" ? config.name : `${schema}.${config.name}`
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function createPgDialect() {
|
|
96
|
+
return {
|
|
97
|
+
id: "pg",
|
|
98
|
+
jsonText(column, key) {
|
|
99
|
+
return sql`${column}->>${keyLiteral(key)}`;
|
|
100
|
+
},
|
|
101
|
+
cast(value, type) {
|
|
102
|
+
if (!CAST_NAME.test(type)) throw new InternalError(`cast: '${type}' is not a type name`);
|
|
103
|
+
return sql`(${value})::${sql.raw(type)}`;
|
|
104
|
+
},
|
|
105
|
+
jsonbMerge(column, patch) {
|
|
106
|
+
return sql`${column} || ${sql.param(patch, column)}`;
|
|
107
|
+
},
|
|
108
|
+
jsonParam(value, column) {
|
|
109
|
+
return sql`${sql.param(value, column)}`;
|
|
110
|
+
},
|
|
111
|
+
async lockSelect(tx, table, where) {
|
|
112
|
+
return await tx.select().from(table).where(where).for("update");
|
|
113
|
+
},
|
|
114
|
+
mapError(error) {
|
|
115
|
+
const pg = findPgError(error);
|
|
116
|
+
if (pg !== null) return mapSqlState(pg, error);
|
|
117
|
+
if (isQueryError(error)) return new InternalError("Database error", { cause: error });
|
|
118
|
+
return null;
|
|
119
|
+
},
|
|
120
|
+
async introspect(db, tables) {
|
|
121
|
+
const drift = {
|
|
122
|
+
missingTables: [],
|
|
123
|
+
missingColumns: []
|
|
124
|
+
};
|
|
125
|
+
if (tables.length === 0) return drift;
|
|
126
|
+
const names = tables.map(qualifiedName);
|
|
127
|
+
const matches = names.map((t) => sql`(table_schema = ${t.schema} and table_name = ${t.name})`);
|
|
128
|
+
const rows = await db.select({
|
|
129
|
+
schema: sql`table_schema`,
|
|
130
|
+
table: sql`table_name`,
|
|
131
|
+
column: sql`column_name`
|
|
132
|
+
}).from(sql`information_schema.columns`).where(sql.join(matches, sql` or `));
|
|
133
|
+
const present = /* @__PURE__ */ new Map();
|
|
134
|
+
for (const row of rows) {
|
|
135
|
+
const key = `${row.schema}.${row.table}`;
|
|
136
|
+
const columns = present.get(key) ?? /* @__PURE__ */ new Set();
|
|
137
|
+
columns.add(row.column);
|
|
138
|
+
present.set(key, columns);
|
|
139
|
+
}
|
|
140
|
+
tables.forEach((table, i) => {
|
|
141
|
+
const name = names[i];
|
|
142
|
+
const columns = present.get(`${name.schema}.${name.name}`);
|
|
143
|
+
if (columns === void 0) {
|
|
144
|
+
drift.missingTables.push(name.label);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const missing = getTableConfig(table).columns.map((c) => c.name).filter((c) => !columns.has(c));
|
|
148
|
+
if (missing.length > 0) drift.missingColumns.push({
|
|
149
|
+
table: name.label,
|
|
150
|
+
columns: missing
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
return drift;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/runtime/constants.ts
|
|
159
|
+
const QUERY_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
160
|
+
"columns",
|
|
161
|
+
"with",
|
|
162
|
+
"where",
|
|
163
|
+
"extras",
|
|
164
|
+
"orderBy",
|
|
165
|
+
"offset",
|
|
166
|
+
"limit",
|
|
167
|
+
"comment"
|
|
168
|
+
]);
|
|
169
|
+
//#endregion
|
|
170
|
+
//#region src/runtime/read/virtual-table.ts
|
|
171
|
+
function virtualTable(table, entity, dialect, kinds) {
|
|
172
|
+
if (entity === null) return table;
|
|
173
|
+
const rewritten = new Set(entity.kindKeys.filter((k) => entity.columns[k]?.columnKind?.expression !== void 0));
|
|
174
|
+
const hidden = new Set(Object.keys(entity.columns).filter((k) => entity.columns[k]?.read === false));
|
|
175
|
+
if (rewritten.size === 0 && hidden.size === 0) return table;
|
|
176
|
+
return new Proxy(table, { get(target, key, receiver) {
|
|
177
|
+
if (typeof key === "string") {
|
|
178
|
+
if (hidden.has(key)) return void 0;
|
|
179
|
+
if (rewritten.has(key)) {
|
|
180
|
+
const column = Reflect.get(target, key, receiver);
|
|
181
|
+
return kindExpression(entity.columns[key]?.columnKind, column, kinds, dialect, null);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return Reflect.get(target, key, receiver);
|
|
185
|
+
} });
|
|
186
|
+
}
|
|
187
|
+
//#endregion
|
|
188
|
+
//#region src/runtime/read/compose.ts
|
|
189
|
+
function projectedKinds(entity) {
|
|
190
|
+
const out = [];
|
|
191
|
+
for (const key of entity?.kindKeys ?? []) {
|
|
192
|
+
const kind = entity?.columns[key]?.columnKind;
|
|
193
|
+
if (kind?.project !== void 0) out.push([key, kind]);
|
|
194
|
+
}
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
function swappedSource(entity, runtime, bound, meta, options) {
|
|
198
|
+
for (const plugin of runtime.plugins) {
|
|
199
|
+
const source = plugin.traits?.source;
|
|
200
|
+
const trait = entity.traits[plugin.id];
|
|
201
|
+
if (source === void 0 || trait === void 0) continue;
|
|
202
|
+
const swapped = source({
|
|
203
|
+
entity,
|
|
204
|
+
state: trait.state,
|
|
205
|
+
operation: meta.operation,
|
|
206
|
+
options: options.get(plugin.id) ?? {},
|
|
207
|
+
ctx: bound.ctx
|
|
208
|
+
});
|
|
209
|
+
if (swapped === void 0) continue;
|
|
210
|
+
if (runtime.relations[swapped.table] === void 0) throw new InternalError(`Plugin '${plugin.id}' reads '${entity.name}' from '${swapped.table}', which is not a table of the engine`, { entity: entity.name });
|
|
211
|
+
return {
|
|
212
|
+
plugin: plugin.id,
|
|
213
|
+
key: swapped.table,
|
|
214
|
+
filter: swapped.filter,
|
|
215
|
+
rule: swapped.rule ?? "read"
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
function sourceOf(entity, runtime, swapped) {
|
|
221
|
+
if (swapped === null) return {
|
|
222
|
+
key: entity.name,
|
|
223
|
+
table: entity.table
|
|
224
|
+
};
|
|
225
|
+
return {
|
|
226
|
+
key: swapped.key,
|
|
227
|
+
table: runtime.relations[swapped.key]?.table
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
var Composer = class {
|
|
231
|
+
req;
|
|
232
|
+
nested = /* @__PURE__ */ new Map();
|
|
233
|
+
readOptions;
|
|
234
|
+
constructor(req) {
|
|
235
|
+
this.req = req;
|
|
236
|
+
this.readOptions = new Set(pluginOptions(req.runtime.plugins).read.keys());
|
|
237
|
+
}
|
|
238
|
+
async rootFilter(entity) {
|
|
239
|
+
const { bound, meta, traitOptions, source } = this.req;
|
|
240
|
+
const rule = source?.rule ?? "read";
|
|
241
|
+
const outcome = await evaluateRule(entity, rule, bound, meta);
|
|
242
|
+
if (outcome.kind === "deny") throw forbidden(entity.name, rule, meta, `access.${rule} denies '${meta.operation}'`);
|
|
243
|
+
return andFilters([
|
|
244
|
+
pluginFilter(entity, bound, meta.operation, traitOptions, {
|
|
245
|
+
writing: false,
|
|
246
|
+
skip: source?.plugin
|
|
247
|
+
}),
|
|
248
|
+
source?.filter,
|
|
249
|
+
outcome.kind === "filter" ? outcome.filter : void 0
|
|
250
|
+
]);
|
|
251
|
+
}
|
|
252
|
+
nestedFilter(tableName) {
|
|
253
|
+
let pending = this.nested.get(tableName);
|
|
254
|
+
if (pending === void 0) {
|
|
255
|
+
pending = this.computeNested(tableName);
|
|
256
|
+
this.nested.set(tableName, pending);
|
|
257
|
+
}
|
|
258
|
+
return pending;
|
|
259
|
+
}
|
|
260
|
+
async computeNested(tableName) {
|
|
261
|
+
const { runtime, bound, meta, traitOptions } = this.req;
|
|
262
|
+
const entity = runtime.entityOrNull(tableName);
|
|
263
|
+
if (entity === null) {
|
|
264
|
+
if (bound.$mode === "system") return void 0;
|
|
265
|
+
throw new ForbiddenError(`Table '${tableName}' is not an entity: user-mode reads cannot reach it through a relation`, {
|
|
266
|
+
operation: meta.operation,
|
|
267
|
+
rule: "entity"
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
const outcome = await evaluateRule(entity, "read", bound, meta);
|
|
271
|
+
if (outcome.kind === "deny") return FALSE_FILTER;
|
|
272
|
+
return andFilters([pluginFilter(entity, bound, meta.operation, traitOptions, READING), outcome.kind === "filter" ? outcome.filter : void 0]);
|
|
273
|
+
}
|
|
274
|
+
async compileRoot(entity, config, injected) {
|
|
275
|
+
const source = sourceOf(entity, this.req.runtime, this.req.source);
|
|
276
|
+
const parts = andFilters([await this.rootFilter(entity), injected]);
|
|
277
|
+
const level = await this.compileLevel(source.key, entity, config, parts, "root");
|
|
278
|
+
const where = level.config.where;
|
|
279
|
+
return {
|
|
280
|
+
...level,
|
|
281
|
+
source,
|
|
282
|
+
where
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
async compileWhere(entity, where, injected) {
|
|
286
|
+
const source = sourceOf(entity, this.req.runtime, this.req.source);
|
|
287
|
+
const user = where === void 0 ? void 0 : await this.rewriteWhere(where, source.key, entity);
|
|
288
|
+
return {
|
|
289
|
+
source,
|
|
290
|
+
where: andFilters([
|
|
291
|
+
await this.rootFilter(entity),
|
|
292
|
+
injected,
|
|
293
|
+
user
|
|
294
|
+
])
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
async compileLevel(tableKey, entity, raw, parts, level) {
|
|
298
|
+
const root = level === "root";
|
|
299
|
+
const cfg = raw === true ? {} : raw;
|
|
300
|
+
if (!isPlainObject(cfg)) throw new BadRequestError(`with.${level} must be true or a query config object`);
|
|
301
|
+
for (const key of Object.keys(cfg)) if (!QUERY_CONFIG_KEYS.has(key) && !(root && this.readOptions.has(key))) throw new BadRequestError(`Unknown query option '${key}'${root ? "" : ` in with.${level}`}`);
|
|
302
|
+
const { runtime, bound, meta } = this.req;
|
|
303
|
+
const out = {};
|
|
304
|
+
for (const key of [
|
|
305
|
+
"offset",
|
|
306
|
+
"limit",
|
|
307
|
+
"comment"
|
|
308
|
+
]) if (cfg[key] !== void 0) out[key] = cfg[key];
|
|
309
|
+
if (root && entity !== null) this.checkLimit(entity, cfg.limit);
|
|
310
|
+
const swappedRoot = root && this.req.source !== null;
|
|
311
|
+
const columns = this.columnsFor(entity, cfg.columns, swappedRoot);
|
|
312
|
+
if (columns !== void 0) out.columns = columns;
|
|
313
|
+
const userWhere = cfg.where === void 0 ? void 0 : await this.rewriteWhere(cfg.where, tableKey, entity);
|
|
314
|
+
const where = andFilters([parts, userWhere]);
|
|
315
|
+
if (where !== void 0) out.where = where;
|
|
316
|
+
const orderBy = cfg.orderBy ?? (root ? entity?.defaultOrder ?? void 0 : void 0);
|
|
317
|
+
if (orderBy !== void 0) out.orderBy = this.rewriteOrderBy(orderBy, tableKey, entity);
|
|
318
|
+
if (cfg.extras !== void 0) out.extras = this.wrapExtras(cfg.extras, entity);
|
|
319
|
+
const withPlan = {};
|
|
320
|
+
if (cfg.with !== void 0) {
|
|
321
|
+
if (!isPlainObject(cfg.with)) throw new BadRequestError("with must be an object");
|
|
322
|
+
const relations = runtime.relations[tableKey]?.relations ?? {};
|
|
323
|
+
const outWith = {};
|
|
324
|
+
for (const [key, value] of Object.entries(cfg.with)) {
|
|
325
|
+
if (value === void 0 || value === false) continue;
|
|
326
|
+
const relation = relations[key];
|
|
327
|
+
if (relation === void 0) throw new BadRequestError(`Unknown relation '${key}' on '${tableKey}'`);
|
|
328
|
+
const target = relation.targetTableName;
|
|
329
|
+
const compiled = await this.compileLevel(target, runtime.entityOrNull(target), value, await this.nestedFilter(target), key);
|
|
330
|
+
outWith[key] = compiled.config;
|
|
331
|
+
withPlan[key] = compiled.plan;
|
|
332
|
+
}
|
|
333
|
+
out.with = outWith;
|
|
334
|
+
}
|
|
335
|
+
return {
|
|
336
|
+
config: out,
|
|
337
|
+
plan: {
|
|
338
|
+
kinds: projectedKinds(entity),
|
|
339
|
+
nullify: entity === null ? [] : fieldReadDenied(entity, bound, meta),
|
|
340
|
+
with: withPlan
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
checkLimit(entity, limit) {
|
|
345
|
+
if (limit === void 0) return;
|
|
346
|
+
if (typeof limit !== "number" || !Number.isInteger(limit) || limit < 0) throw new BadRequestError("limit must be a non-negative integer");
|
|
347
|
+
if (limit > entity.maxLimit) throw new BadRequestError(`limit ${limit} exceeds the maximum of ${entity.maxLimit} for '${entity.name}'`, { entity: entity.name });
|
|
348
|
+
}
|
|
349
|
+
columnKeys(tableKey, entity) {
|
|
350
|
+
if (entity !== null) return new Set(Object.keys(entity.columns));
|
|
351
|
+
const table = this.req.runtime.relations[tableKey]?.table;
|
|
352
|
+
return new Set(table === void 0 ? [] : Object.keys(getTableColumns(table)));
|
|
353
|
+
}
|
|
354
|
+
hiddenKeys(entity) {
|
|
355
|
+
if (entity === null) return [];
|
|
356
|
+
return Object.keys(entity.columns).filter((k) => entity.columns[k]?.read === false);
|
|
357
|
+
}
|
|
358
|
+
columnsFor(entity, columns, swappedRoot) {
|
|
359
|
+
if (entity === null) return columns;
|
|
360
|
+
const hidden = this.hiddenKeys(entity);
|
|
361
|
+
if (columns === void 0) {
|
|
362
|
+
if (!swappedRoot && hidden.length === 0) return void 0;
|
|
363
|
+
return Object.fromEntries(entity.visibleKeys.map((k) => [k, true]));
|
|
364
|
+
}
|
|
365
|
+
if (!isPlainObject(columns)) throw new BadRequestError("columns must be an object");
|
|
366
|
+
const entries = Object.entries(columns).filter(([, v]) => v !== void 0);
|
|
367
|
+
for (const [key, value] of entries) {
|
|
368
|
+
if (!(key in entity.columns)) throw new BadRequestError(`Unknown column '${key}' on '${entity.name}'`, { entity: entity.name });
|
|
369
|
+
if (typeof value !== "boolean") throw new BadRequestError(`columns.${key} must be a boolean`);
|
|
370
|
+
if (value && hidden.includes(key)) throw new BadRequestError(`Column '${key}' of '${entity.name}' is not readable`, { entity: entity.name });
|
|
371
|
+
}
|
|
372
|
+
if (entries.some(([, v]) => v === true)) return Object.fromEntries(entries.filter(([, v]) => v));
|
|
373
|
+
const omitted = /* @__PURE__ */ new Set([...entries.map(([k]) => k), ...hidden]);
|
|
374
|
+
return Object.fromEntries(entity.visibleKeys.filter((k) => !omitted.has(k)).map((k) => [k, true]));
|
|
375
|
+
}
|
|
376
|
+
async rewriteWhere(where, tableKey, entity) {
|
|
377
|
+
if (!isPlainObject(where)) throw new BadRequestError("where must be an object");
|
|
378
|
+
const { runtime } = this.req;
|
|
379
|
+
const columns = this.columnKeys(tableKey, entity);
|
|
380
|
+
const relations = runtime.relations[tableKey]?.relations ?? {};
|
|
381
|
+
const plain = {};
|
|
382
|
+
const extra = [];
|
|
383
|
+
for (const [key, value] of Object.entries(where)) {
|
|
384
|
+
if (value === void 0) continue;
|
|
385
|
+
if (key === "AND" || key === "OR") {
|
|
386
|
+
if (!Array.isArray(value)) throw new BadRequestError(`where.${key} must be an array`);
|
|
387
|
+
plain[key] = await Promise.all(value.map((v) => this.rewriteWhere(v, tableKey, entity)));
|
|
388
|
+
} else if (key === "NOT") plain.NOT = await this.rewriteWhere(value, tableKey, entity);
|
|
389
|
+
else if (key === "RAW") plain.RAW = typeof value === "function" ? (t, ops) => value(this.virtual(t, entity), ops) : value;
|
|
390
|
+
else if (columns.has(key)) {
|
|
391
|
+
if (entity?.columns[key]?.read === false) throw new BadRequestError(`Column '${key}' of '${entity.name}' is not readable`, { entity: entity.name });
|
|
392
|
+
const kind = entity?.columns[key]?.columnKind;
|
|
393
|
+
if (kind?.expression !== void 0) {
|
|
394
|
+
const { dialect } = runtime;
|
|
395
|
+
const { kinds } = this.req;
|
|
396
|
+
extra.push({ RAW: (t) => kindFilterSQL((sample) => kindExpression(kind, t[key], kinds, dialect, sample), key, value) });
|
|
397
|
+
} else plain[key] = value;
|
|
398
|
+
} else if (relations[key] !== void 0) {
|
|
399
|
+
const target = relations[key].targetTableName;
|
|
400
|
+
const parts = await this.nestedFilter(target);
|
|
401
|
+
if (value === true) plain[key] = parts ?? true;
|
|
402
|
+
else if (value === false) {
|
|
403
|
+
if (parts === void 0) plain[key] = false;
|
|
404
|
+
else extra.push({ NOT: { [key]: parts } });
|
|
405
|
+
} else if (isPlainObject(value)) {
|
|
406
|
+
const inner = await this.rewriteWhere(value, target, runtime.entityOrNull(target));
|
|
407
|
+
plain[key] = andFilters([parts, inner]) ?? {};
|
|
408
|
+
} else throw new BadRequestError(`where.${key} must be a boolean or a filter object`);
|
|
409
|
+
} else throw new BadRequestError(`Unknown filter field '${key}' on '${tableKey}'`);
|
|
410
|
+
}
|
|
411
|
+
return extra.length === 0 ? plain : { AND: [plain, ...extra] };
|
|
412
|
+
}
|
|
413
|
+
rewriteOrderBy(orderBy, tableKey, entity) {
|
|
414
|
+
if (typeof orderBy === "function") return (t, ops) => orderBy(this.virtual(t, entity), ops);
|
|
415
|
+
if (!isPlainObject(orderBy)) throw new BadRequestError("orderBy must be an object or a callback");
|
|
416
|
+
const columns = this.columnKeys(tableKey, entity);
|
|
417
|
+
const entries = Object.entries(orderBy).filter(([, v]) => v !== void 0);
|
|
418
|
+
for (const [key, dir] of entries) {
|
|
419
|
+
if (!columns.has(key) || entity?.columns[key]?.read === false) throw new BadRequestError(`Cannot order '${tableKey}' by '${key}'`);
|
|
420
|
+
if (dir !== "asc" && dir !== "desc") throw new BadRequestError(`orderBy.${key} must be 'asc' or 'desc'`);
|
|
421
|
+
}
|
|
422
|
+
const rewritten = new Set(entries.map(([key]) => key).filter((key) => entity?.columns[key]?.columnKind?.expression !== void 0));
|
|
423
|
+
if (rewritten.size === 0) return orderBy;
|
|
424
|
+
const { dialect } = this.req.runtime;
|
|
425
|
+
const { kinds } = this.req;
|
|
426
|
+
return (t, ops) => entries.map(([key, dir]) => {
|
|
427
|
+
const column = t[key];
|
|
428
|
+
const target = rewritten.has(key) ? kindExpression(entity?.columns[key]?.columnKind, column, kinds, dialect, null) : column;
|
|
429
|
+
return dir === "asc" ? ops.asc(target) : ops.desc(target);
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
wrapExtras(extras, entity) {
|
|
433
|
+
if (!isPlainObject(extras)) throw new BadRequestError("extras must be an object");
|
|
434
|
+
const out = {};
|
|
435
|
+
for (const [key, value] of Object.entries(extras)) out[key] = typeof value === "function" ? (t, ops) => value(this.virtual(t, entity), ops) : value;
|
|
436
|
+
return out;
|
|
437
|
+
}
|
|
438
|
+
virtual(table, entity) {
|
|
439
|
+
return virtualTable(table, entity, this.req.runtime.dialect, this.req.kinds);
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
//#endregion
|
|
443
|
+
//#region src/runtime/read/find.ts
|
|
444
|
+
function parseOptions(entity, bound, operation, config) {
|
|
445
|
+
const { runtime } = bound;
|
|
446
|
+
const traits = traitOptions(entity, runtime.plugins, "read", config);
|
|
447
|
+
const kinds = readStates(runtime.plugins, bound.ctx, config);
|
|
448
|
+
const meta = operationMeta(bound.ctx, entity.name, operation, runtime.now());
|
|
449
|
+
return {
|
|
450
|
+
composer: new Composer({
|
|
451
|
+
runtime,
|
|
452
|
+
bound,
|
|
453
|
+
meta,
|
|
454
|
+
kinds,
|
|
455
|
+
traitOptions: traits,
|
|
456
|
+
source: swappedSource(entity, runtime, bound, meta, traits)
|
|
457
|
+
}),
|
|
458
|
+
kinds
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
function collect(rows, plan, cells, hidden) {
|
|
462
|
+
for (const row of rows) {
|
|
463
|
+
for (const [key, kind] of plan.kinds) if (key in row) cells.push({
|
|
464
|
+
row,
|
|
465
|
+
key,
|
|
466
|
+
kind
|
|
467
|
+
});
|
|
468
|
+
for (const key of plan.nullify) if (key in row) hidden.push({
|
|
469
|
+
row,
|
|
470
|
+
key
|
|
471
|
+
});
|
|
472
|
+
for (const [key, sub] of Object.entries(plan.with)) {
|
|
473
|
+
const value = row[key];
|
|
474
|
+
if (Array.isArray(value)) collect(value, sub, cells, hidden);
|
|
475
|
+
else if (isPlainObject(value)) collect([value], sub, cells, hidden);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
async function project(rows, plan, kinds) {
|
|
480
|
+
const cells = [];
|
|
481
|
+
const hidden = [];
|
|
482
|
+
collect(rows, plan, cells, hidden);
|
|
483
|
+
if (cells.length > 0) await projectCells(cells, kinds);
|
|
484
|
+
for (const { row, key } of hidden) row[key] = null;
|
|
485
|
+
return rows;
|
|
486
|
+
}
|
|
487
|
+
function builderFor(bound, key) {
|
|
488
|
+
return bound.$executor.query[key];
|
|
489
|
+
}
|
|
490
|
+
function assertConfig(config) {
|
|
491
|
+
if (config === void 0) return {};
|
|
492
|
+
if (!isPlainObject(config)) throw new BadRequestError("The query config must be an object");
|
|
493
|
+
return config;
|
|
494
|
+
}
|
|
495
|
+
async function runMany(entity, bound, operation, config, injected) {
|
|
496
|
+
const { composer, kinds } = parseOptions(entity, bound, operation, config);
|
|
497
|
+
const compiled = await composer.compileRoot(entity, config, injected);
|
|
498
|
+
return project(await builderFor(bound, compiled.source.key).findMany(compiled.config), compiled.plan, kinds);
|
|
499
|
+
}
|
|
500
|
+
async function find(entity, bound, config) {
|
|
501
|
+
await bound.runtime.ready();
|
|
502
|
+
return guard(bound.runtime, () => runMany(entity, bound, "find", assertConfig(config)));
|
|
503
|
+
}
|
|
504
|
+
async function findOne(entity, bound, config, operation = "findOne", injected) {
|
|
505
|
+
await bound.runtime.ready();
|
|
506
|
+
return guard(bound.runtime, async () => {
|
|
507
|
+
const cfg = assertConfig(config);
|
|
508
|
+
if (cfg.limit !== void 0) throw new BadRequestError(`'${operation}' does not accept a limit`);
|
|
509
|
+
const { composer, kinds } = parseOptions(entity, bound, operation, cfg);
|
|
510
|
+
const compiled = await composer.compileRoot(entity, cfg, injected);
|
|
511
|
+
const row = await builderFor(bound, compiled.source.key).findFirst(compiled.config);
|
|
512
|
+
if (row === void 0) return null;
|
|
513
|
+
return (await project([row], compiled.plan, kinds))[0] ?? null;
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
async function findById(entity, bound, id, config) {
|
|
517
|
+
if (id === void 0 || id === null) throw new BadRequestError("findById needs an id");
|
|
518
|
+
const cfg = assertConfig(config);
|
|
519
|
+
if (cfg.where !== void 0 || cfg.offset !== void 0) throw new BadRequestError("findById does not accept where or offset");
|
|
520
|
+
const row = await findOne(entity, bound, cfg, "findById", { [entity.pk.key]: { eq: id } });
|
|
521
|
+
if (row === null) throw new NotFoundError(`${entity.name} '${String(id)}' not found`, { entity: entity.name });
|
|
522
|
+
return row;
|
|
523
|
+
}
|
|
524
|
+
async function runCount(entity, bound, operation, config) {
|
|
525
|
+
const { composer } = parseOptions(entity, bound, operation, config);
|
|
526
|
+
const { source, where } = await composer.compileWhere(entity, config.where, void 0);
|
|
527
|
+
const sqlWhere = filterToSQL(bound.runtime.relations, source.key, source.table, where);
|
|
528
|
+
return Number(await bound.$executor.$count(source.table, sqlWhere));
|
|
529
|
+
}
|
|
530
|
+
async function count(entity, bound, config) {
|
|
531
|
+
await bound.runtime.ready();
|
|
532
|
+
return guard(bound.runtime, async () => {
|
|
533
|
+
const cfg = assertConfig(config);
|
|
534
|
+
const known = pluginOptions(bound.runtime.plugins).read;
|
|
535
|
+
for (const key of Object.keys(cfg)) if (key !== "where" && !known.has(key)) throw new BadRequestError(`Unknown count option '${key}'`);
|
|
536
|
+
return runCount(entity, bound, "count", cfg);
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
async function page(entity, bound, config) {
|
|
540
|
+
await bound.runtime.ready();
|
|
541
|
+
return guard(bound.runtime, async () => {
|
|
542
|
+
const cfg = assertConfig(config);
|
|
543
|
+
const limit = cfg.limit ?? entity.maxLimit;
|
|
544
|
+
const offset = cfg.offset ?? 0;
|
|
545
|
+
if (typeof offset !== "number" || !Number.isInteger(offset) || offset < 0) throw new BadRequestError("offset must be a non-negative integer");
|
|
546
|
+
const items = await runMany(entity, bound, "page", {
|
|
547
|
+
...cfg,
|
|
548
|
+
limit,
|
|
549
|
+
offset
|
|
550
|
+
});
|
|
551
|
+
const counted = { where: cfg.where };
|
|
552
|
+
for (const key of pluginOptions(bound.runtime.plugins).read.keys()) counted[key] = cfg[key];
|
|
553
|
+
const total = await runCount(entity, bound, "page", counted);
|
|
554
|
+
return {
|
|
555
|
+
items,
|
|
556
|
+
total,
|
|
557
|
+
limit,
|
|
558
|
+
offset,
|
|
559
|
+
hasMore: offset + items.length < total
|
|
560
|
+
};
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
//#endregion
|
|
564
|
+
//#region src/runtime/write/bulk.ts
|
|
565
|
+
const DEFAULT_MAX = 1e3;
|
|
566
|
+
function maxOf(options) {
|
|
567
|
+
const max = options.max ?? DEFAULT_MAX;
|
|
568
|
+
if (typeof max !== "number" || !Number.isInteger(max) || max < 1) throw new BadRequestError("max must be a positive integer");
|
|
569
|
+
return max;
|
|
570
|
+
}
|
|
571
|
+
async function lockMatches(scope, where, rule, values, max) {
|
|
572
|
+
const { entity, bound, meta } = scope;
|
|
573
|
+
if (!isPlainObject(where)) throw new BadRequestError("where must be an object");
|
|
574
|
+
const composer = new Composer({
|
|
575
|
+
runtime: bound.runtime,
|
|
576
|
+
bound,
|
|
577
|
+
meta,
|
|
578
|
+
kinds: readStates(bound.runtime.plugins, bound.ctx, {}),
|
|
579
|
+
traitOptions: values,
|
|
580
|
+
source: null
|
|
581
|
+
});
|
|
582
|
+
const filter = andFilters([
|
|
583
|
+
pluginFilter(entity, bound, meta.operation, values, WRITING),
|
|
584
|
+
rule,
|
|
585
|
+
await composer.rewriteWhere(where, entity.name, entity)
|
|
586
|
+
]);
|
|
587
|
+
const sqlWhere = filterToSQL(bound.runtime.relations, entity.name, entity.table, filter);
|
|
588
|
+
const pk = entity.table[entity.pk.key];
|
|
589
|
+
const rows = await scope.tx.select().from(entity.table).where(sqlWhere).orderBy(asc(pk)).limit(max + 1).for("update");
|
|
590
|
+
if (rows.length > max) throw new BadRequestError(`${meta.operation} matches more than ${max} ${entity.name} rows; narrow the filter or raise max`, { entity: entity.name });
|
|
591
|
+
return rows;
|
|
592
|
+
}
|
|
593
|
+
async function updateMany(entity, bound, where, input, rawOptions) {
|
|
594
|
+
const data = assertData(input);
|
|
595
|
+
assertScalars(entity, data);
|
|
596
|
+
const options = assertOptions(rawOptions, [
|
|
597
|
+
"max",
|
|
598
|
+
...writeOptionKeys(bound),
|
|
599
|
+
...referenceOptionKeys(entity, bound)
|
|
600
|
+
]);
|
|
601
|
+
const max = maxOf(options);
|
|
602
|
+
const states = writeStates(entity, bound.runtime.plugins, bound.ctx, options);
|
|
603
|
+
return runOperation(bound, entity, "updateMany", async (scope) => {
|
|
604
|
+
await beforeOperation(scope, {
|
|
605
|
+
where,
|
|
606
|
+
data,
|
|
607
|
+
options
|
|
608
|
+
});
|
|
609
|
+
const rule = await writeRule(scope, "update", { data });
|
|
610
|
+
const { allowed } = partitionAllowance(scope, data);
|
|
611
|
+
fieldWriteCheck(entity, scope.bound, scope.meta, data, allowed);
|
|
612
|
+
const rows = await lockMatches(scope, where, rule, NO_TRAIT_OPTIONS, max);
|
|
613
|
+
for (const original of rows) {
|
|
614
|
+
const { doc } = await updateDocument(scope, original, data, {
|
|
615
|
+
states,
|
|
616
|
+
op: "update",
|
|
617
|
+
options: NO_TRAIT_OPTIONS,
|
|
618
|
+
withDeleted: options.withDeleted === true
|
|
619
|
+
});
|
|
620
|
+
queueAfterCommit(scope, doc, original);
|
|
621
|
+
}
|
|
622
|
+
const result = { count: rows.length };
|
|
623
|
+
await afterOperation(scope, result);
|
|
624
|
+
return result;
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
async function deleteMany(entity, bound, where, rawOptions) {
|
|
628
|
+
const options = assertOptions(rawOptions, ["max", ...deleteOptionKeys(bound)]);
|
|
629
|
+
const max = maxOf(options);
|
|
630
|
+
const values = traitOptions(entity, bound.runtime.plugins, "delete", options);
|
|
631
|
+
return runOperation(bound, entity, "deleteMany", async (scope) => {
|
|
632
|
+
await beforeOperation(scope, {
|
|
633
|
+
where,
|
|
634
|
+
options
|
|
635
|
+
});
|
|
636
|
+
const rows = await lockMatches(scope, where, await writeRule(scope, "delete", { options: mergedOptions(values) }), values, max);
|
|
637
|
+
for (const original of rows) {
|
|
638
|
+
const doc = await deleteDocument(scope, original, values);
|
|
639
|
+
queueAfterCommit(scope, doc, original);
|
|
640
|
+
}
|
|
641
|
+
const result = { count: rows.length };
|
|
642
|
+
await afterOperation(scope, result);
|
|
643
|
+
return result;
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
//#endregion
|
|
647
|
+
//#region src/runtime/write/links.ts
|
|
648
|
+
function keyOf(table, column) {
|
|
649
|
+
const found = Object.entries(getTableColumns(table)).find(([, c]) => c === column);
|
|
650
|
+
if (found === void 0) throw new InternalError("A many-to-many relation names a column it has not");
|
|
651
|
+
return found[0];
|
|
652
|
+
}
|
|
653
|
+
function joinOf(bound, entity, relation) {
|
|
654
|
+
if (typeof relation !== "string") throw new BadRequestError("relation must be the name of a relation", { entity: entity.name });
|
|
655
|
+
if (!entity.links.has(relation)) throw new UnsupportedError(`Entity '${entity.name}' does not write the links of '${relation}' (r.many(…, { through, writable: true }))`, { entity: entity.name });
|
|
656
|
+
const rel = bound.runtime.relations[entity.name]?.relations[relation];
|
|
657
|
+
const table = rel.throughTable;
|
|
658
|
+
const [joinOwnerKey, joinTargetKey] = [rel.through?.source[0]?._.key, rel.through?.target[0]?._.key];
|
|
659
|
+
if (table === void 0 || joinOwnerKey === void 0 || joinTargetKey === void 0) throw new InternalError(`Relation '${relation}' of '${entity.name}' goes through no table`);
|
|
660
|
+
const target = bound.runtime.entity(rel.targetTableName);
|
|
661
|
+
const columns = getTableColumns(table);
|
|
662
|
+
return {
|
|
663
|
+
table,
|
|
664
|
+
ownerKey: keyOf(entity.table, rel.sourceColumns[0]),
|
|
665
|
+
target,
|
|
666
|
+
targetKey: keyOf(target.table, rel.targetColumns[0]),
|
|
667
|
+
joinOwner: columns[joinOwnerKey],
|
|
668
|
+
joinTarget: columns[joinTargetKey],
|
|
669
|
+
joinOwnerKey,
|
|
670
|
+
joinTargetKey
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
async function writeLinks(entity, bound, op, id, relation, ids) {
|
|
674
|
+
assertId(entity, id);
|
|
675
|
+
const join = joinOf(bound, entity, relation);
|
|
676
|
+
const list = Array.isArray(ids) ? ids : [ids];
|
|
677
|
+
if (list.some((value) => value === void 0 || value === null || typeof value === "object")) throw new BadRequestError(`${op} takes the ids of the rows to ${op}`, { entity: entity.name });
|
|
678
|
+
const targets = [...new Set(list)];
|
|
679
|
+
return runOperation(bound, entity, op, async (scope) => {
|
|
680
|
+
const rule = await writeRule(scope, "update", { data: {} });
|
|
681
|
+
const { partition } = partitionAllowance(scope, {});
|
|
682
|
+
const owner = await lockRow(scope, id, [
|
|
683
|
+
partitionFilter(partition),
|
|
684
|
+
traitFilter(entity, scope.bound, scope.meta.operation, NO_TRAIT_OPTIONS, WRITING),
|
|
685
|
+
rule
|
|
686
|
+
]);
|
|
687
|
+
if (targets.length === 0) return { count: 0 };
|
|
688
|
+
if ((await find(join.target, scope.bound, {
|
|
689
|
+
columns: { [join.targetKey]: true },
|
|
690
|
+
where: { [join.targetKey]: { in: targets } }
|
|
691
|
+
})).length !== targets.length) throw new NotFoundError(`Not every '${join.target.name}' row to ${op} was found`, { entity: join.target.name });
|
|
692
|
+
const ownerValue = owner[join.ownerKey];
|
|
693
|
+
const mine = and(eq(join.joinOwner, ownerValue), inArray(join.joinTarget, targets));
|
|
694
|
+
if (op === "unlink") {
|
|
695
|
+
const removed = await scope.tx.delete(join.table).where(mine).returning();
|
|
696
|
+
const gone = removed.map((row) => row[join.joinTargetKey]);
|
|
697
|
+
if (gone.length > 0) await afterLink(scope, {
|
|
698
|
+
op,
|
|
699
|
+
doc: owner,
|
|
700
|
+
relation,
|
|
701
|
+
ids: gone
|
|
702
|
+
});
|
|
703
|
+
return { count: removed.length };
|
|
704
|
+
}
|
|
705
|
+
const present = new Set((await scope.tx.select({ target: join.joinTarget }).from(join.table).where(mine)).map((row) => row.target));
|
|
706
|
+
const missing = targets.filter((target) => !present.has(target));
|
|
707
|
+
if (missing.length > 0) {
|
|
708
|
+
const rows = missing.map((target) => ({
|
|
709
|
+
[join.joinOwnerKey]: ownerValue,
|
|
710
|
+
[join.joinTargetKey]: target
|
|
711
|
+
}));
|
|
712
|
+
await scope.tx.insert(join.table).values(rows);
|
|
713
|
+
await afterLink(scope, {
|
|
714
|
+
op,
|
|
715
|
+
doc: owner,
|
|
716
|
+
relation,
|
|
717
|
+
ids: missing
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
return { count: missing.length };
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
//#endregion
|
|
724
|
+
//#region src/runtime/execute.ts
|
|
725
|
+
const EXTEND_KEYS = /* @__PURE__ */ new Set([
|
|
726
|
+
"where",
|
|
727
|
+
"orderBy",
|
|
728
|
+
"limit",
|
|
729
|
+
"offset"
|
|
730
|
+
]);
|
|
731
|
+
function inputOf(descriptor) {
|
|
732
|
+
const input = descriptor.input ?? {};
|
|
733
|
+
if (!isPlainObject(input)) throw new BadRequestError("descriptor.input must be an object");
|
|
734
|
+
return input;
|
|
735
|
+
}
|
|
736
|
+
function extendConfig(config, extend) {
|
|
737
|
+
if (extend === void 0) return config;
|
|
738
|
+
if (!isPlainObject(extend)) throw new BadRequestError("extend must be an object");
|
|
739
|
+
for (const key of Object.keys(extend)) if (!EXTEND_KEYS.has(key)) throw new BadRequestError(`extend.${key} is not supported`);
|
|
740
|
+
const out = { ...config };
|
|
741
|
+
const where = andFilters([config.where, extend.where]);
|
|
742
|
+
if (where !== void 0) out.where = where;
|
|
743
|
+
for (const key of [
|
|
744
|
+
"orderBy",
|
|
745
|
+
"limit",
|
|
746
|
+
"offset"
|
|
747
|
+
]) if (extend[key] !== void 0) out[key] = extend[key];
|
|
748
|
+
return out;
|
|
749
|
+
}
|
|
750
|
+
function entityOf(bound, descriptor) {
|
|
751
|
+
if (!isPlainObject(descriptor) || typeof descriptor.entity !== "string") throw new BadRequestError("A descriptor needs an entity name");
|
|
752
|
+
if (typeof descriptor.op !== "string") throw new BadRequestError("A descriptor needs an op");
|
|
753
|
+
return bound.runtime.entity(descriptor.entity);
|
|
754
|
+
}
|
|
755
|
+
async function execute(bound, descriptor, extend) {
|
|
756
|
+
const entity = entityOf(bound, descriptor);
|
|
757
|
+
const input = inputOf(descriptor);
|
|
758
|
+
if (extend !== void 0 && ![
|
|
759
|
+
"find",
|
|
760
|
+
"findOne",
|
|
761
|
+
"count",
|
|
762
|
+
"page"
|
|
763
|
+
].includes(descriptor.op)) throw new BadRequestError(`extend is only supported on read descriptors, not '${descriptor.op}'`);
|
|
764
|
+
switch (descriptor.op) {
|
|
765
|
+
case "find": return find(entity, bound, extendConfig(input, extend));
|
|
766
|
+
case "findOne": return findOne(entity, bound, extendConfig(input, extend));
|
|
767
|
+
case "findById": return findById(entity, bound, input.id, input.config);
|
|
768
|
+
case "count": return count(entity, bound, extendConfig(input, extend));
|
|
769
|
+
case "page": return page(entity, bound, extendConfig(input, extend));
|
|
770
|
+
case "create": return create(entity, bound, input.data, input.options);
|
|
771
|
+
case "update": return update(entity, bound, input.id, input.data, input.options);
|
|
772
|
+
case "updateMany": return updateMany(entity, bound, input.where, input.data, input.options);
|
|
773
|
+
case "delete": return remove(entity, bound, input.id, input.options);
|
|
774
|
+
case "deleteMany": return deleteMany(entity, bound, input.where, input.options);
|
|
775
|
+
case "link":
|
|
776
|
+
case "unlink": return writeLinks(entity, bound, descriptor.op, input.id, input.relation, input.ids);
|
|
777
|
+
default:
|
|
778
|
+
if (!traitOperations(bound.runtime.plugins).has(descriptor.op)) throw new BadRequestError(`Unknown descriptor op '${String(descriptor.op)}'`);
|
|
779
|
+
return runEntityOperation(entity, bound, descriptor.op, input);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
//#endregion
|
|
783
|
+
//#region src/runtime/engine.ts
|
|
784
|
+
const CALLER_LOOKUP = Symbol("typion.callerLookup");
|
|
785
|
+
const OWN_BIND_OPTIONS = /* @__PURE__ */ new Set([
|
|
786
|
+
"request",
|
|
787
|
+
"meta",
|
|
788
|
+
"reason",
|
|
789
|
+
"ctx"
|
|
790
|
+
]);
|
|
791
|
+
function pluginBindOptions(opts) {
|
|
792
|
+
const out = {};
|
|
793
|
+
for (const [key, value] of Object.entries(opts)) if (!OWN_BIND_OPTIONS.has(key) && value !== void 0) out[key] = value;
|
|
794
|
+
return out;
|
|
795
|
+
}
|
|
796
|
+
function defaultAfterCommitError(error, info) {
|
|
797
|
+
console.error(`[typion] afterCommit failed (${info.entity}.${info.operation}, request ${info.requestId})`, error);
|
|
798
|
+
}
|
|
799
|
+
var EngineRuntime = class {
|
|
800
|
+
def;
|
|
801
|
+
db;
|
|
802
|
+
options;
|
|
803
|
+
relations;
|
|
804
|
+
dialect = createPgDialect();
|
|
805
|
+
validation;
|
|
806
|
+
plugins;
|
|
807
|
+
schemas = /* @__PURE__ */ new Map();
|
|
808
|
+
boot = null;
|
|
809
|
+
initializing = false;
|
|
810
|
+
engine;
|
|
811
|
+
constructor(def, db, options) {
|
|
812
|
+
this.def = def;
|
|
813
|
+
this.db = db;
|
|
814
|
+
this.options = options;
|
|
815
|
+
this.relations = def.relations;
|
|
816
|
+
this.validation = def.validation ?? structuralValidation();
|
|
817
|
+
this.plugins = def.plugins;
|
|
818
|
+
}
|
|
819
|
+
now() {
|
|
820
|
+
return this.options.now?.() ?? /* @__PURE__ */ new Date();
|
|
821
|
+
}
|
|
822
|
+
entity(name) {
|
|
823
|
+
const entity = this.entityOrNull(name);
|
|
824
|
+
if (entity === null) throw new BadRequestError(`Unknown entity '${name}'`);
|
|
825
|
+
return entity;
|
|
826
|
+
}
|
|
827
|
+
entityOrNull(name) {
|
|
828
|
+
return Object.hasOwn(this.def.entities, name) ? this.def.entities[name] ?? null : null;
|
|
829
|
+
}
|
|
830
|
+
schema(entity, mode) {
|
|
831
|
+
const key = `${entity.name}:${mode}`;
|
|
832
|
+
let schema = this.schemas.get(key);
|
|
833
|
+
if (schema === void 0) {
|
|
834
|
+
schema = this.validation.base(entity, mode, entity.validate?.fields);
|
|
835
|
+
this.schemas.set(key, schema);
|
|
836
|
+
}
|
|
837
|
+
return schema;
|
|
838
|
+
}
|
|
839
|
+
afterCommitError(error, info) {
|
|
840
|
+
(this.options.onAfterCommitError ?? defaultAfterCommitError)(error, info);
|
|
841
|
+
}
|
|
842
|
+
ready() {
|
|
843
|
+
if (this.initializing) return Promise.resolve();
|
|
844
|
+
if (this.boot === null) this.boot = this.runBoot().catch((error) => {
|
|
845
|
+
this.boot = null;
|
|
846
|
+
throw error;
|
|
847
|
+
});
|
|
848
|
+
return this.boot;
|
|
849
|
+
}
|
|
850
|
+
async runBoot() {
|
|
851
|
+
this.options.checkAdapter?.();
|
|
852
|
+
if (this.options.checkSchema !== false) await this.checkSchema();
|
|
853
|
+
this.initializing = true;
|
|
854
|
+
try {
|
|
855
|
+
for (const plugin of this.plugins) await plugin.spec.init?.(this.engine);
|
|
856
|
+
} finally {
|
|
857
|
+
this.initializing = false;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
async checkSchema() {
|
|
861
|
+
const tables = Object.values(this.def.schema).filter((v) => is(v, PgTable));
|
|
862
|
+
let drift;
|
|
863
|
+
try {
|
|
864
|
+
drift = await this.dialect.introspect(this.db, tables);
|
|
865
|
+
} catch (error) {
|
|
866
|
+
throw normalizeError(this, error);
|
|
867
|
+
}
|
|
868
|
+
if (drift.missingTables.length === 0 && drift.missingColumns.length === 0) return;
|
|
869
|
+
const lines = [
|
|
870
|
+
"The database schema does not match the engine definition.",
|
|
871
|
+
...drift.missingTables.length > 0 ? [` missing tables: ${drift.missingTables.join(", ")}`] : [],
|
|
872
|
+
...drift.missingColumns.map((m) => ` missing columns on ${m.table}: ${m.columns.join(", ")}`),
|
|
873
|
+
"Run `typion db generate` and `typion db migrate` (or `typion db push` in development)."
|
|
874
|
+
];
|
|
875
|
+
throw new ConfigError(lines.join("\n"), "schema_drift", { details: { ...drift } });
|
|
876
|
+
}
|
|
877
|
+
};
|
|
878
|
+
function entityApi(bound, entity) {
|
|
879
|
+
const name = entity.name;
|
|
880
|
+
const schemas = {};
|
|
881
|
+
for (const mode of [
|
|
882
|
+
"insert",
|
|
883
|
+
"update",
|
|
884
|
+
"select"
|
|
885
|
+
]) Object.defineProperty(schemas, mode, {
|
|
886
|
+
enumerable: true,
|
|
887
|
+
get: () => bound.runtime.schema(entity, mode)
|
|
888
|
+
});
|
|
889
|
+
return {
|
|
890
|
+
find: (config) => find(entity, bound, config),
|
|
891
|
+
findOne: (config) => findOne(entity, bound, config),
|
|
892
|
+
findById: (id, config) => findById(entity, bound, id, config),
|
|
893
|
+
count: (config) => count(entity, bound, config),
|
|
894
|
+
page: (config) => page(entity, bound, config),
|
|
895
|
+
query: (config) => descriptor(name, "find", config ?? {}),
|
|
896
|
+
queryOne: (config) => descriptor(name, "findOne", config ?? {}),
|
|
897
|
+
run: async (d, extend) => {
|
|
898
|
+
if (d?.entity !== name) throw new BadRequestError(`A '${String(d?.entity)}' descriptor cannot run on '${name}'`);
|
|
899
|
+
return execute(bound, d, extend);
|
|
900
|
+
},
|
|
901
|
+
create: (data, options) => create(entity, bound, data, options),
|
|
902
|
+
update: (id, data, options) => update(entity, bound, id, data, options),
|
|
903
|
+
updateMany: (where, data, options) => updateMany(entity, bound, where, data, options),
|
|
904
|
+
delete: (id, options) => remove(entity, bound, id, options),
|
|
905
|
+
deleteMany: (where, options) => deleteMany(entity, bound, where, options),
|
|
906
|
+
...entity.links.size === 0 ? {} : {
|
|
907
|
+
link: (id, relation, ids) => writeLinks(entity, bound, "link", id, relation, ids),
|
|
908
|
+
unlink: (id, relation, ids) => writeLinks(entity, bound, "unlink", id, relation, ids)
|
|
909
|
+
},
|
|
910
|
+
schemas,
|
|
911
|
+
...operationMethods(entity, bound.runtime.plugins, async (op, args) => runEntityOperation(entity, bound, op.name, operationInput(op, args)))
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
const EMPTY_QUEUE = new CommitQueue(() => {});
|
|
915
|
+
var BoundImpl = class BoundImpl {
|
|
916
|
+
$engine;
|
|
917
|
+
ctx;
|
|
918
|
+
$tx;
|
|
919
|
+
queue;
|
|
920
|
+
apis = /* @__PURE__ */ new Map();
|
|
921
|
+
plugins;
|
|
922
|
+
constructor($engine, ctx, $tx, queue) {
|
|
923
|
+
this.$engine = $engine;
|
|
924
|
+
this.ctx = ctx;
|
|
925
|
+
this.$tx = $tx;
|
|
926
|
+
this.queue = queue;
|
|
927
|
+
for (const name of Object.keys($engine.runtime.def.entities)) Object.defineProperty(this, name, {
|
|
928
|
+
enumerable: true,
|
|
929
|
+
get: () => this.entity(name)
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
get runtime() {
|
|
933
|
+
return this.$engine.runtime;
|
|
934
|
+
}
|
|
935
|
+
get $ctx() {
|
|
936
|
+
return this.ctx;
|
|
937
|
+
}
|
|
938
|
+
get $mode() {
|
|
939
|
+
return this.ctx.mode;
|
|
940
|
+
}
|
|
941
|
+
get $executor() {
|
|
942
|
+
return this.$tx ?? this.runtime.db;
|
|
943
|
+
}
|
|
944
|
+
get $afterCommit() {
|
|
945
|
+
return this.queue ?? EMPTY_QUEUE;
|
|
946
|
+
}
|
|
947
|
+
get $plugins() {
|
|
948
|
+
this.plugins ??= this.$engine.pluginApis(this);
|
|
949
|
+
return this.plugins;
|
|
950
|
+
}
|
|
951
|
+
entity(name) {
|
|
952
|
+
let api = this.apis.get(name);
|
|
953
|
+
if (api === void 0) {
|
|
954
|
+
api = entityApi(this, this.runtime.entity(name));
|
|
955
|
+
this.apis.set(name, api);
|
|
956
|
+
}
|
|
957
|
+
return api;
|
|
958
|
+
}
|
|
959
|
+
joined(tx, queue) {
|
|
960
|
+
return new BoundImpl(this.$engine, this.ctx, tx, queue);
|
|
961
|
+
}
|
|
962
|
+
systemTwin(reason) {
|
|
963
|
+
if (this.ctx.mode === "system") return this;
|
|
964
|
+
const ctx = {
|
|
965
|
+
...this.ctx,
|
|
966
|
+
mode: "system",
|
|
967
|
+
systemReason: reason
|
|
968
|
+
};
|
|
969
|
+
return new BoundImpl(this.$engine, ctx, this.$tx, this.queue);
|
|
970
|
+
}
|
|
971
|
+
$withTx(tx) {
|
|
972
|
+
return this.joined(tx, this.queue ?? this.$engine.newQueue(this.ctx));
|
|
973
|
+
}
|
|
974
|
+
$run(d, extend) {
|
|
975
|
+
return execute(this, d, extend);
|
|
976
|
+
}
|
|
977
|
+
async $transaction(fn, config) {
|
|
978
|
+
const runtime = this.runtime;
|
|
979
|
+
await runtime.ready();
|
|
980
|
+
try {
|
|
981
|
+
if (this.$tx !== null && this.queue !== null) {
|
|
982
|
+
const queue = this.queue;
|
|
983
|
+
const mark = queue.mark();
|
|
984
|
+
try {
|
|
985
|
+
return await this.$tx.transaction((tx) => fn(this.joined(tx, queue)));
|
|
986
|
+
} catch (error) {
|
|
987
|
+
queue.truncate(mark);
|
|
988
|
+
throw error;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
const queue = this.$engine.newQueue(this.ctx);
|
|
992
|
+
let result;
|
|
993
|
+
try {
|
|
994
|
+
result = await runtime.db.transaction((tx) => fn(this.joined(tx, queue)), config);
|
|
995
|
+
} catch (error) {
|
|
996
|
+
queue.discard();
|
|
997
|
+
throw error;
|
|
998
|
+
}
|
|
999
|
+
await queue.flush();
|
|
1000
|
+
return result;
|
|
1001
|
+
} catch (error) {
|
|
1002
|
+
throw normalizeError(runtime, error);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
1006
|
+
var EngineImpl = class {
|
|
1007
|
+
options;
|
|
1008
|
+
runtime;
|
|
1009
|
+
$plugins = {};
|
|
1010
|
+
constructor(def, options) {
|
|
1011
|
+
this.options = options;
|
|
1012
|
+
const db = options.db;
|
|
1013
|
+
if (db?._?.relations !== def.relations) throw new ConfigError("createEngine: the db was built with other relations; create it with drizzle({ client, relations: def.relations })", "relations_mismatch");
|
|
1014
|
+
this.runtime = new EngineRuntime(def, db, options);
|
|
1015
|
+
this.runtime.engine = this;
|
|
1016
|
+
for (const name of Object.keys(def.entities)) Object.defineProperty(this, name, {
|
|
1017
|
+
enumerable: true,
|
|
1018
|
+
value: {
|
|
1019
|
+
query: (config) => descriptor(name, "find", config ?? {}),
|
|
1020
|
+
queryOne: (config) => descriptor(name, "findOne", config ?? {})
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
Object.assign(this.$plugins, this.pluginApis());
|
|
1024
|
+
}
|
|
1025
|
+
pluginApis(app) {
|
|
1026
|
+
const out = {};
|
|
1027
|
+
for (const plugin of this.runtime.def.plugins) {
|
|
1028
|
+
const errors = {};
|
|
1029
|
+
for (const [code, spec] of Object.entries(plugin.errors)) errors[code] = (input = {}) => new PluginError(input.message ?? spec.message, plugin.id, code, {
|
|
1030
|
+
status: spec.status,
|
|
1031
|
+
details: input.details,
|
|
1032
|
+
cause: input.cause
|
|
1033
|
+
});
|
|
1034
|
+
out[plugin.id] = {
|
|
1035
|
+
...plugin.spec.api?.(this, app),
|
|
1036
|
+
errors
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
return out;
|
|
1040
|
+
}
|
|
1041
|
+
get $def() {
|
|
1042
|
+
return this.runtime.def;
|
|
1043
|
+
}
|
|
1044
|
+
get $db() {
|
|
1045
|
+
return this.runtime.db;
|
|
1046
|
+
}
|
|
1047
|
+
get $dialect() {
|
|
1048
|
+
return this.runtime.dialect;
|
|
1049
|
+
}
|
|
1050
|
+
newQueue(ctx) {
|
|
1051
|
+
return new CommitQueue((error) => this.runtime.afterCommitError(error, {
|
|
1052
|
+
entity: "*",
|
|
1053
|
+
operation: "transaction",
|
|
1054
|
+
requestId: ctx.requestId
|
|
1055
|
+
}));
|
|
1056
|
+
}
|
|
1057
|
+
pluginContext(base, input, allowAsync) {
|
|
1058
|
+
const parts = [];
|
|
1059
|
+
for (const plugin of this.runtime.plugins) {
|
|
1060
|
+
const build = plugin.spec.context;
|
|
1061
|
+
if (build === void 0) continue;
|
|
1062
|
+
const value = build(base, input);
|
|
1063
|
+
if (!allowAsync && typeof value?.then === "function") throw new ConfigError(`Plugin '${plugin.id}' builds its context asynchronously: use $fromRequest() instead of $as()`);
|
|
1064
|
+
parts.push(value);
|
|
1065
|
+
}
|
|
1066
|
+
if (!allowAsync) return Object.assign({}, ...parts);
|
|
1067
|
+
return Promise.all(parts).then((values) => Object.assign({}, ...values));
|
|
1068
|
+
}
|
|
1069
|
+
bind(user, mode, opts, systemReason, callerLookup, pluginCtx) {
|
|
1070
|
+
if (typeof user !== "object" || user === null) throw new BadRequestError("The engine context must be an object");
|
|
1071
|
+
const ctx = buildContext({
|
|
1072
|
+
user,
|
|
1073
|
+
mode,
|
|
1074
|
+
request: opts.request ?? null,
|
|
1075
|
+
systemReason,
|
|
1076
|
+
meta: opts.meta,
|
|
1077
|
+
now: this.runtime.now()
|
|
1078
|
+
});
|
|
1079
|
+
const input = {
|
|
1080
|
+
request: opts.request ?? void 0,
|
|
1081
|
+
headers: opts.request?.headers,
|
|
1082
|
+
options: pluginBindOptions(opts),
|
|
1083
|
+
...callerLookup ? { callerLookup } : {}
|
|
1084
|
+
};
|
|
1085
|
+
const full = {
|
|
1086
|
+
...pluginCtx ?? this.pluginContext(ctx, input, false),
|
|
1087
|
+
...ctx
|
|
1088
|
+
};
|
|
1089
|
+
return new BoundImpl(this, full, null, null);
|
|
1090
|
+
}
|
|
1091
|
+
$as(ctx, opts = {}) {
|
|
1092
|
+
return this.bind(ctx, "user", opts, null, false, null);
|
|
1093
|
+
}
|
|
1094
|
+
$system(opts) {
|
|
1095
|
+
if (typeof opts?.reason !== "string" || opts.reason.trim() === "") throw new ConfigError("$system() needs a non-empty reason: it is recorded in versions and audit rows");
|
|
1096
|
+
return this.bind(opts.ctx ?? {}, "system", opts, opts.reason, opts[CALLER_LOOKUP] === true, null);
|
|
1097
|
+
}
|
|
1098
|
+
$fromRequest(request, opts = {}) {
|
|
1099
|
+
return this.fromInput({
|
|
1100
|
+
request,
|
|
1101
|
+
headers: request.headers,
|
|
1102
|
+
options: pluginBindOptions(opts)
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
$fromHeaders(headers, opts = {}) {
|
|
1106
|
+
return this.fromInput({
|
|
1107
|
+
headers,
|
|
1108
|
+
options: pluginBindOptions(opts)
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
async fromInput(input) {
|
|
1112
|
+
const request = input.request ?? null;
|
|
1113
|
+
const user = this.options.context === void 0 ? {} : await this.options.context(input);
|
|
1114
|
+
const probe = buildContext({
|
|
1115
|
+
user,
|
|
1116
|
+
mode: "user",
|
|
1117
|
+
request,
|
|
1118
|
+
systemReason: null,
|
|
1119
|
+
meta: void 0,
|
|
1120
|
+
now: this.runtime.now()
|
|
1121
|
+
});
|
|
1122
|
+
const pluginCtx = await this.pluginContext(probe, input, true);
|
|
1123
|
+
return this.bind(user, "user", { request }, null, false, pluginCtx);
|
|
1124
|
+
}
|
|
1125
|
+
$withTx(ctx, tx) {
|
|
1126
|
+
const bound = this.$as(ctx);
|
|
1127
|
+
return bound.joined(tx, this.newQueue(bound.ctx));
|
|
1128
|
+
}
|
|
1129
|
+
$execute(d, bound) {
|
|
1130
|
+
if (!(bound instanceof BoundImpl)) return Promise.reject(new BadRequestError("$execute needs an engine bound with $as/$system"));
|
|
1131
|
+
return execute(bound, d);
|
|
1132
|
+
}
|
|
1133
|
+
};
|
|
1134
|
+
function createEngine(def, options) {
|
|
1135
|
+
return new EngineImpl(def, options);
|
|
1136
|
+
}
|
|
1137
|
+
//#endregion
|
|
1138
|
+
//#region src/runtime/typion.ts
|
|
1139
|
+
var typion_exports = /* @__PURE__ */ __exportAll({
|
|
1140
|
+
createFromConfig: () => createFromConfig,
|
|
1141
|
+
getTypion: () => getTypion,
|
|
1142
|
+
standIn: () => standIn
|
|
1143
|
+
});
|
|
1144
|
+
const instances = globalThis.__typion ??= /* @__PURE__ */ new Map();
|
|
1145
|
+
function createFromConfig(config, options) {
|
|
1146
|
+
const context = options.context ?? config.context;
|
|
1147
|
+
const engine = createEngine(config.definition, {
|
|
1148
|
+
...options,
|
|
1149
|
+
onAfterCommitError: options.onAfterCommitError ?? config.onAfterCommitError,
|
|
1150
|
+
checkSchema: options.checkSchema ?? config.checkSchema,
|
|
1151
|
+
db: options.db,
|
|
1152
|
+
context: context === void 0 ? void 0 : (input) => context({
|
|
1153
|
+
...input,
|
|
1154
|
+
system: engine.$system({
|
|
1155
|
+
reason: "context",
|
|
1156
|
+
[CALLER_LOOKUP]: true
|
|
1157
|
+
}),
|
|
1158
|
+
db: options.db
|
|
1159
|
+
})
|
|
1160
|
+
});
|
|
1161
|
+
return engine;
|
|
1162
|
+
}
|
|
1163
|
+
function standIn(config, engine) {
|
|
1164
|
+
const previous = instances.get(config);
|
|
1165
|
+
instances.set(config, engine);
|
|
1166
|
+
return () => {
|
|
1167
|
+
if (instances.get(config) !== engine) return;
|
|
1168
|
+
if (previous === void 0) instances.delete(config);
|
|
1169
|
+
else instances.set(config, previous);
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
async function getTypion(config, options = {}) {
|
|
1173
|
+
const cached = options.fresh === true ? void 0 : instances.get(config);
|
|
1174
|
+
if (cached !== void 0) return cached;
|
|
1175
|
+
const adapter = options.db ?? config.db;
|
|
1176
|
+
if (adapter === void 0) throw new ConfigError("getTypion: the config has no database adapter (`db` in defineConfig)");
|
|
1177
|
+
const engine = createFromConfig(config, {
|
|
1178
|
+
db: adapter.connect(config.definition.relations),
|
|
1179
|
+
...adapter.check === void 0 ? {} : { checkAdapter: () => adapter.check?.() }
|
|
1180
|
+
});
|
|
1181
|
+
if (options.fresh !== true) instances.set(config, engine);
|
|
1182
|
+
return engine;
|
|
1183
|
+
}
|
|
1184
|
+
//#endregion
|
|
1185
|
+
export { typion_exports as i, getTypion as n, standIn as r, createFromConfig as t };
|
|
1186
|
+
|
|
1187
|
+
//# sourceMappingURL=typion-Dh2BCSRz.js.map
|