{"version":3,"sources":["../../../src/orm/errors/migration.ts","../../../src/orm/errors/not-found.ts","../../../src/orm/errors/replay.ts","../../../src/orm/event-log/executor.ts","../../../src/orm/event-log/schema.ts","../../../src/orm/schema.ts","../../../src/orm/event-log/registry.ts","../../../src/orm/event-log/walker.ts","../../../src/orm/event-log/event-log.ts","../../../src/orm/migrations/diff.ts","../../../src/orm/migrations/codegen.ts","../../../src/orm/migrations/apply.ts","../../../src/orm/migrations/pending.ts","../../../src/orm/migrations/validate.ts","../../../src/orm/migrations/migrator.ts","../../../src/orm/query-options.ts","../../../src/orm/relations.ts","../../../src/orm/repo/internals/computeds.ts","../../../src/orm/repo/internals/query-shape.ts","../../../src/orm/schema-validations.ts","../../../src/orm/repo/internals/executors.ts","../../../src/orm/repo/internals/preloads.ts","../../../src/orm/repo/builders.ts","../../../src/orm/repo/als.ts","../../../src/orm/repo/repo.ts"],"sourcesContent":["import { EquippedError } from '../../errors'\n\nexport type OrmMigrationPhase = 'lock' | 'load' | 'session' | 'user' | 'record'\n\nexport class OrmMigrationError extends EquippedError {\n\treadonly id: string\n\treadonly phase: OrmMigrationPhase\n\n\tconstructor(opts: { id: string; phase: OrmMigrationPhase; cause: unknown }) {\n\t\tsuper(`Migration failed at phase '${opts.phase}' for migration '${opts.id}'`, { id: opts.id, phase: opts.phase }, opts.cause)\n\t\tthis.id = opts.id\n\t\tthis.phase = opts.phase\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect } = import.meta.vitest\n\tconst { EquippedError } = await import('../../errors')\n\n\tdescribe('OrmMigrationError', () => {\n\t\ttest('stores id, phase, and cause on the instance', () => {\n\t\t\tconst cause = new Error('connection lost')\n\t\t\tconst err = new OrmMigrationError({ id: '0001-add-users', phase: 'user', cause })\n\t\t\texpect(err.id).toBe('0001-add-users')\n\t\t\texpect(err.phase).toBe('user')\n\t\t\texpect(err.cause).toBe(cause)\n\t\t})\n\n\t\ttest('message includes phase and migration id', () => {\n\t\t\tconst err = new OrmMigrationError({ id: '0002-add-index', phase: 'session', cause: 'boom' })\n\t\t\texpect(err.message).toBe(\"Migration failed at phase 'session' for migration '0002-add-index'\")\n\t\t})\n\n\t\ttest('instanceof EquippedError is true', () => {\n\t\t\tconst err = new OrmMigrationError({ id: 'x', phase: 'lock', cause: null })\n\t\t\texpect(err).toBeInstanceOf(OrmMigrationError)\n\t\t\texpect(err).toBeInstanceOf(EquippedError)\n\t\t})\n\n\t\ttest('discriminates from sibling error classes', async () => {\n\t\t\tconst { OrmValidationError } = await import('./validation')\n\t\t\tconst { OrmNotFoundError } = await import('./not-found')\n\t\t\tconst { OrmReplayError } = await import('./replay')\n\t\t\tconst err = new OrmMigrationError({ id: 'x', phase: 'load', cause: null })\n\t\t\texpect(err).not.toBeInstanceOf(OrmValidationError)\n\t\t\texpect(err).not.toBeInstanceOf(OrmNotFoundError)\n\t\t\texpect(err).not.toBeInstanceOf(OrmReplayError)\n\t\t})\n\n\t\ttest('context carries id and phase', () => {\n\t\t\tconst err = new OrmMigrationError({ id: '0003-drop-table', phase: 'record', cause: 'fail' })\n\t\t\texpect((err.context as any).id).toBe('0003-drop-table')\n\t\t\texpect((err.context as any).phase).toBe('record')\n\t\t})\n\n\t\ttest.each(['lock', 'load', 'session', 'user', 'record'] as const)('phase %s works', (phase) => {\n\t\t\tconst err = new OrmMigrationError({ id: 'test', phase, cause: null })\n\t\t\texpect(err.phase).toBe(phase)\n\t\t})\n\t})\n}\n","import { EquippedError } from '../../errors'\nimport { Filter, FilterGroup } from '../filter'\n\nexport type OrmNotFoundOperation = 'findOne' | 'updateOne' | 'deleteOne'\n\nfunction renderFilter(group: FilterGroup): string {\n\tif (\n\t\tgroup.op === 'and'\n\t\t&& group.children.length === 1\n\t\t&& group.children[0] instanceof Filter\n\t\t&& group.children[0].op === 'eq'\n\t) {\n\t\tconst f = group.children[0]\n\t\treturn `${f.field}=${f.value}`\n\t}\n\treturn renderFilterTree(group)\n}\n\nfunction renderFilterTree(node: FilterGroup | Filter): string {\n\tif (node instanceof Filter) {\n\t\treturn `${node.field} ${node.op} ${JSON.stringify(node.value)}`\n\t}\n\tconst parts = node.children.map((c) => renderFilterTree(c))\n\tif (parts.length === 1) return parts[0]\n\treturn `(${parts.join(` ${node.op} `)})`\n}\n\nexport class OrmNotFoundError extends EquippedError {\n\treadonly schema: string\n\treadonly operation: OrmNotFoundOperation\n\treadonly where: FilterGroup\n\n\tconstructor(opts: {\n\t\tschema: string\n\t\toperation: OrmNotFoundOperation\n\t\twhere: FilterGroup\n\t\tmessage?: string\n\t}) {\n\t\tconst msg = opts.message ?? `${opts.schema}.${opts.operation}: no row matched ${renderFilter(opts.where)}`\n\t\tsuper(msg, {\n\t\t\tschema: opts.schema,\n\t\t\toperation: opts.operation,\n\t\t\twhere: opts.where,\n\t\t})\n\t\tthis.schema = opts.schema\n\t\tthis.operation = opts.operation\n\t\tthis.where = opts.where\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect } = import.meta.vitest\n\tconst { OrmValidationError } = await import('./validation')\n\n\tdescribe('OrmNotFoundError', () => {\n\t\tdescribe('carrier construction', () => {\n\t\t\ttest('stores schema, operation, and where on the instance', () => {\n\t\t\t\tconst where = FilterGroup.create().eq('id', 'u1')\n\t\t\t\tconst err = new OrmNotFoundError({ schema: 'users', operation: 'findOne', where })\n\t\t\t\texpect(err.schema).toBe('users')\n\t\t\t\texpect(err.operation).toBe('findOne')\n\t\t\t\texpect(err.where).toBe(where)\n\t\t\t})\n\n\t\t\ttest('accepts all three operation literals', () => {\n\t\t\t\tconst where = FilterGroup.create().eq('id', 'u1')\n\t\t\t\tfor (const op of ['findOne', 'updateOne', 'deleteOne'] as const) {\n\t\t\t\t\tconst err = new OrmNotFoundError({ schema: 'users', operation: op, where })\n\t\t\t\t\texpect(err.operation).toBe(op)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tdescribe('default message generation', () => {\n\t\t\ttest('PK-keyed filter renders as id=<pk>', () => {\n\t\t\t\tconst where = FilterGroup.create().eq('id', 'abc-123')\n\t\t\t\tconst err = new OrmNotFoundError({ schema: 'users', operation: 'findOne', where })\n\t\t\t\texpect(err.message).toBe('users.findOne: no row matched id=abc-123')\n\t\t\t})\n\n\t\t\ttest('filter-based rendering shows the filter tree', () => {\n\t\t\t\tconst where = FilterGroup.create().eq('email', 'a@b.com').gt('age', 18)\n\t\t\t\tconst err = new OrmNotFoundError({ schema: 'users', operation: 'updateOne', where })\n\t\t\t\texpect(err.message).toBe('users.updateOne: no row matched (email eq \"a@b.com\" and age gt 18)')\n\t\t\t})\n\n\t\t\ttest('single non-eq filter renders without parens', () => {\n\t\t\t\tconst where = FilterGroup.create().gt('age', 18)\n\t\t\t\tconst err = new OrmNotFoundError({ schema: 'users', operation: 'deleteOne', where })\n\t\t\t\texpect(err.message).toBe('users.deleteOne: no row matched age gt 18')\n\t\t\t})\n\t\t})\n\n\t\tdescribe('custom message override', () => {\n\t\t\ttest('custom message replaces the default', () => {\n\t\t\t\tconst where = FilterGroup.create().eq('id', 'u1')\n\t\t\t\tconst err = new OrmNotFoundError({\n\t\t\t\t\tschema: 'users',\n\t\t\t\t\toperation: 'findOne',\n\t\t\t\t\twhere,\n\t\t\t\t\tmessage: 'user must exist',\n\t\t\t\t})\n\t\t\t\texpect(err.message).toBe('user must exist')\n\t\t\t})\n\t\t})\n\n\t\tdescribe('instanceof discrimination', () => {\n\t\t\ttest('instanceof OrmNotFoundError is true', () => {\n\t\t\t\tconst where = FilterGroup.create().eq('id', 'u1')\n\t\t\t\tconst err = new OrmNotFoundError({ schema: 'users', operation: 'findOne', where })\n\t\t\t\texpect(err).toBeInstanceOf(OrmNotFoundError)\n\t\t\t})\n\n\t\t\ttest('instanceof EquippedError is true', () => {\n\t\t\t\tconst where = FilterGroup.create().eq('id', 'u1')\n\t\t\t\tconst err = new OrmNotFoundError({ schema: 'users', operation: 'findOne', where })\n\t\t\t\texpect(err).toBeInstanceOf(EquippedError)\n\t\t\t})\n\n\t\t\ttest('instanceof OrmNotFoundError discriminates from OrmValidationError', () => {\n\t\t\t\tconst where = FilterGroup.create().eq('id', 'u1')\n\t\t\t\tconst notFound = new OrmNotFoundError({ schema: 'users', operation: 'findOne', where })\n\t\t\t\tconst validation = new OrmValidationError('validation', 'users', 'createOne', [])\n\t\t\t\texpect(notFound).not.toBeInstanceOf(OrmValidationError)\n\t\t\t\texpect(validation).not.toBeInstanceOf(OrmNotFoundError)\n\t\t\t})\n\n\t\t\ttest('both are instanceof EquippedError', () => {\n\t\t\t\tconst where = FilterGroup.create().eq('id', 'u1')\n\t\t\t\tconst notFound = new OrmNotFoundError({ schema: 'users', operation: 'findOne', where })\n\t\t\t\tconst validation = new OrmValidationError('validation', 'users', 'createOne', [])\n\t\t\t\texpect(notFound).toBeInstanceOf(EquippedError)\n\t\t\t\texpect(validation).toBeInstanceOf(EquippedError)\n\t\t\t})\n\t\t})\n\t})\n}\n","import { EquippedError } from '../../errors'\n\nexport class OrmReplayError extends EquippedError {\n\treadonly key: string\n\treadonly eventName: string\n\n\tconstructor(opts: { key: string; name: string; cause: unknown }) {\n\t\tsuper(`EventLog replay failed on event ${opts.name} (key=${opts.key})`, { key: opts.key, name: opts.name }, opts.cause)\n\t\tthis.key = opts.key\n\t\tthis.eventName = opts.name\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect } = import.meta.vitest\n\tconst { EquippedError } = await import('../../errors')\n\tconst { OrmValidationError } = await import('./validation')\n\tconst { OrmNotFoundError } = await import('./not-found')\n\n\tdescribe('OrmReplayError', () => {\n\t\ttest('stores key, eventName, and cause on the instance', () => {\n\t\t\tconst cause = new Error('handler blew up')\n\t\t\tconst err = new OrmReplayError({ key: 'evt-123', name: 'user.signup', cause })\n\t\t\texpect(err.key).toBe('evt-123')\n\t\t\texpect(err.eventName).toBe('user.signup')\n\t\t\texpect(err.cause).toBe(cause)\n\t\t})\n\n\t\ttest('message includes handler name and key', () => {\n\t\t\tconst err = new OrmReplayError({ key: 'evt-456', name: 'order.placed', cause: 'boom' })\n\t\t\texpect(err.message).toBe('EventLog replay failed on event order.placed (key=evt-456)')\n\t\t})\n\n\t\ttest('instanceof EquippedError is true', () => {\n\t\t\tconst err = new OrmReplayError({ key: 'k', name: 'n', cause: null })\n\t\t\texpect(err).toBeInstanceOf(OrmReplayError)\n\t\t\texpect(err).toBeInstanceOf(EquippedError)\n\t\t})\n\n\t\ttest('discriminates from OrmValidationError and OrmNotFoundError', () => {\n\t\t\tconst err = new OrmReplayError({ key: 'k', name: 'n', cause: null })\n\t\t\texpect(err).not.toBeInstanceOf(OrmValidationError)\n\t\t\texpect(err).not.toBeInstanceOf(OrmNotFoundError)\n\t\t})\n\n\t\ttest('context carries key and name', () => {\n\t\t\tconst err = new OrmReplayError({ key: 'evt-789', name: 'payment.charged', cause: 'fail' })\n\t\t\texpect((err.context as any).key).toBe('evt-789')\n\t\t\texpect((err.context as any).name).toBe('payment.charged')\n\t\t})\n\t})\n}\n","import { ulid } from 'ulid'\nimport { v } from 'valleyed'\n\nimport { OrmValidationError } from '../errors'\nimport type { EventContext, HandlerDef } from './registry'\nimport { EventLogSchema } from './schema'\nimport type { Repo } from '../repo/repo'\n\nexport async function fire<R>(\n\trepo: Repo<any>,\n\tname: string,\n\tdef: HandlerDef,\n\tpayload: unknown,\n\tctx?: { by?: string; at?: Date },\n): Promise<R> {\n\tconst validated = v.validate(def.pipe, payload)\n\tif (!validated.valid) {\n\t\tthrow new OrmValidationError('validation', 'event_log', 'fire', [\n\t\t\t{ cause: validated.error },\n\t\t])\n\t}\n\n\treturn repo.session(async () => {\n\t\tconst at = ctx?.at ?? new Date()\n\t\tconst ts = at.getTime()\n\t\tconst key = ulid(ts)\n\t\tconst by = ctx?.by ?? null\n\n\t\tawait repo.on(EventLogSchema).one().create({\n\t\t\tkey,\n\t\t\tname,\n\t\t\tts,\n\t\t\tbody: validated.value,\n\t\t\tby,\n\t\t})\n\n\t\tconst evCtx: EventContext = {\n\t\t\tkey,\n\t\t\tname,\n\t\t\tts,\n\t\t\tbody: validated.value,\n\t\t\tby,\n\t\t\tat,\n\t\t\tfirstRun: true,\n\t\t}\n\n\t\treturn await def.handle(validated.value, evCtx) as R\n\t})\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect } = import.meta.vitest\n\tconst { v } = await import('valleyed')\n\tconst { InMemoryAdapter } = await import('../adapters/in-memory')\n\tconst { Repo } = await import('../repo/repo')\n\tconst { OrmValidationError } = await import('../errors')\n\ttype EventContext = import('./registry').EventContext\n\tconst { EventLogSchema } = await import('./schema')\n\n\tfunction makeRepo() {\n\t\tconst adapter = InMemoryAdapter.create({})\n\t\treturn new Repo({\n\t\t\tadapter,\n\t\t\tresolve: (s) => {\n\t\t\t\tif (s === EventLogSchema) return { table: 'events' }\n\t\t\t\treturn { table: s.name }\n\t\t\t},\n\t\t})\n\t}\n\n\tdescribe('FireExecutor', () => {\n\t\ttest('payload validation rejects → throws OrmValidationError, no row persisted', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst def = {\n\t\t\t\tpipe: v.object({ email: v.string() }),\n\t\t\t\thandle: async () => 'ok',\n\t\t\t}\n\t\t\tawait expect(fire(repo, 'test', def, { email: 123 })).rejects.toThrow(OrmValidationError)\n\t\t\tconst rows = await repo.on(EventLogSchema).all().find()\n\t\t\texpect(rows).toHaveLength(0)\n\t\t})\n\n\t\ttest('happy path persists row and runs handler', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tlet called = false\n\t\t\tconst def = {\n\t\t\t\tpipe: v.object({ email: v.string() }),\n\t\t\t\thandle: async (payload: { email: string }) => {\n\t\t\t\t\tcalled = true\n\t\t\t\t\treturn `created-${payload.email}`\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tconst result = await fire(repo, 'user.signup', def, { email: 'a@b.com' }, { by: 'admin' })\n\n\t\t\texpect(result).toBe('created-a@b.com')\n\t\t\texpect(called).toBe(true)\n\n\t\t\tconst rows = await repo.on(EventLogSchema).all().find()\n\t\t\texpect(rows).toHaveLength(1)\n\t\t\texpect(rows[0].name).toBe('user.signup')\n\t\t\texpect(rows[0].body).toEqual({ email: 'a@b.com' })\n\t\t\texpect(rows[0].by).toBe('admin')\n\t\t\texpect(typeof rows[0].ts).toBe('number')\n\t\t\texpect(typeof rows[0].key).toBe('string')\n\t\t})\n\n\t\ttest('handler receives EventContext with firstRun: true', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tlet captured: EventContext | undefined\n\t\t\tconst def = {\n\t\t\t\tpipe: v.object({ x: v.number() }),\n\t\t\t\thandle: async (_payload: unknown, ctx: EventContext) => {\n\t\t\t\t\tcaptured = ctx\n\t\t\t\t},\n\t\t\t}\n\t\t\tconst at = new Date('2025-01-15T12:00:00Z')\n\n\t\t\tawait fire(repo, 'test.event', def, { x: 42 }, { by: 'user-1', at })\n\n\t\t\texpect(captured).toBeDefined()\n\t\t\texpect(captured!.firstRun).toBe(true)\n\t\t\texpect(captured!.name).toBe('test.event')\n\t\t\texpect(captured!.by).toBe('user-1')\n\t\t\texpect(captured!.at).toEqual(at)\n\t\t\texpect(captured!.ts).toBe(at.getTime())\n\t\t\texpect(captured!.body).toEqual({ x: 42 })\n\t\t\texpect(typeof captured!.key).toBe('string')\n\t\t})\n\n\t\ttest('handler throws → no EventLogSchema row remains', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst def = {\n\t\t\t\tpipe: v.object({ val: v.string() }),\n\t\t\t\thandle: async () => {\n\t\t\t\t\tthrow new Error('handler blew up')\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tawait expect(fire(repo, 'boom', def, { val: 'x' })).rejects.toThrow('handler blew up')\n\n\t\t\tconst rows = await repo.on(EventLogSchema).all().find()\n\t\t\texpect(rows).toHaveLength(0)\n\t\t})\n\n\t\ttest('by defaults to null when not provided', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst def = {\n\t\t\t\tpipe: v.string(),\n\t\t\t\thandle: async () => {},\n\t\t\t}\n\n\t\t\tawait fire(repo, 'test', def, 'hello')\n\n\t\t\tconst rows = await repo.on(EventLogSchema).all().find()\n\t\t\texpect(rows).toHaveLength(1)\n\t\t\texpect(rows[0].by).toBeNull()\n\t\t})\n\t})\n}\n","import { ulid } from 'ulid'\nimport { v } from 'valleyed'\n\nimport { Schema } from '../schema'\n\nexport const EventLogSchema = Schema.from('event_log')\n\t.pk('key', v.string(), () => ulid())\n\t.field('name', v.string())\n\t.field('ts', v.number())\n\t.field('body', v.any())\n\t.field('by', v.nullable(v.string()))\n\t.build()\n","import { v, type Pipe, type PipeOutput } from 'valleyed'\n\nimport { ComputedField, SchemaField, type AnyComputedField, type AnySchemaField } from './fields'\nimport type { Prettify } from './utils'\n\ntype AnyPrimaryKeyField = SchemaField<string, Pipe<any, any>, true>\n\nexport type AnySchema = Schema<string, AnyPrimaryKeyField, Record<string, AnySchemaField>, Record<string, AnyComputedField>>\n\nexport type SchemaComputedDefs<S extends AnySchema> = S extends Schema<any, any, any, infer C> ? C : Record<string, AnyComputedField>\n\nexport type SchemaPersistedOutput<S extends AnySchema> = Prettify<{\n\t[K in keyof SchemaFields<S>]: SchemaFields<S>[K] extends SchemaField<any, infer P, any> ? PipeOutput<P> : never\n}>\n\nexport type SchemaComputedOutput<S extends AnySchema> = Prettify<{\n\t[K in keyof SchemaComputedDefs<S>]: SchemaComputedDefs<S>[K] extends ComputedField<any, infer P, any> ? PipeOutput<P> : never\n}>\n\nexport type SchemaOutput<S extends AnySchema> = Prettify<{\n\t[K in keyof SchemaPersistedOutput<S> | keyof SchemaComputedOutput<S>]: K extends keyof SchemaPersistedOutput<S>\n\t\t? SchemaPersistedOutput<S>[K]\n\t\t: K extends keyof SchemaComputedOutput<S>\n\t\t\t? SchemaComputedOutput<S>[K]\n\t\t\t: never\n}>\n\nexport type SchemaFields<S extends AnySchema> =\n\tS extends Schema<any, infer PKField, infer F, any>\n\t\t? PKField extends AnySchemaField\n\t\t\t? Prettify<Record<PKField['name'], PKField> & F>\n\t\t\t: F\n\t\t: never\n\nexport type SchemaTaggedFields<S extends AnySchema> = {\n\t[K in keyof SchemaFields<S>]: SchemaFields<S>[K] & { readonly __schema?: S }\n}\n\nexport class SchemaBuilder<\n\tN extends string,\n\tPKField extends AnyPrimaryKeyField | never = never,\n\tF extends Record<string, AnySchemaField> = {},\n\tC extends Record<string, AnyComputedField> = {},\n> {\n\t#name: N\n\t#pkField: PKField | null\n\t#fieldDefs: F\n\t#computedDefs: C\n\n\tconstructor(name: N, pkField?: PKField | null, fieldDefs?: F, computedDefs?: C) {\n\t\tthis.#name = name\n\t\tthis.#pkField = pkField ?? null\n\t\tthis.#fieldDefs = fieldDefs ?? ({} as F)\n\t\tthis.#computedDefs = computedDefs ?? ({} as C)\n\t}\n\n\tget name() {\n\t\treturn this.#name\n\t}\n\n\tpk<K extends string, P extends Pipe<any, any>>(\n\t\tname: [PKField] extends [never] ? K : never,\n\t\tpipe: P,\n\t\tgenerate: () => PipeOutput<P>,\n\t): SchemaBuilder<N, SchemaField<K, P, true>, F> {\n\t\treturn new SchemaBuilder<N, SchemaField<K, P, true>, F>(\n\t\t\tthis.#name,\n\t\t\tnew SchemaField(name, pipe, { onCreate: generate }),\n\t\t\t{ ...this.#fieldDefs } as F,\n\t\t\tundefined,\n\t\t)\n\t}\n\n\tfield<\n\t\tK extends string,\n\t\tP extends Pipe<any, any>,\n\t\tO extends { onCreate?: () => PipeOutput<P>; onUpdate?: () => PipeOutput<P> } | undefined = undefined,\n\t>(\n\t\tname: K extends keyof F ? never : K,\n\t\tpipe: P,\n\t\topts?: O,\n\t): SchemaBuilder<\n\t\tN,\n\t\tPKField,\n\t\t{\n\t\t\t[Key in keyof F | K]: Key extends K\n\t\t\t\t? SchemaField<K, P, [O] extends [{ onCreate: () => any }] ? true : false>\n\t\t\t\t: Key extends keyof F\n\t\t\t\t\t? F[Key]\n\t\t\t\t\t: never\n\t\t}\n\t> {\n\t\tconst nextFields = { ...this.#fieldDefs, [name]: new SchemaField(name, pipe, opts) }\n\t\treturn new SchemaBuilder(this.#name, this.#pkField, nextFields, { ...this.#computedDefs }) as any\n\t}\n\n\tcomputed<\n\t\tK extends string,\n\t\tDeps extends readonly (keyof SchemaPersistedOutput<Schema<N, PKField, F, C>> & string)[],\n\t\tP extends Pipe<any, any>,\n\t>(\n\t\tname: K extends keyof SchemaPersistedOutput<Schema<N, PKField, F, C>> | keyof C ? never : K,\n\t\tdeps: Deps,\n\t\tpipe: P,\n\t\tcompute: (data: Pick<SchemaPersistedOutput<Schema<N, PKField, F, C>>, Deps[number]>) => PipeOutput<P>,\n\t): SchemaBuilder<\n\t\tN,\n\t\tPKField,\n\t\tF,\n\t\t{\n\t\t\t[Key in keyof C | K]: Key extends K ? ComputedField<K, P, Deps> : Key extends keyof C ? C[Key] : never\n\t\t}\n\t> {\n\t\tconst nextComputed = { ...this.#computedDefs, [name]: new ComputedField(name as K, pipe, deps, compute as any) }\n\t\treturn new SchemaBuilder(this.#name, this.#pkField, { ...this.#fieldDefs }, nextComputed) as any\n\t}\n\n\tbuild(this: [PKField] extends [never] ? never : SchemaBuilder<N, PKField, F, C>): Schema<N, PKField, F, C> {\n\t\tconst self = this as unknown as SchemaBuilder<N, PKField, F, C>\n\t\treturn new Schema<N, PKField, F, C>(self.#name, self.#pkField, self.#fieldDefs, self.#computedDefs)\n\t}\n}\n\nexport class Schema<\n\tN extends string,\n\tPKField extends AnyPrimaryKeyField | never = never,\n\tF extends Record<string, AnySchemaField> = {},\n\tC extends Record<string, AnyComputedField> = {},\n> {\n\t#name: N\n\t#pkField: PKField | null\n\t#fieldDefs: F\n\t#computedDefs: C\n\n\tconstructor(name: N, pkField: PKField | null, fieldDefs: F, computedDefs: C) {\n\t\tthis.#name = name\n\t\tthis.#pkField = pkField\n\t\tthis.#fieldDefs = fieldDefs\n\t\tthis.#computedDefs = computedDefs\n\t\tif (this.#pkField) {\n\t\t\tObject.defineProperty(this.#pkField, '__schema', { value: this, enumerable: false, configurable: true })\n\t\t}\n\t\tfor (const field of Object.values(this.#fieldDefs)) {\n\t\t\tObject.defineProperty(field, '__schema', { value: this, enumerable: false, configurable: true })\n\t\t}\n\t}\n\n\tget name() {\n\t\treturn this.#name\n\t}\n\n\tget pkField() {\n\t\tif (!this.#pkField) throw new Error(`Schema \"${this.#name}\" does not have a primary key defined`)\n\t\treturn this.#pkField\n\t}\n\n\tget fieldDefs(): F {\n\t\treturn this.#fieldDefs\n\t}\n\n\tget computedDefs(): C {\n\t\treturn this.#computedDefs\n\t}\n\n\tget fields() {\n\t\treturn {\n\t\t\t...(this.#pkField ? { [this.#pkField.name]: this.#pkField } : {}),\n\t\t\t...this.#fieldDefs,\n\t\t} as unknown as SchemaTaggedFields<this>\n\t}\n\n\tstatic from<N extends string>(name: N): SchemaBuilder<N> {\n\t\treturn new SchemaBuilder(name)\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect, expectTypeOf } = import.meta.vitest\n\n\tdescribe('SchemaBuilder', () => {\n\t\tconst UserSchema = Schema.from('users')\n\t\t\t.pk('id', v.string(), () => 'generated-id')\n\t\t\t.field('email', v.string())\n\t\t\t.field('name', v.string())\n\t\t\t.field('age', v.optional(v.number()))\n\t\t\t.field('createdAt', v.number(), { onCreate: () => 1000 })\n\t\t\t.field('updatedAt', v.number(), { onCreate: () => 1000, onUpdate: () => 2000 })\n\t\t\t.build()\n\n\t\tdescribe('Schema.from()', () => {\n\t\t\ttest('returns a SchemaBuilder instance', () => {\n\t\t\t\texpect(Schema.from('test')).toBeInstanceOf(SchemaBuilder)\n\t\t\t})\n\n\t\t\ttest('returns a Schema instance after build', () => {\n\t\t\t\tconst s = Schema.from('test').pk('id', v.string(), () => 'x').build()\n\t\t\t\texpect(s).toBeInstanceOf(Schema)\n\t\t\t})\n\n\t\t\ttest('Schema has no builder methods', () => {\n\t\t\t\tconst s = Schema.from('test').pk('id', v.string(), () => 'x').build()\n\t\t\t\texpect(s).not.toHaveProperty('pk')\n\t\t\t\texpect(s).not.toHaveProperty('field')\n\t\t\t\texpect(s).not.toHaveProperty('computed')\n\t\t\t\texpect(s).not.toHaveProperty('build')\n\t\t\t})\n\n\t\t\ttest('stores the schema name on builder', () => {\n\t\t\t\texpect(Schema.from('orders').name).toBe('orders')\n\t\t\t})\n\n\t\t\ttest('stores the schema name after build', () => {\n\t\t\t\texpect(Schema.from('orders').pk('id', v.string(), () => 'x').build().name).toBe('orders')\n\t\t\t})\n\n\t\t\ttest('full builder chain works', () => {\n\t\t\t\tconst s = Schema.from('users')\n\t\t\t\t\t.pk('id', v.string(), () => 'gen')\n\t\t\t\t\t.field('email', v.string())\n\t\t\t\t\t.field('age', v.number())\n\t\t\t\t\t.build()\n\t\t\t\texpect(s.name).toBe('users')\n\t\t\t\texpect(s.pkField.name).toBe('id')\n\t\t\t\texpect(Object.keys(s.fields)).toEqual(['id', 'email', 'age'])\n\t\t\t})\n\n\t\t\ttest('.build() is unavailable without .pk() at the type level', () => {\n\t\t\t\t// @ts-expect-error — .build() requires .pk() to have been called\n\t\t\t\tSchema.from('test').field('name', v.string()).build()\n\t\t\t})\n\t\t})\n\n\t\tdescribe('.name', () => {\n\t\t\ttest('returns the schema name', () => {\n\t\t\t\texpect(UserSchema.name).toBe('users')\n\t\t\t})\n\t\t})\n\n\t\tdescribe('.pk()', () => {\n\t\t\ttest('pkField returns the pk field', () => {\n\t\t\t\texpect(UserSchema.pkField.name).toBe('id')\n\t\t\t})\n\n\t\t\ttest('pk entry is a SchemaField instance', () => {\n\t\t\t\texpect(UserSchema.fields.id).toBeInstanceOf(SchemaField)\n\t\t\t})\n\n\t\t\ttest('pk entry has correct pipe', () => {\n\t\t\t\texpect(UserSchema.fields.id.pipe).toBeDefined()\n\t\t\t})\n\n\t\t\ttest('pk entry has correct name and path', () => {\n\t\t\t\texpect(UserSchema.fields.id.name).toBe('id')\n\t\t\t\texpect(UserSchema.fields.id.path).toEqual(['id'])\n\t\t\t})\n\n\t\t\ttest('pk onCreate generates the value', () => {\n\t\t\t\texpect(UserSchema.fields.id.onCreate?.()).toBe('generated-id')\n\t\t\t})\n\n\t\t\ttest('pkField throws when no pk defined', () => {\n\t\t\t\tconst schema = new Schema('nopk', null, { name: new SchemaField('name', v.string()) }, {})\n\t\t\t\texpect(() => schema.pkField).toThrow()\n\t\t\t})\n\t\t})\n\n\t\tdescribe('.field()', () => {\n\t\t\ttest('all field entries are SchemaField instances', () => {\n\t\t\t\tfor (const entry of Object.values(UserSchema.fieldDefs)) {\n\t\t\t\t\texpect(entry).toBeInstanceOf(SchemaField)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\ttest('field has correct name and path', () => {\n\t\t\t\texpect(UserSchema.fields.email.name).toBe('email')\n\t\t\t\texpect(UserSchema.fields.email.path).toEqual(['email'])\n\t\t\t})\n\n\t\t\ttest('field has correct pipe', () => {\n\t\t\t\texpect(UserSchema.fields.email.pipe).toBeDefined()\n\t\t\t\texpect(UserSchema.fieldDefs.email.pipe).toBe(UserSchema.fields.email.pipe)\n\t\t\t})\n\n\t\t\ttest('field with onCreate stores the function', () => {\n\t\t\t\texpect(UserSchema.fields.createdAt.onCreate?.()).toBe(1000)\n\t\t\t})\n\n\t\t\ttest('field with onUpdate stores the function', () => {\n\t\t\t\texpect(UserSchema.fields.updatedAt.onUpdate?.()).toBe(2000)\n\t\t\t})\n\n\t\t\ttest('field with both hooks stores both', () => {\n\t\t\t\texpect(UserSchema.fields.updatedAt.onCreate?.()).toBe(1000)\n\t\t\t\texpect(UserSchema.fields.updatedAt.onUpdate?.()).toBe(2000)\n\t\t\t})\n\n\t\t\ttest('field with no lifecycle has undefined hooks', () => {\n\t\t\t\texpect(UserSchema.fields.email.onCreate).toBeUndefined()\n\t\t\t\texpect(UserSchema.fields.email.onUpdate).toBeUndefined()\n\t\t\t})\n\t\t})\n\n\t\tdescribe('.computed()', () => {\n\t\t\ttest('registers computed fields and dependencies', () => {\n\t\t\t\tconst WithComputed = Schema.from('users')\n\t\t\t\t\t.pk('id', v.string(), () => 'u1')\n\t\t\t\t\t.field('name', v.string())\n\t\t\t\t\t.field('email', v.string())\n\t\t\t\t\t.computed('display', ['name', 'email'], v.string(), ({ name, email }) => `${name} <${email}>`)\n\t\t\t\t\t.build()\n\n\t\t\t\texpect(Object.keys(WithComputed.computedDefs)).toEqual(['display'])\n\t\t\t\texpect(WithComputed.computedDefs.display.deps).toEqual(['name', 'email'])\n\t\t\t\texpect(WithComputed.computedDefs.display.compute({ name: 'Alice', email: 'a@b.com' })).toBe('Alice <a@b.com>')\n\t\t\t})\n\n\t\t\ttest('computed field names are included in schema output type', () => {\n\t\t\t\tconst WithComputed = Schema.from('users')\n\t\t\t\t\t.pk('id', v.string(), () => 'u1')\n\t\t\t\t\t.field('name', v.string())\n\t\t\t\t\t.computed('nameUpper', ['name'], v.string(), ({ name }) => name.toUpperCase())\n\t\t\t\t\t.build()\n\n\t\t\t\ttype Out = SchemaOutput<typeof WithComputed>\n\t\t\t\tconst value: Out = { id: 'u1', name: 'Alice', nameUpper: 'ALICE' }\n\t\t\t\texpect(Object.keys(WithComputed.computedDefs)).toEqual(['nameUpper'])\n\t\t\t\texpect(value.nameUpper).toBe('ALICE')\n\t\t\t})\n\t\t})\n\n\t\tdescribe('.fields', () => {\n\t\t\ttest('includes all keys including pk', () => {\n\t\t\t\texpect(Object.keys(UserSchema.fields)).toEqual(['id', 'email', 'name', 'age', 'createdAt', 'updatedAt'])\n\t\t\t})\n\n\t\t\ttest('pk entry appears first', () => {\n\t\t\t\texpect(Object.keys(UserSchema.fields)[0]).toBe('id')\n\t\t\t})\n\n\t\t\ttest('schema with no fields has only pk in fields', () => {\n\t\t\t\tconst PkOnly = Schema.from('minimal').pk('id', v.string(), () => 'x').build()\n\t\t\t\texpect(Object.keys(PkOnly.fields)).toEqual(['id'])\n\t\t\t})\n\t\t})\n\n\t\tdescribe('.fieldDefs', () => {\n\t\t\ttest('excludes the pk field', () => {\n\t\t\t\texpect(Object.keys(UserSchema.fieldDefs)).not.toContain('id')\n\t\t\t})\n\n\t\t\ttest('contains all non-pk fields in order', () => {\n\t\t\t\texpect(Object.keys(UserSchema.fieldDefs)).toEqual(['email', 'name', 'age', 'createdAt', 'updatedAt'])\n\t\t\t})\n\n\t\t\ttest('schema with no fields has empty fieldDefs', () => {\n\t\t\t\tconst PkOnly = Schema.from('minimal').pk('id', v.string(), () => 'x').build()\n\t\t\t\texpect(Object.keys(PkOnly.fieldDefs)).toHaveLength(0)\n\t\t\t})\n\t\t})\n\t})\n\n\tdescribe('type-level: Schema.from uniqueness guard', () => {\n\t\ttest('duplicate .field() name is a TS error', () => {\n\t\t\t// @ts-expect-error — duplicate field name 'email' should fail\n\t\t\tSchema.from('test').pk('id', v.string(), () => 'x').field('email', v.string()).field('email', v.string()).build()\n\t\t})\n\t})\n\n\tdescribe('clone-on-step: fan-out independence', () => {\n\t\ttest('.field() returns a new builder, not the same instance', () => {\n\t\t\tconst base = Schema.from('test').pk('id', v.string(), () => 'x')\n\t\t\tconst a = base.field('email', v.string())\n\t\t\texpect(a).not.toBe(base)\n\t\t})\n\n\t\ttest('.pk() returns a new builder, not the same instance', () => {\n\t\t\tconst base = Schema.from('test')\n\t\t\tconst a = base.pk('id', v.string(), () => 'x')\n\t\t\texpect(a).not.toBe(base)\n\t\t})\n\n\t\ttest('.computed() returns a new builder, not the same instance', () => {\n\t\t\tconst base = Schema.from('test').pk('id', v.string(), () => 'x').field('name', v.string())\n\t\t\tconst a = base.computed('upper', ['name'], v.string(), ({ name }) => name.toUpperCase())\n\t\t\texpect(a).not.toBe(base)\n\t\t})\n\n\t\ttest('fan-out from shared base does not pollute either branch', () => {\n\t\t\tconst base = Schema.from('test').pk('id', v.string(), () => 'x')\n\t\t\tconst branchA = base.field('email', v.string()).build()\n\t\t\tconst branchB = base.field('age', v.number()).build()\n\n\t\t\texpect(Object.keys(branchA.fieldDefs)).toEqual(['email'])\n\t\t\texpect(Object.keys(branchB.fieldDefs)).toEqual(['age'])\n\t\t})\n\n\t\ttest('fan-out with computed does not pollute base', () => {\n\t\t\tconst base = Schema.from('test')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('name', v.string())\n\t\t\tconst withComputed = base.computed('upper', ['name'], v.string(), ({ name }) => name.toUpperCase()).build()\n\t\t\tconst withoutComputed = base.build()\n\n\t\t\texpect(Object.keys(withComputed.computedDefs)).toEqual(['upper'])\n\t\t\texpect(Object.keys(withoutComputed.computedDefs)).toEqual([])\n\t\t})\n\t})\n\n\tdescribe('type-level: schema-tagged Fields', () => {\n\t\ttest('fields accessor returns schema-tagged Field instances', () => {\n\t\t\tconst _TestSchema = Schema.from('test').pk('id', v.string(), () => 'x').field('email', v.string()).build()\n\t\t\ttype FieldS = NonNullable<(typeof _TestSchema.fields.id)['__schema']>\n\t\t\texpectTypeOf<FieldS>().toEqualTypeOf<typeof _TestSchema>()\n\n\t\t\ttype FieldS2 = NonNullable<(typeof _TestSchema.fields.email)['__schema']>\n\t\t\texpectTypeOf<FieldS2>().toEqualTypeOf<typeof _TestSchema>()\n\t\t})\n\n\t\ttest('fields carry runtime __schema reference to parent schema', () => {\n\t\t\tconst TestSchema = Schema.from('test').pk('id', v.string(), () => 'x').field('email', v.string()).build()\n\t\t\texpect((TestSchema.fields.id as any).__schema).toBe(TestSchema)\n\t\t\texpect((TestSchema.fields.email as any).__schema).toBe(TestSchema)\n\t\t})\n\t})\n}\n","import type { Pipe, PipeInput, PipeOutput } from 'valleyed'\n\nimport { EquippedError } from '../../errors'\n\nexport type EventContext = {\n\tkey: string\n\tname: string\n\tts: number\n\tbody: unknown\n\tby: string | null\n\tat: Date\n\tfirstRun: boolean\n}\n\nexport type HandlerDef<P extends Pipe<any, any> = Pipe<any, any>, R = unknown> = {\n\tpipe: P\n\thandle: (payload: PipeOutput<P>, ctx: EventContext) => R | Promise<R>\n}\n\nexport type FireFn<P extends Pipe<any, any>, R> = (\n\tpayload: PipeInput<P>,\n\tctx?: { by?: string; at?: Date },\n) => Promise<R>\n\nexport class HandlerRegistry {\n\treadonly #handlers = new Map<string, HandlerDef>()\n\treadonly #order: string[] = []\n\n\tregister(name: string, def: HandlerDef): void {\n\t\tif (this.#handlers.has(name)) {\n\t\t\tthrow new EquippedError(`EventLog handler \"${name}\" is already registered`, { name })\n\t\t}\n\t\tthis.#handlers.set(name, def)\n\t\tthis.#order.push(name)\n\t}\n\n\tget(name: string): HandlerDef {\n\t\tconst def = this.#handlers.get(name)\n\t\tif (!def) {\n\t\t\tthrow new EquippedError(`EventLog handler \"${name}\" is not registered`, { name })\n\t\t}\n\t\treturn def\n\t}\n\n\tlist(): string[] {\n\t\treturn [...this.#order]\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect } = import.meta.vitest\n\tconst { v } = await import('valleyed')\n\tconst { EquippedError } = await import('../../errors')\n\n\tdescribe('HandlerRegistry', () => {\n\t\ttest('register and get a handler', () => {\n\t\t\tconst registry = new HandlerRegistry()\n\t\t\tconst def: HandlerDef = { pipe: v.string(), handle: () => {} }\n\t\t\tregistry.register('user.signup', def)\n\t\t\texpect(registry.get('user.signup')).toBe(def)\n\t\t})\n\n\t\ttest('register duplicate name throws', () => {\n\t\t\tconst registry = new HandlerRegistry()\n\t\t\tconst def: HandlerDef = { pipe: v.string(), handle: () => {} }\n\t\t\tregistry.register('user.signup', def)\n\t\t\texpect(() => registry.register('user.signup', def)).toThrow(EquippedError)\n\t\t\texpect(() => registry.register('user.signup', def)).toThrow('already registered')\n\t\t})\n\n\t\ttest('get missing name throws', () => {\n\t\t\tconst registry = new HandlerRegistry()\n\t\t\texpect(() => registry.get('nonexistent')).toThrow(EquippedError)\n\t\t\texpect(() => registry.get('nonexistent')).toThrow('not registered')\n\t\t})\n\n\t\ttest('list returns names in registration order', () => {\n\t\t\tconst registry = new HandlerRegistry()\n\t\t\tregistry.register('c', { pipe: v.string(), handle: () => {} })\n\t\t\tregistry.register('a', { pipe: v.string(), handle: () => {} })\n\t\t\tregistry.register('b', { pipe: v.string(), handle: () => {} })\n\t\t\texpect(registry.list()).toEqual(['c', 'a', 'b'])\n\t\t})\n\n\t\ttest('list returns a copy, not a reference', () => {\n\t\t\tconst registry = new HandlerRegistry()\n\t\t\tregistry.register('x', { pipe: v.string(), handle: () => {} })\n\t\t\tconst list = registry.list()\n\t\t\tlist.push('y')\n\t\t\texpect(registry.list()).toEqual(['x'])\n\t\t})\n\t})\n}\n","import type { EventContext, HandlerDef, HandlerRegistry } from './registry'\nimport { EventLogSchema } from './schema'\nimport { OrmReplayError } from '../errors/replay'\nimport type { Repo } from '../repo/repo'\n\nconst executeReplay = async (\n\trepo: Repo<any>,\n\tdef: HandlerDef,\n\trow: { key: string; name: string; ts: number; body: unknown; by: string | null },\n): Promise<void> => {\n\tconst evCtx: EventContext = {\n\t\tkey: row.key,\n\t\tname: row.name,\n\t\tts: row.ts,\n\t\tbody: row.body,\n\t\tby: row.by,\n\t\tat: new Date(row.ts),\n\t\tfirstRun: false,\n\t}\n\ttry {\n\t\tawait repo.session(async () => {\n\t\t\tawait def.handle(row.body, evCtx)\n\t\t})\n\t} catch (cause) {\n\t\tthrow new OrmReplayError({ key: row.key, name: row.name, cause })\n\t}\n}\n\nexport async function replay(\n\trepo: Repo<any>,\n\tregistry: HandlerRegistry,\n\topts?: { from?: Date },\n): Promise<void> {\n\tlet query = repo.on(EventLogSchema).all().orderBy('ts', 'asc')\n\tif (opts?.from) {\n\t\tquery = query.where((q) => q.gte('ts', opts.from!.getTime()))\n\t}\n\tconst rows = await query.find()\n\n\tfor (const row of rows) {\n\t\tawait executeReplay(repo, registry.get(row.name), row)\n\t}\n}\n\nexport async function rerun(\n\trepo: Repo<any>,\n\tregistry: HandlerRegistry,\n\tkey: string,\n): Promise<void> {\n\tconst row = await repo.on(EventLogSchema).one().id(key).find()\n\tif (!row) {\n\t\tthrow new OrmReplayError({ key, name: 'unknown', cause: new Error(`Event with key \"${key}\" not found`) })\n\t}\n\tawait executeReplay(repo, registry.get(row.name), row)\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect } = import.meta.vitest\n\tconst { v } = await import('valleyed')\n\tconst { InMemoryAdapter } = await import('../adapters/in-memory')\n\tconst { Schema } = await import('../schema')\n\tconst { Repo } = await import('../repo/repo')\n\tconst { OrmReplayError } = await import('../errors/replay')\n\tconst { HandlerRegistry } = await import('./registry')\n\tconst { EventLogSchema } = await import('./schema')\n\tconst { fire } = await import('./executor')\n\ttype EventContext = import('./registry').EventContext\n\n\tconst CounterSchema = Schema.from('counters')\n\t\t.pk('id', v.string(), () => `c-${Math.random()}`)\n\t\t.field('name', v.string())\n\t\t.field('count', v.number())\n\t\t.build()\n\n\tfunction makeRepo() {\n\t\tconst adapter = InMemoryAdapter.create({})\n\t\treturn new Repo({\n\t\t\tadapter,\n\t\t\tresolve: (s) => {\n\t\t\t\tif (s === EventLogSchema) return { table: 'events' }\n\t\t\t\tif (s === CounterSchema) return { table: 'counters' }\n\t\t\t\treturn { table: s.name }\n\t\t\t},\n\t\t})\n\t}\n\n\tdescribe('ReplayWalker', () => {\n\t\ttest('replay walks rows in ts-ascending order', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst registry = new HandlerRegistry()\n\t\t\tconst order: string[] = []\n\n\t\t\tconst def = {\n\t\t\t\tpipe: v.object({ label: v.string() }),\n\t\t\t\thandle: async (payload: { label: string }) => {\n\t\t\t\t\torder.push(payload.label)\n\t\t\t\t},\n\t\t\t}\n\t\t\tregistry.register('test', def)\n\n\t\t\tawait fire(repo, 'test', def, { label: 'second' }, { at: new Date('2025-01-02') })\n\t\t\tawait fire(repo, 'test', def, { label: 'first' }, { at: new Date('2025-01-01') })\n\t\t\tawait fire(repo, 'test', def, { label: 'third' }, { at: new Date('2025-01-03') })\n\n\t\t\torder.length = 0\n\t\t\tawait replay(repo, registry)\n\n\t\t\texpect(order).toEqual(['first', 'second', 'third'])\n\t\t})\n\n\t\ttest('replay({ from }) skips rows with ts < from', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst registry = new HandlerRegistry()\n\t\t\tconst order: string[] = []\n\n\t\t\tconst def = {\n\t\t\t\tpipe: v.object({ label: v.string() }),\n\t\t\t\thandle: async (payload: { label: string }) => {\n\t\t\t\t\torder.push(payload.label)\n\t\t\t\t},\n\t\t\t}\n\t\t\tregistry.register('test', def)\n\n\t\t\tawait fire(repo, 'test', def, { label: 'old' }, { at: new Date('2025-01-01') })\n\t\t\tawait fire(repo, 'test', def, { label: 'new' }, { at: new Date('2025-01-03') })\n\n\t\t\torder.length = 0\n\t\t\tawait replay(repo, registry, { from: new Date('2025-01-02') })\n\n\t\t\texpect(order).toEqual(['new'])\n\t\t})\n\n\t\ttest('replay passes firstRun: false in EventContext', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst registry = new HandlerRegistry()\n\t\t\tlet captured: EventContext | undefined\n\n\t\t\tconst def = {\n\t\t\t\tpipe: v.object({ x: v.number() }),\n\t\t\t\thandle: async (_payload: unknown, ctx: EventContext) => {\n\t\t\t\t\tcaptured = ctx\n\t\t\t\t},\n\t\t\t}\n\t\t\tregistry.register('test', def)\n\n\t\t\tawait fire(repo, 'test', def, { x: 1 })\n\n\t\t\tawait replay(repo, registry)\n\n\t\t\texpect(captured).toBeDefined()\n\t\t\texpect(captured!.firstRun).toBe(false)\n\t\t})\n\n\t\ttest('replay throws OrmReplayError on first failure with key/name/cause', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst registry = new HandlerRegistry()\n\n\t\t\tconst boom = new Error('handler exploded')\n\t\t\tconst def = {\n\t\t\t\tpipe: v.object({ fail: v.boolean() }),\n\t\t\t\thandle: async (payload: { fail: boolean }, ctx: EventContext) => {\n\t\t\t\t\tif (!ctx.firstRun && payload.fail) throw boom\n\t\t\t\t},\n\t\t\t}\n\t\t\tregistry.register('evt', def)\n\n\t\t\tawait fire(repo, 'evt', def, { fail: false }, { at: new Date('2025-01-01') })\n\t\t\tawait fire(repo, 'evt', def, { fail: true }, { at: new Date('2025-01-02') })\n\t\t\tawait fire(repo, 'evt', def, { fail: false }, { at: new Date('2025-01-03') })\n\n\t\t\tconst rows = await repo.on(EventLogSchema).all().orderBy('ts', 'asc').find()\n\t\t\tconst failKey = rows[1].key\n\n\t\t\tawait expect(replay(repo, registry)).rejects.toThrow(OrmReplayError)\n\t\t\ttry {\n\t\t\t\tawait replay(repo, registry)\n\t\t\t} catch (e: any) {\n\t\t\t\texpect(e.key).toBe(failKey)\n\t\t\t\texpect(e.eventName).toBe('evt')\n\t\t\t\texpect(e.cause).toBe(boom)\n\t\t\t}\n\t\t})\n\n\t\ttest('replay stops on first failure — events after failure not attempted', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst registry = new HandlerRegistry()\n\t\t\tconst executed: string[] = []\n\n\t\t\tconst def = {\n\t\t\t\tpipe: v.object({ label: v.string(), fail: v.boolean() }),\n\t\t\t\thandle: async (payload: { label: string; fail: boolean }, ctx: EventContext) => {\n\t\t\t\t\texecuted.push(payload.label)\n\t\t\t\t\tif (!ctx.firstRun && payload.fail) throw new Error('boom')\n\t\t\t\t},\n\t\t\t}\n\t\t\tregistry.register('test', def)\n\n\t\t\tawait fire(repo, 'test', def, { label: 'A', fail: false }, { at: new Date('2025-01-01') })\n\t\t\tawait fire(repo, 'test', def, { label: 'B', fail: true }, { at: new Date('2025-01-02') })\n\t\t\tawait fire(repo, 'test', def, { label: 'C', fail: false }, { at: new Date('2025-01-03') })\n\n\t\t\texecuted.length = 0\n\t\t\tawait expect(replay(repo, registry)).rejects.toThrow(OrmReplayError)\n\n\t\t\texpect(executed).toEqual(['A', 'B'])\n\t\t})\n\n\t\ttest('per-event session isolation: failure on event N does not roll back events 1..N−1', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst registry = new HandlerRegistry()\n\n\t\t\tconst def = {\n\t\t\t\tpipe: v.object({ name: v.string(), fail: v.boolean() }),\n\t\t\t\thandle: async (payload: { name: string; fail: boolean }, ctx: EventContext) => {\n\t\t\t\t\tawait repo.on(CounterSchema).one().create({ name: payload.name, count: 1 })\n\t\t\t\t\tif (!ctx.firstRun && payload.fail) throw new Error('boom')\n\t\t\t\t},\n\t\t\t}\n\t\t\tregistry.register('test', def)\n\n\t\t\tawait fire(repo, 'test', def, { name: 'alpha', fail: false }, { at: new Date('2025-01-01') })\n\t\t\tawait fire(repo, 'test', def, { name: 'beta', fail: true }, { at: new Date('2025-01-02') })\n\n\t\t\tconst countersBefore = await repo.on(CounterSchema).all().find()\n\t\t\texpect(countersBefore).toHaveLength(2)\n\n\t\t\tawait expect(replay(repo, registry)).rejects.toThrow(OrmReplayError)\n\n\t\t\tconst countersAfter = await repo.on(CounterSchema).all().find()\n\t\t\texpect(countersAfter).toHaveLength(3)\n\t\t\texpect(countersAfter.filter((c) => c.name === 'alpha')).toHaveLength(2)\n\t\t})\n\n\t\ttest('rerun re-executes a single event with firstRun: false', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst registry = new HandlerRegistry()\n\t\t\tlet captured: EventContext | undefined\n\n\t\t\tconst def = {\n\t\t\t\tpipe: v.object({ x: v.number() }),\n\t\t\t\thandle: async (_payload: unknown, ctx: EventContext) => {\n\t\t\t\t\tcaptured = ctx\n\t\t\t\t},\n\t\t\t}\n\t\t\tregistry.register('test', def)\n\n\t\t\tawait fire(repo, 'test', def, { x: 42 })\n\n\t\t\tconst rows = await repo.on(EventLogSchema).all().find()\n\t\t\tconst key = rows[0].key\n\n\t\t\tawait rerun(repo, registry, key)\n\n\t\t\texpect(captured).toBeDefined()\n\t\t\texpect(captured!.firstRun).toBe(false)\n\t\t\texpect(captured!.key).toBe(key)\n\t\t\texpect(captured!.body).toEqual({ x: 42 })\n\t\t})\n\n\t\ttest('rerun throws OrmReplayError for missing key', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst registry = new HandlerRegistry()\n\n\t\t\tawait expect(rerun(repo, registry, 'nonexistent')).rejects.toThrow(OrmReplayError)\n\t\t})\n\t})\n}\n","import type { Pipe, PipeInput } from 'valleyed'\n\nimport { fire } from './executor'\nimport { HandlerRegistry, type FireFn, type HandlerDef } from './registry'\nimport { replay, rerun } from './walker'\nimport type { OrmAdapterLike } from '../adapters/base'\nimport type { Repo } from '../repo/repo'\n\nexport { type EventContext } from './registry'\n\nexport class EventLog<A extends OrmAdapterLike<any>> {\n\treadonly #repo: Repo<A>\n\treadonly #registry: HandlerRegistry\n\n\tconstructor(repo: Repo<A>, registry: HandlerRegistry) {\n\t\tthis.#repo = repo\n\t\tthis.#registry = registry\n\t}\n\n\thandler<P extends Pipe<any, any>, R>(\n\t\tname: string,\n\t\tdef: HandlerDef<P, R>,\n\t): FireFn<P, R> {\n\t\tthis.#registry.register(name, def)\n\t\treturn (payload: PipeInput<P>, ctx?: { by?: string; at?: Date }) =>\n\t\t\tfire<R>(this.#repo as Repo<any>, name, def, payload, ctx)\n\t}\n\n\treplay(opts?: { from?: Date }): Promise<void> {\n\t\treturn replay(this.#repo as Repo<any>, this.#registry, opts)\n\t}\n\n\trerun(key: string): Promise<void> {\n\t\treturn rerun(this.#repo as Repo<any>, this.#registry, key)\n\t}\n\n\tstatic from<A extends OrmAdapterLike<any>>(repo: Repo<A>) {\n\t\treturn { build: () => new EventLog(repo, new HandlerRegistry()) }\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect } = import.meta.vitest\n\tconst { v } = await import('valleyed')\n\tconst { InMemoryAdapter } = await import('../adapters/in-memory')\n\tconst { Schema } = await import('../schema')\n\tconst { Repo } = await import('../repo/repo')\n\tconst { OrmValidationError } = await import('../errors')\n\tconst { EventLogSchema } = await import('./schema')\n\n\tconst UserSchema = Schema.from('users')\n\t\t.pk('id', v.string(), () => `u-${Math.random()}`)\n\t\t.field('email', v.string())\n\t\t.field('createdAt', v.number(), { onCreate: () => Date.now() })\n\t\t.build()\n\n\tfunction makeRepo() {\n\t\tconst adapter = InMemoryAdapter.create({})\n\t\treturn new Repo({\n\t\t\tadapter,\n\t\t\tresolve: (s) => {\n\t\t\t\tif (s === EventLogSchema) return { table: 'events' }\n\t\t\t\tif (s === UserSchema) return { table: 'users' }\n\t\t\t\treturn { table: s.name }\n\t\t\t},\n\t\t})\n\t}\n\n\tdescribe('EventLog', () => {\n\t\ttest('EventLog.from(repo).build() constructs a working instance', () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst log = EventLog.from(repo).build()\n\t\t\texpect(log).toBeInstanceOf(EventLog)\n\t\t})\n\n\t\ttest('handler returns a typed fire function', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst log = EventLog.from(repo).build()\n\n\t\t\tconst fireSignup = log.handler('user.signup', {\n\t\t\t\tpipe: v.object({ email: v.string() }),\n\t\t\t\thandle: async (payload) => `created-${payload.email}`,\n\t\t\t})\n\n\t\t\tconst result = await fireSignup({ email: 'a@b.com' }, { by: 'admin' })\n\t\t\texpect(result).toBe('created-a@b.com')\n\t\t})\n\n\t\ttest('duplicate handler name throws', () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst log = EventLog.from(repo).build()\n\t\t\tconst def = { pipe: v.string(), handle: async () => {} }\n\n\t\t\tlog.handler('test', def)\n\t\t\texpect(() => log.handler('test', def)).toThrow('already registered')\n\t\t})\n\n\t\ttest('fire persists EventLogSchema row', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst log = EventLog.from(repo).build()\n\n\t\t\tconst fireEvent = log.handler('order.placed', {\n\t\t\t\tpipe: v.object({ item: v.string(), qty: v.number() }),\n\t\t\t\thandle: async () => {},\n\t\t\t})\n\n\t\t\tawait fireEvent({ item: 'widget', qty: 3 }, { by: 'user-42' })\n\n\t\t\tconst rows = await repo.on(EventLogSchema).all().find()\n\t\t\texpect(rows).toHaveLength(1)\n\t\t\texpect(rows[0].name).toBe('order.placed')\n\t\t\texpect(rows[0].body).toEqual({ item: 'widget', qty: 3 })\n\t\t\texpect(rows[0].by).toBe('user-42')\n\t\t})\n\n\t\ttest('invalid payload throws OrmValidationError, no row persisted', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst log = EventLog.from(repo).build()\n\n\t\t\tconst fireEvent = log.handler('test', {\n\t\t\t\tpipe: v.object({ x: v.number() }),\n\t\t\t\thandle: async () => {},\n\t\t\t})\n\n\t\t\tawait expect(fireEvent({ x: 'not-a-number' } as any)).rejects.toThrow(OrmValidationError)\n\t\t\tconst rows = await repo.on(EventLogSchema).all().find()\n\t\t\texpect(rows).toHaveLength(0)\n\t\t})\n\n\t\ttest('handler throw rolls back session — no row persisted, no side writes', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst log = EventLog.from(repo).build()\n\n\t\t\tconst fireEvent = log.handler('user.create', {\n\t\t\t\tpipe: v.object({ email: v.string() }),\n\t\t\t\thandle: async (payload) => {\n\t\t\t\t\tawait repo.on(UserSchema).one().create({ email: payload.email })\n\t\t\t\t\tthrow new Error('oops')\n\t\t\t\t},\n\t\t\t})\n\n\t\t\tawait expect(fireEvent({ email: 'a@b.com' })).rejects.toThrow('oops')\n\n\t\t\tconst eventRows = await repo.on(EventLogSchema).all().find()\n\t\t\texpect(eventRows).toHaveLength(0)\n\n\t\t\tconst userRows = await repo.on(UserSchema).all().find()\n\t\t\texpect(userRows).toHaveLength(0)\n\t\t})\n\n\t\ttest('handler receives firstRun: true and correct EventContext', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst log = EventLog.from(repo).build()\n\t\t\tlet captured: any\n\n\t\t\tconst fireEvent = log.handler('test', {\n\t\t\t\tpipe: v.object({ val: v.number() }),\n\t\t\t\thandle: async (_payload, ctx) => {\n\t\t\t\t\tcaptured = ctx\n\t\t\t\t},\n\t\t\t})\n\n\t\t\tconst at = new Date('2025-06-01T00:00:00Z')\n\t\t\tawait fireEvent({ val: 99 }, { by: 'admin', at })\n\n\t\t\texpect(captured.firstRun).toBe(true)\n\t\t\texpect(captured.name).toBe('test')\n\t\t\texpect(captured.by).toBe('admin')\n\t\t\texpect(captured.at).toEqual(at)\n\t\t\texpect(captured.ts).toBe(at.getTime())\n\t\t\texpect(captured.body).toEqual({ val: 99 })\n\t\t})\n\n\t\ttest('end-to-end: register → fire → replay flow', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst log = EventLog.from(repo).build()\n\t\t\tconst replayedPayloads: unknown[] = []\n\n\t\t\tconst fireEvent = log.handler('order.placed', {\n\t\t\t\tpipe: v.object({ item: v.string() }),\n\t\t\t\thandle: async (payload, ctx) => {\n\t\t\t\t\tif (!ctx.firstRun) replayedPayloads.push(payload)\n\t\t\t\t},\n\t\t\t})\n\n\t\t\tawait fireEvent({ item: 'A' }, { at: new Date('2025-01-02') })\n\t\t\tawait fireEvent({ item: 'B' }, { at: new Date('2025-01-01') })\n\n\t\t\tawait log.replay()\n\n\t\t\texpect(replayedPayloads).toEqual([{ item: 'B' }, { item: 'A' }])\n\t\t})\n\n\t\ttest('rerun re-executes a single event with firstRun: false', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst log = EventLog.from(repo).build()\n\t\t\tlet rerunCtx: any\n\n\t\t\tconst fireEvent = log.handler('test', {\n\t\t\t\tpipe: v.object({ val: v.number() }),\n\t\t\t\thandle: async (_payload, ctx) => {\n\t\t\t\t\tif (!ctx.firstRun) rerunCtx = ctx\n\t\t\t\t},\n\t\t\t})\n\n\t\t\tawait fireEvent({ val: 7 }, { by: 'admin' })\n\n\t\t\tconst rows = await repo.on(EventLogSchema).all().find()\n\t\t\tawait log.rerun(rows[0].key)\n\n\t\t\texpect(rerunCtx).toBeDefined()\n\t\t\texpect(rerunCtx.firstRun).toBe(false)\n\t\t\texpect(rerunCtx.body).toEqual({ val: 7 })\n\t\t\texpect(rerunCtx.by).toBe('admin')\n\t\t})\n\n\t\ttest('repo.session(() => log.replay()) makes replay atomic — failure rolls back all', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst log = EventLog.from(repo).build()\n\n\t\t\tconst fireEvent = log.handler('user.create', {\n\t\t\t\tpipe: v.object({ email: v.string(), fail: v.boolean() }),\n\t\t\t\thandle: async (payload, ctx) => {\n\t\t\t\t\tawait repo.on(UserSchema).one().create({ email: payload.email })\n\t\t\t\t\tif (!ctx.firstRun && payload.fail) throw new Error('replay boom')\n\t\t\t\t},\n\t\t\t})\n\n\t\t\tawait fireEvent({ email: 'alice@test.com', fail: false }, { at: new Date('2025-01-01') })\n\t\t\tawait fireEvent({ email: 'bob@test.com', fail: true }, { at: new Date('2025-01-02') })\n\n\t\t\tconst usersBefore = await repo.on(UserSchema).all().find()\n\t\t\texpect(usersBefore).toHaveLength(2)\n\n\t\t\tawait expect(\n\t\t\t\trepo.session(() => log.replay()),\n\t\t\t).rejects.toThrow()\n\n\t\t\tconst usersAfter = await repo.on(UserSchema).all().find()\n\t\t\texpect(usersAfter).toHaveLength(2)\n\t\t})\n\n\t\ttest('end-to-end: fire persists row + handler-side writes atomically', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst log = EventLog.from(repo).build()\n\n\t\t\tconst fireSignup = log.handler('user.signup', {\n\t\t\t\tpipe: v.object({ email: v.string() }),\n\t\t\t\thandle: async (payload, evCtx) => {\n\t\t\t\t\tconst user = await repo.on(UserSchema).one().create({\n\t\t\t\t\t\temail: payload.email,\n\t\t\t\t\t\tcreatedAt: evCtx.at.getTime(),\n\t\t\t\t\t})\n\t\t\t\t\treturn user\n\t\t\t\t},\n\t\t\t})\n\n\t\t\tconst user = await fireSignup({ email: 'alice@test.com' }, { by: 'system' })\n\t\t\texpect(user.email).toBe('alice@test.com')\n\n\t\t\tconst eventRows = await repo.on(EventLogSchema).all().find()\n\t\t\texpect(eventRows).toHaveLength(1)\n\n\t\t\tconst userRows = await repo.on(UserSchema).all().find()\n\t\t\texpect(userRows).toHaveLength(1)\n\t\t})\n\t})\n}\n","import type { FieldTypeName } from '../adapter'\nimport type { OrmAdapter } from '../orm-adapter'\nimport type { AnySchema } from '../schema'\nimport type { DiscoveredField, DiscoveredSchema } from './introspection-types'\nimport type { AnyChange } from './types'\n\nconst EMISSION_ORDER: AnyChange['kind'][] = [\n\t'dropForeignKey',\n\t'dropIndex',\n\t'dropField',\n\t'dropTable',\n\t'renameTable',\n\t'renameField',\n\t'createTable',\n\t'addField',\n\t'modifyField',\n\t'addIndex',\n\t'addForeignKey',\n]\n\ntype SchemaFieldInfo = {\n\tname: string\n\ttype: FieldTypeName\n\tnullable?: boolean\n\tdefault?: string | number | boolean | null\n\tunique?: boolean\n}\n\nfunction adapterSupports(adapter: OrmAdapter, kind: AnyChange['kind']): boolean {\n\tif (kind === 'execute') return true\n\tconst method = `apply${kind[0].toUpperCase()}${kind.slice(1)}` as keyof OrmAdapter\n\treturn typeof (adapter as any)[method] === 'function'\n}\n\nfunction fieldsEqual(target: SchemaFieldInfo, discovered: DiscoveredField): boolean {\n\tif (target.type !== discovered.type) return false\n\tif ((target.nullable ?? false) !== discovered.nullable) return false\n\tif (target.default !== discovered.default) return false\n\tif ((target.unique ?? false) !== (discovered.unique ?? false)) return false\n\treturn true\n}\n\nconst toFieldPayload = (f: SchemaFieldInfo) => ({\n\tname: f.name,\n\ttype: f.type,\n\t...(f.nullable ? { nullable: true as const } : {}),\n})\n\nfunction extractSchemaInfo(schema: AnySchema): {\n\tname: string\n\tpk: { name: string; type: FieldTypeName }\n\tfields: SchemaFieldInfo[]\n} {\n\tconst pkField = schema.pkField\n\tconst pkType = inferFieldType(pkField.pipe)\n\tconst fields: SchemaFieldInfo[] = []\n\tfor (const field of Object.values(schema.fieldDefs)) {\n\t\tconst f = field as any\n\t\tfields.push({\n\t\t\tname: f.name,\n\t\t\ttype: inferFieldType(f.pipe),\n\t\t\tnullable: isNullable(f.pipe),\n\t\t})\n\t}\n\treturn { name: schema.name, pk: { name: pkField.name, type: pkType }, fields }\n}\n\nconst KNOWN_FIELD_TYPES: ReadonlySet<string> = new Set<FieldTypeName>(['string', 'number', 'boolean', 'array', 'object', 'date'])\n\nfunction inferFieldType(pipe: any): FieldTypeName {\n\tif (!pipe) return 'string'\n\tconst schema = typeof pipe.schema === 'function' ? pipe.schema() : undefined\n\tif (schema?.type) {\n\t\tif (KNOWN_FIELD_TYPES.has(schema.type)) return schema.type as FieldTypeName\n\t\treturn 'string'\n\t}\n\tconst std = pipe['~standard']\n\tif (std?.validate) {\n\t\tif (!std.validate(0).issues) return 'number'\n\t\tif (!std.validate('').issues) return 'string'\n\t\tif (!std.validate(true).issues) return 'boolean'\n\t\tif (!std.validate([]).issues) return 'array'\n\t\tif (!std.validate({}).issues) return 'object'\n\t}\n\treturn 'string'\n}\n\nfunction isNullable(pipe: any): boolean {\n\tif (!pipe) return false\n\tconst ctx = typeof pipe.context === 'function' ? pipe.context() : undefined\n\tif (ctx?.optional) return true\n\tconst std = pipe['~standard']\n\tif (std?.validate && !std.validate(undefined).issues) return true\n\treturn false\n}\n\nexport function diffSchemas(\n\tadapter: OrmAdapter,\n\ttarget: ReadonlyArray<AnySchema>,\n\tcurrent: ReadonlyArray<DiscoveredSchema>,\n): ReadonlyArray<AnyChange> {\n\tconst changes: AnyChange[] = []\n\tconst currentByName = new Map(current.map((s) => [s.name, s]))\n\tconst targetByName = new Map(target.map((s) => [s.name, s]))\n\n\tfor (const disc of current) {\n\t\tif (!targetByName.has(disc.name)) {\n\t\t\tfor (const fk of disc.foreignKeys) {\n\t\t\t\tchanges.push({ kind: 'dropForeignKey', table: disc.name, name: fk.name })\n\t\t\t}\n\t\t\tfor (const idx of disc.indexes) {\n\t\t\t\tchanges.push({ kind: 'dropIndex', name: idx.name })\n\t\t\t}\n\t\t\tchanges.push({ kind: 'dropTable', name: disc.name })\n\t\t}\n\t}\n\n\tfor (const schema of target) {\n\t\tconst info = extractSchemaInfo(schema)\n\t\tconst disc = currentByName.get(info.name)\n\n\t\tif (!disc) {\n\t\t\tchanges.push({\n\t\t\t\tkind: 'createTable',\n\t\t\t\tname: info.name,\n\t\t\t\tpk: info.pk,\n\t\t\t\tfields: info.fields.map(toFieldPayload),\n\t\t\t})\n\t\t\tcontinue\n\t\t}\n\n\t\tconst discFieldsByName = new Map(disc.fields.map((f) => [f.name, f]))\n\t\tconst targetFieldsByName = new Map(info.fields.map((f) => [f.name, f]))\n\n\t\tfor (const discField of disc.fields) {\n\t\t\tif (!targetFieldsByName.has(discField.name)) {\n\t\t\t\tchanges.push({ kind: 'dropField', table: info.name, name: discField.name })\n\t\t\t}\n\t\t}\n\n\t\tfor (const tField of info.fields) {\n\t\t\tconst existing = discFieldsByName.get(tField.name)\n\t\t\tif (!existing) {\n\t\t\t\tchanges.push({ kind: 'addField', table: info.name, field: toFieldPayload(tField) })\n\t\t\t} else if (!fieldsEqual(tField, existing)) {\n\t\t\t\tchanges.push({ kind: 'modifyField', table: info.name, name: tField.name, to: toFieldPayload(tField) })\n\t\t\t}\n\t\t}\n\t}\n\n\treturn changes\n\t\t.filter((c) => adapterSupports(adapter, c.kind))\n\t\t.sort((a, b) => EMISSION_ORDER.indexOf(a.kind) - EMISSION_ORDER.indexOf(b.kind))\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect } = import.meta.vitest\n\tconst { v } = await import('valleyed')\n\tconst { InMemoryAdapter } = await import('../adapters/in-memory')\n\tconst { Schema } = await import('../schema')\n\n\tdescribe('diffSchemas', () => {\n\t\tfunction makeAdapter() {\n\t\t\treturn InMemoryAdapter.create({})\n\t\t}\n\n\t\ttest('returns empty array when no changes needed', () => {\n\t\t\tconst adapter = makeAdapter()\n\t\t\tconst UserSchema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('email', v.string())\n\t\t\t\t.build()\n\t\t\tconst current: DiscoveredSchema[] = [{\n\t\t\t\tname: 'users',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [{ name: 'email', type: 'string', nullable: false }],\n\t\t\t\tindexes: [],\n\t\t\t\tforeignKeys: [],\n\t\t\t}]\n\t\t\tconst result = diffSchemas(adapter, [UserSchema], current)\n\t\t\texpect(result).toEqual([])\n\t\t})\n\n\t\ttest('fresh DB returns createTable for everything', () => {\n\t\t\tconst adapter = makeAdapter()\n\t\t\tconst UserSchema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('email', v.string())\n\t\t\t\t.field('age', v.number())\n\t\t\t\t.build()\n\t\t\tconst result = diffSchemas(adapter, [UserSchema], [])\n\t\t\texpect(result).toHaveLength(1)\n\t\t\texpect(result[0]).toEqual({\n\t\t\t\tkind: 'createTable',\n\t\t\t\tname: 'users',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [\n\t\t\t\t\t{ name: 'email', type: 'string' },\n\t\t\t\t\t{ name: 'age', type: 'number' },\n\t\t\t\t],\n\t\t\t})\n\t\t})\n\n\t\ttest('add field detected when target has field absent from current', () => {\n\t\t\tconst adapter = makeAdapter()\n\t\t\tconst UserSchema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('email', v.string())\n\t\t\t\t.field('age', v.number())\n\t\t\t\t.build()\n\t\t\tconst current: DiscoveredSchema[] = [{\n\t\t\t\tname: 'users',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [{ name: 'email', type: 'string', nullable: false }],\n\t\t\t\tindexes: [],\n\t\t\t\tforeignKeys: [],\n\t\t\t}]\n\t\t\tconst result = diffSchemas(adapter, [UserSchema], current)\n\t\t\texpect(result).toHaveLength(1)\n\t\t\texpect(result[0]).toEqual({\n\t\t\t\tkind: 'addField',\n\t\t\t\ttable: 'users',\n\t\t\t\tfield: { name: 'age', type: 'number' },\n\t\t\t})\n\t\t})\n\n\t\ttest('drop field detected when current has field absent from target', () => {\n\t\t\tconst adapter = makeAdapter()\n\t\t\tconst UserSchema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('email', v.string())\n\t\t\t\t.build()\n\t\t\tconst current: DiscoveredSchema[] = [{\n\t\t\t\tname: 'users',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [\n\t\t\t\t\t{ name: 'email', type: 'string', nullable: false },\n\t\t\t\t\t{ name: 'age', type: 'number', nullable: false },\n\t\t\t\t],\n\t\t\t\tindexes: [],\n\t\t\t\tforeignKeys: [],\n\t\t\t}]\n\t\t\tconst result = diffSchemas(adapter, [UserSchema], current)\n\t\t\texpect(result).toHaveLength(1)\n\t\t\texpect(result[0]).toEqual({\n\t\t\t\tkind: 'dropField',\n\t\t\t\ttable: 'users',\n\t\t\t\tname: 'age',\n\t\t\t})\n\t\t})\n\n\t\ttest('modify field detected when type changes', () => {\n\t\t\tconst adapter = makeAdapter()\n\t\t\tconst UserSchema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('age', v.string())\n\t\t\t\t.build()\n\t\t\tconst current: DiscoveredSchema[] = [{\n\t\t\t\tname: 'users',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [{ name: 'age', type: 'number', nullable: false }],\n\t\t\t\tindexes: [],\n\t\t\t\tforeignKeys: [],\n\t\t\t}]\n\t\t\tconst result = diffSchemas(adapter, [UserSchema], current)\n\t\t\texpect(result).toHaveLength(1)\n\t\t\texpect(result[0]).toEqual({\n\t\t\t\tkind: 'modifyField',\n\t\t\t\ttable: 'users',\n\t\t\t\tname: 'age',\n\t\t\t\tto: { name: 'age', type: 'string' },\n\t\t\t})\n\t\t})\n\n\t\ttest('drop table detected when current has table absent from target', () => {\n\t\t\tconst adapter = makeAdapter()\n\t\t\tconst current: DiscoveredSchema[] = [{\n\t\t\t\tname: 'posts',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [{ name: 'title', type: 'string', nullable: false }],\n\t\t\t\tindexes: [{ name: 'posts_title_idx', on: ['title'], unique: false }],\n\t\t\t\tforeignKeys: [{ name: 'posts_author_fk', on: 'authorId', references: { table: 'users', column: 'id' } }],\n\t\t\t}]\n\t\t\tconst result = diffSchemas(adapter, [], current)\n\t\t\tconst kinds = result.map((c) => c.kind)\n\t\t\texpect(kinds).toContain('dropForeignKey')\n\t\t\texpect(kinds).toContain('dropIndex')\n\t\t\texpect(kinds).toContain('dropTable')\n\t\t})\n\n\t\ttest('rename surfaces as drop+add (NOT auto-rename)', () => {\n\t\t\tconst adapter = makeAdapter()\n\t\t\tconst UserSchema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('fullName', v.string())\n\t\t\t\t.build()\n\t\t\tconst current: DiscoveredSchema[] = [{\n\t\t\t\tname: 'users',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [{ name: 'name', type: 'string', nullable: false }],\n\t\t\t\tindexes: [],\n\t\t\t\tforeignKeys: [],\n\t\t\t}]\n\t\t\tconst result = diffSchemas(adapter, [UserSchema], current)\n\t\t\texpect(result).toHaveLength(2)\n\t\t\tconst kinds = result.map((c) => c.kind)\n\t\t\texpect(kinds).toContain('dropField')\n\t\t\texpect(kinds).toContain('addField')\n\t\t\texpect(kinds).not.toContain('renameField')\n\t\t})\n\n\t\ttest('emission order respects fixed canonical sequence', () => {\n\t\t\tconst adapter = makeAdapter()\n\t\t\tconst UserSchema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('email', v.string())\n\t\t\t\t.field('newField', v.number())\n\t\t\t\t.build()\n\t\t\tconst current: DiscoveredSchema[] = [\n\t\t\t\t{\n\t\t\t\t\tname: 'users',\n\t\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\t\tfields: [\n\t\t\t\t\t\t{ name: 'email', type: 'number', nullable: false },\n\t\t\t\t\t\t{ name: 'oldField', type: 'string', nullable: false },\n\t\t\t\t\t],\n\t\t\t\t\tindexes: [],\n\t\t\t\t\tforeignKeys: [],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: 'posts',\n\t\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\t\tfields: [],\n\t\t\t\t\tindexes: [],\n\t\t\t\t\tforeignKeys: [],\n\t\t\t\t},\n\t\t\t]\n\t\t\tconst result = diffSchemas(adapter, [UserSchema], current)\n\t\t\tconst kinds = result.map((c) => c.kind)\n\t\t\tfor (let i = 0; i < kinds.length - 1; i++) {\n\t\t\t\tconst a = EMISSION_ORDER.indexOf(kinds[i])\n\t\t\t\tconst b = EMISSION_ORDER.indexOf(kinds[i + 1])\n\t\t\t\texpect(a).toBeLessThanOrEqual(b)\n\t\t\t}\n\t\t})\n\n\t\ttest('adapter-aware filtering removes unsupported variants', async () => {\n\t\t\tconst { OrmAdapter } = await import('../orm-adapter')\n\t\t\tclass IndexOnlyAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({ table: v.string() })\n\t\t\t\treadonly supportedFieldTypes = ['string', 'number'] as const\n\t\t\t\tasync applyAddIndex() {}\n\t\t\t\tasync applyDropIndex() {}\n\t\t\t\tasync loadMigrations() { return [] }\n\t\t\t\tasync recordMigration() {}\n\t\t\t\tasync introspect() { return [] }\n\t\t\t}\n\t\t\tconst adapter = new (IndexOnlyAdapter as any)() as IndexOnlyAdapter\n\t\t\tconst UserSchema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('email', v.string())\n\t\t\t\t.build()\n\t\t\tconst result = diffSchemas(adapter, [UserSchema], [])\n\t\t\texpect(result).toEqual([])\n\t\t})\n\n\t\ttest('multiple tables: creates missing and drops extra', () => {\n\t\t\tconst adapter = makeAdapter()\n\t\t\tconst UserSchema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('email', v.string())\n\t\t\t\t.build()\n\t\t\tconst PostSchema = Schema.from('posts')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('title', v.string())\n\t\t\t\t.build()\n\t\t\tconst current: DiscoveredSchema[] = [{\n\t\t\t\tname: 'users',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [{ name: 'email', type: 'string', nullable: false }],\n\t\t\t\tindexes: [],\n\t\t\t\tforeignKeys: [],\n\t\t\t}, {\n\t\t\t\tname: 'comments',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [],\n\t\t\t\tindexes: [],\n\t\t\t\tforeignKeys: [],\n\t\t\t}]\n\t\t\tconst result = diffSchemas(adapter, [UserSchema, PostSchema], current)\n\t\t\tconst kinds = result.map((c) => c.kind)\n\t\t\texpect(kinds).toContain('dropTable')\n\t\t\texpect(kinds).toContain('createTable')\n\t\t\tconst dropTable = result.find((c) => c.kind === 'dropTable') as any\n\t\t\texpect(dropTable.name).toBe('comments')\n\t\t\tconst createTable = result.find((c) => c.kind === 'createTable') as any\n\t\t\texpect(createTable.name).toBe('posts')\n\t\t})\n\n\t\ttest('Mongo-style adapter with only index ops: field-bearing target returns empty diff', async () => {\n\t\t\tconst { OrmAdapter } = await import('../orm-adapter')\n\t\t\tclass MongoStyleAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({ table: v.string() })\n\t\t\t\treadonly supportedFieldTypes = ['string', 'number', 'object', 'array'] as const\n\t\t\t\tasync applyAddIndex() {}\n\t\t\t\tasync applyDropIndex() {}\n\t\t\t\tasync loadMigrations() { return [] }\n\t\t\t\tasync recordMigration() {}\n\t\t\t}\n\t\t\tconst adapter = new (MongoStyleAdapter as any)() as MongoStyleAdapter\n\t\t\tconst UserSchema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('email', v.string())\n\t\t\t\t.field('age', v.number())\n\t\t\t\t.build()\n\t\t\tconst current: DiscoveredSchema[] = [{\n\t\t\t\tname: 'users',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [],\n\t\t\t\tindexes: [{ name: 'users_email_idx', on: ['email'], unique: true }],\n\t\t\t\tforeignKeys: [],\n\t\t\t}]\n\t\t\tconst result = diffSchemas(adapter, [UserSchema], current)\n\t\t\tconst kinds = result.map((c) => c.kind)\n\t\t\texpect(kinds.every((k) => k === 'addIndex' || k === 'dropIndex')).toBe(true)\n\t\t})\n\n\t\ttest('drop index and drop FK emitted when table is dropped', () => {\n\t\t\tconst adapter = makeAdapter()\n\t\t\tconst current: DiscoveredSchema[] = [{\n\t\t\t\tname: 'posts',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [],\n\t\t\t\tindexes: [{ name: 'posts_title_idx', on: ['title'], unique: false }],\n\t\t\t\tforeignKeys: [{ name: 'posts_author_fk', on: 'authorId', references: { table: 'users', column: 'id' } }],\n\t\t\t}]\n\t\t\tconst result = diffSchemas(adapter, [], current)\n\t\t\texpect(result).toHaveLength(3)\n\t\t\texpect(result[0].kind).toBe('dropForeignKey')\n\t\t\texpect(result[1].kind).toBe('dropIndex')\n\t\t\texpect(result[2].kind).toBe('dropTable')\n\t\t})\n\n\t\ttest('nullable field mismatch detected as modify', () => {\n\t\t\tconst adapter = makeAdapter()\n\t\t\tconst UserSchema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('email', v.optional(v.string()), { onCreate: () => undefined })\n\t\t\t\t.build()\n\t\t\tconst current: DiscoveredSchema[] = [{\n\t\t\t\tname: 'users',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [{ name: 'email', type: 'string', nullable: false }],\n\t\t\t\tindexes: [],\n\t\t\t\tforeignKeys: [],\n\t\t\t}]\n\t\t\tconst result = diffSchemas(adapter, [UserSchema], current)\n\t\t\texpect(result).toHaveLength(1)\n\t\t\texpect(result[0].kind).toBe('modifyField')\n\t\t})\n\t})\n}\n","import type { OrmAdapterLike } from '../adapters/base'\nimport type { OrmAdapter } from '../orm-adapter'\nimport { diffSchemas } from './diff'\nimport type { DiscoveredSchema } from './introspection-types'\nimport type { ChangeFor } from './types'\nimport type { Repo } from '../repo/repo'\nimport type { AnySchema } from '../schema'\n\nexport type IntrospectableAdapter<A> = A extends { introspect(): Promise<DiscoveredSchema[]> } ? A : never\n\nexport class MigrationCodegen<A extends OrmAdapterLike<any>> {\n\treadonly #adapter: OrmAdapter & { introspect(): Promise<DiscoveredSchema[]> }\n\treadonly #target: ReadonlyArray<AnySchema>\n\n\tconstructor(\n\t\t_repo: Repo<A>,\n\t\tadapter: OrmAdapter & { introspect(): Promise<DiscoveredSchema[]> },\n\t\ttarget: ReadonlyArray<AnySchema>,\n\t) {\n\t\tthis.#adapter = adapter\n\t\tthis.#target = target\n\t}\n\n\tasync diff(): Promise<ReadonlyArray<ChangeFor<A>> | null> {\n\t\tconst current = await this.#adapter.introspect()\n\t\tconst changes = diffSchemas(this.#adapter, this.#target, current)\n\t\tif (changes.length === 0) return null\n\t\treturn changes as unknown as ReadonlyArray<ChangeFor<A>>\n\t}\n\n\tasync discover(): Promise<ReadonlyArray<DiscoveredSchema>> {\n\t\treturn this.#adapter.introspect()\n\t}\n\n\tstatic from<A extends OrmAdapterLike<any>>(\n\t\trepo: Repo<IntrospectableAdapter<A>>,\n\t\tadapter: A & OrmAdapter & { introspect(): Promise<DiscoveredSchema[]> },\n\t) {\n\t\treturn {\n\t\t\ttarget(schemas: ReadonlyArray<AnySchema>) {\n\t\t\t\treturn {\n\t\t\t\t\tbuild(): MigrationCodegen<A> {\n\t\t\t\t\t\treturn new MigrationCodegen(repo as unknown as Repo<A>, adapter, schemas)\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t}\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect, expectTypeOf } = import.meta.vitest\n\tconst { v } = await import('valleyed')\n\tconst { InMemoryAdapter } = await import('../adapters/in-memory')\n\tconst { Schema } = await import('../schema')\n\tconst { Repo } = await import('../repo/repo')\n\tconst { OrmIntrospectionError } = await import('../errors/introspection')\n\n\tconst UserSchema = Schema.from('users')\n\t\t.pk('id', v.string(), () => `u-${Math.random().toString(36).slice(2)}`)\n\t\t.field('email', v.string())\n\t\t.field('age', v.number())\n\t\t.build()\n\n\tconst PostSchema = Schema.from('posts')\n\t\t.pk('id', v.string(), () => `p-${Math.random().toString(36).slice(2)}`)\n\t\t.field('title', v.string())\n\t\t.field('authorId', v.string())\n\t\t.build()\n\n\tfunction makeEnv() {\n\t\tconst adapter = InMemoryAdapter.create({})\n\t\tconst repo = new Repo({ adapter, resolve: (s) => ({ table: s.name }) })\n\t\treturn { adapter, repo }\n\t}\n\n\tdescribe('MigrationCodegen', () => {\n\t\ttest('diff() returns createTable changes for fresh DB', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tconst codegen = MigrationCodegen.from(repo, adapter).target([UserSchema]).build()\n\t\t\tconst changes = await codegen.diff()\n\t\t\texpect(changes).not.toBeNull()\n\t\t\texpect(changes).toHaveLength(1)\n\t\t\texpect(changes![0].kind).toBe('createTable')\n\t\t\tconst ct = changes![0] as any\n\t\t\texpect(ct.name).toBe('users')\n\t\t\texpect(ct.pk).toEqual({ name: 'id', type: 'string' })\n\t\t\texpect(ct.fields).toEqual([\n\t\t\t\t{ name: 'email', type: 'string' },\n\t\t\t\t{ name: 'age', type: 'number' },\n\t\t\t])\n\t\t})\n\n\t\ttest('diff() returns null when target matches current', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tawait adapter.applyCreateTable({\n\t\t\t\tkind: 'createTable',\n\t\t\t\tname: 'users',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [\n\t\t\t\t\t{ name: 'email', type: 'string' },\n\t\t\t\t\t{ name: 'age', type: 'number' },\n\t\t\t\t],\n\t\t\t})\n\t\t\tconst codegen = MigrationCodegen.from(repo, adapter).target([UserSchema]).build()\n\t\t\tconst changes = await codegen.diff()\n\t\t\texpect(changes).toBeNull()\n\t\t})\n\n\t\ttest('discover() returns raw introspection descriptors', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tawait adapter.applyCreateTable({\n\t\t\t\tkind: 'createTable',\n\t\t\t\tname: 'users',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [{ name: 'email', type: 'string' }],\n\t\t\t})\n\t\t\tawait adapter.applyAddIndex({ kind: 'addIndex', table: 'users', on: ['email'], unique: true })\n\t\t\tconst codegen = MigrationCodegen.from(repo, adapter).target([UserSchema]).build()\n\t\t\tconst discovered = await codegen.discover()\n\t\t\texpect(discovered).toHaveLength(1)\n\t\t\texpect(discovered[0].name).toBe('users')\n\t\t\texpect(discovered[0].pk).toEqual({ name: 'id', type: 'string' })\n\t\t\texpect(discovered[0].fields).toEqual([{ name: 'email', type: 'string', nullable: false }])\n\t\t\texpect(discovered[0].indexes).toEqual([{ name: 'users_email_idx', on: ['email'], unique: true }])\n\t\t})\n\n\t\ttest('end-to-end: seed adapter state, declare target, call diff()', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tawait adapter.applyCreateTable({\n\t\t\t\tkind: 'createTable',\n\t\t\t\tname: 'users',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [{ name: 'email', type: 'string' }],\n\t\t\t})\n\t\t\tconst codegen = MigrationCodegen.from(repo, adapter).target([UserSchema, PostSchema]).build()\n\t\t\tconst changes = await codegen.diff()\n\t\t\texpect(changes).not.toBeNull()\n\t\t\tconst kinds = changes!.map((c) => c.kind)\n\t\t\texpect(kinds).toContain('addField')\n\t\t\texpect(kinds).toContain('createTable')\n\t\t\tconst addField = changes!.find((c) => c.kind === 'addField') as any\n\t\t\texpect(addField.table).toBe('users')\n\t\t\texpect(addField.field.name).toBe('age')\n\t\t\tconst createTable = changes!.find((c) => c.kind === 'createTable') as any\n\t\t\texpect(createTable.name).toBe('posts')\n\t\t})\n\n\t\ttest('compile-time: IntrospectableAdapter is never when adapter lacks introspect', async () => {\n\t\t\tconst { OrmAdapter } = await import('../orm-adapter')\n\t\t\tclass _NoIntrospectAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({ table: v.string() })\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t}\n\t\t\ttype NoIntrospect = InstanceType<typeof _NoIntrospectAdapter>\n\t\t\texpectTypeOf<IntrospectableAdapter<NoIntrospect>>().toBeNever()\n\t\t})\n\n\t\ttest('OrmIntrospectionError thrown on unrecognized DB type (sentinel)', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\t// Inject a sentinel value with an invalid type into the adapter's table state\n\t\t\tadapter.tables.set('broken', {\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: new Map([['weirdCol', { name: 'weirdCol', type: 'bytea' as any }]]),\n\t\t\t})\n\t\t\tconst codegen = MigrationCodegen.from(repo, adapter).target([UserSchema]).build()\n\t\t\tconst origIntrospect = adapter.introspect.bind(adapter)\n\t\t\tadapter.introspect = async () => {\n\t\t\t\tconst schemas = await origIntrospect()\n\t\t\t\tfor (const s of schemas) {\n\t\t\t\t\tfor (const f of s.fields) {\n\t\t\t\t\t\tconst validTypes = ['string', 'number', 'boolean', 'null', 'object', 'array', 'date']\n\t\t\t\t\t\tif (!validTypes.includes(f.type)) {\n\t\t\t\t\t\t\tthrow new OrmIntrospectionError({ adapter: 'in-memory', table: s.name, cause: `unsupported type '${f.type}' on column '${f.name}'` })\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn schemas\n\t\t\t}\n\t\t\tawait expect(codegen.diff()).rejects.toThrow(OrmIntrospectionError)\n\t\t\ttry {\n\t\t\t\tawait codegen.diff()\n\t\t\t} catch (err: any) {\n\t\t\t\texpect(err.adapter).toBe('in-memory')\n\t\t\t\texpect(err.table).toBe('broken')\n\t\t\t}\n\t\t})\n\t})\n}\n","import type { OrmAdapterLike } from '../adapters/base'\nimport type { OrmAdapter } from '../orm-adapter'\nimport type { AnyChange } from './types'\nimport type { Repo } from '../repo/repo'\n\nexport async function applyChange(adapter: OrmAdapter, repo: Repo<OrmAdapterLike<any>>, change: AnyChange): Promise<void> {\n\tif (change.kind === 'execute') {\n\t\tawait change.up(repo)\n\t\treturn\n\t}\n\n\tconst methodName = `apply${change.kind[0].toUpperCase()}${change.kind.slice(1)}`\n\tconst method = (adapter as any)[methodName]\n\tif (typeof method !== 'function') {\n\t\tthrow new Error(`Adapter does not support change kind '${change.kind}'`)\n\t}\n\tawait method.call(adapter, change)\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect, vi } = import.meta.vitest\n\n\tdescribe('applyChange', () => {\n\t\ttest('routes addIndex to adapter.applyAddIndex', async () => {\n\t\t\tconst applyAddIndex = vi.fn().mockResolvedValue(undefined)\n\t\t\tconst adapter = { applyAddIndex } as any\n\t\t\tconst repo = {} as any\n\t\t\tawait applyChange(adapter, repo, { kind: 'addIndex', table: 'users', on: ['email'] })\n\t\t\texpect(applyAddIndex).toHaveBeenCalledWith({ kind: 'addIndex', table: 'users', on: ['email'] })\n\t\t})\n\n\t\ttest('routes execute to change.up(repo)', async () => {\n\t\t\tconst up = vi.fn().mockResolvedValue(undefined)\n\t\t\tconst adapter = {} as any\n\t\t\tconst repo = {} as any\n\t\t\tawait applyChange(adapter, repo, { kind: 'execute', up })\n\t\t\texpect(up).toHaveBeenCalledWith(repo)\n\t\t})\n\n\t\ttest('throws when adapter lacks the apply method for a declarative change', async () => {\n\t\t\tconst adapter = {} as any\n\t\t\tconst repo = {} as any\n\t\t\tawait expect(applyChange(adapter, repo, { kind: 'addIndex', table: 't', on: ['a'] }))\n\t\t\t\t.rejects.toThrow(\"Adapter does not support change kind 'addIndex'\")\n\t\t})\n\n\t\ttest('routes createTable to adapter.applyCreateTable', async () => {\n\t\t\tconst applyCreateTable = vi.fn().mockResolvedValue(undefined)\n\t\t\tconst adapter = { applyCreateTable } as any\n\t\t\tconst change = { kind: 'createTable' as const, name: 'users', pk: { name: 'id', type: 'string' }, fields: [] }\n\t\t\tawait applyChange(adapter, {} as any, change)\n\t\t\texpect(applyCreateTable).toHaveBeenCalledWith(change)\n\t\t})\n\n\t\ttest('routes dropTable to adapter.applyDropTable', async () => {\n\t\t\tconst applyDropTable = vi.fn().mockResolvedValue(undefined)\n\t\t\tconst adapter = { applyDropTable } as any\n\t\t\tconst change = { kind: 'dropTable' as const, name: 'users' }\n\t\t\tawait applyChange(adapter, {} as any, change)\n\t\t\texpect(applyDropTable).toHaveBeenCalledWith(change)\n\t\t})\n\n\t\ttest('routes addField to adapter.applyAddField', async () => {\n\t\t\tconst applyAddField = vi.fn().mockResolvedValue(undefined)\n\t\t\tconst adapter = { applyAddField } as any\n\t\t\tconst change = { kind: 'addField' as const, table: 'users', field: { name: 'age', type: 'number' } }\n\t\t\tawait applyChange(adapter, {} as any, change)\n\t\t\texpect(applyAddField).toHaveBeenCalledWith(change)\n\t\t})\n\n\t\ttest('routes dropField to adapter.applyDropField', async () => {\n\t\t\tconst applyDropField = vi.fn().mockResolvedValue(undefined)\n\t\t\tconst adapter = { applyDropField } as any\n\t\t\tconst change = { kind: 'dropField' as const, table: 'users', name: 'age' }\n\t\t\tawait applyChange(adapter, {} as any, change)\n\t\t\texpect(applyDropField).toHaveBeenCalledWith(change)\n\t\t})\n\n\t\ttest('routes modifyField to adapter.applyModifyField', async () => {\n\t\t\tconst applyModifyField = vi.fn().mockResolvedValue(undefined)\n\t\t\tconst adapter = { applyModifyField } as any\n\t\t\tconst change = { kind: 'modifyField' as const, table: 'users', name: 'age', to: { name: 'age', type: 'string' } }\n\t\t\tawait applyChange(adapter, {} as any, change)\n\t\t\texpect(applyModifyField).toHaveBeenCalledWith(change)\n\t\t})\n\n\t\ttest('routes renameTable to adapter.applyRenameTable', async () => {\n\t\t\tconst applyRenameTable = vi.fn().mockResolvedValue(undefined)\n\t\t\tconst adapter = { applyRenameTable } as any\n\t\t\tconst change = { kind: 'renameTable' as const, from: 'users', to: 'accounts' }\n\t\t\tawait applyChange(adapter, {} as any, change)\n\t\t\texpect(applyRenameTable).toHaveBeenCalledWith(change)\n\t\t})\n\n\t\ttest('routes renameField to adapter.applyRenameField', async () => {\n\t\t\tconst applyRenameField = vi.fn().mockResolvedValue(undefined)\n\t\t\tconst adapter = { applyRenameField } as any\n\t\t\tconst change = { kind: 'renameField' as const, table: 'users', from: 'name', to: 'fullName' }\n\t\t\tawait applyChange(adapter, {} as any, change)\n\t\t\texpect(applyRenameField).toHaveBeenCalledWith(change)\n\t\t})\n\n\t\ttest('routes dropIndex to adapter.applyDropIndex', async () => {\n\t\t\tconst applyDropIndex = vi.fn().mockResolvedValue(undefined)\n\t\t\tconst adapter = { applyDropIndex } as any\n\t\t\tconst change = { kind: 'dropIndex' as const, name: 'users_email_idx' }\n\t\t\tawait applyChange(adapter, {} as any, change)\n\t\t\texpect(applyDropIndex).toHaveBeenCalledWith(change)\n\t\t})\n\n\t\ttest('routes addForeignKey to adapter.applyAddForeignKey', async () => {\n\t\t\tconst applyAddForeignKey = vi.fn().mockResolvedValue(undefined)\n\t\t\tconst adapter = { applyAddForeignKey } as any\n\t\t\tconst change = { kind: 'addForeignKey' as const, table: 'posts', on: 'authorId', references: { table: 'users', column: 'id' } }\n\t\t\tawait applyChange(adapter, {} as any, change)\n\t\t\texpect(applyAddForeignKey).toHaveBeenCalledWith(change)\n\t\t})\n\n\t\ttest('routes dropForeignKey to adapter.applyDropForeignKey', async () => {\n\t\t\tconst applyDropForeignKey = vi.fn().mockResolvedValue(undefined)\n\t\t\tconst adapter = { applyDropForeignKey } as any\n\t\t\tconst change = { kind: 'dropForeignKey' as const, table: 'posts', name: 'posts_authorId_fk' }\n\t\t\tawait applyChange(adapter, {} as any, change)\n\t\t\texpect(applyDropForeignKey).toHaveBeenCalledWith(change)\n\t\t})\n\t})\n}\n","import type { AnyMigration } from './types'\nimport { OrmMigrationError } from '../errors/migration'\n\nexport type PendingOpts = { to?: string; steps?: number }\n\nexport function computePending(\n\tdeclared: ReadonlyArray<AnyMigration>,\n\tapplied: ReadonlyArray<{ id: string; appliedAt: number }>,\n\topts?: PendingOpts,\n): { pending: AnyMigration[]; skipped: string[] } {\n\tconst appliedIds = new Set(applied.map((a) => a.id))\n\tconst declaredIds = new Set(declared.map((d) => d.id))\n\n\tconst orphans = applied.filter((a) => !declaredIds.has(a.id)).map((a) => a.id)\n\tif (orphans.length > 0) {\n\t\tthrow new OrmMigrationError({\n\t\t\tid: orphans[0],\n\t\t\tphase: 'load',\n\t\t\tcause: `orphan migrations: [${orphans.join(', ')}]`,\n\t\t})\n\t}\n\n\tconst sorted = [...declared].sort((a, b) => a.id.localeCompare(b.id))\n\n\tconst pending: AnyMigration[] = []\n\tconst skipped: string[] = []\n\tfor (const m of sorted) {\n\t\tif (appliedIds.has(m.id)) {\n\t\t\tskipped.push(m.id)\n\t\t} else {\n\t\t\tpending.push(m)\n\t\t}\n\t}\n\n\tif (opts?.to !== undefined) {\n\t\tconst toIdx = pending.findIndex((m) => m.id === opts.to)\n\t\tif (toIdx === -1) {\n\t\t\tif (appliedIds.has(opts.to)) return { pending: [], skipped }\n\t\t\tthrow new OrmMigrationError({\n\t\t\t\tid: opts.to,\n\t\t\t\tphase: 'load',\n\t\t\t\tcause: `unknown migration id: ${opts.to}`,\n\t\t\t})\n\t\t}\n\t\tpending.splice(toIdx + 1)\n\t}\n\n\tif (opts?.steps !== undefined) {\n\t\tpending.splice(opts.steps)\n\t}\n\n\treturn { pending, skipped }\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect } = import.meta.vitest\n\tconst { OrmMigrationError } = await import('../errors/migration')\n\n\tdescribe('computePending', () => {\n\t\ttest('returns all as pending when none applied', () => {\n\t\t\tconst declared = [\n\t\t\t\t{ id: '0002-add-index', changes: [] },\n\t\t\t\t{ id: '0001-create-table', changes: [] },\n\t\t\t]\n\t\t\tconst result = computePending(declared, [])\n\t\t\texpect(result.pending.map((m) => m.id)).toEqual(['0001-create-table', '0002-add-index'])\n\t\t\texpect(result.skipped).toEqual([])\n\t\t})\n\n\t\ttest('skips already applied migrations', () => {\n\t\t\tconst declared = [\n\t\t\t\t{ id: '0001', changes: [] },\n\t\t\t\t{ id: '0002', changes: [] },\n\t\t\t\t{ id: '0003', changes: [] },\n\t\t\t]\n\t\t\tconst applied = [{ id: '0001', appliedAt: 1 }]\n\t\t\tconst result = computePending(declared, applied)\n\t\t\texpect(result.pending.map((m) => m.id)).toEqual(['0002', '0003'])\n\t\t\texpect(result.skipped).toEqual(['0001'])\n\t\t})\n\n\t\ttest('throws on orphan migrations', () => {\n\t\t\tconst declared = [{ id: '0001', changes: [] }]\n\t\t\tconst applied = [{ id: '0001', appliedAt: 1 }, { id: 'ghost', appliedAt: 2 }]\n\t\t\texpect(() => computePending(declared, applied)).toThrow(OrmMigrationError)\n\t\t\ttry {\n\t\t\t\tcomputePending(declared, applied)\n\t\t\t} catch (err: any) {\n\t\t\t\texpect(err.phase).toBe('load')\n\t\t\t\texpect(err.cause).toContain('ghost')\n\t\t\t}\n\t\t})\n\n\t\ttest('returns empty pending when all applied', () => {\n\t\t\tconst declared = [{ id: '0001', changes: [] }]\n\t\t\tconst applied = [{ id: '0001', appliedAt: 1 }]\n\t\t\tconst result = computePending(declared, applied)\n\t\t\texpect(result.pending).toEqual([])\n\t\t\texpect(result.skipped).toEqual(['0001'])\n\t\t})\n\n\t\ttest('sorts by lex id order', () => {\n\t\t\tconst declared = [\n\t\t\t\t{ id: 'c', changes: [] },\n\t\t\t\t{ id: 'a', changes: [] },\n\t\t\t\t{ id: 'b', changes: [] },\n\t\t\t]\n\t\t\tconst result = computePending(declared, [])\n\t\t\texpect(result.pending.map((m) => m.id)).toEqual(['a', 'b', 'c'])\n\t\t})\n\n\t\ttest('to: returns pending up to and including the named id', () => {\n\t\t\tconst declared = [\n\t\t\t\t{ id: '0001', changes: [] },\n\t\t\t\t{ id: '0002', changes: [] },\n\t\t\t\t{ id: '0003', changes: [] },\n\t\t\t]\n\t\t\tconst result = computePending(declared, [], { to: '0002' })\n\t\t\texpect(result.pending.map((m) => m.id)).toEqual(['0001', '0002'])\n\t\t})\n\n\t\ttest('to: no-op when target id already applied', () => {\n\t\t\tconst declared = [\n\t\t\t\t{ id: '0001', changes: [] },\n\t\t\t\t{ id: '0002', changes: [] },\n\t\t\t\t{ id: '0003', changes: [] },\n\t\t\t]\n\t\t\tconst applied = [{ id: '0001', appliedAt: 1 }, { id: '0002', appliedAt: 2 }]\n\t\t\tconst result = computePending(declared, applied, { to: '0002' })\n\t\t\texpect(result.pending).toEqual([])\n\t\t})\n\n\t\ttest('to: throws on unknown id', () => {\n\t\t\tconst declared = [{ id: '0001', changes: [] }]\n\t\t\texpect(() => computePending(declared, [], { to: 'unknown' })).toThrow(OrmMigrationError)\n\t\t\ttry {\n\t\t\t\tcomputePending(declared, [], { to: 'unknown' })\n\t\t\t} catch (err: any) {\n\t\t\t\texpect(err.phase).toBe('load')\n\t\t\t\texpect(err.id).toBe('unknown')\n\t\t\t}\n\t\t})\n\n\t\ttest('steps: returns the first N pending', () => {\n\t\t\tconst declared = [\n\t\t\t\t{ id: '0001', changes: [] },\n\t\t\t\t{ id: '0002', changes: [] },\n\t\t\t\t{ id: '0003', changes: [] },\n\t\t\t]\n\t\t\tconst result = computePending(declared, [], { steps: 1 })\n\t\t\texpect(result.pending.map((m) => m.id)).toEqual(['0001'])\n\t\t})\n\n\t\ttest('steps: returns all pending when N exceeds count', () => {\n\t\t\tconst declared = [\n\t\t\t\t{ id: '0001', changes: [] },\n\t\t\t\t{ id: '0002', changes: [] },\n\t\t\t]\n\t\t\tconst result = computePending(declared, [], { steps: 5 })\n\t\t\texpect(result.pending.map((m) => m.id)).toEqual(['0001', '0002'])\n\t\t})\n\t})\n}\n","import { OrmValidationError, type OrmValidationFailure } from '../errors/validation'\nimport type { OrmAdapter } from '../orm-adapter'\nimport type { AnyChange, AnyMigration } from './types'\n\nfunction isEmpty(s: string | undefined | null): boolean {\n\treturn !s || s.trim() === ''\n}\n\ntype ChangeContext = { adapter: OrmAdapter; migrationId: string; changeIndex: number; failures: OrmValidationFailure[] }\n\nfunction validateFieldSpec(ctx: ChangeContext, fieldSpec: { name: string; type: string }, fieldLabel: string): void {\n\tif (isEmpty(fieldSpec.name)) {\n\t\tctx.failures.push({ migrationId: ctx.migrationId, changeIndex: ctx.changeIndex, field: fieldLabel, cause: `${fieldLabel} name must be non-empty` })\n\t}\n\tif (ctx.adapter.supportedFieldTypes.length > 0 && !ctx.adapter.supportedFieldTypes.includes(fieldSpec.type as any)) {\n\t\tctx.failures.push({ migrationId: ctx.migrationId, changeIndex: ctx.changeIndex, field: fieldLabel, cause: `unsupported field type '${fieldSpec.type}'` })\n\t}\n}\n\nfunction fail(ctx: ChangeContext, field: string | undefined, cause: string): void {\n\tctx.failures.push({ migrationId: ctx.migrationId, changeIndex: ctx.changeIndex, ...(field ? { field } : {}), cause })\n}\n\nfunction validateChange(adapter: OrmAdapter, change: AnyChange, migrationId: string, changeIndex: number, failures: OrmValidationFailure[]): void {\n\tif (change.kind === 'execute') return\n\n\tconst ctx: ChangeContext = { adapter, migrationId, changeIndex, failures }\n\tconst methodName = `apply${change.kind[0].toUpperCase()}${change.kind.slice(1)}`\n\tif (typeof (adapter as any)[methodName] !== 'function') {\n\t\tfail(ctx, undefined, `adapter does not support change kind '${change.kind}'`)\n\t}\n\n\tswitch (change.kind) {\n\t\tcase 'createTable': {\n\t\t\tif (isEmpty(change.name)) fail(ctx, 'name', 'table name must be non-empty')\n\t\t\tif (isEmpty(change.pk.name)) fail(ctx, 'pk.name', 'pk name must be non-empty')\n\t\t\tif (adapter.supportedFieldTypes.length > 0 && !adapter.supportedFieldTypes.includes(change.pk.type as any)) {\n\t\t\t\tfail(ctx, 'pk.type', `unsupported field type '${change.pk.type}'`)\n\t\t\t}\n\t\t\tconst fieldNames = new Set<string>()\n\t\t\tfor (const f of change.fields) {\n\t\t\t\tvalidateFieldSpec(ctx, f, 'field')\n\t\t\t\tif (fieldNames.has(f.name)) fail(ctx, 'fields', `duplicate field name '${f.name}' in createTable`)\n\t\t\t\tfieldNames.add(f.name)\n\t\t\t}\n\t\t\tif (change.pk.name && fieldNames.has(change.pk.name)) {\n\t\t\t\tfail(ctx, 'pk.name', `pk name '${change.pk.name}' collides with a field name`)\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\tcase 'dropTable': {\n\t\t\tif (isEmpty(change.name)) fail(ctx, 'name', 'table name must be non-empty')\n\t\t\tbreak\n\t\t}\n\t\tcase 'addField': {\n\t\t\tif (isEmpty(change.table)) fail(ctx, 'table', 'table name must be non-empty')\n\t\t\tvalidateFieldSpec(ctx, change.field, 'field')\n\t\t\tbreak\n\t\t}\n\t\tcase 'dropField': {\n\t\t\tif (isEmpty(change.table)) fail(ctx, 'table', 'table name must be non-empty')\n\t\t\tif (isEmpty(change.name)) fail(ctx, 'name', 'field name must be non-empty')\n\t\t\tbreak\n\t\t}\n\t\tcase 'modifyField': {\n\t\t\tif (isEmpty(change.table)) fail(ctx, 'table', 'table name must be non-empty')\n\t\t\tif (isEmpty(change.name)) fail(ctx, 'name', 'field name must be non-empty')\n\t\t\tvalidateFieldSpec(ctx, change.to, 'to')\n\t\t\tbreak\n\t\t}\n\t\tcase 'renameTable': {\n\t\t\tif (isEmpty(change.from)) fail(ctx, 'from', 'table name must be non-empty')\n\t\t\tif (isEmpty(change.to)) fail(ctx, 'to', 'table name must be non-empty')\n\t\t\tbreak\n\t\t}\n\t\tcase 'renameField': {\n\t\t\tif (isEmpty(change.table)) fail(ctx, 'table', 'table name must be non-empty')\n\t\t\tif (isEmpty(change.from)) fail(ctx, 'from', 'field name must be non-empty')\n\t\t\tif (isEmpty(change.to)) fail(ctx, 'to', 'field name must be non-empty')\n\t\t\tbreak\n\t\t}\n\t\tcase 'addIndex': {\n\t\t\tif (isEmpty(change.table)) fail(ctx, 'table', 'table name must be non-empty')\n\t\t\tif (!change.on || change.on.length === 0) fail(ctx, 'on', 'addIndex.on must be non-empty')\n\t\t\tbreak\n\t\t}\n\t\tcase 'dropIndex': {\n\t\t\tif (isEmpty(change.name)) fail(ctx, 'name', 'index name must be non-empty')\n\t\t\tbreak\n\t\t}\n\t\tcase 'addForeignKey': {\n\t\t\tif (isEmpty(change.table)) fail(ctx, 'table', 'table name must be non-empty')\n\t\t\tif (isEmpty(change.on)) fail(ctx, 'on', 'addForeignKey.on must be non-empty')\n\t\t\tif (isEmpty(change.references?.table)) fail(ctx, 'references.table', 'references table must be non-empty')\n\t\t\tif (isEmpty(change.references?.column)) fail(ctx, 'references.column', 'references column must be non-empty')\n\t\t\tbreak\n\t\t}\n\t\tcase 'dropForeignKey': {\n\t\t\tif (isEmpty(change.table)) fail(ctx, 'table', 'table name must be non-empty')\n\t\t\tif (isEmpty(change.name)) fail(ctx, 'name', 'foreign key name must be non-empty')\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nexport function assertNormalisedChanges(adapter: OrmAdapter, migrations: ReadonlyArray<AnyMigration>): void {\n\tconst failures: OrmValidationFailure[] = []\n\n\tconst seenIds = new Set<string>()\n\tfor (const m of migrations) {\n\t\tif (isEmpty(m.id)) {\n\t\t\tfailures.push({ cause: 'migration id must be a non-empty string' })\n\t\t\tcontinue\n\t\t}\n\n\t\tif (seenIds.has(m.id)) {\n\t\t\tfailures.push({ migrationId: m.id, cause: `duplicate migration id '${m.id}'` })\n\t\t}\n\t\tseenIds.add(m.id)\n\n\t\tfor (let i = 0; i < m.changes.length; i++) {\n\t\t\tvalidateChange(adapter, m.changes[i], m.id, i, failures)\n\t\t}\n\t}\n\n\tif (failures.length > 0) {\n\t\tthrow new OrmValidationError('changes', 'migrations', 'build', failures)\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect } = import.meta.vitest\n\n\tdescribe('assertNormalisedChanges', () => {\n\t\tconst fullAdapter = {\n\t\t\tsupportedFieldTypes: ['string', 'number', 'boolean'],\n\t\t\tapplyCreateTable: async () => {},\n\t\t\tapplyDropTable: async () => {},\n\t\t\tapplyAddField: async () => {},\n\t\t\tapplyDropField: async () => {},\n\t\t\tapplyModifyField: async () => {},\n\t\t\tapplyRenameTable: async () => {},\n\t\t\tapplyRenameField: async () => {},\n\t\t\tapplyAddIndex: async () => {},\n\t\t\tapplyDropIndex: async () => {},\n\t\t\tapplyAddForeignKey: async () => {},\n\t\t\tapplyDropForeignKey: async () => {},\n\t\t} as any\n\n\t\tconst adapterWithIndex = { supportedFieldTypes: ['string', 'number'], applyAddIndex: async () => {} } as any\n\t\tconst adapterWithout = { supportedFieldTypes: [] } as any\n\n\t\ttest('passes for valid migrations with all variant kinds', () => {\n\t\t\tconst migrations = [\n\t\t\t\t{ id: '0001', changes: [\n\t\t\t\t\t{ kind: 'createTable' as const, name: 'users', pk: { name: 'id', type: 'string' }, fields: [{ name: 'email', type: 'string' }] },\n\t\t\t\t\t{ kind: 'addField' as const, table: 'users', field: { name: 'age', type: 'number' } },\n\t\t\t\t\t{ kind: 'addIndex' as const, table: 'users', on: ['email'] },\n\t\t\t\t\t{ kind: 'addForeignKey' as const, table: 'users', on: 'orgId', references: { table: 'orgs', column: 'id' } },\n\t\t\t\t] },\n\t\t\t]\n\t\t\texpect(() => assertNormalisedChanges(fullAdapter, migrations)).not.toThrow()\n\t\t})\n\n\t\ttest('invariant A: throws on empty migration id', () => {\n\t\t\tconst migrations = [{ id: '', changes: [] }]\n\t\t\texpect(() => assertNormalisedChanges(fullAdapter, migrations)).toThrow(OrmValidationError)\n\t\t\ttry { assertNormalisedChanges(fullAdapter, migrations) } catch (err: any) {\n\t\t\t\texpect(err.kind).toBe('changes')\n\t\t\t\texpect(err.failures).toHaveLength(1)\n\t\t\t\texpect(err.failures[0].cause).toContain('non-empty')\n\t\t\t}\n\t\t})\n\n\t\ttest('invariant B: throws on duplicate migration ids with migrationId field', () => {\n\t\t\tconst migrations = [{ id: 'dup', changes: [] }, { id: 'dup', changes: [] }]\n\t\t\texpect(() => assertNormalisedChanges(fullAdapter, migrations)).toThrow(OrmValidationError)\n\t\t\ttry { assertNormalisedChanges(fullAdapter, migrations) } catch (err: any) {\n\t\t\t\texpect(err.kind).toBe('changes')\n\t\t\t\tconst dup = err.failures.find((f: any) => f.cause.includes('duplicate'))\n\t\t\t\texpect(dup).toBeDefined()\n\t\t\t\texpect(dup.migrationId).toBe('dup')\n\t\t\t}\n\t\t})\n\n\t\ttest('invariant C: throws when field type not in supportedFieldTypes', () => {\n\t\t\tconst migrations = [{ id: '0001', changes: [\n\t\t\t\t{ kind: 'createTable' as const, name: 'users', pk: { name: 'id', type: 'string' }, fields: [{ name: 'data', type: 'bigint' }] },\n\t\t\t] }]\n\t\t\ttry { assertNormalisedChanges(fullAdapter, migrations) } catch (err: any) {\n\t\t\t\texpect(err.failures.some((f: any) => f.cause.includes(\"unsupported field type 'bigint'\"))).toBe(true)\n\t\t\t}\n\t\t})\n\n\t\ttest('invariant C: throws when pk type not in supportedFieldTypes', () => {\n\t\t\tconst migrations = [{ id: '0001', changes: [\n\t\t\t\t{ kind: 'createTable' as const, name: 'users', pk: { name: 'id', type: 'uuid' }, fields: [] },\n\t\t\t] }]\n\t\t\ttry { assertNormalisedChanges(fullAdapter, migrations) } catch (err: any) {\n\t\t\t\texpect(err.failures.some((f: any) => f.cause.includes(\"unsupported field type 'uuid'\"))).toBe(true)\n\t\t\t}\n\t\t})\n\n\t\ttest('invariant C: throws for addField with unsupported type', () => {\n\t\t\tconst migrations = [{ id: '0001', changes: [\n\t\t\t\t{ kind: 'addField' as const, table: 'users', field: { name: 'x', type: 'bigint' } },\n\t\t\t] }]\n\t\t\ttry { assertNormalisedChanges(fullAdapter, migrations) } catch (err: any) {\n\t\t\t\texpect(err.failures.some((f: any) => f.cause.includes(\"unsupported field type\"))).toBe(true)\n\t\t\t}\n\t\t})\n\n\t\ttest('invariant C: throws for modifyField with unsupported type', () => {\n\t\t\tconst migrations = [{ id: '0001', changes: [\n\t\t\t\t{ kind: 'modifyField' as const, table: 'users', name: 'x', to: { name: 'x', type: 'bigint' } },\n\t\t\t] }]\n\t\t\ttry { assertNormalisedChanges(fullAdapter, migrations) } catch (err: any) {\n\t\t\t\texpect(err.failures.some((f: any) => f.cause.includes(\"unsupported field type\"))).toBe(true)\n\t\t\t}\n\t\t})\n\n\t\ttest('invariant C: skips field type check when supportedFieldTypes is empty', () => {\n\t\t\tconst adapter = { ...fullAdapter, supportedFieldTypes: [] } as any\n\t\t\tconst migrations = [{ id: '0001', changes: [\n\t\t\t\t{ kind: 'createTable' as const, name: 'users', pk: { name: 'id', type: 'anything' }, fields: [{ name: 'x', type: 'custom' }] },\n\t\t\t] }]\n\t\t\texpect(() => assertNormalisedChanges(adapter, migrations)).not.toThrow()\n\t\t})\n\n\t\ttest('invariant D: throws on duplicate field names in createTable', () => {\n\t\t\tconst migrations = [{ id: '0001', changes: [\n\t\t\t\t{ kind: 'createTable' as const, name: 'users', pk: { name: 'id', type: 'string' }, fields: [\n\t\t\t\t\t{ name: 'email', type: 'string' },\n\t\t\t\t\t{ name: 'email', type: 'string' },\n\t\t\t\t] },\n\t\t\t] }]\n\t\t\ttry { assertNormalisedChanges(fullAdapter, migrations) } catch (err: any) {\n\t\t\t\texpect(err.failures.some((f: any) => f.cause.includes(\"duplicate field name 'email'\"))).toBe(true)\n\t\t\t}\n\t\t})\n\n\t\ttest('invariant D: throws when pk.name collides with a field name', () => {\n\t\t\tconst migrations = [{ id: '0001', changes: [\n\t\t\t\t{ kind: 'createTable' as const, name: 'users', pk: { name: 'email', type: 'string' }, fields: [\n\t\t\t\t\t{ name: 'email', type: 'string' },\n\t\t\t\t] },\n\t\t\t] }]\n\t\t\ttry { assertNormalisedChanges(fullAdapter, migrations) } catch (err: any) {\n\t\t\t\texpect(err.failures.some((f: any) => f.cause.includes(\"pk name 'email' collides\"))).toBe(true)\n\t\t\t}\n\t\t})\n\n\t\ttest('invariant E: throws on empty table name in addIndex', () => {\n\t\t\tconst migrations = [{ id: '0001', changes: [{ kind: 'addIndex' as const, table: '', on: ['email'] }] }]\n\t\t\texpect(() => assertNormalisedChanges(adapterWithIndex, migrations)).toThrow(OrmValidationError)\n\t\t})\n\n\t\ttest('invariant E: throws on empty names in dropTable, dropField, renameTable, renameField, dropIndex, dropForeignKey', () => {\n\t\t\tconst migrations = [{ id: '0001', changes: [\n\t\t\t\t{ kind: 'dropTable' as const, name: '' },\n\t\t\t] }]\n\t\t\ttry { assertNormalisedChanges(fullAdapter, migrations) } catch (err: any) {\n\t\t\t\texpect(err.failures.some((f: any) => f.cause.includes('table name must be non-empty'))).toBe(true)\n\t\t\t}\n\t\t})\n\n\t\ttest('invariant E: throws on empty field name in dropField', () => {\n\t\t\tconst migrations = [{ id: '0001', changes: [\n\t\t\t\t{ kind: 'dropField' as const, table: 'users', name: '' },\n\t\t\t] }]\n\t\t\ttry { assertNormalisedChanges(fullAdapter, migrations) } catch (err: any) {\n\t\t\t\texpect(err.failures.some((f: any) => f.cause.includes('field name must be non-empty'))).toBe(true)\n\t\t\t}\n\t\t})\n\n\t\ttest('invariant E: throws on empty renameTable from/to', () => {\n\t\t\tconst migrations = [{ id: '0001', changes: [\n\t\t\t\t{ kind: 'renameTable' as const, from: '', to: '' },\n\t\t\t] }]\n\t\t\ttry { assertNormalisedChanges(fullAdapter, migrations) } catch (err: any) {\n\t\t\t\texpect(err.failures.filter((f: any) => f.cause.includes('table name must be non-empty'))).toHaveLength(2)\n\t\t\t}\n\t\t})\n\n\t\ttest('invariant E: throws on empty renameField from/to/table', () => {\n\t\t\tconst migrations = [{ id: '0001', changes: [\n\t\t\t\t{ kind: 'renameField' as const, table: '', from: '', to: '' },\n\t\t\t] }]\n\t\t\ttry { assertNormalisedChanges(fullAdapter, migrations) } catch (err: any) {\n\t\t\t\texpect(err.failures.length).toBeGreaterThanOrEqual(3)\n\t\t\t}\n\t\t})\n\n\t\ttest('invariant E: throws on empty dropIndex name', () => {\n\t\t\tconst migrations = [{ id: '0001', changes: [\n\t\t\t\t{ kind: 'dropIndex' as const, name: '' },\n\t\t\t] }]\n\t\t\ttry { assertNormalisedChanges(fullAdapter, migrations) } catch (err: any) {\n\t\t\t\texpect(err.failures.some((f: any) => f.cause.includes('index name must be non-empty'))).toBe(true)\n\t\t\t}\n\t\t})\n\n\t\ttest('invariant E: throws on empty dropForeignKey table/name', () => {\n\t\t\tconst migrations = [{ id: '0001', changes: [\n\t\t\t\t{ kind: 'dropForeignKey' as const, table: '', name: '' },\n\t\t\t] }]\n\t\t\ttry { assertNormalisedChanges(fullAdapter, migrations) } catch (err: any) {\n\t\t\t\texpect(err.failures.length).toBeGreaterThanOrEqual(2)\n\t\t\t}\n\t\t})\n\n\t\ttest('invariant F: throws on empty addIndex.on', () => {\n\t\t\tconst migrations = [{ id: '0001', changes: [{ kind: 'addIndex' as const, table: 'users', on: [] as string[] }] }]\n\t\t\texpect(() => assertNormalisedChanges(adapterWithIndex, migrations)).toThrow(OrmValidationError)\n\t\t\ttry { assertNormalisedChanges(adapterWithIndex, migrations) } catch (err: any) {\n\t\t\t\texpect(err.failures.some((f: any) => f.cause.includes('non-empty'))).toBe(true)\n\t\t\t}\n\t\t})\n\n\t\ttest('invariant F: throws on empty addForeignKey.on', () => {\n\t\t\tconst migrations = [{ id: '0001', changes: [\n\t\t\t\t{ kind: 'addForeignKey' as const, table: 'users', on: '', references: { table: 'orgs', column: 'id' } },\n\t\t\t] }]\n\t\t\ttry { assertNormalisedChanges(fullAdapter, migrations) } catch (err: any) {\n\t\t\t\texpect(err.failures.some((f: any) => f.cause.includes('addForeignKey.on must be non-empty'))).toBe(true)\n\t\t\t}\n\t\t})\n\n\t\ttest('invariant F: throws on empty addForeignKey references', () => {\n\t\t\tconst migrations = [{ id: '0001', changes: [\n\t\t\t\t{ kind: 'addForeignKey' as const, table: 'users', on: 'orgId', references: { table: '', column: '' } },\n\t\t\t] }]\n\t\t\ttry { assertNormalisedChanges(fullAdapter, migrations) } catch (err: any) {\n\t\t\t\texpect(err.failures.some((f: any) => f.cause.includes('references table must be non-empty'))).toBe(true)\n\t\t\t\texpect(err.failures.some((f: any) => f.cause.includes('references column must be non-empty'))).toBe(true)\n\t\t\t}\n\t\t})\n\n\t\ttest('invariant G: throws when adapter lacks required apply method', () => {\n\t\t\tconst migrations = [{ id: '0001', changes: [{ kind: 'addIndex' as const, table: 'users', on: ['email'] }] }]\n\t\t\texpect(() => assertNormalisedChanges(adapterWithout, migrations)).toThrow(OrmValidationError)\n\t\t\ttry { assertNormalisedChanges(adapterWithout, migrations) } catch (err: any) {\n\t\t\t\texpect(err.failures.some((f: any) => f.cause.includes('does not support'))).toBe(true)\n\t\t\t}\n\t\t})\n\n\t\ttest('invariant G: throws for each unsupported variant', () => {\n\t\t\tconst noOpAdapter = { supportedFieldTypes: ['string'] } as any\n\t\t\tconst migrations = [{ id: '0001', changes: [\n\t\t\t\t{ kind: 'createTable' as const, name: 'users', pk: { name: 'id', type: 'string' }, fields: [] },\n\t\t\t] }]\n\t\t\ttry { assertNormalisedChanges(noOpAdapter, migrations) } catch (err: any) {\n\t\t\t\texpect(err.failures.some((f: any) => f.cause.includes(\"does not support change kind 'createTable'\"))).toBe(true)\n\t\t\t}\n\t\t})\n\n\t\ttest('invariant H: allows empty changes array (no-op baseline)', () => {\n\t\t\tconst migrations = [{ id: '0001-baseline', changes: [] }]\n\t\t\texpect(() => assertNormalisedChanges(adapterWithIndex, migrations)).not.toThrow()\n\t\t})\n\n\t\ttest('invariant I: collects all failures into single throw', () => {\n\t\t\tconst migrations = [\n\t\t\t\t{ id: '', changes: [] },\n\t\t\t\t{ id: 'dup', changes: [] },\n\t\t\t\t{ id: 'dup', changes: [{ kind: 'addIndex' as const, table: '', on: [] as string[] }] },\n\t\t\t]\n\t\t\ttry { assertNormalisedChanges(adapterWithIndex, migrations) } catch (err: any) {\n\t\t\t\texpect(err.failures.length).toBeGreaterThanOrEqual(4)\n\t\t\t}\n\t\t})\n\n\t\ttest('failures carry migrationId and changeIndex', () => {\n\t\t\tconst migrations = [{ id: 'mig-1', changes: [\n\t\t\t\t{ kind: 'addIndex' as const, table: '', on: [] as string[] },\n\t\t\t] }]\n\t\t\ttry { assertNormalisedChanges(adapterWithIndex, migrations) } catch (err: any) {\n\t\t\t\tfor (const f of err.failures) {\n\t\t\t\t\texpect(f.migrationId).toBe('mig-1')\n\t\t\t\t\texpect(f.changeIndex).toBe(0)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\ttest('passes for execute changes without adapter check', () => {\n\t\t\tconst migrations = [{ id: '0001', changes: [{ kind: 'execute' as const, up: async () => {} }] }]\n\t\t\texpect(() => assertNormalisedChanges(adapterWithout, migrations)).not.toThrow()\n\t\t})\n\t})\n}\n","import type { OrmAdapterLike } from '../adapters/base'\nimport { OrmMigrationError } from '../errors/migration'\nimport type { OrmAdapter } from '../orm-adapter'\nimport { applyChange } from './apply'\nimport { computePending, type PendingOpts } from './pending'\nimport type { AnyChange, AnyMigration, Migration } from './types'\nimport { assertNormalisedChanges } from './validate'\nimport type { Repo } from '../repo/repo'\n\ntype RunResult = { ran: string[]; skipped: string[] }\nexport type StatusEntry = { id: string; applied: boolean; appliedAt?: number }\n\ntype HasAcquireMigrationLock<A> =\n\t'acquireMigrationLock' extends keyof A\n\t\t? A['acquireMigrationLock'] extends (...args: any) => any ? true : false\n\t\t: false\n\ntype WithoutLockStep<A extends OrmAdapterLike<any>> = HasAcquireMigrationLock<A> extends true\n\t? {}\n\t: { withoutLock(): { build(): Migrator<A> } }\n\nexport class Migrator<A extends OrmAdapterLike<any>> {\n\treadonly #repo: Repo<A>\n\treadonly #adapter: OrmAdapter\n\treadonly #migrations: ReadonlyArray<Migration<A>>\n\treadonly #withoutLock: boolean\n\n\tconstructor(repo: Repo<A>, adapter: OrmAdapter, migrations: ReadonlyArray<Migration<A>>, withoutLock = false) {\n\t\tthis.#repo = repo\n\t\tthis.#adapter = adapter\n\t\tthis.#migrations = migrations\n\t\tthis.#withoutLock = withoutLock\n\t}\n\n\t#requireMethod(name: 'loadMigrations' | 'recordMigration'): void {\n\t\tif (typeof this.#adapter[name] !== 'function') {\n\t\t\tconst phase = name === 'loadMigrations' ? 'load' : 'record' as const\n\t\t\tthrow new OrmMigrationError({ id: '', phase, cause: `adapter does not implement ${name}` })\n\t\t}\n\t}\n\n\tasync up(opts?: PendingOpts): Promise<RunResult> {\n\t\tthis.#requireMethod('loadMigrations')\n\t\tthis.#requireMethod('recordMigration')\n\n\t\tconst useLock = !this.#withoutLock && typeof this.#adapter.acquireMigrationLock === 'function'\n\t\tif (useLock) {\n\t\t\ttry {\n\t\t\t\treturn await this.#adapter.acquireMigrationLock!(() => this.#runPending(opts))\n\t\t\t} catch (err) {\n\t\t\t\tif (err instanceof OrmMigrationError) throw err\n\t\t\t\tthrow new OrmMigrationError({ id: '', phase: 'lock', cause: err })\n\t\t\t}\n\t\t}\n\t\treturn this.#runPending(opts)\n\t}\n\n\tasync status(): Promise<StatusEntry[]> {\n\t\tthis.#requireMethod('loadMigrations')\n\t\tconst applied = await this.#adapter.loadMigrations!()\n\t\tconst appliedMap = new Map(applied.map((a) => [a.id, a.appliedAt]))\n\t\tconst sorted = [...this.#migrations].sort((a, b) => a.id.localeCompare(b.id))\n\t\treturn sorted.map((m) => {\n\t\t\tconst appliedAt = appliedMap.get(m.id)\n\t\t\tif (appliedAt !== undefined) return { id: m.id, applied: true as const, appliedAt }\n\t\t\treturn { id: m.id, applied: false as const }\n\t\t})\n\t}\n\n\tasync dry(opts?: PendingOpts): Promise<{ would: string[] }> {\n\t\tthis.#requireMethod('loadMigrations')\n\t\tconst applied = await this.#adapter.loadMigrations!()\n\t\tconst { pending } = computePending(this.#migrations as unknown as ReadonlyArray<AnyMigration>, applied, opts)\n\t\treturn { would: pending.map((m) => m.id) }\n\t}\n\n\tasync #runPending(opts?: PendingOpts): Promise<RunResult> {\n\t\tlet applied: { id: string; appliedAt: number }[]\n\t\ttry {\n\t\t\tapplied = await this.#adapter.loadMigrations!()\n\t\t} catch (err) {\n\t\t\tthrow new OrmMigrationError({ id: '', phase: 'load', cause: err })\n\t\t}\n\n\t\tconst { pending, skipped } = computePending(this.#migrations as unknown as ReadonlyArray<AnyMigration>, applied, opts)\n\t\tconst ran: string[] = []\n\n\t\tfor (const m of pending) {\n\t\t\tconst execute = async () => {\n\t\t\t\tfor (const c of m.changes) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait applyChange(this.#adapter, this.#repo as unknown as Repo<OrmAdapterLike<any>>, c as AnyChange)\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tthrow new OrmMigrationError({ id: m.id, phase: 'user', cause: err })\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tawait this.#adapter.recordMigration!(m.id, Date.now())\n\t\t\t\t} catch (err) {\n\t\t\t\t\tthrow new OrmMigrationError({ id: m.id, phase: 'record', cause: err })\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst shouldWrapInSession = m.tx !== false && typeof this.#adapter.session === 'function'\n\t\t\tif (shouldWrapInSession) {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.#repo.session(execute)\n\t\t\t\t} catch (err) {\n\t\t\t\t\tif (err instanceof OrmMigrationError) throw err\n\t\t\t\t\tthrow new OrmMigrationError({ id: m.id, phase: 'session', cause: err })\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tawait execute()\n\t\t\t}\n\n\t\t\tran.push(m.id)\n\t\t}\n\n\t\treturn { ran, skipped }\n\t}\n\n\tstatic from<A extends OrmAdapterLike<any>>(repo: Repo<A>, adapter: A & OrmAdapter) {\n\t\treturn {\n\t\t\tmigrations(migrations: ReadonlyArray<Migration<A>>) {\n\t\t\t\treturn {\n\t\t\t\t\tbuild(): Migrator<A> {\n\t\t\t\t\t\tassertNormalisedChanges(adapter, migrations as any)\n\t\t\t\t\t\treturn new Migrator(repo, adapter, migrations)\n\t\t\t\t\t},\n\t\t\t\t\twithoutLock() {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tbuild(): Migrator<A> {\n\t\t\t\t\t\t\t\tassertNormalisedChanges(adapter, migrations as any)\n\t\t\t\t\t\t\t\treturn new Migrator(repo, adapter, migrations, true)\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t} as { build(): Migrator<A> } & WithoutLockStep<A>\n\t\t\t},\n\t\t}\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect, expectTypeOf } = import.meta.vitest\n\tconst { v } = await import('valleyed')\n\tconst { InMemoryAdapter } = await import('../adapters/in-memory')\n\tconst { Schema } = await import('../schema')\n\tconst { Repo } = await import('../repo/repo')\n\tconst { OrmValidationError } = await import('../errors/validation')\n\tconst { OrmMigrationError } = await import('../errors/migration')\n\n\tconst UserSchema = Schema.from('users')\n\t\t.pk('id', v.string(), () => `u-${Math.random().toString(36).slice(2)}`)\n\t\t.field('email', v.string())\n\t\t.build()\n\n\tfunction makeEnv() {\n\t\tconst adapter = InMemoryAdapter.create({})\n\t\tconst repo = new Repo({ adapter, resolve: (s) => ({ table: s.name }) })\n\t\treturn { adapter, repo }\n\t}\n\n\tdescribe('Migrator', () => {\n\t\ttest('addIndex migration applies and is recorded', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tconst m: Migration<typeof adapter> = {\n\t\t\t\tid: '0001-add-email-idx',\n\t\t\t\tchanges: [{ kind: 'addIndex', table: 'users', on: ['email'], unique: true }],\n\t\t\t}\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([m]).build()\n\t\t\tconst result = await migrator.up()\n\t\t\texpect(result.ran).toEqual(['0001-add-email-idx'])\n\t\t\texpect(result.skipped).toEqual([])\n\n\t\t\texpect(adapter.indexes.has('users_email_idx')).toBe(true)\n\t\t\tconst idx = adapter.indexes.get('users_email_idx')!\n\t\t\texpect(idx.table).toBe('users')\n\t\t\texpect(idx.on).toEqual(['email'])\n\t\t\texpect(idx.unique).toBe(true)\n\n\t\t\tconst recorded = await adapter.loadMigrations()\n\t\t\texpect(recorded).toHaveLength(1)\n\t\t\texpect(recorded[0].id).toBe('0001-add-email-idx')\n\t\t})\n\n\t\ttest('execute migration runs user code with the same Repo', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tconst m: Migration<typeof adapter> = {\n\t\t\t\tid: '0001-seed-user',\n\t\t\t\tchanges: [{\n\t\t\t\t\tkind: 'execute',\n\t\t\t\t\tup: async (r) => {\n\t\t\t\t\t\tawait r.on(UserSchema).one().create({ id: 'seed-1', email: 'test@example.com' })\n\t\t\t\t\t},\n\t\t\t\t}],\n\t\t\t}\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([m]).build()\n\t\t\tawait migrator.up()\n\n\t\t\tconst row = await repo.on(UserSchema).one().id('seed-1').find()\n\t\t\texpect(row).not.toBeNull()\n\t\t\texpect(row!.email).toBe('test@example.com')\n\t\t})\n\n\t\ttest('duplicate id throws OrmValidationError at build()', () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\texpect(() =>\n\t\t\t\tMigrator.from(repo, adapter)\n\t\t\t\t\t.migrations([\n\t\t\t\t\t\t{ id: 'dup', changes: [] },\n\t\t\t\t\t\t{ id: 'dup', changes: [] },\n\t\t\t\t\t])\n\t\t\t\t\t.build(),\n\t\t\t).toThrow(OrmValidationError)\n\n\t\t\ttry {\n\t\t\t\tMigrator.from(repo, adapter)\n\t\t\t\t\t.migrations([\n\t\t\t\t\t\t{ id: 'dup', changes: [] },\n\t\t\t\t\t\t{ id: 'dup', changes: [] },\n\t\t\t\t\t])\n\t\t\t\t\t.build()\n\t\t\t} catch (err: any) {\n\t\t\t\texpect(err.kind).toBe('changes')\n\t\t\t}\n\t\t})\n\n\t\ttest('up() skips already-applied migrations', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tconst m1: Migration<typeof adapter> = { id: '0001', changes: [] }\n\t\t\tconst m2: Migration<typeof adapter> = {\n\t\t\t\tid: '0002',\n\t\t\t\tchanges: [{ kind: 'addIndex', table: 'users', on: ['email'] }],\n\t\t\t}\n\n\t\t\tawait adapter.recordMigration('0001', Date.now())\n\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([m1, m2]).build()\n\t\t\tconst result = await migrator.up()\n\t\t\texpect(result.ran).toEqual(['0002'])\n\t\t\texpect(result.skipped).toEqual(['0001'])\n\t\t})\n\n\t\ttest('up() returns empty ran when all applied', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tawait adapter.recordMigration('0001', Date.now())\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([{ id: '0001', changes: [] }]).build()\n\t\t\tconst result = await migrator.up()\n\t\t\texpect(result.ran).toEqual([])\n\t\t\texpect(result.skipped).toEqual(['0001'])\n\t\t})\n\n\t\ttest('failed migration rolls back via session and throws OrmMigrationError', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tconst m1: Migration<typeof adapter> = {\n\t\t\t\tid: '0001',\n\t\t\t\tchanges: [{ kind: 'addIndex', table: 'users', on: ['email'] }],\n\t\t\t}\n\t\t\tconst m2: Migration<typeof adapter> = {\n\t\t\t\tid: '0002',\n\t\t\t\tchanges: [{\n\t\t\t\t\tkind: 'execute',\n\t\t\t\t\tup: async () => { throw new Error('boom') },\n\t\t\t\t}],\n\t\t\t}\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([m1, m2]).build()\n\t\t\tawait expect(migrator.up()).rejects.toThrow(OrmMigrationError)\n\n\t\t\tconst recorded = await adapter.loadMigrations()\n\t\t\texpect(recorded.map((r) => r.id)).toEqual(['0001'])\n\t\t})\n\n\t\ttest('multiple changes in one migration all apply', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tconst m: Migration<typeof adapter> = {\n\t\t\t\tid: '0001-multi',\n\t\t\t\tchanges: [\n\t\t\t\t\t{ kind: 'addIndex', table: 'users', on: ['email'], unique: true },\n\t\t\t\t\t{ kind: 'addIndex', table: 'users', on: ['id', 'email'], name: 'users_compound_idx' },\n\t\t\t\t],\n\t\t\t}\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([m]).build()\n\t\t\tawait migrator.up()\n\n\t\t\texpect(adapter.indexes.size).toBe(2)\n\t\t\texpect(adapter.indexes.has('users_email_idx')).toBe(true)\n\t\t\texpect(adapter.indexes.has('users_compound_idx')).toBe(true)\n\t\t})\n\n\t\ttest('lex-sorts migrations by id before running', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tconst order: string[] = []\n\t\t\tconst m1: Migration<typeof adapter> = {\n\t\t\t\tid: '0002',\n\t\t\t\tchanges: [{ kind: 'execute', up: async () => { order.push('0002') } }],\n\t\t\t}\n\t\t\tconst m2: Migration<typeof adapter> = {\n\t\t\t\tid: '0001',\n\t\t\t\tchanges: [{ kind: 'execute', up: async () => { order.push('0001') } }],\n\t\t\t}\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([m1, m2]).build()\n\t\t\tawait migrator.up()\n\t\t\texpect(order).toEqual(['0001', '0002'])\n\t\t})\n\n\t\ttest('createTable migration creates table metadata', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tconst m: Migration<typeof adapter> = {\n\t\t\t\tid: '0001-create-users',\n\t\t\t\tchanges: [{\n\t\t\t\t\tkind: 'createTable',\n\t\t\t\t\tname: 'users',\n\t\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\t\tfields: [\n\t\t\t\t\t\t{ name: 'email', type: 'string', unique: true },\n\t\t\t\t\t\t{ name: 'age', type: 'number', nullable: true },\n\t\t\t\t\t],\n\t\t\t\t}],\n\t\t\t}\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([m]).build()\n\t\t\tawait migrator.up()\n\n\t\t\texpect(adapter.tables.has('users')).toBe(true)\n\t\t\tconst table = adapter.tables.get('users')!\n\t\t\texpect(table.pk).toEqual({ name: 'id', type: 'string' })\n\t\t\texpect(table.fields.size).toBe(2)\n\t\t\texpect(table.fields.get('email')).toEqual({ name: 'email', type: 'string', unique: true })\n\t\t\texpect(table.fields.get('age')).toEqual({ name: 'age', type: 'number', nullable: true })\n\t\t})\n\n\t\ttest('dropTable migration removes table metadata', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tawait adapter.applyCreateTable({ kind: 'createTable', name: 'users', pk: { name: 'id', type: 'string' }, fields: [] })\n\t\t\tconst m: Migration<typeof adapter> = {\n\t\t\t\tid: '0001-drop-users',\n\t\t\t\tchanges: [{ kind: 'dropTable', name: 'users' }],\n\t\t\t}\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([m]).build()\n\t\t\tawait migrator.up()\n\t\t\texpect(adapter.tables.has('users')).toBe(false)\n\t\t})\n\n\t\ttest('addField migration adds field to existing table', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tawait adapter.applyCreateTable({ kind: 'createTable', name: 'users', pk: { name: 'id', type: 'string' }, fields: [] })\n\t\t\tconst m: Migration<typeof adapter> = {\n\t\t\t\tid: '0001-add-email',\n\t\t\t\tchanges: [{ kind: 'addField', table: 'users', field: { name: 'email', type: 'string' } }],\n\t\t\t}\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([m]).build()\n\t\t\tawait migrator.up()\n\t\t\texpect(adapter.tables.get('users')!.fields.has('email')).toBe(true)\n\t\t})\n\n\t\ttest('dropField migration removes field from table', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tawait adapter.applyCreateTable({ kind: 'createTable', name: 'users', pk: { name: 'id', type: 'string' }, fields: [{ name: 'email', type: 'string' }] })\n\t\t\tconst m: Migration<typeof adapter> = {\n\t\t\t\tid: '0001-drop-email',\n\t\t\t\tchanges: [{ kind: 'dropField', table: 'users', name: 'email' }],\n\t\t\t}\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([m]).build()\n\t\t\tawait migrator.up()\n\t\t\texpect(adapter.tables.get('users')!.fields.has('email')).toBe(false)\n\t\t})\n\n\t\ttest('modifyField migration updates field spec', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tawait adapter.applyCreateTable({ kind: 'createTable', name: 'users', pk: { name: 'id', type: 'string' }, fields: [{ name: 'age', type: 'string' }] })\n\t\t\tconst m: Migration<typeof adapter> = {\n\t\t\t\tid: '0001-modify-age',\n\t\t\t\tchanges: [{ kind: 'modifyField', table: 'users', name: 'age', to: { name: 'age', type: 'number', nullable: true } }],\n\t\t\t}\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([m]).build()\n\t\t\tawait migrator.up()\n\t\t\texpect(adapter.tables.get('users')!.fields.get('age')).toEqual({ name: 'age', type: 'number', nullable: true })\n\t\t})\n\n\t\ttest('renameTable migration renames the table key', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tawait adapter.applyCreateTable({ kind: 'createTable', name: 'users', pk: { name: 'id', type: 'string' }, fields: [] })\n\t\t\tconst m: Migration<typeof adapter> = {\n\t\t\t\tid: '0001-rename-users',\n\t\t\t\tchanges: [{ kind: 'renameTable', from: 'users', to: 'accounts' }],\n\t\t\t}\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([m]).build()\n\t\t\tawait migrator.up()\n\t\t\texpect(adapter.tables.has('users')).toBe(false)\n\t\t\texpect(adapter.tables.has('accounts')).toBe(true)\n\t\t})\n\n\t\ttest('renameField migration renames a field', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tawait adapter.applyCreateTable({ kind: 'createTable', name: 'users', pk: { name: 'id', type: 'string' }, fields: [{ name: 'name', type: 'string' }] })\n\t\t\tconst m: Migration<typeof adapter> = {\n\t\t\t\tid: '0001-rename-name',\n\t\t\t\tchanges: [{ kind: 'renameField', table: 'users', from: 'name', to: 'fullName' }],\n\t\t\t}\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([m]).build()\n\t\t\tawait migrator.up()\n\t\t\texpect(adapter.tables.get('users')!.fields.has('name')).toBe(false)\n\t\t\texpect(adapter.tables.get('users')!.fields.has('fullName')).toBe(true)\n\t\t\texpect(adapter.tables.get('users')!.fields.get('fullName')!.name).toBe('fullName')\n\t\t})\n\n\t\ttest('dropIndex migration removes an index', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tawait adapter.applyAddIndex({ kind: 'addIndex', table: 'users', on: ['email'], name: 'users_email_idx' })\n\t\t\tconst m: Migration<typeof adapter> = {\n\t\t\t\tid: '0001-drop-idx',\n\t\t\t\tchanges: [{ kind: 'dropIndex', name: 'users_email_idx' }],\n\t\t\t}\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([m]).build()\n\t\t\tawait migrator.up()\n\t\t\texpect(adapter.indexes.has('users_email_idx')).toBe(false)\n\t\t})\n\n\t\ttest('addForeignKey migration creates a FK entry', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tconst m: Migration<typeof adapter> = {\n\t\t\t\tid: '0001-add-fk',\n\t\t\t\tchanges: [{ kind: 'addForeignKey', table: 'posts', on: 'authorId', references: { table: 'users', column: 'id' } }],\n\t\t\t}\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([m]).build()\n\t\t\tawait migrator.up()\n\t\t\texpect(adapter.foreignKeys.has('posts_authorId_fk')).toBe(true)\n\t\t\tconst fk = adapter.foreignKeys.get('posts_authorId_fk')!\n\t\t\texpect(fk.table).toBe('posts')\n\t\t\texpect(fk.on).toBe('authorId')\n\t\t\texpect(fk.references).toEqual({ table: 'users', column: 'id' })\n\t\t})\n\n\t\ttest('dropForeignKey migration removes a FK entry', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tawait adapter.applyAddForeignKey({ kind: 'addForeignKey', table: 'posts', on: 'authorId', references: { table: 'users', column: 'id' }, name: 'posts_authorId_fk' })\n\t\t\tconst m: Migration<typeof adapter> = {\n\t\t\t\tid: '0001-drop-fk',\n\t\t\t\tchanges: [{ kind: 'dropForeignKey', table: 'posts', name: 'posts_authorId_fk' }],\n\t\t\t}\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([m]).build()\n\t\t\tawait migrator.up()\n\t\t\texpect(adapter.foreignKeys.has('posts_authorId_fk')).toBe(false)\n\t\t})\n\n\t\ttest('full lifecycle: create table, add fields, indexes, FKs, then rename and drop', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tconst migrations: Migration<typeof adapter>[] = [\n\t\t\t\t{\n\t\t\t\t\tid: '0001-create',\n\t\t\t\t\tchanges: [\n\t\t\t\t\t\t{ kind: 'createTable', name: 'users', pk: { name: 'id', type: 'string' }, fields: [{ name: 'email', type: 'string' }] },\n\t\t\t\t\t\t{ kind: 'createTable', name: 'posts', pk: { name: 'id', type: 'string' }, fields: [{ name: 'title', type: 'string' }, { name: 'authorId', type: 'string' }] },\n\t\t\t\t\t\t{ kind: 'addIndex', table: 'users', on: ['email'], unique: true },\n\t\t\t\t\t\t{ kind: 'addForeignKey', table: 'posts', on: 'authorId', references: { table: 'users', column: 'id' } },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tid: '0002-evolve',\n\t\t\t\t\tchanges: [\n\t\t\t\t\t\t{ kind: 'addField', table: 'users', field: { name: 'age', type: 'number', nullable: true } },\n\t\t\t\t\t\t{ kind: 'renameField', table: 'users', from: 'email', to: 'emailAddress' },\n\t\t\t\t\t\t{ kind: 'renameTable', from: 'posts', to: 'articles' },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tid: '0003-cleanup',\n\t\t\t\t\tchanges: [\n\t\t\t\t\t\t{ kind: 'dropField', table: 'users', name: 'age' },\n\t\t\t\t\t\t{ kind: 'dropForeignKey', table: 'posts', name: 'posts_authorId_fk' },\n\t\t\t\t\t\t{ kind: 'dropIndex', name: 'users_email_idx' },\n\t\t\t\t\t\t{ kind: 'dropTable', name: 'articles' },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t]\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations(migrations).build()\n\t\t\tconst result = await migrator.up()\n\t\t\texpect(result.ran).toEqual(['0001-create', '0002-evolve', '0003-cleanup'])\n\n\t\t\texpect(adapter.tables.has('users')).toBe(true)\n\t\t\texpect(adapter.tables.get('users')!.fields.has('emailAddress')).toBe(true)\n\t\t\texpect(adapter.tables.get('users')!.fields.has('age')).toBe(false)\n\t\t\texpect(adapter.tables.has('articles')).toBe(false)\n\t\t\texpect(adapter.indexes.has('users_email_idx')).toBe(false)\n\t\t\texpect(adapter.foreignKeys.has('posts_authorId_fk')).toBe(false)\n\t\t})\n\n\t\ttest('tx:true migration that throws rolls back both user effects and tracker row', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tconst m: Migration<typeof adapter> = {\n\t\t\t\tid: '0001-will-fail',\n\t\t\t\tchanges: [\n\t\t\t\t\t{ kind: 'addIndex', table: 'users', on: ['email'] },\n\t\t\t\t\t{\n\t\t\t\t\t\tkind: 'execute',\n\t\t\t\t\t\tup: async (r) => {\n\t\t\t\t\t\t\tawait r.on(UserSchema).one().create({ id: 'partial', email: 'fail@test.com' })\n\t\t\t\t\t\t\tthrow new Error('mid-migration boom')\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t}\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([m]).build()\n\t\t\tawait expect(migrator.up()).rejects.toThrow(OrmMigrationError)\n\n\t\t\tconst recorded = await adapter.loadMigrations()\n\t\t\texpect(recorded).toHaveLength(0)\n\t\t\texpect(adapter.indexes.size).toBe(0)\n\t\t\tconst row = await repo.on(UserSchema).one().id('partial').find()\n\t\t\texpect(row).toBeNull()\n\t\t})\n\n\t\ttest('tx:false migration that throws leaves partial state durable', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tconst m: Migration<typeof adapter> = {\n\t\t\t\tid: '0001-no-tx',\n\t\t\t\ttx: false,\n\t\t\t\tchanges: [\n\t\t\t\t\t{ kind: 'addIndex', table: 'users', on: ['email'] },\n\t\t\t\t\t{\n\t\t\t\t\t\tkind: 'execute',\n\t\t\t\t\t\tup: async (r) => {\n\t\t\t\t\t\t\tawait r.on(UserSchema).one().create({ id: 'durable', email: 'stay@test.com' })\n\t\t\t\t\t\t\tthrow new Error('partial failure')\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t}\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([m]).build()\n\t\t\tawait expect(migrator.up()).rejects.toThrow(OrmMigrationError)\n\n\t\t\tconst recorded = await adapter.loadMigrations()\n\t\t\texpect(recorded).toHaveLength(0)\n\t\t\texpect(adapter.indexes.has('users_email_idx')).toBe(true)\n\t\t\tconst row = await repo.on(UserSchema).one().id('durable').find()\n\t\t\texpect(row).not.toBeNull()\n\t\t\texpect(row!.email).toBe('stay@test.com')\n\t\t})\n\n\t\ttest('atomic-across-migrations: outer repo.session() wraps all in one tx', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tconst m1: Migration<typeof adapter> = {\n\t\t\t\tid: '0001',\n\t\t\t\tchanges: [{ kind: 'addIndex', table: 'users', on: ['email'] }],\n\t\t\t}\n\t\t\tconst m2: Migration<typeof adapter> = {\n\t\t\t\tid: '0002',\n\t\t\t\tchanges: [{\n\t\t\t\t\tkind: 'execute',\n\t\t\t\t\tup: async () => { throw new Error('fail in m2') },\n\t\t\t\t}],\n\t\t\t}\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([m1, m2]).build()\n\t\t\tawait expect(\n\t\t\t\trepo.session(() => migrator.up()),\n\t\t\t).rejects.toThrow()\n\n\t\t\tconst recorded = await adapter.loadMigrations()\n\t\t\texpect(recorded).toHaveLength(0)\n\t\t\texpect(adapter.indexes.size).toBe(0)\n\t\t})\n\n\t\ttest('simultaneous up() calls serialize via acquireMigrationLock', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tconst order: string[] = []\n\t\t\tlet resolveGate!: () => void\n\t\t\tconst gate = new Promise<void>((r) => { resolveGate = r })\n\n\t\t\tconst migrations: Migration<typeof adapter>[] = [\n\t\t\t\t{\n\t\t\t\t\tid: '0001',\n\t\t\t\t\tchanges: [{\n\t\t\t\t\t\tkind: 'execute',\n\t\t\t\t\t\tup: async () => {\n\t\t\t\t\t\t\torder.push('0001-start')\n\t\t\t\t\t\t\tawait gate\n\t\t\t\t\t\t\torder.push('0001-end')\n\t\t\t\t\t\t},\n\t\t\t\t\t}],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tid: '0002',\n\t\t\t\t\tchanges: [{\n\t\t\t\t\t\tkind: 'execute',\n\t\t\t\t\t\tup: async () => { order.push('0002') },\n\t\t\t\t\t}],\n\t\t\t\t},\n\t\t\t]\n\n\t\t\tconst migrator1 = Migrator.from(repo, adapter).migrations(migrations).build()\n\t\t\tconst migrator2 = Migrator.from(repo, adapter).migrations(migrations).build()\n\n\t\t\tconst p1 = migrator1.up()\n\t\t\tconst p2 = migrator2.up()\n\n\t\t\tawait new Promise((r) => setTimeout(r, 10))\n\t\t\texpect(order).toEqual(['0001-start'])\n\n\t\t\tresolveGate()\n\t\t\tawait Promise.all([p1, p2])\n\n\t\t\texpect(order).toEqual(['0001-start', '0001-end', '0002'])\n\t\t})\n\n\t\ttest('tx:false retries cleanly from scratch after failure', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tlet callCount = 0\n\t\t\tconst m: Migration<typeof adapter> = {\n\t\t\t\tid: '0001-retry',\n\t\t\t\ttx: false,\n\t\t\t\tchanges: [{\n\t\t\t\t\tkind: 'execute',\n\t\t\t\t\tup: async (r) => {\n\t\t\t\t\t\tcallCount++\n\t\t\t\t\t\tif (callCount === 1) {\n\t\t\t\t\t\t\tawait r.on(UserSchema).one().create({ id: 'partial', email: 'p@test.com' })\n\t\t\t\t\t\t\tthrow new Error('first attempt fails')\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait r.on(UserSchema).one().create({ id: 'complete', email: 'c@test.com' })\n\t\t\t\t\t},\n\t\t\t\t}],\n\t\t\t}\n\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([m]).build()\n\t\t\tawait expect(migrator.up()).rejects.toThrow(OrmMigrationError)\n\t\t\texpect(callCount).toBe(1)\n\n\t\t\tconst migrator2 = Migrator.from(repo, adapter).migrations([m]).build()\n\t\t\tconst result = await migrator2.up()\n\t\t\texpect(result.ran).toEqual(['0001-retry'])\n\t\t\texpect(callCount).toBe(2)\n\t\t})\n\n\t\ttest('type-level: withoutLock() rejected when adapter declares acquireMigrationLock', () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tconst step = Migrator.from(repo, adapter).migrations([])\n\t\t\texpectTypeOf(step).toHaveProperty('build')\n\t\t\texpectTypeOf(step).not.toHaveProperty('withoutLock')\n\t\t})\n\n\t\ttest('type-level: withoutLock() accepted when adapter lacks acquireMigrationLock', async () => {\n\t\t\tconst { OrmAdapter } = await import('../orm-adapter')\n\t\t\tclass NoLockAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({ table: v.string() })\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\tasync loadMigrations() { return [] }\n\t\t\t\tasync recordMigration() {}\n\t\t\t\tasync session<T>(fn: () => Promise<T>): Promise<T> { return fn() }\n\t\t\t}\n\t\t\tconst nlAdapter = new (NoLockAdapter as any)() as NoLockAdapter\n\t\t\tconst nlRepo = new Repo({ adapter: nlAdapter, resolve: (s) => ({ table: s.name }) })\n\t\t\tconst step = Migrator.from(nlRepo, nlAdapter).migrations([])\n\t\t\texpectTypeOf(step).toHaveProperty('build')\n\t\t\texpectTypeOf(step).toHaveProperty('withoutLock')\n\t\t})\n\n\t\ttest('up({ to }) runs only migrations up to and including the target', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tconst order: string[] = []\n\t\t\tconst migrations: Migration<typeof adapter>[] = [\n\t\t\t\t{ id: 'm-001', changes: [{ kind: 'execute', up: async () => { order.push('m-001') } }] },\n\t\t\t\t{ id: 'm-002', changes: [{ kind: 'execute', up: async () => { order.push('m-002') } }] },\n\t\t\t\t{ id: 'm-003', changes: [{ kind: 'execute', up: async () => { order.push('m-003') } }] },\n\t\t\t]\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations(migrations).build()\n\t\t\tconst result = await migrator.up({ to: 'm-002' })\n\t\t\texpect(result.ran).toEqual(['m-001', 'm-002'])\n\t\t\texpect(order).toEqual(['m-001', 'm-002'])\n\t\t})\n\n\t\ttest('up({ to }) is a no-op when target already applied', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tawait adapter.recordMigration('m-001', Date.now())\n\t\t\tawait adapter.recordMigration('m-002', Date.now())\n\t\t\tconst migrations: Migration<typeof adapter>[] = [\n\t\t\t\t{ id: 'm-001', changes: [] },\n\t\t\t\t{ id: 'm-002', changes: [] },\n\t\t\t\t{ id: 'm-003', changes: [] },\n\t\t\t]\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations(migrations).build()\n\t\t\tconst result = await migrator.up({ to: 'm-002' })\n\t\t\texpect(result.ran).toEqual([])\n\t\t})\n\n\t\ttest('up({ to: unknown }) throws OrmMigrationError before any side effect', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tlet executed = false\n\t\t\tconst migrations: Migration<typeof adapter>[] = [\n\t\t\t\t{ id: 'm-001', changes: [{ kind: 'execute', up: async () => { executed = true } }] },\n\t\t\t]\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations(migrations).build()\n\t\t\tawait expect(migrator.up({ to: 'unknown' })).rejects.toThrow(OrmMigrationError)\n\t\t\texpect(executed).toBe(false)\n\t\t\ttry {\n\t\t\t\tawait Migrator.from(repo, adapter).migrations(migrations).build().up({ to: 'unknown' })\n\t\t\t} catch (err: any) {\n\t\t\t\texpect(err.phase).toBe('load')\n\t\t\t}\n\t\t})\n\n\t\ttest('up({ steps: 1 }) runs exactly 1 pending migration', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tconst migrations: Migration<typeof adapter>[] = [\n\t\t\t\t{ id: '0001', changes: [{ kind: 'addIndex', table: 'users', on: ['email'] }] },\n\t\t\t\t{ id: '0002', changes: [{ kind: 'addIndex', table: 'users', on: ['id'] }] },\n\t\t\t\t{ id: '0003', changes: [{ kind: 'addIndex', table: 'users', on: ['email', 'id'] }] },\n\t\t\t]\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations(migrations).build()\n\t\t\tconst result = await migrator.up({ steps: 1 })\n\t\t\texpect(result.ran).toEqual(['0001'])\n\t\t\texpect(adapter.indexes.size).toBe(1)\n\t\t})\n\n\t\ttest('status() returns sorted entries with correct applied/pending flags', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tconst now = Date.now()\n\t\t\tawait adapter.recordMigration('0002', now)\n\t\t\tconst migrations: Migration<typeof adapter>[] = [\n\t\t\t\t{ id: '0003', changes: [] },\n\t\t\t\t{ id: '0001', changes: [] },\n\t\t\t\t{ id: '0002', changes: [] },\n\t\t\t]\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations(migrations).build()\n\t\t\tconst entries = await migrator.status()\n\t\t\texpect(entries).toEqual([\n\t\t\t\t{ id: '0001', applied: false },\n\t\t\t\t{ id: '0002', applied: true, appliedAt: now },\n\t\t\t\t{ id: '0003', applied: false },\n\t\t\t])\n\t\t})\n\n\t\ttest('dry() returns the same plan up() would execute, without executing', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tawait adapter.recordMigration('0001', Date.now())\n\t\t\tlet executed = false\n\t\t\tconst migrations: Migration<typeof adapter>[] = [\n\t\t\t\t{ id: '0001', changes: [] },\n\t\t\t\t{ id: '0002', changes: [{ kind: 'execute', up: async () => { executed = true } }] },\n\t\t\t\t{ id: '0003', changes: [] },\n\t\t\t]\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations(migrations).build()\n\t\t\tconst plan = await migrator.dry()\n\t\t\texpect(plan).toEqual({ would: ['0002', '0003'] })\n\t\t\texpect(executed).toBe(false)\n\t\t})\n\n\t\ttest('dry(opts) respects to/steps', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tconst migrations: Migration<typeof adapter>[] = [\n\t\t\t\t{ id: '0001', changes: [] },\n\t\t\t\t{ id: '0002', changes: [] },\n\t\t\t\t{ id: '0003', changes: [] },\n\t\t\t]\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations(migrations).build()\n\t\t\texpect(await migrator.dry({ to: '0002' })).toEqual({ would: ['0001', '0002'] })\n\t\t\texpect(await migrator.dry({ steps: 1 })).toEqual({ would: ['0001'] })\n\t\t})\n\n\t\ttest('orphan migration in tracker throws OrmMigrationError with phase load', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tawait adapter.recordMigration('ghost-001', Date.now())\n\t\t\tconst migrations: Migration<typeof adapter>[] = [\n\t\t\t\t{ id: '0001', changes: [] },\n\t\t\t]\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations(migrations).build()\n\t\t\ttry {\n\t\t\t\tawait migrator.up()\n\t\t\t\texpect.unreachable('should have thrown')\n\t\t\t} catch (err: any) {\n\t\t\t\texpect(err).toBeInstanceOf(OrmMigrationError)\n\t\t\t\texpect(err.phase).toBe('load')\n\t\t\t\texpect(err.cause).toContain('ghost-001')\n\t\t\t}\n\t\t})\n\n\t\ttest('failing user code throws OrmMigrationError with phase user', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tconst m: Migration<typeof adapter> = {\n\t\t\t\tid: '0001',\n\t\t\t\tchanges: [{ kind: 'execute', up: async () => { throw new Error('user boom') } }],\n\t\t\t}\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([m]).build()\n\t\t\ttry {\n\t\t\t\tawait migrator.up()\n\t\t\t\texpect.unreachable('should have thrown')\n\t\t\t} catch (err: any) {\n\t\t\t\texpect(err).toBeInstanceOf(OrmMigrationError)\n\t\t\t\texpect(err.phase).toBe('user')\n\t\t\t\texpect(err.id).toBe('0001')\n\t\t\t}\n\t\t})\n\n\t\ttest('failing recordMigration throws OrmMigrationError with phase record', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tconst origRecord = adapter.recordMigration.bind(adapter)\n\t\t\tadapter.recordMigration = async (id: string, at: number) => {\n\t\t\t\tif (id === '0001') throw new Error('record boom')\n\t\t\t\treturn origRecord(id, at)\n\t\t\t}\n\t\t\tconst m: Migration<typeof adapter> = { id: '0001', changes: [] }\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([m]).build()\n\t\t\ttry {\n\t\t\t\tawait migrator.up()\n\t\t\t\texpect.unreachable('should have thrown')\n\t\t\t} catch (err: any) {\n\t\t\t\texpect(err).toBeInstanceOf(OrmMigrationError)\n\t\t\t\texpect(err.phase).toBe('record')\n\t\t\t\texpect(err.id).toBe('0001')\n\t\t\t}\n\t\t})\n\n\t\ttest('failing acquireMigrationLock throws OrmMigrationError with phase lock', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tadapter.acquireMigrationLock = async () => { throw new Error('lock boom') }\n\t\t\tconst m: Migration<typeof adapter> = { id: '0001', changes: [] }\n\t\t\tconst migrator = Migrator.from(repo, adapter).migrations([m]).build()\n\t\t\ttry {\n\t\t\t\tawait migrator.up()\n\t\t\t\texpect.unreachable('should have thrown')\n\t\t\t} catch (err: any) {\n\t\t\t\texpect(err).toBeInstanceOf(OrmMigrationError)\n\t\t\t\texpect(err.phase).toBe('lock')\n\t\t\t}\n\t\t})\n\t})\n}\n","import { toFieldName, type AnyField } from './fields'\n\nexport class OrderBy {\n\treadonly field: string\n\tconstructor(\n\t\tfield: string | AnyField,\n\t\treadonly direction: 'asc' | 'desc',\n\t) {\n\t\tthis.field = toFieldName(field)\n\t}\n}\n\nexport type QueryOptions<Sel extends string = string> = {\n\torderBy?: OrderBy[]\n\tlimit?: number\n\toffset?: number\n\tselect?: readonly Sel[]\n}\n\nexport type IterationOptions = {\n\tbatchSize?: number\n}\n\nexport type IterationQueryOptions<Sel extends string = string> = QueryOptions<Sel> & IterationOptions\n","import type { AnyField, Field } from './fields'\nimport type { AnySchema, SchemaOutput } from './schema'\n\ntype SchemaPkValueType<S extends AnySchema> = NonNullable<S['pkField']['__valueType']>\ntype FkPkMatch<S extends AnySchema, FK extends AnyField> =\n\tNonNullable<FK['__valueType']> extends SchemaPkValueType<S> ? FK : never\n\nexport class ManyRelation<\n\tN extends string = string,\n\tTgtOutput extends Record<string, any> = Record<string, any>,\n\tFK extends AnyField = AnyField,\n> {\n\tdeclare readonly _output: TgtOutput\n\tconstructor(\n\t\treadonly name: N,\n\t\treadonly source: AnySchema,\n\t\treadonly target: AnySchema,\n\t\treadonly foreignKey: FK,\n\t\treadonly references: AnyField,\n\t) {}\n}\n\nexport class OneRelation<\n\tN extends string = string,\n\tTgtOutput extends Record<string, any> = Record<string, any>,\n\tFK extends AnyField = AnyField,\n> {\n\tdeclare readonly _output: TgtOutput\n\tconstructor(\n\t\treadonly name: N,\n\t\treadonly source: AnySchema,\n\t\treadonly target: AnySchema,\n\t\treadonly foreignKey: FK,\n\t\treadonly fkOwner: 'source' | 'target',\n\t\treadonly references: AnyField,\n\t) {}\n}\n\nexport type AnyRelDef = ManyRelation<string, Record<string, any>, any> | OneRelation<string, Record<string, any>, any>\n\nexport type ResolveRelDef<D extends AnyRelDef> =\n\tD extends OneRelation<any, infer TOut, any> ? TOut | null : D extends ManyRelation<any, infer TOut, any> ? TOut[] : never\n\nexport interface NestedPreloadDef<D extends AnyRelDef = AnyRelDef> {\n\tdef: D\n\tpreloads?: readonly AnyPreloadDef[]\n}\n\nexport type AnyPreloadDef = AnyRelDef | NestedPreloadDef\n\ntype Shift<T extends readonly unknown[]> = T extends readonly [unknown, ...infer R] ? R : []\ntype NodeName<N extends AnyPreloadDef> = N extends AnyRelDef\n\t? N['name']\n\t: N extends NestedPreloadDef<infer D extends AnyRelDef>\n\t\t? D['name']\n\t\t: never\n\ntype ResolveRelDefWithNested<D extends AnyRelDef, P extends readonly AnyPreloadDef[], Depth extends readonly unknown[]> = Depth extends []\n\t? ResolveRelDef<D>\n\t: D extends OneRelation<any, infer TOut, any>\n\t\t? (TOut & PreloadedMap<P, Shift<Depth>>) | null\n\t\t: D extends ManyRelation<any, infer TOut, any>\n\t\t\t? (TOut & PreloadedMap<P, Shift<Depth>>)[]\n\t\t\t: never\n\ntype NodeValue<N extends AnyPreloadDef, Depth extends readonly unknown[]> =\n\tN extends OneRelation<any, infer TOut, any>\n\t\t? TOut | null\n\t\t: N extends ManyRelation<any, infer TOut, any>\n\t\t\t? TOut[]\n\t\t\t: N extends { def: infer D extends AnyRelDef; preloads?: infer P extends readonly AnyPreloadDef[] }\n\t\t\t\t? ResolveRelDefWithNested<D, P, Depth>\n\t\t\t\t: never\n\nexport type PreloadedMap<P extends readonly AnyPreloadDef[], Depth extends readonly unknown[] = [1, 2, 3, 4, 5]> = Depth extends []\n\t? Record<never, never>\n\t: {\n\t\t\t[N in P[number] as NodeName<N>]: NodeValue<N, Depth>\n\t\t}\n\nclass RelationsBuilder<S extends AnySchema, R extends Record<string, AnyRelDef> = Record<never, never>> {\n\treadonly #source: S\n\treadonly #defs: Record<string, AnyRelDef>\n\n\tconstructor(source: S, defs?: Record<string, AnyRelDef>) {\n\t\tthis.#source = source\n\t\tthis.#defs = defs ?? {}\n\t}\n\n\thasMany<K extends string, T extends AnySchema, FK extends Field<any, any, T>>(\n\t\tname: K extends keyof R ? never : K,\n\t\tfk: FkPkMatch<S, FK>,\n\t): RelationsBuilder<\n\t\tS,\n\t\t{\n\t\t\t[Key in keyof R | K]: Key extends K\n\t\t\t\t? ManyRelation<K, SchemaOutput<T>, FK>\n\t\t\t\t: Key extends keyof R\n\t\t\t\t\t? R[Key]\n\t\t\t\t\t: never\n\t\t}\n\t> {\n\t\tconst target = (fk as any).__schema as AnySchema\n\t\tconst nextDefs = { ...this.#defs, [name]: new ManyRelation(name, this.#source, target, fk as any, this.#source.pkField) }\n\t\treturn new RelationsBuilder(this.#source, nextDefs) as any\n\t}\n\n\thasOne<K extends string, T extends AnySchema, FK extends Field<any, any, T>>(\n\t\tname: K extends keyof R ? never : K,\n\t\tfk: FkPkMatch<S, FK>,\n\t): RelationsBuilder<\n\t\tS,\n\t\t{\n\t\t\t[Key in keyof R | K]: Key extends K\n\t\t\t\t? OneRelation<K, SchemaOutput<T>, FK>\n\t\t\t\t: Key extends keyof R\n\t\t\t\t\t? R[Key]\n\t\t\t\t\t: never\n\t\t}\n\t> {\n\t\tconst target = (fk as any).__schema as AnySchema\n\t\tconst nextDefs = { ...this.#defs, [name]: new OneRelation(name, this.#source, target, fk as any, 'target', this.#source.pkField) }\n\t\treturn new RelationsBuilder(this.#source, nextDefs) as any\n\t}\n\n\tbelongsTo<K extends string, T extends AnySchema, FK extends Field<any, any, S>>(\n\t\tname: K extends keyof R ? never : K,\n\t\tfk: FkPkMatch<T, FK>,\n\t\ttarget: T,\n\t\treferences?: Field<NonNullable<FK['__valueType']>, any, T>,\n\t): RelationsBuilder<\n\t\tS,\n\t\t{\n\t\t\t[Key in keyof R | K]: Key extends K\n\t\t\t\t? OneRelation<K, SchemaOutput<T>, FK>\n\t\t\t\t: Key extends keyof R\n\t\t\t\t\t? R[Key]\n\t\t\t\t\t: never\n\t\t}\n\t> {\n\t\tconst ref = references ?? target.pkField\n\t\tconst nextDefs = { ...this.#defs, [name]: new OneRelation(name, this.#source, target, fk as any, 'source', ref as any) }\n\t\treturn new RelationsBuilder(this.#source, nextDefs) as any\n\t}\n\n\tbuild(): R {\n\t\treturn this.#defs as R\n\t}\n}\n\nexport class Relations {\n\tstatic from<S extends AnySchema>(source: S) {\n\t\treturn new RelationsBuilder(source)\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect, expectTypeOf } = import.meta.vitest\n\tconst { v } = await import('valleyed')\n\tconst { Schema } = await import('./schema')\n\n\tdescribe('Relations.from()', () => {\n\t\tconst UserSchema = Schema.from('users')\n\t\t\t.pk('id', v.string(), () => 'user-id')\n\t\t\t.field('name', v.string())\n\t\t\t.field('orgId', v.string())\n\t\t\t.build()\n\n\t\tconst PostSchema = Schema.from('posts')\n\t\t\t.pk('id', v.string(), () => 'post-id')\n\t\t\t.field('title', v.string())\n\t\t\t.field('userId', v.string())\n\t\t\t.build()\n\n\t\tconst OrgSchema = Schema.from('orgs')\n\t\t\t.pk('id', v.string(), () => 'org-id')\n\t\t\t.field('name', v.string())\n\t\t\t.build()\n\n\t\tconst ProfileSchema = Schema.from('profiles')\n\t\t\t.pk('id', v.string(), () => 'profile-id')\n\t\t\t.field('bio', v.string())\n\t\t\t.field('userId', v.string())\n\t\t\t.build()\n\n\t\ttest('builds relations with hasMany, belongsTo, hasOne', () => {\n\t\t\tconst rels = Relations.from(UserSchema)\n\t\t\t\t.hasMany('posts', PostSchema.fields.userId)\n\t\t\t\t.belongsTo('org', UserSchema.fields.orgId, OrgSchema)\n\t\t\t\t.hasOne('profile', ProfileSchema.fields.userId)\n\t\t\t\t.build()\n\n\t\t\texpect(rels.posts).toBeInstanceOf(ManyRelation)\n\t\t\texpect(rels.org).toBeInstanceOf(OneRelation)\n\t\t\texpect(rels.profile).toBeInstanceOf(OneRelation)\n\t\t})\n\n\t\ttest('source references use outer-scope const (no src param)', () => {\n\t\t\tconst rels = Relations.from(UserSchema)\n\t\t\t\t.belongsTo('org', UserSchema.fields.orgId, OrgSchema)\n\t\t\t\t.build()\n\n\t\t\texpect(rels.org.foreignKey).toBe(UserSchema.fields.orgId)\n\t\t\texpect(rels.org.target).toBe(OrgSchema)\n\t\t})\n\n\t\ttest('clone-on-step: .hasMany() returns a new builder', () => {\n\t\t\tconst base = Relations.from(UserSchema)\n\t\t\tconst a = base.hasMany('posts', PostSchema.fields.userId)\n\t\t\texpect(a).not.toBe(base)\n\t\t})\n\n\t\ttest('clone-on-step: .hasOne() returns a new builder', () => {\n\t\t\tconst base = Relations.from(UserSchema)\n\t\t\tconst a = base.hasOne('profile', ProfileSchema.fields.userId)\n\t\t\texpect(a).not.toBe(base)\n\t\t})\n\n\t\ttest('clone-on-step: .belongsTo() returns a new builder', () => {\n\t\t\tconst base = Relations.from(UserSchema)\n\t\t\tconst a = base.belongsTo('org', UserSchema.fields.orgId, OrgSchema)\n\t\t\texpect(a).not.toBe(base)\n\t\t})\n\n\t\ttest('clone-on-step: fan-out from shared base does not pollute either branch', () => {\n\t\t\tconst base = Relations.from(UserSchema)\n\t\t\tconst branchA = base.hasMany('posts', PostSchema.fields.userId).build()\n\t\t\tconst branchB = base.hasOne('profile', ProfileSchema.fields.userId).build()\n\n\t\t\texpect(Object.keys(branchA)).toEqual(['posts'])\n\t\t\texpect(Object.keys(branchB)).toEqual(['profile'])\n\t\t})\n\t})\n\n\tdescribe('Relations.from() behavior', () => {\n\t\tconst UserSchema = Schema.from('users')\n\t\t\t.pk('id', v.string(), () => 'user-id')\n\t\t\t.field('name', v.string())\n\t\t\t.field('orgId', v.string())\n\t\t\t.field('managerId', v.optional(v.string()), { onCreate: () => undefined })\n\t\t\t.build()\n\n\t\tconst PostSchema = Schema.from('posts')\n\t\t\t.pk('id', v.string(), () => 'post-id')\n\t\t\t.field('title', v.string())\n\t\t\t.field('userId', v.string())\n\t\t\t.build()\n\n\t\tconst OrgSchema = Schema.from('orgs')\n\t\t\t.pk('id', v.string(), () => 'org-id')\n\t\t\t.field('name', v.string())\n\t\t\t.build()\n\n\t\tconst TagSchema = Schema.from('tags')\n\t\t\t.pk('id', v.string(), () => 'tag-id')\n\t\t\t.field('label', v.string())\n\t\t\t.build()\n\n\t\tconst PostTagSchema = Schema.from('post_tags')\n\t\t\t.pk('id', v.string(), () => 'pt-id')\n\t\t\t.field('postId', v.string())\n\t\t\t.field('tagId', v.string())\n\t\t\t.build()\n\n\t\tconst ProfileSchema = Schema.from('profiles')\n\t\t\t.pk('id', v.string(), () => 'profile-id')\n\t\t\t.field('bio', v.string())\n\t\t\t.field('userId', v.string())\n\t\t\t.build()\n\n\t\tconst UserRels = Relations.from(UserSchema)\n\t\t\t.hasMany('posts', PostSchema.fields.userId)\n\t\t\t.belongsTo('org', UserSchema.fields.orgId, OrgSchema)\n\t\t\t.hasOne('profile', ProfileSchema.fields.userId)\n\t\t\t.build()\n\n\t\tconst PostRels = Relations.from(PostSchema)\n\t\t\t.belongsTo('author', PostSchema.fields.userId, UserSchema)\n\t\t\t.hasMany('postTags', PostTagSchema.fields.postId)\n\t\t\t.build()\n\n\t\tconst TagRels = Relations.from(TagSchema)\n\t\t\t.hasMany('postTags', PostTagSchema.fields.tagId)\n\t\t\t.build()\n\n\t\tconst PostTagRels = Relations.from(PostTagSchema)\n\t\t\t.belongsTo('post', PostTagSchema.fields.postId, PostSchema)\n\t\t\t.belongsTo('tag', PostTagSchema.fields.tagId, TagSchema)\n\t\t\t.build()\n\n\t\ttest('hasMany returns ManyRelation instances', () => {\n\t\t\texpect(UserRels.posts).toBeInstanceOf(ManyRelation)\n\t\t\texpect(PostRels.postTags).toBeInstanceOf(ManyRelation)\n\t\t\texpect(TagRels.postTags).toBeInstanceOf(ManyRelation)\n\t\t})\n\n\t\ttest('belongsTo returns OneRelation instances', () => {\n\t\t\texpect(UserRels.org).toBeInstanceOf(OneRelation)\n\t\t\texpect(PostRels.author).toBeInstanceOf(OneRelation)\n\t\t\texpect(PostTagRels.post).toBeInstanceOf(OneRelation)\n\t\t\texpect(PostTagRels.tag).toBeInstanceOf(OneRelation)\n\t\t})\n\n\t\ttest('hasOne returns OneRelation instances', () => {\n\t\t\texpect(UserRels.profile).toBeInstanceOf(OneRelation)\n\t\t})\n\n\t\ttest('hasMany stores correct source, target, foreignKey, and references', () => {\n\t\t\tconst rel = UserRels.posts\n\t\t\texpect(rel.name).toBe('posts')\n\t\t\texpect(rel.source).toBe(UserSchema)\n\t\t\texpect(rel.target).toBe(PostSchema)\n\t\t\texpect(rel.foreignKey).toBe(PostSchema.fields.userId)\n\t\t\texpect(rel.references).toBe(UserSchema.pkField)\n\t\t})\n\n\t\ttest('hasOne stores correct source, target, foreignKey, and fkOwner', () => {\n\t\t\tconst rel = UserRels.profile\n\t\t\texpect(rel.name).toBe('profile')\n\t\t\texpect(rel.source).toBe(UserSchema)\n\t\t\texpect(rel.target).toBe(ProfileSchema)\n\t\t\texpect(rel.foreignKey).toBe(ProfileSchema.fields.userId)\n\t\t\texpect(rel.fkOwner).toBe('target')\n\t\t\texpect(rel.references).toBe(UserSchema.pkField)\n\t\t})\n\n\t\ttest('belongsTo stores correct source, target, foreignKey, and fkOwner', () => {\n\t\t\tconst rel = UserRels.org\n\t\t\texpect(rel.name).toBe('org')\n\t\t\texpect(rel.source).toBe(UserSchema)\n\t\t\texpect(rel.target).toBe(OrgSchema)\n\t\t\texpect(rel.foreignKey).toBe(UserSchema.fields.orgId)\n\t\t\texpect(rel.fkOwner).toBe('source')\n\t\t\texpect(rel.references).toBe(OrgSchema.pkField)\n\t\t})\n\n\t\ttest('join-table hasMany stores correct metadata', () => {\n\t\t\tconst rel = PostRels.postTags\n\t\t\texpect(rel.name).toBe('postTags')\n\t\t\texpect(rel.source).toBe(PostSchema)\n\t\t\texpect(rel.target).toBe(PostTagSchema)\n\t\t\texpect(rel.foreignKey).toBe(PostTagSchema.fields.postId)\n\t\t})\n\n\t\ttest('join-table belongsTo stores correct metadata', () => {\n\t\t\tconst rel = PostTagRels.tag\n\t\t\texpect(rel.name).toBe('tag')\n\t\t\texpect(rel.source).toBe(PostTagSchema)\n\t\t\texpect(rel.target).toBe(TagSchema)\n\t\t\texpect(rel.foreignKey).toBe(PostTagSchema.fields.tagId)\n\t\t})\n\n\t\ttest('self-referential relation works without special casing', () => {\n\t\t\tconst SelfRels = Relations.from(UserSchema)\n\t\t\t\t.belongsTo('manager', UserSchema.fields.managerId!, UserSchema)\n\t\t\t\t.build()\n\n\t\t\texpect(SelfRels.manager).toBeInstanceOf(OneRelation)\n\t\t\texpect(SelfRels.manager.source).toBe(UserSchema)\n\t\t\texpect(SelfRels.manager.target).toBe(UserSchema)\n\t\t\texpect(SelfRels.manager.fkOwner).toBe('source')\n\t\t})\n\n\t\ttest('many-to-many via explicit join schema', () => {\n\t\t\texpect(PostRels.postTags).toBeInstanceOf(ManyRelation)\n\t\t\texpect(PostTagRels.post).toBeInstanceOf(OneRelation)\n\t\t\texpect(PostTagRels.tag).toBeInstanceOf(OneRelation)\n\t\t\texpect(PostTagRels.post.target).toBe(PostSchema)\n\t\t\texpect(PostTagRels.tag.target).toBe(TagSchema)\n\t\t})\n\t})\n\n\tdescribe('type-level: Relations.from uniqueness guard', () => {\n\t\ttest('duplicate relation name is a TS error', () => {\n\t\t\tconst S = Schema.from('test').pk('id', v.string(), () => 'x').build()\n\t\t\tconst T = Schema.from('targets')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('sId', v.string())\n\t\t\t\t.build()\n\t\t\t// @ts-expect-error — duplicate name 'items' should fail\n\t\t\tRelations.from(S).hasMany('items', T.fields.sId).hasMany('items', T.fields.sId)\n\t\t})\n\t})\n\n\tdescribe('type-level: FK-PK type-match guarantee', () => {\n\t\ttest('string FK pointing at number PK is a TS error', () => {\n\t\t\tconst NumPkSchema = Schema.from('nums')\n\t\t\t\t.pk('id', v.number(), () => 0)\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\n\t\t\tconst StringFkSchema = Schema.from('strings')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('numRef', v.string())\n\t\t\t\t.build()\n\n\t\t\t// @ts-expect-error — string FK does not match number PK\n\t\t\tRelations.from(NumPkSchema).hasMany('items', StringFkSchema.fields.numRef)\n\t\t})\n\n\t\ttest('matching FK-PK types compile correctly', () => {\n\t\t\tconst S = Schema.from('source').pk('id', v.string(), () => 'x').build()\n\t\t\tconst T = Schema.from('target')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('sourceId', v.string())\n\t\t\t\t.build()\n\t\t\tconst rels = Relations.from(S).hasMany('items', T.fields.sourceId).build()\n\t\t\texpect(rels.items).toBeInstanceOf(ManyRelation)\n\t\t})\n\t})\n\n\tdescribe('type-level: Field-only-FK rule', () => {\n\t\ttest('raw string FK is a TS error for hasMany', () => {\n\t\t\tconst S = Schema.from('s').pk('id', v.string(), () => 'x').build()\n\t\t\t// @ts-expect-error — raw string not allowed, must be a Field instance\n\t\t\tRelations.from(S).hasMany('items', 'someKey')\n\t\t})\n\n\t\ttest('raw string FK is a TS error for belongsTo', () => {\n\t\t\tconst S = Schema.from('s')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('ref', v.string())\n\t\t\t\t.build()\n\t\t\tconst T = Schema.from('t').pk('id', v.string(), () => 'x').build()\n\t\t\t// @ts-expect-error — raw string not allowed, must be a Field instance\n\t\t\tRelations.from(S).belongsTo('parent', 'ref', T)\n\t\t})\n\t})\n\n\tdescribe('type-level: schema relations-agnosticism', () => {\n\t\ttest('schema artifact contains no relational information', () => {\n\t\t\tconst _S = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\t\t\ttype SKeys = keyof typeof _S\n\t\t\texpectTypeOf<'relations' extends SKeys ? true : false>().toEqualTypeOf<false>()\n\t\t})\n\t})\n}\n","import { v, type Pipe } from 'valleyed'\n\nimport { EquippedError } from '../../../errors'\nimport type { AnySchema } from '../../schema'\n\nexport type ComputedSelectionPlan = {\n\trequestedSelect: Set<string> | null\n\tadapterSelect?: string[]\n\tcomputeNames: string[]\n}\n\nexport function planSelection(schema: AnySchema, select?: readonly string[]): ComputedSelectionPlan {\n\tconst computedDefs = schema.computedDefs as Record<string, { deps: readonly string[] }>\n\tconst computedNames = new Set(Object.keys(computedDefs))\n\tconst persistedNames = new Set(Object.keys(schema.fields))\n\n\tif (!select || select.length === 0) {\n\t\treturn {\n\t\t\trequestedSelect: null,\n\t\t\tadapterSelect: undefined,\n\t\t\tcomputeNames: [...computedNames],\n\t\t}\n\t}\n\n\tconst requestedSelect = new Set(select)\n\tconst adapterSelect = new Set<string>()\n\tconst selectedComputeNames = new Set<string>()\n\n\tfor (const key of select) {\n\t\tif (persistedNames.has(key)) {\n\t\t\tadapterSelect.add(key)\n\t\t\tcontinue\n\t\t}\n\t\tif (computedNames.has(key)) {\n\t\t\tselectedComputeNames.add(key)\n\t\t\tfor (const dep of computedDefs[key].deps) adapterSelect.add(dep)\n\t\t\tcontinue\n\t\t}\n\t\tthrow new EquippedError('Unknown selected field', {\n\t\t\tschema: schema.name,\n\t\t\tselectedField: key,\n\t\t\tavailableFields: [...persistedNames, ...computedNames],\n\t\t})\n\t}\n\n\treturn {\n\t\trequestedSelect,\n\t\tadapterSelect: [...adapterSelect],\n\t\tcomputeNames: [...selectedComputeNames],\n\t}\n}\n\ntype ComputedDef = {\n\tpipe: Pipe<any, any>\n\tdeps: readonly string[]\n\tcompute: (data: Record<string, unknown>) => unknown\n}\n\nexport function applyComputedSelection(\n\tschema: AnySchema,\n\trows: Record<string, unknown>[],\n\tplan: ComputedSelectionPlan,\n): Record<string, unknown>[] {\n\tconst computedDefs = schema.computedDefs as Record<string, ComputedDef>\n\n\treturn rows.map((row) => {\n\t\tconst enriched: Record<string, unknown> = { ...row }\n\t\tfor (const computeName of plan.computeNames) {\n\t\t\tconst def = computedDefs[computeName]\n\t\t\tconst depInput: Record<string, unknown> = {}\n\t\t\tfor (const dep of def.deps) {\n\t\t\t\tif (!(dep in row)) {\n\t\t\t\t\tthrow new EquippedError('Computed field dependency missing from adapter result', {\n\t\t\t\t\t\tschema: schema.name,\n\t\t\t\t\t\tcomputedField: computeName,\n\t\t\t\t\t\tdependency: dep,\n\t\t\t\t\t\tdependencies: def.deps,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tdepInput[dep] = row[dep]\n\t\t\t}\n\t\t\tconst r = v.validate(def.pipe, def.compute(depInput))\n\t\t\tif (!r.valid) throw new EquippedError('Computed field validation failed', {\n\t\t\t\tschema: schema.name,\n\t\t\t\tcomputedField: computeName,\n\t\t\t\tcause: r.error,\n\t\t\t})\n\t\t\tenriched[computeName] = r.value\n\t\t}\n\n\t\tif (!plan.requestedSelect) return enriched\n\n\t\tconst shaped: Record<string, unknown> = {}\n\t\tfor (const key of plan.requestedSelect) {\n\t\t\tif (key in enriched) shaped[key] = enriched[key]\n\t\t}\n\t\treturn shaped\n\t})\n}\n","import { Instance } from '../../../instance'\nimport { OrmValidationError, type OrmValidationFailure } from '../../errors'\nimport { ManyRelation, OneRelation, type AnyPreloadDef, type AnyRelDef, type NestedPreloadDef } from '../../relations'\nimport type { AnySchema } from '../../schema'\n\nconst FALLBACK_PAGINATION_DEFAULT_LIMIT = 100\nconst MAX_PRELOAD_DEPTH = 5\n\nexport type ReadOffsetSource =\n\t| { kind: 'offset'; value: unknown }\n\t| { kind: 'page'; value: unknown }\n\nexport type ReadLimitSource = { value: unknown }\n\nexport type NormalisedAllReadQuery = {\n\tlimit?: number\n\toffset?: number\n}\n\nexport type NormalisedIterationReadQuery = NormalisedAllReadQuery & {\n\tbatchSize?: number\n}\n\nexport type NormalisedPaginatedReadQuery = {\n\tlimit: number\n\toffset: number\n\tcurrent: number\n}\n\nfunction isRelDef(def: unknown): def is AnyRelDef {\n\treturn def instanceof ManyRelation || def instanceof OneRelation\n}\n\nfunction isNestedPreloadDef(def: AnyPreloadDef): def is NestedPreloadDef {\n\treturn typeof def === 'object' && def != null && 'def' in def\n}\n\nfunction relationStep(def: AnyRelDef) {\n\treturn `${def.source.name}.${def.name}->${def.target.name}`\n}\n\nfunction isPositiveInteger(value: unknown): value is number {\n\treturn typeof value === 'number' && Number.isSafeInteger(value) && value > 0\n}\n\nfunction isNonNegativeInteger(value: unknown): value is number {\n\treturn typeof value === 'number' && Number.isSafeInteger(value) && value >= 0\n}\n\nfunction getPaginationDefaultLimit() {\n\treturn Instance.maybeGet()?.settings.utils.paginationDefaultLimit ?? FALLBACK_PAGINATION_DEFAULT_LIMIT\n}\n\nfunction collectSelectFailures(schema: AnySchema, select: readonly string[] | undefined, failures: OrmValidationFailure[]) {\n\tif (!select || select.length === 0) return\n\n\tconst persistedNames = new Set(Object.keys(schema.fields))\n\tconst computedNames = new Set(Object.keys(schema.computedDefs as Record<string, unknown>))\n\tfor (const field of select) {\n\t\tif (persistedNames.has(field) || computedNames.has(field)) continue\n\t\tfailures.push({\n\t\t\tfield,\n\t\t\tcause: `Unknown selected field \"${field}\" on schema \"${schema.name}\"`,\n\t\t})\n\t}\n}\n\nfunction collectPreloadFailures(\n\tschema: AnySchema,\n\tdefs: readonly AnyPreloadDef[] | undefined,\n\tfailures: OrmValidationFailure[],\n\tdepth = 1,\n\tpath: readonly string[] = [],\n) {\n\tif (defs == null) return\n\tif (!Array.isArray(defs)) {\n\t\tfailures.push({ cause: 'Preloads must be an array' })\n\t\treturn\n\t}\n\tif (defs.length === 0) return\n\n\tif (depth > MAX_PRELOAD_DEPTH) {\n\t\tfailures.push({ cause: `Preload depth exceeded max depth ${MAX_PRELOAD_DEPTH}` })\n\t\treturn\n\t}\n\n\tfor (const preload of defs) {\n\t\tconst rawDef = isRelDef(preload) ? preload : isNestedPreloadDef(preload) ? preload.def : undefined\n\t\tif (!isRelDef(rawDef)) {\n\t\t\tfailures.push({ cause: 'Invalid preload definition: expected a relation definition or nested preload definition with `def`' })\n\t\t\tcontinue\n\t\t}\n\n\t\tif (rawDef.source !== schema) {\n\t\t\tfailures.push({\n\t\t\t\tfield: rawDef.name,\n\t\t\t\tcause: `Preload relation \"${rawDef.name}\" belongs to source schema \"${rawDef.source.name}\" but was used from schema \"${schema.name}\"`,\n\t\t\t})\n\t\t}\n\n\t\tconst step = relationStep(rawDef)\n\t\tif (path.includes(step)) {\n\t\t\tfailures.push({ field: rawDef.name, cause: `Preload cycle detected: ${[...path, step].join(' -> ')}` })\n\t\t\tcontinue\n\t\t}\n\n\t\tif (isNestedPreloadDef(preload)) {\n\t\t\tconst nested = preload.preloads ?? []\n\t\t\tif (!Array.isArray(nested)) {\n\t\t\t\tfailures.push({ field: rawDef.name, cause: `Nested preloads for relation \"${rawDef.name}\" must be an array` })\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tcollectPreloadFailures(rawDef.target, nested, failures, depth + 1, [...path, step])\n\t\t}\n\t}\n}\n\nfunction collectLimitFailure(value: unknown, failures: OrmValidationFailure[]): number | undefined {\n\tif (isPositiveInteger(value)) return value\n\tfailures.push({ field: 'limit', option: 'limit', cause: 'Limit must be a positive safe integer' })\n\treturn undefined\n}\n\nfunction collectOffsetFailure(value: unknown, failures: OrmValidationFailure[]): number | undefined {\n\tif (isNonNegativeInteger(value)) return value\n\tfailures.push({ field: 'offset', option: 'offset', cause: 'Offset must be a non-negative safe integer' })\n\treturn undefined\n}\n\nfunction collectPageFailure(value: unknown, failures: OrmValidationFailure[]): number | undefined {\n\tif (isPositiveInteger(value)) return value\n\tfailures.push({ field: 'page', option: 'page', cause: 'Page must be a positive safe integer' })\n\treturn undefined\n}\n\nfunction collectBatchSizeFailure(value: unknown, failures: OrmValidationFailure[]): number | undefined {\n\tif (value === undefined) return undefined\n\tif (isPositiveInteger(value)) return value\n\tfailures.push({ field: 'batchSize', option: 'batchSize', cause: 'Batch size must be a positive safe integer' })\n\treturn undefined\n}\n\nfunction collectIterationOptionFailures(options: unknown, failures: OrmValidationFailure[]): number | undefined {\n\tif (options === undefined) return undefined\n\tif (options == null || typeof options !== 'object' || Array.isArray(options)) {\n\t\tfailures.push({ option: 'batchSize', cause: 'Iteration options must be an object' })\n\t\treturn undefined\n\t}\n\treturn collectBatchSizeFailure((options as { batchSize?: unknown }).batchSize, failures)\n}\n\nfunction throwIfFailures(schema: AnySchema, operation: string, failures: OrmValidationFailure[]) {\n\tif (failures.length > 0) throw new OrmValidationError('query-shape', schema.name, operation, failures)\n}\n\nexport function assertNormalisedFindReadShape(\n\tschema: AnySchema,\n\toperation: string,\n\tstate: {\n\t\tselect: readonly string[] | undefined\n\t\tpreloads: readonly AnyPreloadDef[] | undefined\n\t},\n): void {\n\tconst failures: OrmValidationFailure[] = []\n\tcollectSelectFailures(schema, state.select, failures)\n\tcollectPreloadFailures(schema, state.preloads, failures)\n\tthrowIfFailures(schema, operation, failures)\n}\n\nexport function normaliseAllFindReadShape(\n\tschema: AnySchema,\n\toperation: string,\n\tstate: {\n\t\tselect: readonly string[] | undefined\n\t\tpreloads: readonly AnyPreloadDef[] | undefined\n\t\tlimitSource?: ReadLimitSource\n\t\toffsetSource?: ReadOffsetSource\n\t},\n): NormalisedAllReadQuery {\n\tconst failures: OrmValidationFailure[] = []\n\tcollectSelectFailures(schema, state.select, failures)\n\tcollectPreloadFailures(schema, state.preloads, failures)\n\n\tlet limit = state.limitSource === undefined ? undefined : collectLimitFailure(state.limitSource.value, failures)\n\tlet offset: number | undefined\n\n\tif (state.offsetSource?.kind === 'offset') {\n\t\toffset = collectOffsetFailure(state.offsetSource.value, failures)\n\t} else if (state.offsetSource?.kind === 'page') {\n\t\tconst page = collectPageFailure(state.offsetSource.value, failures)\n\t\tif (limit === undefined && state.limitSource === undefined) {\n\t\t\tlimit = getPaginationDefaultLimit()\n\t\t\tif (!isPositiveInteger(limit)) {\n\t\t\t\tfailures.push({ field: 'limit', cause: 'Default page limit must be a positive safe integer' })\n\t\t\t\tlimit = undefined\n\t\t\t}\n\t\t}\n\t\tif (page !== undefined && limit !== undefined) {\n\t\t\tconst resolvedOffset = (page - 1) * limit\n\t\t\tif (isNonNegativeInteger(resolvedOffset)) {\n\t\t\t\toffset = resolvedOffset\n\t\t\t} else {\n\t\t\t\tfailures.push({ field: 'offset', cause: 'Resolved page offset must be a non-negative safe integer' })\n\t\t\t}\n\t\t}\n\t}\n\n\tthrowIfFailures(schema, operation, failures)\n\treturn { limit, offset }\n}\n\nexport function normaliseAllIterateReadShape(\n\tschema: AnySchema,\n\toperation: string,\n\tstate: {\n\t\tselect: readonly string[] | undefined\n\t\tpreloads: readonly AnyPreloadDef[] | undefined\n\t\tlimitSource?: ReadLimitSource\n\t\toffsetSource?: ReadOffsetSource\n\t},\n\toptions?: unknown,\n): NormalisedIterationReadQuery {\n\tconst failures: OrmValidationFailure[] = []\n\tcollectSelectFailures(schema, state.select, failures)\n\tcollectPreloadFailures(schema, state.preloads, failures)\n\n\tlet limit = state.limitSource === undefined ? undefined : collectLimitFailure(state.limitSource.value, failures)\n\tlet offset: number | undefined\n\tconst batchSize = collectIterationOptionFailures(options, failures)\n\n\tif (state.offsetSource?.kind === 'offset') {\n\t\toffset = collectOffsetFailure(state.offsetSource.value, failures)\n\t} else if (state.offsetSource?.kind === 'page') {\n\t\tconst page = collectPageFailure(state.offsetSource.value, failures)\n\t\tif (limit === undefined && state.limitSource === undefined) {\n\t\t\tlimit = getPaginationDefaultLimit()\n\t\t\tif (!isPositiveInteger(limit)) {\n\t\t\t\tfailures.push({ field: 'limit', option: 'limit', cause: 'Default page limit must be a positive safe integer' })\n\t\t\t\tlimit = undefined\n\t\t\t}\n\t\t}\n\t\tif (page !== undefined && limit !== undefined) {\n\t\t\tconst resolvedOffset = (page - 1) * limit\n\t\t\tif (isNonNegativeInteger(resolvedOffset)) {\n\t\t\t\toffset = resolvedOffset\n\t\t\t} else {\n\t\t\t\tfailures.push({ field: 'offset', option: 'offset', cause: 'Resolved page offset must be a non-negative safe integer' })\n\t\t\t}\n\t\t}\n\t}\n\n\tthrowIfFailures(schema, operation, failures)\n\treturn { limit, offset, batchSize }\n}\n\nexport function normaliseAllPaginateReadShape(\n\tschema: AnySchema,\n\toperation: string,\n\tstate: {\n\t\tselect: readonly string[] | undefined\n\t\tpreloads: readonly AnyPreloadDef[] | undefined\n\t\tlimitSource?: ReadLimitSource\n\t\toffsetSource?: ReadOffsetSource\n\t},\n): NormalisedPaginatedReadQuery {\n\tconst failures: OrmValidationFailure[] = []\n\tcollectSelectFailures(schema, state.select, failures)\n\tcollectPreloadFailures(schema, state.preloads, failures)\n\n\tlet limit: number | undefined\n\tif (state.limitSource === undefined) {\n\t\tlimit = getPaginationDefaultLimit()\n\t\tif (!isPositiveInteger(limit)) {\n\t\t\tfailures.push({ field: 'limit', cause: 'Default page limit must be a positive safe integer' })\n\t\t\tlimit = undefined\n\t\t}\n\t} else {\n\t\tlimit = collectLimitFailure(state.limitSource.value, failures)\n\t}\n\n\tlet offset = 0\n\tlet current = 1\n\n\tif (state.offsetSource?.kind === 'offset') {\n\t\tconst resolvedOffset = collectOffsetFailure(state.offsetSource.value, failures)\n\t\tif (resolvedOffset !== undefined) {\n\t\t\toffset = resolvedOffset\n\t\t\tif (limit !== undefined) current = Math.floor(resolvedOffset / limit) + 1\n\t\t}\n\t} else if (state.offsetSource?.kind === 'page') {\n\t\tconst page = collectPageFailure(state.offsetSource.value, failures)\n\t\tif (page !== undefined) current = page\n\t\tif (page !== undefined && limit !== undefined) {\n\t\t\tconst resolvedOffset = (page - 1) * limit\n\t\t\tif (isNonNegativeInteger(resolvedOffset)) {\n\t\t\t\toffset = resolvedOffset\n\t\t\t} else {\n\t\t\t\tfailures.push({ field: 'offset', cause: 'Resolved page offset must be a non-negative safe integer' })\n\t\t\t}\n\t\t}\n\t}\n\n\tthrowIfFailures(schema, operation, failures)\n\treturn { limit: limit as number, offset, current }\n}\n","import { v, type Pipe, type PipeInput, type PipeOutput } from 'valleyed'\n\nimport { OrmValidationError, type OrmValidationFailure } from './errors'\nimport type { AnySchemaField, SchemaField } from './fields'\nimport { Schema, type AnySchema, type SchemaFields, type SchemaOutput } from './schema'\nimport { SetOp, isUpdateOp, opTouchedFields, type AnyUpdateOp } from './updates'\nimport type { Prettify } from './utils'\n\nexport type SchemaCreateInput<S extends AnySchema> = Prettify<\n\t{\n\t\t[K in keyof SchemaFields<S> as SchemaFields<S>[K] extends SchemaField<any, any, true>\n\t\t\t? never\n\t\t\t: K]: SchemaFields<S>[K] extends AnySchemaField ? PipeInput<SchemaFields<S>[K]['pipe']> : never\n\t} & {\n\t\t[K in keyof SchemaFields<S> as SchemaFields<S>[K] extends SchemaField<any, any, true>\n\t\t\t? K\n\t\t\t: never]?: SchemaFields<S>[K] extends AnySchemaField ? PipeInput<SchemaFields<S>[K]['pipe']> : never\n\t}\n>\n\nexport type SchemaUpdateInput<S extends AnySchema> =\n\tS extends Schema<any, any, infer F>\n\t\t? { [K in keyof F]?: F[K] extends AnySchemaField ? PipeInput<F[K]['pipe']> | AnyUpdateOp : never }\n\t\t: Record<string, unknown>\n\nexport type SchemaUpdateOutput<S extends AnySchema> =\n\tS extends Schema<any, any, infer F>\n\t\t? { [K in keyof F]?: F[K] extends AnySchemaField ? PipeOutput<F[K]['pipe']> | AnyUpdateOp : never }\n\t\t: Record<string, unknown>\n\nexport function tryValidateCreateRow<S extends AnySchema>(\n\ts: S,\n\tdata: Record<string, unknown>,\n): { ok: true; value: SchemaOutput<S> } | { ok: false; failures: OrmValidationFailure[] } {\n\tconst failures: OrmValidationFailure[] = []\n\tconst result: Record<string, unknown> = {}\n\n\tfor (const [key, entry] of Object.entries(s.fields)) {\n\t\tconst pipe = entry.onCreate ? v.defaults(entry.pipe, entry.onCreate()) : entry.pipe\n\t\tconst fieldValue = key in data ? data[key] : undefined\n\t\tconst validated = v.validate(pipe, fieldValue)\n\t\tif (validated.valid) {\n\t\t\tresult[key] = validated.value\n\t\t} else {\n\t\t\tfailures.push({ field: key, cause: validated.error })\n\t\t}\n\t}\n\n\tif (failures.length > 0) return { ok: false, failures }\n\treturn { ok: true, value: result as SchemaOutput<S> }\n}\n\nexport function validateCreate<S extends AnySchema>(s: S, data: Record<string, unknown>): SchemaOutput<S> {\n\tconst result = tryValidateCreateRow(s, data)\n\tif (!result.ok) {\n\t\tthrow new OrmValidationError('validation', s.name, 'createOne', result.failures)\n\t}\n\treturn result.value\n}\n\nexport function validateCreateMany<S extends AnySchema>(s: S, rows: Record<string, unknown>[]): SchemaOutput<S>[] {\n\tconst allFailures: OrmValidationFailure[] = []\n\tconst validated: SchemaOutput<S>[] = []\n\n\tfor (let i = 0; i < rows.length; i++) {\n\t\tconst result = tryValidateCreateRow(s, rows[i])\n\t\tif (result.ok) {\n\t\t\tvalidated.push(result.value)\n\t\t} else {\n\t\t\tfor (const failure of result.failures) {\n\t\t\t\tallFailures.push({ ...failure, rowIndex: i })\n\t\t\t}\n\t\t}\n\t}\n\n\tif (allFailures.length > 0) {\n\t\tthrow new OrmValidationError('validation', s.name, 'createMany', allFailures)\n\t}\n\n\treturn validated\n}\n\nexport function validateUpdate<S extends AnySchema>(s: S, data: Record<string, unknown>): SchemaUpdateOutput<S> {\n\tconst pipes: Record<string, Pipe<any, any>> = {}\n\tconst ops: Record<string, unknown> = {}\n\n\tfor (const [key, value] of Object.entries(data)) {\n\t\tif (isUpdateOp(value)) ops[key] = value\n\t\telse if (key in s.fieldDefs) pipes[key] = s.fieldDefs[key].pipe\n\t}\n\n\tfor (const [key, entry] of Object.entries(s.fieldDefs)) {\n\t\tif (!(key in data) && entry.onUpdate) pipes[key] = v.defaults(entry.pipe, entry.onUpdate())\n\t}\n\n\tconst r = v.validate(v.object(pipes), data)\n\tif (!r.valid) throw new OrmValidationError('validation', s.name, 'updateByPk', [{ cause: r.error }])\n\tconst validated = r.value as Record<string, unknown>\n\treturn { ...validated, ...ops } as SchemaUpdateOutput<S>\n}\n\nexport function validateUpdateOps(schema: AnySchema, ops: AnyUpdateOp[], operation = 'updateByPk'): AnyUpdateOp[] {\n\tconst touched = new Map<string, number[]>()\n\n\tfor (let i = 0; i < ops.length; i++) {\n\t\tfor (const field of opTouchedFields(ops[i])) {\n\t\t\tif (!touched.has(field)) touched.set(field, [])\n\t\t\ttouched.get(field)!.push(i)\n\t\t}\n\t}\n\n\tconst autoBumped: AnyUpdateOp[] = []\n\tfor (const [key, entry] of Object.entries(schema.fieldDefs)) {\n\t\tif (entry.onUpdate && !touched.has(key)) {\n\t\t\tconst bumpOp = new SetOp({ [key]: entry.onUpdate() })\n\t\t\tautoBumped.push(bumpOp)\n\t\t\ttouched.set(key, [ops.length + autoBumped.length - 1])\n\t\t}\n\t}\n\n\tconst allOps = [...ops, ...autoBumped]\n\n\tconst conflicts: OrmValidationFailure[] = []\n\tfor (const [field, indices] of touched) {\n\t\tif (indices.length > 1) {\n\t\t\tconflicts.push({ field, cause: `field \"${field}\" touched by multiple ops at indices [${indices.join(', ')}]` })\n\t\t}\n\t}\n\tif (conflicts.length > 0) {\n\t\tthrow new OrmValidationError('conflicting-ops', schema.name, operation, conflicts)\n\t}\n\n\tconst failures: OrmValidationFailure[] = []\n\tfor (let i = 0; i < allOps.length; i++) {\n\t\tconst op = allOps[i]\n\t\tif (op instanceof SetOp) {\n\t\t\tfor (const [key, value] of Object.entries(op.values)) {\n\t\t\t\tconst fieldDef = schema.fields[key] as AnySchemaField | undefined\n\t\t\t\tif (!fieldDef) continue\n\t\t\t\tconst r = v.validate(fieldDef.pipe, value)\n\t\t\t\tif (!r.valid) {\n\t\t\t\t\tfailures.push({ opIndex: i, field: key, cause: r.error })\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif (failures.length > 0) {\n\t\tthrow new OrmValidationError('validation', schema.name, operation, failures)\n\t}\n\n\treturn allOps\n}\n\nexport function composeSchemaConfig(\n\tresolve: (schema: AnySchema) => unknown,\n\ttransforms: ReadonlyArray<(config: any, schema: AnySchema) => any>,\n\tschema: AnySchema,\n\tpipe: Pipe<any, any>,\n): unknown {\n\tlet config = resolve(schema)\n\tfor (const transform of transforms) {\n\t\tconfig = transform(config, schema)\n\t}\n\tconst r = v.validate(pipe, config)\n\tif (!r.valid) throw new OrmValidationError('validation', schema.name, 'schemaConfig', [{ cause: r.error }])\n\treturn r.value\n}\n\nexport function validateUpsertConflicts(\n\tschema: AnySchema,\n\trawCreate: Record<string, unknown>,\n\tops: AnyUpdateOp[],\n): void {\n\tconst createFields = new Set(Object.keys(rawCreate))\n\tconst conflicts: OrmValidationFailure[] = []\n\tfor (const op of ops) {\n\t\tif (op instanceof SetOp) continue\n\t\tfor (const field of opTouchedFields(op)) {\n\t\t\tif (createFields.has(field)) {\n\t\t\t\tconflicts.push({ field, cause: `field \"${field}\" present in both create payload and atomic op \"${op.kind}\"` })\n\t\t\t}\n\t\t}\n\t}\n\tif (conflicts.length > 0) {\n\t\tthrow new OrmValidationError('conflicting-ops', schema.name, 'upsertOne', conflicts)\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect } = import.meta.vitest\n\tconst { IncOp, MulOp, SetOp, UnsetOp } = await import('./updates')\n\tconst { EquippedError } = await import('../errors')\n\n\tdescribe('validateCreate', () => {\n\t\tconst UserSchema = Schema.from('users')\n\t\t\t.pk('id', v.string(), () => 'auto-id')\n\t\t\t.field('email', v.string())\n\t\t\t.field('name', v.string())\n\t\t\t.field('age', v.optional(v.number()))\n\t\t\t.field('createdAt', v.number(), { onCreate: () => 1000 })\n\t\t\t.build()\n\n\t\ttest('generates pk when not provided', () => {\n\t\t\tconst result = validateCreate(UserSchema, { email: 'a@b.com', name: 'Alice' })\n\t\t\texpect(result.id).toBe('auto-id')\n\t\t})\n\n\t\ttest('uses provided pk when given', () => {\n\t\t\tconst result = validateCreate(UserSchema, { id: 'custom-id', email: 'a@b.com', name: 'Alice' })\n\t\t\texpect(result.id).toBe('custom-id')\n\t\t})\n\n\t\ttest('applies onCreate generator for generated fields', () => {\n\t\t\tconst result = validateCreate(UserSchema, { email: 'a@b.com', name: 'Alice' })\n\t\t\texpect(result.createdAt).toBe(1000)\n\t\t})\n\n\t\ttest('allows overriding generated field values', () => {\n\t\t\tconst result = validateCreate(UserSchema, { email: 'a@b.com', name: 'Alice', createdAt: 9999 })\n\t\t\texpect(result.createdAt).toBe(9999)\n\t\t})\n\n\t\ttest('optional fields default to undefined when not provided', () => {\n\t\t\tconst result = validateCreate(UserSchema, { email: 'a@b.com', name: 'Alice' })\n\t\t\texpect(result.age).toBeUndefined()\n\t\t})\n\n\t\ttest('strips unknown fields', () => {\n\t\t\tconst result = validateCreate(UserSchema, { email: 'a@b.com', name: 'Alice', admin: true })\n\t\t\texpect((result as any).admin).toBeUndefined()\n\t\t})\n\n\t\ttest('throws OrmValidationError on invalid field type', () => {\n\t\t\ttry {\n\t\t\t\tvalidateCreate(UserSchema, { email: 123, name: 'Alice' })\n\t\t\t\texpect.unreachable()\n\t\t\t} catch (e) {\n\t\t\t\texpect(e).toBeInstanceOf(OrmValidationError)\n\t\t\t\tconst err = e as InstanceType<typeof OrmValidationError>\n\t\t\t\texpect(err.kind).toBe('validation')\n\t\t\t\texpect(err.schema).toBe('users')\n\t\t\t\texpect(err.operation).toBe('createOne')\n\t\t\t\texpect(err.failures.length).toBeGreaterThan(0)\n\t\t\t\texpect(err.failures[0].field).toBe('email')\n\t\t\t}\n\t\t})\n\n\t\ttest('throws OrmValidationError when required field is missing', () => {\n\t\t\ttry {\n\t\t\t\tvalidateCreate(UserSchema, { email: 'a@b.com' })\n\t\t\t\texpect.unreachable()\n\t\t\t} catch (e) {\n\t\t\t\texpect(e).toBeInstanceOf(OrmValidationError)\n\t\t\t\tconst err = e as InstanceType<typeof OrmValidationError>\n\t\t\t\texpect(err.kind).toBe('validation')\n\t\t\t\texpect(err.failures.some((f) => f.field === 'name')).toBe(true)\n\t\t\t}\n\t\t})\n\n\t\ttest('collects all field failures in a single error', () => {\n\t\t\ttry {\n\t\t\t\tvalidateCreate(UserSchema, { email: 123, name: 456 })\n\t\t\t\texpect.unreachable()\n\t\t\t} catch (e) {\n\t\t\t\texpect(e).toBeInstanceOf(OrmValidationError)\n\t\t\t\tconst err = e as InstanceType<typeof OrmValidationError>\n\t\t\t\tconst failedFields = err.failures.map((f) => f.field)\n\t\t\t\texpect(failedFields).toContain('email')\n\t\t\t\texpect(failedFields).toContain('name')\n\t\t\t}\n\t\t})\n\t})\n\n\tdescribe('validateCreateMany', () => {\n\t\tconst ItemSchema = Schema.from('items')\n\t\t\t.pk('id', v.string(), () => 'item-id')\n\t\t\t.field('title', v.string())\n\t\t\t.field('price', v.number())\n\t\t\t.build()\n\n\t\ttest('validates all rows and returns validated documents', () => {\n\t\t\tconst results = validateCreateMany(ItemSchema, [\n\t\t\t\t{ title: 'A', price: 10 },\n\t\t\t\t{ title: 'B', price: 20 },\n\t\t\t])\n\t\t\texpect(results).toHaveLength(2)\n\t\t\texpect(results[0].title).toBe('A')\n\t\t\texpect(results[1].title).toBe('B')\n\t\t})\n\n\t\ttest('collects failures across rows with rowIndex populated', () => {\n\t\t\ttry {\n\t\t\t\tvalidateCreateMany(ItemSchema, [\n\t\t\t\t\t{ title: 'Good', price: 10 },\n\t\t\t\t\t{ title: 123, price: 20 },\n\t\t\t\t\t{ title: 'Also bad', price: 'not-a-number' },\n\t\t\t\t])\n\t\t\t\texpect.unreachable()\n\t\t\t} catch (e) {\n\t\t\t\texpect(e).toBeInstanceOf(OrmValidationError)\n\t\t\t\tconst err = e as InstanceType<typeof OrmValidationError>\n\t\t\t\texpect(err.kind).toBe('validation')\n\t\t\t\texpect(err.schema).toBe('items')\n\t\t\t\texpect(err.operation).toBe('createMany')\n\t\t\t\texpect(err.failures.length).toBeGreaterThanOrEqual(2)\n\t\t\t\tconst rowIndices = err.failures.map((f) => f.rowIndex)\n\t\t\t\texpect(rowIndices).toContain(1)\n\t\t\t\texpect(rowIndices).toContain(2)\n\t\t\t\texpect(rowIndices).not.toContain(0)\n\t\t\t}\n\t\t})\n\n\t\ttest('throws single error even with multiple bad rows', () => {\n\t\t\ttry {\n\t\t\t\tvalidateCreateMany(ItemSchema, [\n\t\t\t\t\t{ title: 1, price: 'bad' },\n\t\t\t\t\t{ title: 2, price: 'worse' },\n\t\t\t\t])\n\t\t\t\texpect.unreachable()\n\t\t\t} catch (e) {\n\t\t\t\texpect(e).toBeInstanceOf(OrmValidationError)\n\t\t\t\tconst err = e as InstanceType<typeof OrmValidationError>\n\t\t\t\texpect(err.failures.filter((f) => f.rowIndex === 0).length).toBeGreaterThan(0)\n\t\t\t\texpect(err.failures.filter((f) => f.rowIndex === 1).length).toBeGreaterThan(0)\n\t\t\t}\n\t\t})\n\t})\n\n\tdescribe('validateUpdate', () => {\n\t\tconst UserSchema = Schema.from('users')\n\t\t\t.pk('id', v.string(), () => 'auto-id')\n\t\t\t.field('email', v.string())\n\t\t\t.field('name', v.string())\n\t\t\t.field('updatedAt', v.number(), { onCreate: () => 1000, onUpdate: () => 2000 })\n\t\t\t.build()\n\n\t\ttest('validates only provided fields', () => {\n\t\t\tconst result = validateUpdate(UserSchema, { name: 'Bob' })\n\t\t\texpect(result).toHaveProperty('name', 'Bob')\n\t\t\texpect(result).not.toHaveProperty('email')\n\t\t})\n\n\t\ttest('applies onUpdate generator for unset generated fields', () => {\n\t\t\tconst result = validateUpdate(UserSchema, { name: 'Bob' })\n\t\t\texpect(result).toHaveProperty('updatedAt', 2000)\n\t\t})\n\n\t\ttest('allows overriding onUpdate field explicitly', () => {\n\t\t\tconst result = validateUpdate(UserSchema, { name: 'Bob', updatedAt: 5000 })\n\t\t\texpect(result).toHaveProperty('updatedAt', 5000)\n\t\t})\n\n\t\ttest('strips unknown fields', () => {\n\t\t\tconst result = validateUpdate(UserSchema, { name: 'Bob', admin: true })\n\t\t\texpect((result as any).admin).toBeUndefined()\n\t\t})\n\n\t\ttest('throws on invalid field type', () => {\n\t\t\texpect(() => validateUpdate(UserSchema, { name: 123 })).toThrow()\n\t\t})\n\n\t\ttest('passes Op instances through without validation', () => {\n\t\t\tconst result = validateUpdate(UserSchema, { name: new IncOp('name', 1) })\n\t\t\texpect(result.name).toBeInstanceOf(IncOp)\n\t\t})\n\n\t\ttest('Op on a field suppresses onUpdate for that field but not others', () => {\n\t\t\tconst result = validateUpdate(UserSchema, { updatedAt: new IncOp('updatedAt', 1) })\n\t\t\texpect(result.updatedAt).toBeInstanceOf(IncOp)\n\t\t})\n\n\t\ttest('plain value and Op can coexist in same update', () => {\n\t\t\tconst result = validateUpdate(UserSchema, { name: 'Bob', email: new UnsetOp('email') })\n\t\t\texpect(result.name).toBe('Bob')\n\t\t\texpect(result.email).toBeInstanceOf(UnsetOp)\n\t\t\texpect(result.updatedAt).toBe(2000)\n\t\t})\n\n\t\ttest('Op on onUpdate field suppresses its generator', () => {\n\t\t\tconst result = validateUpdate(UserSchema, { updatedAt: new MulOp('updatedAt', 2) })\n\t\t\texpect(result.updatedAt).toBeInstanceOf(MulOp)\n\t\t})\n\t})\n\n\tdescribe('OrmValidationError', () => {\n\t\ttest('has correct kind, schema, operation, and failures', () => {\n\t\t\tconst err = new OrmValidationError('conflicting-ops', 'users', 'updateByPk', [\n\t\t\t\t{ field: 'views', cause: 'conflict' },\n\t\t\t])\n\t\t\texpect(err).toBeInstanceOf(EquippedError)\n\t\t\texpect(err.kind).toBe('conflicting-ops')\n\t\t\texpect(err.schema).toBe('users')\n\t\t\texpect(err.operation).toBe('updateByPk')\n\t\t\texpect(err.failures).toHaveLength(1)\n\t\t})\n\t})\n\n\tdescribe('validateUpdateOps', () => {\n\t\tconst UpdateSchema = Schema.from('items')\n\t\t\t.pk('id', v.string(), () => 'auto')\n\t\t\t.field('name', v.string())\n\t\t\t.field('views', v.number())\n\t\t\t.field('updatedAt', v.number(), { onCreate: () => 1000, onUpdate: () => 2000 })\n\t\t\t.build()\n\n\t\ttest('passes through valid ops', () => {\n\t\t\tconst ops = validateUpdateOps(UpdateSchema, [new SetOp({ name: 'New Name' })])\n\t\t\texpect(ops.length).toBeGreaterThanOrEqual(1)\n\t\t\texpect(ops[0]).toBeInstanceOf(SetOp)\n\t\t})\n\n\t\ttest('auto-bumps onUpdate fields not touched by user ops', () => {\n\t\t\tconst ops = validateUpdateOps(UpdateSchema, [new SetOp({ name: 'New Name' })])\n\t\t\tconst bumped = ops.find((op) => op instanceof SetOp && 'updatedAt' in op.values && op !== ops[0])\n\t\t\texpect(bumped).toBeDefined()\n\t\t\texpect((bumped as SetOp).values.updatedAt).toBe(2000)\n\t\t})\n\n\t\ttest('user set({updatedAt:X}) suppresses auto-bump for that field', () => {\n\t\t\tconst ops = validateUpdateOps(UpdateSchema, [new SetOp({ updatedAt: 9999 })])\n\t\t\tconst setOps = ops.filter((op) => op instanceof SetOp) as SetOp[]\n\t\t\tconst allUpdatedAtValues = setOps.flatMap((op) =>\n\t\t\t\t'updatedAt' in op.values ? [op.values.updatedAt] : [],\n\t\t\t)\n\t\t\texpect(allUpdatedAtValues).toEqual([9999])\n\t\t})\n\n\t\ttest('atomic op on onUpdate field suppresses auto-bump', () => {\n\t\t\tconst ops = validateUpdateOps(UpdateSchema, [new IncOp('updatedAt', 1)])\n\t\t\tconst setOps = ops.filter((op) => op instanceof SetOp) as SetOp[]\n\t\t\tconst hasAutoBumpedUpdatedAt = setOps.some((op) => 'updatedAt' in op.values)\n\t\t\texpect(hasAutoBumpedUpdatedAt).toBe(false)\n\t\t})\n\n\t\ttest('set({views:0}) + inc(views, 1) throws conflicting-ops', () => {\n\t\t\texpect(() =>\n\t\t\t\tvalidateUpdateOps(UpdateSchema, [new SetOp({ views: 0 }), new IncOp('views', 1)]),\n\t\t\t).toThrow(OrmValidationError)\n\n\t\t\ttry {\n\t\t\t\tvalidateUpdateOps(UpdateSchema, [new SetOp({ views: 0 }), new IncOp('views', 1)])\n\t\t\t} catch (e) {\n\t\t\t\tconst err = e as OrmValidationError\n\t\t\t\texpect(err.kind).toBe('conflicting-ops')\n\t\t\t\texpect(err.failures[0].field).toBe('views')\n\t\t\t}\n\t\t})\n\n\t\ttest('cross-kind conflict (unset + inc on same field) throws', () => {\n\t\t\tconst UpdateSchema2 = Schema.from('items2')\n\t\t\t\t.pk('id', v.string(), () => 'auto')\n\t\t\t\t.field('score', v.optional(v.number()))\n\t\t\t\t.build()\n\t\t\texpect(() =>\n\t\t\t\tvalidateUpdateOps(UpdateSchema2, [new UnsetOp('score'), new IncOp('score', 5)]),\n\t\t\t).toThrow(OrmValidationError)\n\t\t})\n\n\t\ttest('SetOp values are pipe-validated', () => {\n\t\t\texpect(() =>\n\t\t\t\tvalidateUpdateOps(UpdateSchema, [new SetOp({ name: 123 as any })]),\n\t\t\t).toThrow(OrmValidationError)\n\n\t\t\ttry {\n\t\t\t\tvalidateUpdateOps(UpdateSchema, [new SetOp({ name: 123 as any })])\n\t\t\t} catch (e) {\n\t\t\t\tconst err = e as OrmValidationError\n\t\t\t\texpect(err.kind).toBe('validation')\n\t\t\t\texpect(err.failures[0].field).toBe('name')\n\t\t\t}\n\t\t})\n\n\t\ttest('atomic op operands are NOT pipe-validated', () => {\n\t\t\tconst ops = validateUpdateOps(UpdateSchema, [new IncOp('views', -999)])\n\t\t\texpect(ops).toHaveLength(2)\n\t\t})\n\n\t\ttest('auto-bumped SetOp values are pipe-validated', () => {\n\t\t\tconst BadSchema = Schema.from('bad')\n\t\t\t\t.pk('id', v.string(), () => 'auto')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.field('counter', v.number(), { onUpdate: () => 'not-a-number' as any })\n\t\t\t\t.build()\n\t\t\texpect(() =>\n\t\t\t\tvalidateUpdateOps(BadSchema, [new SetOp({ name: 'test' })]),\n\t\t\t).toThrow(OrmValidationError)\n\t\t})\n\n\t\ttest('collects all conflicts in one error', () => {\n\t\t\ttry {\n\t\t\t\tvalidateUpdateOps(UpdateSchema, [\n\t\t\t\t\tnew SetOp({ views: 0, name: 'A' }),\n\t\t\t\t\tnew IncOp('views', 1),\n\t\t\t\t\tnew SetOp({ name: 'B' }),\n\t\t\t\t])\n\t\t\t} catch (e) {\n\t\t\t\tconst err = e as OrmValidationError\n\t\t\t\texpect(err.kind).toBe('conflicting-ops')\n\t\t\t\texpect(err.failures.length).toBe(2)\n\t\t\t}\n\t\t})\n\t})\n\n\tdescribe('composeSchemaConfig', () => {\n\t\tconst TestSchema = Schema.from('cfg_test')\n\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t.build()\n\n\t\tconst tablePipe = v.object({ table: v.string() })\n\n\t\ttest('composes base resolver output and validates against pipe', () => {\n\t\t\tconst result = composeSchemaConfig(\n\t\t\t\t() => ({ table: 'users' }),\n\t\t\t\t[],\n\t\t\t\tTestSchema,\n\t\t\t\ttablePipe,\n\t\t\t)\n\t\t\texpect(result).toEqual({ table: 'users' })\n\t\t})\n\n\t\ttest('applies transforms in order before validation', () => {\n\t\t\tconst result = composeSchemaConfig(\n\t\t\t\t() => ({ table: 'users' }),\n\t\t\t\t[\n\t\t\t\t\t(cfg: any) => ({ ...cfg, table: `a_${cfg.table}` }),\n\t\t\t\t\t(cfg: any) => ({ ...cfg, table: `b_${cfg.table}` }),\n\t\t\t\t],\n\t\t\t\tTestSchema,\n\t\t\t\ttablePipe,\n\t\t\t)\n\t\t\texpect(result).toEqual({ table: 'b_a_users' })\n\t\t})\n\n\t\ttest('throws OrmValidationError when composed config fails pipe validation', () => {\n\t\t\ttry {\n\t\t\t\tcomposeSchemaConfig(\n\t\t\t\t\t() => ({ table: 123 }),\n\t\t\t\t\t[],\n\t\t\t\t\tTestSchema,\n\t\t\t\t\ttablePipe,\n\t\t\t\t)\n\t\t\t\texpect.unreachable()\n\t\t\t} catch (e) {\n\t\t\t\texpect(e).toBeInstanceOf(OrmValidationError)\n\t\t\t\tconst err = e as OrmValidationError\n\t\t\t\texpect(err.kind).toBe('validation')\n\t\t\t\texpect(err.schema).toBe('cfg_test')\n\t\t\t\texpect(err.operation).toBe('schemaConfig')\n\t\t\t\texpect(err.failures).toHaveLength(1)\n\t\t\t}\n\t\t})\n\n\t\ttest('throws OrmValidationError when transform corrupts valid base config', () => {\n\t\t\ttry {\n\t\t\t\tcomposeSchemaConfig(\n\t\t\t\t\t() => ({ table: 'users' }),\n\t\t\t\t\t[(cfg: any) => ({ ...cfg, table: 42 })],\n\t\t\t\t\tTestSchema,\n\t\t\t\t\ttablePipe,\n\t\t\t\t)\n\t\t\t\texpect.unreachable()\n\t\t\t} catch (e) {\n\t\t\t\texpect(e).toBeInstanceOf(OrmValidationError)\n\t\t\t\tconst err = e as OrmValidationError\n\t\t\t\texpect(err.kind).toBe('validation')\n\t\t\t\texpect(err.operation).toBe('schemaConfig')\n\t\t\t}\n\t\t})\n\n\t\ttest('returns typed effective config on success', () => {\n\t\t\tconst pipe = v.object({ table: v.string(), prefix: v.optional(v.string()) })\n\t\t\tconst result = composeSchemaConfig(\n\t\t\t\t() => ({ table: 'orders' }),\n\t\t\t\t[(cfg: any) => ({ ...cfg, prefix: 'tenant1' })],\n\t\t\t\tTestSchema,\n\t\t\t\tpipe,\n\t\t\t)\n\t\t\texpect(result).toEqual({ table: 'orders', prefix: 'tenant1' })\n\t\t})\n\t})\n}\n","import { planSelection } from './computeds'\nimport { assertNormalisedFindReadShape, normaliseAllFindReadShape, normaliseAllIterateReadShape, normaliseAllPaginateReadShape, type ReadLimitSource, type ReadOffsetSource } from './query-shape'\nimport type { Paginated, SelectedWithPreloads } from './types'\nimport { assertNormalisedAggregate, assertNormalisedFilter, type FilterGroup } from '../../filter'\nimport type { AggregateSpec } from '../../orm-adapter'\nimport type { IterationOptions, OrderBy } from '../../query-options'\nimport type { AnyPreloadDef } from '../../relations'\nimport type { AnySchema } from '../../schema'\nimport { validateCreate, validateCreateMany, validateUpdate, validateUpsertConflicts, type SchemaCreateInput, type SchemaUpdateInput } from '../../schema-validations'\nimport { SetOp, isUpdateOp, type AnyUpdateOp } from '../../updates'\nimport type { SchemaContext, UpsertInput } from '../builders'\n\nexport async function runOneRead<S extends AnySchema, Sel extends string, P extends readonly AnyPreloadDef[]>(\n\tcontext: SchemaContext<S>,\n\tstate: {\n\t\twhere: FilterGroup\n\t\tselect: readonly Sel[] | undefined\n\t\tpreloads: P\n\t},\n): Promise<SelectedWithPreloads<S, Sel, P> | null> {\n\tassertNormalisedFilter(context.schema, state.where)\n\tassertNormalisedFindReadShape(context.schema, 'findOne', state)\n\tconst row = await context.use.findOne(state.where)\n\tif (!row) return null\n\treturn context.shapeOneRow(state.select, state.preloads, row)\n}\n\nexport async function runAllRead<S extends AnySchema, Sel extends string, P extends readonly AnyPreloadDef[]>(\n\tcontext: SchemaContext<S>,\n\tstate: {\n\t\twhere: FilterGroup\n\t\tselect: readonly Sel[] | undefined\n\t\tpreloads: P\n\t\torderBy: readonly OrderBy[]\n\t\tlimitSource?: ReadLimitSource\n\t\toffsetSource?: ReadOffsetSource\n\t},\n): Promise<SelectedWithPreloads<S, Sel, P>[]> {\n\tassertNormalisedFilter(context.schema, state.where)\n\tconst query = normaliseAllFindReadShape(context.schema, 'findMany', state)\n\tconst plan = planSelection(context.schema, state.select as readonly string[] | undefined)\n\tconst rows = await context.use.findMany(state.where, {\n\t\tselect: plan.adapterSelect,\n\t\torderBy: [...state.orderBy],\n\t\tlimit: query.limit,\n\t\toffset: query.offset,\n\t})\n\treturn context.shapeRows(state.select, state.preloads, rows)\n}\n\nexport async function runAllCount<S extends AnySchema>(\n\tcontext: SchemaContext<S>,\n\tstate: { where: FilterGroup },\n): Promise<number> {\n\tassertNormalisedFilter(context.schema, state.where)\n\treturn context.use.count(state.where)\n}\n\nfunction toPaginated<T>(items: T[], total: number, limit: number, current: number): Paginated<T> {\n\tconst start = 1\n\tconst last = Math.ceil(total / limit) || 1\n\tconst previous = current <= start || current > last ? null : current - 1\n\tconst next = current >= last ? null : current + 1\n\treturn {\n\t\tpages: { current, start, last, previous, next },\n\t\tdocs: { limit, total, count: items.length },\n\t\titems,\n\t}\n}\n\nexport async function runAllPaginate<S extends AnySchema, Sel extends string, P extends readonly AnyPreloadDef[]>(\n\tcontext: SchemaContext<S>,\n\tstate: {\n\t\twhere: FilterGroup\n\t\tselect: readonly Sel[] | undefined\n\t\tpreloads: P\n\t\torderBy: readonly OrderBy[]\n\t\tlimitSource?: ReadLimitSource\n\t\toffsetSource?: ReadOffsetSource\n\t},\n): Promise<Paginated<SelectedWithPreloads<S, Sel, P>>> {\n\tassertNormalisedFilter(context.schema, state.where)\n\tconst query = normaliseAllPaginateReadShape(context.schema, 'paginate', state)\n\tconst plan = planSelection(context.schema, state.select as readonly string[] | undefined)\n\tconst use = context.use\n\tconst [rows, total] = await Promise.all([\n\t\tuse.findMany(state.where, {\n\t\t\tselect: plan.adapterSelect,\n\t\t\torderBy: [...state.orderBy],\n\t\t\tlimit: query.limit,\n\t\t\toffset: query.offset,\n\t\t}),\n\t\tuse.count(state.where),\n\t])\n\tconst items = await context.shapeRows(state.select, state.preloads, rows)\n\treturn toPaginated(items, total, query.limit, query.current)\n}\n\nexport async function* runAllIterate<S extends AnySchema, Sel extends string, P extends readonly AnyPreloadDef[]>(\n\tcontext: SchemaContext<S>,\n\tstate: {\n\t\twhere: FilterGroup\n\t\tselect: readonly Sel[] | undefined\n\t\tpreloads: P\n\t\torderBy: readonly OrderBy[]\n\t\tlimitSource?: ReadLimitSource\n\t\toffsetSource?: ReadOffsetSource\n\t},\n\toptions?: IterationOptions,\n): AsyncGenerator<SelectedWithPreloads<S, Sel, P>, void, void> {\n\tassertNormalisedFilter(context.schema, state.where)\n\tconst query = normaliseAllIterateReadShape(context.schema, 'iterate', state, options)\n\tconst plan = planSelection(context.schema, state.select as readonly string[] | undefined)\n\n\tfor await (const row of context.use.iterateMany(state.where, {\n\t\tselect: plan.adapterSelect,\n\t\torderBy: [...state.orderBy],\n\t\tlimit: query.limit,\n\t\toffset: query.offset,\n\t\t...(query.batchSize === undefined ? {} : { batchSize: query.batchSize }),\n\t})) {\n\t\tconst shaped = await context.shapeRows(state.select, state.preloads, [row])\n\t\tconst first = shaped[0]\n\t\tif (first) yield first\n\t}\n}\n\nexport async function runOneCreate<S extends AnySchema, Sel extends string, P extends readonly AnyPreloadDef[]>(\n\tcontext: SchemaContext<S>,\n\tstate: { select: readonly Sel[] | undefined; preloads: P },\n\tdata: SchemaCreateInput<S>,\n): Promise<SelectedWithPreloads<S, Sel, P>> {\n\tconst validated = validateCreate(context.schema, data as any)\n\tconst row = await context.use.createOne(validated as any)\n\tconst [resolved] = await context.shapeRows(state.select, state.preloads, [row])\n\treturn resolved\n}\n\nexport async function runAllCreate<S extends AnySchema, Sel extends string, P extends readonly AnyPreloadDef[]>(\n\tcontext: SchemaContext<S>,\n\tstate: { select: readonly Sel[] | undefined; preloads: P },\n\tdata: SchemaCreateInput<S>[],\n): Promise<SelectedWithPreloads<S, Sel, P>[]> {\n\tconst validated = validateCreateMany(context.schema, data as any)\n\tconst rows = await context.use.createMany(validated as any)\n\treturn context.shapeRows(state.select, state.preloads, rows)\n}\n\nexport async function runOneUpdate<S extends AnySchema, Sel extends string, P extends readonly AnyPreloadDef[]>(\n\tcontext: SchemaContext<S>,\n\tstate: { where: FilterGroup; select: readonly Sel[] | undefined; preloads: P },\n\tdata: SchemaUpdateInput<S>,\n): Promise<SelectedWithPreloads<S, Sel, P> | null> {\n\tassertNormalisedFilter(context.schema, state.where)\n\tconst validated = validateUpdate(context.schema, data as any)\n\tconst row = await context.use.updateOne(state.where, validated as any)\n\treturn context.shapeOneRow(state.select, state.preloads, row)\n}\n\nexport async function runAllUpdate<S extends AnySchema, Sel extends string, P extends readonly AnyPreloadDef[]>(\n\tcontext: SchemaContext<S>,\n\tstate: { where: FilterGroup; select: readonly Sel[] | undefined; preloads: P },\n\tdata: SchemaUpdateInput<S>,\n): Promise<SelectedWithPreloads<S, Sel, P>[]> {\n\tassertNormalisedFilter(context.schema, state.where)\n\tconst validated = validateUpdate(context.schema, data as any)\n\tconst rows = await context.use.updateMany(state.where, validated as any)\n\treturn context.shapeRows(state.select, state.preloads, rows)\n}\n\nexport async function runOneUpsert<S extends AnySchema, Sel extends string, P extends readonly AnyPreloadDef[]>(\n\tcontext: SchemaContext<S>,\n\tstate: { where: FilterGroup; select: readonly Sel[] | undefined; preloads: P },\n\tdata: UpsertInput<S>,\n): Promise<SelectedWithPreloads<S, Sel, P>> {\n\tassertNormalisedFilter(context.schema, state.where)\n\tconst create = validateCreate(context.schema, data.create as any)\n\tconst ops: AnyUpdateOp[] = []\n\tif ('update' in data) {\n\t\tconst validated = validateUpdate(context.schema, data.update as any)\n\t\tconst plainValues: Record<string, unknown> = {}\n\t\tfor (const [key, value] of Object.entries(validated)) {\n\t\t\tif (isUpdateOp(value)) {\n\t\t\t\tops.push(value)\n\t\t\t} else {\n\t\t\t\tplainValues[key] = value\n\t\t\t}\n\t\t}\n\t\tif (Object.keys(plainValues).length > 0) {\n\t\t\tops.unshift(new SetOp(plainValues))\n\t\t}\n\t}\n\tif (ops.length > 0) {\n\t\tvalidateUpsertConflicts(context.schema, data.create as Record<string, unknown>, ops)\n\t}\n\tconst row = await context.use.upsertOne(state.where, create as any, ops)\n\treturn (await context.shapeOneRow(state.select, state.preloads, row)) as SelectedWithPreloads<S, Sel, P>\n}\n\nexport async function runOneDelete<S extends AnySchema, Sel extends string, P extends readonly AnyPreloadDef[]>(\n\tcontext: SchemaContext<S>,\n\tstate: { where: FilterGroup; select: readonly Sel[] | undefined; preloads: P },\n): Promise<SelectedWithPreloads<S, Sel, P> | null> {\n\tassertNormalisedFilter(context.schema, state.where)\n\tconst row = await context.use.deleteOne(state.where)\n\treturn context.shapeOneRow(state.select, state.preloads, row)\n}\n\nexport async function runAllDelete<S extends AnySchema, Sel extends string, P extends readonly AnyPreloadDef[]>(\n\tcontext: SchemaContext<S>,\n\tstate: { where: FilterGroup; select: readonly Sel[] | undefined; preloads: P },\n): Promise<SelectedWithPreloads<S, Sel, P>[]> {\n\tassertNormalisedFilter(context.schema, state.where)\n\tconst rows = await context.use.deleteMany(state.where)\n\treturn context.shapeRows(state.select, state.preloads, rows)\n}\n\nexport async function runAggregate<S extends AnySchema>(\n\tcontext: SchemaContext<S>,\n\tspec: AggregateSpec,\n): Promise<Array<Record<string, unknown>>> {\n\tassertNormalisedAggregate(context.schema, context.use, spec)\n\treturn context.use.aggregate(spec)\n}\n","import { EquippedError } from '../../../errors'\nimport type { OrmUse } from '../../adapters/base'\nimport { FilterGroup } from '../../filter'\nimport type { AnyPreloadDef, AnyRelDef, NestedPreloadDef } from '../../relations'\nimport { ManyRelation, OneRelation } from '../../relations'\nimport type { AnySchema } from '../../schema'\n\nconst MAX_PRELOAD_DEPTH = 5\n\ntype ResolvedPreloadDef = {\n\tdef: AnyRelDef\n\tpreloads: ResolvedPreloadDef[]\n}\n\nfunction isNestedPreloadDef(def: AnyPreloadDef): def is NestedPreloadDef {\n\treturn typeof def === 'object' && def != null && 'def' in def\n}\n\nfunction relationStep(def: AnyRelDef) {\n\treturn `${def.source.name}.${def.name}->${def.target.name}`\n}\n\nfunction uniqueDefinedValues(entities: readonly Record<string, unknown>[], key: string) {\n\treturn [...new Set(entities.map((entity) => entity[key]).filter((value) => value != null))]\n}\n\nfunction attachOneRelation(\n\tentities: readonly Record<string, unknown>[],\n\tname: string,\n\tlookupKey: string,\n\tlookup: ReadonlyMap<unknown, Record<string, unknown>>,\n) {\n\treturn entities.map((entity) => ({ ...entity, [name]: lookup.get(entity[lookupKey]) ?? null }))\n}\n\nfunction attachManyRelation(\n\tentities: readonly Record<string, unknown>[],\n\tname: string,\n\tlookupKey: string,\n\tlookup: ReadonlyMap<unknown, Record<string, unknown>[]>,\n) {\n\treturn entities.map((entity) => ({ ...entity, [name]: lookup.get(entity[lookupKey]) ?? [] }))\n}\n\nfunction normalizePreloads(defs: readonly AnyPreloadDef[]): ResolvedPreloadDef[] {\n\treturn defs.map((def) => {\n\t\tif (def instanceof ManyRelation || def instanceof OneRelation) {\n\t\t\treturn { def, preloads: [] }\n\t\t}\n\n\t\tif (!isNestedPreloadDef(def) || !(def.def instanceof ManyRelation || def.def instanceof OneRelation)) {\n\t\t\tthrow new Error('Invalid preload definition: nested preloads must include a relation definition in `def`')\n\t\t}\n\n\t\treturn {\n\t\t\tdef: def.def,\n\t\t\tpreloads: normalizePreloads(def.preloads ?? []),\n\t\t}\n\t})\n}\n\nexport async function resolvePreloads<T extends Record<string, unknown>>(\n\tentities: T[],\n\tdefs: readonly AnyPreloadDef[],\n\tgetUse: (s: AnySchema) => OrmUse,\n) {\n\treturn resolvePreloadNodes(entities, normalizePreloads(defs), getUse, 1, []) as unknown as T[]\n}\n\nasync function resolvePreloadNodes(\n\tentities: Record<string, unknown>[],\n\tdefs: readonly ResolvedPreloadDef[],\n\tgetUse: (s: AnySchema) => OrmUse,\n\tdepth: number,\n\tpath: readonly string[],\n) {\n\tfor (const def of defs) entities = await resolvePreload(entities, def, getUse, depth, path)\n\treturn entities\n}\n\nasync function resolvePreload(\n\tentities: Record<string, unknown>[],\n\tnode: ResolvedPreloadDef,\n\tgetUse: (s: AnySchema) => OrmUse,\n\tdepth: number,\n\tpath: readonly string[],\n): Promise<Record<string, unknown>[]> {\n\tif (depth > MAX_PRELOAD_DEPTH) {\n\t\tthrow new EquippedError(`Preload depth exceeded max depth ${MAX_PRELOAD_DEPTH}`, {\n\t\t\toperation: 'resolvePreload',\n\t\t\tdepth,\n\t\t\tmaxDepth: MAX_PRELOAD_DEPTH,\n\t\t})\n\t}\n\n\tconst { def } = node\n\tconst { target, name } = def\n\tconst step = relationStep(def)\n\tif (path.includes(step)) {\n\t\tthrow new EquippedError(`Preload cycle detected: ${[...path, step].join(' -> ')}`, {\n\t\t\toperation: 'resolvePreload',\n\t\t\tpath,\n\t\t\tstep,\n\t\t})\n\t}\n\tconst nextPath = [...path, step]\n\n\tif (def instanceof OneRelation) {\n\t\tif (def.fkOwner === 'source') {\n\t\t\tconst refCol = def.references.name\n\t\t\tconst fkValues = uniqueDefinedValues(entities, def.foreignKey.name)\n\t\t\tif (fkValues.length === 0) return entities.map((e) => ({ ...e, [name]: null }))\n\n\t\t\tlet related = await getUse(target).findMany(FilterGroup.create().in(refCol, fkValues))\n\t\t\tif (node.preloads.length > 0 && related.length > 0) {\n\t\t\t\trelated = await resolvePreloadNodes(related, node.preloads, getUse, depth + 1, nextPath)\n\t\t\t}\n\t\t\tconst lookup = new Map(related.map((r) => [r[refCol], r]))\n\t\t\treturn attachOneRelation(entities, name, def.foreignKey.name, lookup)\n\t\t}\n\n\t\tconst refCol = def.references.name\n\t\tconst refValues = uniqueDefinedValues(entities, refCol)\n\t\tif (refValues.length === 0) return entities.map((e) => ({ ...e, [name]: null }))\n\n\t\tlet related = await getUse(target).findMany(FilterGroup.create().in(def.foreignKey, refValues))\n\t\tif (node.preloads.length > 0 && related.length > 0) {\n\t\t\trelated = await resolvePreloadNodes(related, node.preloads, getUse, depth + 1, nextPath)\n\t\t}\n\t\tconst lookup = new Map(related.map((r) => [r[def.foreignKey.name], r]))\n\t\treturn attachOneRelation(entities, name, refCol, lookup)\n\t}\n\n\tif (def instanceof ManyRelation) {\n\t\tconst refCol = def.references.name\n\t\tconst refValues = uniqueDefinedValues(entities, refCol)\n\t\tif (refValues.length === 0) return entities.map((e) => ({ ...e, [name]: [] }))\n\n\t\tlet related = await getUse(target).findMany(FilterGroup.create().in(def.foreignKey, refValues))\n\t\tif (node.preloads.length > 0 && related.length > 0) {\n\t\t\trelated = await resolvePreloadNodes(related, node.preloads, getUse, depth + 1, nextPath)\n\t\t}\n\n\t\tconst grouped = new Map<unknown, Record<string, unknown>[]>()\n\t\tfor (const r of related) {\n\t\t\tconst fk = r[def.foreignKey.name]\n\t\t\tif (!grouped.has(fk)) grouped.set(fk, [])\n\t\t\tgrouped.get(fk)!.push(r)\n\t\t}\n\t\treturn attachManyRelation(entities, name, refCol, grouped)\n\t}\n\n\tthrow new Error(`Unknown relation kind: ${String(Reflect.get(def as object, 'kind'))}; expected OneRelation or ManyRelation`)\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect } = import.meta.vitest\n\tconst { v } = await import('valleyed')\n\tconst { InMemoryAdapter } = await import('../../adapters/in-memory')\n\tconst { OrmValidationError } = await import('../../errors')\n\tconst { Relations } = await import('../../relations')\n\tconst { Repo } = await import('../repo')\n\tconst { Schema } = await import('../../schema')\n\n\tdescribe('repo preload resolution', () => {\n\t\tlet userCounter = 0\n\t\tlet postCounter = 0\n\t\tlet profileCounter = 0\n\t\tlet orgCounter = 0\n\t\tlet aCounter = 0\n\t\tlet bCounter = 0\n\t\tlet cCounter = 0\n\t\tlet dCounter = 0\n\t\tlet eCounter = 0\n\t\tlet fCounter = 0\n\t\tlet gCounter = 0\n\n\t\tconst UserSchema = Schema.from('users')\n\t\t\t.pk('id', v.string(), () => `u${++userCounter}`)\n\t\t\t.field('email', v.string())\n\t\t\t.field('name', v.string())\n\t\t\t.field('orgId', v.optional(v.string()), { onCreate: () => undefined })\n\t\t\t.build()\n\n\t\tconst PostSchema = Schema.from('posts')\n\t\t\t.pk('id', v.string(), () => `p${++postCounter}`)\n\t\t\t.field('title', v.string())\n\t\t\t.field('userId', v.string())\n\t\t\t.build()\n\n\t\tconst ProfileSchema = Schema.from('profiles')\n\t\t\t.pk('id', v.string(), () => `pr${++profileCounter}`)\n\t\t\t.field('bio', v.string())\n\t\t\t.field('userId', v.string())\n\t\t\t.build()\n\n\t\tconst OrgSchema = Schema.from('orgs')\n\t\t\t.pk('id', v.string(), () => `o${++orgCounter}`)\n\t\t\t.field('name', v.string())\n\t\t\t.build()\n\n\t\tconst ASchema = Schema.from('as')\n\t\t\t.pk('id', v.string(), () => `a${++aCounter}`)\n\t\t\t.build()\n\t\tconst BSchema = Schema.from('bs')\n\t\t\t.pk('id', v.string(), () => `b${++bCounter}`)\n\t\t\t.field('aId', v.string())\n\t\t\t.build()\n\t\tconst CSchema = Schema.from('cs')\n\t\t\t.pk('id', v.string(), () => `c${++cCounter}`)\n\t\t\t.field('bId', v.string())\n\t\t\t.build()\n\t\tconst DSchema = Schema.from('ds')\n\t\t\t.pk('id', v.string(), () => `d${++dCounter}`)\n\t\t\t.field('cId', v.string())\n\t\t\t.build()\n\t\tconst ESchema = Schema.from('es')\n\t\t\t.pk('id', v.string(), () => `e${++eCounter}`)\n\t\t\t.field('dId', v.string())\n\t\t\t.build()\n\t\tconst FSchema = Schema.from('fs')\n\t\t\t.pk('id', v.string(), () => `f${++fCounter}`)\n\t\t\t.field('eId', v.string())\n\t\t\t.build()\n\t\tconst GSchema = Schema.from('gs')\n\t\t\t.pk('id', v.string(), () => `g${++gCounter}`)\n\t\t\t.field('fId', v.string())\n\t\t\t.build()\n\n\t\tconst UserRels = Relations.from(UserSchema)\n\t\t\t.hasMany('posts', PostSchema.fields.userId)\n\t\t\t.hasOne('profile', ProfileSchema.fields.userId)\n\t\t\t.belongsTo('org', UserSchema.fields.orgId, OrgSchema)\n\t\t\t.build()\n\n\t\tconst PostRels = Relations.from(PostSchema)\n\t\t\t.belongsTo('author', PostSchema.fields.userId, UserSchema)\n\t\t\t.build()\n\n\t\tconst ARels = Relations.from(ASchema).hasMany('bs', BSchema.fields.aId).build()\n\t\tconst BRels = Relations.from(BSchema).hasMany('cs', CSchema.fields.bId).build()\n\t\tconst CRels = Relations.from(CSchema).hasMany('ds', DSchema.fields.cId).build()\n\t\tconst DRels = Relations.from(DSchema).hasMany('es', ESchema.fields.dId).build()\n\t\tconst ERels = Relations.from(ESchema).hasMany('fs', FSchema.fields.eId).build()\n\t\tconst FRels = Relations.from(FSchema).hasMany('gs', GSchema.fields.fId).build()\n\n\t\tfunction makeRepo() {\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\treturn Repo.from(adapter).resolve((s) => ({ table: s.name })).build()\n\t\t}\n\n\t\tasync function expectPreloadQueryShapeError(read: Promise<unknown>, cause: RegExp) {\n\t\t\ttry {\n\t\t\t\tawait read\n\t\t\t\texpect.unreachable('preload query-shape validation should have failed')\n\t\t\t} catch (error) {\n\t\t\t\texpect(error).toBeInstanceOf(OrmValidationError)\n\t\t\t\tconst err = error as InstanceType<typeof OrmValidationError>\n\t\t\t\texpect(err.kind).toBe('query-shape')\n\t\t\t\texpect(err.failures.some((failure) => cause.test(String(failure.cause)))).toBe(true)\n\t\t\t}\n\t\t}\n\n\t\ttest('hasMany preload resolves related entities', async () => {\n\t\t\tconst Repo = makeRepo()\n\t\t\tconst user = await Repo.on(UserSchema).one().create({ email: 'u@test.com', name: 'User' })\n\t\t\tawait Repo.on(PostSchema).one().create({ title: 'Post 1', userId: user.id })\n\t\t\tawait Repo.on(PostSchema).one().create({ title: 'Post 2', userId: user.id })\n\n\t\t\tconst users = await Repo.on(UserSchema).all().preload([UserRels.posts]).find()\n\t\t\texpect(users[0].posts).toHaveLength(2)\n\t\t})\n\n\t\ttest('hasOne and belongsTo preloads resolve null and non-null branches', async () => {\n\t\t\tconst Repo = makeRepo()\n\t\t\tconst user = await Repo.on(UserSchema).one().create({ email: 'x@test.com', name: 'X' })\n\t\t\tawait Repo.on(ProfileSchema).one().create({ bio: 'Hello', userId: user.id })\n\n\t\t\tconst users = await Repo.on(UserSchema).all().preload([UserRels.profile]).find()\n\t\t\texpect(users[0].profile?.bio).toBe('Hello')\n\n\t\t\tconst usersWithoutOrg = await Repo.on(UserSchema).all().preload([UserRels.org]).find()\n\t\t\texpect(usersWithoutOrg[0].org).toBeNull()\n\t\t})\n\n\t\ttest('nested preload resolves recursively', async () => {\n\t\t\tconst Repo = makeRepo()\n\t\t\tconst user = await Repo.on(UserSchema).one().create({ email: 'nested@test.com', name: 'Nested User' })\n\t\t\tawait Repo.on(ProfileSchema).one().create({ bio: 'Hello nested', userId: user.id })\n\t\t\tawait Repo.on(PostSchema).one().create({ title: 'Nested Post', userId: user.id })\n\n\t\t\tconst users = await Repo.on(UserSchema)\n\t\t\t\t.all()\n\t\t\t\t.preload([\n\t\t\t\t\t{\n\t\t\t\t\t\tdef: UserRels.posts,\n\t\t\t\t\t\tpreloads: [{ def: PostRels.author, preloads: [UserRels.profile] }],\n\t\t\t\t\t},\n\t\t\t\t])\n\t\t\t\t.find()\n\n\t\t\tconst author = users[0].posts[0].author\n\t\t\tconst profile = author?.profile\n\t\t\texpect(author?.id).toBe(user.id)\n\t\t\texpect(profile?.bio).toBe('Hello nested')\n\t\t})\n\n\t\ttest('cycle detection throws descriptive error', async () => {\n\t\t\tconst Repo = makeRepo()\n\t\t\tconst user = await Repo.on(UserSchema).one().create({ email: 'cycle@test.com', name: 'Cycle User' })\n\t\t\tawait Repo.on(PostSchema).one().create({ title: 'Cycle Post', userId: user.id })\n\n\t\t\tawait expectPreloadQueryShapeError(\n\t\t\t\tRepo.on(UserSchema)\n\t\t\t\t\t.all()\n\t\t\t\t\t.preload([\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdef: UserRels.posts,\n\t\t\t\t\t\t\tpreloads: [{ def: PostRels.author, preloads: [UserRels.posts] }],\n\t\t\t\t\t\t},\n\t\t\t\t\t])\n\t\t\t\t\t.find(),\n\t\t\t\t/Preload cycle detected/,\n\t\t\t)\n\t\t})\n\n\t\ttest('depth limit throws when chain exceeds max depth', async () => {\n\t\t\tconst Repo = makeRepo()\n\t\t\tconst a = await Repo.on(ASchema).one().create({})\n\t\t\tconst b = await Repo.on(BSchema).one().create({ aId: a.id })\n\t\t\tconst c = await Repo.on(CSchema).one().create({ bId: b.id })\n\t\t\tconst d = await Repo.on(DSchema).one().create({ cId: c.id })\n\t\t\tconst e = await Repo.on(ESchema).one().create({ dId: d.id })\n\t\t\tconst f = await Repo.on(FSchema).one().create({ eId: e.id })\n\t\t\tawait Repo.on(GSchema).one().create({ fId: f.id })\n\n\t\t\tawait expectPreloadQueryShapeError(\n\t\t\t\tRepo.on(ASchema)\n\t\t\t\t\t.all()\n\t\t\t\t\t.preload([\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdef: ARels.bs,\n\t\t\t\t\t\t\tpreloads: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tdef: BRels.cs,\n\t\t\t\t\t\t\t\t\tpreloads: [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tdef: CRels.ds,\n\t\t\t\t\t\t\t\t\t\t\tpreloads: [\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\tdef: DRels.es,\n\t\t\t\t\t\t\t\t\t\t\t\t\tpreloads: [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tdef: ERels.fs,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tpreloads: [FRels.gs],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t},\n\t\t\t\t\t])\n\t\t\t\t\t.find(),\n\t\t\t\t/Preload depth exceeded/,\n\t\t\t)\n\t\t})\n\n\t\ttest('invalid nested preload definition throws a validation error', async () => {\n\t\t\tconst Repo = makeRepo()\n\t\t\tawait Repo.on(UserSchema).one().create({ email: 'u@test.com', name: 'User' })\n\n\t\t\tawait expectPreloadQueryShapeError(\n\t\t\t\tRepo.on(UserSchema)\n\t\t\t\t\t.all()\n\t\t\t\t\t.preload([{ def: {} as any }])\n\t\t\t\t\t.find(),\n\t\t\t\t/Invalid preload definition/,\n\t\t\t)\n\t\t})\n\n\t\ttest('findOne with preloads resolves relations', async () => {\n\t\t\tconst Repo = makeRepo()\n\t\t\tconst user = await Repo.on(UserSchema).one().create({ email: 'u@test.com', name: 'User' })\n\t\t\tawait Repo.on(PostSchema).one().create({ title: 'Post', userId: user.id })\n\n\t\t\tconst found = await Repo.on(UserSchema).one().id(user.id).preload([UserRels.posts]).find()\n\t\t\texpect(found?.posts).toHaveLength(1)\n\t\t})\n\n\t\ttest('N+1 avoidance: N parents + children loaded in 2 queries, not N+1', async () => {\n\t\t\tconst { vi } = await import('vitest')\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tlet queryCount = 0\n\t\t\tconst origUse = adapter.use.bind(adapter)\n\t\t\t;(adapter as any).use = vi.fn((schema: any, config: any) => {\n\t\t\t\tconst use = origUse(schema, config)\n\t\t\t\treturn {\n\t\t\t\t\t...use,\n\t\t\t\t\tfindMany: async (...args: any[]) => {\n\t\t\t\t\t\tqueryCount++\n\t\t\t\t\t\treturn use.findMany(...(args as [any, any]))\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tconst repo = Repo.from(adapter).resolve((s) => ({ table: s.name })).build()\n\t\t\tconst u1 = await repo.on(UserSchema).one().create({ email: 'a@test.com', name: 'A' })\n\t\t\tconst u2 = await repo.on(UserSchema).one().create({ email: 'b@test.com', name: 'B' })\n\t\t\tconst u3 = await repo.on(UserSchema).one().create({ email: 'c@test.com', name: 'C' })\n\t\t\tawait repo.on(PostSchema).one().create({ title: 'P1', userId: u1.id })\n\t\t\tawait repo.on(PostSchema).one().create({ title: 'P2', userId: u1.id })\n\t\t\tawait repo.on(PostSchema).one().create({ title: 'P3', userId: u2.id })\n\t\t\tawait repo.on(PostSchema).one().create({ title: 'P4', userId: u3.id })\n\n\t\t\tqueryCount = 0\n\t\t\tconst users = await repo.on(UserSchema).all().preload([UserRels.posts]).find()\n\n\t\t\texpect(users).toHaveLength(3)\n\t\t\texpect(users.find((u) => u.id === u1.id)!.posts).toHaveLength(2)\n\t\t\texpect(users.find((u) => u.id === u2.id)!.posts).toHaveLength(1)\n\t\t\texpect(users.find((u) => u.id === u3.id)!.posts).toHaveLength(1)\n\t\t\texpect(queryCount).toBe(2)\n\t\t})\n\t})\n}\n","import type { InferRawArgs, InferRawReturn } from '../adapter'\nimport type { OrmUse } from '../adapters/base'\nimport { OrmNotFoundError, type OrmNotFoundOperation } from '../errors'\nimport { toFieldName, type AnyField, type Field } from '../fields'\nimport { FilterGroup, type FilterFactory } from '../filter'\nimport type { AggregateSpec } from '../orm-adapter'\nimport { OrderBy, type IterationOptions } from '../query-options'\nimport type { AnyPreloadDef } from '../relations'\nimport type { AnySchema, SchemaOutput } from '../schema'\nimport type { SchemaCreateInput, SchemaUpdateInput } from '../schema-validations'\nimport { applyComputedSelection, planSelection } from './internals/computeds'\nimport {\n\trunAggregate,\n\trunAllCount,\n\trunAllCreate,\n\trunAllDelete,\n\trunAllIterate,\n\trunAllPaginate,\n\trunAllRead,\n\trunAllUpdate,\n\trunOneCreate,\n\trunOneDelete,\n\trunOneRead,\n\trunOneUpdate,\n\trunOneUpsert,\n} from './internals/executors'\nimport { resolvePreloads } from './internals/preloads'\nimport type { ReadLimitSource, ReadOffsetSource } from './internals/query-shape'\nimport type { Paginated, SelectedWithPreloads } from './internals/types'\n\nexport type { Paginated }\n\ntype MaybeNull<T, Req extends boolean> = Req extends true ? T : T | null\ntype SchemaPrimaryKeyValue<S extends AnySchema> = SchemaOutput<S>[S['pkField']['name'] & keyof SchemaOutput<S>]\nexport type UpsertInput<S extends AnySchema> = { create: SchemaCreateInput<S> } | { create: SchemaCreateInput<S>; update: SchemaUpdateInput<S> }\ntype ReadState<Sel extends string, P extends readonly AnyPreloadDef[] = readonly AnyPreloadDef[]> = {\n\twhere?: FilterGroup\n\tselect?: readonly Sel[]\n\tpreloads?: P\n}\n\nexport type HasMethod<A, Method extends string> =\n\tMethod extends keyof A\n\t\t? A[Method] extends (...args: any) => any\n\t\t\t? true\n\t\t\t: false\n\t\t: false\n\nexport type OneBuilderSurface<S extends AnySchema, A = unknown, Sel extends string = never, P extends readonly AnyPreloadDef[] = [], Req extends boolean = false> =\n\tOneBuilder<S, A, Sel, P, Req> &\n\t(HasMethod<A, 'updateMany'> extends true ? {} : { update: never }) &\n\t(HasMethod<A, 'deleteMany'> extends true ? {} : { delete: never }) &\n\t(HasMethod<A, 'upsertOne'> extends true ? {} : { upsert: never })\n\nexport type AllBuilderSurface<S extends AnySchema, A = unknown, Sel extends string = never, P extends readonly AnyPreloadDef[] = []> =\n\tAllBuilder<S, A, Sel, P> &\n\t(HasMethod<A, 'iterateMany'> extends true ? {} : { iterate: never }) &\n\t(HasMethod<A, 'updateMany'> extends true ? {} : { update: never }) &\n\t(HasMethod<A, 'deleteMany'> extends true ? {} : { delete: never }) &\n\t(HasMethod<A, 'count'> extends true ? {} : { count: never }) &\n\t([HasMethod<A, 'findMany'>, HasMethod<A, 'count'>] extends [true, true] ? {} : { paginate: never })\n\ntype HasNonEmptyAggregateOps<A> = A extends { aggregateOps: readonly [any, ...any[]] } ? true : false\n\nexport type SchemaRefSurface<S extends AnySchema, A = unknown> =\n\tOmit<SchemaRef<S, A>, 'raw' | 'aggregate'> &\n\t(HasMethod<A, 'raw'> extends true\n\t\t? { raw: <T = InferRawReturn<A>>(...args: InferRawArgs<A>) => Promise<T> }\n\t\t: { raw: never }) &\n\t([HasMethod<A, 'aggregate'>, HasNonEmptyAggregateOps<A>] extends [true, true]\n\t\t? { aggregate: SchemaRef<S, A>['aggregate'] }\n\t\t: { aggregate: never })\n\ntype ReadBuilderFor<TBuilder, S extends AnySchema, A, Sel extends string, P extends readonly AnyPreloadDef[]> = TBuilder extends {\n\t_builderKind: 'one'\n\t_req: infer Req extends boolean\n}\n\t? OneBuilderSurface<S, A, Sel, P, Req>\n\t: TBuilder extends { _builderKind: 'all' }\n\t\t? AllBuilderSurface<S, A, Sel, P>\n\t\t: never\n\nexport class SchemaContext<S extends AnySchema> {\n\tconstructor(\n\t\treadonly schema: S,\n\t\tprivate readonly getUse: (target: AnySchema) => OrmUse,\n\t) {}\n\n\tasync shapeRows<Sel extends string, P extends readonly AnyPreloadDef[]>(\n\t\tselect: readonly Sel[] | undefined,\n\t\tpreloads: P,\n\t\trows: Record<string, unknown>[],\n\t): Promise<SelectedWithPreloads<S, Sel, P>[]> {\n\t\tconst plan = planSelection(this.schema, select)\n\t\tconst selected = applyComputedSelection(this.schema, rows, plan)\n\t\tif (preloads.length === 0) return selected as SelectedWithPreloads<S, Sel, P>[]\n\t\treturn (await resolvePreloads(selected, preloads, this.getUse)) as SelectedWithPreloads<S, Sel, P>[]\n\t}\n\n\tasync shapeOneRow<Sel extends string, P extends readonly AnyPreloadDef[]>(\n\t\tselect: readonly Sel[] | undefined,\n\t\tpreloads: P,\n\t\trow: Record<string, unknown> | null,\n\t): Promise<SelectedWithPreloads<S, Sel, P> | null> {\n\t\tif (!row) return null\n\t\tconst [resolved] = await this.shapeRows(select, preloads, [row])\n\t\treturn resolved ?? null\n\t}\n\n\tget use() {\n\t\treturn this.getUse(this.schema)\n\t}\n}\n\nabstract class ReadSelectState<S extends AnySchema, A = unknown, Sel extends string = never, P extends readonly AnyPreloadDef[] = []> {\n\tprotected readonly _context: SchemaContext<S>\n\tprotected _where: FilterGroup\n\tprotected _select: readonly Sel[] | undefined\n\tprotected _preloads: P\n\n\tconstructor(context: SchemaContext<S>, state?: ReadState<Sel, P>) {\n\t\tthis._context = context\n\t\tthis._where = state?.where ? state.where.clone() : FilterGroup.create()\n\t\tthis._select = state?.select\n\t\tthis._preloads = state?.preloads ?? ([] as unknown as P)\n\t}\n\n\twhere(factory: FilterFactory): this {\n\t\tconst nextGroup = factory(this._where.clone())\n\t\treturn this._clone<Sel, P>({\n\t\t\twhere: nextGroup,\n\t\t\tselect: this._select as readonly Sel[] | undefined,\n\t\t\tpreloads: this._preloads,\n\t\t}) as unknown as this\n\t}\n\n\tselect<NewSel extends keyof SchemaOutput<S> & string>(fields: readonly NewSel[]): ReadBuilderFor<this, S, A, NewSel, P> {\n\t\treturn this._clone<NewSel, P>({ select: fields })\n\t}\n\n\tpreload<NewP extends readonly AnyPreloadDef[]>(defs: NewP): ReadBuilderFor<this, S, A, Sel, NewP> {\n\t\treturn this._clone<Sel, NewP>({ preloads: defs })\n\t}\n\n\tprotected _readState<NewSel extends string = Sel, NewP extends readonly AnyPreloadDef[] = P>(\n\t\tnext: ReadState<NewSel, NewP> = {} as ReadState<NewSel, NewP>,\n\t): ReadState<NewSel, NewP> {\n\t\treturn {\n\t\t\twhere: next.where ?? this._where.clone(),\n\t\t\tselect: next.select ?? (this._select as unknown as NewSel[]),\n\t\t\tpreloads: next.preloads ?? ([...this._preloads] as unknown as NewP),\n\t\t}\n\t}\n\n\tprotected abstract _clone<NewSel extends string, NewP extends readonly AnyPreloadDef[]>(\n\t\t_next: ReadState<NewSel, NewP>,\n\t): ReadBuilderFor<this, S, A, NewSel, NewP>\n}\n\nexport class SchemaRef<S extends AnySchema, A = unknown> {\n\treadonly #context: SchemaContext<S>\n\n\tconstructor(context: SchemaContext<S>) {\n\t\tthis.#context = context\n\t}\n\n\tone(): OneBuilderSurface<S, A, never, []> {\n\t\treturn new OneBuilder<S, A, never, []>(this.#context) as OneBuilderSurface<S, A, never, []>\n\t}\n\n\tall(): AllBuilderSurface<S, A, never, []> {\n\t\treturn new AllBuilder<S, A, never, []>(this.#context) as AllBuilderSurface<S, A, never, []>\n\t}\n\n\taggregate(): AggregateBuilder<S, A, {}, {}, false, false, false> {\n\t\treturn new AggregateBuilder<S, A, {}, {}, false, false, false>(this.#context)\n\t}\n\n\traw(...args: any[]) {\n\t\treturn this.#context.use.raw(...args)\n\t}\n}\n\nexport class OneBuilder<S extends AnySchema, A = unknown, Sel extends string = never, P extends readonly AnyPreloadDef[] = [], Req extends boolean = false> extends ReadSelectState<S, A, Sel, P> {\n\tdeclare readonly _builderKind: 'one'\n\tdeclare readonly _req: Req\n\n\tprotected _required: boolean\n\tprotected _requiredMessage: string | undefined\n\n\tconstructor(context: SchemaContext<S>, state?: ReadState<Sel, P>, reqState?: { required: boolean; message?: string }) {\n\t\tsuper(context, state)\n\t\tthis._required = reqState?.required ?? false\n\t\tthis._requiredMessage = reqState?.message\n\t}\n\n\tid(value: SchemaPrimaryKeyValue<S>): this {\n\t\tconst nextGroup = this._where.clone().eq(this._context.schema.pkField, value)\n\t\treturn this._clone<Sel, P>({\n\t\t\twhere: nextGroup,\n\t\t\tselect: this._select as readonly Sel[] | undefined,\n\t\t\tpreloads: this._preloads,\n\t\t}) as this\n\t}\n\n\trequired(this: OneBuilder<S, A, Sel, P, false>, message?: string): OneBuilderSurface<S, A, Sel, P, true> {\n\t\treturn new OneBuilder<S, A, Sel, P, true>(this._context, this._readState(), { required: true, message }) as OneBuilderSurface<S, A, Sel, P, true>\n\t}\n\n\tprivate _assertFound(result: unknown, operation: OrmNotFoundOperation): void {\n\t\tif (this._required && result === null) {\n\t\t\tthrow new OrmNotFoundError({ schema: this._context.schema.name, operation, where: this._where, message: this._requiredMessage })\n\t\t}\n\t}\n\n\tprotected _clone<NewSel extends string, NewP extends readonly AnyPreloadDef[]>(next: ReadState<NewSel, NewP>) {\n\t\treturn new OneBuilder<S, A, NewSel, NewP, Req>(this._context, this._readState(next), { required: this._required, message: this._requiredMessage }) as any\n\t}\n\n\tcreate(data: SchemaCreateInput<S>) {\n\t\treturn runOneCreate(\n\t\t\tthis._context,\n\t\t\t{\n\t\t\t\tselect: this._select,\n\t\t\t\tpreloads: this._preloads,\n\t\t\t},\n\t\t\tdata,\n\t\t)\n\t}\n\n\tasync update(data: SchemaUpdateInput<S>): Promise<MaybeNull<SelectedWithPreloads<S, Sel, P>, Req>> {\n\t\tconst result = await runOneUpdate(\n\t\t\tthis._context,\n\t\t\t{\n\t\t\t\twhere: this._where,\n\t\t\t\tselect: this._select,\n\t\t\t\tpreloads: this._preloads,\n\t\t\t},\n\t\t\tdata,\n\t\t)\n\t\tthis._assertFound(result, 'updateOne')\n\t\treturn result as any\n\t}\n\n\tupsert(data: UpsertInput<S>) {\n\t\treturn runOneUpsert(\n\t\t\tthis._context,\n\t\t\t{\n\t\t\t\twhere: this._where,\n\t\t\t\tselect: this._select,\n\t\t\t\tpreloads: this._preloads,\n\t\t\t},\n\t\t\tdata,\n\t\t)\n\t}\n\n\tasync delete(): Promise<MaybeNull<SelectedWithPreloads<S, Sel, P>, Req>> {\n\t\tconst result = await runOneDelete(this._context, {\n\t\t\twhere: this._where,\n\t\t\tselect: this._select,\n\t\t\tpreloads: this._preloads,\n\t\t})\n\t\tthis._assertFound(result, 'deleteOne')\n\t\treturn result as any\n\t}\n\n\tasync find(): Promise<MaybeNull<SelectedWithPreloads<S, Sel, P>, Req>> {\n\t\tconst result = await runOneRead(this._context, {\n\t\t\twhere: this._where,\n\t\t\tselect: this._select,\n\t\t\tpreloads: this._preloads,\n\t\t})\n\t\tthis._assertFound(result, 'findOne')\n\t\treturn result as any\n\t}\n}\n\nexport class AllBuilder<S extends AnySchema, A = unknown, Sel extends string = never, P extends readonly AnyPreloadDef[] = []> extends ReadSelectState<S, A, Sel, P> {\n\tdeclare readonly _builderKind: 'all'\n\n\t#orderBy: OrderBy[]\n\t#limitSource: ReadLimitSource | undefined\n\t#offsetSource: ReadOffsetSource | undefined\n\n\tconstructor(\n\t\tcontext: SchemaContext<S>,\n\t\tstate?: ReadState<Sel, P>,\n\t\tqueryState?: { orderBy?: OrderBy[]; limitSource?: ReadLimitSource; offsetSource?: ReadOffsetSource },\n\t) {\n\t\tsuper(context, state)\n\t\tthis.#orderBy = queryState?.orderBy ?? []\n\t\tthis.#limitSource = queryState?.limitSource\n\t\tthis.#offsetSource = queryState?.offsetSource\n\t}\n\n\t#withQuery(queryOverride: Partial<{ orderBy: OrderBy[]; limitSource: ReadLimitSource; offsetSource: ReadOffsetSource }>) {\n\t\tconst has = (key: keyof typeof queryOverride) => Object.prototype.hasOwnProperty.call(queryOverride, key)\n\t\treturn new AllBuilder<S, A, Sel, P>(\n\t\t\tthis._context,\n\t\t\tthis._readState(),\n\t\t\t{\n\t\t\t\torderBy: has('orderBy') ? queryOverride.orderBy : [...this.#orderBy],\n\t\t\t\tlimitSource: has('limitSource') ? queryOverride.limitSource : this.#limitSource,\n\t\t\t\toffsetSource: has('offsetSource') ? queryOverride.offsetSource : this.#offsetSource,\n\t\t\t},\n\t\t) as this\n\t}\n\n\tprotected _clone<NewSel extends string, NewP extends readonly AnyPreloadDef[]>(next: ReadState<NewSel, NewP>) {\n\t\treturn new AllBuilder<S, A, NewSel, NewP>(\n\t\t\tthis._context,\n\t\t\tthis._readState({\n\t\t\t\twhere: next.where,\n\t\t\t\tselect: next.select,\n\t\t\t\tpreloads: next.preloads,\n\t\t\t}),\n\t\t\t{ orderBy: [...this.#orderBy], limitSource: this.#limitSource, offsetSource: this.#offsetSource },\n\t\t) as any\n\t}\n\n\torderBy(field: string | AnyField, direction: 'asc' | 'desc' = 'asc') {\n\t\treturn this.#withQuery({ orderBy: [...this.#orderBy, new OrderBy(field, direction)] })\n\t}\n\n\tlimit(limit: number) {\n\t\treturn this.#withQuery({ limitSource: { value: limit } })\n\t}\n\n\toffset(offset: number) {\n\t\treturn this.#withQuery({ offsetSource: { kind: 'offset', value: offset } })\n\t}\n\n\tpage(page: number) {\n\t\treturn this.#withQuery({ offsetSource: { kind: 'page', value: page } })\n\t}\n\n\tcreate(data: SchemaCreateInput<S>[]) {\n\t\treturn runAllCreate(\n\t\t\tthis._context,\n\t\t\t{\n\t\t\t\tselect: this._select,\n\t\t\t\tpreloads: this._preloads,\n\t\t\t},\n\t\t\tdata,\n\t\t)\n\t}\n\n\tupdate(data: SchemaUpdateInput<S>) {\n\t\treturn runAllUpdate(\n\t\t\tthis._context,\n\t\t\t{\n\t\t\t\twhere: this._where,\n\t\t\t\tselect: this._select,\n\t\t\t\tpreloads: this._preloads,\n\t\t\t},\n\t\t\tdata,\n\t\t)\n\t}\n\n\tdelete() {\n\t\treturn runAllDelete(this._context, {\n\t\t\twhere: this._where,\n\t\t\tselect: this._select,\n\t\t\tpreloads: this._preloads,\n\t\t})\n\t}\n\n\tfind() {\n\t\treturn runAllRead(this._context, {\n\t\t\twhere: this._where,\n\t\t\tselect: this._select,\n\t\t\tpreloads: this._preloads,\n\t\t\torderBy: this.#orderBy,\n\t\t\tlimitSource: this.#limitSource,\n\t\t\toffsetSource: this.#offsetSource,\n\t\t})\n\t}\n\n\tcount() {\n\t\treturn runAllCount(this._context, { where: this._where })\n\t}\n\n\tpaginate(): Promise<Paginated<SelectedWithPreloads<S, Sel, P>>> {\n\t\treturn runAllPaginate(this._context, {\n\t\t\twhere: this._where,\n\t\t\tselect: this._select,\n\t\t\tpreloads: this._preloads,\n\t\t\torderBy: this.#orderBy,\n\t\t\tlimitSource: this.#limitSource,\n\t\t\toffsetSource: this.#offsetSource,\n\t\t})\n\t}\n\n\titerate(options?: IterationOptions) {\n\t\treturn runAllIterate(this._context, {\n\t\t\twhere: this._where,\n\t\t\tselect: this._select,\n\t\t\tpreloads: this._preloads,\n\t\t\torderBy: this.#orderBy,\n\t\t\tlimitSource: this.#limitSource,\n\t\t\toffsetSource: this.#offsetSource,\n\t\t}, options)\n\t}\n}\n\ntype AggregateEntry = AggregateSpec['aggregates'][number]\n\ntype FieldsToGroupKeys<F extends readonly AnyField[]> = {\n\t[E in F[number] as E extends Field<any, infer N> ? N : never]: E extends Field<infer V> ? V : never\n}\n\nexport class AggregateBuilder<\n\tS extends AnySchema,\n\tA = unknown,\n\tAggs = {},\n\tGroupKeys = {},\n\tHasGroupBy extends boolean = false,\n\tHasWhere extends boolean = false,\n\tHasHaving extends boolean = false,\n> {\n\treadonly #context: SchemaContext<S>\n\treadonly #where: FilterGroup\n\treadonly #having: FilterGroup\n\treadonly #aggregates: readonly AggregateEntry[]\n\treadonly #groupBy: readonly string[]\n\n\tconstructor(\n\t\tcontext: SchemaContext<S>,\n\t\tstate?: { where?: FilterGroup; having?: FilterGroup; aggregates?: readonly AggregateEntry[]; groupBy?: readonly string[] },\n\t) {\n\t\tthis.#context = context\n\t\tthis.#where = state?.where ? state.where.clone() : FilterGroup.create()\n\t\tthis.#having = state?.having ? state.having.clone() : FilterGroup.create()\n\t\tthis.#aggregates = state?.aggregates ?? []\n\t\tthis.#groupBy = state?.groupBy ?? []\n\t}\n\n\tget #state() {\n\t\treturn { where: this.#where, having: this.#having, aggregates: this.#aggregates, groupBy: this.#groupBy }\n\t}\n\n\twhere(\n\t\t...args: HasWhere extends true ? [never] : [factory: FilterFactory]\n\t): AggregateBuilder<S, A, Aggs, GroupKeys, HasGroupBy, true, HasHaving> {\n\t\tconst factory = args[0] as FilterFactory\n\t\treturn new AggregateBuilder<S, A, Aggs, GroupKeys, HasGroupBy, true, HasHaving>(this.#context, {\n\t\t\t...this.#state,\n\t\t\twhere: factory(this.#where.clone()),\n\t\t})\n\t}\n\n\thaving(\n\t\t...args: HasHaving extends true ? [never] : [factory: FilterFactory]\n\t): AggregateBuilder<S, A, Aggs, GroupKeys, HasGroupBy, HasWhere, true> {\n\t\tconst factory = args[0] as FilterFactory\n\t\treturn new AggregateBuilder<S, A, Aggs, GroupKeys, HasGroupBy, HasWhere, true>(this.#context, {\n\t\t\t...this.#state,\n\t\t\thaving: factory(this.#having.clone()),\n\t\t})\n\t}\n\n\tgroupBy<F extends readonly Field<string | number | boolean | Date>[]>(\n\t\t...fields: HasGroupBy extends true ? [never] : [...F]\n\t): AggregateBuilder<S, A, Aggs, GroupKeys & FieldsToGroupKeys<F>, true, HasWhere, HasHaving> {\n\t\treturn new AggregateBuilder<S, A, Aggs, GroupKeys & FieldsToGroupKeys<F>, true, HasWhere, HasHaving>(this.#context, {\n\t\t\t...this.#state,\n\t\t\tgroupBy: (fields as readonly AnyField[]).map((f) => toFieldName(f)),\n\t\t})\n\t}\n\n\tcount<K extends string>(\n\t\t...[alias]: K extends keyof Aggs ? [never] : [alias: K]\n\t): AggregateBuilder<S, A, Aggs & Record<K, number>, GroupKeys, HasGroupBy, HasWhere, HasHaving> {\n\t\treturn new AggregateBuilder<S, A, Aggs & Record<K, number>, GroupKeys, HasGroupBy, HasWhere, HasHaving>(this.#context, {\n\t\t\t...this.#state,\n\t\t\taggregates: [...this.#aggregates, { fn: 'count', alias: alias as string }],\n\t\t})\n\t}\n\n\tcountDistinct<K extends string>(\n\t\tfield: AnyField,\n\t\t...[alias]: K extends keyof Aggs ? [never] : [alias: K]\n\t): AggregateBuilder<S, A, Aggs & Record<K, number>, GroupKeys, HasGroupBy, HasWhere, HasHaving> {\n\t\treturn new AggregateBuilder<S, A, Aggs & Record<K, number>, GroupKeys, HasGroupBy, HasWhere, HasHaving>(this.#context, {\n\t\t\t...this.#state,\n\t\t\taggregates: [...this.#aggregates, { fn: 'countDistinct', field: toFieldName(field), alias: alias as string }],\n\t\t})\n\t}\n\n\tsum<K extends string>(\n\t\tfield: Field<number>,\n\t\t...[alias]: K extends keyof Aggs ? [never] : [alias: K]\n\t): AggregateBuilder<S, A, Aggs & Record<K, number>, GroupKeys, HasGroupBy, HasWhere, HasHaving> {\n\t\treturn new AggregateBuilder<S, A, Aggs & Record<K, number>, GroupKeys, HasGroupBy, HasWhere, HasHaving>(this.#context, {\n\t\t\t...this.#state,\n\t\t\taggregates: [...this.#aggregates, { fn: 'sum', field: toFieldName(field), alias: alias as string }],\n\t\t})\n\t}\n\n\tavg<K extends string>(\n\t\tfield: Field<number>,\n\t\t...[alias]: K extends keyof Aggs ? [never] : [alias: K]\n\t): AggregateBuilder<S, A, Aggs & Record<K, number>, GroupKeys, HasGroupBy, HasWhere, HasHaving> {\n\t\treturn new AggregateBuilder<S, A, Aggs & Record<K, number>, GroupKeys, HasGroupBy, HasWhere, HasHaving>(this.#context, {\n\t\t\t...this.#state,\n\t\t\taggregates: [...this.#aggregates, { fn: 'avg', field: toFieldName(field), alias: alias as string }],\n\t\t})\n\t}\n\n\tmin<F extends Field<number | string | Date>, K extends string>(\n\t\tfield: F,\n\t\t...[alias]: K extends keyof Aggs ? [never] : [alias: K]\n\t): AggregateBuilder<S, A, Aggs & Record<K, F extends Field<infer V> ? V : never>, GroupKeys, HasGroupBy, HasWhere, HasHaving> {\n\t\treturn new AggregateBuilder<S, A, Aggs & Record<K, F extends Field<infer V> ? V : never>, GroupKeys, HasGroupBy, HasWhere, HasHaving>(this.#context, {\n\t\t\t...this.#state,\n\t\t\taggregates: [...this.#aggregates, { fn: 'min', field: toFieldName(field), alias: alias as string }],\n\t\t})\n\t}\n\n\tmax<F extends Field<number | string | Date>, K extends string>(\n\t\tfield: F,\n\t\t...[alias]: K extends keyof Aggs ? [never] : [alias: K]\n\t): AggregateBuilder<S, A, Aggs & Record<K, F extends Field<infer V> ? V : never>, GroupKeys, HasGroupBy, HasWhere, HasHaving> {\n\t\treturn new AggregateBuilder<S, A, Aggs & Record<K, F extends Field<infer V> ? V : never>, GroupKeys, HasGroupBy, HasWhere, HasHaving>(this.#context, {\n\t\t\t...this.#state,\n\t\t\taggregates: [...this.#aggregates, { fn: 'max', field: toFieldName(field), alias: alias as string }],\n\t\t})\n\t}\n\n\tasync run(\n\t\t..._: [keyof Aggs] extends [never] ? [never] : []\n\t): Promise<HasGroupBy extends true ? (Aggs & GroupKeys)[] : Aggs> {\n\t\tconst spec: AggregateSpec = {\n\t\t\taggregates: this.#aggregates,\n\t\t\tgroupBy: this.#groupBy,\n\t\t}\n\t\tif (this.#where.children.length > 0) {\n\t\t\tspec.where = this.#where\n\t\t}\n\t\tif (this.#having.children.length > 0) {\n\t\t\tspec.having = this.#having\n\t\t}\n\t\tconst rows = await runAggregate(this.#context, spec)\n\t\tif (this.#groupBy.length > 0) {\n\t\t\treturn rows as any\n\t\t}\n\t\treturn rows[0] as any\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect, expectTypeOf, beforeEach, vi } = import.meta.vitest\n\tconst { v } = await import('valleyed')\n\tconst { Instance } = await import('../../instance')\n\tconst { InMemoryAdapter } = await import('../adapters/in-memory')\n\tconst { OrmValidationError } = await import('../errors')\n\tconst { OrmAdapter } = await import('../orm-adapter')\n\tconst { Relations } = await import('../relations')\n\tconst { Repo } = await import('./repo')\n\tconst { Schema } = await import('../schema')\n\n\tdescribe('builders', () => {\n\t\tlet repo: any\n\t\tbeforeEach(() => {\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\trepo = Repo.from(adapter).resolve((s) => ({ table: s.name })).build()\n\t\t})\n\n\t\ttest('update() executes and returns updated row', async () => {\n\t\t\tconst UserSchema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => `u-${Math.random().toString(36).slice(2, 8)}`)\n\t\t\t\t.field('email', v.string())\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\n\t\t\tconst created = await repo.on(UserSchema).one().create({ email: 'up@test.com', name: 'Before' })\n\t\t\tconst updated = await repo.on(UserSchema).one().id(created.id).update({ name: 'After' })\n\t\t\texpect(updated?.name).toBe('After')\n\t\t\tconst found = await repo.on(UserSchema).one().id(created.id).find()\n\t\t\texpect(found?.name).toBe('After')\n\t\t})\n\n\t\ttest('find() returns rows', async () => {\n\t\t\tconst UserSchema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => `u-${Math.random().toString(36).slice(2, 8)}`)\n\t\t\t\t.field('email', v.string())\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\n\t\t\tawait repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.all()\n\t\t\t\t.create([\n\t\t\t\t\t{ email: 'a@x.com', name: 'Alice' },\n\t\t\t\t\t{ email: 'b@x.com', name: 'Bob' },\n\t\t\t\t])\n\n\t\t\tconst rows = await repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.or([(g) => g.eq('name', 'Alice'), (g) => g.eq('name', 'Bob')]))\n\t\t\t\t.find()\n\t\t\texpect(rows).toHaveLength(2)\n\t\t})\n\n\t\ttest('write branches do not leak filters', async () => {\n\t\t\tconst UserSchema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => `u-${Math.random().toString(36).slice(2, 8)}`)\n\t\t\t\t.field('email', v.string())\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\n\t\t\tawait repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.all()\n\t\t\t\t.create([\n\t\t\t\t\t{ email: 'alice@x.com', name: 'Alice' },\n\t\t\t\t\t{ email: 'bob@x.com', name: 'Bob' },\n\t\t\t\t])\n\n\t\t\tconst base = repo.on(UserSchema).all()\n\t\t\tawait base.where((q) => q.eq('name', 'Alice')).update({ name: 'A Updated' })\n\t\t\tawait base.where((q) => q.eq('name', 'Bob')).update({ name: 'B Updated' })\n\n\t\t\tconst all = await repo.on(UserSchema).all().orderBy('name', 'asc').find()\n\t\t\texpect(all.map((r) => r.name)).toEqual(['A Updated', 'B Updated'])\n\t\t})\n\t})\n\n\tdescribe('.required() modifier', () => {\n\t\tconst UserSchema = Schema.from('users')\n\t\t\t.pk('id', v.string(), () => `u-${Math.random().toString(36).slice(2, 8)}`)\n\t\t\t.field('email', v.string())\n\t\t\t.field('name', v.string())\n\t\t\t.build()\n\n\t\tlet repo: any\n\t\tbeforeEach(() => {\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\trepo = Repo.from(adapter).resolve((s) => ({ table: s.name })).build()\n\t\t})\n\n\t\tdescribe('type narrowing', () => {\n\t\t\ttype IsExact<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false\n\t\t\ttype TestA = { findOne: (...a: any[]) => any; createOne: (...a: any[]) => any; createMany: (...a: any[]) => any; updateMany: (...a: any[]) => any; deleteMany: (...a: any[]) => any; upsertOne: (...a: any[]) => any; findMany: (...a: any[]) => any; deleteOne: (...a: any[]) => any; updateOne: (...a: any[]) => any }\n\t\t\ttype S = typeof UserSchema\n\t\t\ttype Result = import('./internals/types').SelectedWithPreloads<S, never, []>\n\t\t\ttype NameOnly = import('./internals/types').SelectedWithPreloads<S, 'name', []>\n\n\t\t\ttest('find() returns T | null without .required()', () => {\n\t\t\t\ttype R = ReturnType<OneBuilderSurface<S, TestA, never, [], false>['find']>\n\t\t\t\tconst _: IsExact<R, Promise<Result | null>> = true\n\t\t\t\tvoid _\n\t\t\t})\n\n\t\t\ttest('find() returns T with .required()', () => {\n\t\t\t\ttype R = ReturnType<OneBuilderSurface<S, TestA, never, [], true>['find']>\n\t\t\t\tconst _: IsExact<R, Promise<Result>> = true\n\t\t\t\tvoid _\n\t\t\t})\n\n\t\t\ttest('update() returns T with .required()', () => {\n\t\t\t\ttype R = ReturnType<OneBuilderSurface<S, TestA, never, [], true>['update']>\n\t\t\t\tconst _: IsExact<R, Promise<Result>> = true\n\t\t\t\tvoid _\n\t\t\t})\n\n\t\t\ttest('delete() returns T with .required()', () => {\n\t\t\t\ttype R = ReturnType<OneBuilderSurface<S, TestA, never, [], true>['delete']>\n\t\t\t\tconst _: IsExact<R, Promise<Result>> = true\n\t\t\t\tvoid _\n\t\t\t})\n\n\t\t\ttest('create() return type unchanged regardless of Req', () => {\n\t\t\t\ttype WithReq = ReturnType<OneBuilderSurface<S, TestA, never, [], true>['create']>\n\t\t\t\ttype WithoutReq = ReturnType<OneBuilderSurface<S, TestA, never, [], false>['create']>\n\t\t\t\tconst _: IsExact<WithReq, WithoutReq> = true\n\t\t\t\tvoid _\n\t\t\t})\n\n\t\t\ttest('.required() preserves Req through select()', () => {\n\t\t\t\ttype R = ReturnType<OneBuilderSurface<S, TestA, 'name', [], true>['find']>\n\t\t\t\tconst _: IsExact<R, Promise<NameOnly>> = true\n\t\t\t\tvoid _\n\t\t\t})\n\n\t\t\ttest('.required() once-per-step: Req=true surface has uncallable required()', () => {\n\t\t\t\ttype ReqBuilder = OneBuilderSurface<S, TestA, never, [], true>\n\t\t\t\ttype RequiredMethod = ReqBuilder['required']\n\t\t\t\ttype ThisParam = ThisParameterType<RequiredMethod>\n\t\t\t\ttype _check = ThisParam extends OneBuilder<S, TestA, never, [], false> ? true : false\n\t\t\t\tconst _: _check = true\n\t\t\t\tvoid _\n\t\t\t})\n\t\t})\n\n\t\tdescribe('runtime throw behaviour', () => {\n\t\t\ttest('.required().find() throws OrmNotFoundError when no row matched', async () => {\n\t\t\t\tawait expect(\n\t\t\t\t\trepo.on(UserSchema).one().required().id('nonexistent').find(),\n\t\t\t\t).rejects.toThrow(OrmNotFoundError)\n\t\t\t})\n\n\t\t\ttest('.required().update() throws OrmNotFoundError when no row matched', async () => {\n\t\t\t\tawait expect(\n\t\t\t\t\trepo.on(UserSchema).one().required().id('nonexistent').update({ name: 'X' }),\n\t\t\t\t).rejects.toThrow(OrmNotFoundError)\n\t\t\t})\n\n\t\t\ttest('.required().delete() throws OrmNotFoundError when no row matched', async () => {\n\t\t\t\tawait expect(\n\t\t\t\t\trepo.on(UserSchema).one().required().id('nonexistent').delete(),\n\t\t\t\t).rejects.toThrow(OrmNotFoundError)\n\t\t\t})\n\n\t\t\ttest('thrown error carries schema, operation, and where', async () => {\n\t\t\t\ttry {\n\t\t\t\t\tawait repo.on(UserSchema).one().required().id('u-abc').find()\n\t\t\t\t\texpect.unreachable('should have thrown')\n\t\t\t\t} catch (e) {\n\t\t\t\t\texpect(e).toBeInstanceOf(OrmNotFoundError)\n\t\t\t\t\tconst err = e as InstanceType<typeof OrmNotFoundError>\n\t\t\t\t\texpect(err.schema).toBe('users')\n\t\t\t\t\texpect(err.operation).toBe('findOne')\n\t\t\t\t\texpect(err.where).toBeInstanceOf(FilterGroup)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\ttest('update throws with operation updateOne', async () => {\n\t\t\t\ttry {\n\t\t\t\t\tawait repo.on(UserSchema).one().required().id('u-abc').update({ name: 'X' })\n\t\t\t\t\texpect.unreachable('should have thrown')\n\t\t\t\t} catch (e) {\n\t\t\t\t\texpect((e as any).operation).toBe('updateOne')\n\t\t\t\t}\n\t\t\t})\n\n\t\t\ttest('delete throws with operation deleteOne', async () => {\n\t\t\t\ttry {\n\t\t\t\t\tawait repo.on(UserSchema).one().required().id('u-abc').delete()\n\t\t\t\t\texpect.unreachable('should have thrown')\n\t\t\t\t} catch (e) {\n\t\t\t\t\texpect((e as any).operation).toBe('deleteOne')\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tdescribe('no throw when row exists', () => {\n\t\t\ttest('.required().find() returns the row when found', async () => {\n\t\t\t\tconst created = await repo.on(UserSchema).one().create({ email: 'a@b.com', name: 'A' })\n\t\t\t\tconst found = await repo.on(UserSchema).one().required().id(created.id).find()\n\t\t\t\texpect(found.name).toBe('A')\n\t\t\t})\n\n\t\t\ttest('.required().update() returns the updated row', async () => {\n\t\t\t\tconst created = await repo.on(UserSchema).one().create({ email: 'a@b.com', name: 'A' })\n\t\t\t\tconst updated = await repo.on(UserSchema).one().required().id(created.id).update({ name: 'B' })\n\t\t\t\texpect(updated.name).toBe('B')\n\t\t\t})\n\n\t\t\ttest('.required().delete() returns the deleted row', async () => {\n\t\t\t\tconst created = await repo.on(UserSchema).one().create({ email: 'a@b.com', name: 'A' })\n\t\t\t\tconst deleted = await repo.on(UserSchema).one().required().id(created.id).delete()\n\t\t\t\texpect(deleted.name).toBe('A')\n\t\t\t})\n\t\t})\n\n\t\tdescribe('custom message', () => {\n\t\t\ttest('.required(message) uses custom message on throw', async () => {\n\t\t\t\ttry {\n\t\t\t\t\tawait repo.on(UserSchema).one().required('user must exist').id('u-abc').find()\n\t\t\t\t\texpect.unreachable('should have thrown')\n\t\t\t\t} catch (e) {\n\t\t\t\t\texpect((e as any).message).toBe('user must exist')\n\t\t\t\t}\n\t\t\t})\n\n\t\t\ttest('default message for PK-keyed chain', async () => {\n\t\t\t\ttry {\n\t\t\t\t\tawait repo.on(UserSchema).one().required().id('u-abc').find()\n\t\t\t\t\texpect.unreachable('should have thrown')\n\t\t\t\t} catch (e) {\n\t\t\t\t\texpect((e as any).message).toBe('users.findOne: no row matched id=u-abc')\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tdescribe('default behaviour preserved', () => {\n\t\t\ttest('find() returns null without .required()', async () => {\n\t\t\t\tconst result = await repo.on(UserSchema).one().id('nonexistent').find()\n\t\t\t\texpect(result).toBeNull()\n\t\t\t})\n\n\t\t\ttest('update() returns null without .required()', async () => {\n\t\t\t\tconst result = await repo.on(UserSchema).one().id('nonexistent').update({ name: 'X' })\n\t\t\t\texpect(result).toBeNull()\n\t\t\t})\n\n\t\t\ttest('delete() returns null without .required()', async () => {\n\t\t\t\tconst result = await repo.on(UserSchema).one().id('nonexistent').delete()\n\t\t\t\texpect(result).toBeNull()\n\t\t\t})\n\t\t})\n\n\t\tdescribe('runtime no-op for create/upsert', () => {\n\t\t\ttest('.required() before create() returns the created row', async () => {\n\t\t\t\tconst created = await repo.on(UserSchema).one().required().create({ email: 'x@y.com', name: 'X' })\n\t\t\t\texpect(created.email).toBe('x@y.com')\n\t\t\t\texpect(created.name).toBe('X')\n\t\t\t})\n\n\t\t\ttest('.required() before upsert() returns the upserted row', async () => {\n\t\t\t\tconst upserted = await repo\n\t\t\t\t\t.on(UserSchema)\n\t\t\t\t\t.one()\n\t\t\t\t\t.required()\n\t\t\t\t\t.where((q: any) => q.eq('email', 'x@y.com'))\n\t\t\t\t\t.upsert({ create: { email: 'x@y.com', name: 'X' } })\n\t\t\t\texpect(upserted.email).toBe('x@y.com')\n\t\t\t\texpect(upserted.name).toBe('X')\n\t\t\t})\n\t\t})\n\t})\n\n\tdescribe('read query-shape validation and page state', () => {\n\t\tlet itemCounter = 0\n\t\tlet userCounter = 0\n\t\tlet postCounter = 0\n\n\t\tconst ItemSchema = Schema.from('paged_items')\n\t\t\t.pk('id', v.string(), () => `item-${++itemCounter}`)\n\t\t\t.field('position', v.number())\n\t\t\t.field('name', v.string())\n\t\t\t.build()\n\n\t\tconst UserSchema = Schema.from('query_shape_users')\n\t\t\t.pk('id', v.string(), () => `user-${++userCounter}`)\n\t\t\t.field('name', v.string())\n\t\t\t.build()\n\n\t\tconst PostSchema = Schema.from('query_shape_posts')\n\t\t\t.pk('id', v.string(), () => `post-${++postCounter}`)\n\t\t\t.field('userId', v.string())\n\t\t\t.field('title', v.string())\n\t\t\t.build()\n\n\t\tconst UserRels = Relations.from(UserSchema).hasMany('posts', PostSchema.fields.userId).build()\n\t\tconst PostRels = Relations.from(PostSchema).belongsTo('author', PostSchema.fields.userId, UserSchema).build()\n\n\t\tfunction makeRepo() {\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\treturn Repo.from(adapter).resolve((s) => ({ table: s.name })).build()\n\t\t}\n\n\t\tasync function seedItems(repo: any, count: number) {\n\t\t\tawait repo.on(ItemSchema).all().create(\n\t\t\t\tArray.from({ length: count }, (_, index) => ({ position: index + 1, name: `Item ${index + 1}` })),\n\t\t\t)\n\t\t}\n\n\t\tfunction expectQueryShapeError(error: unknown, field?: string) {\n\t\t\texpect(error).toBeInstanceOf(OrmValidationError)\n\t\t\tconst err = error as InstanceType<typeof OrmValidationError>\n\t\t\texpect(err.kind).toBe('query-shape')\n\t\t\tif (field) expect(err.failures.some((failure) => failure.field === field)).toBe(true)\n\t\t}\n\n\t\ttest('.page(n) is 1-based offset sugar for .find()', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait seedItems(repo, 10)\n\n\t\t\tconst rows = await repo.on(ItemSchema).all().orderBy('position', 'asc').limit(3).page(2).find()\n\n\t\t\texpect(rows.map((row) => row.position)).toEqual([4, 5, 6])\n\t\t})\n\n\t\ttest('.page(2).limit(50).find() uses the final effective limit for offset', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait seedItems(repo, 125)\n\n\t\t\tconst rows = await repo.on(ItemSchema).all().orderBy('position', 'asc').page(2).limit(50).find()\n\n\t\t\texpect(rows).toHaveLength(50)\n\t\t\texpect(rows[0].position).toBe(51)\n\t\t\texpect(rows.at(-1)?.position).toBe(100)\n\t\t})\n\n\t\ttest('duplicate .limit(...) calls keep last-wins behavior', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait seedItems(repo, 10)\n\n\t\t\tconst rows = await repo.on(ItemSchema).all().orderBy('position', 'asc').limit(7).limit(3).find()\n\n\t\t\texpect(rows.map((row) => row.position)).toEqual([1, 2, 3])\n\t\t})\n\n\t\ttest('.offset(...) and .page(...) are last-source-wins', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait seedItems(repo, 20)\n\n\t\t\tconst offsetWins = await repo.on(ItemSchema).all().orderBy('position', 'asc').limit(5).page(2).offset(10).find()\n\t\t\tconst pageWins = await repo.on(ItemSchema).all().orderBy('position', 'asc').limit(5).offset(10).page(2).find()\n\n\t\t\texpect(offsetWins.map((row) => row.position)).toEqual([11, 12, 13, 14, 15])\n\t\t\texpect(pageWins.map((row) => row.position)).toEqual([6, 7, 8, 9, 10])\n\t\t})\n\n\t\ttest('invalid page, limit, and offset values fail as query-shape errors at find()', async () => {\n\t\t\tconst repo = makeRepo()\n\n\t\t\tfor (const [field, read] of [\n\t\t\t\t['page', () => repo.on(ItemSchema).all().page(0 as any).find()],\n\t\t\t\t['limit', () => repo.on(ItemSchema).all().limit(0 as any).find()],\n\t\t\t\t['limit', () => repo.on(ItemSchema).all().limit(undefined as any).page(2).find()],\n\t\t\t\t['offset', () => repo.on(ItemSchema).all().offset(-1 as any).find()],\n\t\t\t] as const) {\n\t\t\t\ttry {\n\t\t\t\t\tawait read()\n\t\t\t\t\texpect.unreachable(`${field} should have failed`)\n\t\t\t\t} catch (error) {\n\t\t\t\t\texpectQueryShapeError(error, field)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\ttest('unknown selected fields fail as query-shape errors on find reads before rows are loaded', async () => {\n\t\t\tconst repo = makeRepo()\n\n\t\t\ttry {\n\t\t\t\tawait repo.on(ItemSchema).one().select(['unknownField' as any]).find()\n\t\t\t\texpect.unreachable('one().find() should have failed')\n\t\t\t} catch (error) {\n\t\t\t\texpectQueryShapeError(error, 'unknownField')\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tawait repo.on(ItemSchema).all().select(['unknownField' as any]).find()\n\t\t\t\texpect.unreachable('all().find() should have failed')\n\t\t\t} catch (error) {\n\t\t\t\texpectQueryShapeError(error, 'unknownField')\n\t\t\t}\n\t\t})\n\n\t\ttest('invalid and coherency-broken preload definitions fail as query-shape errors on find reads', async () => {\n\t\t\tconst repo = makeRepo()\n\n\t\t\tfor (const read of [\n\t\t\t\t() => repo.on(UserSchema).all().preload([{ def: {} as any }]).find(),\n\t\t\t\t() => repo.on(UserSchema).all().preload([PostRels.author]).find(),\n\t\t\t\t() => repo.on(UserSchema).all().preload([{ def: UserRels.posts, preloads: [UserRels.posts] }]).find(),\n\t\t\t]) {\n\t\t\t\ttry {\n\t\t\t\t\tawait read()\n\t\t\t\t\texpect.unreachable('preload should have failed')\n\t\t\t\t} catch (error) {\n\t\t\t\t\texpectQueryShapeError(error)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\n\t\ttest('orderBy(string) remains an unvalidated raw-string escape hatch', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait seedItems(repo, 2)\n\n\t\t\tconst rows = await repo.on(ItemSchema).all().orderBy('missingRawSortField', 'asc').find()\n\n\t\t\texpect(rows).toHaveLength(2)\n\t\t})\n\n\t\ttest('page-based reads without an initialized Instance use default limit 100', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait seedItems(repo, 250)\n\n\t\t\tconst rows = await repo.on(ItemSchema).all().orderBy('position', 'asc').page(2).find()\n\n\t\t\texpect(rows).toHaveLength(100)\n\t\t\texpect(rows[0].position).toBe(101)\n\t\t\texpect(rows.at(-1)?.position).toBe(200)\n\t\t})\n\n\t\ttest('find() without page, offset, or limit keeps existing flat-array behavior', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait seedItems(repo, 12)\n\n\t\t\tconst rows = await repo.on(ItemSchema).all().orderBy('position', 'asc').find()\n\n\t\t\texpect(rows).toHaveLength(12)\n\t\t\texpect(rows.map((row) => row.position)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])\n\t\t})\n\n\t\ttest('.paginate() returns a first-page envelope with docs computed from filter-only count', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait seedItems(repo, 5)\n\n\t\t\tconst page = await repo.on(ItemSchema).all().orderBy('position', 'asc').limit(2).paginate()\n\n\t\t\texpect(page.items.map((row) => row.position)).toEqual([1, 2])\n\t\t\texpect(page.docs).toEqual({ limit: 2, total: 5, count: 2 })\n\t\t\texpect(page.pages).toEqual({ current: 1, start: 1, last: 3, previous: null, next: 2 })\n\t\t})\n\n\t\ttest('.paginate() reports pages.last = 1 for empty result sets', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait seedItems(repo, 3)\n\n\t\t\tconst page = await repo.on(ItemSchema).all().where((q) => q.eq('name', 'missing')).limit(5).paginate()\n\n\t\t\texpect(page.items).toEqual([])\n\t\t\texpect(page.docs).toEqual({ limit: 5, total: 0, count: 0 })\n\t\t\texpect(page.pages).toEqual({ current: 1, start: 1, last: 1, previous: null, next: null })\n\t\t})\n\n\t\ttest('.paginate() preserves the previous neighbor on the last real page', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait seedItems(repo, 5)\n\n\t\t\tconst page = await repo.on(ItemSchema).all().orderBy('position', 'asc').limit(2).page(3).paginate()\n\n\t\t\texpect(page.items.map((row) => row.position)).toEqual([5])\n\t\t\texpect(page.docs).toEqual({ limit: 2, total: 5, count: 1 })\n\t\t\texpect(page.pages).toEqual({ current: 3, start: 1, last: 3, previous: 2, next: null })\n\t\t})\n\n\t\ttest('.paginate() preserves past-last page requests with no navigation neighbors', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait seedItems(repo, 5)\n\n\t\t\tconst page = await repo.on(ItemSchema).all().orderBy('position', 'asc').limit(2).page(4).paginate()\n\n\t\t\texpect(page.items).toEqual([])\n\t\t\texpect(page.docs).toEqual({ limit: 2, total: 5, count: 0 })\n\t\t\texpect(page.pages).toEqual({ current: 4, start: 1, last: 3, previous: null, next: null })\n\t\t})\n\n\t\ttest('.paginate() does not require orderBy', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait seedItems(repo, 3)\n\n\t\t\tconst page = await repo.on(ItemSchema).all().limit(2).paginate()\n\n\t\t\texpect(page.items).toHaveLength(2)\n\t\t\texpect(page.docs).toEqual({ limit: 2, total: 3, count: 2 })\n\t\t\texpect(page.pages).toEqual({ current: 1, start: 1, last: 2, previous: null, next: 2 })\n\t\t})\n\n\t\ttest('.paginate() without an explicit limit uses the safe default page limit without Instance.get()', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait seedItems(repo, 101)\n\t\t\tconst getSpy = vi.spyOn(Instance, 'get').mockImplementation(() => {\n\t\t\t\tthrow new Error('Instance.get() should not be required for pagination defaults')\n\t\t\t})\n\n\t\t\ttry {\n\t\t\t\tconst page = await repo.on(ItemSchema).all().orderBy('position', 'asc').paginate()\n\n\t\t\t\texpect(page.items).toHaveLength(100)\n\t\t\t\texpect(page.docs).toEqual({ limit: 100, total: 101, count: 100 })\n\t\t\t\texpect(page.pages).toEqual({ current: 1, start: 1, last: 2, previous: null, next: 2 })\n\t\t\t} finally {\n\t\t\t\tgetSpy.mockRestore()\n\t\t\t}\n\t\t})\n\n\t\ttest('.paginate() counts only the filter while page items honor limit and offset', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait repo.on(ItemSchema).all().create([\n\t\t\t\t{ position: 1, name: 'keep' },\n\t\t\t\t{ position: 2, name: 'drop' },\n\t\t\t\t{ position: 3, name: 'keep' },\n\t\t\t\t{ position: 4, name: 'keep' },\n\t\t\t])\n\n\t\t\tconst page = await repo\n\t\t\t\t.on(ItemSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.eq('name', 'keep'))\n\t\t\t\t.orderBy('position', 'asc')\n\t\t\t\t.limit(1)\n\t\t\t\t.page(2)\n\t\t\t\t.paginate()\n\n\t\t\texpect(page.items.map((row) => row.position)).toEqual([3])\n\t\t\texpect(page.docs).toEqual({ limit: 1, total: 3, count: 1 })\n\t\t\texpect(page.pages).toEqual({ current: 2, start: 1, last: 3, previous: 1, next: 3 })\n\t\t})\n\n\t\ttest('.paginate() starts item and count reads in parallel after query-shape validation and config resolution', async () => {\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst origUse = adapter.use.bind(adapter)\n\t\t\tconst events: string[] = []\n\t\t\t;(adapter as any).use = vi.fn((schema: AnySchema, config: any) => {\n\t\t\t\tconst use = origUse(schema, config)\n\t\t\t\treturn {\n\t\t\t\t\t...use,\n\t\t\t\t\tfindMany: async (...args: any[]) => {\n\t\t\t\t\t\tevents.push('findMany:start')\n\t\t\t\t\t\tawait Promise.resolve()\n\t\t\t\t\t\tevents.push('findMany:end')\n\t\t\t\t\t\treturn use.findMany(...(args as [any, any]))\n\t\t\t\t\t},\n\t\t\t\t\tcount: async (...args: any[]) => {\n\t\t\t\t\t\tevents.push('count:start')\n\t\t\t\t\t\tawait Promise.resolve()\n\t\t\t\t\t\tevents.push('count:end')\n\t\t\t\t\t\treturn use.count(...(args as [any]))\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t})\n\t\t\tconst repo = Repo.from(adapter)\n\t\t\t\t.resolve((s) => {\n\t\t\t\t\tevents.push('resolve')\n\t\t\t\t\treturn { table: s.name }\n\t\t\t\t})\n\t\t\t\t.build()\n\t\t\tawait seedItems(repo, 3)\n\t\t\tevents.length = 0\n\n\t\t\tconst page = await repo.on(ItemSchema).all().where((q) => q.eq('name', 'Item 1')).limit(1).paginate()\n\n\t\t\texpect(events[0]).toBe('resolve')\n\t\t\texpect(events).toContain('count:start')\n\t\t\texpect(events).toContain('findMany:end')\n\t\t\texpect(events.indexOf('count:start')).toBeLessThan(events.indexOf('findMany:end'))\n\t\t\texpect(page.items.map((row) => row.position)).toEqual([1])\n\t\t\texpect(page.docs).toEqual({ limit: 1, total: 1, count: 1 })\n\t\t})\n\n\t\ttest('.paginate() items preserve selected row shape', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait seedItems(repo, 2)\n\n\t\t\tconst page = await repo.on(ItemSchema).all().orderBy('position', 'asc').select(['name']).limit(1).paginate()\n\n\t\t\texpect(page.items).toEqual([{ name: 'Item 1' }])\n\t\t\texpect(page.docs).toMatchObject({ total: 2, count: 1 })\n\t\t})\n\n\t\ttest('.paginate() items preserve preloaded row shape', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst alice = await repo.on(UserSchema).one().create({ name: 'Alice' })\n\t\t\tconst bob = await repo.on(UserSchema).one().create({ name: 'Bob' })\n\t\t\tawait repo.on(PostSchema).all().create([\n\t\t\t\t{ userId: alice.id, title: 'A1' },\n\t\t\t\t{ userId: alice.id, title: 'A2' },\n\t\t\t\t{ userId: bob.id, title: 'B1' },\n\t\t\t])\n\n\t\t\tconst page = await repo.on(UserSchema).all().orderBy('name', 'asc').preload([UserRels.posts]).limit(1).paginate()\n\n\t\t\texpect(page.items).toHaveLength(1)\n\t\t\texpect(page.items[0].name).toBe('Alice')\n\t\t\texpect(page.items[0].posts.map((post: any) => post.title)).toEqual(['A1', 'A2'])\n\t\t\texpect(page.docs).toEqual({ limit: 1, total: 2, count: 1 })\n\t\t})\n\n\t\ttest('.paginate() uses offset-source last-wins and the final effective limit', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait seedItems(repo, 10)\n\n\t\t\tconst pageSource = await repo.on(ItemSchema).all().orderBy('position', 'asc').page(2).limit(3).paginate()\n\t\t\tconst offsetSource = await repo.on(ItemSchema).all().orderBy('position', 'asc').page(2).limit(2).offset(4).paginate()\n\n\t\t\texpect(pageSource.items.map((row) => row.position)).toEqual([4, 5, 6])\n\t\t\texpect(pageSource.pages.current).toBe(2)\n\t\t\texpect(pageSource.docs.limit).toBe(3)\n\t\t\texpect(offsetSource.items.map((row) => row.position)).toEqual([5, 6])\n\t\t\texpect(offsetSource.pages.current).toBe(3)\n\t\t\texpect(offsetSource.docs.limit).toBe(2)\n\t\t})\n\t})\n\n\tdescribe('type-level: paginate surface', () => {\n\t\tconst _TestSchema = Schema.from('paginate_type_users')\n\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t.field('name', v.string())\n\t\t\t.build()\n\t\ttype S = typeof _TestSchema\n\n\t\ttest('Paginated<T> exposes pages, docs, and items', () => {\n\t\t\ttype Page = Paginated<{ id: string }>\n\t\t\texpectTypeOf<Page>().toHaveProperty('pages')\n\t\t\texpectTypeOf<Page>().toHaveProperty('docs')\n\t\t\texpectTypeOf<Page>().toHaveProperty('items')\n\t\t\texpectTypeOf<Page['items']>().toEqualTypeOf<{ id: string }[]>()\n\t\t})\n\n\t\ttest('.paginate() is a no-argument AllBuilder terminal', () => {\n\t\t\ttype Adapter = { findMany: (...a: any[]) => any; count: (...a: any[]) => any }\n\t\t\ttype All = AllBuilderSurface<S, Adapter>\n\t\t\ttype Result = import('./internals/types').SelectedWithPreloads<S, never, []>\n\n\t\t\texpectTypeOf<All['paginate']>().toBeFunction()\n\t\t\texpectTypeOf<Parameters<All['paginate']>>().toEqualTypeOf<[]>()\n\t\t\texpectTypeOf<ReturnType<All['paginate']>>().toEqualTypeOf<Promise<Paginated<Result>>>()\n\t\t})\n\n\t\ttest('.paginate() exists only on AllBuilder', () => {\n\t\t\ttype Adapter = { findMany: (...a: any[]) => any; count: (...a: any[]) => any }\n\t\t\ttype One = OneBuilderSurface<S, Adapter>\n\t\t\ttype OneHasPaginate = 'paginate' extends keyof One ? true : false\n\n\t\t\texpectTypeOf<OneHasPaginate>().toEqualTypeOf<false>()\n\t\t})\n\n\t\ttest('.paginate() is gated on both findMany and count', () => {\n\t\t\tclass FullAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\treadonly queryableOps = ['eq'] as const\n\t\t\t\tasync findMany() { return [] }\n\t\t\t\tasync count() { return 0 }\n\t\t\t}\n\t\t\tclass CountOnlyAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\treadonly queryableOps = ['eq'] as const\n\t\t\t\tasync count() { return 0 }\n\t\t\t}\n\t\t\tclass FindOnlyAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\treadonly queryableOps = ['eq'] as const\n\t\t\t\tasync findMany() { return [] }\n\t\t\t}\n\t\t\ttype Full = AllBuilderSurface<S, FullAdapter>\n\t\t\ttype MissingFindMany = AllBuilderSurface<S, CountOnlyAdapter>\n\t\t\ttype MissingCount = AllBuilderSurface<S, FindOnlyAdapter>\n\n\t\t\texpectTypeOf<Full['paginate']>().toBeFunction()\n\t\t\texpectTypeOf<MissingFindMany['paginate']>().toBeNever()\n\t\t\texpectTypeOf<MissingCount['paginate']>().toBeNever()\n\t\t})\n\t})\n}\n","import { AsyncLocalStorage } from 'node:async_hooks'\n\nimport type { AnySchema } from '../schema'\n\nexport type ConfigTransform<C> = (config: C, schema: AnySchema) => C\n\nconst store = new AsyncLocalStorage<ConfigTransform<any>[]>()\n\nexport function run<C, T>(transform: ConfigTransform<C>, fn: () => T): T {\n\tconst current = store.getStore() ?? []\n\treturn store.run([...current, transform], fn)\n}\n\nexport function currentTransforms<C>(): ConfigTransform<C>[] {\n\treturn store.getStore() ?? []\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect } = import.meta.vitest\n\n\ttype TestConfig = { prefix: string }\n\tconst dummySchema = { name: 'test' } as AnySchema\n\n\tfunction applyTransforms(base: TestConfig): TestConfig {\n\t\tlet config = base\n\t\tfor (const t of currentTransforms<TestConfig>()) {\n\t\t\tconfig = t(config, dummySchema)\n\t\t}\n\t\treturn config\n\t}\n\n\tdescribe('ALS runtime', () => {\n\t\ttest('reads outside run() see no transforms (default config)', () => {\n\t\t\tconst transforms = currentTransforms<TestConfig>()\n\t\t\texpect(transforms).toEqual([])\n\t\t\texpect(applyTransforms({ prefix: 'base' })).toEqual({ prefix: 'base' })\n\t\t})\n\n\t\ttest('reads inside run() see the override', () => {\n\t\t\tconst result = run<TestConfig, TestConfig>(\n\t\t\t\t(config) => ({ prefix: `scoped_${config.prefix}` }),\n\t\t\t\t() => applyTransforms({ prefix: 'base' }),\n\t\t\t)\n\t\t\texpect(result).toEqual({ prefix: 'scoped_base' })\n\t\t})\n\n\t\ttest('reads after run() returns see no transforms', () => {\n\t\t\trun<TestConfig, void>(\n\t\t\t\t(config) => ({ prefix: `inner_${config.prefix}` }),\n\t\t\t\t() => {},\n\t\t\t)\n\t\t\texpect(applyTransforms({ prefix: 'base' })).toEqual({ prefix: 'base' })\n\t\t})\n\n\t\ttest('nested run() calls compose — inner derives from outer', () => {\n\t\t\tconst result = run<TestConfig, TestConfig>(\n\t\t\t\t(config) => ({ prefix: `a_${config.prefix}` }),\n\t\t\t\t() =>\n\t\t\t\t\trun<TestConfig, TestConfig>(\n\t\t\t\t\t\t(config) => ({ prefix: `b_${config.prefix}` }),\n\t\t\t\t\t\t() => applyTransforms({ prefix: 'base' }),\n\t\t\t\t\t),\n\t\t\t)\n\t\t\texpect(result).toEqual({ prefix: 'b_a_base' })\n\t\t})\n\n\t\ttest('two parallel run() calls do not bleed into each other', async () => {\n\t\t\tconst results = await Promise.all([\n\t\t\t\trun<TestConfig, Promise<TestConfig>>(\n\t\t\t\t\t(config) => ({ prefix: `tenant1_${config.prefix}` }),\n\t\t\t\t\tasync () => {\n\t\t\t\t\t\tawait new Promise((r) => setTimeout(r, 10))\n\t\t\t\t\t\treturn applyTransforms({ prefix: 'base' })\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t\trun<TestConfig, Promise<TestConfig>>(\n\t\t\t\t\t(config) => ({ prefix: `tenant2_${config.prefix}` }),\n\t\t\t\t\tasync () => {\n\t\t\t\t\t\tawait new Promise((r) => setTimeout(r, 10))\n\t\t\t\t\t\treturn applyTransforms({ prefix: 'base' })\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t])\n\n\t\t\texpect(results[0]).toEqual({ prefix: 'tenant1_base' })\n\t\t\texpect(results[1]).toEqual({ prefix: 'tenant2_base' })\n\t\t})\n\n\t\ttest('overrides survive across awaits inside fn', async () => {\n\t\t\tconst result = await run<TestConfig, Promise<TestConfig>>(\n\t\t\t\t(config) => ({ prefix: `async_${config.prefix}` }),\n\t\t\t\tasync () => {\n\t\t\t\t\tawait new Promise((r) => setTimeout(r, 5))\n\t\t\t\t\tconst mid = applyTransforms({ prefix: 'step1' })\n\t\t\t\t\tawait new Promise((r) => setTimeout(r, 5))\n\t\t\t\t\tconst end = applyTransforms({ prefix: 'step2' })\n\t\t\t\t\treturn { prefix: `${mid.prefix}+${end.prefix}` }\n\t\t\t\t},\n\t\t\t)\n\t\t\texpect(result).toEqual({ prefix: 'async_step1+async_step2' })\n\t\t})\n\t})\n}\n","import type { Pipe } from 'valleyed'\n\nimport type { InferAdapterConfig } from '../adapter'\nimport { currentTransforms, run } from './als'\nimport { SchemaContext, SchemaRef, type HasMethod, type SchemaRefSurface } from './builders'\nimport type { OrmAdapterConfig, OrmAdapterLike } from '../adapters/base'\nimport type { IterationOptions } from '../query-options'\nimport type { AnySchema } from '../schema'\nimport { composeSchemaConfig } from '../schema-validations'\n\nexport type { ConfigTransform } from './als'\n\nexport class Repo<A extends OrmAdapterLike<any>> {\n\treadonly #adapter: A\n\treadonly #defaults: (schema: AnySchema) => InferAdapterConfig<A>\n\treadonly #schemaConfigPipe: Pipe<any, any> | undefined\n\n\tconstructor({ adapter, resolve }: { adapter: A; resolve: (schema: AnySchema) => InferAdapterConfig<A> }) {\n\t\tthis.#adapter = adapter\n\t\tthis.#defaults = resolve\n\t\tthis.#schemaConfigPipe = (adapter as any).schemaConfigPipe\n\t}\n\n\t#getConfig(s: AnySchema): InferAdapterConfig<A> {\n\t\tconst transforms = [...currentTransforms<InferAdapterConfig<A>>()]\n\t\tif (this.#schemaConfigPipe) {\n\t\t\treturn composeSchemaConfig(this.#defaults, transforms, s, this.#schemaConfigPipe) as InferAdapterConfig<A>\n\t\t}\n\t\tlet config = this.#defaults(s)\n\t\tfor (const transform of transforms) {\n\t\t\tconfig = transform(config, s)\n\t\t}\n\t\treturn config\n\t}\n\n\t#getUse(s: AnySchema) {\n\t\treturn this.#adapter.use(s, this.#getConfig(s))\n\t}\n\n\ton<S extends AnySchema>(schema: S): SchemaRefSurface<S, A> {\n\t\treturn new SchemaRef<S, A>(new SchemaContext(schema, (target) => this.#getUse(target))) as unknown as SchemaRefSurface<S, A>\n\t}\n\n\tstatic from<NewA extends OrmAdapterLike<any>>(adapter: NewA): RepoBuilder<NewA> {\n\t\treturn new RepoBuilder<NewA>(adapter)\n\t}\n\n\tasync session<T>(fn: () => Promise<T>): Promise<T> {\n\t\treturn this.#adapter.session?.(fn) ?? fn()\n\t}\n\n\tresolve<T>(resolver: (config: InferAdapterConfig<A>, schema: AnySchema) => InferAdapterConfig<A>, fn: () => T): T {\n\t\treturn run<InferAdapterConfig<A>, T>(resolver, fn)\n\t}\n}\n\nclass RepoBuilder<A extends OrmAdapterLike<any>> {\n\t#adapter: unknown\n\t#resolve: unknown\n\n\tconstructor(adapter?: unknown, resolve?: unknown) {\n\t\tthis.#adapter = adapter\n\t\tthis.#resolve = resolve\n\t}\n\n\tresolve(fn: (schema: AnySchema) => OrmAdapterConfig<A>): RepoBuilder<A> {\n\t\treturn new RepoBuilder<A>(this.#adapter, fn)\n\t}\n\n\tbuild(this: RepoBuilder<A>): RepoSurface<A> {\n\t\treturn new Repo<A>({\n\t\t\tadapter: this.#adapter as any,\n\t\t\tresolve: this.#resolve as any,\n\t\t}) as RepoSurface<A>\n\t}\n}\n\nexport type RepoSurface<A extends OrmAdapterLike<any>> = Repo<A> &\n\t(HasMethod<A, 'session'> extends true ? {} : { session: never })\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect, expectTypeOf, vi } = import.meta.vitest\n\tconst { v } = await import('valleyed')\n\tconst { InMemoryAdapter } = await import('../adapters/in-memory')\n\tconst { OrmAdapter } = await import('../orm-adapter')\n\tconst { OrmValidationError } = await import('../errors')\n\tconst { Instance } = await import('../../instance')\n\tconst { Relations } = await import('../relations')\n\tconst { Schema } = await import('../schema')\n\n\tfunction mockInstance() {\n\t\treturn vi.spyOn(Instance, 'on').mockImplementation(() => {})\n\t}\n\n\tdescribe('Repo.from() and repo.on()', () => {\n\t\ttest('Repo.from(adapter).resolve(...).build() creates a working repo', async () => {\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst TestSchema = Schema.from('test')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\t\t\tconst repo = Repo.from(adapter)\n\t\t\t\t.resolve((s) => ({ table: s.name }))\n\t\t\t\t.build()\n\t\t\tconst created = await repo.on(TestSchema).one().create({ name: 'Hello' })\n\t\t\texpect(created.name).toBe('Hello')\n\t\t\tconst found = await repo.on(TestSchema).one().id(created.id).find()\n\t\t\texpect(found?.name).toBe('Hello')\n\t\t})\n\n\t\ttest('repo.on(schema) returns a SchemaRef', async () => {\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst TestSchema = Schema.from('test')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\t\t\tconst repo = Repo.from(adapter)\n\t\t\t\t.resolve((s) => ({ table: s.name }))\n\t\t\t\t.build()\n\t\t\tconst ref = repo.on(TestSchema)\n\t\t\texpect(ref).toBeInstanceOf(SchemaRef)\n\t\t})\n\t})\n\n\tdescribe('clone-on-step: RepoBuilder fan-out independence', () => {\n\t\ttest('.resolve() returns a new builder, not the same instance', () => {\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst base = Repo.from(adapter)\n\t\t\tconst a = base.resolve((s) => ({ table: s.name }))\n\t\t\texpect(a).not.toBe(base)\n\t\t})\n\t})\n\n\tdescribe('repo/Repo core behavior', () => {\n\t\tlet userCounter = 0\n\t\tlet postCounter = 0\n\t\tlet profileCounter = 0\n\t\tlet orgCounter = 0\n\n\t\tconst UserSchema = Schema.from('users')\n\t\t\t.pk('id', v.string(), () => `u${++userCounter}`)\n\t\t\t.field('email', v.string())\n\t\t\t.field('name', v.string())\n\t\t\t.field('orgId', v.optional(v.string()), { onCreate: () => undefined })\n\t\t\t.field('createdAt', v.number(), { onCreate: () => 1000 })\n\t\t\t.build()\n\n\t\tconst PostSchema = Schema.from('posts')\n\t\t\t.pk('id', v.string(), () => `p${++postCounter}`)\n\t\t\t.field('title', v.string())\n\t\t\t.field('userId', v.string())\n\t\t\t.build()\n\n\t\tconst ProfileSchema = Schema.from('profiles')\n\t\t\t.pk('id', v.string(), () => `pr${++profileCounter}`)\n\t\t\t.field('bio', v.string())\n\t\t\t.field('userId', v.string())\n\t\t\t.build()\n\n\t\tconst OrgSchema = Schema.from('orgs')\n\t\t\t.pk('id', v.string(), () => `o${++orgCounter}`)\n\t\t\t.field('name', v.string())\n\t\t\t.build()\n\n\t\tconst PersonSchema = Schema.from('people')\n\t\t\t.pk('id', v.string(), () => `person-${++userCounter}`)\n\t\t\t.field('firstName', v.string())\n\t\t\t.field('lastName', v.string())\n\t\t\t.computed('fullName', ['firstName', 'lastName'], v.string(), ({ firstName, lastName }) => `${firstName} ${lastName}`)\n\t\t\t.build()\n\n\t\tconst UserRels = Relations.from(UserSchema)\n\t\t\t.hasMany('posts', PostSchema.fields.userId)\n\t\t\t.hasOne('profile', ProfileSchema.fields.userId)\n\t\t\t.belongsTo('org', UserSchema.fields.orgId, OrgSchema)\n\t\t\t.build()\n\n\t\tfunction makeRepo() {\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\treturn Repo.from(adapter)\n\t\t\t\t.resolve((s) => ({ table: s.name }))\n\t\t\t\t.build()\n\t\t}\n\n\t\ttest('fluent builders support one/all read chains', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst created = await repo.on(UserSchema).one().create({ email: 'fluent@test.com', name: 'Fluent User' })\n\n\t\t\tconst one = await repo.on(UserSchema).one().id(created.id).select(['id', 'name']).find()\n\t\t\texpect(one).toEqual({ id: created.id, name: 'Fluent User' })\n\n\t\t\tconst all = await repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.eq('id', created.id))\n\t\t\t\t.orderBy('createdAt', 'desc')\n\t\t\t\t.limit(1)\n\t\t\t\t.select(['id'])\n\t\t\t\t.find()\n\n\t\t\texpect(all).toEqual([{ id: created.id }])\n\t\t})\n\n\t\ttest('all().count() returns filtered counts and ignores query-shape state', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.all()\n\t\t\t\t.create([\n\t\t\t\t\t{ email: 'alice@count.com', name: 'Alice' },\n\t\t\t\t\t{ email: 'bob@count.com', name: 'Bob' },\n\t\t\t\t\t{ email: 'alice2@count.com', name: 'Alice' },\n\t\t\t\t])\n\n\t\t\tconst count = await repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.eq('name', 'Alice'))\n\t\t\t\t.select(['does_not_exist'] as any)\n\t\t\t\t.preload(['does_not_exist'] as any)\n\t\t\t\t.orderBy('does_not_exist')\n\t\t\t\t.limit(0)\n\t\t\t\t.page(0)\n\t\t\t\t.count()\n\n\t\t\texpect(count).toBe(2)\n\t\t})\n\n\t\ttest('all().count() validates only the filter', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait repo.on(UserSchema).one().create({ email: 'bad-filter-count@test.com', name: 'Alice' })\n\n\t\t\tawait expect(\n\t\t\t\trepo\n\t\t\t\t\t.on(UserSchema)\n\t\t\t\t\t.all()\n\t\t\t\t\t.where((q) => q.eq('does_not_exist' as any, 'x'))\n\t\t\t\t\t.select(['also_missing'] as any)\n\t\t\t\t\t.preload(['also_missing'] as any)\n\t\t\t\t\t.limit(0)\n\t\t\t\t\t.count(),\n\t\t\t).rejects.toBeInstanceOf(OrmValidationError)\n\t\t})\n\n\t\ttest('all().count() is distinct from aggregate count', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.all()\n\t\t\t\t.create([\n\t\t\t\t\t{ email: 'one@agg-count.com', name: 'Same' },\n\t\t\t\t\t{ email: 'two@agg-count.com', name: 'Same' },\n\t\t\t\t])\n\n\t\t\tconst count = await repo.on(UserSchema).all().where((q) => q.eq('name', 'Same')).count()\n\t\t\tconst aggregate = await repo.on(UserSchema).aggregate().count('total').where((q) => q.eq('name', 'Same')).run()\n\n\t\t\texpect(count).toBe(2)\n\t\t\texpect(aggregate).toEqual({ total: 2 })\n\t\t})\n\n\t\ttest('builder snapshots are immutable across chain branches', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.all()\n\t\t\t\t.create([\n\t\t\t\t\t{ email: 'alice@branch.com', name: 'Alice' },\n\t\t\t\t\t{ email: 'bob@branch.com', name: 'Bob' },\n\t\t\t\t])\n\n\t\t\tconst base = repo.on(UserSchema).all()\n\t\t\tconst branchA = base.where((q) => q.eq('name', 'Alice')).select(['id'])\n\t\t\tconst branchB = base.where((q) => q.eq('name', 'Bob')).select(['name'])\n\n\t\t\tconst rowsA = await branchA.find()\n\t\t\tconst rowsB = await branchB.find()\n\n\t\t\texpect(rowsA).toHaveLength(1)\n\t\t\texpect(rowsB).toHaveLength(1)\n\t\t\texpect(rowsA.every((r) => 'id' in r && !('name' in r))).toBe(true)\n\t\t\texpect(rowsB).toEqual([{ name: 'Bob' }])\n\t\t})\n\n\t\ttest('fluent builders support write chains with preloads', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst org = await repo.on(OrgSchema).one().create({ name: 'Fluent Org' })\n\n\t\t\tconst user = await repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.one()\n\t\t\t\t.preload([UserRels.org])\n\t\t\t\t.create({ email: 'writer@test.com', name: 'Writer', orgId: org.id })\n\n\t\t\texpect((user.org as any).name).toBe('Fluent Org')\n\n\t\t\tconst updated = await repo.on(UserSchema).one().id(user.id).select(['id', 'name']).update({ name: 'Updated Writer' })\n\n\t\t\texpect(updated).toEqual({ id: user.id, name: 'Updated Writer' })\n\n\t\t\tconst deleted = await repo.on(UserSchema).one().id(user.id).select(['id']).delete()\n\t\t\texpect(deleted).toEqual({ id: user.id })\n\t\t})\n\n\t\ttest('session supports multi-operation writes with fluent builders', async () => {\n\t\t\tconst repo = makeRepo()\n\n\t\t\tconst insertedId = await repo.session(async () => {\n\t\t\t\tconst created = await repo.on(UserSchema).one().create({ email: 'tx@fluent.com', name: 'Tx Fluent' })\n\t\t\t\tawait repo.on(UserSchema).one().id(created.id).update({ name: 'Tx Fluent Updated' })\n\t\t\t\treturn created.id\n\t\t\t})\n\n\t\t\tconst persisted = await repo.on(UserSchema).one().id(insertedId).select(['name']).find()\n\t\t\texpect(persisted).toEqual({ name: 'Tx Fluent Updated' })\n\t\t})\n\n\t\ttest('create/find/update/delete flows work', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst user = await repo.on(UserSchema).one().create({ email: 'a@b.com', name: 'Alice' })\n\t\t\texpect(user.id).toMatch(/^u\\d+$/)\n\t\t\texpect(user.createdAt).toBe(1000)\n\n\t\t\tconst found = await repo.on(UserSchema).one().id(user.id).find()\n\t\t\texpect(found?.id).toBe(user.id)\n\n\t\t\tconst updated = await repo.on(UserSchema).one().id(user.id).update({ name: 'Updated' })\n\t\t\texpect(updated?.name).toBe('Updated')\n\n\t\t\tconst deleted = await repo.on(UserSchema).one().id(user.id).delete()\n\t\t\texpect(deleted?.id).toBe(user.id)\n\t\t})\n\n\t\ttest('findById, updateById, and deleteById target the schema primary key', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst user = await repo.on(UserSchema).one().create({ email: 'id@test.com', name: 'ById' })\n\n\t\t\tconst found = await repo.on(UserSchema).one().id(user.id).find()\n\t\t\texpect(found?.id).toBe(user.id)\n\n\t\t\tconst updated = await repo.on(UserSchema).one().id(user.id).update({ name: 'Changed' })\n\t\t\texpect(updated?.name).toBe('Changed')\n\n\t\t\tconst deleted = await repo.on(UserSchema).one().id(user.id).delete()\n\t\t\texpect(deleted?.id).toBe(user.id)\n\t\t\texpect(await repo.on(UserSchema).one().id(user.id).find()).toBeNull()\n\t\t})\n\n\t\ttest('createMany, findMany and upsertOne work', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.all()\n\t\t\t\t.create([\n\t\t\t\t\t{ email: 'a@b.com', name: 'Alice' },\n\t\t\t\t\t{ email: 'b@c.com', name: 'Bob' },\n\t\t\t\t])\n\t\t\texpect(await repo.on(UserSchema).all().find()).toHaveLength(2)\n\n\t\t\tconst inserted = await repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.one()\n\t\t\t\t.where((q) => q.eq('id', 'u-fixed'))\n\t\t\t\t.upsert({ create: { email: 'new@test.com', name: 'New' } })\n\t\t\texpect(inserted.name).toBe('New')\n\t\t})\n\n\t\ttest('accepts chainable where input for filters and options', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.all()\n\t\t\t\t.create([\n\t\t\t\t\t{ email: 'a@b.com', name: 'Alice' },\n\t\t\t\t\t{ email: 'b@c.com', name: 'Bob' },\n\t\t\t\t])\n\n\t\t\tconst rows = await repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.or([(g) => g.eq('name', 'Alice'), (g) => g.eq('name', 'Bob')]))\n\t\t\t\t.orderBy('name', 'desc')\n\t\t\t\t.limit(1)\n\t\t\t\t.find()\n\n\t\t\texpect(rows).toHaveLength(1)\n\t\t\texpect(rows[0].name).toBe('Bob')\n\t\t})\n\n\t\ttest('all().iterate uses adapter iterateMany without findMany emulation', async () => {\n\t\t\tconst TestSchema = Schema.from('iter_repo_users')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.field('age', v.number())\n\t\t\t\t.build()\n\t\t\tlet findManyCalls = 0\n\t\t\tlet capturedOptions: unknown\n\n\t\t\tclass IterAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({ table: v.string() })\n\t\t\t\treadonly supportedFieldTypes = ['string', 'number'] as const\n\t\t\t\treadonly queryableOps = ['gt'] as const\n\t\t\t\tasync findMany(): Promise<Record<string, unknown>[]> {\n\t\t\t\t\tfindManyCalls += 1\n\t\t\t\t\tthrow new Error('findMany should not be called')\n\t\t\t\t}\n\t\t\t\tasync *iterateMany(_s: AnySchema, _c: unknown, _f: unknown, options?: unknown) {\n\t\t\t\t\tcapturedOptions = options\n\t\t\t\t\tyield { id: 'u3', name: 'Carol', age: 40 }\n\t\t\t\t\tyield { id: 'u1', name: 'Alice', age: 30 }\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst adapter = new (IterAdapter as any)() as IterAdapter\n\t\t\tconst repo = Repo.from(adapter).resolve((s) => ({ table: s.name })).build()\n\t\t\tconst rows: Array<{ id: string; name: string; age: number }> = []\n\t\t\tfor await (const row of repo.on(TestSchema).all().where((q) => q.gt('age', 19)).orderBy('age', 'desc').offset(1).limit(2).iterate()) {\n\t\t\t\trows.push(row)\n\t\t\t}\n\n\t\t\texpect(findManyCalls).toBe(0)\n\t\t\texpect(capturedOptions).toMatchObject({ orderBy: [{ field: 'age', direction: 'desc' }], offset: 1, limit: 2 })\n\t\t\texpect(rows.map((row) => row.name)).toEqual(['Carol', 'Alice'])\n\t\t})\n\n\t\ttest('all().iterate accepts batchSize and forwards it only to iterateMany', async () => {\n\t\t\tconst TestSchema = Schema.from('iter_batch_users')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\t\t\tconst events: unknown[] = []\n\n\t\t\tclass IterAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({ table: v.string() })\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\treadonly queryableOps = ['eq'] as const\n\t\t\t\tasync findMany(_s: AnySchema, _c: unknown, _f: unknown, options?: unknown) {\n\t\t\t\t\tevents.push({ method: 'findMany', options })\n\t\t\t\t\treturn [{ id: 'u1', name: 'Alice' }]\n\t\t\t\t}\n\t\t\t\tasync count(_s: AnySchema, _c: unknown, _f: unknown) {\n\t\t\t\t\tevents.push({ method: 'count' })\n\t\t\t\t\treturn 1\n\t\t\t\t}\n\t\t\t\tasync *iterateMany(_s: AnySchema, _c: unknown, _f: unknown, options?: unknown) {\n\t\t\t\t\tevents.push({ method: 'iterateMany', options })\n\t\t\t\t\tyield { id: 'u1', name: 'Alice' }\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst adapter = new (IterAdapter as any)() as IterAdapter\n\t\t\tconst repo = Repo.from(adapter).resolve((s) => ({ table: s.name })).build()\n\n\t\t\tconst iterated: Array<{ id: string; name: string }> = []\n\t\t\tfor await (const row of repo.on(TestSchema).all().limit(10).iterate({ batchSize: 3 })) {\n\t\t\t\titerated.push(row)\n\t\t\t}\n\t\t\tawait repo.on(TestSchema).all().limit(10).find()\n\t\t\tawait repo.on(TestSchema).all().limit(10).paginate()\n\n\t\t\texpect(iterated).toEqual([{ id: 'u1', name: 'Alice' }])\n\t\t\texpect(events).toEqual([\n\t\t\t\t{ method: 'iterateMany', options: expect.objectContaining({ limit: 10, batchSize: 3 }) },\n\t\t\t\t{ method: 'findMany', options: expect.not.objectContaining({ batchSize: expect.anything() }) },\n\t\t\t\t{ method: 'findMany', options: expect.not.objectContaining({ batchSize: expect.anything() }) },\n\t\t\t\t{ method: 'count' },\n\t\t\t])\n\t\t})\n\n\t\ttest('all().iterate validates batchSize before adapter iteration runs', async () => {\n\t\t\tconst TestSchema = Schema.from('iter_invalid_batch_users')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\t\t\tlet iterateManyCalls = 0\n\n\t\t\tclass IterAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({ table: v.string() })\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\tasync *iterateMany() {\n\t\t\t\t\titerateManyCalls += 1\n\t\t\t\t\tyield { id: 'u1', name: 'Alice' }\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst adapter = new (IterAdapter as any)() as IterAdapter\n\t\t\tconst repo = Repo.from(adapter).resolve((s) => ({ table: s.name })).build()\n\n\t\t\tfor (const batchSize of [0, -1, 1.5, Number.MAX_SAFE_INTEGER + 1, Number.POSITIVE_INFINITY, Number.NaN, '2'] as const) {\n\t\t\t\ttry {\n\t\t\t\t\tfor await (const _row of repo.on(TestSchema).all().iterate({ batchSize } as any)) {\n\t\t\t\t\t\tvoid _row\n\t\t\t\t\t}\n\t\t\t\t\texpect.unreachable(`batchSize ${String(batchSize)} should have failed`)\n\t\t\t\t} catch (error) {\n\t\t\t\t\texpect(error).toBeInstanceOf(OrmValidationError)\n\t\t\t\t\tconst err = error as InstanceType<typeof OrmValidationError>\n\t\t\t\t\texpect(err.kind).toBe('query-shape')\n\t\t\t\t\texpect(err.operation).toBe('iterate')\n\t\t\t\t\texpect(err.failures).toEqual([\n\t\t\t\t\t\texpect.objectContaining({ option: 'batchSize' }),\n\t\t\t\t\t])\n\t\t\t\t}\n\t\t\t}\n\t\t\texpect(iterateManyCalls).toBe(0)\n\t\t})\n\n\t\ttest('all().iterate yields selected and preloaded documents one at a time', async () => {\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tlet preloadReads = 0\n\t\t\tconst origUse = adapter.use.bind(adapter)\n\t\t\t;(adapter as any).use = vi.fn((schema: AnySchema, config: any) => {\n\t\t\t\tconst use = origUse(schema, config)\n\t\t\t\treturn {\n\t\t\t\t\t...use,\n\t\t\t\t\tfindMany: async (...args: any[]) => {\n\t\t\t\t\t\tif (schema === PostSchema) preloadReads += 1\n\t\t\t\t\t\treturn use.findMany(...(args as [any, any]))\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t})\n\t\t\tconst repo = Repo.from(adapter).resolve((s) => ({ table: s.name })).build()\n\t\t\tconst alice = await repo.on(UserSchema).one().create({ email: 'alice@iter.com', name: 'Alice' })\n\t\t\tconst bob = await repo.on(UserSchema).one().create({ email: 'bob@iter.com', name: 'Bob' })\n\t\t\tawait repo.on(PostSchema).one().create({ title: 'A1', userId: alice.id })\n\t\t\tawait repo.on(PostSchema).one().create({ title: 'A2', userId: alice.id })\n\t\t\tawait repo.on(PostSchema).one().create({ title: 'B1', userId: bob.id })\n\n\t\t\tconst rows: any[] = []\n\t\t\tconst iterator = repo.on(UserSchema).all().orderBy('name', 'asc').select(['id', 'name']).preload([UserRels.posts]).iterate({ batchSize: 1 })\n\t\t\texpect(iterator[Symbol.asyncIterator]()).toBe(iterator)\n\t\t\tfor await (const row of iterator) rows.push(row)\n\n\t\t\texpect(rows).toHaveLength(2)\n\t\t\texpect(rows.map((row) => row.name)).toEqual(['Alice', 'Bob'])\n\t\t\texpect(rows[0]).not.toHaveProperty('email')\n\t\t\texpect(rows[0].posts.map((post) => post.title)).toEqual(['A1', 'A2'])\n\t\t\texpect(rows[1].posts.map((post) => post.title)).toEqual(['B1'])\n\t\t\texpect(preloadReads).toBe(2)\n\t\t})\n\n\t\ttest('all().iterate honors page-based offsets and validates read query shape', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait repo.on(UserSchema).all().create([\n\t\t\t\t{ email: 'u1@iter.com', name: 'A' },\n\t\t\t\t{ email: 'u2@iter.com', name: 'B' },\n\t\t\t\t{ email: 'u3@iter.com', name: 'C' },\n\t\t\t\t{ email: 'u4@iter.com', name: 'D' },\n\t\t\t\t{ email: 'u5@iter.com', name: 'E' },\n\t\t\t])\n\n\t\t\tconst paged: string[] = []\n\t\t\tfor await (const row of repo.on(UserSchema).all().orderBy('name', 'asc').limit(2).page(2).iterate()) {\n\t\t\t\tpaged.push(row.name)\n\t\t\t}\n\t\t\texpect(paged).toEqual(['C', 'D'])\n\n\t\t\ttry {\n\t\t\t\tfor await (const _row of repo.on(UserSchema).all().select(['missing' as any]).iterate()) {\n\t\t\t\t\tvoid _row\n\t\t\t\t}\n\t\t\t\texpect.unreachable('iterate should validate selected fields before reading')\n\t\t\t} catch (error) {\n\t\t\t\texpect(error).toBeInstanceOf(OrmValidationError)\n\t\t\t\texpect((error as InstanceType<typeof OrmValidationError>).kind).toBe('query-shape')\n\t\t\t}\n\t\t})\n\n\t\ttest('resolve chains adapter config transforms', async () => {\n\t\t\tconst seenConfigs: unknown[] = []\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst origUse = adapter.use.bind(adapter)\n\t\t\t;(adapter as any).use = vi.fn((s: any, config: any) => {\n\t\t\t\tseenConfigs.push(config)\n\t\t\t\treturn origUse(s, config)\n\t\t\t})\n\n\t\t\tconst repo = Repo.from(adapter)\n\t\t\t\t.resolve((s) => ({ table: s.name }))\n\t\t\t\t.build()\n\n\t\t\tawait repo.resolve(\n\t\t\t\t(config) => ({ table: `a_${config.table}` }),\n\t\t\t\tasync () => {\n\t\t\t\t\tawait repo.resolve(\n\t\t\t\t\t\t(config) => ({ table: `b_${config.table}` }),\n\t\t\t\t\t\tasync () => {\n\t\t\t\t\t\t\tawait repo.on(UserSchema).all().find()\n\t\t\t\t\t\t},\n\t\t\t\t\t)\n\t\t\t\t},\n\t\t\t)\n\n\t\t\texpect(seenConfigs[0]).toEqual({ table: 'b_a_users' })\n\t\t})\n\n\t\ttest('session returns callback value and persists writes', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tlet insertedId = ''\n\t\t\tconst result = await repo.session(async () => {\n\t\t\t\tconst inserted = await repo.on(UserSchema).one().create({ email: 't@test.com', name: 'TxUser' })\n\t\t\t\tinsertedId = inserted.id\n\t\t\t\treturn 42\n\t\t\t})\n\n\t\t\texpect(result).toBe(42)\n\t\t\texpect(await repo.on(UserSchema).one().id(insertedId).find()).not.toBeNull()\n\t\t})\n\n\t\ttest('preloads can be resolved on mutation methods', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst org = await repo.on(OrgSchema).one().create({ name: 'Corp' })\n\t\t\tconst user = await repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.one()\n\t\t\t\t.preload([UserRels.org])\n\t\t\t\t.create({ email: 'u@test.com', name: 'User', orgId: org.id })\n\t\t\texpect((user.org as any).name).toBe('Corp')\n\n\t\t\tawait repo.on(PostSchema).one().create({ title: 'Post', userId: user.id })\n\t\t\tconst updated = await repo.on(UserSchema).one().id(user.id).preload([UserRels.posts]).update({ name: 'Updated' })\n\t\t\texpect(updated?.posts).toHaveLength(1)\n\t\t})\n\n\t\ttest('in-memory adapter without crud.raw collapses schemaRef.raw to never', () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst ref = repo.on(UserSchema)\n\t\t\texpectTypeOf(ref.raw).toBeNever()\n\t\t})\n\n\t\ttest('raw forwards user args through adapter.use to adapter.raw', async () => {\n\t\t\tlet capturedArgs: unknown[] = []\n\t\t\tconst spy = mockInstance()\n\t\t\tclass RawAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({ table: v.string() })\n\t\t\t\treadonly supportedFieldTypes = ['string', 'number'] as const\n\t\t\t\treadonly queryableOps = ['eq'] as const\n\t\t\t\tasync findMany() { return [] }\n\t\t\t\tasync raw(_s: any, _c: any, command: string, params: unknown[]) {\n\t\t\t\t\tcapturedArgs = [_s, _c, command, params]\n\t\t\t\t\treturn { rows: [{ id: '1' }] }\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst rawAdapter = new (RawAdapter as any)() as InstanceType<typeof RawAdapter>\n\t\t\tspy.mockRestore()\n\n\t\t\tconst repo = Repo.from(rawAdapter)\n\t\t\t\t.resolve((s) => ({ table: s.name }))\n\t\t\t\t.build()\n\t\t\tconst TestSchema = Schema.from('raw_test')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.build()\n\n\t\t\tconst result = await repo.on(TestSchema).raw('SELECT * FROM raw_test WHERE id = $1', ['abc'])\n\t\t\texpect(capturedArgs[0]).toBe(TestSchema)\n\t\t\texpect(capturedArgs[1]).toEqual({ table: 'raw_test' })\n\t\t\texpect(capturedArgs[2]).toBe('SELECT * FROM raw_test WHERE id = $1')\n\t\t\texpect(capturedArgs[3]).toEqual(['abc'])\n\t\t\texpect(result).toEqual({ rows: [{ id: '1' }] })\n\t\t})\n\n\t\ttest('raw with single-arg adapter (mongo-style) forwards correctly', async () => {\n\t\t\tlet capturedPipeline: unknown = undefined\n\t\t\tconst spy = mockInstance()\n\t\t\tclass MongoStyleAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({ col: v.string() })\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\tasync raw(_s: any, _c: any, pipeline: Record<string, unknown>[]) {\n\t\t\t\t\tcapturedPipeline = pipeline\n\t\t\t\t\treturn [{ total: 42 }]\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst mongoStyleAdapter = new (MongoStyleAdapter as any)() as InstanceType<typeof MongoStyleAdapter>\n\t\t\tspy.mockRestore()\n\n\t\t\tconst repo = Repo.from(mongoStyleAdapter)\n\t\t\t\t.resolve(() => ({ col: 'test' }))\n\t\t\t\t.build()\n\t\t\tconst TestSchema = Schema.from('mongo_test')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.build()\n\n\t\t\tconst result = await repo.on(TestSchema).raw([{ $count: 'total' }])\n\t\t\texpect(capturedPipeline).toEqual([{ $count: 'total' }])\n\t\t\texpect(result).toEqual([{ total: 42 }])\n\t\t})\n\n\t\ttest('computed fields are derived and shaped correctly when selected', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst created = await repo.on(PersonSchema).one().create({ firstName: 'Ada', lastName: 'Lovelace' })\n\t\t\tconst rows = await repo.on(PersonSchema).all().select(['id', 'fullName']).find()\n\n\t\t\texpect(rows).toEqual([{ id: created.id, fullName: 'Ada Lovelace' }])\n\t\t})\n\n\t\ttest('computed field selection auto-includes dependencies for adapter reads', async () => {\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst origUse = adapter.use.bind(adapter)\n\t\t\tlet seenSelect: string[] | undefined\n\t\t\t;(adapter as any).use = vi.fn((schema: any, config: any) => {\n\t\t\t\tconst use = origUse(schema, config)\n\t\t\t\treturn {\n\t\t\t\t\t...use,\n\t\t\t\t\tfindMany: async (filter: any, options: any) => {\n\t\t\t\t\t\tseenSelect = options?.select as string[] | undefined\n\t\t\t\t\t\treturn use.findMany(filter, options)\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tconst repo = Repo.from(adapter)\n\t\t\t\t.resolve((s) => ({ table: s.name }))\n\t\t\t\t.build()\n\t\t\tawait repo.on(PersonSchema).one().create({ firstName: 'Grace', lastName: 'Hopper' })\n\t\t\tawait repo.on(PersonSchema).all().select(['id', 'fullName']).find()\n\n\t\t\texpect(seenSelect).toEqual(expect.arrayContaining(['id', 'firstName', 'lastName']))\n\t\t})\n\n\t\ttest('unknown selected fields fail fast', async () => {\n\t\t\tconst { EquippedError } = await import('../../errors')\n\t\t\tconst repo = makeRepo()\n\t\t\tawait repo.on(PersonSchema).one().create({ firstName: 'Ada', lastName: 'Lovelace' })\n\n\t\t\tawait expect(\n\t\t\t\trepo\n\t\t\t\t\t.on(PersonSchema)\n\t\t\t\t\t.all()\n\t\t\t\t\t.select(['unknownField' as any])\n\t\t\t\t\t.find(),\n\t\t\t).rejects.toBeInstanceOf(EquippedError)\n\t\t})\n\n\t\ttest('missing computed dependencies in adapter output fail fast', async () => {\n\t\t\tconst { EquippedError } = await import('../../errors')\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst origUse = adapter.use.bind(adapter)\n\t\t\t;(adapter as any).use = vi.fn((schema: any, config: any) => {\n\t\t\t\tconst use = origUse(schema, config)\n\t\t\t\treturn {\n\t\t\t\t\t...use,\n\t\t\t\t\tfindMany: async (filter: any, options: any) => {\n\t\t\t\t\t\tconst rows = await use.findMany(filter, options)\n\t\t\t\t\t\treturn rows.map((row: any) => {\n\t\t\t\t\t\t\tconst next = { ...row }\n\t\t\t\t\t\t\tdelete (next as any).lastName\n\t\t\t\t\t\t\treturn next\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tconst repo = Repo.from(adapter)\n\t\t\t\t.resolve((s) => ({ table: s.name }))\n\t\t\t\t.build()\n\t\t\tawait repo.on(PersonSchema).one().create({ firstName: 'Katherine', lastName: 'Johnson' })\n\n\t\t\tawait expect(repo.on(PersonSchema).all().select(['fullName']).find()).rejects.toBeInstanceOf(EquippedError)\n\t\t})\n\n\t\ttest('one().id().find() returns seeded document and null for missing', async () => {\n\t\t\tconst TestSchema = Schema.from('findbytest')\n\t\t\t\t.pk('id', v.string(), () => 'gen')\n\t\t\t\t.build()\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst repo = Repo.from(adapter)\n\t\t\t\t.resolve((s) => ({ table: s.name }))\n\t\t\t\t.build()\n\n\t\t\tconst use = adapter.use(TestSchema, { table: 'findbytest' })\n\t\t\tawait use.createOne({ id: 'x' })\n\n\t\t\tconst found = await repo.on(TestSchema).one().id('x').find()\n\t\t\texpect(found).toEqual({ id: 'x' })\n\n\t\t\tconst missing = await repo.on(TestSchema).one().id('missing').find()\n\t\t\texpect(missing).toBeNull()\n\t\t})\n\n\t\tdescribe('schema-per-call create path', () => {\n\t\t\ttest('createOne round-trip via one().id().find()', async () => {\n\t\t\t\tconst repo = makeRepo()\n\t\t\t\tconst inserted = await repo.on(UserSchema).one().create({ email: 'rt@test.com', name: 'RoundTrip' })\n\t\t\t\texpect(inserted.email).toBe('rt@test.com')\n\t\t\t\texpect(inserted.name).toBe('RoundTrip')\n\t\t\t\texpect(inserted.id).toBeDefined()\n\n\t\t\t\tconst found = await repo.on(UserSchema).one().id(inserted.id).find()\n\t\t\t\texpect(found).not.toBeNull()\n\t\t\t\texpect(found!.id).toBe(inserted.id)\n\t\t\t\texpect(found!.email).toBe('rt@test.com')\n\t\t\t})\n\n\t\t\ttest('createOne injects onCreate defaults for missing fields', async () => {\n\t\t\t\tconst repo = makeRepo()\n\t\t\t\tconst inserted = await repo.on(UserSchema).one().create({ email: 'defaults@test.com', name: 'Defaults' })\n\t\t\t\texpect(inserted.createdAt).toBe(1000)\n\t\t\t\texpect(inserted.id).toBeDefined()\n\t\t\t})\n\n\t\t\ttest('createOne throws OrmValidationError with kind validation and field populated', async () => {\n\t\t\t\tconst { OrmValidationError } = await import('../errors')\n\t\t\t\tconst repo = makeRepo()\n\t\t\t\ttry {\n\t\t\t\t\tawait repo.on(UserSchema).one().create({ email: 123 as any, name: 'Bad' })\n\t\t\t\t\texpect.unreachable()\n\t\t\t\t} catch (e) {\n\t\t\t\t\texpect(e).toBeInstanceOf(OrmValidationError)\n\t\t\t\t\tconst err = e as InstanceType<typeof OrmValidationError>\n\t\t\t\t\texpect(err.kind).toBe('validation')\n\t\t\t\t\texpect(err.operation).toBe('createOne')\n\t\t\t\t\texpect(err.schema).toBe('users')\n\t\t\t\t\texpect(err.failures.length).toBeGreaterThan(0)\n\t\t\t\t\texpect(err.failures[0].field).toBe('email')\n\t\t\t\t}\n\t\t\t})\n\n\t\t\ttest('createMany round-trip via one().id().find()', async () => {\n\t\t\t\tconst repo = makeRepo()\n\t\t\t\tconst inserted = await repo.on(UserSchema).all().create([\n\t\t\t\t\t{ email: 'a@test.com', name: 'Alice' },\n\t\t\t\t\t{ email: 'b@test.com', name: 'Bob' },\n\t\t\t\t])\n\t\t\t\texpect(inserted).toHaveLength(2)\n\n\t\t\t\tconst foundA = await repo.on(UserSchema).one().id(inserted[0].id).find()\n\t\t\t\tconst foundB = await repo.on(UserSchema).one().id(inserted[1].id).find()\n\t\t\t\texpect(foundA!.email).toBe('a@test.com')\n\t\t\t\texpect(foundB!.email).toBe('b@test.com')\n\t\t\t})\n\n\t\t\ttest('createMany collects all failures with rowIndex and throws single OrmValidationError', async () => {\n\t\t\t\tconst { OrmValidationError } = await import('../errors')\n\t\t\t\tconst repo = makeRepo()\n\t\t\t\ttry {\n\t\t\t\t\tawait repo.on(UserSchema).all().create([\n\t\t\t\t\t\t{ email: 'good@test.com', name: 'Good' },\n\t\t\t\t\t\t{ email: 123 as any, name: 'Bad' },\n\t\t\t\t\t\t{ email: 'also-bad' as any, name: 456 as any },\n\t\t\t\t\t])\n\t\t\t\t\texpect.unreachable()\n\t\t\t\t} catch (e) {\n\t\t\t\t\texpect(e).toBeInstanceOf(OrmValidationError)\n\t\t\t\t\tconst err = e as InstanceType<typeof OrmValidationError>\n\t\t\t\t\texpect(err.kind).toBe('validation')\n\t\t\t\t\texpect(err.operation).toBe('createMany')\n\t\t\t\t\texpect(err.schema).toBe('users')\n\t\t\t\t\tconst rowIndices = err.failures.map((f) => f.rowIndex)\n\t\t\t\t\texpect(rowIndices).toContain(1)\n\t\t\t\t\texpect(rowIndices).toContain(2)\n\t\t\t\t\texpect(rowIndices).not.toContain(0)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\ttest('one().id().find() returns null for non-existent pk', async () => {\n\t\t\t\tconst repo = makeRepo()\n\t\t\t\tconst result = await repo.on(UserSchema).one().id('nonexistent').find()\n\t\t\t\texpect(result).toBeNull()\n\t\t\t})\n\n\t\t\ttest('createMany with onCreate defaults applied to all rows', async () => {\n\t\t\t\tconst repo = makeRepo()\n\t\t\t\tconst inserted = await repo.on(UserSchema).all().create([\n\t\t\t\t\t{ email: 'x@test.com', name: 'X' },\n\t\t\t\t\t{ email: 'y@test.com', name: 'Y' },\n\t\t\t\t])\n\t\t\t\texpect(inserted[0].createdAt).toBe(1000)\n\t\t\t\texpect(inserted[1].createdAt).toBe(1000)\n\t\t\t\texpect(inserted[0].id).toBeDefined()\n\t\t\t\texpect(inserted[1].id).toBeDefined()\n\t\t\t})\n\t\t})\n\n\t\ttest('one().id().delete() removes and returns document, null for missing', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst user = await repo.on(UserSchema).one().create({ email: 'del@test.com', name: 'ToDelete' })\n\n\t\t\tconst deleted = await repo.on(UserSchema).one().id(user.id).delete()\n\t\t\texpect(deleted).toEqual(expect.objectContaining({ id: user.id, name: 'ToDelete' }))\n\n\t\t\tconst found = await repo.on(UserSchema).one().id(user.id).find()\n\t\t\texpect(found).toBeNull()\n\n\t\t\tconst missing = await repo.on(UserSchema).one().id('nonexistent').delete()\n\t\t\texpect(missing).toBeNull()\n\t\t})\n\n\t\ttest('one().where().update() updates first matching document via filter', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.all()\n\t\t\t\t.create([\n\t\t\t\t\t{ email: 'a@test.com', name: 'Alice' },\n\t\t\t\t\t{ email: 'b@test.com', name: 'Bob' },\n\t\t\t\t])\n\n\t\t\tconst updated = await repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.one()\n\t\t\t\t.where((q) => q.eq('name', 'Alice'))\n\t\t\t\t.update({ name: 'Alicia' })\n\t\t\texpect(updated?.name).toBe('Alicia')\n\t\t})\n\n\t\ttest('one().where().update() with non-unique filter selects first match', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.all()\n\t\t\t\t.create([\n\t\t\t\t\t{ email: 'a@test.com', name: 'Same' },\n\t\t\t\t\t{ email: 'b@test.com', name: 'Same' },\n\t\t\t\t])\n\n\t\t\tconst updated = await repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.one()\n\t\t\t\t.where((q) => q.eq('name', 'Same'))\n\t\t\t\t.update({ name: 'Changed' })\n\t\t\texpect(updated?.name).toBe('Changed')\n\n\t\t\tconst all = await repo.on(UserSchema).all().find()\n\t\t\tconst changedCount = all.filter((u) => u.name === 'Changed').length\n\t\t\texpect(changedCount).toBe(1)\n\t\t})\n\n\t\ttest('all().where().update() updates all matching documents', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.all()\n\t\t\t\t.create([\n\t\t\t\t\t{ email: 'a@test.com', name: 'Same' },\n\t\t\t\t\t{ email: 'b@test.com', name: 'Same' },\n\t\t\t\t\t{ email: 'c@test.com', name: 'Different' },\n\t\t\t\t])\n\n\t\t\tconst updated = await repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.eq('name', 'Same'))\n\t\t\t\t.update({ name: 'Updated' })\n\t\t\texpect(updated).toHaveLength(2)\n\t\t\texpect(updated.every((u) => u.name === 'Updated')).toBe(true)\n\t\t})\n\n\t\ttest('all().where().update() applies auto-bump for onUpdate fields', async () => {\n\t\t\tconst AutoSchema = Schema.from('auto')\n\t\t\t\t.pk('id', v.string(), () => `a${++userCounter}`)\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.field('updatedAt', v.number(), { onCreate: () => 0, onUpdate: () => 9999 })\n\t\t\t\t.build()\n\t\t\tconst repo = makeRepo()\n\t\t\tawait repo.on(AutoSchema).one().create({ name: 'A' })\n\n\t\t\tconst updated = await repo\n\t\t\t\t.on(AutoSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.eq('name', 'A'))\n\t\t\t\t.update({ name: 'B' })\n\t\t\texpect(updated[0].updatedAt).toBe(9999)\n\t\t})\n\n\t\ttest('one().where().delete() removes first matching document', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.all()\n\t\t\t\t.create([\n\t\t\t\t\t{ email: 'a@test.com', name: 'Alice' },\n\t\t\t\t\t{ email: 'b@test.com', name: 'Bob' },\n\t\t\t\t])\n\n\t\t\tconst deleted = await repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.one()\n\t\t\t\t.where((q) => q.eq('name', 'Alice'))\n\t\t\t\t.delete()\n\t\t\texpect(deleted?.name).toBe('Alice')\n\n\t\t\tconst remaining = await repo.on(UserSchema).all().find()\n\t\t\texpect(remaining).toHaveLength(1)\n\t\t})\n\n\t\ttest('all().where().delete() removes all matching and returns them', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tawait repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.all()\n\t\t\t\t.create([\n\t\t\t\t\t{ email: 'a@test.com', name: 'ToDelete' },\n\t\t\t\t\t{ email: 'b@test.com', name: 'ToDelete' },\n\t\t\t\t\t{ email: 'c@test.com', name: 'Keep' },\n\t\t\t\t])\n\n\t\t\tconst deleted = await repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.eq('name', 'ToDelete'))\n\t\t\t\t.delete()\n\t\t\texpect(deleted).toHaveLength(2)\n\n\t\t\tconst remaining = await repo.on(UserSchema).all().find()\n\t\t\texpect(remaining).toHaveLength(1)\n\t\t\texpect(remaining[0].name).toBe('Keep')\n\t\t})\n\n\t\ttest('round-trip update via filter preserves data integrity', async () => {\n\t\t\tconst repo = makeRepo()\n\t\t\tconst user = await repo.on(UserSchema).one().create({ email: 'rt@test.com', name: 'Original' })\n\n\t\t\tawait repo\n\t\t\t\t.on(UserSchema)\n\t\t\t\t.one()\n\t\t\t\t.where((q) => q.eq('id', user.id))\n\t\t\t\t.update({ name: 'Modified' })\n\n\t\t\tconst found = await repo.on(UserSchema).one().id(user.id).find()\n\t\t\texpect(found?.name).toBe('Modified')\n\t\t\texpect(found?.email).toBe('rt@test.com')\n\t\t})\n\t})\n\n\tdescribe('type-level: per-op gating on FilterGroup', () => {\n\t\ttest('undeclared filter op is never on GatedFilterGroup', () => {\n\t\t\tconst { GatedFilterGroup: _type } = {} as any\n\t\t\ttype EqOnlyOps = readonly ['eq']\n\t\t\ttype Gated = import('../filter').GatedFilterGroup<EqOnlyOps>\n\t\t\texpectTypeOf<Gated['eq']>().not.toBeNever()\n\t\t\texpectTypeOf<Gated['ne']>().toBeNever()\n\t\t\texpectTypeOf<Gated['gt']>().toBeNever()\n\t\t\texpectTypeOf<Gated['and']>().not.toBeNever()\n\t\t\texpectTypeOf<Gated['or']>().not.toBeNever()\n\t\t})\n\n\t\ttest('all declared ops are available, undeclared are never', () => {\n\t\t\ttype TwoOps = readonly ['eq', 'gt']\n\t\t\ttype Gated = import('../filter').GatedFilterGroup<TwoOps>\n\t\t\texpectTypeOf<Gated['eq']>().not.toBeNever()\n\t\t\texpectTypeOf<Gated['gt']>().not.toBeNever()\n\t\t\texpectTypeOf<Gated['ne']>().toBeNever()\n\t\t\texpectTypeOf<Gated['lt']>().toBeNever()\n\t\t\texpectTypeOf<Gated['in']>().toBeNever()\n\t\t})\n\t})\n\n\tdescribe('type-level: missing queryableOps yields empty ops', () => {\n\t\ttest('adapter without queryableOps yields empty InferAdapterQueryableOps', () => {\n\t\t\tclass MinimalAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\treadonly queryableOps = [] as const\n\t\t\t}\n\t\t\ttype Ops = import('../adapter').InferAdapterQueryableOps<MinimalAdapter>\n\t\t\texpectTypeOf<Ops>().toEqualTypeOf<readonly []>()\n\t\t})\n\t})\n\n\tdescribe('type-level: method gating via class-based adapter', () => {\n\t\ttest('adapter with findMany enables all().find()', () => {\n\t\t\tclass FindAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\treadonly queryableOps = ['eq'] as const\n\t\t\t\tasync findMany() { return [] }\n\t\t\t}\n\t\t\ttype A = FindAdapter\n\t\t\ttype S = import('../schema').AnySchema\n\t\t\ttype All = import('./builders').AllBuilderSurface<S, A>\n\t\t\texpectTypeOf<All['find']>().toBeFunction()\n\t\t})\n\n\t\ttest('adapter with iterateMany enables all().iterate(options?)', () => {\n\t\t\tclass IterateAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\treadonly queryableOps = ['eq'] as const\n\t\t\t\tasync *iterateMany() {}\n\t\t\t}\n\t\t\ttype A = IterateAdapter\n\t\t\ttype S = import('../schema').AnySchema\n\t\t\ttype All = import('./builders').AllBuilderSurface<S, A>\n\t\t\texpectTypeOf<All['iterate']>().toBeFunction()\n\t\t\texpectTypeOf<Parameters<All['iterate']>>().toEqualTypeOf<[options?: IterationOptions]>()\n\t\t\texpectTypeOf<ReturnType<All['iterate']>>().toEqualTypeOf<AsyncGenerator<import('./internals/types').SelectedWithPreloads<S, never, []>, void, void>>()\n\t\t})\n\n\t\ttest('iterate exists only on AllBuilder and has no CDC-style sibling methods', () => {\n\t\t\tclass IterateAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\treadonly queryableOps = ['eq'] as const\n\t\t\t\tasync *iterateMany() {}\n\t\t\t}\n\t\t\ttype A = IterateAdapter\n\t\t\ttype S = import('../schema').AnySchema\n\t\t\ttype One = import('./builders').OneBuilderSurface<S, A>\n\t\t\ttype All = import('./builders').AllBuilderSurface<S, A>\n\t\t\ttype OneHasIterate = 'iterate' extends keyof One ? true : false\n\t\t\ttype CdcKeys = Extract<'stream' | 'watch' | 'subscribe' | 'changeFeed', keyof All>\n\t\t\texpectTypeOf<OneHasIterate>().toEqualTypeOf<false>()\n\t\t\texpectTypeOf<CdcKeys>().toBeNever()\n\t\t})\n\n\t\ttest('missing iterateMany collapses all().iterate to never', () => {\n\t\t\tclass FindOnlyAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\treadonly queryableOps = ['eq'] as const\n\t\t\t\tasync findMany() { return [] }\n\t\t\t}\n\t\t\ttype A = FindOnlyAdapter\n\t\t\ttype S = import('../schema').AnySchema\n\t\t\ttype All = import('./builders').AllBuilderSurface<S, A>\n\t\t\texpectTypeOf<All['iterate']>().toBeNever()\n\t\t})\n\n\t\ttest('missing updateMany collapses one().update and all().update to never', () => {\n\t\t\tclass ReadOnlyAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\tasync findMany() { return [] }\n\t\t\t}\n\t\t\ttype A = ReadOnlyAdapter\n\t\t\ttype S = import('../schema').AnySchema\n\t\t\ttype One = import('./builders').OneBuilderSurface<S, A>\n\t\t\ttype All = import('./builders').AllBuilderSurface<S, A>\n\t\t\texpectTypeOf<One['update']>().toBeNever()\n\t\t\texpectTypeOf<All['update']>().toBeNever()\n\t\t})\n\n\t\ttest('missing deleteMany collapses one().delete and all().delete to never', () => {\n\t\t\tclass NoDeleteAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\tasync findMany() { return [] }\n\t\t\t}\n\t\t\ttype A = NoDeleteAdapter\n\t\t\ttype S = import('../schema').AnySchema\n\t\t\ttype One = import('./builders').OneBuilderSurface<S, A>\n\t\t\ttype All = import('./builders').AllBuilderSurface<S, A>\n\t\t\texpectTypeOf<One['delete']>().toBeNever()\n\t\t\texpectTypeOf<All['delete']>().toBeNever()\n\t\t})\n\n\t\ttest('missing raw collapses schemaRef.raw to never', () => {\n\t\t\tclass NoRawAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\tasync findMany() { return [] }\n\t\t\t}\n\t\t\ttype A = NoRawAdapter\n\t\t\ttype Ref = import('./builders').SchemaRefSurface<import('../schema').AnySchema, A>\n\t\t\texpectTypeOf<Ref['raw']>().toBeNever()\n\t\t})\n\n\t\ttest('missing upsertOne collapses one().upsert to never', () => {\n\t\t\tclass NoUpsertAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\treadonly queryableOps = ['eq'] as const\n\t\t\t\tasync findMany() { return [] }\n\t\t\t}\n\t\t\ttype A = NoUpsertAdapter\n\t\t\ttype S = import('../schema').AnySchema\n\t\t\ttype One = import('./builders').OneBuilderSurface<S, A>\n\t\t\texpectTypeOf<One['upsert']>().toBeNever()\n\t\t})\n\n\t\ttest('adapter with updateMany enables one().update and all().update', () => {\n\t\t\tclass WithUpdateAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\treadonly queryableOps = ['eq'] as const\n\t\t\t\tasync findMany() { return [] }\n\t\t\t\tasync updateMany() { return [] }\n\t\t\t}\n\t\t\ttype A = WithUpdateAdapter\n\t\t\ttype S = import('../schema').AnySchema\n\t\t\ttype One = import('./builders').OneBuilderSurface<S, A>\n\t\t\ttype All = import('./builders').AllBuilderSurface<S, A>\n\t\t\texpectTypeOf<One['update']>().toBeFunction()\n\t\t\texpectTypeOf<All['update']>().toBeFunction()\n\t\t})\n\n\t\ttest('adapter with deleteMany enables one().delete and all().delete', () => {\n\t\t\tclass WithDeleteAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\treadonly queryableOps = ['eq'] as const\n\t\t\t\tasync findMany() { return [] }\n\t\t\t\tasync deleteMany() { return [] }\n\t\t\t}\n\t\t\ttype A = WithDeleteAdapter\n\t\t\ttype S = import('../schema').AnySchema\n\t\t\ttype One = import('./builders').OneBuilderSurface<S, A>\n\t\t\ttype All = import('./builders').AllBuilderSurface<S, A>\n\t\t\texpectTypeOf<One['delete']>().toBeFunction()\n\t\t\texpectTypeOf<All['delete']>().toBeFunction()\n\t\t})\n\n\t\ttest('missing count collapses all().count to never', () => {\n\t\t\tclass NoCountAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\treadonly queryableOps = ['eq'] as const\n\t\t\t\tasync findMany() { return [] }\n\t\t\t}\n\t\t\ttype A = NoCountAdapter\n\t\t\ttype S = import('../schema').AnySchema\n\t\t\ttype All = import('./builders').AllBuilderSurface<S, A>\n\t\t\texpectTypeOf<All['count']>().toBeNever()\n\t\t})\n\n\t\ttest('adapter with count enables all().count', () => {\n\t\t\tclass WithCountAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\treadonly queryableOps = ['eq'] as const\n\t\t\t\tasync findMany() { return [] }\n\t\t\t\tasync count() { return 0 }\n\t\t\t}\n\t\t\ttype A = WithCountAdapter\n\t\t\ttype S = import('../schema').AnySchema\n\t\t\ttype All = import('./builders').AllBuilderSurface<S, A>\n\t\t\texpectTypeOf<All['count']>().toBeFunction()\n\t\t})\n\n\t\ttest('adapter with raw enables schemaRef.raw', () => {\n\t\t\tclass WithRawAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\tasync raw(_s: any, _c: any, _command: string) { return { rows: [] } }\n\t\t\t}\n\t\t\ttype A = WithRawAdapter\n\t\t\ttype Ref = import('./builders').SchemaRefSurface<import('../schema').AnySchema, A>\n\t\t\texpectTypeOf<Ref['raw']>().toBeFunction()\n\t\t})\n\n\t\ttest('adapter raw signature drives arg-tuple inference on schemaRef.raw', () => {\n\t\t\tclass TypedRawAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({ table: v.string() })\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\tasync raw(_s: any, _c: any, _sql: string, _params: number[]) { return [42] }\n\t\t\t}\n\t\t\ttype A = TypedRawAdapter\n\t\t\ttype Ref = import('./builders').SchemaRefSurface<import('../schema').AnySchema, A>\n\t\t\texpectTypeOf<Ref['raw']>().toBeFunction()\n\t\t\texpectTypeOf<Ref['raw']>().parameters.toEqualTypeOf<[sql: string, params: number[]]>()\n\t\t})\n\n\t\ttest('per-call <T> override narrows raw return type', () => {\n\t\t\tclass DefaultRawAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({ table: v.string() })\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\tasync raw(_s: any, _c: any, _sql: string) { return { rows: [] as unknown[] } }\n\t\t\t}\n\t\t\ttype A = DefaultRawAdapter\n\t\t\ttype Ref = import('./builders').SchemaRefSurface<import('../schema').AnySchema, A>\n\t\t\texpectTypeOf<Ref['raw']>().toBeFunction()\n\t\t\texpectTypeOf<Ref['raw']>().parameters.toEqualTypeOf<[sql: string]>()\n\t\t})\n\n\t\ttest('adapter with zero-arg raw infers empty arg tuple', () => {\n\t\t\tclass ZeroArgRawAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\tasync raw(_s: any, _c: any) { return 'pong' }\n\t\t\t}\n\t\t\ttype A = ZeroArgRawAdapter\n\t\t\ttype Ref = import('./builders').SchemaRefSurface<import('../schema').AnySchema, A>\n\t\t\texpectTypeOf<Ref['raw']>().parameters.toEqualTypeOf<[]>()\n\t\t\texpectTypeOf<Ref['raw']>().toBeFunction()\n\t\t})\n\n\t\ttest('gating survives through select() chains', () => {\n\t\t\tclass MinimalAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t}\n\t\t\ttype A = MinimalAdapter\n\t\t\tconst _TestSchema = Schema.from('test')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\t\t\ttype S = typeof _TestSchema\n\t\t\ttype One = import('./builders').OneBuilderSurface<S, A, 'id'>\n\t\t\ttype All = import('./builders').AllBuilderSurface<S, A, 'id'>\n\t\t\texpectTypeOf<One['update']>().toBeNever()\n\t\t\texpectTypeOf<One['delete']>().toBeNever()\n\t\t\texpectTypeOf<All['update']>().toBeNever()\n\t\t\texpectTypeOf<All['delete']>().toBeNever()\n\t\t})\n\t})\n\n\tdescribe('type-level: aggregate gating', () => {\n\t\ttest('adapter with aggregateOps = [] narrows repo.on(S).aggregate to never', () => {\n\t\t\tclass EmptyAggAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\treadonly aggregateOps = [] as const\n\t\t\t}\n\t\t\ttype A = EmptyAggAdapter\n\t\t\ttype Ref = import('./builders').SchemaRefSurface<import('../schema').AnySchema, A>\n\t\t\texpectTypeOf<Ref['aggregate']>().toBeNever()\n\t\t})\n\n\t\ttest('adapter with aggregate method and non-empty aggregateOps narrows to callable', () => {\n\t\t\tclass FullAggAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string', 'number'] as const\n\t\t\t\treadonly aggregateOps = ['count', 'sum'] as const\n\t\t\t\tasync aggregate() { return [] }\n\t\t\t}\n\t\t\ttype A = FullAggAdapter\n\t\t\ttype Ref = import('./builders').SchemaRefSurface<import('../schema').AnySchema, A>\n\t\t\texpectTypeOf<Ref['aggregate']>().toBeFunction()\n\t\t})\n\n\t\ttest('adapter with aggregate method but empty aggregateOps narrows to never', () => {\n\t\t\tclass MethodOnlyAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\treadonly aggregateOps = [] as const\n\t\t\t\tasync aggregate() { return [] }\n\t\t\t}\n\t\t\ttype A = MethodOnlyAdapter\n\t\t\ttype Ref = import('./builders').SchemaRefSurface<import('../schema').AnySchema, A>\n\t\t\texpectTypeOf<Ref['aggregate']>().toBeNever()\n\t\t})\n\n\t\ttest('adapter with non-empty aggregateOps but no aggregate method narrows to never', () => {\n\t\t\tclass OpsOnlyAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\treadonly aggregateOps = ['count'] as const\n\t\t\t}\n\t\t\ttype A = OpsOnlyAdapter\n\t\t\ttype Ref = import('./builders').SchemaRefSurface<import('../schema').AnySchema, A>\n\t\t\texpectTypeOf<Ref['aggregate']>().toBeNever()\n\t\t})\n\n\t\ttest('default aggregateOps on OrmAdapter is empty, so aggregate is never by default', () => {\n\t\t\tclass DefaultAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t}\n\t\t\ttype A = DefaultAdapter\n\t\t\ttype Ref = import('./builders').SchemaRefSurface<import('../schema').AnySchema, A>\n\t\t\texpectTypeOf<Ref['aggregate']>().toBeNever()\n\t\t})\n\t})\n\n\tdescribe('type-level: AggregateBuilder', () => {\n\t\tclass AggAdapter extends OrmAdapter {\n\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\treadonly supportedFieldTypes = ['string', 'number', 'boolean', 'object', 'array'] as const\n\t\t\treadonly aggregateOps = ['count', 'countDistinct', 'sum', 'avg', 'min', 'max'] as const\n\t\t\tasync aggregate() { return [] }\n\t\t}\n\t\tconst _S = Schema.from('items')\n\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t.field('name', v.string())\n\t\t\t.field('amount', v.number())\n\t\t\t.field('active', v.boolean())\n\t\t\t.field('tags', v.array(v.string()))\n\t\t\t.field('meta', v.object({ x: v.number() }))\n\t\t\t.build()\n\t\ttype S = typeof _S\n\t\ttype A = AggAdapter\n\t\ttype Builder = import('./builders').AggregateBuilder<S, A, {}, {}, false>\n\n\t\ttest('.count(alias) adds alias to result type as number', () => {\n\t\t\ttype AfterCount = ReturnType<Builder['count']>\n\t\t\ttype Result = Awaited<ReturnType<AfterCount['run']>>\n\t\t\texpectTypeOf<Result>().toHaveProperty('total')\n\t\t})\n\n\t\ttest('duplicate alias: \"a\" extends keyof Aggs triggers never guard', () => {\n\t\t\ttype AggsWithA = { a: number }\n\t\t\ttype DuplicateGuard = 'a' extends keyof AggsWithA ? [never] : [alias: 'a']\n\t\t\texpectTypeOf<DuplicateGuard>().toEqualTypeOf<[never]>()\n\t\t})\n\n\t\ttest('.run() on empty Aggs requires never arg (compile error gate)', () => {\n\t\t\ttype RunParams = Parameters<Builder['run']>\n\t\t\texpectTypeOf<RunParams>().toEqualTypeOf<[never]>()\n\t\t})\n\n\t\ttest('.run() on non-empty Aggs takes no args', () => {\n\t\t\ttype WithCount = import('./builders').AggregateBuilder<S, A, { total: number }, {}, false>\n\t\t\ttype RunParams = Parameters<WithCount['run']>\n\t\t\texpectTypeOf<RunParams>().toEqualTypeOf<[]>()\n\t\t})\n\n\t\ttest('.run() returns single object when HasGroupBy = false', () => {\n\t\t\ttype WithCount = import('./builders').AggregateBuilder<S, A, { total: number }, {}, false>\n\t\t\ttype Result = Awaited<ReturnType<WithCount['run']>>\n\t\t\texpectTypeOf<Result>().toEqualTypeOf<{ total: number }>()\n\t\t})\n\n\t\ttest('.run() returns array when HasGroupBy = true', () => {\n\t\t\ttype WithGroupBy = import('./builders').AggregateBuilder<S, A, { total: number }, { name: string }, true>\n\t\t\ttype Result = Awaited<ReturnType<WithGroupBy['run']>>\n\t\t\texpectTypeOf<Result>().toEqualTypeOf<({ total: number } & { name: string })[]>()\n\t\t})\n\n\t\ttest('group-key fields appear in result row with schema-declared types', () => {\n\t\t\ttype WithGroupBy = import('./builders').AggregateBuilder<S, A, { total: number }, { name: string; amount: number }, true>\n\t\t\ttype Result = Awaited<ReturnType<WithGroupBy['run']>>\n\t\t\ttype Row = Result[number]\n\t\t\texpectTypeOf<Row>().toHaveProperty('total')\n\t\t\texpectTypeOf<Row>().toHaveProperty('name')\n\t\t\texpectTypeOf<Row>().toHaveProperty('amount')\n\t\t})\n\n\t\ttest('.sum rejects string field (field-type constraint)', () => {\n\t\t\ttype SumFieldParam = Parameters<Builder['sum']>[0]\n\t\t\ttype IsAccepted = typeof _S.fields.name extends SumFieldParam ? true : false\n\t\t\texpectTypeOf<IsAccepted>().toEqualTypeOf<false>()\n\t\t})\n\n\t\ttest('.avg rejects string field (field-type constraint)', () => {\n\t\t\ttype AvgFieldParam = Parameters<Builder['avg']>[0]\n\t\t\ttype IsAccepted = typeof _S.fields.name extends AvgFieldParam ? true : false\n\t\t\texpectTypeOf<IsAccepted>().toEqualTypeOf<false>()\n\t\t})\n\n\t\ttest('.min rejects array field', () => {\n\t\t\ttype MinFieldParam = Parameters<Builder['min']>[0]\n\t\t\ttype IsAccepted = typeof _S.fields.tags extends MinFieldParam ? true : false\n\t\t\texpectTypeOf<IsAccepted>().toEqualTypeOf<false>()\n\t\t})\n\n\t\ttest('.max rejects object field', () => {\n\t\t\ttype MaxFieldParam = Parameters<Builder['max']>[0]\n\t\t\ttype IsAccepted = typeof _S.fields.meta extends MaxFieldParam ? true : false\n\t\t\texpectTypeOf<IsAccepted>().toEqualTypeOf<false>()\n\t\t})\n\n\t\ttest('.where().where() is a compile error', () => {\n\t\t\ttype AfterWhere = import('./builders').AggregateBuilder<S, A, {}, {}, false, true, false>\n\t\t\ttype WhereParams = Parameters<AfterWhere['where']>\n\t\t\texpectTypeOf<WhereParams>().toEqualTypeOf<[never]>()\n\t\t})\n\n\t\ttest('.having().having() is a compile error', () => {\n\t\t\ttype AfterHaving = import('./builders').AggregateBuilder<S, A, {}, {}, false, false, true>\n\t\t\ttype HavingParams = Parameters<AfterHaving['having']>\n\t\t\texpectTypeOf<HavingParams>().toEqualTypeOf<[never]>()\n\t\t})\n\n\t\ttest('.groupBy().groupBy() is a compile error', () => {\n\t\t\ttype AfterGroupBy = import('./builders').AggregateBuilder<S, A, {}, { name: string }, true, false, false>\n\t\t\ttype GroupByParams = Parameters<AfterGroupBy['groupBy']>\n\t\t\texpectTypeOf<GroupByParams>().toEqualTypeOf<[never]>()\n\t\t})\n\n\t\ttest('clone-on-step: .where() returns a new builder', () => {\n\t\t\ttype After = ReturnType<Builder['where']>\n\t\t\texpectTypeOf<After>().toMatchTypeOf<import('./builders').AggregateBuilder<S, A, {}, {}, false, true, false>>()\n\t\t})\n\t})\n\n\tdescribe('repo.on().all().where().find() end-to-end', () => {\n\t\tconst TestSchema = Schema.from('e2e')\n\t\t\t.pk('id', v.string(), () => `e-${Math.random().toString(36).slice(2)}`)\n\t\t\t.field('name', v.string())\n\t\t\t.field('age', v.number())\n\t\t\t.field('active', v.boolean())\n\t\t\t.field('tags', v.array(v.string()))\n\t\t\t.field('score', v.optional(v.number()), { onCreate: () => undefined })\n\t\t\t.build()\n\n\t\tfunction makeE2eRepo() {\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst repo = Repo.from(adapter)\n\t\t\t\t.resolve((s) => ({ table: s.name }))\n\t\t\t\t.build()\n\t\t\treturn { repo, adapter }\n\t\t}\n\n\t\tasync function seedData(repo: any) {\n\t\t\tawait repo\n\t\t\t\t.on(TestSchema)\n\t\t\t\t.all()\n\t\t\t\t.create([\n\t\t\t\t\t{ name: 'Alice', age: 30, active: true, tags: ['admin', 'user'] },\n\t\t\t\t\t{ name: 'Bob', age: 20, active: false, tags: ['user'] },\n\t\t\t\t\t{ name: 'Carol', age: 40, active: true, tags: ['admin'] },\n\t\t\t\t\t{ name: 'Dave', age: 25, active: true, tags: ['user', 'guest'], score: 100 },\n\t\t\t\t])\n\t\t}\n\n\t\ttest('all().where().find() returns matching documents', async () => {\n\t\t\tconst { repo } = makeE2eRepo()\n\t\t\tawait seedData(repo)\n\t\t\tconst results = await repo\n\t\t\t\t.on(TestSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.eq(TestSchema.fields.active, true))\n\t\t\t\t.find()\n\t\t\texpect(results).toHaveLength(3)\n\t\t\texpect(results.every((r) => r.active === true)).toBe(true)\n\t\t})\n\n\t\ttest('one().where().find() returns first matching document', async () => {\n\t\t\tconst { repo } = makeE2eRepo()\n\t\t\tawait seedData(repo)\n\t\t\tconst result = await repo\n\t\t\t\t.on(TestSchema)\n\t\t\t\t.one()\n\t\t\t\t.where((q) => q.eq(TestSchema.fields.name, 'Bob'))\n\t\t\t\t.find()\n\t\t\texpect(result).not.toBeNull()\n\t\t\texpect(result!.name).toBe('Bob')\n\t\t})\n\n\t\ttest('one().where().find() returns null when no match', async () => {\n\t\t\tconst { repo } = makeE2eRepo()\n\t\t\tawait seedData(repo)\n\t\t\tconst result = await repo\n\t\t\t\t.on(TestSchema)\n\t\t\t\t.one()\n\t\t\t\t.where((q) => q.eq(TestSchema.fields.name, 'Nobody'))\n\t\t\t\t.find()\n\t\t\texpect(result).toBeNull()\n\t\t})\n\n\t\ttest('eq filter op matches exact value', async () => {\n\t\t\tconst { repo } = makeE2eRepo()\n\t\t\tawait seedData(repo)\n\t\t\tconst results = await repo\n\t\t\t\t.on(TestSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.eq(TestSchema.fields.age, 30))\n\t\t\t\t.find()\n\t\t\texpect(results).toHaveLength(1)\n\t\t\texpect(results[0].name).toBe('Alice')\n\t\t})\n\n\t\ttest('ne filter op excludes matching values', async () => {\n\t\t\tconst { repo } = makeE2eRepo()\n\t\t\tawait seedData(repo)\n\t\t\tconst results = await repo\n\t\t\t\t.on(TestSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.ne(TestSchema.fields.name, 'Alice'))\n\t\t\t\t.find()\n\t\t\texpect(results).toHaveLength(3)\n\t\t\texpect(results.every((r) => r.name !== 'Alice')).toBe(true)\n\t\t})\n\n\t\ttest('gt filter op matches values greater than', async () => {\n\t\t\tconst { repo } = makeE2eRepo()\n\t\t\tawait seedData(repo)\n\t\t\tconst results = await repo\n\t\t\t\t.on(TestSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.gt(TestSchema.fields.age, 25))\n\t\t\t\t.find()\n\t\t\texpect(results.map((r) => r.name).sort()).toEqual(['Alice', 'Carol'])\n\t\t})\n\n\t\ttest('gte filter op matches values greater than or equal', async () => {\n\t\t\tconst { repo } = makeE2eRepo()\n\t\t\tawait seedData(repo)\n\t\t\tconst results = await repo\n\t\t\t\t.on(TestSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.gte(TestSchema.fields.age, 30))\n\t\t\t\t.find()\n\t\t\texpect(results.map((r) => r.name).sort()).toEqual(['Alice', 'Carol'])\n\t\t})\n\n\t\ttest('lt filter op matches values less than', async () => {\n\t\t\tconst { repo } = makeE2eRepo()\n\t\t\tawait seedData(repo)\n\t\t\tconst results = await repo\n\t\t\t\t.on(TestSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.lt(TestSchema.fields.age, 25))\n\t\t\t\t.find()\n\t\t\texpect(results).toHaveLength(1)\n\t\t\texpect(results[0].name).toBe('Bob')\n\t\t})\n\n\t\ttest('lte filter op matches values less than or equal', async () => {\n\t\t\tconst { repo } = makeE2eRepo()\n\t\t\tawait seedData(repo)\n\t\t\tconst results = await repo\n\t\t\t\t.on(TestSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.lte(TestSchema.fields.age, 25))\n\t\t\t\t.find()\n\t\t\texpect(results.map((r) => r.name).sort()).toEqual(['Bob', 'Dave'])\n\t\t})\n\n\t\ttest('in filter op matches values in array', async () => {\n\t\t\tconst { repo } = makeE2eRepo()\n\t\t\tawait seedData(repo)\n\t\t\tconst results = await repo\n\t\t\t\t.on(TestSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.in(TestSchema.fields.name, ['Alice', 'Carol']))\n\t\t\t\t.find()\n\t\t\texpect(results.map((r) => r.name).sort()).toEqual(['Alice', 'Carol'])\n\t\t})\n\n\t\ttest('notIn filter op excludes values in array', async () => {\n\t\t\tconst { repo } = makeE2eRepo()\n\t\t\tawait seedData(repo)\n\t\t\tconst results = await repo\n\t\t\t\t.on(TestSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.notIn(TestSchema.fields.name, ['Alice', 'Carol']))\n\t\t\t\t.find()\n\t\t\texpect(results.map((r) => r.name).sort()).toEqual(['Bob', 'Dave'])\n\t\t})\n\n\t\ttest('like filter op matches substring case-insensitively', async () => {\n\t\t\tconst { repo } = makeE2eRepo()\n\t\t\tawait seedData(repo)\n\t\t\tconst results = await repo\n\t\t\t\t.on(TestSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.like(TestSchema.fields.name, 'ali'))\n\t\t\t\t.find()\n\t\t\texpect(results).toHaveLength(1)\n\t\t\texpect(results[0].name).toBe('Alice')\n\t\t})\n\n\t\ttest('exists filter op matches non-null values', async () => {\n\t\t\tconst { repo } = makeE2eRepo()\n\t\t\tawait seedData(repo)\n\t\t\tconst results = await repo\n\t\t\t\t.on(TestSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.exists(TestSchema.fields.score))\n\t\t\t\t.find()\n\t\t\texpect(results).toHaveLength(1)\n\t\t\texpect(results[0].name).toBe('Dave')\n\t\t})\n\n\t\ttest('notExists filter op matches null/undefined values', async () => {\n\t\t\tconst { repo } = makeE2eRepo()\n\t\t\tawait seedData(repo)\n\t\t\tconst results = await repo\n\t\t\t\t.on(TestSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.notExists(TestSchema.fields.score))\n\t\t\t\t.find()\n\t\t\texpect(results).toHaveLength(3)\n\t\t})\n\n\t\ttest('contains filter op matches array subset', async () => {\n\t\t\tconst { repo } = makeE2eRepo()\n\t\t\tawait seedData(repo)\n\t\t\tconst results = await repo\n\t\t\t\t.on(TestSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.contains('tags', ['admin']))\n\t\t\t\t.find()\n\t\t\texpect(results.map((r) => r.name).sort()).toEqual(['Alice', 'Carol'])\n\t\t})\n\n\t\ttest('notContains filter op excludes array subset', async () => {\n\t\t\tconst { repo } = makeE2eRepo()\n\t\t\tawait seedData(repo)\n\t\t\tconst results = await repo\n\t\t\t\t.on(TestSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.notContains('tags', ['admin']))\n\t\t\t\t.find()\n\t\t\texpect(results.map((r) => r.name).sort()).toEqual(['Bob', 'Dave'])\n\t\t})\n\n\t\ttest('and combinator requires all conditions', async () => {\n\t\t\tconst { repo } = makeE2eRepo()\n\t\t\tawait seedData(repo)\n\t\t\tconst results = await repo\n\t\t\t\t.on(TestSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.and([(g) => g.gt(TestSchema.fields.age, 20), (g) => g.eq(TestSchema.fields.active, true)]))\n\t\t\t\t.find()\n\t\t\texpect(results.map((r) => r.name).sort()).toEqual(['Alice', 'Carol', 'Dave'])\n\t\t})\n\n\t\ttest('or combinator matches any condition', async () => {\n\t\t\tconst { repo } = makeE2eRepo()\n\t\t\tawait seedData(repo)\n\t\t\tconst results = await repo\n\t\t\t\t.on(TestSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.or([(g) => g.eq(TestSchema.fields.name, 'Alice'), (g) => g.eq(TestSchema.fields.name, 'Bob')]))\n\t\t\t\t.find()\n\t\t\texpect(results.map((r) => r.name).sort()).toEqual(['Alice', 'Bob'])\n\t\t})\n\n\t\ttest('raw-string field overload works end-to-end', async () => {\n\t\t\tconst { repo } = makeE2eRepo()\n\t\t\tawait seedData(repo)\n\t\t\tconst results = await repo\n\t\t\t\t.on(TestSchema)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.eq('name', 'Alice'))\n\t\t\t\t.find()\n\t\t\texpect(results).toHaveLength(1)\n\t\t\texpect(results[0].name).toBe('Alice')\n\t\t})\n\n\t\ttest('empty and([]) throws at builder time', async () => {\n\t\t\tconst { repo } = makeE2eRepo()\n\t\t\tawait seedData(repo)\n\t\t\texpect(() =>\n\t\t\t\trepo\n\t\t\t\t\t.on(TestSchema)\n\t\t\t\t\t.all()\n\t\t\t\t\t.where((q) => q.and([]))\n\t\t\t\t\t.find(),\n\t\t\t).toThrow()\n\t\t})\n\n\t\ttest('empty or([]) throws at builder time', async () => {\n\t\t\tconst { repo } = makeE2eRepo()\n\t\t\tawait seedData(repo)\n\t\t\texpect(() =>\n\t\t\t\trepo\n\t\t\t\t\t.on(TestSchema)\n\t\t\t\t\t.all()\n\t\t\t\t\t.where((q) => q.or([]))\n\t\t\t\t\t.find(),\n\t\t\t).toThrow()\n\t\t})\n\n\t\ttest('unknown field in filter throws OrmValidationError at boundary', async () => {\n\t\t\tconst { OrmValidationError: OrmValErr } = await import('../errors')\n\t\t\tconst { repo } = makeE2eRepo()\n\t\t\tawait seedData(repo)\n\t\t\tawait expect(\n\t\t\t\trepo\n\t\t\t\t\t.on(TestSchema)\n\t\t\t\t\t.all()\n\t\t\t\t\t.where((q) => q.eq('nonexistentField', 42))\n\t\t\t\t\t.find(),\n\t\t\t).rejects.toBeInstanceOf(OrmValErr)\n\t\t})\n\t})\n\n\tdescribe('one().id().update() (replaces updateByPk)', () => {\n\t\tlet viewCounter = 0\n\t\tconst ItemSchema = Schema.from('items')\n\t\t\t.pk('id', v.string(), () => `item-${++viewCounter}`)\n\t\t\t.field('title', v.string())\n\t\t\t.field('views', v.number())\n\t\t\t.field('updatedAt', v.number(), { onCreate: () => 1000, onUpdate: () => Date.now() })\n\t\t\t.build()\n\n\t\tfunction makeUpdateRepo() {\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\treturn Repo.from(adapter)\n\t\t\t\t.resolve((s) => ({ table: s.name }))\n\t\t\t\t.build()\n\t\t}\n\n\t\ttest('round-trip update via one().id().update()', async () => {\n\t\t\tconst repo = makeUpdateRepo()\n\t\t\tconst item = await repo.on(ItemSchema).one().create({ title: 'Hello', views: 0 })\n\t\t\tconst updated = await repo.on(ItemSchema).one().id(item.id).update({ title: 'Updated' })\n\t\t\texpect(updated).not.toBeNull()\n\t\t\texpect(updated!.title).toBe('Updated')\n\t\t\texpect(updated!.views).toBe(0)\n\n\t\t\tconst found = await repo.on(ItemSchema).one().id(item.id).find()\n\t\t\texpect(found!.title).toBe('Updated')\n\t\t})\n\n\t\ttest('auto-bump injects updatedAt on un-touched onUpdate field', async () => {\n\t\t\tconst repo = makeUpdateRepo()\n\t\t\tconst item = await repo.on(ItemSchema).one().create({ title: 'Hello', views: 0 })\n\t\t\tconst before = item.updatedAt\n\n\t\t\tconst updated = await repo.on(ItemSchema).one().id(item.id).update({ title: 'Changed' })\n\t\t\texpect(updated!.updatedAt).not.toBe(before)\n\t\t\texpect(updated!.updatedAt).toBeGreaterThanOrEqual(before)\n\t\t})\n\n\t\ttest('user set({updatedAt:X}) suppresses auto-bump', async () => {\n\t\t\tconst repo = makeUpdateRepo()\n\t\t\tconst item = await repo.on(ItemSchema).one().create({ title: 'Hello', views: 0 })\n\n\t\t\tconst updated = await repo.on(ItemSchema).one().id(item.id).update({ updatedAt: 9999 })\n\t\t\texpect(updated!.updatedAt).toBe(9999)\n\t\t})\n\n\t\ttest('update with inc op via SchemaUpdateInput', async () => {\n\t\t\tconst { IncOp } = await import('../updates')\n\t\t\tconst repo = makeUpdateRepo()\n\t\t\tconst item = await repo.on(ItemSchema).one().create({ title: 'Hello', views: 10 })\n\n\t\t\tconst updated = await repo.on(ItemSchema).one().id(item.id).update({ views: new IncOp('views', 5) })\n\t\t\texpect(updated!.views).toBe(15)\n\t\t})\n\n\t\ttest('update returns null for missing pk', async () => {\n\t\t\tconst repo = makeUpdateRepo()\n\t\t\tconst result = await repo.on(ItemSchema).one().id('nonexistent').update({ title: 'X' })\n\t\t\texpect(result).toBeNull()\n\t\t})\n\t})\n\n\tdescribe('one().where().upsert() (replaces upsertOne)', () => {\n\t\tlet upsertCounter = 0\n\n\t\tconst UpsertSchema = Schema.from('upserts')\n\t\t\t.pk('id', v.string(), () => `up-${++upsertCounter}`)\n\t\t\t.field('email', v.string())\n\t\t\t.field('name', v.string())\n\t\t\t.field('views', v.number())\n\t\t\t.field('createdAt', v.number(), { onCreate: () => 1000 })\n\t\t\t.field('updatedAt', v.number(), { onCreate: () => 1000, onUpdate: () => 9999 })\n\t\t\t.build()\n\n\t\tfunction makeUpsertRepo() {\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\treturn Repo.from(adapter)\n\t\t\t\t.resolve((s) => ({ table: s.name }))\n\t\t\t\t.build()\n\t\t}\n\n\t\ttest('create-then-update path: row missing → inserts and applies update', async () => {\n\t\t\tconst repo = makeUpsertRepo()\n\n\t\t\tconst result = await repo\n\t\t\t\t.on(UpsertSchema)\n\t\t\t\t.one()\n\t\t\t\t.where((q) => q.eq('email', 'alice@test.com'))\n\t\t\t\t.upsert({\n\t\t\t\t\tcreate: { email: 'alice@test.com', name: 'Alice', views: 0 },\n\t\t\t\t\tupdate: { name: 'Alice Updated' },\n\t\t\t\t})\n\n\t\t\texpect(result.email).toBe('alice@test.com')\n\t\t\texpect(result.name).toBe('Alice Updated')\n\t\t\texpect(result.createdAt).toBe(1000)\n\t\t})\n\n\t\ttest('create path with set value overriding create field is allowed', async () => {\n\t\t\tconst repo = makeUpsertRepo()\n\n\t\t\tconst result = await repo\n\t\t\t\t.on(UpsertSchema)\n\t\t\t\t.one()\n\t\t\t\t.where((q) => q.eq('email', 'override@test.com'))\n\t\t\t\t.upsert({\n\t\t\t\t\tcreate: { email: 'override@test.com', name: 'Original', views: 0 },\n\t\t\t\t\tupdate: { views: 42 },\n\t\t\t\t})\n\n\t\t\texpect(result.views).toBe(42)\n\t\t\texpect(result.name).toBe('Original')\n\t\t})\n\n\t\ttest('update-only-on-exists path: row exists → ignores create, applies update + auto-bump', async () => {\n\t\t\tconst repo = makeUpsertRepo()\n\n\t\t\tawait repo.on(UpsertSchema).one().create({ email: 'bob@test.com', name: 'Bob', views: 42 })\n\n\t\t\tconst result = await repo\n\t\t\t\t.on(UpsertSchema)\n\t\t\t\t.one()\n\t\t\t\t.where((q) => q.eq('email', 'bob@test.com'))\n\t\t\t\t.upsert({\n\t\t\t\t\tcreate: { email: 'bob@test.com', name: 'IGNORED', views: 0 },\n\t\t\t\t\tupdate: { name: 'Bob Updated' },\n\t\t\t\t})\n\n\t\t\texpect(result.name).toBe('Bob Updated')\n\t\t\texpect(result.views).toBe(42)\n\t\t\texpect(result.updatedAt).toBe(9999)\n\t\t})\n\n\t\ttest('create-vs-op conflict throws OrmValidationError with kind conflicting-ops', async () => {\n\t\t\tconst { IncOp } = await import('../updates')\n\t\t\tconst { OrmValidationError } = await import('../errors')\n\t\t\tconst repo = makeUpsertRepo()\n\n\t\t\ttry {\n\t\t\t\tawait repo\n\t\t\t\t\t.on(UpsertSchema)\n\t\t\t\t\t.one()\n\t\t\t\t\t.where((q) => q.eq('email', 'conflict@test.com'))\n\t\t\t\t\t.upsert({\n\t\t\t\t\t\tcreate: { email: 'conflict@test.com', name: 'Conflict', views: 0 },\n\t\t\t\t\t\tupdate: { views: new IncOp('views', 1) },\n\t\t\t\t\t})\n\t\t\t\texpect.unreachable()\n\t\t\t} catch (e) {\n\t\t\t\texpect(e).toBeInstanceOf(OrmValidationError)\n\t\t\t\tconst err = e as InstanceType<typeof OrmValidationError>\n\t\t\t\texpect(err.kind).toBe('conflicting-ops')\n\t\t\t\texpect(err.operation).toBe('upsertOne')\n\t\t\t\texpect(err.failures[0].field).toBe('views')\n\t\t\t}\n\t\t})\n\n\t\ttest('empty-update-allowed: create with no update is allowed', async () => {\n\t\t\tconst repo = makeUpsertRepo()\n\n\t\t\tconst result = await repo\n\t\t\t\t.on(UpsertSchema)\n\t\t\t\t.one()\n\t\t\t\t.where((q) => q.eq('email', 'noops@test.com'))\n\t\t\t\t.upsert({ create: { email: 'noops@test.com', name: 'NoOps', views: 0 } })\n\n\t\t\texpect(result.email).toBe('noops@test.com')\n\t\t\texpect(result.name).toBe('NoOps')\n\t\t})\n\n\t\ttest('empty-update-allowed: if exists do nothing', async () => {\n\t\t\tconst repo = makeUpsertRepo()\n\n\t\t\tawait repo.on(UpsertSchema).one().create({ email: 'exists@test.com', name: 'Original', views: 5 })\n\n\t\t\tconst result = await repo\n\t\t\t\t.on(UpsertSchema)\n\t\t\t\t.one()\n\t\t\t\t.where((q) => q.eq('email', 'exists@test.com'))\n\t\t\t\t.upsert({ create: { email: 'exists@test.com', name: 'IGNORED', views: 0 } })\n\n\t\t\texpect(result.name).toBe('Original')\n\t\t\texpect(result.views).toBe(5)\n\t\t})\n\n\t\ttest('upsert-returns-document rule: returns resulting document', async () => {\n\t\t\tconst repo = makeUpsertRepo()\n\n\t\t\tconst insertResult = await repo\n\t\t\t\t.on(UpsertSchema)\n\t\t\t\t.one()\n\t\t\t\t.where((q) => q.eq('email', 'doc@test.com'))\n\t\t\t\t.upsert({ create: { email: 'doc@test.com', name: 'Doc', views: 0 } })\n\t\t\texpect(insertResult.id).toBeDefined()\n\t\t\texpect(insertResult.email).toBe('doc@test.com')\n\n\t\t\tconst updateResult = await repo\n\t\t\t\t.on(UpsertSchema)\n\t\t\t\t.one()\n\t\t\t\t.where((q) => q.eq('email', 'doc@test.com'))\n\t\t\t\t.upsert({\n\t\t\t\t\tcreate: { email: 'doc@test.com', name: 'IGNORED', views: 0 },\n\t\t\t\t\tupdate: { name: 'DocUpdated' },\n\t\t\t\t})\n\t\t\texpect(updateResult.id).toBe(insertResult.id)\n\t\t\texpect(updateResult.name).toBe('DocUpdated')\n\t\t})\n\n\t\ttest('validates create payload via validateCreate (onCreate defaults injected)', async () => {\n\t\t\tconst repo = makeUpsertRepo()\n\n\t\t\tconst result = await repo\n\t\t\t\t.on(UpsertSchema)\n\t\t\t\t.one()\n\t\t\t\t.where((q) => q.eq('email', 'defaults@test.com'))\n\t\t\t\t.upsert({ create: { email: 'defaults@test.com', name: 'Defaults', views: 0 } })\n\n\t\t\texpect(result.id).toBeDefined()\n\t\t\texpect(result.createdAt).toBe(1000)\n\t\t})\n\n\t\ttest('validates create payload and throws OrmValidationError on invalid', async () => {\n\t\t\tconst { OrmValidationError } = await import('../errors')\n\t\t\tconst repo = makeUpsertRepo()\n\n\t\t\tawait expect(\n\t\t\t\trepo\n\t\t\t\t\t.on(UpsertSchema)\n\t\t\t\t\t.one()\n\t\t\t\t\t.where((q) => q.eq('email', 'bad@test.com'))\n\t\t\t\t\t.upsert({ create: { email: 123 as any, name: 'Bad', views: 0 } }),\n\t\t\t).rejects.toBeInstanceOf(OrmValidationError)\n\t\t})\n\n\t\ttest('upsert-filter-incompatible error from adapter boundary', async () => {\n\t\t\tconst { OrmValidationError } = await import('../errors')\n\n\t\t\tconst spy = mockInstance()\n\t\t\tclass RestrictedAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({ table: v.string() })\n\t\t\t\treadonly supportedFieldTypes = ['string', 'number', 'boolean'] as const\n\t\t\t\treadonly queryableOps = ['eq'] as const\n\t\t\t\treadonly updateOps = ['set'] as const\n\t\t\t\tasync findMany() { return [] }\n\t\t\t\tasync upsertOne(_schema: any, _config: any, _filter: any, _create: any, _ops: any): Promise<Record<string, unknown>> {\n\t\t\t\t\tthrow new OrmValidationError('upsert-filter-incompatible', 'test', 'upsertOne', [\n\t\t\t\t\t\t{ cause: 'adapter requires single eq filter on unique column, received complex filter' },\n\t\t\t\t\t])\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst restrictedAdapter = new (RestrictedAdapter as any)() as InstanceType<typeof RestrictedAdapter>\n\t\t\tspy.mockRestore()\n\n\t\t\tconst repo = Repo.from(restrictedAdapter)\n\t\t\t\t.resolve(() => ({ table: 'test' }))\n\t\t\t\t.build()\n\n\t\t\ttry {\n\t\t\t\tawait repo\n\t\t\t\t\t.on(UpsertSchema)\n\t\t\t\t\t.one()\n\t\t\t\t\t.where((q: any) => q.eq('email', 'x@test.com'))\n\t\t\t\t\t.upsert({ create: { email: 'x@test.com', name: 'X', views: 0 } })\n\t\t\t\texpect.unreachable()\n\t\t\t} catch (e) {\n\t\t\t\texpect(e).toBeInstanceOf(OrmValidationError)\n\t\t\t\tconst err = e as InstanceType<typeof OrmValidationError>\n\t\t\t\texpect(err.kind).toBe('upsert-filter-incompatible')\n\t\t\t}\n\t\t})\n\t})\n\n\tdescribe('type-level: upsert gating', () => {\n\t\ttest('adapter with upsertOne enables one().upsert', () => {\n\t\t\tclass WithUpsertAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string', 'number'] as const\n\t\t\t\treadonly queryableOps = ['eq'] as const\n\t\t\t\treadonly updateOps = ['set'] as const\n\t\t\t\tasync findMany() { return [] }\n\t\t\t\tasync upsertOne() { return {} }\n\t\t\t}\n\t\t\ttype A = WithUpsertAdapter\n\t\t\ttype S = import('../schema').AnySchema\n\t\t\ttype One = import('./builders').OneBuilderSurface<S, A>\n\t\t\texpectTypeOf<One['upsert']>().toBeFunction()\n\t\t})\n\n\t\ttest('adapter without upsertOne collapses one().upsert to never', () => {\n\t\t\tclass NoUpsertAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string', 'number'] as const\n\t\t\t\treadonly queryableOps = ['eq'] as const\n\t\t\t\treadonly updateOps = ['set'] as const\n\t\t\t\tasync findMany() { return [] }\n\t\t\t}\n\t\t\ttype A = NoUpsertAdapter\n\t\t\ttype S = import('../schema').AnySchema\n\t\t\ttype One = import('./builders').OneBuilderSurface<S, A>\n\t\t\texpectTypeOf<One['upsert']>().toBeNever()\n\t\t})\n\n\t\ttest('adapter without upsertOne (crud-only) collapses one().upsert to never', () => {\n\t\t\tclass CrudOnlyAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string', 'number'] as const\n\t\t\t\treadonly updateOps = ['set'] as const\n\t\t\t\tasync findByPk() { return null }\n\t\t\t}\n\t\t\ttype A = CrudOnlyAdapter\n\t\t\ttype S = import('../schema').AnySchema\n\t\t\ttype One = import('./builders').OneBuilderSurface<S, A>\n\t\t\texpectTypeOf<One['upsert']>().toBeNever()\n\t\t})\n\t})\n\n\tdescribe('repo.session — transactional behaviour', () => {\n\t\tconst SchemaA = Schema.from('accounts')\n\t\t\t.pk('id', v.string(), () => `a-${Math.random().toString(36).slice(2)}`)\n\t\t\t.field('balance', v.number())\n\t\t\t.build()\n\t\tconst SchemaB = Schema.from('ledger')\n\t\t\t.pk('id', v.string(), () => `l-${Math.random().toString(36).slice(2)}`)\n\t\t\t.field('amount', v.number())\n\t\t\t.field('accountId', v.string())\n\t\t\t.build()\n\n\t\tfunction makeSessionRepo() {\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\treturn Repo.from(adapter)\n\t\t\t\t.resolve((s) => ({ table: s.name }))\n\t\t\t\t.build()\n\t\t}\n\n\t\ttest('throw-to-rollback: uncaught throw rolls back all writes and rejects with same error', async () => {\n\t\t\tconst repo = makeSessionRepo()\n\t\t\tawait repo.on(SchemaA).one().create({ balance: 100 })\n\n\t\t\tconst err = new Error('boom')\n\t\t\tawait expect(\n\t\t\t\trepo.session(async () => {\n\t\t\t\t\tawait repo.on(SchemaA).one().create({ balance: 200 })\n\t\t\t\t\tthrow err\n\t\t\t\t}),\n\t\t\t).rejects.toBe(err)\n\n\t\t\tconst all = await repo\n\t\t\t\t.on(SchemaA)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.gte('balance', 0))\n\t\t\t\t.find()\n\t\t\texpect(all).toHaveLength(1)\n\t\t\texpect(all[0].balance).toBe(100)\n\t\t})\n\n\t\ttest('return-to-commit: successful return commits and resolves with callback value', async () => {\n\t\t\tconst repo = makeSessionRepo()\n\t\t\tconst result = await repo.session(async () => {\n\t\t\t\tawait repo.on(SchemaA).one().create({ balance: 500 })\n\t\t\t\treturn 'committed'\n\t\t\t})\n\n\t\t\texpect(result).toBe('committed')\n\t\t\tconst all = await repo\n\t\t\t\t.on(SchemaA)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.gte('balance', 0))\n\t\t\t\t.find()\n\t\t\texpect(all).toHaveLength(1)\n\t\t\texpect(all[0].balance).toBe(500)\n\t\t})\n\n\t\ttest('cross-schema session: multiple schemas share one tx; on throw both roll back', async () => {\n\t\t\tconst repo = makeSessionRepo()\n\t\t\tconst account = await repo.on(SchemaA).one().create({ balance: 1000 })\n\n\t\t\tawait expect(\n\t\t\t\trepo.session(async () => {\n\t\t\t\t\tawait repo\n\t\t\t\t\t\t.on(SchemaA)\n\t\t\t\t\t\t.all()\n\t\t\t\t\t\t.where((q) => q.eq('id', account.id))\n\t\t\t\t\t\t.update({ balance: 900 })\n\t\t\t\t\tawait repo.on(SchemaB).one().create({ amount: 100, accountId: account.id })\n\t\t\t\t\tthrow new Error('tx failure')\n\t\t\t\t}),\n\t\t\t).rejects.toThrow('tx failure')\n\n\t\t\tconst acct = await repo.on(SchemaA).one().id(account.id).find()\n\t\t\texpect(acct!.balance).toBe(1000)\n\n\t\t\tconst entries = await repo\n\t\t\t\t.on(SchemaB)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.eq('accountId', account.id))\n\t\t\t\t.find()\n\t\t\texpect(entries).toHaveLength(0)\n\t\t})\n\n\t\ttest('nested session: inner repo.session delegates to adapter (flat in in-memory)', async () => {\n\t\t\tconst repo = makeSessionRepo()\n\n\t\t\tconst result = await repo.session(async () => {\n\t\t\t\tawait repo.on(SchemaA).one().create({ balance: 100 })\n\t\t\t\tconst inner = await repo.session(async () => {\n\t\t\t\t\tawait repo.on(SchemaA).one().create({ balance: 200 })\n\t\t\t\t\treturn 'inner'\n\t\t\t\t})\n\t\t\t\treturn inner\n\t\t\t})\n\n\t\t\texpect(result).toBe('inner')\n\t\t\tconst all = await repo\n\t\t\t\t.on(SchemaA)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.gte('balance', 0))\n\t\t\t\t.find()\n\t\t\texpect(all).toHaveLength(2)\n\t\t})\n\n\t\ttest('nested session: throw in inner rolls back entire outer tx (flat nesting)', async () => {\n\t\t\tconst repo = makeSessionRepo()\n\t\t\tawait repo.on(SchemaA).one().create({ balance: 50 })\n\n\t\t\tawait expect(\n\t\t\t\trepo.session(async () => {\n\t\t\t\t\tawait repo.on(SchemaA).one().create({ balance: 100 })\n\t\t\t\t\tawait repo.session(async () => {\n\t\t\t\t\t\tawait repo.on(SchemaA).one().create({ balance: 200 })\n\t\t\t\t\t\tthrow new Error('inner boom')\n\t\t\t\t\t})\n\t\t\t\t}),\n\t\t\t).rejects.toThrow('inner boom')\n\n\t\t\tconst all = await repo\n\t\t\t\t.on(SchemaA)\n\t\t\t\t.all()\n\t\t\t\t.where((q) => q.gte('balance', 0))\n\t\t\t\t.find()\n\t\t\texpect(all).toHaveLength(1)\n\t\t\texpect(all[0].balance).toBe(50)\n\t\t})\n\n\t\ttest('no-tx-argument rule: session callback receives no arguments', async () => {\n\t\t\tconst repo = makeSessionRepo()\n\t\t\tlet argCount = -1\n\t\t\tawait repo.session(async function () {\n\t\t\t\targCount = arguments.length\n\t\t\t})\n\t\t\texpect(argCount).toBe(0)\n\t\t})\n\t})\n\n\tdescribe('type-level: session gating', () => {\n\t\ttest('missing session collapses repo.session to never', () => {\n\t\t\tclass NoSessionAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\tasync findMany() { return [] }\n\t\t\t}\n\t\t\ttype A = NoSessionAdapter\n\t\t\tconst _repo = {} as import('./repo').RepoSurface<A>\n\t\t\texpectTypeOf(_repo.session).toBeNever()\n\t\t})\n\n\t\ttest('adapter with session enables repo.session', () => {\n\t\t\tclass WithSessionAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\tasync session<T>(fn: () => Promise<T>): Promise<T> { return fn() }\n\t\t\t}\n\t\t\ttype A = WithSessionAdapter\n\t\t\tconst _repo = {} as import('./repo').RepoSurface<A>\n\t\t\texpectTypeOf(_repo.session).toBeFunction()\n\t\t})\n\t})\n\n\tdescribe('ALS-backed resolve', () => {\n\t\ttest('reads inside repo.resolve see the override', async () => {\n\t\t\tconst seenConfigs: unknown[] = []\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst origUse = adapter.use.bind(adapter)\n\t\t\t;(adapter as any).use = vi.fn((s: any, config: any) => {\n\t\t\t\tseenConfigs.push(config)\n\t\t\t\treturn origUse(s, config)\n\t\t\t})\n\n\t\t\tconst TestSchema = Schema.from('test')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\t\t\tconst repo = Repo.from(adapter)\n\t\t\t\t.resolve((s) => ({ table: s.name }))\n\t\t\t\t.build()\n\n\t\t\tawait repo.resolve(\n\t\t\t\t(config) => ({ table: `override_${config.table}` }),\n\t\t\t\tasync () => {\n\t\t\t\t\tawait repo.on(TestSchema).all().find()\n\t\t\t\t},\n\t\t\t)\n\n\t\t\texpect(seenConfigs[0]).toEqual({ table: 'override_test' })\n\t\t})\n\n\t\ttest('reads outside repo.resolve see the default config', async () => {\n\t\t\tconst seenConfigs: unknown[] = []\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst origUse = adapter.use.bind(adapter)\n\t\t\t;(adapter as any).use = vi.fn((s: any, config: any) => {\n\t\t\t\tseenConfigs.push(config)\n\t\t\t\treturn origUse(s, config)\n\t\t\t})\n\n\t\t\tconst TestSchema = Schema.from('test')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\t\t\tconst repo = Repo.from(adapter)\n\t\t\t\t.resolve((s) => ({ table: s.name }))\n\t\t\t\t.build()\n\n\t\t\tawait repo.resolve(\n\t\t\t\t(config) => ({ table: `scoped_${config.table}` }),\n\t\t\t\tasync () => {},\n\t\t\t)\n\n\t\t\tawait repo.on(TestSchema).all().find()\n\t\t\texpect(seenConfigs[0]).toEqual({ table: 'test' })\n\t\t})\n\n\t\ttest('two parallel repo.resolve calls do not bleed into each other', async () => {\n\t\t\tconst seenConfigs: unknown[] = []\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst origUse = adapter.use.bind(adapter)\n\t\t\t;(adapter as any).use = vi.fn((s: any, config: any) => {\n\t\t\t\tseenConfigs.push(config)\n\t\t\t\treturn origUse(s, config)\n\t\t\t})\n\n\t\t\tconst TestSchema = Schema.from('test')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\t\t\tconst repo = Repo.from(adapter)\n\t\t\t\t.resolve((s) => ({ table: s.name }))\n\t\t\t\t.build()\n\n\t\t\tawait Promise.all([\n\t\t\t\trepo.resolve(\n\t\t\t\t\t(config) => ({ table: `tenant1_${config.table}` }),\n\t\t\t\t\tasync () => {\n\t\t\t\t\t\tawait new Promise((r) => setTimeout(r, 10))\n\t\t\t\t\t\tawait repo.on(TestSchema).all().find()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t\trepo.resolve(\n\t\t\t\t\t(config) => ({ table: `tenant2_${config.table}` }),\n\t\t\t\t\tasync () => {\n\t\t\t\t\t\tawait new Promise((r) => setTimeout(r, 10))\n\t\t\t\t\t\tawait repo.on(TestSchema).all().find()\n\t\t\t\t\t},\n\t\t\t\t),\n\t\t\t])\n\n\t\t\texpect(seenConfigs).toContainEqual({ table: 'tenant1_test' })\n\t\t\texpect(seenConfigs).toContainEqual({ table: 'tenant2_test' })\n\t\t\texpect(seenConfigs).not.toContainEqual({ table: 'tenant2_tenant1_test' })\n\t\t\texpect(seenConfigs).not.toContainEqual({ table: 'tenant1_tenant2_test' })\n\t\t})\n\n\t\ttest('overrides survive across awaits inside fn', async () => {\n\t\t\tconst seenConfigs: unknown[] = []\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst origUse = adapter.use.bind(adapter)\n\t\t\t;(adapter as any).use = vi.fn((s: any, config: any) => {\n\t\t\t\tseenConfigs.push(config)\n\t\t\t\treturn origUse(s, config)\n\t\t\t})\n\n\t\t\tconst TestSchema = Schema.from('test')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\t\t\tconst repo = Repo.from(adapter)\n\t\t\t\t.resolve((s) => ({ table: s.name }))\n\t\t\t\t.build()\n\n\t\t\tawait repo.resolve(\n\t\t\t\t(config) => ({ table: `async_${config.table}` }),\n\t\t\t\tasync () => {\n\t\t\t\t\tawait repo.on(TestSchema).all().find()\n\t\t\t\t\tawait new Promise((r) => setTimeout(r, 10))\n\t\t\t\t\tawait repo.on(TestSchema).all().find()\n\t\t\t\t},\n\t\t\t)\n\n\t\t\texpect(seenConfigs).toHaveLength(2)\n\t\t\texpect(seenConfigs[0]).toEqual({ table: 'async_test' })\n\t\t\texpect(seenConfigs[1]).toEqual({ table: 'async_test' })\n\t\t})\n\t})\n\n\tdescribe('class-based OrmAdapter with Repo', async () => {\n\t\tconst { OrmAdapter } = await import('../orm-adapter')\n\t\tconst { FilterGroup } = await import('../filter')\n\t\tconst { OrmValidationError } = await import('../errors')\n\n\t\tfunction makeClassAdapter() {\n\t\t\tconst stores = new Map<string, Map<string, Record<string, unknown>>>()\n\t\t\tfunction getStore(name: string) {\n\t\t\t\tif (!stores.has(name)) stores.set(name, new Map())\n\t\t\t\treturn stores.get(name)!\n\t\t\t}\n\n\t\t\tclass TestClassAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({ table: v.string() })\n\t\t\t\treadonly supportedFieldTypes = ['string', 'number'] as const\n\t\t\t\treadonly queryableOps = ['eq'] as const\n\t\t\t\treadonly updateOps = ['set'] as const\n\n\t\t\t\tasync findMany(_schema: import('../schema').AnySchema, config: unknown, group: import('../filter').FilterGroup) {\n\t\t\t\t\tconst cfg = config as { table: string }\n\t\t\t\t\tconst store = getStore(cfg.table)\n\t\t\t\t\treturn [...store.values()].filter((doc) => {\n\t\t\t\t\t\tfor (const child of group.children) {\n\t\t\t\t\t\t\tif (child instanceof (FilterGroup as any).constructor) continue\n\t\t\t\t\t\t\tconst f = child as any\n\t\t\t\t\t\t\tif (f.op === 'eq' && doc[f.field] !== f.value) return false\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\tasync createMany(s: import('../schema').AnySchema, config: unknown, data: Record<string, unknown>[]) {\n\t\t\t\t\tconst cfg = config as { table: string }\n\t\t\t\t\tconst store = getStore(cfg.table)\n\t\t\t\t\tconst pk = s.pkField.name\n\t\t\t\t\tfor (const d of data) store.set(String(d[pk]), { ...d })\n\t\t\t\t\treturn data.map((d) => ({ ...d }))\n\t\t\t\t}\n\n\t\t\t\tasync updateMany(s: import('../schema').AnySchema, config: unknown, group: import('../filter').FilterGroup, data: Record<string, unknown>) {\n\t\t\t\t\tconst rows = await this.findMany(s, config, group)\n\t\t\t\t\tconst cfg = config as { table: string }\n\t\t\t\t\tconst store = getStore(cfg.table)\n\t\t\t\t\tconst pk = s.pkField.name\n\t\t\t\t\treturn rows.map((row) => {\n\t\t\t\t\t\tconst updated = { ...row, ...data }\n\t\t\t\t\t\tstore.set(String(updated[pk]), updated)\n\t\t\t\t\t\treturn { ...updated }\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\tasync deleteMany(s: import('../schema').AnySchema, config: unknown, group: import('../filter').FilterGroup) {\n\t\t\t\t\tconst rows = await this.findMany(s, config, group)\n\t\t\t\t\tconst cfg = config as { table: string }\n\t\t\t\t\tconst store = getStore(cfg.table)\n\t\t\t\t\tconst pk = s.pkField.name\n\t\t\t\t\tfor (const row of rows) store.delete(String(row[pk]))\n\t\t\t\t\treturn rows\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst spy = mockInstance()\n\t\t\tconst adapter = new (TestClassAdapter as any)() as InstanceType<typeof TestClassAdapter>\n\t\t\tspy.mockRestore()\n\t\t\treturn { adapter, stores }\n\t\t}\n\n\t\tconst TestSchema = Schema.from('class_test')\n\t\t\t.pk('id', v.string(), () => `ct-${Math.random().toString(36).slice(2, 8)}`)\n\t\t\t.field('name', v.string())\n\t\t\t.field('age', v.number())\n\t\t\t.build()\n\n\t\ttest('Repo.from(classAdapter).resolve(...).build() creates a working repo', async () => {\n\t\t\tconst { adapter } = makeClassAdapter()\n\t\t\tconst repo = Repo.from(adapter)\n\t\t\t\t.resolve((s) => ({ table: s.name }))\n\t\t\t\t.build()\n\n\t\t\tconst created = await repo.on(TestSchema).one().create({ name: 'Alice', age: 30 })\n\t\t\texpect(created.name).toBe('Alice')\n\t\t\texpect(created.age).toBe(30)\n\n\t\t\tconst found = await repo.on(TestSchema).one().id(created.id).find()\n\t\t\texpect(found).not.toBeNull()\n\t\t\texpect(found!.name).toBe('Alice')\n\t\t})\n\n\t\ttest('dispatch validator validates config against schemaConfigPipe', async () => {\n\t\t\tconst { adapter } = makeClassAdapter()\n\t\t\tconst repo = Repo.from(adapter)\n\t\t\t\t.resolve(() => ({ table: 123 }) as any)\n\t\t\t\t.build()\n\n\t\t\tawait expect(\n\t\t\t\trepo.on(TestSchema).all().find(),\n\t\t\t).rejects.toBeInstanceOf(OrmValidationError)\n\t\t})\n\n\t\ttest('dispatch validator validates after transforms applied', async () => {\n\t\t\tconst { adapter } = makeClassAdapter()\n\t\t\tconst repo = Repo.from(adapter)\n\t\t\t\t.resolve((s) => ({ table: s.name }))\n\t\t\t\t.build()\n\n\t\t\tawait expect(\n\t\t\t\trepo.resolve(\n\t\t\t\t\t() => ({ table: 42 }) as any,\n\t\t\t\t\tasync () => repo.on(TestSchema).all().find(),\n\t\t\t\t),\n\t\t\t).rejects.toBeInstanceOf(OrmValidationError)\n\t\t})\n\n\t\ttest('createMany + findMany round-trip works', async () => {\n\t\t\tconst { adapter } = makeClassAdapter()\n\t\t\tconst repo = Repo.from(adapter)\n\t\t\t\t.resolve((s) => ({ table: s.name }))\n\t\t\t\t.build()\n\n\t\t\tconst created = await repo.on(TestSchema).all().create([\n\t\t\t\t{ name: 'Alice', age: 30 },\n\t\t\t\t{ name: 'Bob', age: 25 },\n\t\t\t])\n\t\t\texpect(created).toHaveLength(2)\n\n\t\t\tconst all = await repo.on(TestSchema).all().find()\n\t\t\texpect(all).toHaveLength(2)\n\t\t})\n\n\t\ttest('update and delete work on class-based adapter', async () => {\n\t\t\tconst { adapter } = makeClassAdapter()\n\t\t\tconst repo = Repo.from(adapter)\n\t\t\t\t.resolve((s) => ({ table: s.name }))\n\t\t\t\t.build()\n\n\t\t\tconst user = await repo.on(TestSchema).one().create({ name: 'Alice', age: 30 })\n\t\t\tconst updated = await repo.on(TestSchema).one().id(user.id).update({ name: 'Alicia' })\n\t\t\texpect(updated?.name).toBe('Alicia')\n\n\t\t\tconst deleted = await repo.on(TestSchema).one().id(user.id).delete()\n\t\t\texpect(deleted?.id).toBe(user.id)\n\n\t\t\tconst found = await repo.on(TestSchema).one().id(user.id).find()\n\t\t\texpect(found).toBeNull()\n\t\t})\n\t})\n\n\tdescribe('repo.on().aggregate() end-to-end', () => {\n\t\tconst OrderSchema = Schema.from('orders')\n\t\t\t.pk('id', v.string(), () => `o-${Math.random().toString(36).slice(2, 8)}`)\n\t\t\t.field('product', v.string())\n\t\t\t.field('amount', v.number())\n\t\t\t.field('region', v.string())\n\t\t\t.build()\n\n\t\tfunction makeAggRepo() {\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst repo = Repo.from(adapter).resolve((s) => ({ table: s.name })).build()\n\t\t\treturn { repo, adapter }\n\t\t}\n\n\t\tasync function seedOrders(repo: any) {\n\t\t\tawait repo.on(OrderSchema).all().create([\n\t\t\t\t{ product: 'Widget', amount: 10, region: 'US' },\n\t\t\t\t{ product: 'Gadget', amount: 20, region: 'EU' },\n\t\t\t\t{ product: 'Widget', amount: 30, region: 'US' },\n\t\t\t\t{ product: 'Gadget', amount: 40, region: 'EU' },\n\t\t\t\t{ product: 'Widget', amount: 50, region: 'US' },\n\t\t\t])\n\t\t}\n\n\t\ttest('count returns total row count', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tawait seedOrders(repo)\n\t\t\tconst result = await repo.on(OrderSchema).aggregate().count('total').run()\n\t\t\texpect(result).toEqual({ total: 5 })\n\t\t})\n\n\t\ttest('count with where filter returns filtered count', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tawait seedOrders(repo)\n\t\t\tconst result = await repo\n\t\t\t\t.on(OrderSchema)\n\t\t\t\t.aggregate()\n\t\t\t\t.where((q) => q.eq('region', 'US'))\n\t\t\t\t.count('total')\n\t\t\t\t.run()\n\t\t\texpect(result).toEqual({ total: 3 })\n\t\t})\n\n\t\ttest('count on empty result returns zero', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tconst result = await repo.on(OrderSchema).aggregate().count('total').run()\n\t\t\texpect(result).toEqual({ total: 0 })\n\t\t})\n\n\t\ttest('clone-on-step: base builder is not mutated by fan-out', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tawait seedOrders(repo)\n\t\t\tconst base = repo.on(OrderSchema).aggregate().count('a')\n\t\t\tconst withFilter = base.where((q) => q.eq('region', 'US'))\n\t\t\tconst resultAll = await base.count('b').run()\n\t\t\tconst resultFiltered = await withFilter.count('c').run()\n\t\t\texpect(resultAll.a).toBe(5)\n\t\t\texpect(resultFiltered.a).toBe(3)\n\t\t})\n\n\t\ttest('where before count produces correct result', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tawait seedOrders(repo)\n\t\t\tconst result = await repo\n\t\t\t\t.on(OrderSchema)\n\t\t\t\t.aggregate()\n\t\t\t\t.count('total')\n\t\t\t\t.where((q) => q.eq('product', 'Gadget'))\n\t\t\t\t.run()\n\t\t\texpect(result).toEqual({ total: 2 })\n\t\t})\n\n\t\ttest('boundary validation rejects unknown field in where filter', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tawait expect(\n\t\t\t\trepo.on(OrderSchema).aggregate().count('total').where((q) => q.eq('nonexistent', 'x')).run(),\n\t\t\t).rejects.toThrow(OrmValidationError)\n\t\t})\n\n\t\ttest('sum returns total of numeric field', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tawait seedOrders(repo)\n\t\t\tconst result = await repo.on(OrderSchema).aggregate().sum(OrderSchema.fields.amount, 'revenue').run()\n\t\t\texpect(result).toEqual({ revenue: 150 })\n\t\t})\n\n\t\ttest('avg returns average of numeric field', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tawait seedOrders(repo)\n\t\t\tconst result = await repo.on(OrderSchema).aggregate().avg(OrderSchema.fields.amount, 'avgAmount').run()\n\t\t\texpect(result).toEqual({ avgAmount: 30 })\n\t\t})\n\n\t\ttest('min returns minimum value', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tawait seedOrders(repo)\n\t\t\tconst result = await repo.on(OrderSchema).aggregate().min(OrderSchema.fields.amount, 'lowest').run()\n\t\t\texpect(result).toEqual({ lowest: 10 })\n\t\t})\n\n\t\ttest('max returns maximum value', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tawait seedOrders(repo)\n\t\t\tconst result = await repo.on(OrderSchema).aggregate().max(OrderSchema.fields.amount, 'highest').run()\n\t\t\texpect(result).toEqual({ highest: 50 })\n\t\t})\n\n\t\ttest('countDistinct returns unique value count', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tawait seedOrders(repo)\n\t\t\tconst result = await repo.on(OrderSchema).aggregate().countDistinct(OrderSchema.fields.product, 'unique').run()\n\t\t\texpect(result).toEqual({ unique: 2 })\n\t\t})\n\n\t\ttest('min on string field returns lexicographic minimum', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tawait seedOrders(repo)\n\t\t\tconst result = await repo.on(OrderSchema).aggregate().min(OrderSchema.fields.region, 'firstRegion').run()\n\t\t\texpect(result).toEqual({ firstRegion: 'EU' })\n\t\t})\n\n\t\ttest('max on string field returns lexicographic maximum', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tawait seedOrders(repo)\n\t\t\tconst result = await repo.on(OrderSchema).aggregate().max(OrderSchema.fields.region, 'lastRegion').run()\n\t\t\texpect(result).toEqual({ lastRegion: 'US' })\n\t\t})\n\n\t\ttest('combined aggregators in single chain', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tawait seedOrders(repo)\n\t\t\tconst result = await repo\n\t\t\t\t.on(OrderSchema)\n\t\t\t\t.aggregate()\n\t\t\t\t.count('total')\n\t\t\t\t.sum(OrderSchema.fields.amount, 'revenue')\n\t\t\t\t.avg(OrderSchema.fields.amount, 'avgAmount')\n\t\t\t\t.min(OrderSchema.fields.amount, 'lowest')\n\t\t\t\t.max(OrderSchema.fields.amount, 'highest')\n\t\t\t\t.run()\n\t\t\texpect(result).toEqual({ total: 5, revenue: 150, avgAmount: 30, lowest: 10, highest: 50 })\n\t\t})\n\n\t\ttest('groupBy single column returns per-group results', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tawait seedOrders(repo)\n\t\t\tconst result = await repo\n\t\t\t\t.on(OrderSchema)\n\t\t\t\t.aggregate()\n\t\t\t\t.groupBy(OrderSchema.fields.region)\n\t\t\t\t.count('cnt')\n\t\t\t\t.sum(OrderSchema.fields.amount, 'total')\n\t\t\t\t.run()\n\t\t\texpect(result).toHaveLength(2)\n\t\t\tconst us = result.find((r) => r.region === 'US')\n\t\t\tconst eu = result.find((r) => r.region === 'EU')\n\t\t\texpect(us).toEqual({ region: 'US', cnt: 3, total: 90 })\n\t\t\texpect(eu).toEqual({ region: 'EU', cnt: 2, total: 60 })\n\t\t})\n\n\t\ttest('groupBy multi-column returns per-combo results', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tawait seedOrders(repo)\n\t\t\tconst result = await repo\n\t\t\t\t.on(OrderSchema)\n\t\t\t\t.aggregate()\n\t\t\t\t.groupBy(OrderSchema.fields.region, OrderSchema.fields.product)\n\t\t\t\t.count('cnt')\n\t\t\t\t.run()\n\t\t\texpect(result).toHaveLength(2)\n\t\t\tconst usWidget = result.find((r) => r.region === 'US' && r.product === 'Widget')\n\t\t\tconst euGadget = result.find((r) => r.region === 'EU' && r.product === 'Gadget')\n\t\t\texpect(usWidget?.cnt).toBe(3)\n\t\t\texpect(euGadget?.cnt).toBe(2)\n\t\t})\n\n\t\ttest('where + groupBy filters before grouping', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tawait seedOrders(repo)\n\t\t\tconst result = await repo\n\t\t\t\t.on(OrderSchema)\n\t\t\t\t.aggregate()\n\t\t\t\t.where((q) => q.gt('amount', 15))\n\t\t\t\t.groupBy(OrderSchema.fields.region)\n\t\t\t\t.count('cnt')\n\t\t\t\t.run()\n\t\t\tconst us = result.find((r) => r.region === 'US')\n\t\t\tconst eu = result.find((r) => r.region === 'EU')\n\t\t\texpect(us?.cnt).toBe(2)\n\t\t\texpect(eu?.cnt).toBe(2)\n\t\t})\n\n\t\ttest('having filters after aggregation', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tawait seedOrders(repo)\n\t\t\tconst result = await repo\n\t\t\t\t.on(OrderSchema)\n\t\t\t\t.aggregate()\n\t\t\t\t.groupBy(OrderSchema.fields.region)\n\t\t\t\t.count('cnt')\n\t\t\t\t.sum(OrderSchema.fields.amount, 'total')\n\t\t\t\t.having((q) => q.gt('total', 80))\n\t\t\t\t.run()\n\t\t\texpect(result).toHaveLength(1)\n\t\t\texpect(result[0].region).toBe('US')\n\t\t\texpect(result[0].total).toBe(90)\n\t\t})\n\n\t\ttest('where + having work together', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tawait seedOrders(repo)\n\t\t\tconst result = await repo\n\t\t\t\t.on(OrderSchema)\n\t\t\t\t.aggregate()\n\t\t\t\t.where((q) => q.gt('amount', 15))\n\t\t\t\t.groupBy(OrderSchema.fields.region)\n\t\t\t\t.count('cnt')\n\t\t\t\t.having((q) => q.gte('cnt', 2))\n\t\t\t\t.run()\n\t\t\texpect(result).toHaveLength(2)\n\t\t})\n\n\t\ttest('countDistinct with groupBy', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tawait seedOrders(repo)\n\t\t\tconst result = await repo\n\t\t\t\t.on(OrderSchema)\n\t\t\t\t.aggregate()\n\t\t\t\t.groupBy(OrderSchema.fields.region)\n\t\t\t\t.countDistinct(OrderSchema.fields.product, 'uniqueProducts')\n\t\t\t\t.run()\n\t\t\tconst us = result.find((r) => r.region === 'US')\n\t\t\tconst eu = result.find((r) => r.region === 'EU')\n\t\t\texpect(us?.uniqueProducts).toBe(1)\n\t\t\texpect(eu?.uniqueProducts).toBe(1)\n\t\t})\n\n\t\ttest('step order is irrelevant: groupBy before or after aggregators', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tawait seedOrders(repo)\n\t\t\tconst resultA = await repo\n\t\t\t\t.on(OrderSchema)\n\t\t\t\t.aggregate()\n\t\t\t\t.groupBy(OrderSchema.fields.region)\n\t\t\t\t.count('cnt')\n\t\t\t\t.run()\n\t\t\tconst resultB = await repo\n\t\t\t\t.on(OrderSchema)\n\t\t\t\t.aggregate()\n\t\t\t\t.count('cnt')\n\t\t\t\t.groupBy(OrderSchema.fields.region)\n\t\t\t\t.run()\n\t\t\texpect(resultA.sort((a, b) => a.region.localeCompare(b.region)))\n\t\t\t\t.toEqual(resultB.sort((a, b) => a.region.localeCompare(b.region)))\n\t\t})\n\n\t\ttest('boundary validation rejects unknown groupBy field', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tawait expect(\n\t\t\t\t(repo.on(OrderSchema).aggregate() as any).groupBy({ name: 'nonexistent', path: ['nonexistent'] }).count('total').run(),\n\t\t\t).rejects.toThrow(OrmValidationError)\n\t\t})\n\n\t\ttest('boundary validation rejects unknown having field', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tawait expect(\n\t\t\t\trepo\n\t\t\t\t\t.on(OrderSchema)\n\t\t\t\t\t.aggregate()\n\t\t\t\t\t.groupBy(OrderSchema.fields.region)\n\t\t\t\t\t.count('cnt')\n\t\t\t\t\t.having((q) => q.gt('nonexistent', 0))\n\t\t\t\t\t.run(),\n\t\t\t).rejects.toThrow(OrmValidationError)\n\t\t})\n\n\t\ttest('min/max on empty store returns null', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tconst result = await repo\n\t\t\t\t.on(OrderSchema)\n\t\t\t\t.aggregate()\n\t\t\t\t.min(OrderSchema.fields.amount, 'lowest')\n\t\t\t\t.max(OrderSchema.fields.amount, 'highest')\n\t\t\t\t.run()\n\t\t\texpect(result).toEqual({ lowest: null, highest: null })\n\t\t})\n\n\t\ttest('avg on empty store returns zero', async () => {\n\t\t\tconst { repo } = makeAggRepo()\n\t\t\tconst result = await repo.on(OrderSchema).aggregate().avg(OrderSchema.fields.amount, 'avgAmount').run()\n\t\t\texpect(result).toEqual({ avgAmount: 0 })\n\t\t})\n\t})\n\n}\n"],"mappings":"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","names":["OrmMigrationError","EquippedError","opts","renderFilter","group","Filter","f","renderFilterTree","node","parts","c","OrmNotFoundError","EquippedError","opts","msg","OrmReplayError","EquippedError","opts","ulid","v","ulid","v","SchemaBuilder","_SchemaBuilder","#name","#pkField","#fieldDefs","#computedDefs","name","pkField","fieldDefs","computedDefs","pipe","generate","SchemaField","opts","nextFields","deps","compute","nextComputed","ComputedField","self","Schema","field","EventLogSchema","Schema","v","ulid","fire","repo","name","def","payload","ctx","validated","v","OrmValidationError","at","ts","key","ulid","by","EventLogSchema","evCtx","HandlerRegistry","#handlers","#order","name","def","EquippedError","executeReplay","repo","def","row","evCtx","cause","OrmReplayError","replay","registry","opts","query","EventLogSchema","q","rows","rerun","key","EventLog","_EventLog","#repo","#registry","repo","registry","name","def","payload","ctx","fire","opts","replay","key","rerun","HandlerRegistry","EMISSION_ORDER","adapterSupports","adapter","kind","method","fieldsEqual","target","discovered","toFieldPayload","f","extractSchemaInfo","schema","pkField","pkType","inferFieldType","fields","field","isNullable","KNOWN_FIELD_TYPES","pipe","std","diffSchemas","current","changes","currentByName","targetByName","disc","fk","idx","info","discFieldsByName","targetFieldsByName","discField","tField","existing","c","a","b","MigrationCodegen","_MigrationCodegen","#adapter","#target","_repo","adapter","target","current","changes","diffSchemas","repo","schemas","applyChange","adapter","repo","change","methodName","method","computePending","declared","applied","opts","appliedIds","a","declaredIds","d","orphans","OrmMigrationError","sorted","b","pending","skipped","m","toIdx","isEmpty","s","validateFieldSpec","ctx","fieldSpec","fieldLabel","fail","field","cause","validateChange","adapter","change","migrationId","changeIndex","failures","methodName","fieldNames","f","assertNormalisedChanges","migrations","seenIds","m","i","OrmValidationError","Migrator","_Migrator","#repo","#adapter","#migrations","#withoutLock","repo","adapter","migrations","withoutLock","#requireMethod","name","phase","OrmMigrationError","opts","#runPending","err","applied","appliedMap","a","b","m","appliedAt","pending","computePending","skipped","ran","execute","applyChange","assertNormalisedChanges","OrderBy","field","direction","toFieldName","ManyRelation","name","source","target","foreignKey","references","OneRelation","fkOwner","RelationsBuilder","_RelationsBuilder","#source","#defs","defs","fk","nextDefs","ref","Relations","v","planSelection","schema","select","computedDefs","computedNames","persistedNames","requestedSelect","adapterSelect","selectedComputeNames","key","dep","EquippedError","applyComputedSelection","rows","plan","row","enriched","computeName","def","depInput","r","v","shaped","FALLBACK_PAGINATION_DEFAULT_LIMIT","MAX_PRELOAD_DEPTH","isRelDef","def","ManyRelation","OneRelation","isNestedPreloadDef","relationStep","isPositiveInteger","value","isNonNegativeInteger","getPaginationDefaultLimit","Instance","collectSelectFailures","schema","select","failures","persistedNames","computedNames","field","collectPreloadFailures","defs","depth","path","preload","rawDef","step","nested","collectLimitFailure","collectOffsetFailure","collectPageFailure","collectBatchSizeFailure","collectIterationOptionFailures","options","throwIfFailures","operation","OrmValidationError","assertNormalisedFindReadShape","state","normaliseAllFindReadShape","limit","offset","page","resolvedOffset","normaliseAllIterateReadShape","batchSize","normaliseAllPaginateReadShape","current","v","tryValidateCreateRow","s","data","failures","result","key","entry","pipe","v","fieldValue","validated","validateCreate","OrmValidationError","validateCreateMany","rows","allFailures","i","failure","validateUpdate","pipes","ops","value","isUpdateOp","r","validateUpdateOps","schema","operation","touched","field","opTouchedFields","autoBumped","bumpOp","SetOp","allOps","conflicts","indices","op","fieldDef","composeSchemaConfig","resolve","transforms","config","transform","validateUpsertConflicts","rawCreate","createFields","runOneRead","context","state","assertNormalisedFilter","assertNormalisedFindReadShape","row","runAllRead","query","normaliseAllFindReadShape","plan","planSelection","rows","runAllCount","toPaginated","items","total","limit","current","last","previous","next","runAllPaginate","normaliseAllPaginateReadShape","use","runAllIterate","options","normaliseAllIterateReadShape","first","runOneCreate","data","validated","validateCreate","resolved","runAllCreate","validateCreateMany","runOneUpdate","validateUpdate","runAllUpdate","runOneUpsert","create","ops","plainValues","key","value","isUpdateOp","SetOp","validateUpsertConflicts","runOneDelete","runAllDelete","runAggregate","spec","assertNormalisedAggregate","MAX_PRELOAD_DEPTH","isNestedPreloadDef","def","relationStep","uniqueDefinedValues","entities","key","entity","value","attachOneRelation","name","lookupKey","lookup","attachManyRelation","normalizePreloads","defs","ManyRelation","OneRelation","resolvePreloads","getUse","resolvePreloadNodes","depth","path","resolvePreload","node","EquippedError","target","step","nextPath","refCol","fkValues","e","related","FilterGroup","r","refValues","grouped","fk","SchemaContext","schema","getUse","select","preloads","rows","plan","planSelection","selected","applyComputedSelection","resolvePreloads","row","resolved","ReadSelectState","context","state","FilterGroup","factory","nextGroup","fields","defs","next","SchemaRef","#context","OneBuilder","AllBuilder","AggregateBuilder","args","_OneBuilder","reqState","value","message","result","operation","OrmNotFoundError","data","runOneCreate","runOneUpdate","runOneUpsert","runOneDelete","runOneRead","_AllBuilder","#orderBy","#limitSource","#offsetSource","queryState","#withQuery","queryOverride","has","key","field","direction","OrderBy","limit","offset","page","runAllCreate","runAllUpdate","runAllDelete","runAllRead","runAllCount","runAllPaginate","options","runAllIterate","_AggregateBuilder","#where","#having","#aggregates","#groupBy","#state","f","toFieldName","alias","_","spec","runAggregate","AsyncLocalStorage","store","run","transform","fn","current","currentTransforms","Repo","#adapter","#defaults","#schemaConfigPipe","adapter","resolve","#getConfig","s","transforms","currentTransforms","composeSchemaConfig","config","transform","#getUse","schema","SchemaRef","SchemaContext","target","RepoBuilder","fn","resolver","run","_RepoBuilder","#resolve"]}