peta-orm 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -23
- package/dist/{collection-PFmrQHyM.mjs → collection-D9YZn2mL.mjs} +4 -9
- package/dist/{crud-BCWvg5MI.mjs → crud-Di2nvpjB.mjs} +6 -23
- package/dist/{errors-sfFJolfu.mjs → errors-i-gCZnlW.mjs} +14 -1
- package/dist/{factory-BBvIMQuc.mjs → factory-_JPR5fVl.mjs} +5 -4
- package/dist/helpers-CcxHFhtz.mjs +40 -0
- package/dist/{hooks-BD0xy7uw.mjs → hooks-D508wLQg.mjs} +2 -16
- package/dist/index.d.mts +315 -202
- package/dist/index.mjs +782 -397
- package/dist/model-helpers-BBpD3qdv.mjs +14 -0
- package/package.json +3 -7
- package/dist/save-D5UKXvqC.mjs +0 -331
package/dist/index.mjs
CHANGED
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
-
import { n as createCollection } from "./collection-
|
|
3
|
-
import { a as
|
|
4
|
-
import { a as registerSoftDeletesFor, n as getSoftDeleteConfig, o as
|
|
5
|
-
import {
|
|
6
|
-
import { a as
|
|
7
|
-
import { a as
|
|
2
|
+
import { n as createCollection } from "./collection-D9YZn2mL.mjs";
|
|
3
|
+
import { a as ModelNotFoundError, c as RelationNotFoundError, i as DatabaseError, l as ValidationError, n as isUniqueConstraintError, o as ModelNotRegisteredError, r as normalizeError, s as RelationNotAllowedError } from "./errors-i-gCZnlW.mjs";
|
|
4
|
+
import { a as registerTimestampsFor, i as registerSoftDeletesFor, n as getSoftDeleteConfig, o as createHookManager, r as hasSoftDelete, t as getHooksFor } from "./hooks-D508wLQg.mjs";
|
|
5
|
+
import { n as getPrimaryKeyColumn, t as getDb } from "./model-helpers-BBpD3qdv.mjs";
|
|
6
|
+
import { a as castValue, i as applyCastsToData, o as prepareForDb, r as initRuntime, t as createInstance } from "./factory-_JPR5fVl.mjs";
|
|
7
|
+
import { a as getRawRelations, d as setExists, f as syncOriginal, i as getExists, o as getState } from "./state-LtlHp6XV.mjs";
|
|
8
|
+
import { a as resolveThunk, i as resolveTargetId, n as getPivotInfo, r as groupByArray, t as findRelated } from "./helpers-CcxHFhtz.mjs";
|
|
8
9
|
import { type } from "arktype";
|
|
9
10
|
import { Kysely, sql } from "kysely";
|
|
11
|
+
import { pathToFileURL } from "node:url";
|
|
10
12
|
import { ulid as ulid$1 } from "ulid";
|
|
11
13
|
//#region src/columns/arktype.ts
|
|
12
14
|
function createArkTypeSchemaConfig() {
|
|
13
15
|
function compile(dataType, args, constraints) {
|
|
14
16
|
return type(buildDef(dataType, args, constraints));
|
|
15
17
|
}
|
|
18
|
+
function formatProblems(result) {
|
|
19
|
+
const raw = result.flatProblemsByPath;
|
|
20
|
+
if (!raw) return "Validation failed";
|
|
21
|
+
return Object.entries(raw).map(([path, msgs]) => `${path}: ${msgs.join(", ")}`).join("; ");
|
|
22
|
+
}
|
|
16
23
|
function parse(schema, value) {
|
|
17
24
|
const result = schema(value);
|
|
18
|
-
if (result instanceof type.errors) throw new ValidationError(
|
|
25
|
+
if (result instanceof type.errors) throw new ValidationError(formatProblems(result));
|
|
19
26
|
return result;
|
|
20
27
|
}
|
|
21
28
|
function assert(schema, value) {
|
|
@@ -23,7 +30,7 @@ function createArkTypeSchemaConfig() {
|
|
|
23
30
|
try {
|
|
24
31
|
return t.assert(value);
|
|
25
32
|
} catch (e) {
|
|
26
|
-
if (isArkError(e)) throw new ValidationError(
|
|
33
|
+
if (isArkError(e)) throw new ValidationError(formatProblems(e.arkErrors));
|
|
27
34
|
throw e;
|
|
28
35
|
}
|
|
29
36
|
}
|
|
@@ -97,12 +104,6 @@ function subTypeModifier(dataType, hasEmail, hasUrl) {
|
|
|
97
104
|
function isArkError(e) {
|
|
98
105
|
return typeof e === "object" && e !== null && "arkErrors" in e;
|
|
99
106
|
}
|
|
100
|
-
function extractProblems(result) {
|
|
101
|
-
const problems = /* @__PURE__ */ new Map();
|
|
102
|
-
const raw = result.flatProblemsByPath;
|
|
103
|
-
if (raw) for (const [path, msgs] of Object.entries(raw)) problems.set(path, msgs);
|
|
104
|
-
return problems;
|
|
105
|
-
}
|
|
106
107
|
//#endregion
|
|
107
108
|
//#region src/columns/column.ts
|
|
108
109
|
function createColumn(schema, dataType, args = [], constraints = []) {
|
|
@@ -139,8 +140,7 @@ function createColumn(schema, dataType, args = [], constraints = []) {
|
|
|
139
140
|
get defaultValue() {
|
|
140
141
|
const c = constraints.find((c) => c.type === "default");
|
|
141
142
|
if (!c) return void 0;
|
|
142
|
-
|
|
143
|
-
return typeof val === "function" ? val : val;
|
|
143
|
+
return c.args[0];
|
|
144
144
|
},
|
|
145
145
|
hasConstraint(type) {
|
|
146
146
|
return constraints.some((c) => c.type === type);
|
|
@@ -189,7 +189,27 @@ function createColumn(schema, dataType, args = [], constraints = []) {
|
|
|
189
189
|
}
|
|
190
190
|
//#endregion
|
|
191
191
|
//#region src/columns/types.ts
|
|
192
|
-
|
|
192
|
+
/**
|
|
193
|
+
* Pre-configured column type factory backed by ArkType validation.
|
|
194
|
+
*
|
|
195
|
+
* The most common usage — just import and use:
|
|
196
|
+
* ```ts
|
|
197
|
+
* import { t } from "peta-orm"
|
|
198
|
+
* const id = t.integer().primaryKey()
|
|
199
|
+
* ```
|
|
200
|
+
*
|
|
201
|
+
* For a custom validation backend, use `createColumnTypes({ schema })` instead.
|
|
202
|
+
*/
|
|
203
|
+
const t = createColumnTypes({ schema: createArkTypeSchemaConfig() });
|
|
204
|
+
/**
|
|
205
|
+
* Create a column type factory with a custom validation schema backend.
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```ts
|
|
209
|
+
* const t = createColumnTypes({ schema: myCustomSchemaConfig })
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
function createColumnTypes(config) {
|
|
193
213
|
const schema = config.schema;
|
|
194
214
|
function col(dataType, args) {
|
|
195
215
|
return createColumn(schema, dataType, args);
|
|
@@ -217,6 +237,62 @@ function t(config) {
|
|
|
217
237
|
};
|
|
218
238
|
}
|
|
219
239
|
//#endregion
|
|
240
|
+
//#region src/init.ts
|
|
241
|
+
/**
|
|
242
|
+
* Create a lazy-initialized singleton factory.
|
|
243
|
+
*
|
|
244
|
+
* The factory function is called only once — on the first call to the returned
|
|
245
|
+
* function. Subsequent calls return the same resolved promise. This avoids
|
|
246
|
+
* module-level side effects: importing a model file won't trigger database
|
|
247
|
+
* connection or schema initialization until the first explicit `await db()`.
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* ```ts
|
|
251
|
+
* import { createClient } from "@libsql/client"
|
|
252
|
+
* import { LibsqlDialect } from "@libsql/kysely-libsql"
|
|
253
|
+
* import { createDb, createORM, defineModel, t } from "peta-orm"
|
|
254
|
+
*
|
|
255
|
+
* const User = defineModel("users", { columns: { ... } })
|
|
256
|
+
*
|
|
257
|
+
* async function setup() {
|
|
258
|
+
* const client = createClient({ url: "file:my-app.db" })
|
|
259
|
+
* await client.execute("CREATE TABLE IF NOT EXISTS users (...)") // schema init
|
|
260
|
+
* const orm = createORM({ dialect: new LibsqlDialect({ client }) })
|
|
261
|
+
* orm.registerAll(User)
|
|
262
|
+
* return orm
|
|
263
|
+
* }
|
|
264
|
+
*
|
|
265
|
+
* export const db = createDb(setup)
|
|
266
|
+
* // Usage: const orm = await db()
|
|
267
|
+
* ```
|
|
268
|
+
*/
|
|
269
|
+
function createDb(factory) {
|
|
270
|
+
let promise = null;
|
|
271
|
+
return () => {
|
|
272
|
+
if (!promise) promise = factory();
|
|
273
|
+
return promise;
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
//#endregion
|
|
277
|
+
//#region src/integrations/elysia.ts
|
|
278
|
+
/**
|
|
279
|
+
* Elysia.js plugin that attaches the ORM instance to the app context.
|
|
280
|
+
*/
|
|
281
|
+
function petaPlugin(options) {
|
|
282
|
+
return (app) => app.decorate("peta", options.peta);
|
|
283
|
+
}
|
|
284
|
+
//#endregion
|
|
285
|
+
//#region src/integrations/hono.ts
|
|
286
|
+
/**
|
|
287
|
+
* Hono middleware that sets the ORM instance on the context.
|
|
288
|
+
*/
|
|
289
|
+
function petaMiddleware(options) {
|
|
290
|
+
return async (c, next) => {
|
|
291
|
+
c.set("peta", options.peta);
|
|
292
|
+
await next();
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
//#endregion
|
|
220
296
|
//#region src/hooks/static.ts
|
|
221
297
|
var static_exports = /* @__PURE__ */ __exportAll({
|
|
222
298
|
addStaticHook: () => addStaticHook,
|
|
@@ -248,11 +324,353 @@ function hasStaticHooks(def, event) {
|
|
|
248
324
|
return (staticHooks.get(def)?.get(event)?.length ?? 0) > 0;
|
|
249
325
|
}
|
|
250
326
|
//#endregion
|
|
327
|
+
//#region src/model/save.ts
|
|
328
|
+
var save_exports = /* @__PURE__ */ __exportAll({
|
|
329
|
+
getConfig: () => getConfig,
|
|
330
|
+
insertManyModel: () => insertManyModel,
|
|
331
|
+
insertModel: () => insertModel,
|
|
332
|
+
reloadModel: () => reloadModel,
|
|
333
|
+
saveModel: () => saveModel,
|
|
334
|
+
setConfig: () => setConfig,
|
|
335
|
+
updateModel: () => updateModel
|
|
336
|
+
});
|
|
337
|
+
async function saveModel(def, model) {
|
|
338
|
+
const hm = getHooksFor(def);
|
|
339
|
+
const exists = getExists(model);
|
|
340
|
+
const pk = getPrimaryKeyColumn(def);
|
|
341
|
+
const db = getDb(def);
|
|
342
|
+
const config = getConfig(def);
|
|
343
|
+
if (exists) {
|
|
344
|
+
const dirty = getState(model).attributes;
|
|
345
|
+
const original = getState(model).original;
|
|
346
|
+
const changed = {};
|
|
347
|
+
for (const key of Object.keys(dirty)) if (dirty[key] !== original[key]) changed[key] = config?.casts?.[key] ? prepareForDb(dirty[key], config.casts[key]) : dirty[key];
|
|
348
|
+
if (Object.keys(changed).length === 0) return model;
|
|
349
|
+
await hm.trigger("beforeUpdate", model);
|
|
350
|
+
await hm.trigger("beforeSave", model);
|
|
351
|
+
const pkValue = model.get(pk);
|
|
352
|
+
try {
|
|
353
|
+
await db.updateTable(def.table).set(changed).where(pk, "=", pkValue).execute();
|
|
354
|
+
} catch (e) {
|
|
355
|
+
throw normalizeError(e, def.table);
|
|
356
|
+
}
|
|
357
|
+
syncOriginal(model);
|
|
358
|
+
await hm.trigger("afterUpdate", model);
|
|
359
|
+
await hm.trigger("afterSave", model);
|
|
360
|
+
} else {
|
|
361
|
+
await hm.trigger("beforeCreate", model);
|
|
362
|
+
await hm.trigger("beforeSave", model);
|
|
363
|
+
const data = {};
|
|
364
|
+
const attrs = getState(model).attributes;
|
|
365
|
+
for (const [key, value] of Object.entries(attrs)) if (key !== pk || value !== void 0) data[key] = config?.casts?.[key] ? prepareForDb(value, config.casts[key]) : value;
|
|
366
|
+
try {
|
|
367
|
+
const result = await db.insertInto(def.table).values(data).returningAll().executeTakeFirst();
|
|
368
|
+
if (result) {
|
|
369
|
+
const applied = config?.casts ? applyCastsToData(config, result) : result;
|
|
370
|
+
for (const [key, value] of Object.entries(applied)) getState(model).attributes[key] = value;
|
|
371
|
+
}
|
|
372
|
+
} catch (e) {
|
|
373
|
+
throw normalizeError(e, def.table);
|
|
374
|
+
}
|
|
375
|
+
setExists(model, true);
|
|
376
|
+
syncOriginal(model);
|
|
377
|
+
await hm.trigger("afterCreate", model);
|
|
378
|
+
await hm.trigger("afterSave", model);
|
|
379
|
+
}
|
|
380
|
+
return model;
|
|
381
|
+
}
|
|
382
|
+
async function insertModel(def, data) {
|
|
383
|
+
const config = getConfig(def) ?? { columns: def.columns };
|
|
384
|
+
if (!Object.keys(data).some((key) => key in def.relations)) {
|
|
385
|
+
const model = createInstance(def, config, data, false);
|
|
386
|
+
await saveModel(def, model);
|
|
387
|
+
return model;
|
|
388
|
+
}
|
|
389
|
+
const { extractRelationData, processCreateRelations } = await import("./crud-Di2nvpjB.mjs");
|
|
390
|
+
const { columnData, relationOps } = extractRelationData(def, data);
|
|
391
|
+
for (const [relName, op] of Object.entries(relationOps)) {
|
|
392
|
+
const relation = def.relations[relName];
|
|
393
|
+
if (relation?.type === "belongsTo") {
|
|
394
|
+
const bop = op;
|
|
395
|
+
const relatedDef = relation.relatedModelClass;
|
|
396
|
+
if (bop.create) {
|
|
397
|
+
const related = await relatedDef.insert(bop.create);
|
|
398
|
+
columnData[relation.foreignKey] = related.get(relation.localKey);
|
|
399
|
+
} else if (bop.connect) {
|
|
400
|
+
const cond = bop.connect;
|
|
401
|
+
const condKey = Object.keys(cond)[0];
|
|
402
|
+
const found = await relatedDef.query().where(condKey, "=", cond[condKey]).executeTakeFirst();
|
|
403
|
+
if (found) columnData[relation.foreignKey] = found.get(relation.localKey);
|
|
404
|
+
} else if (bop.connectOrCreate) {
|
|
405
|
+
const { where, create } = bop.connectOrCreate;
|
|
406
|
+
const whereKey = Object.keys(where)[0];
|
|
407
|
+
const found = await relatedDef.query().where(whereKey, "=", where[whereKey]).executeTakeFirst();
|
|
408
|
+
if (found) columnData[relation.foreignKey] = found.get(relation.localKey);
|
|
409
|
+
else {
|
|
410
|
+
const created = await relatedDef.insert(create);
|
|
411
|
+
columnData[relation.foreignKey] = created.get(relation.localKey);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
const model = createInstance(def, config, columnData, false);
|
|
417
|
+
await saveModel(def, model);
|
|
418
|
+
const postOps = {};
|
|
419
|
+
for (const [relName, op] of Object.entries(relationOps)) {
|
|
420
|
+
const relation = def.relations[relName];
|
|
421
|
+
if (relation && relation.type !== "belongsTo") postOps[relName] = op;
|
|
422
|
+
}
|
|
423
|
+
if (Object.keys(postOps).length > 0) await processCreateRelations(def, model, postOps);
|
|
424
|
+
return model;
|
|
425
|
+
}
|
|
426
|
+
async function insertManyModel(def, dataArray) {
|
|
427
|
+
const db = getDb(def);
|
|
428
|
+
const pk = getPrimaryKeyColumn(def);
|
|
429
|
+
const config = getConfig(def);
|
|
430
|
+
const hm = getHooksFor(def);
|
|
431
|
+
const instances = [];
|
|
432
|
+
for (const data of dataArray) {
|
|
433
|
+
const instance = createInstance(def, config ?? { columns: def.columns }, {}, false);
|
|
434
|
+
const fillData = {};
|
|
435
|
+
for (const [key, value] of Object.entries(data)) if (key !== pk) fillData[key] = value;
|
|
436
|
+
instance.fill(fillData);
|
|
437
|
+
await hm.trigger("beforeCreate", instance);
|
|
438
|
+
instances.push(instance);
|
|
439
|
+
}
|
|
440
|
+
const prepared = instances.map((inst) => {
|
|
441
|
+
const attrs = inst.attributes ?? {};
|
|
442
|
+
const row = {};
|
|
443
|
+
for (const [key, value] of Object.entries(attrs)) row[key] = config?.casts?.[key] ? prepareForDb(value, config.casts[key]) : value;
|
|
444
|
+
return row;
|
|
445
|
+
});
|
|
446
|
+
let results;
|
|
447
|
+
try {
|
|
448
|
+
results = await db.insertInto(def.table).values(prepared).returningAll().execute();
|
|
449
|
+
} catch (e) {
|
|
450
|
+
throw normalizeError(e, def.table);
|
|
451
|
+
}
|
|
452
|
+
const models = results.map((row) => {
|
|
453
|
+
const applied = config?.casts ? applyCastsToData(config, row) : row;
|
|
454
|
+
return createInstance(def, config ?? { columns: def.columns }, applied, true);
|
|
455
|
+
});
|
|
456
|
+
for (const model of models) await hm.trigger("afterCreate", model);
|
|
457
|
+
return models;
|
|
458
|
+
}
|
|
459
|
+
async function updateModel(def, id, data) {
|
|
460
|
+
const model = await def.findOrFail(id);
|
|
461
|
+
if (!Object.keys(data).some((key) => key in def.relations)) {
|
|
462
|
+
model.fill(data);
|
|
463
|
+
await saveModel(def, model);
|
|
464
|
+
return model;
|
|
465
|
+
}
|
|
466
|
+
const { extractRelationData } = await import("./crud-Di2nvpjB.mjs");
|
|
467
|
+
const { columnData, relationOps } = extractRelationData(def, data);
|
|
468
|
+
model.fill(columnData);
|
|
469
|
+
await saveModel(def, model);
|
|
470
|
+
const pkValue = model.get("id");
|
|
471
|
+
if (pkValue == null) return model;
|
|
472
|
+
for (const [relName, op] of Object.entries(relationOps)) {
|
|
473
|
+
const relation = def.relations[relName];
|
|
474
|
+
if (!relation) continue;
|
|
475
|
+
const relatedDef = relation.relatedModelClass;
|
|
476
|
+
const db = relatedDef._orm?.kysely;
|
|
477
|
+
if (!db) continue;
|
|
478
|
+
if (relation.type === "belongsTo") {
|
|
479
|
+
const bop = op;
|
|
480
|
+
if (bop.update) {
|
|
481
|
+
const fkValue = model.get(relation.foreignKey);
|
|
482
|
+
if (fkValue != null) {
|
|
483
|
+
const related = await relatedDef.find(fkValue);
|
|
484
|
+
if (related) {
|
|
485
|
+
related.fill(bop.update);
|
|
486
|
+
const { saveModel: saveRel } = await Promise.resolve().then(() => save_exports);
|
|
487
|
+
await saveRel(relatedDef, related);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
} else if (bop.upsert) {
|
|
491
|
+
const { update, create } = bop.upsert;
|
|
492
|
+
const fkValue = model.get(relation.foreignKey);
|
|
493
|
+
if (fkValue != null) {
|
|
494
|
+
const related = await relatedDef.find(fkValue);
|
|
495
|
+
if (related) {
|
|
496
|
+
related.fill(update);
|
|
497
|
+
const { saveModel: saveRel } = await Promise.resolve().then(() => save_exports);
|
|
498
|
+
await saveRel(relatedDef, related);
|
|
499
|
+
}
|
|
500
|
+
} else {
|
|
501
|
+
const created = await relatedDef.insert(create);
|
|
502
|
+
await db.updateTable(def.table).set({ [relation.foreignKey]: created.get(relation.localKey) }).where("id", "=", pkValue).execute();
|
|
503
|
+
model.set(relation.foreignKey, created.get(relation.localKey));
|
|
504
|
+
}
|
|
505
|
+
} else if (bop.disconnect) {
|
|
506
|
+
await db.updateTable(def.table).set({ [relation.foreignKey]: null }).where("id", "=", pkValue).execute();
|
|
507
|
+
model.set(relation.foreignKey, null);
|
|
508
|
+
} else if (bop.delete) {
|
|
509
|
+
const fkValue = model.get(relation.foreignKey);
|
|
510
|
+
if (fkValue != null) {
|
|
511
|
+
const related = await relatedDef.find(fkValue);
|
|
512
|
+
if (related) {
|
|
513
|
+
const { deleteModel: delRel } = await Promise.resolve().then(() => delete_exports);
|
|
514
|
+
await delRel(relatedDef, related);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
} else if (relation.type === "hasMany" || relation.type === "hasOne") {
|
|
519
|
+
const hop = op;
|
|
520
|
+
if (hop.create) for (const childData of hop.create) await relatedDef.insert({
|
|
521
|
+
...childData,
|
|
522
|
+
[relation.foreignKey]: pkValue
|
|
523
|
+
});
|
|
524
|
+
if (hop.update) {
|
|
525
|
+
const queries = Array.isArray(hop.update?.where) ? hop.update.where : [hop.update?.where];
|
|
526
|
+
for (const where of queries) {
|
|
527
|
+
const whereKey = Object.keys(where)[0];
|
|
528
|
+
await relatedDef.query().where(whereKey, "=", where[whereKey]).all().updateMany(hop.update.data);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (hop.delete) {
|
|
532
|
+
const queries = Array.isArray(hop.delete) ? hop.delete : [hop.delete];
|
|
533
|
+
for (const where of queries) {
|
|
534
|
+
const whereKey = Object.keys(where)[0];
|
|
535
|
+
await relatedDef.query().where(whereKey, "=", where[whereKey]).all().deleteMany();
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
} else if (relation.type === "manyToMany") {
|
|
539
|
+
const mop = op;
|
|
540
|
+
if (mop.create) {
|
|
541
|
+
const throughTable = relation.throughTable;
|
|
542
|
+
const fpk = relation.foreignPivotKey;
|
|
543
|
+
const rpk = relation.relatedPivotKey;
|
|
544
|
+
for (const childData of mop.create) {
|
|
545
|
+
const child = await relatedDef.insert(childData);
|
|
546
|
+
try {
|
|
547
|
+
await db.insertInto(throughTable).values({
|
|
548
|
+
[fpk]: pkValue,
|
|
549
|
+
[rpk]: child.get(relation.localKey ?? "id")
|
|
550
|
+
}).execute();
|
|
551
|
+
} catch (e) {
|
|
552
|
+
if (!isUniqueConstraintError(e)) throw normalizeError(e, throughTable);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
if (mop.connect) {
|
|
557
|
+
const throughTable = relation.throughTable;
|
|
558
|
+
const fpk = relation.foreignPivotKey;
|
|
559
|
+
const rpk = relation.relatedPivotKey;
|
|
560
|
+
const connectLookup = /* @__PURE__ */ new Map();
|
|
561
|
+
const connectObjects = mop.connect.filter((t) => typeof t !== "number" && typeof t !== "string");
|
|
562
|
+
if (connectObjects.length > 0) {
|
|
563
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
564
|
+
for (const t of connectObjects) {
|
|
565
|
+
const k = Object.keys(t)[0];
|
|
566
|
+
if (!byKey.has(k)) byKey.set(k, []);
|
|
567
|
+
byKey.get(k).push(t[k]);
|
|
568
|
+
}
|
|
569
|
+
for (const [k, vals] of byKey) {
|
|
570
|
+
const records = await relatedDef.query().whereIn(k, vals).execute();
|
|
571
|
+
for (const r of records) connectLookup.set(r.get(k), r.get("id"));
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
for (const target of mop.connect) {
|
|
575
|
+
let targetId = target;
|
|
576
|
+
if (typeof target !== "number" && typeof target !== "string") {
|
|
577
|
+
const t = target;
|
|
578
|
+
targetId = connectLookup.get(t[Object.keys(t)[0]]);
|
|
579
|
+
}
|
|
580
|
+
if (targetId != null) try {
|
|
581
|
+
await db.insertInto(throughTable).values({
|
|
582
|
+
[fpk]: pkValue,
|
|
583
|
+
[rpk]: targetId
|
|
584
|
+
}).execute();
|
|
585
|
+
} catch (e) {
|
|
586
|
+
if (!isUniqueConstraintError(e)) throw normalizeError(e, throughTable);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
if (mop.disconnect) {
|
|
591
|
+
const throughTable = relation.throughTable;
|
|
592
|
+
const fpk = relation.foreignPivotKey;
|
|
593
|
+
const rpk = relation.relatedPivotKey;
|
|
594
|
+
const queries = Array.isArray(mop.disconnect) ? mop.disconnect : [mop.disconnect];
|
|
595
|
+
for (const where of queries) if (typeof where === "object" && Object.keys(where).length > 0) {
|
|
596
|
+
const key = Object.keys(where)[0];
|
|
597
|
+
const val = Object.values(where)[0];
|
|
598
|
+
if (key === "id") await db.deleteFrom(throughTable).where(fpk, "=", pkValue).where(rpk, "=", val).execute();
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
if (mop.set) {
|
|
602
|
+
const throughTable = relation.throughTable;
|
|
603
|
+
const fpk = relation.foreignPivotKey;
|
|
604
|
+
const rpk = relation.relatedPivotKey;
|
|
605
|
+
const current = await db.selectFrom(throughTable).select(rpk).where(fpk, "=", pkValue).execute();
|
|
606
|
+
const currentIds = new Set(current.map((r) => r[rpk]));
|
|
607
|
+
const desiredIds = /* @__PURE__ */ new Set();
|
|
608
|
+
const setLookup = /* @__PURE__ */ new Map();
|
|
609
|
+
const setObjects = mop.set.filter((t) => typeof t !== "number" && typeof t !== "string");
|
|
610
|
+
if (setObjects.length > 0) {
|
|
611
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
612
|
+
for (const t of setObjects) {
|
|
613
|
+
const k = Object.keys(t)[0];
|
|
614
|
+
if (!byKey.has(k)) byKey.set(k, []);
|
|
615
|
+
byKey.get(k).push(t[k]);
|
|
616
|
+
}
|
|
617
|
+
for (const [k, vals] of byKey) {
|
|
618
|
+
const records = await relatedDef.query().whereIn(k, vals).execute();
|
|
619
|
+
for (const r of records) setLookup.set(r.get(k), r.get("id"));
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
for (const target of mop.set) {
|
|
623
|
+
let targetId = target;
|
|
624
|
+
if (typeof target !== "number" && typeof target !== "string") {
|
|
625
|
+
const t = target;
|
|
626
|
+
targetId = setLookup.get(t[Object.keys(t)[0]]);
|
|
627
|
+
}
|
|
628
|
+
if (targetId != null) {
|
|
629
|
+
desiredIds.add(targetId);
|
|
630
|
+
if (!currentIds.has(targetId)) try {
|
|
631
|
+
await db.insertInto(throughTable).values({
|
|
632
|
+
[fpk]: pkValue,
|
|
633
|
+
[rpk]: targetId
|
|
634
|
+
}).execute();
|
|
635
|
+
} catch (e) {
|
|
636
|
+
if (!isUniqueConstraintError(e)) throw normalizeError(e, throughTable);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
for (const id of currentIds) if (!desiredIds.has(id)) await db.deleteFrom(throughTable).where(fpk, "=", pkValue).where(rpk, "=", id).execute();
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return model;
|
|
645
|
+
}
|
|
646
|
+
async function reloadModel(def, model) {
|
|
647
|
+
const pk = getPrimaryKeyColumn(def);
|
|
648
|
+
const pkValue = model.get(pk);
|
|
649
|
+
if (pkValue == null) throw new DatabaseError("Cannot reload model without primary key", "MISSING_ID");
|
|
650
|
+
const db = getDb(def);
|
|
651
|
+
try {
|
|
652
|
+
const row = await db.selectFrom(def.table).selectAll().where(pk, "=", pkValue).executeTakeFirst();
|
|
653
|
+
if (row) {
|
|
654
|
+
const config = getConfig(def);
|
|
655
|
+
const applied = config?.casts ? applyCastsToData(config, row) : row;
|
|
656
|
+
const state = getState(model);
|
|
657
|
+
state.attributes = { ...applied };
|
|
658
|
+
state.original = { ...applied };
|
|
659
|
+
}
|
|
660
|
+
} catch (e) {
|
|
661
|
+
throw normalizeError(e, def.table);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
const configMap = /* @__PURE__ */ new WeakMap();
|
|
665
|
+
function setConfig(def, config) {
|
|
666
|
+
configMap.set(def, config);
|
|
667
|
+
}
|
|
668
|
+
function getConfig(def) {
|
|
669
|
+
return configMap.get(def);
|
|
670
|
+
}
|
|
671
|
+
//#endregion
|
|
251
672
|
//#region src/relations/eager.ts
|
|
252
673
|
var eager_exports = /* @__PURE__ */ __exportAll({ EagerLoader: () => EagerLoader });
|
|
253
|
-
function isMorphRelation(relation) {
|
|
254
|
-
return relation._morphMap !== void 0;
|
|
255
|
-
}
|
|
256
674
|
var EagerLoader = class {
|
|
257
675
|
async loadRelated(models, eagerLoad, def) {
|
|
258
676
|
const { name, constraints } = eagerLoad;
|
|
@@ -266,7 +684,7 @@ var EagerLoader = class {
|
|
|
266
684
|
const nestedName = name.slice(dotIdx + 1);
|
|
267
685
|
const relation = def.relations[baseName];
|
|
268
686
|
if (!relation) throw new Error(`Relation "${baseName}" not found on ${def.name}`);
|
|
269
|
-
if (
|
|
687
|
+
if (relation._morphMap !== void 0) throw new Error(`Cannot eagerly load nested relation "${nestedName}" through polymorphic relation "${baseName}" on ${def.name}. Nested eager loading through polymorphic belongsTo is not supported.`);
|
|
270
688
|
await relation.loadEager(models, baseName, null);
|
|
271
689
|
const relatedModels = [];
|
|
272
690
|
for (const model of models) {
|
|
@@ -288,64 +706,31 @@ var EagerLoader = class {
|
|
|
288
706
|
}
|
|
289
707
|
};
|
|
290
708
|
//#endregion
|
|
291
|
-
//#region src/relations/graph/
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
const THUNK_CACHE$3 = /* @__PURE__ */ new WeakMap();
|
|
313
|
-
function resolveThunk$3(thunk) {
|
|
314
|
-
let cls = THUNK_CACHE$3.get(thunk);
|
|
315
|
-
if (!cls) {
|
|
316
|
-
cls = thunk();
|
|
317
|
-
THUNK_CACHE$3.set(thunk, cls);
|
|
709
|
+
//#region src/relations/graph/delete.ts
|
|
710
|
+
async function deleteGraph(def, model, options = {}) {
|
|
711
|
+
const db = getDb(def);
|
|
712
|
+
const pk = getPrimaryKeyColumn(def);
|
|
713
|
+
const pkValue = model.get(pk);
|
|
714
|
+
if (pkValue == null) throw new Error("Cannot deleteGraph: model has no primary key");
|
|
715
|
+
for (const [relationName, relation] of Object.entries(def.relations)) {
|
|
716
|
+
if (options.allowedRelations && !options.allowedRelations.includes(relationName)) continue;
|
|
717
|
+
const relatedDef = relation.relatedModelClass;
|
|
718
|
+
if (relation.type === "hasMany") {
|
|
719
|
+
const children = await relatedDef.query().where(relation.foreignKey, "=", pkValue).execute();
|
|
720
|
+
for (const child of children) await child.$delete();
|
|
721
|
+
} else if (relation.type === "manyToMany") {
|
|
722
|
+
const throughTable = relation.throughTable;
|
|
723
|
+
const fpk = relation.foreignPivotKey;
|
|
724
|
+
await db.deleteFrom(throughTable).where(fpk, "=", pkValue).execute();
|
|
725
|
+
} else if (relation.type === "hasOne") {
|
|
726
|
+
const child = await relatedDef.query().where(relation.foreignKey, "=", pkValue).first();
|
|
727
|
+
if (child) await child.$delete();
|
|
728
|
+
}
|
|
318
729
|
}
|
|
319
|
-
|
|
730
|
+
await model.$delete();
|
|
320
731
|
}
|
|
321
732
|
//#endregion
|
|
322
733
|
//#region src/relations/graph/parser.ts
|
|
323
|
-
function getPrimaryKeyColumn$2(def) {
|
|
324
|
-
const cols = def.columns;
|
|
325
|
-
for (const [name, col] of Object.entries(cols)) if (col.isPrimaryKey) return name;
|
|
326
|
-
return "id";
|
|
327
|
-
}
|
|
328
|
-
function getDb$1(def) {
|
|
329
|
-
if (!def._orm) throw new Error("Model not registered");
|
|
330
|
-
return def._orm.kysely;
|
|
331
|
-
}
|
|
332
|
-
function getPivotInfo(relation) {
|
|
333
|
-
if (relation.type !== "manyToMany" || !relation.throughTable) throw new Error("Not a many-to-many relation");
|
|
334
|
-
return {
|
|
335
|
-
throughTable: relation.throughTable,
|
|
336
|
-
foreignPivotKey: relation.foreignPivotKey ?? "",
|
|
337
|
-
relatedPivotKey: relation.relatedPivotKey ?? ""
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
async function findRelated(def, conditions) {
|
|
341
|
-
const key = Object.keys(conditions)[0];
|
|
342
|
-
return def.query().where(key, "=", conditions[key]).executeTakeFirst();
|
|
343
|
-
}
|
|
344
|
-
async function resolveTargetId(def, target) {
|
|
345
|
-
if (typeof target === "number" || typeof target === "string") return target;
|
|
346
|
-
const found = await findRelated(def, target);
|
|
347
|
-
if (found) return found.get("id");
|
|
348
|
-
}
|
|
349
734
|
/**
|
|
350
735
|
* Splits a graph node's keys into column data and relation operations.
|
|
351
736
|
* Handles both the old-style { create: ..., connect: ... } wrappers and
|
|
@@ -509,7 +894,7 @@ async function processNode(node, def, parentFK, options, context, path) {
|
|
|
509
894
|
}
|
|
510
895
|
const instance = await def.insert(columnData);
|
|
511
896
|
if (nodeId && typeof nodeId === "string") context.processedRefs.set(nodeId, instance);
|
|
512
|
-
const pkCol = getPrimaryKeyColumn
|
|
897
|
+
const pkCol = getPrimaryKeyColumn(def);
|
|
513
898
|
const pkValue = instance.get(pkCol);
|
|
514
899
|
if (pkValue == null) throw new DatabaseError("Cannot process relations without primary key", "MISSING_ID");
|
|
515
900
|
for (const [relName, op] of Object.entries(relationOps)) {
|
|
@@ -528,7 +913,7 @@ async function processNode(node, def, parentFK, options, context, path) {
|
|
|
528
913
|
return instance;
|
|
529
914
|
}
|
|
530
915
|
async function processBelongsTo(relation, op, options, context, path, parentColumnData) {
|
|
531
|
-
if (
|
|
916
|
+
if (relation._morphMap !== void 0) return processMorphTo(relation, op, options, context, path, parentColumnData);
|
|
532
917
|
const relatedDef = relation.relatedModelClass;
|
|
533
918
|
if (op["#dbRef"] != null) {
|
|
534
919
|
const id = op["#dbRef"];
|
|
@@ -556,8 +941,8 @@ async function processBelongsTo(relation, op, options, context, path, parentColu
|
|
|
556
941
|
*/
|
|
557
942
|
async function processMorphTo(relation, op, options, context, path, parentColumnData) {
|
|
558
943
|
const morphMap = relation._morphMap;
|
|
559
|
-
const morphType =
|
|
560
|
-
const morphId =
|
|
944
|
+
const morphType = relation._morphType;
|
|
945
|
+
const morphId = relation._morphId;
|
|
561
946
|
if (!morphMap || Object.keys(morphMap).length === 0) throw new Error("Cannot process MorphTo relation: no morphMap provided. Define a morphMap with model thunks when calling defineMorphTo().");
|
|
562
947
|
let typeValue = op.type;
|
|
563
948
|
if (!typeValue) {
|
|
@@ -567,7 +952,7 @@ async function processMorphTo(relation, op, options, context, path, parentColumn
|
|
|
567
952
|
if (!typeValue) throw new Error(`Cannot resolve MorphTo: no type specified. Provide a "type" key in the relation data (e.g., { type: "${Object.keys(morphMap)[0]}" }). Available types: ${Object.keys(morphMap).join(", ")}`);
|
|
568
953
|
const thunk = morphMap[typeValue];
|
|
569
954
|
if (!thunk) throw new Error(`No model registered for morph type "${typeValue}" in MorphTo. Available types: ${Object.keys(morphMap).join(", ")}`);
|
|
570
|
-
const relatedDef = resolveThunk
|
|
955
|
+
const relatedDef = resolveThunk(thunk);
|
|
571
956
|
let instance = null;
|
|
572
957
|
if (op["#dbRef"] != null) {
|
|
573
958
|
const id = op["#dbRef"];
|
|
@@ -609,9 +994,9 @@ async function processHasMany(_instance, relation, op, pkValue, options, context
|
|
|
609
994
|
continue;
|
|
610
995
|
}
|
|
611
996
|
const parentData = { [fk]: pkValue };
|
|
612
|
-
if (
|
|
613
|
-
const typeCol =
|
|
614
|
-
const typeVal =
|
|
997
|
+
if (relation._morphType !== void 0 && relation._morphMap === void 0) {
|
|
998
|
+
const typeCol = relation._morphType;
|
|
999
|
+
const typeVal = relation._morphTypeValue;
|
|
615
1000
|
if (typeVal !== void 0) parentData[typeCol] = typeVal;
|
|
616
1001
|
}
|
|
617
1002
|
await processNode(item, relatedDef, parentData, options, context, path);
|
|
@@ -631,7 +1016,7 @@ async function processHasMany(_instance, relation, op, pkValue, options, context
|
|
|
631
1016
|
async function processManyToMany(_instance, relation, op, pkValue, options, context, path) {
|
|
632
1017
|
const relatedDef = relation.relatedModelClass;
|
|
633
1018
|
const { throughTable, foreignPivotKey, relatedPivotKey } = getPivotInfo(relation);
|
|
634
|
-
const db = getDb
|
|
1019
|
+
const db = getDb(relatedDef);
|
|
635
1020
|
const items = Array.isArray(op) ? op : Array.isArray(op?.create) ? op.create : [];
|
|
636
1021
|
for (const item of items) {
|
|
637
1022
|
if (item["#dbRef"] != null) {
|
|
@@ -641,7 +1026,9 @@ async function processManyToMany(_instance, relation, op, pkValue, options, cont
|
|
|
641
1026
|
[foreignPivotKey]: pkValue,
|
|
642
1027
|
[relatedPivotKey]: id
|
|
643
1028
|
}).execute();
|
|
644
|
-
} catch {
|
|
1029
|
+
} catch (e) {
|
|
1030
|
+
if (!isUniqueConstraintError(e)) throw normalizeError(e, throughTable);
|
|
1031
|
+
}
|
|
645
1032
|
continue;
|
|
646
1033
|
}
|
|
647
1034
|
const relatedId = (await processNode(item, relatedDef, null, options, context, path)).get(relation.localKey ?? "id");
|
|
@@ -650,7 +1037,9 @@ async function processManyToMany(_instance, relation, op, pkValue, options, cont
|
|
|
650
1037
|
[foreignPivotKey]: pkValue,
|
|
651
1038
|
[relatedPivotKey]: relatedId
|
|
652
1039
|
}).execute();
|
|
653
|
-
} catch {
|
|
1040
|
+
} catch (e) {
|
|
1041
|
+
if (!isUniqueConstraintError(e)) throw normalizeError(e, throughTable);
|
|
1042
|
+
}
|
|
654
1043
|
}
|
|
655
1044
|
const connectItems = !Array.isArray(op) ? op?.connect ?? [] : [];
|
|
656
1045
|
for (const target of connectItems) {
|
|
@@ -660,7 +1049,9 @@ async function processManyToMany(_instance, relation, op, pkValue, options, cont
|
|
|
660
1049
|
[foreignPivotKey]: pkValue,
|
|
661
1050
|
[relatedPivotKey]: targetId
|
|
662
1051
|
}).execute();
|
|
663
|
-
} catch {
|
|
1052
|
+
} catch (e) {
|
|
1053
|
+
if (!isUniqueConstraintError(e)) throw normalizeError(e, throughTable);
|
|
1054
|
+
}
|
|
664
1055
|
}
|
|
665
1056
|
}
|
|
666
1057
|
//#endregion
|
|
@@ -704,7 +1095,7 @@ async function upsertNode(node, def, parentFK, options, context, _path) {
|
|
|
704
1095
|
if (relatedInstance) columnData[relation.foreignKey] = relatedInstance.get(relation.localKey);
|
|
705
1096
|
}
|
|
706
1097
|
}
|
|
707
|
-
const pkCol = getPrimaryKeyColumn
|
|
1098
|
+
const pkCol = getPrimaryKeyColumn(def);
|
|
708
1099
|
const idValue = columnData[pkCol] ?? node[pkCol];
|
|
709
1100
|
let instance;
|
|
710
1101
|
if (idValue != null) if (isRelPathAllowed(pkCol, options.noUpdate)) {
|
|
@@ -740,13 +1131,13 @@ async function upsertHasMany(_instance, relation, op, pkValue, options, context,
|
|
|
740
1131
|
const existingChildren = await relatedDef.query().where(fk, "=", pkValue).execute();
|
|
741
1132
|
const existingMap = /* @__PURE__ */ new Map();
|
|
742
1133
|
for (const child of existingChildren) {
|
|
743
|
-
const pkCol = getPrimaryKeyColumn
|
|
1134
|
+
const pkCol = getPrimaryKeyColumn(relatedDef);
|
|
744
1135
|
existingMap.set(child.get(pkCol), child);
|
|
745
1136
|
}
|
|
746
1137
|
const items = Array.isArray(op) ? op : Array.isArray(op?.create) ? op.create : [];
|
|
747
1138
|
const incomingIds = /* @__PURE__ */ new Set();
|
|
748
1139
|
for (const item of items) {
|
|
749
|
-
const itemId = item[getPrimaryKeyColumn
|
|
1140
|
+
const itemId = item[getPrimaryKeyColumn(relatedDef)] ?? item.id;
|
|
750
1141
|
if (itemId != null) {
|
|
751
1142
|
incomingIds.add(itemId);
|
|
752
1143
|
if (!isRelPathAllowed(relNameFromPath(path), options.noUpdate)) {
|
|
@@ -762,7 +1153,7 @@ async function upsertHasMany(_instance, relation, op, pkValue, options, context,
|
|
|
762
1153
|
if (!rel) continue;
|
|
763
1154
|
const nestedPath = `${path}.${relName}`;
|
|
764
1155
|
assertRelationAllowed(relatedDef, nestedPath, context.allowedGraphSet);
|
|
765
|
-
const childPk = existing.get(getPrimaryKeyColumn
|
|
1156
|
+
const childPk = existing.get(getPrimaryKeyColumn(relatedDef));
|
|
766
1157
|
if (rel.type === "hasMany" || rel.type === "hasOne") await upsertHasMany(existing, rel, relOp, childPk, options, context, nestedPath);
|
|
767
1158
|
else if (rel.type === "manyToMany") await upsertManyToMany(existing, rel, relOp, childPk, options, context, nestedPath);
|
|
768
1159
|
}
|
|
@@ -771,9 +1162,9 @@ async function upsertHasMany(_instance, relation, op, pkValue, options, context,
|
|
|
771
1162
|
}
|
|
772
1163
|
}
|
|
773
1164
|
const parentData = { [fk]: pkValue };
|
|
774
|
-
if (
|
|
775
|
-
const typeCol =
|
|
776
|
-
const typeVal =
|
|
1165
|
+
if (relation._morphType !== void 0 && relation._morphMap === void 0) {
|
|
1166
|
+
const typeCol = relation._morphType;
|
|
1167
|
+
const typeVal = relation._morphTypeValue;
|
|
777
1168
|
if (typeVal !== void 0) parentData[typeCol] = typeVal;
|
|
778
1169
|
}
|
|
779
1170
|
await processNode(item, relatedDef, parentData, options, context, path);
|
|
@@ -793,7 +1184,7 @@ async function upsertHasMany(_instance, relation, op, pkValue, options, context,
|
|
|
793
1184
|
async function upsertManyToMany(_instance, relation, op, pkValue, options, context, path) {
|
|
794
1185
|
const relatedDef = relation.relatedModelClass;
|
|
795
1186
|
const { throughTable, foreignPivotKey, relatedPivotKey } = getPivotInfo(relation);
|
|
796
|
-
const db = getDb
|
|
1187
|
+
const db = getDb(relatedDef);
|
|
797
1188
|
let existingPivotIds = /* @__PURE__ */ new Set();
|
|
798
1189
|
try {
|
|
799
1190
|
const pivots = await db.selectFrom(throughTable).select(relatedPivotKey).where(foreignPivotKey, "=", pkValue).execute();
|
|
@@ -801,6 +1192,14 @@ async function upsertManyToMany(_instance, relation, op, pkValue, options, conte
|
|
|
801
1192
|
} catch {}
|
|
802
1193
|
const incomingIds = /* @__PURE__ */ new Set();
|
|
803
1194
|
const items = Array.isArray(op) ? op : Array.isArray(op?.create) ? op.create : [];
|
|
1195
|
+
const needsUpdate = !isRelPathAllowed(relNameFromPath(path), options.noUpdate);
|
|
1196
|
+
const pkCol = getPrimaryKeyColumn(relatedDef);
|
|
1197
|
+
const itemIds = items.filter((i) => i["#dbRef"] == null).map((i) => i[pkCol] ?? i.id).filter((id) => id != null);
|
|
1198
|
+
const batchMap = /* @__PURE__ */ new Map();
|
|
1199
|
+
if (needsUpdate && itemIds.length > 0) {
|
|
1200
|
+
const records = await relatedDef.query().whereIn(pkCol, itemIds).execute();
|
|
1201
|
+
for (const r of records) batchMap.set(r.get(pkCol), r);
|
|
1202
|
+
}
|
|
804
1203
|
for (const item of items) {
|
|
805
1204
|
if (item["#dbRef"] != null) {
|
|
806
1205
|
const id = item["#dbRef"];
|
|
@@ -810,14 +1209,16 @@ async function upsertManyToMany(_instance, relation, op, pkValue, options, conte
|
|
|
810
1209
|
[foreignPivotKey]: pkValue,
|
|
811
1210
|
[relatedPivotKey]: id
|
|
812
1211
|
}).execute();
|
|
813
|
-
} catch {
|
|
1212
|
+
} catch (e) {
|
|
1213
|
+
if (!isUniqueConstraintError(e)) throw normalizeError(e, throughTable);
|
|
1214
|
+
}
|
|
814
1215
|
continue;
|
|
815
1216
|
}
|
|
816
|
-
const itemId = item[
|
|
1217
|
+
const itemId = item[pkCol] ?? item.id;
|
|
817
1218
|
if (itemId != null) {
|
|
818
1219
|
incomingIds.add(itemId);
|
|
819
|
-
if (
|
|
820
|
-
const existing =
|
|
1220
|
+
if (needsUpdate) {
|
|
1221
|
+
const existing = batchMap.get(itemId);
|
|
821
1222
|
if (existing) {
|
|
822
1223
|
existing.fill(item);
|
|
823
1224
|
await existing.$save();
|
|
@@ -828,7 +1229,9 @@ async function upsertManyToMany(_instance, relation, op, pkValue, options, conte
|
|
|
828
1229
|
[foreignPivotKey]: pkValue,
|
|
829
1230
|
[relatedPivotKey]: itemId
|
|
830
1231
|
}).execute();
|
|
831
|
-
} catch {
|
|
1232
|
+
} catch (e) {
|
|
1233
|
+
if (!isUniqueConstraintError(e)) throw normalizeError(e, throughTable);
|
|
1234
|
+
}
|
|
832
1235
|
} else {
|
|
833
1236
|
const relatedId = (await processNode(item, relatedDef, null, options, context, path)).get(relation.localKey ?? "id");
|
|
834
1237
|
if (relatedId != null) {
|
|
@@ -838,13 +1241,33 @@ async function upsertManyToMany(_instance, relation, op, pkValue, options, conte
|
|
|
838
1241
|
[foreignPivotKey]: pkValue,
|
|
839
1242
|
[relatedPivotKey]: relatedId
|
|
840
1243
|
}).execute();
|
|
841
|
-
} catch {
|
|
1244
|
+
} catch (e) {
|
|
1245
|
+
if (!isUniqueConstraintError(e)) throw normalizeError(e, throughTable);
|
|
1246
|
+
}
|
|
842
1247
|
}
|
|
843
1248
|
}
|
|
844
1249
|
}
|
|
845
1250
|
const connectItems = !Array.isArray(op) ? op?.connect ?? [] : [];
|
|
1251
|
+
const connectLookup = /* @__PURE__ */ new Map();
|
|
1252
|
+
const connectObjects = connectItems.filter((t) => typeof t !== "number" && typeof t !== "string");
|
|
1253
|
+
if (connectObjects.length > 0) {
|
|
1254
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1255
|
+
for (const t of connectObjects) {
|
|
1256
|
+
const k = Object.keys(t)[0];
|
|
1257
|
+
if (!byKey.has(k)) byKey.set(k, []);
|
|
1258
|
+
byKey.get(k).push(t[k]);
|
|
1259
|
+
}
|
|
1260
|
+
for (const [k, vals] of byKey) {
|
|
1261
|
+
const records = await relatedDef.query().whereIn(k, vals).execute();
|
|
1262
|
+
for (const r of records) connectLookup.set(r.get(k), r.get("id"));
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
846
1265
|
for (const target of connectItems) {
|
|
847
|
-
|
|
1266
|
+
let targetId = target;
|
|
1267
|
+
if (typeof target !== "number" && typeof target !== "string") {
|
|
1268
|
+
const t = target;
|
|
1269
|
+
targetId = connectLookup.get(t[Object.keys(t)[0]]);
|
|
1270
|
+
}
|
|
848
1271
|
if (targetId != null) {
|
|
849
1272
|
incomingIds.add(targetId);
|
|
850
1273
|
if (!existingPivotIds.has(targetId)) try {
|
|
@@ -852,7 +1275,9 @@ async function upsertManyToMany(_instance, relation, op, pkValue, options, conte
|
|
|
852
1275
|
[foreignPivotKey]: pkValue,
|
|
853
1276
|
[relatedPivotKey]: targetId
|
|
854
1277
|
}).execute();
|
|
855
|
-
} catch {
|
|
1278
|
+
} catch (e) {
|
|
1279
|
+
if (!isUniqueConstraintError(e)) throw normalizeError(e, throughTable);
|
|
1280
|
+
}
|
|
856
1281
|
}
|
|
857
1282
|
}
|
|
858
1283
|
const relName = relNameFromPath(path);
|
|
@@ -862,12 +1287,16 @@ async function upsertManyToMany(_instance, relation, op, pkValue, options, conte
|
|
|
862
1287
|
for (const pivotId of existingPivotIds) if (!incomingIds.has(pivotId)) {
|
|
863
1288
|
if (shouldUnrelate) try {
|
|
864
1289
|
await db.deleteFrom(throughTable).where(foreignPivotKey, "=", pkValue).where(relatedPivotKey, "=", pivotId).execute();
|
|
865
|
-
} catch {
|
|
1290
|
+
} catch (e) {
|
|
1291
|
+
if (!isUniqueConstraintError(e)) throw normalizeError(e, throughTable);
|
|
1292
|
+
}
|
|
866
1293
|
else if (shouldDelete) {
|
|
867
1294
|
await (await relatedDef.find(pivotId))?.$delete();
|
|
868
1295
|
try {
|
|
869
1296
|
await db.deleteFrom(throughTable).where(foreignPivotKey, "=", pkValue).where(relatedPivotKey, "=", pivotId).execute();
|
|
870
|
-
} catch {
|
|
1297
|
+
} catch (e) {
|
|
1298
|
+
if (!isUniqueConstraintError(e)) throw normalizeError(e, throughTable);
|
|
1299
|
+
}
|
|
871
1300
|
}
|
|
872
1301
|
}
|
|
873
1302
|
}
|
|
@@ -875,14 +1304,12 @@ async function upsertManyToMany(_instance, relation, op, pkValue, options, conte
|
|
|
875
1304
|
//#endregion
|
|
876
1305
|
//#region src/relations/graph/index.ts
|
|
877
1306
|
var graph_exports = /* @__PURE__ */ __exportAll({
|
|
1307
|
+
deleteGraph: () => deleteGraph,
|
|
878
1308
|
insertGraph: () => insertGraph,
|
|
879
1309
|
upsertGraph: () => upsertGraph
|
|
880
1310
|
});
|
|
881
1311
|
//#endregion
|
|
882
1312
|
//#region src/query/builder.ts
|
|
883
|
-
function rawSql(str) {
|
|
884
|
-
return sql([str]);
|
|
885
|
-
}
|
|
886
1313
|
const SAFE_COLUMN = /^[a-zA-Z_*][a-zA-Z0-9_.*]*$/;
|
|
887
1314
|
function createQueryBuilder(def, peta) {
|
|
888
1315
|
const db = peta?.kysely ?? def._orm?.kysely;
|
|
@@ -897,7 +1324,6 @@ function createQueryBuilder(def, peta) {
|
|
|
897
1324
|
let hasEffectiveWhere = false;
|
|
898
1325
|
let selectedColumns = null;
|
|
899
1326
|
const aggregateColumns = [];
|
|
900
|
-
const aggregateAliases = [];
|
|
901
1327
|
const whereOps = [];
|
|
902
1328
|
let allowedGraphSet = null;
|
|
903
1329
|
function validateColumn(ref) {
|
|
@@ -941,6 +1367,16 @@ function createQueryBuilder(def, peta) {
|
|
|
941
1367
|
}
|
|
942
1368
|
return models;
|
|
943
1369
|
}
|
|
1370
|
+
const _withAggregate = (relationName, aggregateSql, alias, isExists = false) => {
|
|
1371
|
+
const rel = def.relations[relationName];
|
|
1372
|
+
if (!rel) throw new RelationNotFoundError(def.name, relationName);
|
|
1373
|
+
const relatedTable = rel.relatedModelClass.table;
|
|
1374
|
+
const fk = rel.foreignKey;
|
|
1375
|
+
const lk = rel.localKey;
|
|
1376
|
+
const sql = isExists ? `(SELECT EXISTS(SELECT 1 FROM ${relatedTable} WHERE ${relatedTable}.${fk} = ${def.table}.${lk})) as ${alias}` : `(SELECT ${aggregateSql} FROM ${relatedTable} WHERE ${relatedTable}.${fk} = ${def.table}.${lk}) as ${alias}`;
|
|
1377
|
+
aggregateColumns.push(sql);
|
|
1378
|
+
return self;
|
|
1379
|
+
};
|
|
944
1380
|
const self = {
|
|
945
1381
|
then(onfulfilled, onrejected) {
|
|
946
1382
|
return runExecute().then(onfulfilled, onrejected);
|
|
@@ -948,7 +1384,7 @@ function createQueryBuilder(def, peta) {
|
|
|
948
1384
|
execute: runExecute,
|
|
949
1385
|
async collect() {
|
|
950
1386
|
const items = await runExecute();
|
|
951
|
-
const { createCollection } = await import("./collection-
|
|
1387
|
+
const { createCollection } = await import("./collection-D9YZn2mL.mjs").then((n) => n.t);
|
|
952
1388
|
return createCollection(items);
|
|
953
1389
|
},
|
|
954
1390
|
async executeTakeFirst() {
|
|
@@ -1004,70 +1440,10 @@ function createQueryBuilder(def, peta) {
|
|
|
1004
1440
|
return Number(result?.max ?? 0);
|
|
1005
1441
|
},
|
|
1006
1442
|
withCount(relation) {
|
|
1007
|
-
|
|
1008
|
-
const rel = def.relations[relation];
|
|
1009
|
-
if (!rel) throw new RelationNotFoundError(def.name, relation);
|
|
1010
|
-
const relatedTable = rel.relatedModelClass.table;
|
|
1011
|
-
const fk = rel.foreignKey;
|
|
1012
|
-
const lk = rel.localKey;
|
|
1013
|
-
aggregateColumns.push(`(SELECT COUNT(*) FROM ${relatedTable} WHERE ${relatedTable}.${fk} = ${def.table}.${lk}) as ${alias}`);
|
|
1014
|
-
aggregateAliases.push(alias);
|
|
1015
|
-
return self;
|
|
1443
|
+
return _withAggregate(relation, "COUNT(*)", `${relation}_count`);
|
|
1016
1444
|
},
|
|
1017
1445
|
withSum(relation, column) {
|
|
1018
|
-
|
|
1019
|
-
const rel = def.relations[relation];
|
|
1020
|
-
if (!rel) throw new RelationNotFoundError(def.name, relation);
|
|
1021
|
-
const relatedTable = rel.relatedModelClass.table;
|
|
1022
|
-
const fk = rel.foreignKey;
|
|
1023
|
-
const lk = rel.localKey;
|
|
1024
|
-
aggregateColumns.push(`(SELECT COALESCE(SUM(${relatedTable}.${validateColumn(column)}), 0) FROM ${relatedTable} WHERE ${relatedTable}.${fk} = ${def.table}.${lk}) as ${alias}`);
|
|
1025
|
-
aggregateAliases.push(alias);
|
|
1026
|
-
return self;
|
|
1027
|
-
},
|
|
1028
|
-
withAvg(relation, column) {
|
|
1029
|
-
const alias = `${relation}_avg_${column}`;
|
|
1030
|
-
const rel = def.relations[relation];
|
|
1031
|
-
if (!rel) throw new RelationNotFoundError(def.name, relation);
|
|
1032
|
-
const relatedTable = rel.relatedModelClass.table;
|
|
1033
|
-
const fk = rel.foreignKey;
|
|
1034
|
-
const lk = rel.localKey;
|
|
1035
|
-
aggregateColumns.push(`(SELECT AVG(${relatedTable}.${validateColumn(column)}) FROM ${relatedTable} WHERE ${relatedTable}.${fk} = ${def.table}.${lk}) as ${alias}`);
|
|
1036
|
-
aggregateAliases.push(alias);
|
|
1037
|
-
return self;
|
|
1038
|
-
},
|
|
1039
|
-
withMin(relation, column) {
|
|
1040
|
-
const alias = `${relation}_min_${column}`;
|
|
1041
|
-
const rel = def.relations[relation];
|
|
1042
|
-
if (!rel) throw new RelationNotFoundError(def.name, relation);
|
|
1043
|
-
const relatedTable = rel.relatedModelClass.table;
|
|
1044
|
-
const fk = rel.foreignKey;
|
|
1045
|
-
const lk = rel.localKey;
|
|
1046
|
-
aggregateColumns.push(`(SELECT MIN(${relatedTable}.${validateColumn(column)}) FROM ${relatedTable} WHERE ${relatedTable}.${fk} = ${def.table}.${lk}) as ${alias}`);
|
|
1047
|
-
aggregateAliases.push(alias);
|
|
1048
|
-
return self;
|
|
1049
|
-
},
|
|
1050
|
-
withMax(relation, column) {
|
|
1051
|
-
const alias = `${relation}_max_${column}`;
|
|
1052
|
-
const rel = def.relations[relation];
|
|
1053
|
-
if (!rel) throw new RelationNotFoundError(def.name, relation);
|
|
1054
|
-
const relatedTable = rel.relatedModelClass.table;
|
|
1055
|
-
const fk = rel.foreignKey;
|
|
1056
|
-
const lk = rel.localKey;
|
|
1057
|
-
aggregateColumns.push(`(SELECT MAX(${relatedTable}.${validateColumn(column)}) FROM ${relatedTable} WHERE ${relatedTable}.${fk} = ${def.table}.${lk}) as ${alias}`);
|
|
1058
|
-
aggregateAliases.push(alias);
|
|
1059
|
-
return self;
|
|
1060
|
-
},
|
|
1061
|
-
withExists(relation) {
|
|
1062
|
-
const alias = `${relation}_exists`;
|
|
1063
|
-
const rel = def.relations[relation];
|
|
1064
|
-
if (!rel) throw new RelationNotFoundError(def.name, relation);
|
|
1065
|
-
const relatedTable = rel.relatedModelClass.table;
|
|
1066
|
-
const fk = rel.foreignKey;
|
|
1067
|
-
const lk = rel.localKey;
|
|
1068
|
-
aggregateColumns.push(`(SELECT EXISTS(SELECT 1 FROM ${relatedTable} WHERE ${relatedTable}.${fk} = ${def.table}.${lk})) as ${alias}`);
|
|
1069
|
-
aggregateAliases.push(alias);
|
|
1070
|
-
return self;
|
|
1446
|
+
return _withAggregate(relation, `COALESCE(SUM(${validateColumn(column)}), 0)`, `${relation}_sum_${column}`);
|
|
1071
1447
|
},
|
|
1072
1448
|
async chunk(size, callback) {
|
|
1073
1449
|
let offset = 0;
|
|
@@ -1128,6 +1504,19 @@ function createQueryBuilder(def, peta) {
|
|
|
1128
1504
|
allowGraph: allowedGraphSet ? [...allowedGraphSet] : options?.allowGraph
|
|
1129
1505
|
});
|
|
1130
1506
|
},
|
|
1507
|
+
async upsert(data) {
|
|
1508
|
+
const pk = getPrimaryKeyColumn(def);
|
|
1509
|
+
const pkValue = data[pk];
|
|
1510
|
+
if (pkValue != null) {
|
|
1511
|
+
const existing = await db.selectFrom(def.table).selectAll().where(pk, "=", pkValue).executeTakeFirst();
|
|
1512
|
+
if (existing) {
|
|
1513
|
+
const instance = def.hydrate(existing);
|
|
1514
|
+
instance.fill(data);
|
|
1515
|
+
return instance.$save();
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
return insertModel(def, data);
|
|
1519
|
+
},
|
|
1131
1520
|
async updateMany(data) {
|
|
1132
1521
|
applyScopes();
|
|
1133
1522
|
assertWhereForMutation();
|
|
@@ -1156,9 +1545,9 @@ function createQueryBuilder(def, peta) {
|
|
|
1156
1545
|
for (const op of whereOps) updateQb = op(updateQb);
|
|
1157
1546
|
try {
|
|
1158
1547
|
const result = await updateQb.execute();
|
|
1159
|
-
return Number(result
|
|
1548
|
+
return Number(result[0]?.numUpdatedRows ?? 0);
|
|
1160
1549
|
} catch (e) {
|
|
1161
|
-
const { normalizeError } = await import("./errors-
|
|
1550
|
+
const { normalizeError } = await import("./errors-i-gCZnlW.mjs").then((n) => n.t);
|
|
1162
1551
|
throw normalizeError(e, def.table);
|
|
1163
1552
|
}
|
|
1164
1553
|
},
|
|
@@ -1199,9 +1588,9 @@ function createQueryBuilder(def, peta) {
|
|
|
1199
1588
|
let numDeleted = 0;
|
|
1200
1589
|
try {
|
|
1201
1590
|
const result = await deleteQb.execute();
|
|
1202
|
-
numDeleted = Number(result
|
|
1591
|
+
numDeleted = Number(result[0]?.numDeletedRows ?? 0);
|
|
1203
1592
|
} catch (e) {
|
|
1204
|
-
const { normalizeError } = await import("./errors-
|
|
1593
|
+
const { normalizeError } = await import("./errors-i-gCZnlW.mjs").then((n) => n.t);
|
|
1205
1594
|
throw normalizeError(e, def.table);
|
|
1206
1595
|
}
|
|
1207
1596
|
if (hasAfter && deletedRows.length > 0) {
|
|
@@ -1234,34 +1623,38 @@ function createQueryBuilder(def, peta) {
|
|
|
1234
1623
|
hasEffectiveWhere = values.length > 0;
|
|
1235
1624
|
return self;
|
|
1236
1625
|
},
|
|
1237
|
-
whereInPivot(column, values) {
|
|
1238
|
-
qb = qb.where(validateColumn(column), "in", values);
|
|
1239
|
-
whereOps.push((q) => q.where(validateColumn(column), "in", values));
|
|
1240
|
-
hasEffectiveWhere = values.length > 0;
|
|
1241
|
-
return self;
|
|
1242
|
-
},
|
|
1243
1626
|
has(relationName) {
|
|
1244
1627
|
const rel = def.relations[relationName];
|
|
1245
1628
|
if (!rel) throw new RelationNotFoundError(def.name, relationName);
|
|
1246
1629
|
const relatedTable = rel.relatedModelClass.table;
|
|
1247
1630
|
const fk = rel.foreignKey;
|
|
1248
1631
|
const lk = rel.localKey;
|
|
1249
|
-
const existsExpr =
|
|
1632
|
+
const existsExpr = sql([`EXISTS (SELECT 1 FROM ${relatedTable} WHERE ${relatedTable}.${fk} = ${def.table}.${lk})`]);
|
|
1250
1633
|
qb = qb.where(existsExpr);
|
|
1251
1634
|
hasEffectiveWhere = true;
|
|
1252
1635
|
return self;
|
|
1253
1636
|
},
|
|
1254
|
-
whereHas(relationName,
|
|
1255
|
-
|
|
1637
|
+
whereHas(relationName, callback) {
|
|
1638
|
+
const rel = def.relations[relationName];
|
|
1639
|
+
if (!rel) throw new RelationNotFoundError(def.name, relationName);
|
|
1640
|
+
const relatedTable = rel.relatedModelClass.table;
|
|
1641
|
+
const fk = rel.foreignKey;
|
|
1642
|
+
const lk = rel.localKey;
|
|
1643
|
+
let subQuery = db.selectFrom(relatedTable).select(sql`1`.as("dummy")).whereRef(`${relatedTable}.${fk}`, "=", `${def.table}.${lk}`);
|
|
1644
|
+
if (callback) subQuery = callback(subQuery) ?? subQuery;
|
|
1645
|
+
qb = qb.where(sql`EXISTS ${subQuery}`);
|
|
1646
|
+
hasEffectiveWhere = true;
|
|
1647
|
+
return self;
|
|
1256
1648
|
},
|
|
1257
|
-
whereDoesntHave(relationName,
|
|
1649
|
+
whereDoesntHave(relationName, callback) {
|
|
1258
1650
|
const rel = def.relations[relationName];
|
|
1259
1651
|
if (!rel) throw new RelationNotFoundError(def.name, relationName);
|
|
1260
1652
|
const relatedTable = rel.relatedModelClass.table;
|
|
1261
1653
|
const fk = rel.foreignKey;
|
|
1262
1654
|
const lk = rel.localKey;
|
|
1263
|
-
|
|
1264
|
-
|
|
1655
|
+
let subQuery = db.selectFrom(relatedTable).select(sql`1`.as("dummy")).whereRef(`${relatedTable}.${fk}`, "=", `${def.table}.${lk}`);
|
|
1656
|
+
if (callback) subQuery = callback(subQuery) ?? subQuery;
|
|
1657
|
+
qb = qb.where(sql`NOT EXISTS ${subQuery}`);
|
|
1265
1658
|
hasEffectiveWhere = true;
|
|
1266
1659
|
return self;
|
|
1267
1660
|
},
|
|
@@ -1357,6 +1750,44 @@ function createQueryBuilder(def, peta) {
|
|
|
1357
1750
|
return self;
|
|
1358
1751
|
}
|
|
1359
1752
|
//#endregion
|
|
1753
|
+
//#region src/model/computed.ts
|
|
1754
|
+
var computed_exports = /* @__PURE__ */ __exportAll({
|
|
1755
|
+
applyComputedColumnsAsync: () => applyComputedColumnsAsync,
|
|
1756
|
+
getComputedConfig: () => getComputedConfig,
|
|
1757
|
+
setComputedConfig: () => setComputedConfig
|
|
1758
|
+
});
|
|
1759
|
+
const computedConfigs = /* @__PURE__ */ new WeakMap();
|
|
1760
|
+
function setComputedConfig(def, config) {
|
|
1761
|
+
computedConfigs.set(def, config);
|
|
1762
|
+
}
|
|
1763
|
+
function getComputedConfig(def) {
|
|
1764
|
+
return computedConfigs.get(def);
|
|
1765
|
+
}
|
|
1766
|
+
/**
|
|
1767
|
+
* Apply computed columns to a set of loaded records.
|
|
1768
|
+
* Handles SQL, runtime, and batch computed columns.
|
|
1769
|
+
*/
|
|
1770
|
+
/** Resolve a ComputedConfig entry (lazy function or plain object). */
|
|
1771
|
+
function resolveComputedColumn(entry) {
|
|
1772
|
+
return typeof entry === "function" ? entry() : entry;
|
|
1773
|
+
}
|
|
1774
|
+
/**
|
|
1775
|
+
* Apply computed columns and return a promise (for async batch computes).
|
|
1776
|
+
*/
|
|
1777
|
+
async function applyComputedColumnsAsync(records, computedConfig, selectedColumns) {
|
|
1778
|
+
if (records.length === 0) return;
|
|
1779
|
+
const relevant = Object.entries(computedConfig).filter(([name]) => !selectedColumns || selectedColumns.includes(name));
|
|
1780
|
+
const batchDefs = relevant.filter(([, c]) => resolveComputedColumn(c).type === "batch");
|
|
1781
|
+
for (const [name, col] of batchDefs) if (col.batchCompute) {
|
|
1782
|
+
const values = await col.batchCompute(records);
|
|
1783
|
+
for (let i = 0; i < records.length && i < values.length; i++) records[i].set(name, values[i]);
|
|
1784
|
+
}
|
|
1785
|
+
for (const record of records) for (const [name, c] of relevant) {
|
|
1786
|
+
const col = resolveComputedColumn(c);
|
|
1787
|
+
if (col.type === "runtime" && col.compute) record.set(name, col.compute(record));
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
//#endregion
|
|
1360
1791
|
//#region src/model/scopes.ts
|
|
1361
1792
|
const globalScopes = /* @__PURE__ */ new WeakMap();
|
|
1362
1793
|
function addScope(def, name, callback) {
|
|
@@ -1396,16 +1827,41 @@ function defineModel(table, config) {
|
|
|
1396
1827
|
return this.query().first();
|
|
1397
1828
|
},
|
|
1398
1829
|
async create(data) {
|
|
1399
|
-
return (await
|
|
1830
|
+
return (await Promise.resolve().then(() => save_exports)).insertModel(def, data);
|
|
1400
1831
|
},
|
|
1401
1832
|
async insert(data) {
|
|
1402
|
-
return (await
|
|
1833
|
+
return (await Promise.resolve().then(() => save_exports)).insertModel(def, data);
|
|
1834
|
+
},
|
|
1835
|
+
upsert(data) {
|
|
1836
|
+
return this.query().all().upsert(data);
|
|
1403
1837
|
},
|
|
1404
1838
|
async insertMany(dataArray) {
|
|
1405
|
-
return (await
|
|
1839
|
+
return (await Promise.resolve().then(() => save_exports)).insertManyModel(def, dataArray);
|
|
1840
|
+
},
|
|
1841
|
+
async updateMany(data, where) {
|
|
1842
|
+
let qb = this.query().all();
|
|
1843
|
+
if (where.length > 0) {
|
|
1844
|
+
const keys = Object.keys(where[0]);
|
|
1845
|
+
for (const key of keys) {
|
|
1846
|
+
const values = where.map((c) => c[key]);
|
|
1847
|
+
qb = qb.whereIn(key, values);
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
return qb.updateMany(data);
|
|
1851
|
+
},
|
|
1852
|
+
async deleteMany(where) {
|
|
1853
|
+
let qb = this.query().all();
|
|
1854
|
+
if (where.length > 0) {
|
|
1855
|
+
const keys = Object.keys(where[0]);
|
|
1856
|
+
for (const key of keys) {
|
|
1857
|
+
const values = where.map((c) => c[key]);
|
|
1858
|
+
qb = qb.whereIn(key, values);
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
return qb.deleteMany();
|
|
1406
1862
|
},
|
|
1407
1863
|
async update(id, data) {
|
|
1408
|
-
return (await
|
|
1864
|
+
return (await Promise.resolve().then(() => save_exports)).updateModel(def, id, data);
|
|
1409
1865
|
},
|
|
1410
1866
|
async insertGraph(data, options) {
|
|
1411
1867
|
return (await Promise.resolve().then(() => graph_exports)).insertGraph(def, data, options);
|
|
@@ -1413,6 +1869,12 @@ function defineModel(table, config) {
|
|
|
1413
1869
|
async upsertGraph(data, options) {
|
|
1414
1870
|
return (await Promise.resolve().then(() => graph_exports)).upsertGraph(def, data, options);
|
|
1415
1871
|
},
|
|
1872
|
+
async deleteGraph(idOrInstance, options) {
|
|
1873
|
+
const model = typeof idOrInstance === "object" ? idOrInstance : await def.find(idOrInstance);
|
|
1874
|
+
if (!model) return;
|
|
1875
|
+
const { deleteGraph: deleteGraphFn } = await Promise.resolve().then(() => graph_exports);
|
|
1876
|
+
return deleteGraphFn(def, model, options);
|
|
1877
|
+
},
|
|
1416
1878
|
async delete(id) {
|
|
1417
1879
|
const model = await this.findOrFail(id);
|
|
1418
1880
|
await (await Promise.resolve().then(() => delete_exports)).deleteModel(def, model);
|
|
@@ -1453,39 +1915,19 @@ function defineModel(table, config) {
|
|
|
1453
1915
|
beforeUpdate(callback) {
|
|
1454
1916
|
return addStaticHook(def, "beforeUpdate", callback);
|
|
1455
1917
|
},
|
|
1456
|
-
afterUpdate(callback) {
|
|
1457
|
-
return addStaticHook(def, "afterUpdate", callback);
|
|
1458
|
-
},
|
|
1459
|
-
beforeCreate(callback) {
|
|
1460
|
-
return addStaticHook(def, "beforeCreate", callback);
|
|
1461
|
-
},
|
|
1462
|
-
afterCreate(callback) {
|
|
1463
|
-
return addStaticHook(def, "afterCreate", callback);
|
|
1464
|
-
},
|
|
1465
|
-
beforeFind(callback) {
|
|
1466
|
-
return addStaticHook(def, "beforeFind", callback);
|
|
1467
|
-
},
|
|
1468
|
-
afterFind(callback) {
|
|
1469
|
-
return addStaticHook(def, "afterFind", callback);
|
|
1470
|
-
},
|
|
1471
1918
|
_init(orm) {
|
|
1472
1919
|
def._orm = orm;
|
|
1473
|
-
setConfig
|
|
1474
|
-
Promise.resolve().then(() => serialize_exports).then((mod) => mod.setConfig?.(def, config));
|
|
1920
|
+
setConfig(def, config);
|
|
1475
1921
|
}
|
|
1476
1922
|
};
|
|
1477
|
-
setConfig
|
|
1478
|
-
|
|
1479
|
-
if (config.computed) Promise.resolve().then(() => computed_exports).then((mod) => mod.setComputedConfig?.(def, config.computed));
|
|
1923
|
+
setConfig(def, config);
|
|
1924
|
+
if (config.computed) setComputedConfig(def, config.computed);
|
|
1480
1925
|
def.registerTimestamps = (createdAtCol, updatedAtCol) => {
|
|
1481
1926
|
registerTimestampsFor(def, createdAtCol, updatedAtCol);
|
|
1482
1927
|
};
|
|
1483
1928
|
def.registerSoftDeletes = (deletedAtCol) => {
|
|
1484
1929
|
registerSoftDeletesFor(def, deletedAtCol);
|
|
1485
1930
|
};
|
|
1486
|
-
def.discover = async () => {
|
|
1487
|
-
throw new Error("discover() not yet implemented in v2");
|
|
1488
|
-
};
|
|
1489
1931
|
return def;
|
|
1490
1932
|
}
|
|
1491
1933
|
//#endregion
|
|
@@ -1532,44 +1974,6 @@ var Attribute = class Attribute {
|
|
|
1532
1974
|
}
|
|
1533
1975
|
};
|
|
1534
1976
|
//#endregion
|
|
1535
|
-
//#region src/model/computed.ts
|
|
1536
|
-
var computed_exports = /* @__PURE__ */ __exportAll({
|
|
1537
|
-
applyComputedColumnsAsync: () => applyComputedColumnsAsync,
|
|
1538
|
-
getComputedConfig: () => getComputedConfig,
|
|
1539
|
-
setComputedConfig: () => setComputedConfig
|
|
1540
|
-
});
|
|
1541
|
-
const computedConfigs = /* @__PURE__ */ new WeakMap();
|
|
1542
|
-
function setComputedConfig(def, config) {
|
|
1543
|
-
computedConfigs.set(def, config);
|
|
1544
|
-
}
|
|
1545
|
-
function getComputedConfig(def) {
|
|
1546
|
-
return computedConfigs.get(def);
|
|
1547
|
-
}
|
|
1548
|
-
/**
|
|
1549
|
-
* Apply computed columns to a set of loaded records.
|
|
1550
|
-
* Handles SQL, runtime, and batch computed columns.
|
|
1551
|
-
*/
|
|
1552
|
-
/** Resolve a ComputedConfig entry (lazy function or plain object). */
|
|
1553
|
-
function resolveComputedColumn(entry) {
|
|
1554
|
-
return typeof entry === "function" ? entry() : entry;
|
|
1555
|
-
}
|
|
1556
|
-
/**
|
|
1557
|
-
* Apply computed columns and return a promise (for async batch computes).
|
|
1558
|
-
*/
|
|
1559
|
-
async function applyComputedColumnsAsync(records, computedConfig, selectedColumns) {
|
|
1560
|
-
if (records.length === 0) return;
|
|
1561
|
-
const relevant = Object.entries(computedConfig).filter(([name]) => !selectedColumns || selectedColumns.includes(name));
|
|
1562
|
-
const batchDefs = relevant.filter(([, c]) => resolveComputedColumn(c).type === "batch");
|
|
1563
|
-
for (const [name, col] of batchDefs) if (col.batchCompute) {
|
|
1564
|
-
const values = await col.batchCompute(records);
|
|
1565
|
-
for (let i = 0; i < records.length && i < values.length; i++) records[i].set(name, values[i]);
|
|
1566
|
-
}
|
|
1567
|
-
for (const record of records) for (const [name, c] of relevant) {
|
|
1568
|
-
const col = resolveComputedColumn(c);
|
|
1569
|
-
if (col.type === "runtime" && col.compute) record.set(name, col.compute(record));
|
|
1570
|
-
}
|
|
1571
|
-
}
|
|
1572
|
-
//#endregion
|
|
1573
1977
|
//#region src/model/delete.ts
|
|
1574
1978
|
var delete_exports = /* @__PURE__ */ __exportAll({
|
|
1575
1979
|
deleteModel: () => deleteModel,
|
|
@@ -1577,20 +1981,8 @@ var delete_exports = /* @__PURE__ */ __exportAll({
|
|
|
1577
1981
|
restoreModel: () => restoreModel,
|
|
1578
1982
|
trashedModel: () => trashedModel
|
|
1579
1983
|
});
|
|
1580
|
-
function getTable(def) {
|
|
1581
|
-
return def.table;
|
|
1582
|
-
}
|
|
1583
|
-
function getDb(def) {
|
|
1584
|
-
if (!def._orm) throw new ModelNotRegisteredError(def.name);
|
|
1585
|
-
return def._orm.kysely;
|
|
1586
|
-
}
|
|
1587
|
-
function getPrimaryKeyColumn$1(def) {
|
|
1588
|
-
const cols = def.columns;
|
|
1589
|
-
for (const [name, col] of Object.entries(cols)) if (col.isPrimaryKey) return name;
|
|
1590
|
-
return "id";
|
|
1591
|
-
}
|
|
1592
1984
|
async function deleteModel(def, model) {
|
|
1593
|
-
const pk = getPrimaryKeyColumn
|
|
1985
|
+
const pk = getPrimaryKeyColumn(def);
|
|
1594
1986
|
const pkValue = model.get(pk);
|
|
1595
1987
|
if (pkValue == null) throw new DatabaseError("Cannot delete model without primary key", "MISSING_ID");
|
|
1596
1988
|
const hm = getHooksFor(def);
|
|
@@ -1599,9 +1991,9 @@ async function deleteModel(def, model) {
|
|
|
1599
1991
|
const config = getSoftDeleteConfig(def);
|
|
1600
1992
|
const db = getDb(def);
|
|
1601
1993
|
try {
|
|
1602
|
-
await db.updateTable(
|
|
1994
|
+
await db.updateTable(def.table).set({ [config.column]: (/* @__PURE__ */ new Date()).toISOString() }).where(pk, "=", pkValue).execute();
|
|
1603
1995
|
} catch (e) {
|
|
1604
|
-
throw normalizeError(e,
|
|
1996
|
+
throw normalizeError(e, def.table);
|
|
1605
1997
|
}
|
|
1606
1998
|
model.set(config.column, (/* @__PURE__ */ new Date()).toISOString());
|
|
1607
1999
|
await hm.trigger("afterDelete", model);
|
|
@@ -1609,31 +2001,31 @@ async function deleteModel(def, model) {
|
|
|
1609
2001
|
await hm.trigger("beforeDelete", model);
|
|
1610
2002
|
const db = getDb(def);
|
|
1611
2003
|
try {
|
|
1612
|
-
await db.deleteFrom(
|
|
2004
|
+
await db.deleteFrom(def.table).where(pk, "=", pkValue).execute();
|
|
1613
2005
|
} catch (e) {
|
|
1614
|
-
throw normalizeError(e,
|
|
2006
|
+
throw normalizeError(e, def.table);
|
|
1615
2007
|
}
|
|
1616
2008
|
setExists(model, false);
|
|
1617
2009
|
await hm.trigger("afterDelete", model);
|
|
1618
2010
|
}
|
|
1619
2011
|
}
|
|
1620
2012
|
async function forceDeleteModel(def, model) {
|
|
1621
|
-
const pk = getPrimaryKeyColumn
|
|
2013
|
+
const pk = getPrimaryKeyColumn(def);
|
|
1622
2014
|
const pkValue = model.get(pk);
|
|
1623
2015
|
if (pkValue == null) throw new DatabaseError("Cannot delete model without primary key", "MISSING_ID");
|
|
1624
2016
|
const hm = getHooksFor(def);
|
|
1625
2017
|
await hm.trigger("beforeForceDelete", model);
|
|
1626
2018
|
const db = getDb(def);
|
|
1627
2019
|
try {
|
|
1628
|
-
await db.deleteFrom(
|
|
2020
|
+
await db.deleteFrom(def.table).where(pk, "=", pkValue).execute();
|
|
1629
2021
|
} catch (e) {
|
|
1630
|
-
throw normalizeError(e,
|
|
2022
|
+
throw normalizeError(e, def.table);
|
|
1631
2023
|
}
|
|
1632
2024
|
setExists(model, false);
|
|
1633
2025
|
await hm.trigger("afterForceDelete", model);
|
|
1634
2026
|
}
|
|
1635
2027
|
async function restoreModel(def, model) {
|
|
1636
|
-
const pk = getPrimaryKeyColumn
|
|
2028
|
+
const pk = getPrimaryKeyColumn(def);
|
|
1637
2029
|
const pkValue = model.get(pk);
|
|
1638
2030
|
if (pkValue == null) return;
|
|
1639
2031
|
if (!hasSoftDelete(def)) return;
|
|
@@ -1642,9 +2034,9 @@ async function restoreModel(def, model) {
|
|
|
1642
2034
|
const config = getSoftDeleteConfig(def);
|
|
1643
2035
|
const db = getDb(def);
|
|
1644
2036
|
try {
|
|
1645
|
-
await db.updateTable(
|
|
2037
|
+
await db.updateTable(def.table).set({ [config.column]: null }).where(pk, "=", pkValue).execute();
|
|
1646
2038
|
} catch (e) {
|
|
1647
|
-
throw normalizeError(e,
|
|
2039
|
+
throw normalizeError(e, def.table);
|
|
1648
2040
|
}
|
|
1649
2041
|
model.set(config.column, null);
|
|
1650
2042
|
setExists(model, true);
|
|
@@ -1657,14 +2049,6 @@ function trashedModel(def, model) {
|
|
|
1657
2049
|
}
|
|
1658
2050
|
//#endregion
|
|
1659
2051
|
//#region src/model/relation.ts
|
|
1660
|
-
var relation_exports = /* @__PURE__ */ __exportAll({
|
|
1661
|
-
getModelDef: () => getModelDef,
|
|
1662
|
-
loadModelRelations: () => loadModelRelations
|
|
1663
|
-
});
|
|
1664
|
-
const modelDefs = /* @__PURE__ */ new WeakMap();
|
|
1665
|
-
function getModelDef(instance) {
|
|
1666
|
-
return modelDefs.get(instance);
|
|
1667
|
-
}
|
|
1668
2052
|
async function loadModelRelations(model, def, ...relations) {
|
|
1669
2053
|
const { EagerLoader } = await Promise.resolve().then(() => eager_exports);
|
|
1670
2054
|
const loader = new EagerLoader();
|
|
@@ -1672,18 +2056,13 @@ async function loadModelRelations(model, def, ...relations) {
|
|
|
1672
2056
|
}
|
|
1673
2057
|
//#endregion
|
|
1674
2058
|
//#region src/model/serialize.ts
|
|
1675
|
-
var serialize_exports = /* @__PURE__ */ __exportAll({
|
|
1676
|
-
getConfig: () => getConfig,
|
|
1677
|
-
modelToJSON: () => modelToJSON,
|
|
1678
|
-
setConfig: () => setConfig
|
|
1679
|
-
});
|
|
1680
2059
|
const VISITED = /* @__PURE__ */ new WeakSet();
|
|
1681
2060
|
/** Type guard: checks if a value looks like a SerializableModel. */
|
|
1682
2061
|
function isSerializableModel(value) {
|
|
1683
2062
|
return value !== null && typeof value === "object" && typeof value.$toJSON === "function";
|
|
1684
2063
|
}
|
|
1685
2064
|
function modelToJSON(def, model) {
|
|
1686
|
-
const config = getConfig
|
|
2065
|
+
const config = getConfig(def);
|
|
1687
2066
|
const state = getState(model);
|
|
1688
2067
|
const result = {};
|
|
1689
2068
|
const attributes = state.attributes;
|
|
@@ -1730,13 +2109,6 @@ function modelToJSON(def, model) {
|
|
|
1730
2109
|
}
|
|
1731
2110
|
return result;
|
|
1732
2111
|
}
|
|
1733
|
-
const configMap = /* @__PURE__ */ new WeakMap();
|
|
1734
|
-
function setConfig(def, config) {
|
|
1735
|
-
configMap.set(def, config);
|
|
1736
|
-
}
|
|
1737
|
-
function getConfig(def) {
|
|
1738
|
-
return configMap.get(def);
|
|
1739
|
-
}
|
|
1740
2112
|
//#endregion
|
|
1741
2113
|
//#region src/relations/related-query.ts
|
|
1742
2114
|
/**
|
|
@@ -1895,9 +2267,14 @@ initRuntime({
|
|
|
1895
2267
|
/**
|
|
1896
2268
|
* Create an ORM instance — the central registry that wires Kysely to model definitions.
|
|
1897
2269
|
* Replaces createPeta() from v0.x.
|
|
2270
|
+
*
|
|
2271
|
+
* Pass either `dialect` (to auto-create a Kysely instance) or `kysely` (to reuse one).
|
|
2272
|
+
* Passing a pre-existing Kysely instance avoids creating a second connection for
|
|
2273
|
+
* migration runners or other tools that already have their own Kysely.
|
|
1898
2274
|
*/
|
|
1899
2275
|
function createORM(config) {
|
|
1900
|
-
|
|
2276
|
+
if (!config.dialect && !config.kysely) throw new Error("createORM: provide either `dialect` (to create a Kysely instance) or `kysely` (to reuse one)");
|
|
2277
|
+
const kysely = config.kysely ?? new Kysely({ dialect: config.dialect });
|
|
1901
2278
|
const modelMap = /* @__PURE__ */ new Map();
|
|
1902
2279
|
const orm = {
|
|
1903
2280
|
kysely,
|
|
@@ -1916,13 +2293,47 @@ function createORM(config) {
|
|
|
1916
2293
|
await kysely.destroy();
|
|
1917
2294
|
},
|
|
1918
2295
|
async transaction(fn) {
|
|
1919
|
-
return kysely.transaction().execute((trx) =>
|
|
2296
|
+
return kysely.transaction().execute(async (trx) => {
|
|
2297
|
+
this._trx = trx;
|
|
2298
|
+
try {
|
|
2299
|
+
return await fn(this);
|
|
2300
|
+
} finally {
|
|
2301
|
+
delete this._trx;
|
|
2302
|
+
}
|
|
2303
|
+
});
|
|
1920
2304
|
},
|
|
1921
2305
|
get models() {
|
|
1922
2306
|
return modelMap;
|
|
1923
2307
|
},
|
|
1924
2308
|
getModel(name) {
|
|
1925
2309
|
return modelMap.get(name);
|
|
2310
|
+
},
|
|
2311
|
+
async discover(pattern) {
|
|
2312
|
+
const entries = [];
|
|
2313
|
+
const glob = new Bun.Glob("**/*.ts");
|
|
2314
|
+
const starIdx = pattern.lastIndexOf("*");
|
|
2315
|
+
const baseDir = starIdx >= 0 ? pattern.slice(0, pattern.lastIndexOf("/", starIdx)) : pattern;
|
|
2316
|
+
try {
|
|
2317
|
+
for await (const match of glob.scan({
|
|
2318
|
+
cwd: baseDir,
|
|
2319
|
+
absolute: true,
|
|
2320
|
+
onlyFiles: true
|
|
2321
|
+
})) entries.push(match);
|
|
2322
|
+
} catch {}
|
|
2323
|
+
if (entries.length === 0) throw new Error(`discover: no files matched pattern "${pattern}"`);
|
|
2324
|
+
const models = [];
|
|
2325
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2326
|
+
for (const fp of entries) {
|
|
2327
|
+
const mod = await import(pathToFileURL(fp).href);
|
|
2328
|
+
for (const val of Object.values(mod)) if (val && typeof val === "object" && "columns" in val && "table" in val && typeof val.table === "string" && val.table.length > 0) {
|
|
2329
|
+
const def = val;
|
|
2330
|
+
if (!seen.has(def.table)) {
|
|
2331
|
+
seen.add(def.table);
|
|
2332
|
+
models.push(def);
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
return models;
|
|
1926
2337
|
}
|
|
1927
2338
|
};
|
|
1928
2339
|
if (config.models) for (const [_name, model] of Object.entries(config.models)) orm.register(model);
|
|
@@ -1968,12 +2379,6 @@ function createPaginator(items, total, perPage, currentPage) {
|
|
|
1968
2379
|
get onLastPage() {
|
|
1969
2380
|
return currentPage >= lastPage;
|
|
1970
2381
|
},
|
|
1971
|
-
get count() {
|
|
1972
|
-
return items.length;
|
|
1973
|
-
},
|
|
1974
|
-
map(fn) {
|
|
1975
|
-
return items.map(fn);
|
|
1976
|
-
},
|
|
1977
2382
|
toJSON() {
|
|
1978
2383
|
return {
|
|
1979
2384
|
data: collection.toJSON(),
|
|
@@ -1993,11 +2398,6 @@ function createPaginator(items, total, perPage, currentPage) {
|
|
|
1993
2398
|
}
|
|
1994
2399
|
//#endregion
|
|
1995
2400
|
//#region src/plugins/soft-deletes.ts
|
|
1996
|
-
let _hooksMod = null;
|
|
1997
|
-
async function getHooksMod() {
|
|
1998
|
-
if (!_hooksMod) _hooksMod = await import("./hooks-BD0xy7uw.mjs").then((n) => n.i);
|
|
1999
|
-
return _hooksMod;
|
|
2000
|
-
}
|
|
2001
2401
|
/**
|
|
2002
2402
|
* Plugin that enables soft-delete behavior on a model.
|
|
2003
2403
|
* Sets `deletedAt` on delete, automatically filters out deleted records.
|
|
@@ -2011,24 +2411,20 @@ async function getHooksMod() {
|
|
|
2011
2411
|
function softDeletes(opts) {
|
|
2012
2412
|
const column = opts?.column ?? "deletedAt";
|
|
2013
2413
|
return (def) => {
|
|
2014
|
-
|
|
2414
|
+
registerSoftDeletesFor(def, column);
|
|
2015
2415
|
def.on("beforeDelete", async (model) => {
|
|
2016
2416
|
const pk = getPrimaryKeyColumn(def);
|
|
2017
2417
|
const pkValue = model.get(pk);
|
|
2018
2418
|
if (pkValue == null) return;
|
|
2019
|
-
const db = def
|
|
2020
|
-
if (!db) return;
|
|
2419
|
+
const db = getDb(def);
|
|
2021
2420
|
try {
|
|
2022
2421
|
await db.updateTable(def.table).set({ [column]: (/* @__PURE__ */ new Date()).toISOString() }).where(pk, "=", pkValue).execute();
|
|
2023
|
-
} catch {
|
|
2422
|
+
} catch (e) {
|
|
2423
|
+
if (!isUniqueConstraintError(e)) throw normalizeError(e, def.table);
|
|
2424
|
+
}
|
|
2024
2425
|
});
|
|
2025
2426
|
};
|
|
2026
2427
|
}
|
|
2027
|
-
function getPrimaryKeyColumn(def) {
|
|
2028
|
-
const cols = def.columns;
|
|
2029
|
-
for (const [name, col] of Object.entries(cols)) if (col.isPrimaryKey) return name;
|
|
2030
|
-
return "id";
|
|
2031
|
-
}
|
|
2032
2428
|
//#endregion
|
|
2033
2429
|
//#region src/plugins/timestamps.ts
|
|
2034
2430
|
/**
|
|
@@ -2076,34 +2472,14 @@ function ulid() {
|
|
|
2076
2472
|
}
|
|
2077
2473
|
//#endregion
|
|
2078
2474
|
//#region src/relations/has-many.ts
|
|
2079
|
-
const THUNK_CACHE$2 = /* @__PURE__ */ new WeakMap();
|
|
2080
|
-
function resolveThunk$2(thunk) {
|
|
2081
|
-
let cls = THUNK_CACHE$2.get(thunk);
|
|
2082
|
-
if (!cls) {
|
|
2083
|
-
cls = thunk();
|
|
2084
|
-
THUNK_CACHE$2.set(thunk, cls);
|
|
2085
|
-
}
|
|
2086
|
-
return cls;
|
|
2087
|
-
}
|
|
2088
2475
|
function guessForeignKey(modelDef) {
|
|
2089
2476
|
const table = modelDef.table;
|
|
2090
2477
|
return `${table.endsWith("s") ? table.slice(0, -1) : table}Id`;
|
|
2091
2478
|
}
|
|
2092
|
-
function groupByArray$1(items, key) {
|
|
2093
|
-
const result = {};
|
|
2094
|
-
for (const item of items) {
|
|
2095
|
-
const v = item.get(key);
|
|
2096
|
-
if (v == null) continue;
|
|
2097
|
-
const k = String(v);
|
|
2098
|
-
if (!result[k]) result[k] = [];
|
|
2099
|
-
result[k].push(item);
|
|
2100
|
-
}
|
|
2101
|
-
return result;
|
|
2102
|
-
}
|
|
2103
2479
|
function hasMany(relatedThunk, options = {}) {
|
|
2104
2480
|
let _related;
|
|
2105
2481
|
function getRelated() {
|
|
2106
|
-
if (!_related) _related = resolveThunk
|
|
2482
|
+
if (!_related) _related = resolveThunk(relatedThunk) ?? void 0;
|
|
2107
2483
|
if (!_related) throw new Error(`Cannot resolve hasMany relation — related model not found`);
|
|
2108
2484
|
return _related;
|
|
2109
2485
|
}
|
|
@@ -2134,7 +2510,7 @@ function hasMany(relatedThunk, options = {}) {
|
|
|
2134
2510
|
else qb.where(foreignKey, "=", -1);
|
|
2135
2511
|
},
|
|
2136
2512
|
match(models, results, relationName) {
|
|
2137
|
-
const grouped = groupByArray
|
|
2513
|
+
const grouped = groupByArray(results, foreignKey);
|
|
2138
2514
|
for (const model of models) {
|
|
2139
2515
|
const key = String(model.get(localKey));
|
|
2140
2516
|
model.$setRelation(relationName, grouped[key] ?? []);
|
|
@@ -2162,7 +2538,7 @@ function hasOne(relatedThunk, options = {}) {
|
|
|
2162
2538
|
Object.defineProperty(result, key, desc);
|
|
2163
2539
|
}
|
|
2164
2540
|
result.match = function match(models, results, relationName) {
|
|
2165
|
-
const grouped = groupByArray
|
|
2541
|
+
const grouped = groupByArray(results, base.foreignKey);
|
|
2166
2542
|
for (const model of models) {
|
|
2167
2543
|
const key = String(model.get(base.localKey));
|
|
2168
2544
|
model.$setRelation(relationName, (grouped[key] ?? [])[0] ?? null);
|
|
@@ -2174,7 +2550,7 @@ function hasOne(relatedThunk, options = {}) {
|
|
|
2174
2550
|
return result;
|
|
2175
2551
|
}
|
|
2176
2552
|
function belongsTo(relatedThunk, options = {}) {
|
|
2177
|
-
const related = resolveThunk
|
|
2553
|
+
const related = resolveThunk(relatedThunk);
|
|
2178
2554
|
const foreignKey = options.foreignKey ?? guessForeignKey(related);
|
|
2179
2555
|
const localKey = options.localKey ?? "id";
|
|
2180
2556
|
return {
|
|
@@ -2221,24 +2597,15 @@ function belongsTo(relatedThunk, options = {}) {
|
|
|
2221
2597
|
}
|
|
2222
2598
|
//#endregion
|
|
2223
2599
|
//#region src/relations/many-to-many.ts
|
|
2224
|
-
const THUNK_CACHE$1 = /* @__PURE__ */ new WeakMap();
|
|
2225
2600
|
/** Stores pivot data for many-to-many relation results (instead of attaching to the model). */
|
|
2226
2601
|
const pivotData = /* @__PURE__ */ new WeakMap();
|
|
2227
|
-
function resolveThunk$1(thunk) {
|
|
2228
|
-
let cls = THUNK_CACHE$1.get(thunk);
|
|
2229
|
-
if (!cls) {
|
|
2230
|
-
cls = thunk();
|
|
2231
|
-
THUNK_CACHE$1.set(thunk, cls);
|
|
2232
|
-
}
|
|
2233
|
-
return cls;
|
|
2234
|
-
}
|
|
2235
2602
|
function snakeCase(str) {
|
|
2236
2603
|
return str.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`).replace(/^_/, "");
|
|
2237
2604
|
}
|
|
2238
2605
|
function manyToMany(relatedThunk, options) {
|
|
2239
2606
|
let _related;
|
|
2240
2607
|
function getRelated() {
|
|
2241
|
-
if (!_related) _related = resolveThunk
|
|
2608
|
+
if (!_related) _related = resolveThunk(relatedThunk) ?? void 0;
|
|
2242
2609
|
if (!_related) throw new Error(`Cannot resolve manyToMany relation — related model not found`);
|
|
2243
2610
|
return _related;
|
|
2244
2611
|
}
|
|
@@ -2324,11 +2691,11 @@ function manyToMany(relatedThunk, options) {
|
|
|
2324
2691
|
};
|
|
2325
2692
|
}
|
|
2326
2693
|
function hasManyThrough(relatedThunk, throughThunk, options = {}) {
|
|
2327
|
-
const related = resolveThunk
|
|
2328
|
-
const through = resolveThunk
|
|
2694
|
+
const related = resolveThunk(relatedThunk);
|
|
2695
|
+
const through = resolveThunk(throughThunk);
|
|
2329
2696
|
const foreignKey = options.foreignKey ?? `${snakeCase(through.table)}Id`;
|
|
2330
2697
|
const localKey = options.localKey ?? "id";
|
|
2331
|
-
const throughForeignKey = options.throughForeignKey ?? `${snakeCase(resolveThunk
|
|
2698
|
+
const throughForeignKey = options.throughForeignKey ?? `${snakeCase(resolveThunk(relatedThunk).table)}Id`;
|
|
2332
2699
|
const throughLocalKey = options.throughLocalKey ?? "id";
|
|
2333
2700
|
return {
|
|
2334
2701
|
type: "hasManyThrough",
|
|
@@ -2387,26 +2754,6 @@ function hasManyThrough(relatedThunk, throughThunk, options = {}) {
|
|
|
2387
2754
|
}
|
|
2388
2755
|
//#endregion
|
|
2389
2756
|
//#region src/relations/morph.ts
|
|
2390
|
-
function groupByArray(items, key) {
|
|
2391
|
-
const result = {};
|
|
2392
|
-
for (const item of items) {
|
|
2393
|
-
const v = item.get(key);
|
|
2394
|
-
if (v == null) continue;
|
|
2395
|
-
const k = String(v);
|
|
2396
|
-
if (!result[k]) result[k] = [];
|
|
2397
|
-
result[k].push(item);
|
|
2398
|
-
}
|
|
2399
|
-
return result;
|
|
2400
|
-
}
|
|
2401
|
-
const THUNK_CACHE = /* @__PURE__ */ new WeakMap();
|
|
2402
|
-
function resolveThunk(thunk) {
|
|
2403
|
-
let cls = THUNK_CACHE.get(thunk);
|
|
2404
|
-
if (!cls) {
|
|
2405
|
-
cls = thunk();
|
|
2406
|
-
THUNK_CACHE.set(thunk, cls);
|
|
2407
|
-
}
|
|
2408
|
-
return cls;
|
|
2409
|
-
}
|
|
2410
2757
|
/**
|
|
2411
2758
|
* Resolve the related model for a MorphTo relation given a parent instance.
|
|
2412
2759
|
* Looks up the parent's `{name}Type` column value in the relation's morphMap
|
|
@@ -2474,12 +2821,12 @@ function defineMorphTo(options) {
|
|
|
2474
2821
|
*/
|
|
2475
2822
|
query(parent) {
|
|
2476
2823
|
const typeValue = parent.get(morphType);
|
|
2477
|
-
if (!typeValue) throw new Error(`Cannot resolve morphTo "${options.name}": "${morphType}" is null on ${
|
|
2824
|
+
if (!typeValue) throw new Error(`Cannot resolve morphTo "${options.name}": "${morphType}" is null on ${parent.constructor?.name ?? "model"}`);
|
|
2478
2825
|
const thunk = morphMap[typeValue];
|
|
2479
2826
|
if (!thunk) throw new Error(`No model registered for morph type "${typeValue}" in morphTo "${options.name}". Available types: ${Object.keys(morphMap).join(", ") || "(none)"}`);
|
|
2480
2827
|
const relatedDef = resolveThunk(thunk);
|
|
2481
2828
|
const id = parent.get(morphId);
|
|
2482
|
-
if (id == null) throw new Error(`Cannot resolve morphTo "${options.name}": "${morphId}" is null on ${
|
|
2829
|
+
if (id == null) throw new Error(`Cannot resolve morphTo "${options.name}": "${morphId}" is null on ${parent.constructor?.name ?? "model"}`);
|
|
2483
2830
|
return relatedDef.query().where("id", "=", id);
|
|
2484
2831
|
},
|
|
2485
2832
|
addEagerConstraints(_query, _models) {},
|
|
@@ -2577,28 +2924,66 @@ function defineMorphMany(options) {
|
|
|
2577
2924
|
}
|
|
2578
2925
|
};
|
|
2579
2926
|
}
|
|
2927
|
+
//#endregion
|
|
2928
|
+
//#region src/repo/index.ts
|
|
2580
2929
|
/**
|
|
2581
|
-
*
|
|
2930
|
+
* Create a repository — a composable set of chainable query methods.
|
|
2931
|
+
*
|
|
2932
|
+
* ```ts
|
|
2933
|
+
* const userRepo = createRepo(User, {
|
|
2934
|
+
* queryMethods: {
|
|
2935
|
+
* search(q, query: string) {
|
|
2936
|
+
* return q.where('name', 'like', `%${query}%`)
|
|
2937
|
+
* },
|
|
2938
|
+
* },
|
|
2939
|
+
* })
|
|
2940
|
+
* const users = await userRepo.search('john').paginate(1, 20)
|
|
2941
|
+
* ```
|
|
2582
2942
|
*/
|
|
2583
|
-
function
|
|
2584
|
-
const
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2943
|
+
function createRepo(model, methods) {
|
|
2944
|
+
const customMethods = /* @__PURE__ */ new Map();
|
|
2945
|
+
if (methods.queryMethods) for (const [name, fn] of Object.entries(methods.queryMethods)) customMethods.set(name, fn);
|
|
2946
|
+
/**
|
|
2947
|
+
* Wrap a QueryBuilder so custom methods are available for chaining.
|
|
2948
|
+
* Custom methods operate on the CURRENT QB (not a fresh one), so
|
|
2949
|
+
* chaining carries forward previous conditions.
|
|
2950
|
+
*/
|
|
2951
|
+
function wrapQB(qb) {
|
|
2952
|
+
return new Proxy(qb, { get(target, prop) {
|
|
2953
|
+
if (typeof prop === "string") {
|
|
2954
|
+
if (customMethods.has(prop)) {
|
|
2955
|
+
const fn = customMethods.get(prop);
|
|
2956
|
+
return (...args) => {
|
|
2957
|
+
return wrapQB(fn(target, ...args));
|
|
2958
|
+
};
|
|
2959
|
+
}
|
|
2960
|
+
if (methods.methods?.[prop]) return (...args) => methods.methods[prop](...args);
|
|
2596
2961
|
}
|
|
2962
|
+
const val = target[prop];
|
|
2963
|
+
if (typeof val === "function") return function(...args) {
|
|
2964
|
+
const result = val.apply(target, args);
|
|
2965
|
+
return result === target ? wrapQB(result) : result;
|
|
2966
|
+
};
|
|
2967
|
+
return val;
|
|
2968
|
+
} });
|
|
2969
|
+
}
|
|
2970
|
+
return new Proxy({}, { get(_target, prop) {
|
|
2971
|
+
if (typeof prop === "string") {
|
|
2972
|
+
if (customMethods.has(prop)) {
|
|
2973
|
+
const fn = customMethods.get(prop);
|
|
2974
|
+
return (...args) => {
|
|
2975
|
+
return wrapQB(fn(model.query(), ...args));
|
|
2976
|
+
};
|
|
2977
|
+
}
|
|
2978
|
+
if (methods.methods?.[prop]) return (...args) => methods.methods[prop](...args);
|
|
2597
2979
|
}
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
function
|
|
2601
|
-
|
|
2980
|
+
const qb = model.query();
|
|
2981
|
+
const val = qb[prop];
|
|
2982
|
+
if (typeof val === "function") return (...args) => {
|
|
2983
|
+
return wrapQB(val.apply(qb, args));
|
|
2984
|
+
};
|
|
2985
|
+
return val;
|
|
2986
|
+
} });
|
|
2602
2987
|
}
|
|
2603
2988
|
//#endregion
|
|
2604
|
-
export { Attribute, DatabaseError, ModelNotFoundError, ModelNotRegisteredError, RelationNotAllowedError, RelationNotFoundError, ValidationError, belongsTo, createArkTypeSchemaConfig, createCollection, createColumn, createHookManager, createORM, createORM as createPeta, createPaginator, createQueryBuilder, defineModel, defineMorphMany,
|
|
2989
|
+
export { Attribute, DatabaseError, ModelNotFoundError, ModelNotRegisteredError, RelationNotAllowedError, RelationNotFoundError, ValidationError, belongsTo, createArkTypeSchemaConfig, createCollection, createColumn, createColumnTypes, createDb, createHookManager, createORM, createORM as createPeta, createPaginator, createQueryBuilder, createRepo, defineModel, defineMorphMany, defineMorphTo, deleteGraph, hasMany, hasManyThrough, hasOne, manyToMany, eager_exports as n, normalizeError, petaMiddleware, petaPlugin, resolveMorphRelation, softDeletes, t, timestamps, ulid };
|