@cleverbrush/orm 0.0.0-beta-20260424142030

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/change-tracker.ts","../src/errors.ts","../src/dbset.ts","../src/save-graph.ts","../src/variant-write.ts","../src/dbcontext.ts"],"sourcesContent":["// @cleverbrush/orm — public API\n//\n// EF-Core-like typed ORM layer on top of `@cleverbrush/knex-schema`.\n//\n// The full `@cleverbrush/knex-schema` surface is re-exported so consumers\n// can drop down to schema-aware `query(knex, schema)` and `BoundQuery` for\n// custom query scenarios that don't fit the entity/DbSet model.\n\nexport * from '@cleverbrush/knex-schema';\nexport type {\n EntityEntry,\n EntryState,\n SavingChangesHook\n} from './change-tracker.js';\nexport type {\n DbContext,\n EntityMap,\n TrackedDbContext\n} from './dbcontext.js';\nexport { createDb } from './dbcontext.js';\nexport type { DbSet, EntityQuery, VariantDbSet } from './dbset.js';\nexport {\n ConcurrencyError,\n EntityNotFoundError,\n InvariantViolationError,\n PendingChangesError\n} from './errors.js';\nexport type {\n EntityResult,\n EntityResultByVariant,\n ExtractBranch,\n HasVariants,\n RelKeyTree,\n ResolvedRel,\n SaveGraph,\n VariantInsertPayload,\n VariantResult,\n VariantUpdatePayload,\n WithIncluded,\n WithVariantIncluded\n} from './result-types.js';\n","// @cleverbrush/orm — Change tracker\n//\n// Implements the identity map and entry-state tracking used by tracked\n// `DbContext` instances (opt-in via `{ tracking: true }`).\n//\n// Architecture:\n// - `TrackedEntry` — per-row metadata: state, original snapshot, PK tuple,\n// entitySetKey, optional variantKey.\n// - `IdentityMap` — WeakRef-backed map keyed by (entitySetKey, pkKey).\n// A `FinalizationRegistry` prunes dead refs automatically.\n// - `ChangeTracker` — top-level manager; holds the identity map, registered\n// `onSavingChanges` hooks, and exposes `attach`, `detach`,\n// `entry`, `saveChanges`, `discardChanges`, `reload`.\n\nimport {\n buildColumnMap,\n getPrimaryKeyColumns,\n getRowVersionColumn\n} from '@cleverbrush/knex-schema';\nimport type { Knex } from 'knex';\nimport { ConcurrencyError, InvariantViolationError } from './errors.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Entry state in the identity map. Mirrors EF Core's EntityState.\n * @public\n */\nexport type EntryState = 'Added' | 'Unchanged' | 'Modified' | 'Deleted';\n\n/**\n * Public view of a tracked entry, exposed via `db.entry(entity)`.\n *\n * @public\n */\nexport interface EntityEntry<T extends object> {\n /** Current state of the entry. */\n readonly state: EntryState;\n /** Snapshot of the values at the time the entity was last loaded/saved. */\n readonly originalValues: Readonly<T>;\n /** Live object (same reference as the tracked entity). */\n readonly currentValues: T;\n /**\n * Returns `true` when a specific field has changed since the last\n * snapshot, or `true` when any field has changed when called without\n * arguments.\n */\n isModified(field?: keyof T): boolean;\n /**\n * Reset all changes to the snapshot values and transition state back\n * to `'Unchanged'` (or `'Added'` if the entity was never persisted).\n */\n reset(): void;\n}\n\n/** @internal Per-entity-set configuration needed by the tracker. */\nexport interface EntitySetConfig {\n /** The entity schema (used for PK resolution). */\n schema: any;\n /** Logical entity-set name (e.g. `'todos'`). */\n entitySetKey: string;\n}\n\n/** @internal Raw tracked entry stored in the identity map. */\ninterface RawEntry {\n /** The live entity object. */\n entity: object;\n /** Schema-level entity-set name. */\n entitySetKey: string;\n /** For polymorphic CTI/STI entities, the discriminator value. */\n variantKey: string | undefined;\n /** JSON-stringified PK tuple — used as the map key. */\n pkKey: string;\n /** Frozen copy of the entity's persisted columns at snapshot time. */\n originalSnapshot: Record<string, unknown>;\n /** Current state. */\n state: EntryState;\n /**\n * The row-version column property key and its snapshotted value.\n * `null` when the schema has no `.rowVersion()` column.\n */\n rowVersion: {\n propertyKey: string;\n columnName: string;\n strategy: 'increment' | 'timestamp' | 'manual';\n snapshotValue: unknown;\n } | null;\n}\n\n// ---------------------------------------------------------------------------\n// Identity map\n// ---------------------------------------------------------------------------\n\n/**\n * WeakRef-backed identity map.\n *\n * Structure: Map< entitySetKey, Map< pkKey, WeakRef<RawEntry> > >\n *\n * A `FinalizationRegistry` automatically removes dead references when the\n * entity object is garbage-collected.\n *\n * @internal\n */\nclass IdentityMap {\n readonly #map = new Map<string, Map<string, WeakRef<RawEntry>>>();\n readonly #registry: FinalizationRegistry<{ setKey: string; pkKey: string }>;\n\n constructor() {\n this.#registry = new FinalizationRegistry(({ setKey, pkKey }) => {\n const inner = this.#map.get(setKey);\n if (inner) {\n inner.delete(pkKey);\n if (inner.size === 0) this.#map.delete(setKey);\n }\n });\n }\n\n /** Store a new entry. */\n set(entry: RawEntry): void {\n let inner = this.#map.get(entry.entitySetKey);\n if (!inner) {\n inner = new Map();\n this.#map.set(entry.entitySetKey, inner);\n }\n const ref = new WeakRef(entry);\n inner.set(entry.pkKey, ref);\n this.#registry.register(entry.entity, {\n setKey: entry.entitySetKey,\n pkKey: entry.pkKey\n });\n }\n\n /** Retrieve an existing entry by entity-set key and pk key, or `null`. */\n get(entitySetKey: string, pkKey: string): RawEntry | null {\n const ref = this.#map.get(entitySetKey)?.get(pkKey);\n if (!ref) return null;\n const entry = ref.deref();\n if (!entry) {\n this.#map.get(entitySetKey)?.delete(pkKey);\n return null;\n }\n return entry;\n }\n\n /** Find the entry for a given entity object. O(n) over the set entries. */\n findByEntity(entity: object): RawEntry | null {\n for (const inner of this.#map.values()) {\n for (const ref of inner.values()) {\n const entry = ref.deref();\n if (entry?.entity === entity) return entry;\n }\n }\n return null;\n }\n\n /** Remove an entry. */\n delete(entitySetKey: string, pkKey: string): void {\n this.#map.get(entitySetKey)?.delete(pkKey);\n }\n\n /** Iterate over all live entries. */\n *entries(): IterableIterator<RawEntry> {\n for (const inner of this.#map.values()) {\n for (const ref of inner.values()) {\n const entry = ref.deref();\n if (entry) yield entry;\n }\n }\n }\n\n /** Return all live entries with any of the given states. */\n byState(...states: EntryState[]): RawEntry[] {\n const set = new Set(states);\n const result: RawEntry[] = [];\n for (const entry of this.entries()) {\n if (set.has(entry.state)) result.push(entry);\n }\n return result;\n }\n\n /** Clear all entries. */\n clear(): void {\n this.#map.clear();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Build a stable JSON-encoded PK key from a list of property values. */\nfunction buildPkKey(pkValues: unknown[]): string {\n return JSON.stringify(pkValues);\n}\n\n/** Extract PK property values from an entity given a schema. */\nfunction extractPkValues(\n schema: any,\n entity: Record<string, unknown>\n): unknown[] {\n const pkInfo = getPrimaryKeyColumns(schema);\n return pkInfo.propertyKeys.map(k => entity[k]);\n}\n\n/** Take a shallow snapshot of the entity's persisted (non-relation) columns. */\nfunction snapshotEntity(entity: object, schema: any): Record<string, unknown> {\n const introspected = (schema as any).introspect?.() as {\n properties?: Record<string, unknown>;\n };\n const propKeys = new Set(Object.keys(introspected?.properties ?? {}));\n const snap: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(entity as Record<string, unknown>)) {\n if (propKeys.has(k)) snap[k] = v;\n }\n return Object.freeze(snap);\n}\n\n/** Check if two values differ (shallow). */\nfunction isDirty(\n original: Record<string, unknown>,\n current: Record<string, unknown>\n): boolean {\n for (const key of Object.keys(original)) {\n if (!Object.is(original[key], current[key])) return true;\n }\n // Check for new keys on current that weren't in original\n for (const key of Object.keys(current)) {\n if (!(key in original) && current[key] !== undefined) return true;\n }\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// ChangeTracker\n// ---------------------------------------------------------------------------\n\n/**\n * Pre-save hook callback signature registered via\n * `TrackedDbContext.onSavingChanges(hook)`.\n * @public\n */\nexport type SavingChangesHook = (\n entry: EntityEntry<object>\n) => void | Promise<void>;\n\n/**\n * The change-tracking core used by tracked `DbContext` instances.\n *\n * Consumers interact with this via the `DbContext` API methods; the\n * `ChangeTracker` itself is not exported as part of the public API.\n *\n * @internal\n */\nexport class ChangeTracker {\n readonly #identityMap = new IdentityMap();\n /** Added entries whose PK is not yet known (no identity-map slot). */\n readonly #addedWithoutPk = new Set<RawEntry>();\n readonly #hooks: SavingChangesHook[] = [];\n /** entity-set-key → EntitySetConfig */\n readonly #configs = new Map<string, EntitySetConfig>();\n\n /** Register an entity set so the tracker knows its schema. */\n registerEntitySet(config: EntitySetConfig): void {\n this.#configs.set(config.entitySetKey, config);\n }\n\n /** Register a pre-save hook. */\n onSavingChanges(hook: SavingChangesHook): void {\n this.#hooks.push(hook);\n }\n\n /**\n * Attach an entity to the tracker under the given entity-set key.\n *\n * If an entry with the same PK already exists, returns the EXISTING\n * tracked object (identity-map guarantee). If the entity is already the\n * same object, just updates its snapshot.\n *\n * @returns The tracked entity (same ref when already in the map).\n */\n attach<T extends object>(\n entitySetKey: string,\n entity: T,\n state: EntryState = 'Unchanged'\n ): T {\n const config = this.#configs.get(entitySetKey);\n if (!config) {\n throw new Error(\n `ChangeTracker.attach: unknown entity set \"${entitySetKey}\". ` +\n `Make sure it was registered via registerEntitySet().`\n );\n }\n const pkValues = extractPkValues(\n config.schema,\n entity as unknown as Record<string, unknown>\n );\n if (pkValues.some(v => v === undefined || v === null)) {\n // No PK yet — treat as Added; keep in secondary set\n const existing = (entity as any)[TRACKER_ENTRY_SYMBOL] as\n | RawEntry\n | undefined;\n if (existing) return entity; // already tracked\n const snap = snapshotEntity(entity, config.schema);\n const rvCol = getRowVersionColumn(config.schema);\n const entry: RawEntry = {\n entity,\n entitySetKey,\n variantKey: resolveVariantKey(\n config.schema,\n entity as Record<string, unknown>\n ),\n pkKey: '',\n originalSnapshot: snap,\n state: 'Added',\n rowVersion: rvCol\n ? {\n ...rvCol,\n snapshotValue: (entity as any)[rvCol.propertyKey]\n }\n : null\n };\n (entity as any)[TRACKER_ENTRY_SYMBOL] = entry;\n this.#addedWithoutPk.add(entry);\n return entity;\n }\n\n const pkKey = buildPkKey(pkValues);\n const existing = this.#identityMap.get(entitySetKey, pkKey);\n if (existing) {\n // Identity-map hit: update snapshot and return the EXISTING object\n if (existing.entity !== entity) {\n return existing.entity as T;\n }\n // Same object — refresh snapshot if transitioning to Unchanged\n if (state === 'Unchanged') {\n existing.originalSnapshot = snapshotEntity(\n entity,\n config.schema\n );\n existing.state = 'Unchanged';\n if (existing.rowVersion) {\n existing.rowVersion.snapshotValue = (entity as any)[\n existing.rowVersion.propertyKey\n ];\n }\n }\n return entity;\n }\n\n const snap = snapshotEntity(entity, config.schema);\n const rvCol = getRowVersionColumn(config.schema);\n const entry: RawEntry = {\n entity,\n entitySetKey,\n variantKey: resolveVariantKey(\n config.schema,\n entity as Record<string, unknown>\n ),\n pkKey,\n originalSnapshot: snap,\n state,\n rowVersion: rvCol\n ? {\n ...rvCol,\n snapshotValue: (entity as any)[rvCol.propertyKey]\n }\n : null\n };\n this.#identityMap.set(entry);\n (entity as any)[TRACKER_ENTRY_SYMBOL] = entry;\n return entity;\n }\n\n /** Detach an entity from tracking. */\n detach(entity: object): void {\n const entry = this.#identityMap.findByEntity(entity);\n if (entry) {\n this.#identityMap.delete(entry.entitySetKey, entry.pkKey);\n } else {\n const symEntry = (entity as any)[TRACKER_ENTRY_SYMBOL] as\n | RawEntry\n | undefined;\n if (symEntry) this.#addedWithoutPk.delete(symEntry);\n }\n delete (entity as any)[TRACKER_ENTRY_SYMBOL];\n }\n\n /**\n * Mark an entity for deletion on the next `saveChanges()` call.\n * The entity must already be tracked.\n */\n remove(entity: object): void {\n const entry = this.#identityMap.findByEntity(entity);\n if (!entry) {\n throw new Error(\n 'ChangeTracker.remove: entity is not tracked. ' +\n 'Attach it first or use `db.attach()` before calling `remove()`.'\n );\n }\n entry.state = 'Deleted';\n }\n\n /** Return the public `EntityEntry` view for a tracked entity. */\n entry<T extends object>(entity: T): EntityEntry<T> {\n const rawEntry =\n this.#identityMap.findByEntity(entity) ??\n ((entity as any)[TRACKER_ENTRY_SYMBOL] as RawEntry | undefined);\n if (!rawEntry) {\n throw new Error(\n 'ChangeTracker.entry: entity is not tracked. ' +\n 'Attach it first or load it via a tracked DbSet.'\n );\n }\n const config = this.#configs.get(rawEntry.entitySetKey);\n void config; // reserved for future schema-aware field filtering\n\n return {\n get state() {\n return rawEntry.state;\n },\n get originalValues() {\n return rawEntry.originalSnapshot as Readonly<T>;\n },\n get currentValues() {\n return entity;\n },\n isModified(field?: keyof T): boolean {\n const current = entity as Record<string, unknown>;\n if (field !== undefined) {\n return !Object.is(\n rawEntry.originalSnapshot[field as string],\n current[field as string]\n );\n }\n return isDirty(rawEntry.originalSnapshot, current);\n },\n reset(): void {\n // Restore current values from snapshot\n const current = entity as Record<string, unknown>;\n for (const [k, v] of Object.entries(\n rawEntry.originalSnapshot\n )) {\n current[k] = v;\n }\n rawEntry.state = rawEntry.pkKey ? 'Unchanged' : 'Added';\n }\n };\n }\n\n /**\n * Discard all pending changes: reset all Modified entries to their\n * snapshots, remove Added entries from the tracker, restore Deleted\n * entries to Unchanged.\n */\n discardChanges(): void {\n // Discard Added-without-PK entries\n for (const entry of this.#addedWithoutPk) {\n delete (entry.entity as any)[TRACKER_ENTRY_SYMBOL];\n }\n this.#addedWithoutPk.clear();\n\n for (const entry of this.#identityMap.entries()) {\n if (entry.state === 'Added') {\n this.#identityMap.delete(entry.entitySetKey, entry.pkKey);\n } else if (\n entry.state === 'Modified' ||\n entry.state === 'Deleted' ||\n (entry.state === 'Unchanged' &&\n isDirty(\n entry.originalSnapshot,\n entry.entity as Record<string, unknown>\n ))\n ) {\n // Restore values from snapshot\n const current = entry.entity as Record<string, unknown>;\n for (const [k, v] of Object.entries(entry.originalSnapshot)) {\n current[k] = v;\n }\n entry.state = 'Unchanged';\n }\n }\n }\n\n /**\n * Refresh a tracked entity from the DB, replacing its current values\n * and snapshot with the freshly-loaded row.\n */\n async reload(entity: object, knex: Knex): Promise<void> {\n const entry = this.#identityMap.findByEntity(entity);\n if (!entry) return;\n\n const config = this.#configs.get(entry.entitySetKey);\n if (!config) return;\n\n const pkInfo = getPrimaryKeyColumns(config.schema);\n const tableName = config.schema.getExtension?.('tableName') as string;\n if (!tableName || pkInfo.propertyKeys.length === 0) return;\n\n const { propToCol, colToProp } = buildColumnMap(config.schema);\n const qb = knex(tableName);\n const pkValues = extractPkValues(\n config.schema,\n entity as Record<string, unknown>\n );\n for (let i = 0; i < pkInfo.propertyKeys.length; i++) {\n const colName =\n propToCol.get(pkInfo.propertyKeys[i]) ?? pkInfo.propertyKeys[i];\n qb.andWhere(colName, pkValues[i] as any);\n }\n const row = await qb.first();\n if (!row) return;\n\n // Re-map column names → property names\n const mapped = entity as Record<string, unknown>;\n for (const [col, val] of Object.entries(\n row as Record<string, unknown>\n )) {\n mapped[colToProp.get(col) ?? col] = val;\n }\n // Refresh snapshot and rowVersion\n entry.originalSnapshot = snapshotEntity(entity, config.schema);\n if (entry.rowVersion) {\n entry.rowVersion.snapshotValue = (entity as any)[\n entry.rowVersion.propertyKey\n ];\n }\n entry.state = 'Unchanged';\n }\n\n /**\n * Returns `true` when there are any pending changes (Added / Modified /\n * Deleted entries, or silently mutated Unchanged entries).\n */\n hasPendingChanges(): boolean {\n if (this.#addedWithoutPk.size > 0) return true;\n for (const entry of this.#identityMap.entries()) {\n if (entry.state !== 'Unchanged') return true;\n if (\n isDirty(\n entry.originalSnapshot,\n entry.entity as Record<string, unknown>\n )\n )\n return true;\n }\n return false;\n }\n\n /**\n * Flush all pending changes to the database within a single transaction.\n *\n * Returns the number of rows inserted, updated, and deleted.\n *\n * Invariants checked:\n * - PK columns must not have changed since the snapshot (throws\n * `InvariantViolationError`).\n * - Discriminator columns must not have changed (throws\n * `InvariantViolationError`).\n * - For `rowVersion` columns: WHERE clause enforces the snapshot value;\n * zero affected rows throws `ConcurrencyError`.\n */\n async saveChanges(\n knex: Knex\n ): Promise<{ inserted: number; updated: number; deleted: number }> {\n let inserted = 0;\n let updated = 0;\n let deleted = 0;\n\n // 1. Compute dirty states (scan all entries)\n const added: RawEntry[] = [\n ...this.#addedWithoutPk,\n ...this.#identityMap.byState('Added')\n ];\n const modified: RawEntry[] = [];\n const deleted_entries = this.#identityMap.byState('Deleted');\n\n for (const entry of this.#identityMap.byState(\n 'Unchanged',\n 'Modified'\n )) {\n if (entry.state === 'Modified') {\n modified.push(entry);\n continue;\n }\n // Check if Unchanged entry has been silently mutated\n if (\n isDirty(\n entry.originalSnapshot,\n entry.entity as Record<string, unknown>\n )\n ) {\n entry.state = 'Modified';\n modified.push(entry);\n }\n }\n\n if (\n added.length === 0 &&\n modified.length === 0 &&\n deleted_entries.length === 0\n ) {\n return { inserted: 0, updated: 0, deleted: 0 };\n }\n\n // 2. Validate invariants for modified / deleted entries\n for (const entry of [...modified, ...deleted_entries]) {\n const config = this.#configs.get(entry.entitySetKey);\n if (!config) continue;\n const current = entry.entity as Record<string, unknown>;\n const pkInfo = getPrimaryKeyColumns(config.schema);\n const tableName = config.schema.getExtension?.(\n 'tableName'\n ) as string;\n\n // PK must not have changed\n for (const propKey of pkInfo.propertyKeys) {\n if (\n !Object.is(\n entry.originalSnapshot[propKey],\n current[propKey]\n )\n ) {\n throw new InvariantViolationError(\n tableName,\n entry.pkKey,\n propKey,\n `Primary key column \"${propKey}\" must not be changed on a tracked entity. ` +\n `Original: ${JSON.stringify(entry.originalSnapshot[propKey])}, ` +\n `Current: ${JSON.stringify(current[propKey])}.`\n );\n }\n }\n\n // Discriminator must not have changed (for polymorphic entities)\n if (entry.variantKey !== undefined) {\n const variantsExt = config.schema.getExtension?.(\n 'variants'\n ) as { discriminatorKey?: string } | null;\n const discKey = variantsExt?.discriminatorKey;\n if (\n discKey &&\n !Object.is(\n entry.originalSnapshot[discKey],\n current[discKey]\n )\n ) {\n throw new InvariantViolationError(\n tableName,\n entry.pkKey,\n discKey,\n `Discriminator column \"${discKey}\" must not be changed on a tracked entity. ` +\n `To change a polymorphic entity's type, delete and re-insert it as a new variant.`\n );\n }\n }\n }\n\n // 3. Fire onSavingChanges hooks\n for (const entry of [...added, ...modified, ...deleted_entries]) {\n const config = this.#configs.get(entry.entitySetKey);\n if (!config) continue;\n const publicEntry = this.entry(entry.entity as object);\n for (const hook of this.#hooks) {\n await hook(publicEntry);\n }\n }\n\n // 4. Execute all changes in a single transaction\n await knex.transaction(async (trx: Knex.Transaction) => {\n // Inserts (Added)\n for (const entry of added) {\n const config = this.#configs.get(entry.entitySetKey);\n if (!config) continue;\n const tableName = config.schema.getExtension?.(\n 'tableName'\n ) as string;\n if (!tableName) continue;\n\n const { propToCol } = buildColumnMap(config.schema);\n const current = entry.entity as Record<string, unknown>;\n const row: Record<string, unknown> = {};\n for (const [propKey, val] of Object.entries(current)) {\n const colName = propToCol.get(propKey) ?? propKey;\n if (val !== undefined) row[colName] = val;\n }\n const result = await trx(tableName).insert(row).returning('*');\n const returned =\n Array.isArray(result) && result.length > 0\n ? result[0]\n : null;\n if (returned && typeof returned === 'object') {\n const { colToProp } = buildColumnMap(config.schema);\n const mapped = entry.entity as Record<string, unknown>;\n for (const [col, val] of Object.entries(\n returned as Record<string, unknown>\n )) {\n mapped[colToProp.get(col) ?? col] = val;\n }\n }\n inserted++;\n }\n\n // Updates (Modified)\n for (const entry of modified) {\n const config = this.#configs.get(entry.entitySetKey);\n if (!config) continue;\n const tableName = config.schema.getExtension?.(\n 'tableName'\n ) as string;\n if (!tableName) continue;\n\n const { propToCol } = buildColumnMap(config.schema);\n const pkInfo = getPrimaryKeyColumns(config.schema);\n const current = entry.entity as Record<string, unknown>;\n\n // Build the SET clause — only changed columns, excluding PK\n const pkPropSet = new Set(pkInfo.propertyKeys);\n const updateData: Record<string, unknown> = {};\n for (const propKey of Object.keys(entry.originalSnapshot)) {\n if (pkPropSet.has(propKey)) continue;\n if (\n !Object.is(\n entry.originalSnapshot[propKey],\n current[propKey]\n )\n ) {\n updateData[propToCol.get(propKey) ?? propKey] =\n current[propKey];\n }\n }\n // Also pick up new keys not in snapshot\n for (const [propKey, val] of Object.entries(current)) {\n if (pkPropSet.has(propKey)) continue;\n if (\n !(propKey in entry.originalSnapshot) &&\n val !== undefined\n ) {\n updateData[propToCol.get(propKey) ?? propKey] = val;\n }\n }\n\n // Handle rowVersion\n if (entry.rowVersion) {\n const rv = entry.rowVersion;\n const rvCol =\n propToCol.get(rv.propertyKey) ?? rv.propertyKey;\n if (rv.strategy === 'increment') {\n const newVal = Number(rv.snapshotValue ?? 0) + 1;\n updateData[rvCol] = newVal;\n current[rv.propertyKey] = newVal;\n } else if (rv.strategy === 'timestamp') {\n const now = new Date();\n updateData[rvCol] = now;\n current[rv.propertyKey] = now;\n }\n // 'manual': caller already set the new value in current\n }\n\n if (Object.keys(updateData).length === 0) continue;\n\n // Build WHERE clause with PK + optional rowVersion check\n let qb = trx(tableName);\n for (let i = 0; i < pkInfo.propertyKeys.length; i++) {\n const colName =\n propToCol.get(pkInfo.propertyKeys[i]) ??\n pkInfo.propertyKeys[i];\n qb = qb.andWhere(\n colName,\n current[pkInfo.propertyKeys[i]] as any\n ) as any;\n }\n if (entry.rowVersion) {\n const rv = entry.rowVersion;\n const rvCol =\n propToCol.get(rv.propertyKey) ?? rv.propertyKey;\n qb = qb.andWhere(rvCol, rv.snapshotValue as any) as any;\n }\n\n const affected = await qb.update(updateData);\n if (affected === 0 && entry.rowVersion) {\n const tableName2 = config.schema.getExtension?.(\n 'tableName'\n ) as string;\n throw new ConcurrencyError(\n tableName2,\n extractPkValues(config.schema, current),\n entry.rowVersion.snapshotValue\n );\n }\n updated++;\n }\n\n // Deletes (Deleted) — children before parents (simple heuristic:\n // delete in reverse registration order for now; full topo-sort\n // only needed when FK deps cross entity sets)\n for (const entry of [...deleted_entries].reverse()) {\n const config = this.#configs.get(entry.entitySetKey);\n if (!config) continue;\n const tableName = config.schema.getExtension?.(\n 'tableName'\n ) as string;\n if (!tableName) continue;\n\n const { propToCol } = buildColumnMap(config.schema);\n const pkInfo = getPrimaryKeyColumns(config.schema);\n const current = entry.entity as Record<string, unknown>;\n\n let qb = trx(tableName);\n for (let i = 0; i < pkInfo.propertyKeys.length; i++) {\n const colName =\n propToCol.get(pkInfo.propertyKeys[i]) ??\n pkInfo.propertyKeys[i];\n qb = qb.andWhere(\n colName,\n current[pkInfo.propertyKeys[i]] as any\n ) as any;\n }\n if (entry.rowVersion) {\n const rv = entry.rowVersion;\n const rvCol =\n propToCol.get(rv.propertyKey) ?? rv.propertyKey;\n qb = qb.andWhere(rvCol, rv.snapshotValue as any) as any;\n }\n\n const affected = await qb.delete();\n if (affected === 0 && entry.rowVersion) {\n throw new ConcurrencyError(\n tableName,\n extractPkValues(config.schema, current),\n entry.rowVersion?.snapshotValue\n );\n }\n deleted++;\n }\n });\n\n // 5. Refresh snapshots for inserted and updated entries; detach deleted\n for (const entry of added) {\n entry.originalSnapshot = snapshotEntity(\n entry.entity,\n this.#configs.get(entry.entitySetKey)!.schema\n );\n entry.state = 'Unchanged';\n // Re-register in identity map with the new PK\n const config = this.#configs.get(entry.entitySetKey)!;\n const pkValues = extractPkValues(\n config.schema,\n entry.entity as Record<string, unknown>\n );\n if (pkValues.every(v => v !== undefined && v !== null)) {\n entry.pkKey = buildPkKey(pkValues);\n this.#identityMap.set(entry);\n }\n // Remove from the without-PK secondary set\n this.#addedWithoutPk.delete(entry);\n if (entry.rowVersion) {\n entry.rowVersion.snapshotValue = (entry.entity as any)[\n entry.rowVersion.propertyKey\n ];\n }\n }\n for (const entry of modified) {\n entry.originalSnapshot = snapshotEntity(\n entry.entity,\n this.#configs.get(entry.entitySetKey)!.schema\n );\n entry.state = 'Unchanged';\n if (entry.rowVersion) {\n entry.rowVersion.snapshotValue = (entry.entity as any)[\n entry.rowVersion.propertyKey\n ];\n }\n }\n for (const entry of deleted_entries) {\n this.#identityMap.delete(entry.entitySetKey, entry.pkKey);\n }\n\n return { inserted, updated, deleted };\n }\n\n /**\n * Generate a summary of pending changes for error messages.\n * @internal\n */\n pendingSummary(): string {\n const counts: Record<EntryState, number> = {\n Added: 0,\n Modified: 0,\n Deleted: 0,\n Unchanged: 0\n };\n counts.Added += this.#addedWithoutPk.size;\n for (const entry of this.#identityMap.entries()) {\n if (entry.state !== 'Unchanged') counts[entry.state]++;\n else if (\n isDirty(\n entry.originalSnapshot,\n entry.entity as Record<string, unknown>\n )\n )\n counts.Modified++;\n }\n const parts: string[] = [];\n if (counts.Added) parts.push(`${counts.Added} Added`);\n if (counts.Modified) parts.push(`${counts.Modified} Modified`);\n if (counts.Deleted) parts.push(`${counts.Deleted} Deleted`);\n return `(${parts.join(', ')})`;\n }\n\n /** Clear the entire identity map (used on dispose). */\n clear(): void {\n // Remove TRACKER_ENTRY_SYMBOL from all in-map entities so entry() throws.\n for (const entry of this.#identityMap.entries()) {\n delete (entry.entity as any)[TRACKER_ENTRY_SYMBOL];\n }\n for (const entry of this.#addedWithoutPk) {\n delete (entry.entity as any)[TRACKER_ENTRY_SYMBOL];\n }\n this.#addedWithoutPk.clear();\n this.#identityMap.clear();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Symbol used to tag entities with their raw tracker entry (for Added items without PK). */\nconst TRACKER_ENTRY_SYMBOL: unique symbol = Symbol(\n '@cleverbrush/orm:trackerEntry'\n);\n\n/** Resolve the variant discriminator value for an entity (if polymorphic). */\nfunction resolveVariantKey(\n schema: any,\n entity: Record<string, unknown>\n): string | undefined {\n const variantsExt = schema?.getExtension?.('variants') as {\n discriminatorKey?: string;\n } | null;\n const discKey = variantsExt?.discriminatorKey;\n if (!discKey) return undefined;\n return entity[discKey] as string | undefined;\n}\n","// @cleverbrush/orm — typed error classes\n\n/**\n * Thrown by {@link DbSet.findOrFail} when no row matches the supplied\n * primary key value(s). The `entity` field carries the SQL table name (or\n * the entity's schema name when a table name is unavailable); `pk` is the\n * exact value (scalar or tuple) that was looked up.\n *\n * @public\n */\nexport class EntityNotFoundError extends Error {\n readonly entity: string;\n readonly pk: unknown;\n\n constructor(entity: string, pk: unknown) {\n super(\n `Entity \"${entity}\" not found for primary key ${JSON.stringify(pk)}`\n );\n this.name = 'EntityNotFoundError';\n this.entity = entity;\n this.pk = pk;\n }\n}\n\n/**\n * Thrown by {@link DbContext.saveChanges} when an UPDATE or DELETE detects\n * that the row's row-version column (`.rowVersion()`) has changed since the\n * entity was last loaded — indicating a concurrent modification.\n *\n * @public\n */\nexport class ConcurrencyError extends Error {\n /** Table name or entity label. */\n readonly entity: string;\n /** Primary-key value(s) of the conflicting row. */\n readonly pk: unknown;\n /** The row-version value the ORM expected. */\n readonly expected: unknown;\n\n constructor(entity: string, pk: unknown, expected: unknown) {\n super(\n `Concurrency conflict on \"${entity}\" (pk=${JSON.stringify(pk)}): ` +\n `the row has been modified or deleted since it was loaded. ` +\n `Expected rowVersion = ${JSON.stringify(expected)}.`\n );\n this.name = 'ConcurrencyError';\n this.entity = entity;\n this.pk = pk;\n this.expected = expected;\n }\n}\n\n/**\n * Thrown by {@link DbContext.saveChanges} when an invariant is violated on\n * a tracked entity — e.g. the primary key or discriminator column was mutated.\n *\n * @public\n */\nexport class InvariantViolationError extends Error {\n readonly entity: string;\n readonly pk: unknown;\n readonly field: string;\n\n constructor(entity: string, pk: unknown, field: string, detail: string) {\n super(\n `Invariant violation on \"${entity}\" (pk=${JSON.stringify(pk)}): ${detail}`\n );\n this.name = 'InvariantViolationError';\n this.entity = entity;\n this.pk = pk;\n this.field = field;\n }\n}\n\n/**\n * Thrown by the `[Symbol.asyncDispose]` implementation on a tracked\n * {@link DbContext} when there are unsaved changes at scope exit.\n *\n * Callers must explicitly call `await db.saveChanges()` or\n * `db.discardChanges()` before the `await using` block exits.\n *\n * @public\n */\nexport class PendingChangesError extends Error {\n /** Number of dirty / added / deleted entries. */\n readonly pendingCount: number;\n\n constructor(pendingCount: number, summary: string) {\n super(\n `DbContext disposed with ${pendingCount} pending change(s). ` +\n `Call saveChanges() or discardChanges() before the context goes out of scope. ` +\n summary\n );\n this.name = 'PendingChangesError';\n this.pendingCount = pendingCount;\n }\n}\n","// @cleverbrush/orm — DbSet\n//\n// `DbSet<TEntity>` is the typed query starter for an entity, modelled on\n// EF Core's `DbSet<TEntity>`. It is structurally an `EntityQuery` whose\n// underlying `SchemaQueryBuilder` is allocated lazily on each chain so\n// independent chains never share state.\n//\n// Calling any query method (`.where()`, `.include()`, `.first()`, `.insert()`,\n// etc.) on a `DbSet` allocates a fresh `SchemaQueryBuilder` and forwards the\n// call to it. Subsequent calls on the returned `EntityQuery` reuse that\n// same underlying builder.\n\nimport type {\n ColumnRef,\n Entity,\n EntityRelations,\n EntitySchema,\n PrimaryKeyValueOf\n} from '@cleverbrush/knex-schema';\nimport {\n getPrimaryKeyColumns,\n getVariants,\n type SchemaQueryBuilder,\n query as schemaQuery\n} from '@cleverbrush/knex-schema';\nimport type { Knex } from 'knex';\n\nimport { EntityNotFoundError } from './errors.js';\nimport type {\n EntityResult,\n RelKeyTree,\n SaveGraph,\n VariantInsertPayload,\n VariantResult,\n VariantUpdatePayload,\n WithIncluded,\n WithVariantIncluded\n} from './result-types.js';\nimport { saveGraph } from './save-graph.js';\nimport {\n deleteVariant as _deleteVariant,\n insertVariant as _insertVariant,\n updateVariant as _updateVariant\n} from './variant-write.js';\n\n// ---------------------------------------------------------------------------\n// EntityQuery — public typed query handle\n// ---------------------------------------------------------------------------\n\n/**\n * The typed query handle returned by every {@link DbSet} method that\n * initiates a query (e.g. `.where()`, `.include()`, `.first()`).\n *\n * Structurally it IS a {@link SchemaQueryBuilder} for the entity's schema,\n * with typed `.include()` / `.includeVariant()` methods overlaid. All other\n * `SchemaQueryBuilder` methods are available and `this`-returning chains\n * preserve the typed wrapper.\n *\n * @public\n */\nexport interface EntityQuery<TEntity extends Entity<any, any, any>, TResult>\n extends Omit<\n SchemaQueryBuilder<EntitySchema<TEntity>, TResult>,\n 'include' | 'includeVariant'\n > {\n /**\n * Eager-load a relation declared on `TEntity` via `.hasOne()` /\n * `.hasMany()` / `.belongsTo()` / `.belongsToMany()`. Selector returns\n * the relation key as a string literal.\n */\n include<K extends keyof EntityRelations<TEntity> & string>(\n sel: (t: RelKeyTree<TEntity>) => K,\n customize?: (q: SchemaQueryBuilder<any, any>) => void\n ): EntityQuery<TEntity, WithIncluded<TEntity, TResult, K>>;\n\n /**\n * Eager-load a relation declared inside a polymorphic variant (CTI/STI).\n * The relation is only populated on rows whose discriminator matches\n * `variantKey`. Type-level: when `relationName` is a key of the\n * entity's relations, it appears only on the matching branch of the\n * discriminated-union result; otherwise the result type is unchanged.\n */\n includeVariant<TVariant extends string, TRel extends string>(\n variantKey: TVariant,\n relationName: TRel,\n customize?: (q: SchemaQueryBuilder<any, any>) => void\n ): EntityQuery<\n TEntity,\n TRel extends keyof EntityRelations<TEntity> & string\n ? WithVariantIncluded<TEntity, TResult, TVariant, TRel>\n : TResult\n >;\n\n /**\n * Look up a single entity by primary key. Returns `undefined` when no\n * row matches.\n *\n * The PK column(s) are resolved automatically from the entity's schema\n * via the `primaryKey` / `hasPrimaryKey` extensions. Composite PKs are\n * passed as a tuple in declaration order:\n *\n * ```ts\n * await db.todos.find(42); // single PK\n * await db.userRoles.find([userId, role]); // composite PK\n * ```\n *\n * Any chained `.include()` / `.includeVariant()` calls on this query\n * are honoured by the underlying `SELECT`.\n */\n find(\n pk: PrimaryKeyValueOf<EntitySchema<TEntity>>\n ): Promise<TResult | undefined>;\n\n /**\n * Like {@link find} but throws {@link EntityNotFoundError} when no row\n * matches.\n */\n findOrFail(pk: PrimaryKeyValueOf<EntitySchema<TEntity>>): Promise<TResult>;\n\n /**\n * Look up multiple entities by primary key in a single SQL statement.\n * Returns rows in DB order (no ordering guarantee relative to `pks`).\n *\n * For composite PKs each element of `pks` is a tuple matching\n * declaration order. Returns `[]` when `pks` is empty.\n */\n findMany(\n pks: ReadonlyArray<PrimaryKeyValueOf<EntitySchema<TEntity>>>\n ): Promise<TResult[]>;\n}\n\n// ---------------------------------------------------------------------------\n// DbSet — public typed query starter\n// ---------------------------------------------------------------------------\n\n/**\n * Typed query starter for a registered entity. Structurally it behaves like\n * a fresh {@link EntityQuery} — every method invocation allocates a new\n * underlying `SchemaQueryBuilder` so chains stay isolated.\n *\n * Additional members beyond `EntityQuery`:\n *\n * - `entity` — the wrapped {@link Entity} definition.\n * - `query()` — explicit factory for a fresh `EntityQuery` (rarely needed).\n * - `withTransaction(trx)` — return a new `DbSet` bound to a transaction.\n *\n * @public\n */\nexport interface DbSet<TEntity extends Entity<any, any, any>>\n extends EntityQuery<TEntity, EntityResult<TEntity>> {\n /** The wrapped entity definition. */\n readonly entity: TEntity;\n\n /**\n * Allocate a fresh `EntityQuery` (rarely needed; method shortcuts on the\n * `DbSet` itself already do this).\n */\n query(): EntityQuery<TEntity, EntityResult<TEntity>>;\n\n /**\n * Return a new {@link DbSet} bound to `trx`. All queries via the returned\n * set execute inside the transaction.\n */\n withTransaction(trx: Knex.Transaction): DbSet<TEntity>;\n\n /**\n * Persist a nested write graph (root + related entities) in a single\n * transaction. Each node is INSERTed when its primary-key fields are\n * absent and UPDATEd when they are present (composite-PK aware).\n *\n * Topology:\n * - `belongsTo` parents are saved first; their PKs feed the root's FK.\n * - The root row is then written.\n * - `hasOne` / `hasMany` / `belongsToMany` children inherit the root's\n * PK into their FK column.\n * - `belongsToMany` children may be passed as full nested graphs (new\n * rows) or as `{ pk: value }` references (link existing rows).\n *\n * If called inside an existing transaction (via `withTransaction`), the\n * outer transaction is reused; otherwise a fresh one is opened and\n * automatically rolled back on failure.\n */\n save(graph: SaveGraph<TEntity>): Promise<EntityResult<TEntity>>;\n\n /**\n * Return a typed variant view pre-filtered to `variantKey`.\n *\n * Behaves like EF Core's `Set<DerivedType>()`: all reads automatically\n * filter by the discriminator value, and `insert` / `update` / `delete`\n * apply the correct single-table (STI) or two-table (CTI) logic.\n *\n * ```ts\n * const assigned = db.activities.ofVariant('assigned');\n *\n * // Insert a new variant row (discriminator set automatically):\n * await assigned.insert({ todoId: 42, userId: 7, assigneeId: 9 });\n *\n * // Update variant-specific columns for matching rows:\n * await assigned.where(t => t.id, activityId).update({ assigneeId: 10 });\n *\n * // Delete variant rows (CTI: variant table first, then base):\n * await assigned.where(t => t.id, activityId).delete();\n *\n * // Find by PK (result is narrowed to the variant branch):\n * const a = await assigned.find(activityId);\n * // a.type === 'assigned' and a.assigneeId is available\n * ```\n */\n ofVariant<K extends string>(variantKey: K): VariantDbSet<TEntity, K>;\n}\n\n// ---------------------------------------------------------------------------\n// VariantDbSet — typed view scoped to a single polymorphic variant\n// ---------------------------------------------------------------------------\n\n/**\n * A `DbSet`-like handle scoped to a single polymorphic variant (one branch\n * of a discriminated union entity). All reads are automatically pre-filtered\n * by the discriminator; all writes apply the correct STI / CTI logic.\n *\n * Obtain via `DbSet.ofVariant('key')` — analogous to EF Core's\n * `Set<DerivedType>()`.\n *\n * @public\n */\nexport interface VariantDbSet<\n TEntity extends Entity<any, any, any>,\n K extends string\n> extends Omit<\n SchemaQueryBuilder<EntitySchema<TEntity>, VariantResult<TEntity, K>>,\n 'include' | 'includeVariant' | 'insert' | 'update' | 'delete' | 'where'\n > {\n // Re-declared so that `this` resolves to `VariantDbSet<TEntity, K>`\n // rather than the raw `SchemaQueryBuilder` (Omit doesn't preserve `this`).\n where(\n column: ColumnRef<EntitySchema<TEntity>>,\n operator: string,\n value: any\n ): this;\n where(column: ColumnRef<EntitySchema<TEntity>>, value: any): this;\n where(raw: Knex.Raw, operator: string, value: any): this;\n where(callback: (builder: Knex.QueryBuilder) => void): this;\n where(record: Record<string, any>): this;\n where(raw: Knex.Raw): this;\n /**\n * Eager-load a relation declared on `TEntity`. Identical to\n * {@link EntityQuery.include}.\n */\n include<R extends keyof EntityRelations<TEntity> & string>(\n sel: (t: RelKeyTree<TEntity>) => R,\n customize?: (q: SchemaQueryBuilder<any, any>) => void\n ): VariantDbSet<TEntity, K>;\n\n /** Look up a single row by PK, typed to this variant. */\n find(\n pk: PrimaryKeyValueOf<EntitySchema<TEntity>>\n ): Promise<VariantResult<TEntity, K> | undefined>;\n\n /** Like {@link find} but throws {@link EntityNotFoundError} when absent. */\n findOrFail(\n pk: PrimaryKeyValueOf<EntitySchema<TEntity>>\n ): Promise<VariantResult<TEntity, K>>;\n\n /** Look up multiple rows by PK, typed to this variant. */\n findMany(\n pks: ReadonlyArray<PrimaryKeyValueOf<EntitySchema<TEntity>>>\n ): Promise<VariantResult<TEntity, K>[]>;\n\n /**\n * Insert a new variant row. The discriminator is set automatically.\n *\n * For CTI: inserts the base row first, then the variant row (transactional).\n */\n insert(\n payload: VariantInsertPayload<TEntity, K>\n ): Promise<VariantResult<TEntity, K>>;\n\n /**\n * Update variant-specific columns for rows matched by the current\n * WHERE clause. For CTI, only the variant table is updated.\n */\n update(patch: VariantUpdatePayload<TEntity, K>): Promise<void>;\n\n /** Delete rows matched by the current WHERE clause (CTI: atomic). */\n delete(): Promise<void>;\n\n /** Return a new variant view bound to `trx`. */\n withTransaction(trx: Knex.Transaction): VariantDbSet<TEntity, K>;\n}\n\n// ---------------------------------------------------------------------------\n// Runtime construction\n// ---------------------------------------------------------------------------\n\nconst ENTITY_KEY_TREE_CACHE = new WeakMap<object, Record<string, string>>();\n\nfunction getEntityKeyTree(\n entity: Entity<any, any, any>\n): Record<string, string> {\n const cached = ENTITY_KEY_TREE_CACHE.get(entity);\n if (cached) return cached;\n const props =\n (\n entity.schema as {\n introspect?: () => { properties?: Record<string, unknown> };\n }\n ).introspect?.()?.properties ?? {};\n const tree: Record<string, string> = {};\n for (const k of Object.keys(props)) tree[k] = k;\n ENTITY_KEY_TREE_CACHE.set(entity, tree);\n return tree;\n}\n\n/**\n * Extract the Knex instance from a `SchemaQueryBuilder`.\n * SchemaQueryBuilder stores it as `#knex` (private), but the `query()`\n * factory function passes it as the first constructor argument.\n * We retrieve it via the `.toQuery()` builder's knex client reference.\n * @internal\n */\n\n/**\n * Wrap a `SchemaQueryBuilder` in a Proxy that overlays typed\n * `.include()` / `.includeVariant()` methods and re-wraps `this`-returning\n * methods so chains keep their typed wrapper.\n *\n * @internal\n */\nfunction wrapQuery<TEntity extends Entity<any, any, any>, TResult>(\n sqb: SchemaQueryBuilder<EntitySchema<TEntity>, TResult>,\n entity: TEntity,\n _knexInst: Knex,\n onResults?: (items: unknown[]) => unknown[] | undefined\n): EntityQuery<TEntity, TResult> {\n const proxy: EntityQuery<TEntity, TResult> = new Proxy(\n sqb as unknown as object,\n {\n get(target, prop, receiver) {\n if (prop === 'include') {\n return (\n sel: (t: Record<string, string>) => string,\n customize?: (q: SchemaQueryBuilder<any, any>) => void\n ) => {\n const name = sel(getEntityKeyTree(entity));\n (\n sqb as unknown as {\n include: (\n n: string,\n c?: (\n q: SchemaQueryBuilder<any, any>\n ) => void\n ) => void;\n }\n ).include(name, customize);\n return proxy;\n };\n }\n if (prop === 'includeVariant') {\n return (\n variantKey: string,\n relationName: string,\n customize?: (q: SchemaQueryBuilder<any, any>) => void\n ) => {\n (\n sqb as unknown as {\n includeVariant: (\n v: string,\n r: string,\n c?: (\n q: SchemaQueryBuilder<any, any>\n ) => void\n ) => void;\n }\n ).includeVariant(variantKey, relationName, customize);\n return proxy;\n };\n }\n if (prop === '_sqb') return sqb;\n if (prop === '_entity') return entity;\n\n if (\n prop === 'find' ||\n prop === 'findOrFail' ||\n prop === 'findMany'\n ) {\n return makeFindMethod(\n prop as 'find' | 'findOrFail' | 'findMany',\n sqb as unknown as SchemaQueryBuilder<any, any>,\n entity,\n proxy as unknown as EntityQuery<TEntity, TResult>\n );\n }\n\n // Guard: block direct update/delete/insert on polymorphic\n // entities — callers must use db.set.ofVariant('key').op().\n if (\n prop === 'update' ||\n prop === 'delete' ||\n prop === 'insert'\n ) {\n if (getVariants((entity as any).schema)) {\n return () => {\n throw new Error(\n `${String(prop)}() cannot be called directly on a polymorphic entity set. ` +\n `Use db.set.ofVariant('key').${String(prop)}() instead.`\n );\n };\n }\n }\n\n const value = Reflect.get(target, prop, receiver);\n if (typeof value !== 'function') return value;\n return function (this: unknown, ...args: unknown[]) {\n const result = (value as Function).apply(sqb, args);\n // Re-wrap `this`-returning methods so chains keep typing.\n if (result === sqb) return proxy;\n // Wrap Promise results to auto-attach tracked entities.\n if (\n onResults != null &&\n result != null &&\n typeof (result as any).then === 'function'\n ) {\n return (result as Promise<unknown>).then(\n (resolved: unknown) => {\n if (Array.isArray(resolved)) {\n const entities = resolved.filter(\n r =>\n r !== null &&\n r !== undefined &&\n typeof r === 'object'\n );\n if (entities.length > 0) {\n const mapped = onResults(entities);\n if (mapped) {\n const entityToMapped = new Map<\n unknown,\n unknown\n >();\n for (\n let i = 0;\n i < entities.length;\n i++\n ) {\n entityToMapped.set(\n entities[i],\n mapped[i]\n );\n }\n return resolved.map(\n r => entityToMapped.get(r) ?? r\n );\n }\n }\n } else if (\n resolved !== null &&\n resolved !== undefined &&\n typeof resolved === 'object'\n ) {\n const mapped = onResults([resolved]);\n if (mapped && mapped.length > 0)\n return mapped[0];\n }\n return resolved;\n }\n );\n }\n return result;\n };\n }\n }\n ) as unknown as EntityQuery<TEntity, TResult>;\n return proxy;\n}\n\n// ---------------------------------------------------------------------------\n// find / findOrFail / findMany\n// ---------------------------------------------------------------------------\n\n/**\n * Build the runtime implementation of `find` / `findOrFail` / `findMany`\n * for a given underlying `SchemaQueryBuilder` and entity.\n *\n * Honours composite primary keys; `pks`/`pk` are accepted as either scalars\n * (single-column PK) or tuples (composite PK).\n *\n * @internal\n */\nfunction makeFindMethod<TEntity extends Entity<any, any, any>>(\n method: 'find' | 'findOrFail' | 'findMany',\n sqb: SchemaQueryBuilder<any, any>,\n entity: TEntity,\n proxy: EntityQuery<TEntity, unknown>\n): (...args: unknown[]) => Promise<unknown> {\n return async (...args: unknown[]): Promise<unknown> => {\n const pkInfo = getPrimaryKeyColumns(\n entity.schema as Parameters<typeof getPrimaryKeyColumns>[0]\n );\n if (pkInfo.propertyKeys.length === 0) {\n throw new Error(\n `${method}(): entity has no primary key declared. ` +\n `Use \\`.primaryKey()\\` on a column or \\`.hasPrimaryKey([...])\\` on the schema.`\n );\n }\n const propertyKeys = pkInfo.propertyKeys;\n const isComposite = propertyKeys.length > 1;\n const tableName =\n (\n entity.schema as {\n getExtension?: (k: string) => unknown;\n }\n ).getExtension?.('tableName') ?? '<entity>';\n const entityLabel = String(tableName);\n\n const applyPkFilter = (pk: unknown): void => {\n const tuple = normalisePkTuple(pk, isComposite, method);\n if (tuple.length !== propertyKeys.length) {\n throw new Error(\n `${method}(): expected ${propertyKeys.length} primary-key value(s), got ${tuple.length}.`\n );\n }\n for (let i = 0; i < propertyKeys.length; i++) {\n (\n proxy as unknown as {\n andWhere: (\n col: string,\n op: string,\n val: unknown\n ) => void;\n }\n ).andWhere(propertyKeys[i], '=', tuple[i]);\n }\n };\n\n if (method === 'findMany') {\n const pks = (args[0] ?? []) as ReadonlyArray<unknown>;\n if (!Array.isArray(pks)) {\n throw new Error(\n 'findMany(): expected an array of primary-key values.'\n );\n }\n if (pks.length === 0) return [];\n if (!isComposite) {\n const propKey = propertyKeys[0];\n const tuples = pks.map(p =>\n normalisePkTuple(p, false, 'findMany')\n );\n (\n proxy as unknown as {\n whereIn: (\n col: string,\n vals: readonly unknown[]\n ) => void;\n }\n ).whereIn(\n propKey,\n tuples.map(t => t[0])\n );\n return await (\n proxy as unknown as { execute: () => Promise<unknown[]> }\n ).execute();\n }\n // Composite PK — emit OR-grouped predicates. We must use\n // COLUMN names (not property names) here because the inner\n // knex `apply()` callback bypasses SchemaQueryBuilder's\n // property-to-column translation.\n const columnNames = pkInfo.columnNames;\n (\n sqb as unknown as {\n apply: (fn: (qb: Knex.QueryBuilder) => void) => void;\n }\n ).apply(qb => {\n qb.andWhere(function (this: Knex.QueryBuilder) {\n for (const pk of pks) {\n const tuple = normalisePkTuple(pk, true, 'findMany');\n this.orWhere(function (this: Knex.QueryBuilder) {\n for (let i = 0; i < columnNames.length; i++) {\n this.andWhere(columnNames[i], tuple[i] as any);\n }\n });\n }\n });\n });\n return await (\n proxy as unknown as { execute: () => Promise<unknown[]> }\n ).execute();\n }\n\n // find / findOrFail\n applyPkFilter(args[0]);\n const row = await (\n proxy as unknown as {\n first: () => Promise<unknown | undefined>;\n }\n ).first();\n if (row === undefined && method === 'findOrFail') {\n throw new EntityNotFoundError(entityLabel, args[0]);\n }\n return row;\n };\n}\n\n/**\n * Normalise a `find` / `findMany` PK argument to a tuple of values.\n * Single-column PKs accept either a scalar or a length-1 tuple; composite\n * PKs require a tuple.\n *\n * @internal\n */\nfunction normalisePkTuple(\n pk: unknown,\n isComposite: boolean,\n method: string\n): readonly unknown[] {\n if (isComposite) {\n if (!Array.isArray(pk)) {\n throw new Error(\n `${method}(): composite primary key requires a tuple argument.`\n );\n }\n return pk as readonly unknown[];\n }\n if (Array.isArray(pk)) return pk as readonly unknown[];\n return [pk];\n}\n\n/**\n * Construct a typed {@link DbSet} for an entity, bound to a Knex instance.\n *\n * @internal — use {@link createDb} for normal application code.\n */\nexport function makeDbSet<TEntity extends Entity<any, any, any>>(\n knex: Knex,\n entity: TEntity,\n onResults?: (items: unknown[]) => unknown[] | undefined\n): DbSet<TEntity> {\n const proxy: DbSet<TEntity> = new Proxy({} as object, {\n get(_target, prop) {\n if (prop === 'entity') return entity;\n if (prop === 'query') {\n return () => {\n const sqb = schemaQuery(\n knex,\n entity.schema as EntitySchema<TEntity>\n );\n return wrapQuery(\n sqb as SchemaQueryBuilder<\n EntitySchema<TEntity>,\n EntityResult<TEntity>\n >,\n entity,\n knex,\n onResults\n );\n };\n }\n if (prop === 'withTransaction') {\n return (trx: Knex.Transaction) =>\n makeDbSet(trx as unknown as Knex, entity, onResults);\n }\n if (prop === 'save') {\n return (graph: Record<string, unknown>) => {\n // If `knex` is already a transaction handle (from\n // `withTransaction`), reuse it; otherwise saveGraph\n // opens a fresh transaction internally.\n const maybeTrx = (\n knex as unknown as { isTransaction?: boolean }\n ).isTransaction\n ? (knex as unknown as Knex.Transaction)\n : undefined;\n return saveGraph(\n knex,\n entity.schema,\n graph,\n maybeTrx\n ) as Promise<unknown>;\n };\n }\n if (prop === 'ofVariant') {\n return (variantKey: string) => {\n if (!getVariants((entity as any).schema)) {\n throw new Error(\n `ofVariant(): entity schema is not polymorphic (no variants declared).`\n );\n }\n return makeVariantDbSet(\n knex,\n entity,\n variantKey,\n onResults\n );\n };\n }\n // Any other property/method: allocate a fresh query and\n // forward. Methods are bound so callers can use `.method(...)`.\n const fresh = schemaQuery(\n knex,\n entity.schema as EntitySchema<TEntity>\n );\n const query = wrapQuery(\n fresh as SchemaQueryBuilder<\n EntitySchema<TEntity>,\n EntityResult<TEntity>\n >,\n entity,\n knex,\n onResults\n );\n const value = (\n query as unknown as Record<string | symbol, unknown>\n )[prop];\n if (typeof value !== 'function') return value;\n return (value as Function).bind(query);\n }\n }) as DbSet<TEntity>;\n return proxy;\n}\n\n// ---------------------------------------------------------------------------\n// VariantDbSet runtime\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap an SQB (which must already have `selectVariants([variantKey])` applied)\n * in a proxy that routes `insert`, `update`, and `delete` through the\n * variant-aware helpers, and re-wraps `this`-returning chain methods so that\n * chains stay within the variant proxy rather than escaping to a plain\n * `SchemaQueryBuilder`.\n *\n * @internal\n */\nfunction wrapVariantQuery<\n TEntity extends Entity<any, any, any>,\n K extends string\n>(\n sqb: SchemaQueryBuilder<EntitySchema<TEntity>, VariantResult<TEntity, K>>,\n entity: TEntity,\n variantKey: K,\n knexInst: Knex,\n onResults?: (items: unknown[]) => unknown[] | undefined\n): VariantDbSet<TEntity, K> {\n const proxy: VariantDbSet<TEntity, K> = new Proxy(\n sqb as unknown as object,\n {\n get(target, prop, receiver) {\n // --- Typed include override (same as wrapQuery) ---\n if (prop === 'include') {\n return (\n sel: (t: Record<string, string>) => string,\n customize?: (q: SchemaQueryBuilder<any, any>) => void\n ) => {\n const name = sel(getEntityKeyTree(entity));\n (\n sqb as unknown as {\n include: (\n n: string,\n c?: (\n q: SchemaQueryBuilder<any, any>\n ) => void\n ) => void;\n }\n ).include(name, customize);\n return proxy;\n };\n }\n if (prop === 'includeVariant') {\n return (\n vk: string,\n relationName: string,\n customize?: (q: SchemaQueryBuilder<any, any>) => void\n ) => {\n (\n sqb as unknown as {\n includeVariant: (\n v: string,\n r: string,\n c?: (\n q: SchemaQueryBuilder<any, any>\n ) => void\n ) => void;\n }\n ).includeVariant(vk, relationName, customize);\n return proxy;\n };\n }\n\n if (prop === '_sqb') return sqb;\n if (prop === '_entity') return entity;\n\n // --- find* delegated to the shared helper ---\n if (\n prop === 'find' ||\n prop === 'findOrFail' ||\n prop === 'findMany'\n ) {\n return makeFindMethod(\n prop as 'find' | 'findOrFail' | 'findMany',\n sqb as unknown as SchemaQueryBuilder<any, any>,\n entity,\n proxy as unknown as EntityQuery<\n TEntity,\n VariantResult<TEntity, K>\n >\n );\n }\n\n // --- Variant-aware insert ---\n if (prop === 'insert') {\n return async (\n payload: Record<string, unknown>\n ): Promise<unknown> => {\n const maybeTrx = (\n knexInst as unknown as { isTransaction?: boolean }\n ).isTransaction\n ? (knexInst as unknown as Knex.Transaction)\n : undefined;\n const result = await _insertVariant(\n knexInst,\n entity.schema,\n variantKey,\n payload,\n maybeTrx\n );\n if (\n onResults &&\n result != null &&\n typeof result === 'object'\n ) {\n onResults([result]);\n }\n return result;\n };\n }\n\n // --- Variant-aware update (collect PKs first, then UPDATE) ---\n if (prop === 'update') {\n return async (\n set: Record<string, unknown>\n ): Promise<void> => {\n const pkInfo = getPrimaryKeyColumns(\n entity.schema as Parameters<\n typeof getPrimaryKeyColumns\n >[0]\n );\n const rows = (await (\n proxy as unknown as {\n execute: () => Promise<\n Array<Record<string, unknown>>\n >;\n }\n ).execute()) as Array<Record<string, unknown>>;\n const pkProp = pkInfo.propertyKeys[0];\n const pkValues = rows\n .map(r => r[pkProp])\n .filter(v => v !== undefined);\n const maybeTrx = (\n knexInst as unknown as { isTransaction?: boolean }\n ).isTransaction\n ? (knexInst as unknown as Knex.Transaction)\n : undefined;\n await _updateVariant(\n knexInst,\n entity.schema,\n variantKey,\n set,\n pkValues,\n maybeTrx\n );\n };\n }\n\n // --- Variant-aware delete (collect PKs first, then DELETE) ---\n if (prop === 'delete') {\n return async (): Promise<void> => {\n const pkInfo = getPrimaryKeyColumns(\n entity.schema as Parameters<\n typeof getPrimaryKeyColumns\n >[0]\n );\n const rows = (await (\n proxy as unknown as {\n execute: () => Promise<\n Array<Record<string, unknown>>\n >;\n }\n ).execute()) as Array<Record<string, unknown>>;\n const pkProp = pkInfo.propertyKeys[0];\n const pkValues = rows\n .map(r => r[pkProp])\n .filter(v => v !== undefined);\n const maybeTrx = (\n knexInst as unknown as { isTransaction?: boolean }\n ).isTransaction\n ? (knexInst as unknown as Knex.Transaction)\n : undefined;\n await _deleteVariant(\n knexInst,\n entity.schema,\n variantKey,\n pkValues,\n maybeTrx\n );\n };\n }\n\n // --- Catch-all: forward to SQB; re-wrap this-returning calls ---\n const value = Reflect.get(target, prop, receiver);\n if (typeof value !== 'function') return value;\n return function (this: unknown, ...args: unknown[]) {\n const result = (value as Function).apply(sqb, args);\n if (result === sqb) return proxy;\n if (\n onResults != null &&\n result != null &&\n typeof (result as any).then === 'function'\n ) {\n return (result as Promise<unknown>).then(\n (resolved: unknown) => {\n if (Array.isArray(resolved)) {\n const entities = resolved.filter(\n r =>\n r !== null &&\n r !== undefined &&\n typeof r === 'object'\n );\n if (entities.length > 0) {\n const mapped = onResults(entities);\n if (mapped) {\n const entityToMapped = new Map<\n unknown,\n unknown\n >();\n for (\n let i = 0;\n i < entities.length;\n i++\n ) {\n entityToMapped.set(\n entities[i],\n mapped[i]\n );\n }\n return resolved.map(\n r => entityToMapped.get(r) ?? r\n );\n }\n }\n } else if (\n resolved !== null &&\n resolved !== undefined &&\n typeof resolved === 'object'\n ) {\n const mapped = onResults([resolved]);\n if (mapped && mapped.length > 0)\n return mapped[0];\n }\n return resolved;\n }\n );\n }\n return result;\n };\n }\n }\n ) as unknown as VariantDbSet<TEntity, K>;\n return proxy;\n}\n\n/**\n * Construct a typed {@link VariantDbSet} scoped to `variantKey`.\n *\n * Every method invocation allocates a fresh `SchemaQueryBuilder` with\n * `selectVariants([variantKey])` pre-applied so that reads are automatically\n * filtered to the correct discriminator value.\n *\n * @internal — obtain via `DbSet.ofVariant('key')`.\n */\nfunction makeVariantDbSet<\n TEntity extends Entity<any, any, any>,\n K extends string\n>(\n knex: Knex,\n entity: TEntity,\n variantKey: K,\n onResults?: (items: unknown[]) => unknown[] | undefined\n): VariantDbSet<TEntity, K> {\n const proxy: VariantDbSet<TEntity, K> = new Proxy({} as object, {\n get(_target, prop) {\n if (prop === 'withTransaction') {\n return (trx: Knex.Transaction) =>\n makeVariantDbSet(\n trx as unknown as Knex,\n entity,\n variantKey,\n onResults\n );\n }\n // Allocate a fresh SQB with the variant filter baked in, then\n // wrap it in the variant-aware query proxy.\n const fresh = schemaQuery(\n knex,\n entity.schema as EntitySchema<TEntity>\n );\n (\n fresh as unknown as {\n selectVariants?: (keys: string[]) => void;\n }\n ).selectVariants?.([variantKey]);\n const query = wrapVariantQuery(\n fresh as SchemaQueryBuilder<\n EntitySchema<TEntity>,\n VariantResult<TEntity, K>\n >,\n entity,\n variantKey,\n knex,\n onResults\n );\n const value = (\n query as unknown as Record<string | symbol, unknown>\n )[prop];\n if (typeof value !== 'function') return value;\n return (value as Function).bind(query);\n }\n }) as VariantDbSet<TEntity, K>;\n return proxy;\n}\n","/**\n * Save-graph executor: persists a nested write graph (root + related\n * entities) within a single transaction, deciding insert vs update from\n * the presence of primary-key fields.\n *\n * Topology:\n * 1. `belongsTo` parents are saved first; their generated PKs are written\n * into the root's foreign-key columns.\n * 2. The root row is then inserted or updated.\n * 3. `hasOne` / `hasMany` children are saved with the root's PK propagated\n * into their foreign-key column.\n * 4. `belongsToMany` children are saved, then through-table rows written.\n *\n * Hooks (`beforeInsert` / `afterInsert` / `beforeUpdate`) fire naturally\n * because all writes go through `SchemaQueryBuilder`.\n *\n * @packageDocumentation\n */\n\nimport {\n buildColumnMap,\n getPrimaryKeyColumns,\n query as schemaQuery\n} from '@cleverbrush/knex-schema';\nimport type { Knex } from 'knex';\n\n/**\n * Spec entry persisted by `Entity.hasOne/hasMany/belongsTo/belongsToMany`\n * onto the schema's `'relations'` extension.\n *\n * @internal\n */\ninterface RelationSpec {\n readonly type: 'belongsTo' | 'hasOne' | 'hasMany' | 'belongsToMany';\n readonly name: string;\n readonly schema: any;\n readonly foreignKey?: string;\n readonly through?: {\n readonly table: string;\n readonly localKey: string;\n readonly foreignKey: string;\n };\n}\n\n/**\n * @internal Read the relations extension from a schema, returning `[]`\n * when none is declared.\n */\nfunction getRelations(schema: any): readonly RelationSpec[] {\n const ext =\n (schema?.getExtension?.('relations') as RelationSpec[] | undefined) ??\n [];\n return ext;\n}\n\n/**\n * @internal Resolve a property name to its column name; falls back to the\n * input when the schema doesn't declare an explicit column mapping.\n */\nfunction colFor(schema: any, prop: string): string {\n const { propToCol } = buildColumnMap(schema);\n return propToCol.get(prop) ?? prop;\n}\n\n/**\n * @internal Whether a graph node already carries values for every PK\n * column declared on the schema. Used to decide insert vs update.\n */\nfunction hasFullPk(schema: any, node: Record<string, unknown>): boolean {\n const pk = getPrimaryKeyColumns(schema);\n if (pk.propertyKeys.length === 0) return false;\n return pk.propertyKeys.every(k => node[k] !== undefined);\n}\n\n/**\n * Persist a graph rooted at `node` whose shape matches `schema` (and its\n * declared relations). All writes are funnelled through `trx` so the\n * caller can compose this with an outer transaction.\n *\n * @internal — call via `DbSet.save()`.\n */\nexport async function saveGraph(\n knex: Knex,\n schema: any,\n node: Record<string, unknown>,\n trx?: Knex.Transaction\n): Promise<Record<string, unknown>> {\n if (trx) return saveNode(trx as unknown as Knex, schema, node);\n return knex.transaction(t => saveNode(t as unknown as Knex, schema, node));\n}\n\n/**\n * @internal Recursive worker. Operates on the supplied `knex` (which is\n * already a transaction handle inside `saveGraph`).\n */\nasync function saveNode(\n trx: Knex,\n schema: any,\n node: Record<string, unknown>\n): Promise<Record<string, unknown>> {\n const relations = getRelations(schema);\n const relByName = new Map<string, RelationSpec>();\n for (const r of relations) relByName.set(r.name, r);\n\n // Split node into own fields vs nested relation values.\n const ownFields: Record<string, unknown> = {};\n const nested: Record<string, { spec: RelationSpec; value: unknown }> = {};\n for (const [k, v] of Object.entries(node)) {\n const spec = relByName.get(k);\n if (spec) {\n nested[k] = { spec, value: v };\n } else {\n ownFields[k] = v;\n }\n }\n\n // ---------------------------------------------------------------------\n // 1. belongsTo parents — save first; copy their PK into our FK column.\n // ---------------------------------------------------------------------\n for (const { spec, value } of Object.values(nested)) {\n if (spec.type !== 'belongsTo') continue;\n if (value == null) continue;\n if (typeof value !== 'object' || Array.isArray(value)) {\n throw new Error(\n `save: relation \"${spec.name}\" expects an object (belongsTo).`\n );\n }\n const savedParent = await saveNode(\n trx,\n spec.schema,\n value as Record<string, unknown>\n );\n const parentPk = getPrimaryKeyColumns(spec.schema);\n if (parentPk.propertyKeys.length !== 1) {\n throw new Error(\n `save: belongsTo \"${spec.name}\" requires the parent ` +\n `entity to have a single-column primary key.`\n );\n }\n const parentPkVal = savedParent[parentPk.propertyKeys[0]];\n if (!spec.foreignKey) {\n throw new Error(\n `save: belongsTo \"${spec.name}\" is missing a foreignKey.`\n );\n }\n // Resolve property-name FK to a property on this schema that the\n // SchemaQueryBuilder will translate to a column at write time.\n ownFields[spec.foreignKey] = parentPkVal;\n }\n\n // ---------------------------------------------------------------------\n // 2. Save self (insert vs update from PK presence).\n // ---------------------------------------------------------------------\n const isUpdate = hasFullPk(schema, ownFields);\n const sqb = schemaQuery(trx, schema) as unknown as {\n andWhere: (col: string, op: string, val: unknown) => unknown;\n update: (data: unknown) => Promise<unknown>;\n insert: (data: unknown) => Promise<unknown>;\n };\n\n let saved: Record<string, unknown>;\n\n if (isUpdate) {\n const pk = getPrimaryKeyColumns(schema);\n const updateData: Record<string, unknown> = { ...ownFields };\n for (const k of pk.propertyKeys) delete updateData[k];\n for (const k of pk.propertyKeys) {\n sqb.andWhere(k, '=', ownFields[k]);\n }\n // SchemaQueryBuilder.update returns the updated row(s).\n const result = (await sqb.update(updateData)) as unknown;\n if (Array.isArray(result)) {\n saved = (result[0] ?? ownFields) as Record<string, unknown>;\n } else if (result && typeof result === 'object') {\n saved = result as Record<string, unknown>;\n } else {\n saved = ownFields;\n }\n // Re-merge PK fields in case `update` returned a partial row.\n for (const k of pk.propertyKeys) saved[k] = ownFields[k];\n } else {\n const inserted = (await sqb.insert(ownFields)) as Record<\n string,\n unknown\n >;\n saved = inserted ?? ownFields;\n }\n\n // Pull back our own PK so children can reference it.\n const ownPk = getPrimaryKeyColumns(schema);\n if (ownPk.propertyKeys.length === 1) {\n const k = ownPk.propertyKeys[0];\n if (saved[k] === undefined && ownFields[k] !== undefined) {\n saved[k] = ownFields[k];\n }\n }\n\n // ---------------------------------------------------------------------\n // 3. hasOne / hasMany / belongsToMany children.\n // ---------------------------------------------------------------------\n for (const [name, { spec, value }] of Object.entries(nested)) {\n if (spec.type === 'belongsTo') continue;\n if (value == null) continue;\n\n if (spec.type === 'hasOne') {\n if (typeof value !== 'object' || Array.isArray(value)) {\n throw new Error(\n `save: relation \"${name}\" expects a single object (hasOne).`\n );\n }\n const ownSinglePk = ownPk.propertyKeys[0];\n if (ownPk.propertyKeys.length !== 1) {\n throw new Error(\n `save: hasOne \"${name}\" requires this entity to have ` +\n `a single-column primary key.`\n );\n }\n const child = { ...(value as Record<string, unknown>) };\n if (spec.foreignKey) child[spec.foreignKey] = saved[ownSinglePk];\n const savedChild = await saveNode(trx, spec.schema, child);\n saved[name] = savedChild;\n continue;\n }\n\n if (spec.type === 'hasMany') {\n if (!Array.isArray(value)) {\n throw new Error(\n `save: relation \"${name}\" expects an array (hasMany).`\n );\n }\n const ownSinglePk = ownPk.propertyKeys[0];\n if (ownPk.propertyKeys.length !== 1) {\n throw new Error(\n `save: hasMany \"${name}\" requires this entity to have ` +\n `a single-column primary key.`\n );\n }\n const savedChildren: Record<string, unknown>[] = [];\n for (const child of value as Record<string, unknown>[]) {\n const childCopy = { ...child };\n if (spec.foreignKey) {\n childCopy[spec.foreignKey] = saved[ownSinglePk];\n }\n savedChildren.push(await saveNode(trx, spec.schema, childCopy));\n }\n saved[name] = savedChildren;\n continue;\n }\n\n // belongsToMany: insert children first, then write through-table rows.\n if (spec.type === 'belongsToMany') {\n if (!spec.through) {\n throw new Error(\n `save: belongsToMany \"${name}\" is missing a through config.`\n );\n }\n if (!Array.isArray(value)) {\n throw new Error(\n `save: relation \"${name}\" expects an array (belongsToMany).`\n );\n }\n const ownSinglePk = ownPk.propertyKeys[0];\n if (ownPk.propertyKeys.length !== 1) {\n throw new Error(\n `save: belongsToMany \"${name}\" requires this entity ` +\n `to have a single-column primary key.`\n );\n }\n const savedChildren: Record<string, unknown>[] = [];\n for (const child of value as Record<string, unknown>[]) {\n const childPk = getPrimaryKeyColumns(spec.schema);\n if (childPk.propertyKeys.length !== 1) {\n throw new Error(\n `save: belongsToMany \"${name}\" requires the ` +\n `child entity to have a single-column primary key.`\n );\n }\n const childKey = childPk.propertyKeys[0];\n let savedChild: Record<string, unknown>;\n if (child[childKey] !== undefined) {\n // Reference-only: use as-is, do NOT touch the child row.\n savedChild = { ...child };\n } else {\n savedChild = await saveNode(trx, spec.schema, child);\n }\n savedChildren.push(savedChild);\n\n // Insert into the through table. Skip duplicates silently;\n // typical pivot tables have a composite PK on (a,b).\n await trx(spec.through.table)\n .insert({\n [spec.through.localKey]: saved[ownSinglePk],\n [spec.through.foreignKey]: savedChild[childKey]\n })\n .onConflict([\n spec.through.localKey,\n spec.through.foreignKey\n ])\n .ignore();\n }\n saved[name] = savedChildren;\n }\n }\n\n // Silence unused-var warning for `colFor` exported helper (reserved\n // for future column-name normalisation when relations expose column\n // names rather than property names).\n void colFor;\n\n return saved;\n}\n","// @cleverbrush/orm — Polymorphic write helpers\n//\n// Runtime implementations for `insertVariant`, `updateVariant`,\n// `deleteVariant`, and `findVariant`. These are invoked from `DbSet` /\n// `EntityQuery` and handle the two-table atomicity required for CTI\n// (Class Table Inheritance) variants.\n\nimport {\n buildColumnMap,\n getPrimaryKeyColumns,\n getVariants,\n query as schemaQuery\n} from '@cleverbrush/knex-schema';\nimport type { Knex } from 'knex';\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the variant config for a schema, throwing if the schema is not\n * polymorphic or if the requested variant key is unknown.\n * @internal\n */\nfunction requireVariantSpec(schema: any, variantKey: string) {\n const raw = getVariants(schema);\n if (!raw) {\n throw new Error(\n `insertVariant / deleteVariant / updateVariant / findVariant: ` +\n `entity schema is not polymorphic (no variants declared).`\n );\n }\n const spec = raw.variants[variantKey];\n if (!spec) {\n const known = Object.keys(raw.variants).join(', ');\n throw new Error(\n `Variant key \"${variantKey}\" is unknown. Known variants: ${known}.`\n );\n }\n return { config: raw, spec };\n}\n\n/**\n * Derive the SQL discriminator column name from the schema's propToCol map.\n * @internal\n */\nfunction _resolveDiscriminatorColumn(\n schema: any,\n discriminatorKey: string\n): string {\n const { propToCol } = buildColumnMap(schema);\n return propToCol.get(discriminatorKey) ?? discriminatorKey;\n}\n\n// ---------------------------------------------------------------------------\n// insertVariant\n// ---------------------------------------------------------------------------\n\n/**\n * Transactionally insert a polymorphic entity row.\n *\n * For CTI variants:\n * 1. Inserts the base row with `discriminatorColumn = variantKey` and all\n * base-table payload columns; captures the auto-generated PK.\n * 2. Inserts the variant row with the FK set to the base PK and all\n * variant-table payload columns.\n *\n * For STI variants:\n * - Inserts a single row into the base table with the discriminator set.\n *\n * @internal\n */\nexport async function insertVariant(\n knex: Knex,\n schema: any,\n variantKey: string,\n payload: Record<string, unknown>,\n trx?: Knex.Transaction\n): Promise<Record<string, unknown>> {\n // Validate before opening a transaction so errors surface immediately.\n const { config, spec } = requireVariantSpec(schema, variantKey);\n\n const run = async (t: Knex): Promise<Record<string, unknown>> => {\n const discKey = config.discriminatorKey;\n const baseTableName = schema.getExtension?.('tableName') as string;\n if (!baseTableName) {\n throw new Error('insertVariant: base schema has no table name.');\n }\n\n if (spec.storage === 'sti') {\n // Single-table: insert into base table with discriminator column\n const row = { ...payload, [discKey]: variantKey };\n const sqb = schemaQuery(t, schema) as unknown as {\n insert: (data: unknown) => Promise<Record<string, unknown>>;\n };\n return (await sqb.insert(row)) ?? row;\n }\n\n // CTI: two-table insert\n const variantSchema = spec.schema;\n const variantTableName = spec.tableName as string;\n const fkCol = spec.foreignKey as string;\n\n // Split payload between base-schema columns and variant-schema columns\n const baseIntrospected = (schema as any).introspect?.() as {\n properties?: Record<string, unknown>;\n };\n const variantIntrospected = variantSchema.introspect?.() as {\n properties?: Record<string, unknown>;\n };\n const basePropKeys = new Set(\n Object.keys(baseIntrospected?.properties ?? {})\n );\n const variantPropKeys = new Set(\n Object.keys(variantIntrospected?.properties ?? {})\n );\n\n const basePayload: Record<string, unknown> = { [discKey]: variantKey };\n const variantPayload: Record<string, unknown> = {};\n\n for (const [key, val] of Object.entries(payload)) {\n // Discriminator is set automatically; FK is set from base PK\n if (key === discKey || key === fkCol) continue;\n if (basePropKeys.has(key)) {\n basePayload[key] = val;\n } else if (variantPropKeys.has(key)) {\n variantPayload[key] = val;\n }\n // Keys that match neither schema are silently dropped\n }\n\n // 1. Insert base row using raw knex (not SchemaQueryBuilder) to avoid\n // polymorphic result-resolution running before the variant row exists.\n const { propToCol: basePropToCol } = buildColumnMap(schema);\n const baseRowForInsert: Record<string, unknown> = {};\n for (const [propKey, val] of Object.entries(basePayload)) {\n baseRowForInsert[basePropToCol.get(propKey) ?? propKey] = val;\n }\n const baseInsertResult = await (t as unknown as Knex)(baseTableName)\n .insert(baseRowForInsert)\n .returning('*');\n const baseRow: Record<string, unknown> =\n Array.isArray(baseInsertResult) && baseInsertResult.length > 0\n ? (baseInsertResult[0] as Record<string, unknown>)\n : baseRowForInsert;\n\n // Resolve the base PK value from the returned row\n const pkInfo = getPrimaryKeyColumns(schema);\n if (pkInfo.propertyKeys.length === 0) {\n throw new Error(\n `insertVariant: base schema has no primary key declared.`\n );\n }\n if (pkInfo.propertyKeys.length > 1) {\n throw new Error(\n `insertVariant: composite primary keys are not yet supported for polymorphic CTI inserts.`\n );\n }\n const pkPropKey = pkInfo.propertyKeys[0];\n const pkColName = pkInfo.columnNames[0];\n const pkValue = baseRow[pkPropKey] ?? baseRow[pkColName];\n if (pkValue === undefined) {\n throw new Error(\n `insertVariant: could not resolve PK value from base insert result.`\n );\n }\n\n // 2. Insert variant row using raw knex (not SchemaQueryBuilder) so we\n // can set the FK column even if it isn't in the schema's column map.\n const { propToCol: varPropToCol } = buildColumnMap(variantSchema);\n const variantRow: Record<string, unknown> = { [fkCol]: pkValue };\n for (const [propKey, val] of Object.entries(variantPayload)) {\n const colName = varPropToCol.get(propKey) ?? propKey;\n variantRow[colName] = val;\n }\n const discColInVariant = varPropToCol.get(discKey);\n if (discColInVariant) {\n variantRow[discColInVariant] = variantKey;\n }\n await (t as unknown as Knex)(variantTableName).insert(variantRow);\n\n // 3. Merge and return\n const { colToProp: baseColToProp } = buildColumnMap(schema);\n const result: Record<string, unknown> = {};\n for (const [col, val] of Object.entries(baseRow)) {\n result[baseColToProp.get(col) ?? col] = val;\n }\n // Ensure discriminator and variant payload are in the result\n result[discKey] = variantKey;\n for (const [propKey, val] of Object.entries(variantPayload)) {\n result[propKey] = val;\n }\n return result;\n };\n\n if (trx) return run(trx as unknown as Knex);\n return (knex as Knex).transaction(t => run(t as unknown as Knex));\n}\n\n// ---------------------------------------------------------------------------\n// updateVariant\n// ---------------------------------------------------------------------------\n\n/**\n * Update variant-specific columns for all rows matching the current query\n * whose discriminator equals `variantKey`.\n *\n * For CTI: runs UPDATE on the variant table, joining on the FK = base PK.\n * For STI: runs UPDATE on the base table filtered by discriminator.\n *\n * The `getPks` callback must return the PK values of all rows the caller's\n * WHERE clause matched (resolved before this function is called).\n *\n * @internal\n */\nexport async function updateVariant(\n knex: Knex,\n schema: any,\n variantKey: string,\n set: Record<string, unknown>,\n pkValues: readonly unknown[],\n trx?: Knex.Transaction\n): Promise<void> {\n if (pkValues.length === 0) return;\n\n const db: Knex = (trx as unknown as Knex) ?? knex;\n const { config, spec } = requireVariantSpec(schema, variantKey);\n const discKey = config.discriminatorKey;\n\n if (spec.storage === 'sti') {\n // STI: update the base table restricted to the discriminator value\n const { propToCol } = buildColumnMap(schema);\n const discCol = propToCol.get(discKey) ?? discKey;\n const pkInfo = getPrimaryKeyColumns(schema);\n if (pkInfo.columnNames.length === 0) {\n throw new Error(\n `updateVariant: base schema has no primary key declared.`\n );\n }\n if (pkInfo.columnNames.length > 1) {\n throw new Error(\n `updateVariant: composite primary keys are not supported for STI updates.`\n );\n }\n const pkColName = pkInfo.columnNames[0];\n const baseTable = schema.getExtension?.('tableName') as string;\n\n const updateData: Record<string, unknown> = {};\n for (const [propKey, val] of Object.entries(set)) {\n updateData[propToCol.get(propKey) ?? propKey] = val;\n }\n\n await db(baseTable)\n .whereIn(pkColName, pkValues as any[])\n .andWhere(discCol, variantKey)\n .update(updateData);\n return;\n }\n\n // CTI: update the variant table\n const variantSchema = spec.schema;\n const variantTableName = spec.tableName as string;\n const fkCol = spec.foreignKey as string;\n const { propToCol: varPropToCol } = buildColumnMap(variantSchema);\n\n const updateData: Record<string, unknown> = {};\n for (const [propKey, val] of Object.entries(set)) {\n // Don't allow updating the FK (it's the join column)\n if (propKey === fkCol) continue;\n updateData[varPropToCol.get(propKey) ?? propKey] = val;\n }\n\n if (Object.keys(updateData).length === 0) return;\n\n await db(variantTableName)\n .whereIn(fkCol, pkValues as any[])\n .update(updateData);\n}\n\n// ---------------------------------------------------------------------------\n// deleteVariant\n// ---------------------------------------------------------------------------\n\n/**\n * Delete rows for a specific variant key, given their base-table PK values.\n *\n * For CTI: deletes variant rows first (FK-constraint order), then base rows.\n * For STI: deletes the single base-table row filtered by discriminator.\n *\n * @internal\n */\nexport async function deleteVariant(\n knex: Knex,\n schema: any,\n variantKey: string,\n pkValues: readonly unknown[],\n trx?: Knex.Transaction\n): Promise<void> {\n if (pkValues.length === 0) return;\n\n const run = async (t: Knex): Promise<void> => {\n const { config, spec } = requireVariantSpec(schema, variantKey);\n const discKey = config.discriminatorKey;\n const baseTable = schema.getExtension?.('tableName') as string;\n const pkInfo = getPrimaryKeyColumns(schema);\n if (pkInfo.columnNames.length === 0) {\n throw new Error(\n `deleteVariant: base schema has no primary key declared.`\n );\n }\n if (pkInfo.columnNames.length > 1) {\n throw new Error(\n `deleteVariant: composite primary keys are not supported for variant deletes.`\n );\n }\n const pkColName = pkInfo.columnNames[0];\n\n if (spec.storage === 'sti') {\n const { propToCol } = buildColumnMap(schema);\n const discCol = propToCol.get(discKey) ?? discKey;\n await (t as unknown as Knex)(baseTable)\n .whereIn(pkColName, pkValues as any[])\n .andWhere(discCol, variantKey)\n .delete();\n return;\n }\n\n // CTI: variant rows first, then base rows\n const variantTableName = spec.tableName as string;\n const fkCol = spec.foreignKey as string;\n\n await (t as unknown as Knex)(variantTableName)\n .whereIn(fkCol, pkValues as any[])\n .delete();\n\n await (t as unknown as Knex)(baseTable)\n .whereIn(pkColName, pkValues as any[])\n .delete();\n };\n\n if (trx) return run(trx as unknown as Knex);\n return (knex as Knex).transaction(t => run(t as unknown as Knex));\n}\n","// @cleverbrush/orm — DbContext\n//\n// `createDb(knex, { todos: TodoEntity, users: UserEntity })` returns a\n// strongly typed context object whose properties are typed `DbSet`s for\n// each registered entity.\n//\n// Pass `{ tracking: true }` as the third argument to get a `TrackedDbContext`\n// that adds identity-map tracking, `saveChanges`, `discardChanges`, `reload`,\n// `attach`, `detach`, `entry`, and `[Symbol.asyncDispose]`.\n\nimport type { Entity } from '@cleverbrush/knex-schema';\nimport type { Knex } from 'knex';\nimport {\n ChangeTracker,\n type EntityEntry,\n type EntitySetConfig,\n type SavingChangesHook\n} from './change-tracker.js';\nimport { type DbSet, makeDbSet } from './dbset.js';\nimport { PendingChangesError } from './errors.js';\n\n/**\n * The map of entity name → entity definition passed to {@link createDb}.\n *\n * @public\n */\nexport type EntityMap = Record<string, Entity<any, any, any>>;\n\n/**\n * The shape of the context object returned by {@link createDb}: every\n * registered entity becomes a typed `DbSet` property.\n *\n * @public\n */\nexport type DbContext<TMap extends EntityMap> = {\n readonly [K in keyof TMap]: DbSet<TMap[K]>;\n} & {\n /**\n * The underlying Knex instance the context was constructed with.\n */\n readonly knex: Knex;\n\n /**\n * Run `callback` inside a Knex transaction. The callback receives a new\n * `DbContext` whose `DbSet`s are bound to the transaction.\n */\n transaction<T>(callback: (db: DbContext<TMap>) => Promise<T>): Promise<T>;\n\n /**\n * Return a new `DbContext` whose `DbSet`s are bound to `trx`. Useful\n * when you already have a Knex transaction in scope.\n */\n withTransaction(trx: Knex.Transaction): DbContext<TMap>;\n};\n\n/**\n * Extended context returned when `{ tracking: true }` is passed to\n * {@link createDb}. Adds identity-map tracking, unit-of-work `saveChanges`,\n * and `[Symbol.asyncDispose]` support for `await using` blocks.\n *\n * @public\n */\nexport type TrackedDbContext<TMap extends EntityMap> = DbContext<TMap> & {\n /**\n * Attach an entity to the identity map so its changes will be tracked.\n * If an entity with the same primary key is already tracked, the existing\n * tracked object is returned (identity-map guarantee).\n *\n * @param entitySetKey — the property name used when registering the entity\n * (e.g. `'todos'` for `createDb(knex, { todos: TodoEntity })`).\n */\n attach<T extends object>(entitySetKey: string, entity: T): T;\n\n /**\n * Remove an entity from the identity map. Any subsequent changes to the\n * object will not be persisted by `saveChanges`.\n */\n detach(entity: object): void;\n\n /**\n * Mark a tracked entity for deletion on the next `saveChanges()` call.\n */\n remove(entity: object): void;\n\n /**\n * Get the public entry-state view for a tracked entity.\n */\n entry<T extends object>(entity: T): EntityEntry<T>;\n\n /**\n * Register a callback that is invoked for each changed entry just before\n * the changes are flushed to the database. Use to apply audit fields etc.\n */\n onSavingChanges(hook: SavingChangesHook): void;\n\n /**\n * Reload a tracked entity from the database, overwriting its current\n * values and refreshing the internal snapshot.\n */\n reload(entity: object): Promise<void>;\n\n /**\n * Discard all pending changes. Modified entries are reset to their\n * snapshots; Added entries are detached; Deleted entries are restored\n * to `Unchanged`.\n */\n discardChanges(): void;\n\n /**\n * Flush all pending changes to the database inside a single transaction.\n *\n * @returns Counts of inserted, updated, and deleted rows.\n */\n saveChanges(): Promise<{\n inserted: number;\n updated: number;\n deleted: number;\n }>;\n\n /**\n * Implements `Symbol.asyncDispose` for `await using` blocks.\n *\n * Throws {@link PendingChangesError} when there are unsaved changes at\n * the time of disposal — callers must call `saveChanges()` or\n * `discardChanges()` before the scope exits.\n */\n [Symbol.asyncDispose](): Promise<void>;\n};\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Build a non-tracking `DbContext` (existing behaviour). */\nfunction buildContext<TMap extends EntityMap>(\n knex: Knex,\n entities: TMap\n): DbContext<TMap> {\n const sets = {} as { [K in keyof TMap]: DbSet<TMap[K]> };\n for (const key of Object.keys(entities) as Array<keyof TMap>) {\n sets[key] = makeDbSet(knex, entities[key]) as DbSet<TMap[keyof TMap]>;\n }\n\n const ctx = {\n ...sets,\n knex,\n withTransaction(trx: Knex.Transaction): DbContext<TMap> {\n return buildContext(trx as unknown as Knex, entities);\n },\n async transaction<T>(\n callback: (db: DbContext<TMap>) => Promise<T>\n ): Promise<T> {\n return knex.transaction(async (trx: Knex.Transaction) => {\n return callback(buildContext(trx as unknown as Knex, entities));\n });\n }\n } satisfies DbContext<TMap>;\n\n return ctx as DbContext<TMap>;\n}\n\n/** Build a tracking `DbContext`. */\nfunction buildTrackedContext<TMap extends EntityMap>(\n knex: Knex,\n entities: TMap,\n tracker: ChangeTracker\n): TrackedDbContext<TMap> {\n const sets = {} as { [K in keyof TMap]: DbSet<TMap[K]> };\n for (const key of Object.keys(entities) as Array<keyof TMap>) {\n const entitySetKey = key as string;\n const onResults = (items: unknown[]): unknown[] => {\n return items.map(item => {\n if (item !== null && typeof item === 'object') {\n return tracker.attach(\n entitySetKey,\n item as object,\n 'Unchanged'\n );\n }\n return item;\n });\n };\n sets[key as keyof TMap] = makeDbSet(\n knex,\n entities[key],\n onResults\n ) as DbSet<TMap[keyof TMap]>;\n }\n\n const ctx: TrackedDbContext<TMap> = {\n ...(sets as { [K in keyof TMap]: DbSet<TMap[K]> }),\n knex,\n\n withTransaction(trx: Knex.Transaction): DbContext<TMap> {\n return buildTrackedContext(\n trx as unknown as Knex,\n entities,\n tracker\n );\n },\n\n async transaction<T>(\n callback: (db: DbContext<TMap>) => Promise<T>\n ): Promise<T> {\n return knex.transaction(async (trx: Knex.Transaction) => {\n return callback(\n buildTrackedContext(\n trx as unknown as Knex,\n entities,\n tracker\n )\n );\n });\n },\n\n attach<T extends object>(entitySetKey: string, entity: T): T {\n return tracker.attach(entitySetKey, entity, 'Unchanged');\n },\n\n detach(entity: object): void {\n tracker.detach(entity);\n },\n\n remove(entity: object): void {\n tracker.remove(entity);\n },\n\n entry<T extends object>(entity: T): EntityEntry<T> {\n return tracker.entry(entity);\n },\n\n onSavingChanges(hook: SavingChangesHook): void {\n tracker.onSavingChanges(hook);\n },\n\n async reload(entity: object): Promise<void> {\n return tracker.reload(entity, knex);\n },\n\n discardChanges(): void {\n tracker.discardChanges();\n },\n\n async saveChanges(): Promise<{\n inserted: number;\n updated: number;\n deleted: number;\n }> {\n return tracker.saveChanges(knex);\n },\n\n async [Symbol.asyncDispose](): Promise<void> {\n if (tracker.hasPendingChanges()) {\n const summary = tracker.pendingSummary();\n const count = parseInt(summary.match(/\\d+/)?.[0] ?? '1', 10);\n tracker.clear();\n throw new PendingChangesError(count, summary);\n }\n tracker.clear();\n }\n };\n\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Create a typed database context.\n *\n * @example\n * ```ts\n * const TodoEntity = defineEntity(TodoSchema)\n * .belongsTo(t => t.author, l => l.userId, r => r.id);\n *\n * const UserEntity = defineEntity(UserSchema);\n *\n * const db = createDb(knex, { todos: TodoEntity, users: UserEntity });\n *\n * const todo = await db.todos\n * .where(t => t.id, '=', 42)\n * .include(t => t.author)\n * .first();\n *\n * await db.transaction(async dbTrx => {\n * const u = await dbTrx.users.insert({ email: 'a@b.c', role: 'user', authProvider: 'local', createdAt: new Date() });\n * await dbTrx.todos.insert({ title: 'Hi', userId: u.id, completed: false, createdAt: new Date(), updatedAt: new Date() });\n * });\n * ```\n *\n * @public\n */\nexport function createDb<TMap extends EntityMap>(\n knex: Knex,\n entities: TMap,\n opts: { tracking: true }\n): TrackedDbContext<TMap>;\nexport function createDb<TMap extends EntityMap>(\n knex: Knex,\n entities: TMap,\n opts?: { tracking?: false | undefined }\n): DbContext<TMap>;\nexport function createDb<TMap extends EntityMap>(\n knex: Knex,\n entities: TMap,\n opts?: { tracking?: boolean }\n): DbContext<TMap> | TrackedDbContext<TMap> {\n if (opts?.tracking) {\n const tracker = new ChangeTracker();\n // Register all entity sets with the tracker\n for (const key of Object.keys(entities)) {\n const cfg: EntitySetConfig = {\n schema: entities[key].schema,\n entitySetKey: key\n };\n tracker.registerEntitySet(cfg);\n }\n return buildTrackedContext(knex, entities, tracker);\n }\n return buildContext(knex, entities);\n}\n"],"mappings":"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","names":["buildColumnMap","getPrimaryKeyColumns","getRowVersionColumn","EntityNotFoundError","entity","pk","ConcurrencyError","expected","InvariantViolationError","field","detail","PendingChangesError","pendingCount","summary","IdentityMap","#map","#registry","setKey","pkKey","inner","entry","ref","entitySetKey","entity","states","set","result","buildPkKey","pkValues","extractPkValues","schema","getPrimaryKeyColumns","k","snapshotEntity","introspected","propKeys","snap","v","isDirty","original","current","key","ChangeTracker","#identityMap","#addedWithoutPk","#hooks","#configs","config","hook","state","TRACKER_ENTRY_SYMBOL","rvCol","getRowVersionColumn","resolveVariantKey","existing","symEntry","rawEntry","field","knex","pkInfo","tableName","propToCol","colToProp","buildColumnMap","qb","i","colName","row","mapped","col","val","inserted","updated","deleted","added","modified","deleted_entries","propKey","InvariantViolationError","discKey","publicEntry","trx","returned","pkPropSet","updateData","rv","newVal","now","tableName2","ConcurrencyError","counts","parts","getPrimaryKeyColumns","getVariants","schemaQuery","buildColumnMap","getPrimaryKeyColumns","schemaQuery","getRelations","schema","colFor","prop","propToCol","hasFullPk","node","pk","k","saveGraph","knex","trx","saveNode","t","relations","relByName","ownFields","nested","v","spec","value","savedParent","parentPk","parentPkVal","isUpdate","sqb","saved","updateData","result","ownPk","name","ownSinglePk","child","savedChild","savedChildren","childCopy","childPk","childKey","buildColumnMap","getPrimaryKeyColumns","getVariants","schemaQuery","requireVariantSpec","schema","variantKey","raw","spec","known","insertVariant","knex","schema","variantKey","payload","trx","config","spec","requireVariantSpec","run","t","discKey","baseTableName","row","schemaQuery","variantSchema","variantTableName","fkCol","baseIntrospected","variantIntrospected","basePropKeys","variantPropKeys","basePayload","variantPayload","key","val","basePropToCol","buildColumnMap","baseRowForInsert","propKey","baseInsertResult","baseRow","pkInfo","getPrimaryKeyColumns","pkPropKey","pkColName","pkValue","varPropToCol","variantRow","colName","discColInVariant","baseColToProp","result","col","updateVariant","set","pkValues","db","propToCol","discCol","baseTable","updateData","deleteVariant","ENTITY_KEY_TREE_CACHE","getEntityKeyTree","entity","cached","props","tree","k","wrapQuery","sqb","_knexInst","onResults","proxy","target","prop","receiver","sel","customize","name","variantKey","relationName","makeFindMethod","getVariants","value","args","result","resolved","entities","r","mapped","entityToMapped","i","method","pkInfo","getPrimaryKeyColumns","propertyKeys","isComposite","tableName","entityLabel","applyPkFilter","pk","tuple","normalisePkTuple","pks","propKey","tuples","p","t","columnNames","qb","row","EntityNotFoundError","makeDbSet","knex","_target","schemaQuery","trx","graph","maybeTrx","saveGraph","makeVariantDbSet","fresh","query","wrapVariantQuery","knexInst","vk","payload","insertVariant","set","rows","pkProp","pkValues","v","updateVariant","deleteVariant","buildContext","knex","entities","sets","key","makeDbSet","trx","callback","buildTrackedContext","tracker","entitySetKey","onResults","items","item","entity","hook","summary","count","PendingChangesError","createDb","opts","ChangeTracker","cfg"]}
@@ -0,0 +1,154 @@
1
+ import type { Entity, EntityRelations, EntitySchema, RelationInfo } from '@cleverbrush/knex-schema';
2
+ import type { InferType, ObjectSchemaBuilder } from '@cleverbrush/schema';
3
+ /**
4
+ * The natural result row for a freshly initiated query against an entity.
5
+ *
6
+ * For ordinary entities this is `InferType<EntitySchema<T>>`. For
7
+ * polymorphic entities (declared via `.withVariants()`), this resolves to
8
+ * the discriminated union of all variant rows, so:
9
+ *
10
+ * ```ts
11
+ * const a = await db.activities.find(1);
12
+ * if (a?.type === 'assigned') {
13
+ * a.assigneeId; // narrowed
14
+ * }
15
+ * ```
16
+ *
17
+ * @public
18
+ */
19
+ export type EntityResult<TEntity extends Entity<any, any, any>> = TEntity extends Entity<any, any, infer U> ? [U] extends [never] ? InferType<EntitySchema<TEntity>> : U : InferType<EntitySchema<TEntity>>;
20
+ /**
21
+ * Discriminated-union variant rows for a polymorphic entity. Resolves to
22
+ * `never` for non-polymorphic entities.
23
+ *
24
+ * @public
25
+ */
26
+ export type EntityResultByVariant<TEntity extends Entity<any, any, any>> = TEntity extends Entity<any, any, infer U> ? [U] extends [never] ? never : U : never;
27
+ /**
28
+ * Project the navigation-property type added by a single relation: arrays
29
+ * for one-to-many / many-to-many, optional singletons for to-one.
30
+ *
31
+ * @public
32
+ */
33
+ export type ResolvedRel<R> = R extends RelationInfo<infer Kind, infer F> ? F extends ObjectSchemaBuilder<any, any, any, any, any, any, any> ? Kind extends 'hasMany' | 'belongsToMany' ? Array<InferType<F>> : InferType<F> | undefined : never : never;
34
+ /**
35
+ * `{ [relName]: relName }` — the accessor object passed to `t => t.relName`
36
+ * selectors on `.include()`. Restricted to the relation keys declared on
37
+ * `TEntity`.
38
+ *
39
+ * @public
40
+ */
41
+ export type RelKeyTree<TEntity extends Entity<any, any, any>> = {
42
+ readonly [K in keyof EntityRelations<TEntity> & string]: K;
43
+ };
44
+ /**
45
+ * Result type after `.include('rel')` is applied.
46
+ *
47
+ * @public
48
+ */
49
+ export type WithIncluded<TEntity extends Entity<any, any, any>, TResult, K extends keyof EntityRelations<TEntity> & string> = TResult & {
50
+ [P in K]: ResolvedRel<EntityRelations<TEntity>[P]>;
51
+ };
52
+ /**
53
+ * Result type after `.includeVariant(variant, rel)` is applied — adds
54
+ * `Rel` only on the discriminated branch matching `Variant`, leaving
55
+ * other branches untouched.
56
+ *
57
+ * @public
58
+ */
59
+ export type WithVariantIncluded<TEntity extends Entity<any, any, any>, TResult, Variant extends string, Rel extends keyof EntityRelations<TEntity> & string> = TResult extends infer U ? U extends Record<string, unknown> ? Extract<U, Record<string, Variant>> extends never ? U : Extract<U, Record<string, Variant>> extends U ? U & {
60
+ [P in Rel]: ResolvedRel<EntityRelations<TEntity>[P]>;
61
+ } : Exclude<U, Extract<U, Record<string, Variant>>> | (Extract<U, Record<string, Variant>> & {
62
+ [P in Rel]: ResolvedRel<EntityRelations<TEntity>[P]>;
63
+ }) : U : never;
64
+ /**
65
+ * @internal Extract the branch of a discriminated union whose discriminator
66
+ * property has exactly the literal type `K`. Works without knowing the
67
+ * discriminator property name by scanning all properties of each branch.
68
+ *
69
+ * Example:
70
+ * ```ts
71
+ * // Union = { type: 'assigned'; ... } | { type: 'commented'; ... }
72
+ * ExtractBranch<Union, 'assigned'> // → { type: 'assigned'; ... }
73
+ * ```
74
+ */
75
+ type ExtractBranch<Union, K extends string> = Union extends infer B ? {
76
+ [P in keyof B]-?: B[P] extends K ? K extends B[P] ? true : never : never;
77
+ }[keyof B] extends never ? never : B : never;
78
+ /**
79
+ * The concrete result shape for a specific polymorphic variant.
80
+ *
81
+ * For a polymorphic entity that uses `.withVariants()`, resolves to the
82
+ * branch of the discriminated union where the discriminator equals `K`.
83
+ * Resolves to `never` for non-polymorphic entities or unknown keys.
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * type AssignedActivity = VariantResult<typeof ActivityEntity, 'assigned'>;
88
+ * // → { type: 'assigned'; id: number; todoId: number; assigneeId: number; … }
89
+ * ```
90
+ *
91
+ * @public
92
+ */
93
+ export type VariantResult<TEntity extends Entity<any, any, any>, K extends string> = ExtractBranch<EntityResultByVariant<TEntity>, K>;
94
+ /**
95
+ * Write payload for `DbSet.insertVariant(key, payload)`.
96
+ *
97
+ * All columns from the matching variant branch are **optional** (so
98
+ * auto-generated PKs and columns with DB defaults can be omitted), and the
99
+ * discriminator field is **excluded** (it is set automatically from `key`).
100
+ *
101
+ * @example
102
+ * ```ts
103
+ * // Only valid fields for the 'assigned' variant; type-checked at compile time:
104
+ * await db.activities.insertVariant('assigned', {
105
+ * todoId: 42,
106
+ * userId: 7,
107
+ * assigneeId: 9,
108
+ * });
109
+ * ```
110
+ *
111
+ * @public
112
+ */
113
+ export type VariantInsertPayload<TEntity extends Entity<any, any, any>, K extends string> = VariantResult<TEntity, K> extends infer B extends Record<string, unknown> ? Omit<{
114
+ [P in keyof B]?: B[P];
115
+ }, {
116
+ [P in keyof B]-?: B[P] extends K ? K extends B[P] ? P : never : never;
117
+ }[keyof B]> : never;
118
+ /**
119
+ * Write payload for `EntityQuery.updateVariant(key, set)`.
120
+ *
121
+ * Same shape as {@link VariantInsertPayload} — partial of the variant
122
+ * branch with the discriminator excluded.
123
+ *
124
+ * @public
125
+ */
126
+ export type VariantUpdatePayload<TEntity extends Entity<any, any, any>, K extends string> = VariantInsertPayload<TEntity, K>;
127
+ /**
128
+ * @internal Re-export ExtractBranch for use in DbSet/EntityQuery types.
129
+ */
130
+ export type { ExtractBranch };
131
+ /**
132
+ * `true` when `TEntity` is a polymorphic entity (has at least one variant
133
+ * declared via `.stiVariant()` / `.ctiVariant()`); `false` otherwise.
134
+ *
135
+ * @public
136
+ */
137
+ export type HasVariants<TEntity extends Entity<any, any, any>> = EntityResultByVariant<TEntity> extends never ? false : true;
138
+ type EntityFromForeign<F> = F extends ObjectSchemaBuilder<any, any, any, any, any, any, any> ? Entity<F, any> : never;
139
+ /**
140
+ * Shape accepted by `DbSet.save(graph)` — a partial of the entity row
141
+ * plus optional nested relation values:
142
+ *
143
+ * - `belongsTo` / `hasOne` relations accept a single nested object.
144
+ * - `hasMany` / `belongsToMany` relations accept an array of nested objects.
145
+ *
146
+ * Each nested object is itself a {@link SaveGraph} of the foreign entity,
147
+ * so `belongsToMany` can pass `{ id }` to attach an existing row, or a
148
+ * full nested graph to create a new row and link it.
149
+ *
150
+ * @public
151
+ */
152
+ export type SaveGraph<TEntity extends Entity<any, any, any>> = Partial<EntityResult<TEntity>> & {
153
+ [K in keyof EntityRelations<TEntity>]?: EntityRelations<TEntity>[K] extends RelationInfo<infer Kind, infer F> ? Kind extends 'hasMany' | 'belongsToMany' ? ReadonlyArray<SaveGraph<EntityFromForeign<F>>> : SaveGraph<EntityFromForeign<F>> : never;
154
+ };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Save-graph executor: persists a nested write graph (root + related
3
+ * entities) within a single transaction, deciding insert vs update from
4
+ * the presence of primary-key fields.
5
+ *
6
+ * Topology:
7
+ * 1. `belongsTo` parents are saved first; their generated PKs are written
8
+ * into the root's foreign-key columns.
9
+ * 2. The root row is then inserted or updated.
10
+ * 3. `hasOne` / `hasMany` children are saved with the root's PK propagated
11
+ * into their foreign-key column.
12
+ * 4. `belongsToMany` children are saved, then through-table rows written.
13
+ *
14
+ * Hooks (`beforeInsert` / `afterInsert` / `beforeUpdate`) fire naturally
15
+ * because all writes go through `SchemaQueryBuilder`.
16
+ *
17
+ * @packageDocumentation
18
+ */
19
+ import type { Knex } from 'knex';
20
+ /**
21
+ * Persist a graph rooted at `node` whose shape matches `schema` (and its
22
+ * declared relations). All writes are funnelled through `trx` so the
23
+ * caller can compose this with an outer transaction.
24
+ *
25
+ * @internal — call via `DbSet.save()`.
26
+ */
27
+ export declare function saveGraph(knex: Knex, schema: any, node: Record<string, unknown>, trx?: Knex.Transaction): Promise<Record<string, unknown>>;
@@ -0,0 +1,38 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Transactionally insert a polymorphic entity row.
4
+ *
5
+ * For CTI variants:
6
+ * 1. Inserts the base row with `discriminatorColumn = variantKey` and all
7
+ * base-table payload columns; captures the auto-generated PK.
8
+ * 2. Inserts the variant row with the FK set to the base PK and all
9
+ * variant-table payload columns.
10
+ *
11
+ * For STI variants:
12
+ * - Inserts a single row into the base table with the discriminator set.
13
+ *
14
+ * @internal
15
+ */
16
+ export declare function insertVariant(knex: Knex, schema: any, variantKey: string, payload: Record<string, unknown>, trx?: Knex.Transaction): Promise<Record<string, unknown>>;
17
+ /**
18
+ * Update variant-specific columns for all rows matching the current query
19
+ * whose discriminator equals `variantKey`.
20
+ *
21
+ * For CTI: runs UPDATE on the variant table, joining on the FK = base PK.
22
+ * For STI: runs UPDATE on the base table filtered by discriminator.
23
+ *
24
+ * The `getPks` callback must return the PK values of all rows the caller's
25
+ * WHERE clause matched (resolved before this function is called).
26
+ *
27
+ * @internal
28
+ */
29
+ export declare function updateVariant(knex: Knex, schema: any, variantKey: string, set: Record<string, unknown>, pkValues: readonly unknown[], trx?: Knex.Transaction): Promise<void>;
30
+ /**
31
+ * Delete rows for a specific variant key, given their base-table PK values.
32
+ *
33
+ * For CTI: deletes variant rows first (FK-constraint order), then base rows.
34
+ * For STI: deletes the single base-table row filtered by discriminator.
35
+ *
36
+ * @internal
37
+ */
38
+ export declare function deleteVariant(knex: Knex, schema: any, variantKey: string, pkValues: readonly unknown[], trx?: Knex.Transaction): Promise<void>;