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/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-PFmrQHyM.mjs";
3
- import { a as ModelNotRegisteredError, c as ValidationError, i as ModelNotFoundError, n as normalizeError, o as RelationNotAllowedError, r as DatabaseError, s as RelationNotFoundError } from "./errors-sfFJolfu.mjs";
4
- import { a as registerSoftDeletesFor, n as getSoftDeleteConfig, o as registerTimestampsFor, r as hasSoftDelete, s as createHookManager, t as getHooksFor } from "./hooks-BD0xy7uw.mjs";
5
- import { a as castValue, r as initRuntime, t as createInstance } from "./factory-BBvIMQuc.mjs";
6
- import { a as getRawRelations, d as setExists, o as getState } from "./state-LtlHp6XV.mjs";
7
- import { a as setConfig$1, n as reloadModel, r as saveModel, t as getConfig$1 } from "./save-D5UKXvqC.mjs";
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([...extractProblems(result).entries()].map(([path, msgs]) => `${path}: ${msgs.join(", ")}`).join("; "));
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([...extractProblems(e.arkErrors).entries()].map(([path, msgs]) => `${path}: ${msgs.join(", ")}`).join("; "));
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
- const val = c.args[0];
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
- function t(config) {
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 (isMorphRelation(relation)) 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.`);
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/morph.ts
292
- /** Whether this relation is a MorphTo (polymorphic belongsTo) */
293
- function isMorphToRelation(relation) {
294
- return relation._morphMap !== void 0;
295
- }
296
- /** Whether this relation is a MorphMany or MorphOne (polymorphic hasMany/hasOne) */
297
- function isMorphManyRelation(relation) {
298
- return relation._morphType !== void 0 && !isMorphToRelation(relation);
299
- }
300
- /** Get the morph type column name (e.g. "commentableType") from a morph relation */
301
- function getMorphType(relation) {
302
- return relation._morphType;
303
- }
304
- /** Get the morph type value (e.g. "morph_posts") from a MorphMany/MorphOne relation */
305
- function getMorphTypeValue(relation) {
306
- return relation._morphTypeValue;
307
- }
308
- /** Get the morph id column name (e.g. "commentableId") from a morph relation */
309
- function getMorphId(relation) {
310
- return relation._morphId;
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
- return cls;
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$2(def);
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 (isMorphToRelation(relation)) return processMorphTo(relation, op, options, context, path, parentColumnData);
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 = getMorphType(relation);
560
- const morphId = getMorphId(relation);
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$3(thunk);
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 (isMorphManyRelation(relation)) {
613
- const typeCol = getMorphType(relation);
614
- const typeVal = getMorphTypeValue(relation);
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$1(relatedDef);
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$2(def);
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$2(relatedDef);
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$2(relatedDef)] ?? item.id;
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$2(relatedDef));
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 (isMorphManyRelation(relation)) {
775
- const typeCol = getMorphType(relation);
776
- const typeVal = getMorphTypeValue(relation);
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$1(relatedDef);
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[getPrimaryKeyColumn$2(relatedDef)] ?? item.id;
1217
+ const itemId = item[pkCol] ?? item.id;
817
1218
  if (itemId != null) {
818
1219
  incomingIds.add(itemId);
819
- if (!isRelPathAllowed(relNameFromPath(path), options.noUpdate)) {
820
- const existing = await relatedDef.find(itemId);
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
- const targetId = await resolveTargetId(relatedDef, target);
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-PFmrQHyM.mjs").then((n) => n.t);
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
- const alias = `${relation}_count`;
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
- const alias = `${relation}_sum_${column}`;
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.numUpdatedRows ?? 0);
1548
+ return Number(result[0]?.numUpdatedRows ?? 0);
1160
1549
  } catch (e) {
1161
- const { normalizeError } = await import("./errors-sfFJolfu.mjs").then((n) => n.t);
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.numDeletedRows ?? 0);
1591
+ numDeleted = Number(result[0]?.numDeletedRows ?? 0);
1203
1592
  } catch (e) {
1204
- const { normalizeError } = await import("./errors-sfFJolfu.mjs").then((n) => n.t);
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 = rawSql(`EXISTS (SELECT 1 FROM ${relatedTable} WHERE ${relatedTable}.${fk} = ${def.table}.${lk})`);
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, _callback) {
1255
- return self.has(relationName);
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, _callback) {
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
- const notExistsExpr = rawSql(`NOT EXISTS (SELECT 1 FROM ${relatedTable} WHERE ${relatedTable}.${fk} = ${def.table}.${lk})`);
1264
- qb = qb.where(notExistsExpr);
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 import("./save-D5UKXvqC.mjs").then((n) => n.i)).insertModel(def, data);
1830
+ return (await Promise.resolve().then(() => save_exports)).insertModel(def, data);
1400
1831
  },
1401
1832
  async insert(data) {
1402
- return (await import("./save-D5UKXvqC.mjs").then((n) => n.i)).insertModel(def, data);
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 import("./save-D5UKXvqC.mjs").then((n) => n.i)).insertManyModel(def, dataArray);
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 import("./save-D5UKXvqC.mjs").then((n) => n.i)).updateModel(def, id, data);
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$1(def, config);
1474
- Promise.resolve().then(() => serialize_exports).then((mod) => mod.setConfig?.(def, config));
1920
+ setConfig(def, config);
1475
1921
  }
1476
1922
  };
1477
- setConfig$1(def, config);
1478
- Promise.resolve().then(() => serialize_exports).then((mod) => mod.setConfig?.(def, config));
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$1(def);
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(getTable(def)).set({ [config.column]: (/* @__PURE__ */ new Date()).toISOString() }).where(pk, "=", pkValue).execute();
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, getTable(def));
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(getTable(def)).where(pk, "=", pkValue).execute();
2004
+ await db.deleteFrom(def.table).where(pk, "=", pkValue).execute();
1613
2005
  } catch (e) {
1614
- throw normalizeError(e, getTable(def));
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$1(def);
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(getTable(def)).where(pk, "=", pkValue).execute();
2020
+ await db.deleteFrom(def.table).where(pk, "=", pkValue).execute();
1629
2021
  } catch (e) {
1630
- throw normalizeError(e, getTable(def));
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$1(def);
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(getTable(def)).set({ [config.column]: null }).where(pk, "=", pkValue).execute();
2037
+ await db.updateTable(def.table).set({ [config.column]: null }).where(pk, "=", pkValue).execute();
1646
2038
  } catch (e) {
1647
- throw normalizeError(e, getTable(def));
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$1(def) ?? getConfig(def);
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
- const kysely = new Kysely({ dialect: config.dialect });
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) => fn(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
- getHooksMod().then((mod) => mod.registerSoftDeletesFor(def, column));
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._orm?.kysely;
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$2(relatedThunk) ?? void 0;
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$1(results, foreignKey);
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$1(results, base.foreignKey);
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$2(relatedThunk);
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$1(relatedThunk) ?? void 0;
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$1(relatedThunk);
2328
- const through = resolveThunk$1(throughThunk);
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$1(relatedThunk).table)}Id`;
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 ${defName(parent)}`);
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 ${defName(parent)}`);
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
- * Define a polymorphic hasOne relationship.
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 defineMorphOne(options) {
2584
- const base = defineMorphMany(options);
2585
- return {
2586
- ...base,
2587
- type: "hasOne",
2588
- async getResults(parent) {
2589
- return (await base.getResults(parent))[0] ?? null;
2590
- },
2591
- match(models, results, relationName) {
2592
- const grouped = groupByArray(results, base.foreignKey);
2593
- for (const model of models) {
2594
- const related = grouped[String(model.get("id"))] ?? [];
2595
- model.$setRelation(relationName, related[0] ?? null);
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 defName(instance) {
2601
- return instance.constructor?.name ?? "model";
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, defineMorphOne, defineMorphTo, hasMany, hasManyThrough, hasOne, eager_exports as i, manyToMany, relation_exports as n, normalizeError, delete_exports as r, resolveMorphRelation, softDeletes, t, timestamps, ulid };
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 };