{"version":3,"sources":["../../../../../src/orm/adapters/json/index.ts","../../../../../src/utilities/configurable.ts","../../../../../src/errors/equippedError.ts","../../../../../src/instance/index.ts","../../../../../src/instance/hooks.ts","../../../../../src/instance/settings.ts","../../../../../src/orm/fields.ts","../../../../../src/orm/filter.ts","../../../../../src/orm/orm-adapter.ts","../../../../../src/orm/adapters/in-memory/index.ts","../../../../../src/orm/updates.ts"],"sourcesContent":["import { open, readFile, rename, unlink, writeFile } from 'node:fs/promises'\n\nimport { v } from 'valleyed'\n\nimport { configurable } from '../../../utilities/configurable'\nimport type { FilterGroup } from '../../filter'\nimport type { DiscoveredSchema } from '../../migrations/introspection-types'\nimport type { AddFieldChange, AddForeignKeyChange, AddIndexChange, AnyFieldSpec, CreateTableChange, DropFieldChange, DropForeignKeyChange, DropIndexChange, DropTableChange, ModifyFieldChange, RenameFieldChange, RenameTableChange } from '../../migrations/types'\nimport { OrmAdapter, type AggregateSpec } from '../../orm-adapter'\nimport type { IterationQueryOptions, QueryOptions } from '../../query-options'\nimport type { AnySchema } from '../../schema'\nimport type { AnyUpdateOp } from '../../updates'\nimport { InMemoryAdapter, type InMemoryRepoConfig } from '../in-memory'\n\nexport type JsonAdapterConfig = InMemoryRepoConfig\n\nconst jsonConnectionPipe = () =>\n\tv.object({\n\t\tfilePath: v.string(),\n\t})\n\nexport class JsonAdapter extends configurable(jsonConnectionPipe, OrmAdapter) {\n\treadonly schemaConfigPipe = v.object({ table: v.string() })\n\n\treadonly #inMemory = InMemoryAdapter.create({})\n\n\treadonly supportedFieldTypes = this.#inMemory.supportedFieldTypes\n\treadonly queryableOps = this.#inMemory.queryableOps\n\treadonly updateOps = this.#inMemory.updateOps\n\treadonly aggregateOps = this.#inMemory.aggregateOps\n\n\t#writeQueue: Promise<void> = Promise.resolve()\n\n\tprotected constructor(config: typeof JsonAdapter.Config) {\n\t\tsuper(config)\n\t}\n\n\tget stores() {\n\t\treturn this.#inMemory.stores\n\t}\n\n\t#serialize(): string {\n\t\tconst obj: Record<string, unknown> = {}\n\n\t\tconst migrations = [...this.#inMemory.migrations.values()]\n\t\tif (migrations.length > 0) obj['__migrations'] = migrations\n\n\t\tconst tables: Record<string, unknown> = {}\n\t\tfor (const [name, meta] of this.#inMemory.tables.entries()) {\n\t\t\ttables[name] = { pk: meta.pk, fields: Object.fromEntries(meta.fields.entries()) }\n\t\t}\n\t\tif (Object.keys(tables).length > 0) obj['__tables'] = tables\n\n\t\tconst indexes: Record<string, unknown> = {}\n\t\tfor (const [name, meta] of this.#inMemory.indexes.entries()) {\n\t\t\tindexes[name] = { table: meta.table, on: [...meta.on], unique: meta.unique }\n\t\t}\n\t\tif (Object.keys(indexes).length > 0) obj['__indexes'] = indexes\n\n\t\tconst foreignKeys: Record<string, unknown> = {}\n\t\tfor (const [name, meta] of this.#inMemory.foreignKeys.entries()) {\n\t\t\tforeignKeys[name] = {\n\t\t\t\ttable: meta.table,\n\t\t\t\ton: meta.on,\n\t\t\t\treferences: meta.references,\n\t\t\t\t...(meta.onDelete ? { onDelete: meta.onDelete } : {}),\n\t\t\t\t...(meta.onUpdate ? { onUpdate: meta.onUpdate } : {}),\n\t\t\t}\n\t\t}\n\t\tif (Object.keys(foreignKeys).length > 0) obj['__foreignKeys'] = foreignKeys\n\n\t\tfor (const [name, store] of this.stores.entries()) {\n\t\t\tconst records: Record<string, Record<string, unknown>> = {}\n\t\t\tfor (const [pk, doc] of store.entries()) {\n\t\t\t\trecords[pk] = doc\n\t\t\t}\n\t\t\tobj[name] = records\n\t\t}\n\n\t\treturn JSON.stringify(obj)\n\t}\n\n\tasync #atomicWrite(): Promise<void> {\n\t\tconst data = this.#serialize()\n\t\tconst tmpPath = this.config.filePath + '.tmp.' + process.pid + '.' + Date.now()\n\t\tawait writeFile(tmpPath, data, 'utf-8')\n\t\tawait rename(tmpPath, this.config.filePath)\n\t}\n\n\t#persistToDisk(): Promise<void> {\n\t\tconst next = this.#writeQueue.then(() => this.#atomicWrite())\n\t\tthis.#writeQueue = next.catch(() => {})\n\t\treturn next\n\t}\n\n\tasync connect(): Promise<void> {\n\t\tlet raw: string\n\t\ttry {\n\t\t\traw = await readFile(this.config.filePath, 'utf-8')\n\t\t} catch {\n\t\t\treturn\n\t\t}\n\t\tconst data = JSON.parse(raw) as Record<string, unknown>\n\n\t\tthis.#inMemory.migrations.clear()\n\t\tif (Array.isArray(data['__migrations'])) {\n\t\t\tfor (const m of data['__migrations'] as Array<{ id: string; appliedAt: number }>) {\n\t\t\t\tthis.#inMemory.migrations.set(m.id, { id: m.id, appliedAt: m.appliedAt })\n\t\t\t}\n\t\t}\n\n\t\tthis.#inMemory.tables.clear()\n\t\tif (data['__tables'] && typeof data['__tables'] === 'object') {\n\t\t\tfor (const [name, meta] of Object.entries(data['__tables'] as Record<string, any>)) {\n\t\t\t\tconst fields = new Map<string, AnyFieldSpec>()\n\t\t\t\tif (meta.fields) {\n\t\t\t\t\tfor (const [fieldName, fspec] of Object.entries(meta.fields as Record<string, AnyFieldSpec>)) {\n\t\t\t\t\t\tfields.set(fieldName, fspec)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.#inMemory.tables.set(name, { pk: meta.pk, fields })\n\t\t\t}\n\t\t}\n\n\t\tthis.#inMemory.indexes.clear()\n\t\tif (data['__indexes'] && typeof data['__indexes'] === 'object') {\n\t\t\tfor (const [name, meta] of Object.entries(data['__indexes'] as Record<string, any>)) {\n\t\t\t\tthis.#inMemory.indexes.set(name, { table: meta.table, on: meta.on, unique: meta.unique })\n\t\t\t}\n\t\t}\n\n\t\tthis.#inMemory.foreignKeys.clear()\n\t\tif (data['__foreignKeys'] && typeof data['__foreignKeys'] === 'object') {\n\t\t\tfor (const [name, meta] of Object.entries(data['__foreignKeys'] as Record<string, any>)) {\n\t\t\t\tthis.#inMemory.foreignKeys.set(name, meta)\n\t\t\t}\n\t\t}\n\n\t\tthis.stores.clear()\n\t\tfor (const [name, records] of Object.entries(data)) {\n\t\t\tif (name.startsWith('__')) continue\n\t\t\tconst store = new Map<string, Record<string, unknown>>()\n\t\t\tfor (const [pk, doc] of Object.entries(records as Record<string, Record<string, unknown>>)) {\n\t\t\t\tstore.set(pk, doc)\n\t\t\t}\n\t\t\tthis.stores.set(name, store)\n\t\t}\n\t}\n\n\tasync disconnect(): Promise<void> {\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync findByPk(schema: AnySchema, config: unknown, pk: unknown) {\n\t\treturn this.#inMemory.findByPk(schema, config, pk)\n\t}\n\n\tasync createMany(schema: AnySchema, config: unknown, data: Record<string, unknown>[]) {\n\t\tconst result = await this.#inMemory.createMany(schema, config, data)\n\t\tawait this.#persistToDisk()\n\t\treturn result\n\t}\n\n\tasync updateByPk(schema: AnySchema, config: unknown, pk: unknown, ops: AnyUpdateOp[]) {\n\t\tconst result = await this.#inMemory.updateByPk(schema, config, pk, ops)\n\t\tif (result) await this.#persistToDisk()\n\t\treturn result\n\t}\n\n\tasync deleteByPk(schema: AnySchema, config: unknown, pk: unknown) {\n\t\tconst result = await this.#inMemory.deleteByPk(schema, config, pk)\n\t\tif (result) await this.#persistToDisk()\n\t\treturn result\n\t}\n\n\tasync findMany(schema: AnySchema, config: unknown, group: FilterGroup, options?: QueryOptions) {\n\t\treturn this.#inMemory.findMany(schema, config, group, options)\n\t}\n\n\tasync count(schema: AnySchema, config: unknown, group: FilterGroup) {\n\t\treturn this.#inMemory.count(schema, config, group)\n\t}\n\n\titerateMany(schema: AnySchema, config: unknown, group: FilterGroup, options?: IterationQueryOptions) {\n\t\treturn this.#inMemory.iterateMany(schema, config, group, options)\n\t}\n\n\tasync updateMany(schema: AnySchema, config: unknown, group: FilterGroup, data: Record<string, unknown>) {\n\t\tconst result = await this.#inMemory.updateMany(schema, config, group, data)\n\t\tif (result.length) await this.#persistToDisk()\n\t\treturn result\n\t}\n\n\tasync deleteMany(schema: AnySchema, config: unknown, filter: FilterGroup) {\n\t\tconst result = await this.#inMemory.deleteMany(schema, config, filter)\n\t\tif (result.length) await this.#persistToDisk()\n\t\treturn result\n\t}\n\n\tasync upsertOne(schema: AnySchema, config: unknown, filter: FilterGroup, create: Record<string, unknown>, ops: AnyUpdateOp[]) {\n\t\tconst result = await this.#inMemory.upsertOne(schema, config, filter, create, ops)\n\t\tawait this.#persistToDisk()\n\t\treturn result\n\t}\n\n\tasync aggregate(schema: AnySchema, config: unknown, spec: AggregateSpec) {\n\t\treturn this.#inMemory.aggregate(schema, config, spec)\n\t}\n\n\tasync session<T>(fn: () => Promise<T>): Promise<T> {\n\t\treturn this.#inMemory.session(async () => {\n\t\t\tconst result = await fn()\n\t\t\tawait this.#persistToDisk()\n\t\t\treturn result\n\t\t})\n\t}\n\n\tasync loadMigrations(): Promise<{ id: string; appliedAt: number }[]> {\n\t\tawait this.connect()\n\t\treturn this.#inMemory.loadMigrations()\n\t}\n\n\tasync recordMigration(id: string, appliedAt: number): Promise<void> {\n\t\tawait this.#inMemory.recordMigration(id, appliedAt)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync acquireMigrationLock<T>(fn: () => Promise<T>): Promise<T> {\n\t\tconst lockPath = this.config.filePath + '.lock'\n\t\tconst deadline = Date.now() + 30_000\n\t\twhile (true) {\n\t\t\ttry {\n\t\t\t\tconst handle = await open(lockPath, 'wx')\n\t\t\t\tawait handle.close()\n\t\t\t\tbreak\n\t\t\t} catch (err: any) {\n\t\t\t\tif (err.code !== 'EEXIST') throw err\n\t\t\t\tif (Date.now() > deadline) throw new Error('Timed out waiting for migration lock')\n\t\t\t\tawait new Promise((r) => setTimeout(r, 50))\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\treturn await fn()\n\t\t} finally {\n\t\t\tawait unlink(lockPath).catch(() => {})\n\t\t}\n\t}\n\n\tasync applyCreateTable(change: CreateTableChange<any>): Promise<void> {\n\t\tawait this.#inMemory.applyCreateTable(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyDropTable(change: DropTableChange): Promise<void> {\n\t\tawait this.#inMemory.applyDropTable(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyAddField(change: AddFieldChange<any>): Promise<void> {\n\t\tawait this.#inMemory.applyAddField(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyDropField(change: DropFieldChange): Promise<void> {\n\t\tawait this.#inMemory.applyDropField(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyModifyField(change: ModifyFieldChange<any>): Promise<void> {\n\t\tawait this.#inMemory.applyModifyField(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyRenameTable(change: RenameTableChange): Promise<void> {\n\t\tawait this.#inMemory.applyRenameTable(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyRenameField(change: RenameFieldChange): Promise<void> {\n\t\tawait this.#inMemory.applyRenameField(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyAddIndex(change: AddIndexChange): Promise<void> {\n\t\tawait this.#inMemory.applyAddIndex(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyDropIndex(change: DropIndexChange): Promise<void> {\n\t\tawait this.#inMemory.applyDropIndex(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyAddForeignKey(change: AddForeignKeyChange): Promise<void> {\n\t\tawait this.#inMemory.applyAddForeignKey(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync applyDropForeignKey(change: DropForeignKeyChange): Promise<void> {\n\t\tawait this.#inMemory.applyDropForeignKey(change)\n\t\tawait this.#persistToDisk()\n\t}\n\n\tasync introspect(): Promise<DiscoveredSchema[]> {\n\t\treturn this.#inMemory.introspect()\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect, beforeEach, afterEach } = import.meta.vitest\n\tconst { FilterGroup } = await import('../../filter')\n\tconst { OrderBy } = await import('../../query-options')\n\tconst { Schema } = await import('../../schema')\n\tconst { IncOp, PatchOp, PullOp, PushOp } = await import('../../updates')\n\tconst { v } = await import('valleyed')\n\tconst { mkdtemp, rm } = await import('node:fs/promises')\n\tconst { tmpdir } = await import('node:os')\n\tconst { join } = await import('node:path')\n\tconst { Migrator } = await import('../../migrations/migrator')\n\tconst { Repo } = await import('../../repo/repo')\n\tconst { OrmMigrationError } = await import('../../errors/migration')\n\ttype Migration<A extends import('../base').OrmAdapterLike<any>> = import('../../migrations/types').Migration<A>\n\n\tlet tmpDir: string\n\tlet filePath: string\n\n\tbeforeEach(async () => {\n\t\ttmpDir = await mkdtemp(join(tmpdir(), 'json-adapter-'))\n\t\tfilePath = join(tmpDir, 'test.json')\n\t})\n\n\tafterEach(async () => {\n\t\tawait rm(tmpDir, { recursive: true, force: true })\n\t})\n\n\tdescribe('json adapter — class construction', () => {\n\t\ttest('JsonAdapter.create({ filePath }) produces a working adapter', () => {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\texpect(adapter).toBeInstanceOf(JsonAdapter)\n\t\t\texpect(adapter.schemaConfigPipe).toBeDefined()\n\t\t\texpect(adapter.supportedFieldTypes).toEqual(['string', 'number', 'boolean', 'null', 'object', 'array', 'date'])\n\t\t})\n\n\t\ttest('auto-wired Instance hooks register connect/disconnect keyed by class', async () => {\n\t\t\tconst { Instance: Inst } = await import('../../../instance')\n\t\t\tconst onSpy = (await import('vitest')).vi.spyOn(Inst, 'on').mockImplementation(() => {})\n\n\t\t\tJsonAdapter.create({ filePath })\n\n\t\t\texpect(onSpy).toHaveBeenCalledWith('start', expect.any(Function), expect.objectContaining({ class: JsonAdapter }))\n\t\t\texpect(onSpy).toHaveBeenCalledWith('close', expect.any(Function), expect.objectContaining({ class: JsonAdapter }))\n\n\t\t\tonSpy.mockRestore()\n\t\t})\n\t})\n\n\tdescribe('json adapter — surface parity with in-memory', () => {\n\t\ttest('CRUD: create, find, update, delete round-trip', async () => {\n\t\t\tconst schema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => 'u')\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\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\n\t\t\tconst use = adapter.use(schema, { table: 'users' })\n\n\t\t\tconst created = await use.createMany([\n\t\t\t\t{ id: 'u1', name: 'Alice', age: 30 },\n\t\t\t\t{ id: 'u2', name: 'Bob', age: 20 },\n\t\t\t])\n\t\t\texpect(created).toHaveLength(2)\n\n\t\t\tconst found = await use.findOne(FilterGroup.create().eq('id', 'u1'))\n\t\t\texpect(found).toEqual({ id: 'u1', name: 'Alice', age: 30 })\n\n\t\t\tconst many = await use.findMany(FilterGroup.create().gt('age', 19), {\n\t\t\t\torderBy: [new OrderBy('age', 'asc')],\n\t\t\t})\n\t\t\texpect(many).toEqual([\n\t\t\t\t{ id: 'u2', name: 'Bob', age: 20 },\n\t\t\t\t{ id: 'u1', name: 'Alice', age: 30 },\n\t\t\t])\n\n\t\t\tawait use.updateOne(FilterGroup.create().eq('id', 'u1'), { age: 31 })\n\t\t\tconst updated = await use.findOne(FilterGroup.create().eq('id', 'u1'))\n\t\t\texpect(updated?.age).toBe(31)\n\n\t\t\tconst deleted = await use.deleteOne(FilterGroup.create().eq('id', 'u2'))\n\t\t\texpect(deleted).toEqual({ id: 'u2', name: 'Bob', age: 20 })\n\n\t\t\tconst remaining = await use.findMany(FilterGroup.create())\n\t\t\texpect(remaining).toHaveLength(1)\n\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('supports nested filters, ordering, select, offset and limit', async () => {\n\t\t\tconst schema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => 'u')\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\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'users' })\n\n\t\t\tawait use.createMany([\n\t\t\t\t{ id: 'u1', name: 'Alice', age: 30 },\n\t\t\t\t{ id: 'u2', name: 'Bob', age: 20 },\n\t\t\t\t{ id: 'u3', name: 'Carol', age: 40 },\n\t\t\t])\n\n\t\t\tconst builtGroup = FilterGroup.create().and([\n\t\t\t\t(q) => q.gt('age', 19),\n\t\t\t\t(q) => q.or([(g) => g.eq('name', 'Alice'), (g) => g.eq('name', 'Carol')]),\n\t\t\t])\n\t\t\tconst options = { orderBy: [new OrderBy('age', 'desc')], offset: 1, limit: 1, select: ['id', 'name'] as const }\n\t\t\tconst rows = await use.findMany(builtGroup, options)\n\t\t\texpect(rows).toEqual([{ id: 'u1', name: 'Alice' }])\n\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('count delegates to the in-memory store and persists separately from query options', async () => {\n\t\t\tconst schema = Schema.from('count_users')\n\t\t\t\t.pk('id', v.string(), () => 'u')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'count_users' })\n\t\t\tawait use.createMany([\n\t\t\t\t{ id: 'u1', name: 'Alice' },\n\t\t\t\t{ id: 'u2', name: 'Bob' },\n\t\t\t\t{ id: 'u3', name: 'Alice' },\n\t\t\t])\n\n\t\t\tawait expect(use.count(FilterGroup.create().eq('name', 'Alice'))).resolves.toBe(2)\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('iterateMany with batchSize matches findMany through in-memory delegation', async () => {\n\t\t\tconst schema = Schema.from('json_iter_users')\n\t\t\t\t.pk('id', v.string(), () => 'u')\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\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'json_iter_users' })\n\t\t\tawait use.createMany([\n\t\t\t\t{ id: 'u1', name: 'Alice', age: 30 },\n\t\t\t\t{ id: 'u2', name: 'Bob', age: 20 },\n\t\t\t\t{ id: 'u3', name: 'Carol', age: 40 },\n\t\t\t\t{ id: 'u4', name: 'Dan', age: 50 },\n\t\t\t])\n\n\t\t\tconst filter = FilterGroup.create().gt('age', 19)\n\t\t\tconst options = {\n\t\t\t\torderBy: [new OrderBy('age', 'desc')],\n\t\t\t\toffset: 1,\n\t\t\t\tlimit: 2,\n\t\t\t\tselect: ['id', 'name'],\n\t\t\t}\n\t\t\tconst expected = await use.findMany(filter, options)\n\t\t\tconst rows: Record<string, unknown>[] = []\n\t\t\tfor await (const row of use.iterateMany(filter, { ...options, batchSize: 2 })) {\n\t\t\t\trows.push(row)\n\t\t\t}\n\t\t\texpect(rows).toEqual(expected)\n\t\t\texpect(rows).toEqual([\n\t\t\t\t{ id: 'u3', name: 'Carol' },\n\t\t\t\t{ id: 'u1', name: 'Alice' },\n\t\t\t])\n\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('supports update operators and rollback on failed session', async () => {\n\t\t\tconst schema = Schema.from('docs')\n\t\t\t\t.pk('id', v.string(), () => 'd1')\n\t\t\t\t.field('count', v.number())\n\t\t\t\t.field('tags', v.array(v.string()))\n\t\t\t\t.field('meta', v.object({ a: v.number() }))\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'docs' })\n\n\t\t\tawait use.createOne({ id: 'd1', count: 1, tags: ['x'], meta: { a: 1 } })\n\n\t\t\tawait adapter.session(async () => {\n\t\t\t\tawait use.updateOne(FilterGroup.create().eq('id', 'd1'), {\n\t\t\t\t\tcount: new IncOp('count', 2),\n\t\t\t\t\ttags: new PushOp('tags', 'y'),\n\t\t\t\t\tmeta: new PatchOp('meta', { a: 9 }),\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tawait expect(\n\t\t\t\tadapter.session(async () => {\n\t\t\t\t\tawait use.updateOne(FilterGroup.create().eq('id', 'd1'), { tags: new PullOp('tags', 'x') })\n\t\t\t\t\tthrow new Error('boom')\n\t\t\t\t}),\n\t\t\t).rejects.toThrow('boom')\n\n\t\t\tconst row = await use.findOne(FilterGroup.create().eq('id', 'd1'))\n\t\t\texpect(row).toEqual({ id: 'd1', count: 3, tags: ['x', 'y'], meta: { a: 9 } })\n\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('upsertOne inserts when missing and updates when existing', async () => {\n\t\t\tconst schema = Schema.from('items')\n\t\t\t\t.pk('id', v.string(), () => 'i')\n\t\t\t\t.field('val', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'items' })\n\n\t\t\tconst inserted = await use.upsertOne(FilterGroup.create().eq('id', 'i1'), { id: 'i1', val: 10 }, [])\n\t\t\texpect(inserted).toEqual({ id: 'i1', val: 10 })\n\n\t\t\tconst updated = await use.upsertOne(FilterGroup.create().eq('id', 'i1'), { id: 'i1', val: 10 }, [new IncOp('val', 5)])\n\t\t\texpect(updated).toEqual({ id: 'i1', val: 15 })\n\n\t\t\tawait adapter.disconnect()\n\t\t})\n\t})\n\n\tdescribe('json adapter — persistence', () => {\n\t\ttest('state persists across adapter restarts', async () => {\n\t\t\tconst schema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => 'u')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\n\t\t\tconst a1 = JsonAdapter.create({ filePath })\n\t\t\tawait a1.connect()\n\t\t\tconst use1 = a1.use(schema, { table: 'users' })\n\t\t\tawait use1.createMany([\n\t\t\t\t{ id: 'u1', name: 'Alice' },\n\t\t\t\t{ id: 'u2', name: 'Bob' },\n\t\t\t])\n\t\t\tawait a1.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst use2 = a2.use(schema, { table: 'users' })\n\t\t\tconst rows = await use2.findMany(FilterGroup.create())\n\t\t\texpect(rows).toHaveLength(2)\n\t\t\texpect(rows.map((r) => r.name).sort()).toEqual(['Alice', 'Bob'])\n\t\t\tawait a2.disconnect()\n\t\t})\n\n\t\ttest('failed session does not persist rolled-back state to disk', async () => {\n\t\t\tconst schema = Schema.from('docs')\n\t\t\t\t.pk('id', v.string(), () => 'd')\n\t\t\t\t.field('val', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst a1 = JsonAdapter.create({ filePath })\n\t\t\tawait a1.connect()\n\t\t\tconst use1 = a1.use(schema, { table: 'docs' })\n\t\t\tawait use1.createOne({ id: 'd1', val: 1 })\n\t\t\tawait a1.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst use2 = a2.use(schema, { table: 'docs' })\n\t\t\tawait expect(\n\t\t\t\ta2.session(async () => {\n\t\t\t\t\tawait use2.updateOne(FilterGroup.create().eq('id', 'd1'), { val: 999 })\n\t\t\t\t\tthrow new Error('rollback')\n\t\t\t\t}),\n\t\t\t).rejects.toThrow('rollback')\n\t\t\tawait a2.disconnect()\n\n\t\t\tconst a3 = JsonAdapter.create({ filePath })\n\t\t\tawait a3.connect()\n\t\t\tconst use3 = a3.use(schema, { table: 'docs' })\n\t\t\tconst row = await use3.findOne(FilterGroup.create().eq('id', 'd1'))\n\t\t\texpect(row?.val).toBe(1)\n\t\t\tawait a3.disconnect()\n\t\t})\n\t})\n\n\tdescribe('json adapter — atomic writes', () => {\n\t\ttest('file is valid JSON after every write', async () => {\n\t\t\tconst { readFile: rf } = await import('node:fs/promises')\n\t\t\tconst schema = Schema.from('items')\n\t\t\t\t.pk('id', v.string(), () => 'i')\n\t\t\t\t.field('val', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'items' })\n\n\t\t\tfor (let i = 0; i < 10; i++) {\n\t\t\t\tawait use.createOne({ id: `i${i}`, val: i })\n\t\t\t\tconst raw = await rf(filePath, 'utf-8')\n\t\t\t\texpect(() => JSON.parse(raw)).not.toThrow()\n\t\t\t}\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('no .tmp files remain after writes complete', async () => {\n\t\t\tconst { readdir } = await import('node:fs/promises')\n\t\t\tconst schema = Schema.from('items')\n\t\t\t\t.pk('id', v.string(), () => 'i')\n\t\t\t\t.field('val', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'items' })\n\n\t\t\tfor (let i = 0; i < 5; i++) {\n\t\t\t\tawait use.createOne({ id: `i${i}`, val: i })\n\t\t\t}\n\t\t\tawait adapter.disconnect()\n\n\t\t\tconst files = await readdir(tmpDir)\n\t\t\tconst tmpFiles = files.filter((f) => f.includes('.tmp.'))\n\t\t\texpect(tmpFiles).toHaveLength(0)\n\t\t})\n\t})\n\n\tdescribe('json adapter — aggregate delegation', () => {\n\t\tconst orderSchema = Schema.from('orders')\n\t\t\t.pk('id', v.string(), () => 'o')\n\t\t\t.field('amount', v.number())\n\t\t\t.field('region', v.string())\n\t\t\t.build()\n\n\t\tconst orderSeed = [\n\t\t\t{ id: 'o1', amount: 100, region: 'us' },\n\t\t\t{ id: 'o2', amount: 200, region: 'eu' },\n\t\t\t{ id: 'o3', amount: 150, region: 'us' },\n\t\t]\n\n\t\tasync function makeSeededAdapter() {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(orderSchema, { table: 'orders' })\n\t\t\tawait use.createMany(orderSeed)\n\t\t\treturn adapter\n\t\t}\n\n\t\ttest('aggregateOps matches in-memory adapter', () => {\n\t\t\tconst json = JsonAdapter.create({ filePath })\n\t\t\tconst inMem = InMemoryAdapter.create({})\n\t\t\texpect(json.aggregateOps).toEqual(inMem.aggregateOps)\n\t\t\texpect(json.aggregateOps).toEqual(['count', 'countDistinct', 'sum', 'avg', 'min', 'max'])\n\t\t})\n\n\t\ttest('count returns identical result to in-memory', async () => {\n\t\t\tconst adapter = await makeSeededAdapter()\n\t\t\tconst result = await adapter.aggregate(orderSchema, { table: 'orders' }, {\n\t\t\t\taggregates: [{ fn: 'count', alias: 'total' }],\n\t\t\t\tgroupBy: [],\n\t\t\t})\n\t\t\texpect(result).toEqual([{ total: 3 }])\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('countDistinct returns identical result to in-memory', async () => {\n\t\t\tconst adapter = await makeSeededAdapter()\n\t\t\tconst result = await adapter.aggregate(orderSchema, { table: 'orders' }, {\n\t\t\t\taggregates: [{ fn: 'countDistinct', field: 'region', alias: 'uniqueRegions' }],\n\t\t\t\tgroupBy: [],\n\t\t\t})\n\t\t\texpect(result).toEqual([{ uniqueRegions: 2 }])\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('sum, avg, min, max return identical results to in-memory', async () => {\n\t\t\tconst adapter = await makeSeededAdapter()\n\t\t\tconst result = await adapter.aggregate(orderSchema, { table: 'orders' }, {\n\t\t\t\taggregates: [\n\t\t\t\t\t{ fn: 'sum', field: 'amount', alias: 'totalAmount' },\n\t\t\t\t\t{ fn: 'avg', field: 'amount', alias: 'avgAmount' },\n\t\t\t\t\t{ fn: 'min', field: 'amount', alias: 'minAmount' },\n\t\t\t\t\t{ fn: 'max', field: 'amount', alias: 'maxAmount' },\n\t\t\t\t],\n\t\t\t\tgroupBy: [],\n\t\t\t})\n\t\t\texpect(result).toEqual([{ totalAmount: 450, avgAmount: 150, minAmount: 100, maxAmount: 200 }])\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('groupBy returns identical results to in-memory', async () => {\n\t\t\tconst adapter = await makeSeededAdapter()\n\t\t\tconst result = await adapter.aggregate(orderSchema, { table: 'orders' }, {\n\t\t\t\taggregates: [\n\t\t\t\t\t{ fn: 'count', alias: 'cnt' },\n\t\t\t\t\t{ fn: 'sum', field: 'amount', alias: 'total' },\n\t\t\t\t],\n\t\t\t\tgroupBy: ['region'],\n\t\t\t})\n\t\t\tconst sorted = result.sort((a, b) => String(a.region).localeCompare(String(b.region)))\n\t\t\texpect(sorted).toEqual([\n\t\t\t\t{ region: 'eu', cnt: 1, total: 200 },\n\t\t\t\t{ region: 'us', cnt: 2, total: 250 },\n\t\t\t])\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('where pre-filter returns identical results to in-memory', async () => {\n\t\t\tconst adapter = await makeSeededAdapter()\n\t\t\tconst result = await adapter.aggregate(orderSchema, { table: 'orders' }, {\n\t\t\t\twhere: FilterGroup.create().gt('amount', 100),\n\t\t\t\taggregates: [{ fn: 'count', alias: 'cnt' }],\n\t\t\t\tgroupBy: [],\n\t\t\t})\n\t\t\texpect(result).toEqual([{ cnt: 2 }])\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('having post-filter returns identical results to in-memory', async () => {\n\t\t\tconst adapter = await makeSeededAdapter()\n\t\t\tconst result = await adapter.aggregate(orderSchema, { table: 'orders' }, {\n\t\t\t\taggregates: [{ fn: 'count', alias: 'cnt' }],\n\t\t\t\tgroupBy: ['region'],\n\t\t\t\thaving: FilterGroup.create().gt('cnt', 1),\n\t\t\t})\n\t\t\texpect(result).toEqual([{ region: 'us', cnt: 2 }])\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('aggregate delegates to same in-memory instance used by CRUD', async () => {\n\t\t\tconst schema = Schema.from('items')\n\t\t\t\t.pk('id', v.string(), () => 'i')\n\t\t\t\t.field('val', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'items' })\n\n\t\t\tawait use.createMany([\n\t\t\t\t{ id: 'i1', val: 10 },\n\t\t\t\t{ id: 'i2', val: 20 },\n\t\t\t])\n\n\t\t\tconst spec: AggregateSpec = {\n\t\t\t\taggregates: [{ fn: 'sum', field: 'val', alias: 'total' }],\n\t\t\t\tgroupBy: [],\n\t\t\t}\n\t\t\tconst result = await adapter.aggregate(schema, { table: 'items' }, spec)\n\t\t\texpect(result).toEqual([{ total: 30 }])\n\n\t\t\tawait use.createOne({ id: 'i3', val: 30 })\n\t\t\tconst result2 = await adapter.aggregate(schema, { table: 'items' }, spec)\n\t\t\texpect(result2).toEqual([{ total: 60 }])\n\n\t\t\tawait adapter.disconnect()\n\t\t})\n\t})\n\n\tdescribe('json adapter — concurrent writes serialize', () => {\n\t\ttest('parallel writes from same process all persist', async () => {\n\t\t\tconst schema = Schema.from('items')\n\t\t\t\t.pk('id', v.string(), () => 'i')\n\t\t\t\t.field('val', v.number())\n\t\t\t\t.build()\n\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tconst use = adapter.use(schema, { table: 'items' })\n\n\t\t\tawait Promise.all(Array.from({ length: 20 }, (_, i) => use.createOne({ id: `i${i}`, val: i })))\n\n\t\t\tconst rows = await use.findMany(FilterGroup.create())\n\t\t\texpect(rows).toHaveLength(20)\n\t\t\tawait adapter.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst use2 = a2.use(schema, { table: 'items' })\n\t\t\tconst reloaded = await use2.findMany(FilterGroup.create())\n\t\t\texpect(reloaded).toHaveLength(20)\n\t\t\tawait a2.disconnect()\n\t\t})\n\t})\n\n\tdescribe('json adapter — migration storage', () => {\n\t\ttest('recordMigration + loadMigrations round-trip', async () => {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\n\t\t\tawait adapter.recordMigration('m-001', 1000)\n\t\t\tawait adapter.recordMigration('m-002', 2000)\n\n\t\t\tconst loaded = await adapter.loadMigrations()\n\t\t\texpect(loaded).toHaveLength(2)\n\t\t\texpect(loaded).toEqual([\n\t\t\t\t{ id: 'm-001', appliedAt: 1000 },\n\t\t\t\t{ id: 'm-002', appliedAt: 2000 },\n\t\t\t])\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('migration tracker persists across adapter restarts', async () => {\n\t\t\tconst a1 = JsonAdapter.create({ filePath })\n\t\t\tawait a1.connect()\n\t\t\tawait a1.recordMigration('m-001', 1000)\n\t\t\tawait a1.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst loaded = await a2.loadMigrations()\n\t\t\texpect(loaded).toEqual([{ id: 'm-001', appliedAt: 1000 }])\n\t\t\tawait a2.disconnect()\n\t\t})\n\n\t\ttest('__migrations key in JSON file stores tracker data', async () => {\n\t\t\tconst { readFile: rf } = await import('node:fs/promises')\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tawait adapter.recordMigration('m-001', 1000)\n\t\t\tawait adapter.disconnect()\n\n\t\t\tconst raw = JSON.parse(await rf(filePath, 'utf-8'))\n\t\t\texpect(raw['__migrations']).toEqual([{ id: 'm-001', appliedAt: 1000 }])\n\t\t})\n\t})\n\n\tdescribe('json adapter — apply* methods', () => {\n\t\ttest('applyCreateTable persists table metadata', async () => {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\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', unique: true }],\n\t\t\t})\n\t\t\tawait adapter.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst schemas = await a2.introspect()\n\t\t\texpect(schemas).toHaveLength(1)\n\t\t\texpect(schemas[0].name).toBe('users')\n\t\t\texpect(schemas[0].pk).toEqual({ name: 'id', type: 'string' })\n\t\t\texpect(schemas[0].fields).toEqual([{ name: 'email', type: 'string', nullable: false, unique: true }])\n\t\t\tawait a2.disconnect()\n\t\t})\n\n\t\ttest('applyAddIndex and applyDropIndex persist across restarts', async () => {\n\t\t\tconst a1 = JsonAdapter.create({ filePath })\n\t\t\tawait a1.connect()\n\t\t\tawait a1.applyCreateTable({ kind: 'createTable', name: 'users', pk: { name: 'id', type: 'string' }, fields: [] })\n\t\t\tawait a1.applyAddIndex({ kind: 'addIndex', table: 'users', on: ['email'], unique: true })\n\t\t\tawait a1.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst schemas = await a2.introspect()\n\t\t\texpect(schemas[0].indexes).toEqual([{ name: 'users_email_idx', on: ['email'], unique: true }])\n\t\t\tawait a2.applyDropIndex({ kind: 'dropIndex', name: 'users_email_idx' })\n\t\t\tawait a2.disconnect()\n\n\t\t\tconst a3 = JsonAdapter.create({ filePath })\n\t\t\tawait a3.connect()\n\t\t\tconst schemas2 = await a3.introspect()\n\t\t\texpect(schemas2[0].indexes).toEqual([])\n\t\t\tawait a3.disconnect()\n\t\t})\n\n\t\ttest('applyAddForeignKey and applyDropForeignKey persist across restarts', async () => {\n\t\t\tconst a1 = JsonAdapter.create({ filePath })\n\t\t\tawait a1.connect()\n\t\t\tawait a1.applyCreateTable({ kind: 'createTable', name: 'users', pk: { name: 'id', type: 'string' }, fields: [] })\n\t\t\tawait a1.applyCreateTable({ kind: 'createTable', name: 'posts', pk: { name: 'id', type: 'string' }, fields: [] })\n\t\t\tawait a1.applyAddForeignKey({ kind: 'addForeignKey', table: 'posts', on: 'authorId', references: { table: 'users', column: 'id' }, onDelete: 'cascade' })\n\t\t\tawait a1.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst schemas = await a2.introspect()\n\t\t\tconst posts = schemas.find((s) => s.name === 'posts')!\n\t\t\texpect(posts.foreignKeys).toEqual([{\n\t\t\t\tname: 'posts_authorId_fk',\n\t\t\t\ton: 'authorId',\n\t\t\t\treferences: { table: 'users', column: 'id' },\n\t\t\t\tonDelete: 'cascade',\n\t\t\t}])\n\t\t\tawait a2.applyDropForeignKey({ kind: 'dropForeignKey', table: 'posts', name: 'posts_authorId_fk' })\n\t\t\tawait a2.disconnect()\n\n\t\t\tconst a3 = JsonAdapter.create({ filePath })\n\t\t\tawait a3.connect()\n\t\t\tconst schemas2 = await a3.introspect()\n\t\t\tconst posts2 = schemas2.find((s) => s.name === 'posts')!\n\t\t\texpect(posts2.foreignKeys).toEqual([])\n\t\t\tawait a3.disconnect()\n\t\t})\n\t})\n\n\tdescribe('json adapter — acquireMigrationLock', () => {\n\t\ttest('file lock serialises concurrent calls', async () => {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\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 p1 = adapter.acquireMigrationLock(async () => {\n\t\t\t\torder.push('p1-start')\n\t\t\t\tawait gate\n\t\t\t\torder.push('p1-end')\n\t\t\t})\n\t\t\tawait new Promise((r) => setTimeout(r, 100))\n\t\t\tconst p2 = adapter.acquireMigrationLock(async () => {\n\t\t\t\torder.push('p2')\n\t\t\t})\n\n\t\t\tawait new Promise((r) => setTimeout(r, 100))\n\t\t\texpect(order).toEqual(['p1-start'])\n\n\t\t\tresolveGate()\n\t\t\tawait Promise.all([p1, p2])\n\t\t\texpect(order).toEqual(['p1-start', 'p1-end', 'p2'])\n\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('lock file is cleaned up after successful execution', async () => {\n\t\t\tconst { access } = await import('node:fs/promises')\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\n\t\t\tawait adapter.acquireMigrationLock(async () => { /* no-op */ })\n\n\t\t\tawait expect(access(filePath + '.lock')).rejects.toThrow()\n\t\t\tawait adapter.disconnect()\n\t\t})\n\n\t\ttest('lock file is cleaned up after failed execution', async () => {\n\t\t\tconst { access } = await import('node:fs/promises')\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\n\t\t\tawait expect(\n\t\t\t\tadapter.acquireMigrationLock(async () => { throw new Error('boom') }),\n\t\t\t).rejects.toThrow('boom')\n\n\t\t\tawait expect(access(filePath + '.lock')).rejects.toThrow()\n\t\t\tawait adapter.disconnect()\n\t\t})\n\t})\n\n\tdescribe('json adapter — end-to-end migrations via Migrator', () => {\n\t\tconst UserSchema = Schema.from('users')\n\t\t\t.pk('id', v.string(), () => `u-${Math.random().toString(36).slice(2)}`)\n\t\t\t.field('email', v.string())\n\t\t\t.build()\n\n\t\tfunction makeEnv() {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tconst repo = new Repo({ adapter, resolve: (s) => ({ table: s.name }) })\n\t\t\treturn { adapter, repo }\n\t\t}\n\n\t\ttest('end-to-end migration run covering every declarative variant + execute', async () => {\n\t\t\tconst { adapter, repo } = makeEnv()\n\t\t\tawait adapter.connect()\n\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: 'modifyField', table: 'users', name: 'email', to: { name: 'email', type: 'string', unique: true, nullable: true } },\n\t\t\t\t\t\t{ kind: 'renameField', table: 'posts', from: 'title', to: 'headline' },\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\t{\n\t\t\t\t\tid: '0004-seed',\n\t\t\t\t\tchanges: [{\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: 'seed-1', email: 'test@example.com' })\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 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', '0004-seed'])\n\n\t\t\tconst recorded = await adapter.loadMigrations()\n\t\t\texpect(recorded).toHaveLength(4)\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\n\t\t\t// Verify via re-reading the file\n\t\t\tawait adapter.disconnect()\n\t\t\tconst { adapter: a2, repo: r2 } = makeEnv()\n\t\t\tawait a2.connect()\n\t\t\tconst recorded2 = await a2.loadMigrations()\n\t\t\texpect(recorded2).toHaveLength(4)\n\n\t\t\tconst row2 = await r2.on(UserSchema).one().id('seed-1').find()\n\t\t\texpect(row2!.email).toBe('test@example.com')\n\t\t\tawait a2.disconnect()\n\t\t})\n\n\t\ttest('file lock serialises concurrent migrator.up() runs', async () => {\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<JsonAdapter>[] = [\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: [{ kind: 'execute', up: async () => { order.push('0002') } }],\n\t\t\t\t},\n\t\t\t]\n\n\t\t\tconst adapter1 = JsonAdapter.create({ filePath })\n\t\t\tawait adapter1.connect()\n\t\t\tconst repo1 = new Repo({ adapter: adapter1, resolve: (s) => ({ table: s.name }) })\n\t\t\tconst migrator1 = Migrator.from(repo1, adapter1).migrations(migrations).build()\n\n\t\t\tconst adapter2 = JsonAdapter.create({ filePath })\n\t\t\tawait adapter2.connect()\n\t\t\tconst repo2 = new Repo({ adapter: adapter2, resolve: (s) => ({ table: s.name }) })\n\t\t\tconst migrator2 = Migrator.from(repo2, adapter2).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, 200))\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\n\t\t\tawait adapter1.disconnect()\n\t\t\tawait adapter2.disconnect()\n\t\t})\n\n\t\ttest('tx:true rollback leaves file unchanged on failure', async () => {\n\t\t\tconst { adapter } = makeEnv()\n\t\t\tawait adapter.connect()\n\n\t\t\tawait adapter.applyCreateTable({ kind: 'createTable', name: 'users', pk: { name: 'id', type: 'string' }, fields: [] })\n\t\t\tawait adapter.disconnect()\n\n\t\t\tconst { adapter: a2, repo: r2 } = makeEnv()\n\t\t\tawait a2.connect()\n\n\t\t\tconst m: Migration<typeof a2> = {\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(r2, a2).migrations([m]).build()\n\t\t\tawait expect(migrator.up()).rejects.toThrow(OrmMigrationError)\n\t\t\tawait a2.disconnect()\n\n\t\t\tconst { adapter: a3, repo: r3 } = makeEnv()\n\t\t\tawait a3.connect()\n\t\t\tconst recorded = await a3.loadMigrations()\n\t\t\texpect(recorded).toHaveLength(0)\n\t\t\tconst schemas = await a3.introspect()\n\t\t\tconst users = schemas.find((s) => s.name === 'users')!\n\t\t\texpect(users.indexes).toEqual([])\n\t\t\tconst row = await r3.on(UserSchema).one().id('partial').find()\n\t\t\texpect(row).toBeNull()\n\t\t\tawait a3.disconnect()\n\t\t})\n\n\t\ttest('introspect() round-trip: apply* then introspect returns matching descriptors', async () => {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\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', unique: true },\n\t\t\t\t\t{ name: 'age', type: 'number', nullable: true },\n\t\t\t\t],\n\t\t\t})\n\t\t\tawait adapter.applyAddIndex({ kind: 'addIndex', table: 'users', on: ['email'], unique: true })\n\t\t\tawait adapter.applyCreateTable({\n\t\t\t\tkind: 'createTable',\n\t\t\t\tname: 'posts',\n\t\t\t\tpk: { name: 'id', type: 'string' },\n\t\t\t\tfields: [{ name: 'authorId', type: 'string' }],\n\t\t\t})\n\t\t\tawait adapter.applyAddForeignKey({\n\t\t\t\tkind: 'addForeignKey',\n\t\t\t\ttable: 'posts',\n\t\t\t\ton: 'authorId',\n\t\t\t\treferences: { table: 'users', column: 'id' },\n\t\t\t\tonDelete: 'cascade',\n\t\t\t})\n\t\t\tawait adapter.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\tconst schemas = await a2.introspect()\n\n\t\t\tconst users = schemas.find((s) => s.name === 'users')!\n\t\t\texpect(users.pk).toEqual({ name: 'id', type: 'string' })\n\t\t\texpect(users.fields).toEqual([\n\t\t\t\t{ name: 'email', type: 'string', nullable: false, unique: true },\n\t\t\t\t{ name: 'age', type: 'number', nullable: true },\n\t\t\t])\n\t\t\texpect(users.indexes).toEqual([{ name: 'users_email_idx', on: ['email'], unique: true }])\n\n\t\t\tconst posts = schemas.find((s) => s.name === 'posts')!\n\t\t\texpect(posts.pk).toEqual({ name: 'id', type: 'string' })\n\t\t\texpect(posts.fields).toEqual([{ name: 'authorId', type: 'string', nullable: false }])\n\t\t\texpect(posts.foreignKeys).toEqual([{\n\t\t\t\tname: 'posts_authorId_fk',\n\t\t\t\ton: 'authorId',\n\t\t\t\treferences: { table: 'users', column: 'id' },\n\t\t\t\tonDelete: 'cascade',\n\t\t\t}])\n\n\t\t\tawait a2.disconnect()\n\t\t})\n\n\t\ttest('metadata keys do not leak into data stores', async () => {\n\t\t\tconst adapter = JsonAdapter.create({ filePath })\n\t\t\tawait adapter.connect()\n\t\t\tawait adapter.recordMigration('m-001', 1000)\n\t\t\tawait adapter.applyCreateTable({ kind: 'createTable', name: 'users', pk: { name: 'id', type: 'string' }, fields: [] })\n\n\t\t\texpect(adapter.stores.has('__migrations')).toBe(false)\n\t\t\texpect(adapter.stores.has('__tables')).toBe(false)\n\t\t\tawait adapter.disconnect()\n\n\t\t\tconst a2 = JsonAdapter.create({ filePath })\n\t\t\tawait a2.connect()\n\t\t\texpect(a2.stores.has('__migrations')).toBe(false)\n\t\t\texpect(a2.stores.has('__tables')).toBe(false)\n\t\t\tawait a2.disconnect()\n\t\t})\n\t})\n}\n","import { v, type ConditionalObjectKeys, type Pipe, type PipeInput, type PipeOutput } from 'valleyed'\n\ntype CtorParams<T> = ConstructorParameters<T & (abstract new (...args: any[]) => any)>\ntype BaseCtorParams<T> = T extends abstract new (...args: infer A) => any ? A : never\n\nexport function configurable<P extends Pipe<any, any>, Base extends abstract new (...args: any[]) => any>(pipeFn: () => P, base: Base) {\n\tconst pipe = pipeFn()\n\tv.compile(pipe)\n\n\tabstract class Configurable extends (base as unknown as new (...args: any[]) => any) {\n\t\tdeclare static readonly Config: PipeOutput<P>\n\n\t\tprotected constructor (protected readonly config: PipeOutput<P>, ...baseArgs: BaseCtorParams<Base>) {\n\t\t\t// eslint-disable-next-line constructor-super\n\t\t\tsuper(...baseArgs)\n\t\t}\n\n\t\tstatic create<This extends Function & { prototype: any }>(\n\t\t\tthis: This,\n\t\t\tinput: ConditionalObjectKeys<PipeInput<P>>,\n\t\t\t...args: CtorParams<This> extends [PipeOutput<P>, ...infer R] ? R : never\n\t\t): This['prototype'] {\n\t\t\tconst r = v.validate(pipe, input)\n\t\t\tif (!r.valid) throw r.error\n\t\t\treturn new (this as any)(r.value, ...args) as This['prototype']\n\t\t}\n\t}\n\n\treturn Configurable as unknown as (abstract new (validated: PipeOutput<P>, ...baseArgs: BaseCtorParams<Base>) => InstanceType<Base> & { readonly config: PipeOutput<P> }) & {\n\t\treadonly Config: PipeOutput<P>\n\t\tcreate<This extends Function & { prototype: any }>(\n\t\t\tthis: This,\n\t\t\tinput: ConditionalObjectKeys<PipeInput<P>>,\n\t\t\t...args: CtorParams<This> extends [PipeOutput<P>, ...infer R] ? R : never\n\t\t): This['prototype']\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect, expectTypeOf } = import.meta.vitest\n\tconst { v } = await import('valleyed')\n\n\tconst testPipe = () =>\n\t\tv.object({\n\t\t\thost: v.string(),\n\t\t\tport: v.number(),\n\t\t})\n\n\ttype TestConfig = PipeOutput<ReturnType<typeof testPipe>>\n\n\tclass TestBase {\n\t\tbaseValue: string\n\t\tconstructor() {\n\t\t\tthis.baseValue = 'base'\n\t\t}\n\t}\n\n\tclass TestBaseWithArgs {\n\t\tlabel: string\n\t\tconstructor(label: string) {\n\t\t\tthis.label = label\n\t\t}\n\t}\n\n\tdescribe('configurable', () => {\n\t\ttest('validation runs in static create before constructor body executes', () => {\n\t\t\tlet constructorRan = false\n\n\t\t\tconst Wrapped = configurable(testPipe, TestBase)\n\t\t\tclass MyClass extends Wrapped {\n\t\t\t\tprotected constructor(config: typeof MyClass.Config) {\n\t\t\t\t\tsuper(config)\n\t\t\t\t\tconstructorRan = true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpect(() => MyClass.create({ host: 123, port: 'bad' } as any)).toThrow()\n\t\t\texpect(constructorRan).toBe(false)\n\n\t\t\tMyClass.create({ host: 'localhost', port: 3000 })\n\t\t\texpect(constructorRan).toBe(true)\n\t\t})\n\n\t\ttest('constructor receives validated value', () => {\n\t\t\tconst Wrapped = configurable(testPipe, TestBase)\n\t\t\tlet receivedConfig: unknown\n\n\t\t\tclass MyClass extends Wrapped {\n\t\t\t\tprotected constructor(config: typeof MyClass.Config) {\n\t\t\t\t\tsuper(config)\n\t\t\t\t\treceivedConfig = this.config\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tMyClass.create({ host: 'localhost', port: 3000 })\n\n\t\t\texpect(receivedConfig).toEqual({ host: 'localhost', port: 3000 })\n\t\t})\n\n\t\ttest('external new is a compile error', () => {\n\t\t\tconst Wrapped = configurable(testPipe, TestBase)\n\t\t\tclass MyClass extends Wrapped {\n\t\t\t\tprotected constructor(config: typeof MyClass.Config) {\n\t\t\t\t\tsuper(config)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// @ts-expect-error — external `new` on a class with protected constructor is a compile error\n\t\t\tvoid (() => new MyClass({ host: 'localhost', port: 3000 }))\n\t\t})\n\n\t\ttest('static Config resolves to PipeOutput<P> at the type level', () => {\n\t\t\tconst Wrapped = configurable(testPipe, TestBase)\n\t\t\tclass MyClass extends Wrapped {\n\t\t\t\tprotected constructor(config: typeof MyClass.Config) {\n\t\t\t\t\tsuper(config)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\texpectTypeOf<typeof MyClass.Config>().toEqualTypeOf<TestConfig>()\n\t\t\texpect(MyClass.create).toBeTypeOf('function')\n\t\t})\n\n\t\ttest('ConstructorParameters<This>-based extras inference works for non-zero-arg leaf signatures', () => {\n\t\t\tconst Wrapped = configurable(testPipe, TestBase)\n\t\t\tclass MyClass extends Wrapped {\n\t\t\t\textra: number\n\t\t\t\tprotected constructor(config: typeof MyClass.Config, extra: number) {\n\t\t\t\t\tsuper(config)\n\t\t\t\t\tthis.extra = extra\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst instance = MyClass.create({ host: 'localhost', port: 3000 }, 42)\n\n\t\t\texpect(instance.extra).toBe(42)\n\t\t\texpectTypeOf(instance).toHaveProperty('extra')\n\t\t\texpectTypeOf(instance.extra).toEqualTypeOf<number>()\n\n\t\t\t// @ts-expect-error — wrong extra type\n\t\t\tvoid (() => MyClass.create({ host: 'localhost', port: 3000 }, 'not-a-number'))\n\t\t})\n\n\t\ttest('base-args forwarding works for non-zero-arg bases', () => {\n\t\t\tconst Wrapped = configurable(testPipe, TestBaseWithArgs)\n\t\t\tclass MyClass extends Wrapped {\n\t\t\t\tprotected constructor(config: typeof MyClass.Config, label: string) {\n\t\t\t\t\tsuper(config, label)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst instance = MyClass.create({ host: 'localhost', port: 3000 }, 'test-label')\n\n\t\t\texpect(instance.label).toBe('test-label')\n\t\t})\n\n\t\ttest('config is accessible as protected readonly on instances', () => {\n\t\t\tconst Wrapped = configurable(testPipe, TestBase)\n\t\t\tclass MyClass extends Wrapped {\n\t\t\t\tprotected constructor(config: typeof MyClass.Config) {\n\t\t\t\t\tsuper(config)\n\t\t\t\t}\n\n\t\t\t\tgetHost() {\n\t\t\t\t\treturn this.config.host\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst instance = MyClass.create({ host: 'localhost', port: 3000 })\n\t\t\texpect(instance.getHost()).toBe('localhost')\n\t\t})\n\n\t\ttest('accepts an abstract base class', () => {\n\t\t\tabstract class AbstractBase {\n\t\t\t\tabstract greet(): string\n\t\t\t}\n\n\t\t\tconst Wrapped = configurable(testPipe, AbstractBase)\n\t\t\tclass Concrete extends Wrapped {\n\t\t\t\tprotected constructor(config: typeof Concrete.Config) {\n\t\t\t\t\tsuper(config)\n\t\t\t\t}\n\t\t\t\tgreet() {\n\t\t\t\t\treturn `hello from ${this.config.host}`\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst instance = Concrete.create({ host: 'localhost', port: 3000 })\n\t\t\texpect(instance.greet()).toBe('hello from localhost')\n\t\t\texpectTypeOf(instance).toHaveProperty('config')\n\t\t})\n\t})\n}\n","export class EquippedError extends Error {\n\tconstructor(\n\t\tpublic readonly message: string,\n\t\tpublic readonly context: Record<string, unknown>,\n\t\tpublic readonly cause?: unknown,\n\t) {\n\t\tsuper(message, { cause })\n\t}\n}\n","import pino, { type Logger } from 'pino'\nimport { ulid } from 'ulid'\nimport { type Pipe, v } from 'valleyed'\n\nimport { EquippedError } from '../errors'\nimport { type ClassRef, type HookCb, type HookEvent, type HookOptions, type HookRecord, registerHook, runHooks } from './hooks'\nimport { instanceSettingsPipe, type Settings, type SettingsInput } from './settings'\n\nexport type { ClassRef, HookCb, HookEvent, HookOptions }\n\nexport class Instance {\n\tstatic #id: string | undefined\n\tstatic #instance: Instance | undefined\n\tstatic #hooks: Partial<Record<HookEvent, HookRecord[]>> = {}\n\treadonly settings: Readonly<Settings>\n\treadonly log: Logger<never>\n\n\tprivate constructor(settings: Settings) {\n\t\tInstance.#instance = this\n\t\tthis.settings = Object.freeze(settings)\n\t\tthis.log = pino<never>({\n\t\t\tlevel: this.settings.log.level,\n\t\t\tserializers: {\n\t\t\t\terr: pino.stdSerializers.err,\n\t\t\t\terror: pino.stdSerializers.err,\n\t\t\t\treq: pino.stdSerializers.req,\n\t\t\t\tres: pino.stdSerializers.res,\n\t\t\t},\n\t\t\tmixin: () => ({\n\t\t\t\tinstanceId: Instance.#id,\n\t\t\t}),\n\t\t})\n\t\tInstance.#registerOnExitHandler()\n\t}\n\n\talias(id: string) {\n\t\tif (Instance.#id !== undefined) return Instance.crash(new EquippedError('Instance already has an alias', {}))\n\t\tInstance.#id = id\n\t}\n\n\tget id() {\n\t\tif (Instance.#id === undefined) return Instance.crash(new EquippedError('Instance doesnt have an alias yet', {}))\n\t\treturn Instance.#id\n\t}\n\n\tgetScopedName(name: string, key = '.') {\n\t\treturn [this.settings.app.name, name].join(key)\n\t}\n\n\tasync start() {\n\t\ttry {\n\t\t\tawait runHooks(Instance.#hooks['setup'] ?? [])\n\t\t\tawait runHooks(Instance.#hooks['start'] ?? [])\n\t\t} catch (error) {\n\t\t\tInstance.crash(new EquippedError(`Error starting instance`, {}, error))\n\t\t}\n\t}\n\n\tstatic envs<E extends object>(envsPipe: Pipe<unknown, E>): E {\n\t\tconst envValidity = v.validate(envsPipe, process.env)\n\t\tif (!envValidity.valid) {\n\t\t\tInstance.crash(\n\t\t\t\tnew EquippedError(`Environment variables are not valid\\n${envValidity.error.toString()}`, {\n\t\t\t\t\tmessages: envValidity.error.messages,\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\t\treturn envValidity.value\n\t}\n\n\tstatic create(settings: SettingsInput) {\n\t\tif (Instance.#instance) return Instance.crash(new EquippedError('Instance has been initialized already', {}))\n\t\tconst settingsValidity = v.validate(instanceSettingsPipe(), settings)\n\t\tif (!settingsValidity.valid) {\n\t\t\tInstance.crash(\n\t\t\t\tnew EquippedError(`Settings are not valid\\n${settingsValidity.error.toString()}`, {\n\t\t\t\t\tmessages: settingsValidity.error.messages,\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\t\treturn new Instance(settingsValidity.value)\n\t}\n\n\tstatic get() {\n\t\tif (!Instance.#instance)\n\t\t\treturn Instance.crash(\n\t\t\t\tnew EquippedError('Has not been initialized. Make sure an instance has been created before you get an instance', {}),\n\t\t\t)\n\t\treturn Instance.#instance\n\t}\n\n\tstatic maybeGet() {\n\t\treturn Instance.#instance\n\t}\n\n\tstatic on(event: HookEvent, cb: HookCb, options?: HookOptions) {\n\t\tInstance.#hooks[event] ??= []\n\t\tconst record: HookRecord = { cb, class: options?.class, after: options?.after ?? [] }\n\t\tregisterHook(Instance.#hooks[event], record)\n\t}\n\n\tstatic #registerOnExitHandler() {\n\t\tconst signals = {\n\t\t\tSIGHUP: 1,\n\t\t\tSIGINT: 2,\n\t\t\tSIGTERM: 15,\n\t\t}\n\n\t\tObject.entries(signals).forEach(([signal, code]) => {\n\t\t\tprocess.on(signal, async () => {\n\t\t\t\tawait runHooks(Instance.#hooks['close'] ?? [], () => {}, true)\n\t\t\t\tprocess.exit(128 + code)\n\t\t\t})\n\t\t})\n\t}\n\n\tstatic resolveBeforeCrash<T>(cb: () => Promise<T>) {\n\t\tconst value = cb()\n\t\tInstance.on('close', async () => await value)\n\t\treturn value\n\t}\n\n\tstatic crash(error: EquippedError): never {\n\t\t// eslint-disable-next-line no-console\n\t\tconsole.error(error)\n\t\tprocess.exit(1)\n\t}\n\n\tstatic createId(opts?: { prefix?: string; time?: Date }) {\n\t\treturn `${opts?.prefix ?? ''}${ulid(opts?.time?.getTime())}`\n\t}\n}\n","export type HookEvent = 'setup' | 'start' | 'close'\nexport type HookCb = Promise<unknown | void> | (() => void | unknown | Promise<void | unknown>)\n\nexport type ClassRef = Function & { prototype: unknown; name: string }\nexport type HookOptions = {\n\tclass?: ClassRef\n\tafter?: ClassRef[]\n}\nexport type HookRecord = { cb: HookCb; class?: ClassRef; after: ClassRef[] }\n\nexport function registerHook(hooks: HookRecord[], record: HookRecord): void {\n\tfor (const dep of record.after) {\n\t\tif (!hooks.some((h) => h.class === dep)) {\n\t\t\tconst depName = dep.name || 'unknown'\n\t\t\tconst ownerName = record.class?.name || 'anonymous'\n\t\t\tthrow new Error(`Missing dependency: ${ownerName} declares after: [${depName}], but ${depName} is not registered`)\n\t\t}\n\t}\n\n\tif (record.class) {\n\t\tconst tentative = [...hooks, record]\n\t\tdetectCycle(tentative)\n\t}\n\n\thooks.push(record)\n}\n\nfunction detectCycle(hooks: HookRecord[]): void {\n\tconst classToAfter = new Map<ClassRef, ClassRef[]>()\n\tfor (const h of hooks) {\n\t\tif (!h.class) continue\n\t\tconst existing = classToAfter.get(h.class)\n\t\tif (existing) {\n\t\t\tfor (const dep of h.after) {\n\t\t\t\tif (!existing.includes(dep)) existing.push(dep)\n\t\t\t}\n\t\t} else {\n\t\t\tclassToAfter.set(h.class, [...h.after])\n\t\t}\n\t}\n\n\tconst visited = new Set<ClassRef>()\n\tconst stack = new Set<ClassRef>()\n\n\tfunction visit(cls: ClassRef, path: ClassRef[]): void {\n\t\tif (stack.has(cls)) {\n\t\t\tconst cycleStart = path.indexOf(cls)\n\t\t\tconst cycle = [...path.slice(cycleStart), cls]\n\t\t\tconst names = cycle.map((c) => c.name || 'unknown')\n\t\t\tthrow new Error(`Cycle detected: ${names.join(' → ')}`)\n\t\t}\n\t\tif (visited.has(cls)) return\n\t\tstack.add(cls)\n\t\tpath.push(cls)\n\t\tfor (const dep of classToAfter.get(cls) ?? []) {\n\t\t\tvisit(dep, path)\n\t\t}\n\t\tpath.pop()\n\t\tstack.delete(cls)\n\t\tvisited.add(cls)\n\t}\n\n\tfor (const cls of classToAfter.keys()) {\n\t\tvisit(cls, [])\n\t}\n}\n\nexport type HookLayer = HookRecord[]\n\nexport function resolveHookDAG(hooks: HookRecord[], invert: boolean = false): HookLayer[] {\n\tif (hooks.length === 0) return []\n\n\tconst classToHooks = new Map<ClassRef, HookRecord[]>()\n\tconst anonymous: HookRecord[] = []\n\n\tfor (const h of hooks) {\n\t\tif (h.class) {\n\t\t\tconst list = classToHooks.get(h.class)\n\t\t\tif (list) list.push(h)\n\t\t\telse classToHooks.set(h.class, [h])\n\t\t} else {\n\t\t\tanonymous.push(h)\n\t\t}\n\t}\n\n\tconst classes = [...classToHooks.keys()]\n\n\tconst inDegree = new Map<ClassRef, number>()\n\tconst graph = new Map<ClassRef, ClassRef[]>()\n\n\tfor (const cls of classes) {\n\t\tinDegree.set(cls, 0)\n\t\tgraph.set(cls, [])\n\t}\n\n\tfor (const h of hooks) {\n\t\tif (!h.class) continue\n\t\tfor (const dep of h.after) {\n\t\t\tconst [from, to] = invert ? [h.class, dep] : [dep, h.class]\n\t\t\tconst edges = graph.get(from)!\n\t\t\tif (!edges.includes(to)) {\n\t\t\t\tedges.push(to)\n\t\t\t\tinDegree.set(to, inDegree.get(to)! + 1)\n\t\t\t}\n\t\t}\n\t}\n\n\tconst layers: HookLayer[] = []\n\tlet queue = classes.filter((c) => inDegree.get(c) === 0)\n\n\twhile (queue.length > 0) {\n\t\tconst layer: HookRecord[] = []\n\t\tconst next: ClassRef[] = []\n\n\t\tfor (const cls of queue) {\n\t\t\tlayer.push(...(classToHooks.get(cls) ?? []))\n\t\t}\n\t\tlayers.push(layer)\n\n\t\tfor (const cls of queue) {\n\t\t\tfor (const neighbor of graph.get(cls) ?? []) {\n\t\t\t\tconst d = inDegree.get(neighbor)! - 1\n\t\t\t\tinDegree.set(neighbor, d)\n\t\t\t\tif (d === 0) next.push(neighbor)\n\t\t\t}\n\t\t}\n\n\t\tqueue = next\n\t}\n\n\tif (anonymous.length > 0) {\n\t\tconst anonWithDeps = anonymous.filter((h) => h.after.length > 0)\n\t\tconst anonWithoutDeps = anonymous.filter((h) => h.after.length === 0)\n\n\t\tif (anonWithDeps.length > 0) {\n\t\t\tlet maxLayer = -1\n\t\t\tfor (const h of anonWithDeps) {\n\t\t\t\tfor (const dep of h.after) {\n\t\t\t\t\tconst depLayer = layers.findIndex((l) => l.some((r) => r.class === dep))\n\t\t\t\t\tif (depLayer > maxLayer) maxLayer = depLayer\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst insertAt = maxLayer + 1\n\t\t\tif (insertAt < layers.length) {\n\t\t\t\tlayers[insertAt].push(...anonWithDeps)\n\t\t\t} else {\n\t\t\t\tlayers.push(anonWithDeps)\n\t\t\t}\n\t\t}\n\n\t\tif (anonWithoutDeps.length > 0) {\n\t\t\tif (layers.length > 0) {\n\t\t\t\tlayers[layers.length - 1].push(...anonWithoutDeps)\n\t\t\t} else {\n\t\t\t\tlayers.push(anonWithoutDeps)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn layers\n}\n\nexport async function runHooks(\n\thooks: HookRecord[],\n\tonError: (error: Error) => void = (error) => {\n\t\tthrow error\n\t},\n\tinvert: boolean = false,\n) {\n\tconst layers = resolveHookDAG(hooks, invert)\n\tfor (const layer of layers)\n\t\tawait Promise.all(\n\t\t\tlayer.map(async (h) => {\n\t\t\t\ttry {\n\t\t\t\t\tif (typeof h.cb === 'function') return await h.cb()\n\t\t\t\t\treturn await h.cb\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn onError(error instanceof Error ? error : new Error(`${error}`))\n\t\t\t\t}\n\t\t\t}),\n\t\t)\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect } = import.meta.vitest\n\n\tclass A {}\n\tclass B {}\n\tclass C {}\n\tclass D {}\n\n\tconst noop = () => {}\n\n\tdescribe('registerHook', () => {\n\t\ttest('registers a hook with no dependencies', () => {\n\t\t\tconst hooks: HookRecord[] = []\n\t\t\tregisterHook(hooks, { cb: noop, class: A, after: [] })\n\t\t\texpect(hooks).toHaveLength(1)\n\t\t})\n\n\t\ttest('throws on missing dependency', () => {\n\t\t\tconst hooks: HookRecord[] = []\n\t\t\texpect(() => registerHook(hooks, { cb: noop, class: B, after: [A] })).toThrow(/Missing dependency.*B.*A/)\n\t\t})\n\n\t\ttest('throws on cycle: A→B→A', () => {\n\t\t\tconst hooks: HookRecord[] = []\n\t\t\tregisterHook(hooks, { cb: noop, class: A, after: [] })\n\t\t\tregisterHook(hooks, { cb: noop, class: B, after: [A] })\n\t\t\texpect(() => registerHook(hooks, { cb: noop, class: A, after: [B] })).toThrow(/Cycle detected/)\n\t\t})\n\n\t\ttest('throws on transitive cycle: A→B→C→A', () => {\n\t\t\tconst hooks: HookRecord[] = []\n\t\t\tregisterHook(hooks, { cb: noop, class: A, after: [] })\n\t\t\tregisterHook(hooks, { cb: noop, class: B, after: [A] })\n\t\t\tregisterHook(hooks, { cb: noop, class: C, after: [B] })\n\t\t\texpect(() => registerHook(hooks, { cb: noop, class: A, after: [C] })).toThrow(/Cycle detected/)\n\t\t})\n\n\t\ttest('allows anonymous hooks with dependencies', () => {\n\t\t\tconst hooks: HookRecord[] = []\n\t\t\tregisterHook(hooks, { cb: noop, class: A, after: [] })\n\t\t\tregisterHook(hooks, { cb: noop, after: [A] })\n\t\t\texpect(hooks).toHaveLength(2)\n\t\t})\n\n\t\ttest('anonymous hook throws on missing dependency', () => {\n\t\t\tconst hooks: HookRecord[] = []\n\t\t\texpect(() => registerHook(hooks, { cb: noop, after: [A] })).toThrow(/Missing dependency.*anonymous.*A/)\n\t\t})\n\t})\n\n\tdescribe('resolveHookDAG', () => {\n\t\ttest('linear chain: A → B → C', () => {\n\t\t\tconst hooks: HookRecord[] = [\n\t\t\t\t{ cb: noop, class: A, after: [] },\n\t\t\t\t{ cb: noop, class: B, after: [A] },\n\t\t\t\t{ cb: noop, class: C, after: [B] },\n\t\t\t]\n\t\t\tconst layers = resolveHookDAG(hooks)\n\t\t\texpect(layers).toHaveLength(3)\n\t\t\texpect(layers[0].every((h) => h.class === A)).toBe(true)\n\t\t\texpect(layers[1].every((h) => h.class === B)).toBe(true)\n\t\t\texpect(layers[2].every((h) => h.class === C)).toBe(true)\n\t\t})\n\n\t\ttest('fan-out: A → B, A → C (B and C are parallel)', () => {\n\t\t\tconst hooks: HookRecord[] = [\n\t\t\t\t{ cb: noop, class: A, after: [] },\n\t\t\t\t{ cb: noop, class: B, after: [A] },\n\t\t\t\t{ cb: noop, class: C, after: [A] },\n\t\t\t]\n\t\t\tconst layers = resolveHookDAG(hooks)\n\t\t\texpect(layers).toHaveLength(2)\n\t\t\texpect(layers[0].every((h) => h.class === A)).toBe(true)\n\t\t\tconst secondClasses = layers[1].map((h) => h.class)\n\t\t\texpect(secondClasses).toContain(B)\n\t\t\texpect(secondClasses).toContain(C)\n\t\t})\n\n\t\ttest('fan-in: B → D, C → D', () => {\n\t\t\tconst hooks: HookRecord[] = [\n\t\t\t\t{ cb: noop, class: B, after: [] },\n\t\t\t\t{ cb: noop, class: C, after: [] },\n\t\t\t\t{ cb: noop, class: D, after: [B, C] },\n\t\t\t]\n\t\t\tconst layers = resolveHookDAG(hooks)\n\t\t\texpect(layers).toHaveLength(2)\n\t\t\tconst firstClasses = layers[0].map((h) => h.class)\n\t\t\texpect(firstClasses).toContain(B)\n\t\t\texpect(firstClasses).toContain(C)\n\t\t\texpect(layers[1].every((h) => h.class === D)).toBe(true)\n\t\t})\n\n\t\ttest('diamond: A → B, A → C, B → D, C → D', () => {\n\t\t\tconst hooks: HookRecord[] = [\n\t\t\t\t{ cb: noop, class: A, after: [] },\n\t\t\t\t{ cb: noop, class: B, after: [A] },\n\t\t\t\t{ cb: noop, class: C, after: [A] },\n\t\t\t\t{ cb: noop, class: D, after: [B, C] },\n\t\t\t]\n\t\t\tconst layers = resolveHookDAG(hooks)\n\t\t\texpect(layers).toHaveLength(3)\n\t\t\texpect(layers[0][0].class).toBe(A)\n\t\t\tconst midClasses = layers[1].map((h) => h.class)\n\t\t\texpect(midClasses).toContain(B)\n\t\t\texpect(midClasses).toContain(C)\n\t\t\texpect(layers[2][0].class).toBe(D)\n\t\t})\n\n\t\ttest('close-event inversion reverses the graph', () => {\n\t\t\tconst hooks: HookRecord[] = [\n\t\t\t\t{ cb: noop, class: A, after: [] },\n\t\t\t\t{ cb: noop, class: B, after: [A] },\n\t\t\t\t{ cb: noop, class: C, after: [B] },\n\t\t\t]\n\t\t\tconst layers = resolveHookDAG(hooks, true)\n\t\t\texpect(layers).toHaveLength(3)\n\t\t\texpect(layers[0].every((h) => h.class === C)).toBe(true)\n\t\t\texpect(layers[1].every((h) => h.class === B)).toBe(true)\n\t\t\texpect(layers[2].every((h) => h.class === A)).toBe(true)\n\t\t})\n\n\t\ttest('close inversion: fan-out becomes fan-in', () => {\n\t\t\tconst hooks: HookRecord[] = [\n\t\t\t\t{ cb: noop, class: A, after: [] },\n\t\t\t\t{ cb: noop, class: B, after: [A] },\n\t\t\t\t{ cb: noop, class: C, after: [A] },\n\t\t\t]\n\t\t\tconst layers = resolveHookDAG(hooks, true)\n\t\t\texpect(layers).toHaveLength(2)\n\t\t\tconst firstClasses = layers[0].map((h) => h.class)\n\t\t\texpect(firstClasses).toContain(B)\n\t\t\texpect(firstClasses).toContain(C)\n\t\t\texpect(layers[1].every((h) => h.class === A)).toBe(true)\n\t\t})\n\n\t\ttest('multiple hooks per class are all in the same layer', () => {\n\t\t\tconst cb1 = () => {}\n\t\t\tconst cb2 = () => {}\n\t\t\tconst hooks: HookRecord[] = [\n\t\t\t\t{ cb: cb1, class: A, after: [] },\n\t\t\t\t{ cb: cb2, class: A, after: [] },\n\t\t\t\t{ cb: noop, class: B, after: [A] },\n\t\t\t]\n\t\t\tconst layers = resolveHookDAG(hooks)\n\t\t\texpect(layers).toHaveLength(2)\n\t\t\texpect(layers[0]).toHaveLength(2)\n\t\t\texpect(layers[0].every((h) => h.class === A)).toBe(true)\n\t\t})\n\n\t\ttest('anonymous hooks without deps run at deepest level', () => {\n\t\t\tconst hooks: HookRecord[] = [\n\t\t\t\t{ cb: noop, class: A, after: [] },\n\t\t\t\t{ cb: noop, class: B, after: [A] },\n\t\t\t\t{ cb: noop, after: [] },\n\t\t\t]\n\t\t\tconst layers = resolveHookDAG(hooks)\n\t\t\texpect(layers).toHaveLength(2)\n\t\t\tconst lastLayer = layers[layers.length - 1]\n\t\t\texpect(lastLayer.some((h) => h.class === undefined)).toBe(true)\n\t\t})\n\n\t\ttest('anonymous hooks with deps run after their dependencies', () => {\n\t\t\tconst hooks: HookRecord[] = [\n\t\t\t\t{ cb: noop, class: A, after: [] },\n\t\t\t\t{ cb: noop, class: B, after: [A] },\n\t\t\t\t{ cb: noop, class: C, after: [B] },\n\t\t\t\t{ cb: noop, after: [A] },\n\t\t\t]\n\t\t\tconst layers = resolveHookDAG(hooks)\n\t\t\tconst anonLayer = layers.findIndex((l) => l.some((h) => h.class === undefined))\n\t\t\tconst aLayer = layers.findIndex((l) => l.some((h) => h.class === A))\n\t\t\texpect(anonLayer).toBeGreaterThan(aLayer)\n\t\t})\n\n\t\ttest('empty hooks returns empty layers', () => {\n\t\t\texpect(resolveHookDAG([])).toEqual([])\n\t\t})\n\t})\n\n\tdescribe('runHooks', () => {\n\t\ttest('same-depth siblings observably overlap', async () => {\n\t\t\tconst log: string[] = []\n\t\t\tconst hooks: HookRecord[] = [\n\t\t\t\t{\n\t\t\t\t\tcb: async () => {\n\t\t\t\t\t\tlog.push('B-start')\n\t\t\t\t\t\tawait new Promise((r) => setTimeout(r, 20))\n\t\t\t\t\t\tlog.push('B-end')\n\t\t\t\t\t},\n\t\t\t\t\tclass: B,\n\t\t\t\t\tafter: [],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tcb: async () => {\n\t\t\t\t\t\tlog.push('C-start')\n\t\t\t\t\t\tawait new Promise((r) => setTimeout(r, 20))\n\t\t\t\t\t\tlog.push('C-end')\n\t\t\t\t\t},\n\t\t\t\t\tclass: C,\n\t\t\t\t\tafter: [],\n\t\t\t\t},\n\t\t\t]\n\t\t\tawait runHooks(hooks)\n\t\t\texpect(log[0]).toBe('B-start')\n\t\t\texpect(log[1]).toBe('C-start')\n\t\t})\n\n\t\ttest('different depths run sequentially', async () => {\n\t\t\tconst log: string[] = []\n\t\t\tconst hooks: HookRecord[] = [\n\t\t\t\t{\n\t\t\t\t\tcb: async () => {\n\t\t\t\t\t\tlog.push('A')\n\t\t\t\t\t},\n\t\t\t\t\tclass: A,\n\t\t\t\t\tafter: [],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tcb: async () => {\n\t\t\t\t\t\tlog.push('B')\n\t\t\t\t\t},\n\t\t\t\t\tclass: B,\n\t\t\t\t\tafter: [A],\n\t\t\t\t},\n\t\t\t]\n\t\t\tawait runHooks(hooks)\n\t\t\texpect(log).toEqual(['A', 'B'])\n\t\t})\n\n\t\ttest('close inversion runs hooks in reverse dependency order', async () => {\n\t\t\tconst log: string[] = []\n\t\t\tconst hooks: HookRecord[] = [\n\t\t\t\t{ cb: async () => log.push('A'), class: A, after: [] },\n\t\t\t\t{ cb: async () => log.push('B'), class: B, after: [A] },\n\t\t\t\t{ cb: async () => log.push('C'), class: C, after: [B] },\n\t\t\t]\n\t\t\tawait runHooks(hooks, undefined, true)\n\t\t\texpect(log).toEqual(['C', 'B', 'A'])\n\t\t})\n\n\t\ttest('errors are routed to onError handler', async () => {\n\t\t\tconst errors: Error[] = []\n\t\t\tconst hooks: HookRecord[] = [\n\t\t\t\t{\n\t\t\t\t\tcb: () => {\n\t\t\t\t\t\tthrow new Error('boom')\n\t\t\t\t\t},\n\t\t\t\t\tclass: A,\n\t\t\t\t\tafter: [],\n\t\t\t\t},\n\t\t\t]\n\t\t\tawait runHooks(hooks, (e) => {\n\t\t\t\terrors.push(e)\n\t\t\t})\n\t\t\texpect(errors).toHaveLength(1)\n\t\t\texpect(errors[0].message).toBe('boom')\n\t\t})\n\n\t\ttest('raw promise callbacks are awaited', async () => {\n\t\t\tconst log: string[] = []\n\t\t\tconst hooks: HookRecord[] = [\n\t\t\t\t{\n\t\t\t\t\tcb: Promise.resolve().then(() => log.push('resolved')),\n\t\t\t\t\tclass: A,\n\t\t\t\t\tafter: [],\n\t\t\t\t},\n\t\t\t]\n\t\t\tawait runHooks(hooks)\n\t\t\texpect(log).toContain('resolved')\n\t\t})\n\t})\n}\n","import { type ConditionalObjectKeys, type PipeInput, type PipeOutput, v } from 'valleyed'\n\nexport const instanceSettingsPipe = () =>\n\tv.object({\n\t\tapp: v.object({\n\t\t\tname: v.string(),\n\t\t}),\n\t\tlog: v.defaults(\n\t\t\tv.object({\n\t\t\t\tlevel: v.defaults(v.in(['fatal', 'error', 'warn', 'info', 'debug', 'trace', 'silent'] as const), 'info'),\n\t\t\t}),\n\t\t\t{},\n\t\t),\n\t\tutils: v.defaults(\n\t\t\tv.object({\n\t\t\t\thashSaltRounds: v.defaults(v.number(), 10),\n\t\t\t\tpaginationDefaultLimit: v.defaults(v.number(), 100),\n\t\t\t\tmaxFileUploadSizeInMb: v.defaults(v.number(), 500),\n\t\t\t}),\n\t\t\t{},\n\t\t),\n\t})\n\nexport type Settings = PipeOutput<ReturnType<typeof instanceSettingsPipe>>\nexport type SettingsInput = ConditionalObjectKeys<PipeInput<ReturnType<typeof instanceSettingsPipe>>>\n","import { v, type Pipe, type PipeOutput } from 'valleyed'\n\nexport class Field<T = unknown, Name extends string = string, S = unknown> {\n\tdeclare readonly __valueType?: T\n\tdeclare readonly __schema?: S\n\treadonly name: Name\n\treadonly path: readonly string[]\n\n\tconstructor(name: Name, path?: readonly string[]) {\n\t\tthis.name = name\n\t\tthis.path = path ?? [name]\n\t}\n}\n\nexport type AnyField = Field<unknown, string, any>\n\nexport function toFieldName(field: string | AnyField): string {\n\tif (field instanceof Field) return field.path.join('.')\n\treturn field\n}\n\nexport class SchemaField<\n\tName extends string = string,\n\tP extends Pipe<any, any> = Pipe<any, any>,\n\tHasOnCreate extends boolean = false,\n> extends Field<PipeOutput<P>, Name> {\n\tdeclare readonly __hasOnCreate?: HasOnCreate\n\treadonly pipe: P\n\treadonly onCreate?: () => PipeOutput<P>\n\treadonly onUpdate?: () => PipeOutput<P>\n\n\tconstructor(name: Name, pipe: P, opts?: { onCreate?: () => PipeOutput<P>; onUpdate?: () => PipeOutput<P> }) {\n\t\tsuper(name)\n\t\tthis.pipe = pipe\n\t\tthis.onCreate = opts?.onCreate\n\t\tthis.onUpdate = opts?.onUpdate\n\t}\n}\n\nexport type AnySchemaField = SchemaField<string, Pipe<any, any>, boolean>\n\nexport class ComputedField<\n\tName extends string = string,\n\tP extends Pipe<any, any> = Pipe<any, any>,\n\tDeps extends readonly string[] = readonly string[],\n> {\n\tconstructor(\n\t\treadonly name: Name,\n\t\treadonly pipe: P,\n\t\treadonly deps: Deps,\n\t\treadonly compute: (data: Record<Deps[number], unknown>) => PipeOutput<P>,\n\t) {}\n}\n\nexport type AnyComputedField = ComputedField<string, Pipe<any, any>, readonly string[]>\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect } = import.meta.vitest\n\n\tdescribe('Fields', () => {\n\t\tdescribe('Field', () => {\n\t\t\ttest('stores name and defaults path to [name]', () => {\n\t\t\t\tconst f = new Field('email')\n\t\t\t\texpect(f.name).toBe('email')\n\t\t\t\texpect(f.path).toEqual(['email'])\n\t\t\t})\n\n\t\t\ttest('stores provided custom path', () => {\n\t\t\t\tconst f = new Field('profile', ['profile', 'displayName'])\n\t\t\t\texpect(f.name).toBe('profile')\n\t\t\t\texpect(f.path).toEqual(['profile', 'displayName'])\n\t\t\t})\n\t\t})\n\n\t\tdescribe('toFieldName()', () => {\n\t\t\ttest('returns string input unchanged', () => {\n\t\t\t\texpect(toFieldName('createdAt')).toBe('createdAt')\n\t\t\t})\n\n\t\t\ttest('converts Field path into dot notation', () => {\n\t\t\t\tconst f = new Field('profile', ['profile', 'displayName'])\n\t\t\t\texpect(toFieldName(f)).toBe('profile.displayName')\n\t\t\t})\n\t\t})\n\n\t\tdescribe('SchemaField', () => {\n\t\t\ttest('extends Field and stores pipe', () => {\n\t\t\t\tconst mockPipe = v.string()\n\t\t\t\tconst f = new SchemaField('email', mockPipe)\n\t\t\t\texpect(f).toBeInstanceOf(Field)\n\t\t\t\texpect(f.name).toBe('email')\n\t\t\t\texpect(f.path).toEqual(['email'])\n\t\t\t\texpect(f.pipe).toBe(mockPipe)\n\t\t\t})\n\n\t\t\ttest('stores lifecycle hooks when provided', () => {\n\t\t\t\tconst mockPipe = v.number()\n\t\t\t\tconst f = new SchemaField('updatedAt', mockPipe, {\n\t\t\t\t\tonCreate: () => 1000,\n\t\t\t\t\tonUpdate: () => 2000,\n\t\t\t\t})\n\t\t\t\texpect(f.onCreate?.()).toBe(1000)\n\t\t\t\texpect(f.onUpdate?.()).toBe(2000)\n\t\t\t})\n\n\t\t\ttest('keeps lifecycle hooks undefined when omitted', () => {\n\t\t\t\tconst mockPipe = v.string()\n\t\t\t\tconst f = new SchemaField('name', mockPipe)\n\t\t\t\texpect(f.onCreate).toBeUndefined()\n\t\t\t\texpect(f.onUpdate).toBeUndefined()\n\t\t\t})\n\t\t})\n\t})\n}\n","import type { FilterOpName } from './adapter'\nimport { EquippedError } from '../errors'\nimport { OrmValidationError, type OrmValidationFailure } from './errors'\nimport { toFieldName, type AnyField, type Field } from './fields'\nimport type { AggregateSpec } from './orm-adapter'\nimport type { AnySchema } from './schema'\n\nexport class Filter {\n\treadonly field: string\n\tconstructor(\n\t\tfield: string | AnyField,\n\t\treadonly op: FilterOpName,\n\t\treadonly value: unknown,\n\t) {\n\t\tthis.field = toFieldName(field)\n\t}\n}\n\nexport type FilterChild = Filter | FilterGroup\n\nexport type FilterFactory = (q: FilterGroup) => FilterGroup\n\nexport type FilterGroupOp = 'and' | 'or'\n\nexport class FilterGroup {\n\treadonly children: readonly FilterChild[]\n\n\tprivate constructor(\n\t\treadonly op: FilterGroupOp = 'and',\n\t\tchildren?: readonly FilterChild[],\n\t) {\n\t\tthis.children = children ?? []\n\t}\n\n\t#withChild(child: FilterChild): FilterGroup {\n\t\treturn new FilterGroup(this.op, [...this.children, child])\n\t}\n\n\teq<T>(field: string | Field<T>, value: T): FilterGroup {\n\t\treturn this.#withChild(new Filter(field, 'eq', value))\n\t}\n\n\tne<T>(field: string | Field<T>, value: T): FilterGroup {\n\t\treturn this.#withChild(new Filter(field, 'ne', value))\n\t}\n\n\tgt<T>(field: string | Field<T>, value: T): FilterGroup {\n\t\treturn this.#withChild(new Filter(field, 'gt', value))\n\t}\n\n\tgte<T>(field: string | Field<T>, value: T): FilterGroup {\n\t\treturn this.#withChild(new Filter(field, 'gte', value))\n\t}\n\n\tlt<T>(field: string | Field<T>, value: T): FilterGroup {\n\t\treturn this.#withChild(new Filter(field, 'lt', value))\n\t}\n\n\tlte<T>(field: string | Field<T>, value: T): FilterGroup {\n\t\treturn this.#withChild(new Filter(field, 'lte', value))\n\t}\n\n\tin<T>(field: string | Field<T>, value: T[]): FilterGroup {\n\t\treturn this.#withChild(new Filter(field, 'in', value))\n\t}\n\n\tnotIn<T>(field: string | Field<T>, value: T[]): FilterGroup {\n\t\treturn this.#withChild(new Filter(field, 'notIn', value))\n\t}\n\n\tlike(field: string | Field<string>, value: string): FilterGroup {\n\t\treturn this.#withChild(new Filter(field, 'like', value))\n\t}\n\n\texists(field: string | Field<unknown>): FilterGroup {\n\t\treturn this.#withChild(new Filter(field, 'exists', true))\n\t}\n\n\tnotExists(field: string | Field<unknown>): FilterGroup {\n\t\treturn this.#withChild(new Filter(field, 'notExists', true))\n\t}\n\n\tcontains<T>(field: string | Field<T>, value: T[]): FilterGroup {\n\t\treturn this.#withChild(new Filter(field, 'contains', value))\n\t}\n\n\tnotContains<T>(field: string | Field<T>, value: T[]): FilterGroup {\n\t\treturn this.#withChild(new Filter(field, 'notContains', value))\n\t}\n\n\tand(facFns: FilterFactory[]): FilterGroup {\n\t\tif (facFns.length === 0) throw new EquippedError('and() requires at least one filter factory', { op: 'and' })\n\t\tconst group = new FilterGroup('and', facFns.map((fn) => fn(FilterGroup.create())))\n\t\treturn this.#withChild(group)\n\t}\n\n\tor(facFns: FilterFactory[]): FilterGroup {\n\t\tif (facFns.length === 0) throw new EquippedError('or() requires at least one filter factory', { op: 'or' })\n\t\tconst group = new FilterGroup('or', facFns.map((fn) => fn(FilterGroup.create())))\n\t\treturn this.#withChild(group)\n\t}\n\n\tclone(): FilterGroup {\n\t\treturn new FilterGroup(\n\t\t\tthis.op,\n\t\t\tthis.children.map((c) => {\n\t\t\t\tif (c instanceof Filter) return new Filter(c.field, c.op, structuredClone(c.value))\n\t\t\t\treturn c.clone()\n\t\t\t}),\n\t\t)\n\t}\n\n\tstatic create(): FilterGroup {\n\t\treturn new FilterGroup()\n\t}\n}\n\n\nexport type GatedFilterGroup<DeclaredOps extends readonly FilterOpName[]> = {\n\t[K in FilterOpName]: K extends DeclaredOps[number] ? FilterGroup[K] : never\n} & Pick<FilterGroup, 'and' | 'or' | 'clone' | 'children' | 'op'>\n\nexport type GatedFilterFactory<DeclaredOps extends readonly FilterOpName[]> = (\n\tq: GatedFilterGroup<DeclaredOps>,\n) => GatedFilterGroup<DeclaredOps>\n\nexport function assertNormalisedAggregate(schema: AnySchema, adapter: { aggregateOps: readonly string[] }, spec: AggregateSpec): void {\n\tconst failures: OrmValidationFailure[] = []\n\n\tif (spec.aggregates.length === 0) {\n\t\tfailures.push({ cause: 'At least one aggregator step is required' })\n\t}\n\n\tconst seenAliases = new Set<string>()\n\tfor (const agg of spec.aggregates) {\n\t\tif (seenAliases.has(agg.alias)) {\n\t\t\tfailures.push({ alias: agg.alias, cause: `Duplicate alias \"${agg.alias}\"` })\n\t\t}\n\t\tseenAliases.add(agg.alias)\n\n\t\tif (!adapter.aggregateOps.includes(agg.fn)) {\n\t\t\tfailures.push({ alias: agg.alias, cause: `Undeclared aggregate op \"${agg.fn}\"` })\n\t\t}\n\t}\n\n\tconst allFields = schema.fields as Record<string, unknown>\n\tconst fieldNames = new Set(Object.keys(allFields))\n\tfor (const field of spec.groupBy) {\n\t\tif (!fieldNames.has(field)) {\n\t\t\tfailures.push({ field, cause: `Unknown groupBy field \"${field}\" on schema \"${schema.name}\"` })\n\t\t}\n\t\tif (seenAliases.has(field)) {\n\t\t\tfailures.push({ alias: field, cause: `Alias \"${field}\" collides with groupBy field name` })\n\t\t}\n\t}\n\n\tif (failures.length > 0) {\n\t\tthrow new OrmValidationError('aggregate', schema.name, 'aggregate', failures)\n\t}\n\n\tif (spec.where) {\n\t\tassertNormalisedFilter(schema, spec.where)\n\t}\n\n\tif (spec.having) {\n\t\tconst validHavingFields = new Set([...seenAliases, ...spec.groupBy])\n\t\tconst havingErrors: OrmValidationFailure[] = []\n\n\t\tfunction walkHaving(node: FilterChild): void {\n\t\t\tif (node instanceof Filter) {\n\t\t\t\tif (!validHavingFields.has(node.field)) {\n\t\t\t\t\thavingErrors.push({ field: node.field, cause: `Unknown having field \"${node.field}\" — must be an aggregator alias or groupBy field` })\n\t\t\t\t}\n\t\t\t} else if (node instanceof FilterGroup) {\n\t\t\t\tfor (const child of node.children) walkHaving(child)\n\t\t\t}\n\t\t}\n\n\t\tfor (const child of spec.having.children) walkHaving(child)\n\n\t\tif (havingErrors.length > 0) {\n\t\t\tthrow new OrmValidationError('aggregate', schema.name, 'aggregate', havingErrors)\n\t\t}\n\t}\n}\n\nexport function assertNormalisedFilter(schema: AnySchema, group: FilterGroup): void {\n\tconst allFields = schema.fields as Record<string, unknown>\n\tconst fieldNames = new Set(Object.keys(allFields))\n\tconst errors: Array<{ field: string; cause: string }> = []\n\n\tfunction walk(node: FilterChild): void {\n\t\tif (node instanceof Filter) {\n\t\t\tif (!fieldNames.has(node.field)) {\n\t\t\t\terrors.push({ field: node.field, cause: `Unknown field \"${node.field}\" on schema \"${schema.name}\"` })\n\t\t\t}\n\t\t} else if (node instanceof FilterGroup) {\n\t\t\tfor (const child of node.children) walk(child)\n\t\t}\n\t}\n\n\tfor (const child of group.children) walk(child)\n\n\tif (errors.length > 0) {\n\t\tthrow new OrmValidationError(\n\t\t\t'validation',\n\t\t\tschema.name,\n\t\t\t'filter',\n\t\t\terrors.map((e) => ({ field: e.field, cause: e.cause })),\n\t\t)\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect } = import.meta.vitest\n\tconst { v } = await import('valleyed')\n\tconst { Schema } = await import('./schema')\n\n\tconst UserSchema = Schema.from('users')\n\t\t.pk('id', v.string(), () => 'u1')\n\t\t.field('email', v.string())\n\t\t.field('age', v.number())\n\t\t.field('name', v.string())\n\t\t.field('tags', v.array(v.string()))\n\t\t.build()\n\n\tdescribe('FilterGroup', () => {\n\t\tdescribe('filter-op methods', () => {\n\t\t\ttest('eq adds a filter clause with op eq', () => {\n\t\t\t\tconst g = FilterGroup.create().eq(UserSchema.fields.age, 25)\n\t\t\t\texpect(g.children).toHaveLength(1)\n\t\t\t\tconst f = g.children[0] as Filter\n\t\t\t\texpect(f.field).toBe('age')\n\t\t\t\texpect(f.op).toBe('eq')\n\t\t\t\texpect(f.value).toBe(25)\n\t\t\t})\n\n\t\t\ttest('ne adds a filter clause with op ne', () => {\n\t\t\t\tconst g = FilterGroup.create().ne('name', 'Bob')\n\t\t\t\tconst f = g.children[0] as Filter\n\t\t\t\texpect(f.op).toBe('ne')\n\t\t\t\texpect(f.value).toBe('Bob')\n\t\t\t})\n\n\t\t\ttest('gt/gte/lt/lte produce correct ops', () => {\n\t\t\t\tconst g = FilterGroup.create()\n\t\t\t\t\t.gt(UserSchema.fields.age, 10)\n\t\t\t\t\t.gte(UserSchema.fields.age, 20)\n\t\t\t\t\t.lt(UserSchema.fields.age, 30)\n\t\t\t\t\t.lte(UserSchema.fields.age, 40)\n\t\t\t\texpect(g.children.map((c) => (c as Filter).op)).toEqual(['gt', 'gte', 'lt', 'lte'])\n\t\t\t})\n\n\t\t\ttest('in adds a filter clause with array value', () => {\n\t\t\t\tconst g = FilterGroup.create().in(UserSchema.fields.age, [1, 2, 3])\n\t\t\t\tconst f = g.children[0] as Filter\n\t\t\t\texpect(f.op).toBe('in')\n\t\t\t\texpect(f.value).toEqual([1, 2, 3])\n\t\t\t})\n\n\t\t\ttest('notIn adds a filter clause with op notIn', () => {\n\t\t\t\tconst g = FilterGroup.create().notIn(UserSchema.fields.age, [4, 5])\n\t\t\t\tconst f = g.children[0] as Filter\n\t\t\t\texpect(f.op).toBe('notIn')\n\t\t\t\texpect(f.value).toEqual([4, 5])\n\t\t\t})\n\n\t\t\ttest('like adds a filter clause with op like', () => {\n\t\t\t\tconst g = FilterGroup.create().like(UserSchema.fields.email, 'alice')\n\t\t\t\tconst f = g.children[0] as Filter\n\t\t\t\texpect(f.op).toBe('like')\n\t\t\t\texpect(f.value).toBe('alice')\n\t\t\t})\n\n\t\t\ttest('exists adds a filter clause with op exists', () => {\n\t\t\t\tconst g = FilterGroup.create().exists(UserSchema.fields.name)\n\t\t\t\tconst f = g.children[0] as Filter\n\t\t\t\texpect(f.op).toBe('exists')\n\t\t\t})\n\n\t\t\ttest('notExists is its own op, not a boolean form of exists', () => {\n\t\t\t\tconst g = FilterGroup.create().notExists(UserSchema.fields.name)\n\t\t\t\tconst f = g.children[0] as Filter\n\t\t\t\texpect(f.op).toBe('notExists')\n\t\t\t\texpect(f.op).not.toBe('exists')\n\t\t\t})\n\n\t\t\ttest('contains/notContains produce correct ops', () => {\n\t\t\t\tconst g = FilterGroup.create()\n\t\t\t\t\t.contains('tags', ['a'])\n\t\t\t\t\t.notContains('tags', ['b'])\n\t\t\t\texpect((g.children[0] as Filter).op).toBe('contains')\n\t\t\t\texpect((g.children[1] as Filter).op).toBe('notContains')\n\t\t\t})\n\n\t\t\ttest('all 13 filter ops produce matching Filter.op values (name-parity)', () => {\n\t\t\t\tconst g = FilterGroup.create()\n\t\t\t\t\t.eq('f', 1)\n\t\t\t\t\t.ne('f', 1)\n\t\t\t\t\t.gt('f', 1)\n\t\t\t\t\t.gte('f', 1)\n\t\t\t\t\t.lt('f', 1)\n\t\t\t\t\t.lte('f', 1)\n\t\t\t\t\t.in('f', [1])\n\t\t\t\t\t.notIn('f', [1])\n\t\t\t\t\t.like('f', 'x')\n\t\t\t\t\t.exists('f')\n\t\t\t\t\t.notExists('f')\n\t\t\t\t\t.contains('f', [1])\n\t\t\t\t\t.notContains('f', [1])\n\t\t\t\tconst ops = g.children.map((c) => (c as Filter).op)\n\t\t\t\texpect(ops).toEqual([\n\t\t\t\t\t'eq', 'ne', 'gt', 'gte', 'lt', 'lte',\n\t\t\t\t\t'in', 'notIn', 'like', 'exists', 'notExists',\n\t\t\t\t\t'contains', 'notContains',\n\t\t\t\t])\n\t\t\t})\n\t\t})\n\n\t\tdescribe('raw-string field overload', () => {\n\t\t\ttest('accepts raw string field name', () => {\n\t\t\t\tconst g = FilterGroup.create().eq('age', 18)\n\t\t\t\tconst f = g.children[0] as Filter\n\t\t\t\texpect(f.field).toBe('age')\n\t\t\t})\n\n\t\t\ttest('accepts typed Field ref and extracts field name', () => {\n\t\t\t\tconst g = FilterGroup.create().eq(UserSchema.fields.age, 18)\n\t\t\t\tconst f = g.children[0] as Filter\n\t\t\t\texpect(f.field).toBe('age')\n\t\t\t})\n\t\t})\n\n\t\tdescribe('structural combinators', () => {\n\t\t\ttest('and() creates a nested and-group', () => {\n\t\t\t\tconst g = FilterGroup.create().and([\n\t\t\t\t\t(q) => q.eq('age', 10),\n\t\t\t\t\t(q) => q.eq('name', 'Alice'),\n\t\t\t\t])\n\t\t\t\texpect(g.children).toHaveLength(1)\n\t\t\t\tconst nested = g.children[0] as FilterGroup\n\t\t\t\texpect(nested.op).toBe('and')\n\t\t\t\texpect(nested.children).toHaveLength(2)\n\t\t\t})\n\n\t\t\ttest('or() creates a nested or-group', () => {\n\t\t\t\tconst g = FilterGroup.create().or([\n\t\t\t\t\t(q) => q.eq('name', 'Alice'),\n\t\t\t\t\t(q) => q.eq('name', 'Bob'),\n\t\t\t\t])\n\t\t\t\tconst nested = g.children[0] as FilterGroup\n\t\t\t\texpect(nested.op).toBe('or')\n\t\t\t})\n\t\t})\n\n\t\tdescribe('empty-combinator rejection', () => {\n\t\t\ttest('and([]) throws at builder time', () => {\n\t\t\t\texpect(() => FilterGroup.create().and([])).toThrow()\n\t\t\t})\n\n\t\t\ttest('or([]) throws at builder time', () => {\n\t\t\t\texpect(() => FilterGroup.create().or([])).toThrow()\n\t\t\t})\n\n\t\t\ttest('thrown error has stack pointing at offending call', () => {\n\t\t\t\ttry {\n\t\t\t\t\tFilterGroup.create().and([])\n\t\t\t\t\texpect.unreachable()\n\t\t\t\t} catch (e) {\n\t\t\t\t\texpect((e as Error).stack).toContain('filter.ts')\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\n\t\tdescribe('clone()', () => {\n\t\t\ttest('deep-clones the tree', () => {\n\t\t\t\tconst original = FilterGroup.create()\n\t\t\t\t\t.eq('name', 'Alice')\n\t\t\t\t\t.and([(q) => q.gt('age', 20)])\n\t\t\t\tconst cloned = original.clone()\n\n\t\t\t\texpect(cloned.children).toHaveLength(2)\n\t\t\t\texpect(cloned).not.toBe(original)\n\t\t\t\texpect(cloned.children[0]).not.toBe(original.children[0])\n\t\t\t})\n\n\t\t\ttest('structuredClone of values — mutations on clone do not leak', () => {\n\t\t\t\tconst arr = [1, 2, 3]\n\t\t\t\tconst original = FilterGroup.create().in('ids', arr)\n\t\t\t\tconst cloned = original.clone()\n\n\t\t\t\tconst clonedValue = (cloned.children[0] as Filter).value as number[]\n\t\t\t\tclonedValue.push(4)\n\n\t\t\t\texpect((original.children[0] as Filter).value).toEqual([1, 2, 3])\n\t\t\t\texpect(clonedValue).toEqual([1, 2, 3, 4])\n\t\t\t})\n\t\t})\n\t})\n\n\tdescribe('clone-on-step: fan-out independence', () => {\n\t\ttest('.eq() returns a new FilterGroup, not the same instance', () => {\n\t\t\tconst base = FilterGroup.create()\n\t\t\tconst a = base.eq('name', 'Alice')\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 = FilterGroup.create().eq('name', 'Alice')\n\t\t\tconst branchA = base.gt('age', 20)\n\t\t\tconst branchB = base.lt('age', 40)\n\n\t\t\texpect(branchA.children).toHaveLength(2)\n\t\t\texpect(branchB.children).toHaveLength(2)\n\t\t\texpect(base.children).toHaveLength(1)\n\t\t\texpect((branchA.children[1] as Filter).op).toBe('gt')\n\t\t\texpect((branchB.children[1] as Filter).op).toBe('lt')\n\t\t})\n\n\t\ttest('.and() returns a new FilterGroup', () => {\n\t\t\tconst base = FilterGroup.create().eq('name', 'Alice')\n\t\t\tconst withAnd = base.and([(q) => q.gt('age', 20)])\n\t\t\texpect(withAnd).not.toBe(base)\n\t\t\texpect(base.children).toHaveLength(1)\n\t\t\texpect(withAnd.children).toHaveLength(2)\n\t\t})\n\n\t\ttest('.or() returns a new FilterGroup', () => {\n\t\t\tconst base = FilterGroup.create().eq('name', 'Alice')\n\t\t\tconst withOr = base.or([(q) => q.gt('age', 20)])\n\t\t\texpect(withOr).not.toBe(base)\n\t\t\texpect(base.children).toHaveLength(1)\n\t\t\texpect(withOr.children).toHaveLength(2)\n\t\t})\n\t})\n\n\tdescribe('assertNormalisedFilter', () => {\n\t\ttest('passes for valid filter referencing known fields', () => {\n\t\t\tconst g = FilterGroup.create().eq(UserSchema.fields.age, 25)\n\t\t\texpect(() => assertNormalisedFilter(UserSchema, g)).not.toThrow()\n\t\t})\n\n\t\ttest('passes for raw string field that exists on schema', () => {\n\t\t\tconst g = FilterGroup.create().eq('email', 'test@test.com')\n\t\t\texpect(() => assertNormalisedFilter(UserSchema, g)).not.toThrow()\n\t\t})\n\n\t\ttest('rejects unknown field name with OrmValidationError', () => {\n\t\t\tconst g = FilterGroup.create().eq('unknownField', 42)\n\t\t\texpect(() => assertNormalisedFilter(UserSchema, g)).toThrow(OrmValidationError)\n\t\t})\n\n\t\ttest('rejected error has kind validation', () => {\n\t\t\tconst g = FilterGroup.create().eq('badField', 42)\n\t\t\ttry {\n\t\t\t\tassertNormalisedFilter(UserSchema, g)\n\t\t\t\texpect.unreachable()\n\t\t\t} catch (e) {\n\t\t\t\texpect(e).toBeInstanceOf(OrmValidationError)\n\t\t\t\texpect((e as OrmValidationError).kind).toBe('validation')\n\t\t\t\texpect((e as OrmValidationError).failures).toHaveLength(1)\n\t\t\t\texpect((e as OrmValidationError).failures[0].field).toBe('badField')\n\t\t\t}\n\t\t})\n\n\t\ttest('preserves logical field names verbatim for Field refs', () => {\n\t\t\tconst g = FilterGroup.create().eq(UserSchema.fields.email, 'a@b.com')\n\t\t\tconst f = g.children[0] as Filter\n\t\t\texpect(f.field).toBe('email')\n\t\t\tassertNormalisedFilter(UserSchema, g)\n\t\t})\n\n\t\ttest('preserves logical field names verbatim for raw-string overloads', () => {\n\t\t\tconst g = FilterGroup.create().eq('age', 18)\n\t\t\tconst f = g.children[0] as Filter\n\t\t\texpect(f.field).toBe('age')\n\t\t\tassertNormalisedFilter(UserSchema, g)\n\t\t})\n\n\t\ttest('walks nested groups to detect unknown fields', () => {\n\t\t\tconst g = FilterGroup.create().and([\n\t\t\t\t(q) => q.eq('name', 'Alice'),\n\t\t\t\t(q) => q.or([(inner) => inner.eq('nonexistent', 'val')]),\n\t\t\t])\n\t\t\texpect(() => assertNormalisedFilter(UserSchema, g)).toThrow(OrmValidationError)\n\t\t})\n\t})\n\n\tdescribe('assertNormalisedAggregate', () => {\n\t\tconst adapter = { aggregateOps: ['count'] as readonly string[] }\n\n\t\ttest('passes for valid count aggregate', () => {\n\t\t\tconst spec = { aggregates: [{ fn: 'count' as const, alias: 'total' }], groupBy: [] }\n\t\t\texpect(() => assertNormalisedAggregate(UserSchema, adapter, spec)).not.toThrow()\n\t\t})\n\n\t\ttest('rejects empty aggregator list', () => {\n\t\t\tconst spec = { aggregates: [], groupBy: [] }\n\t\t\texpect(() => assertNormalisedAggregate(UserSchema, adapter, spec)).toThrow(OrmValidationError)\n\t\t\ttry {\n\t\t\t\tassertNormalisedAggregate(UserSchema, adapter, spec)\n\t\t\t\texpect.unreachable()\n\t\t\t} catch (e) {\n\t\t\t\texpect((e as OrmValidationError).kind).toBe('aggregate')\n\t\t\t}\n\t\t})\n\n\t\ttest('rejects undeclared aggregate op', () => {\n\t\t\tconst spec = { aggregates: [{ fn: 'sum' as const, alias: 'total', field: 'age' }], groupBy: [] }\n\t\t\texpect(() => assertNormalisedAggregate(UserSchema, adapter, spec)).toThrow(OrmValidationError)\n\t\t\ttry {\n\t\t\t\tassertNormalisedAggregate(UserSchema, adapter, spec)\n\t\t\t\texpect.unreachable()\n\t\t\t} catch (e) {\n\t\t\t\tconst err = e as OrmValidationError\n\t\t\t\texpect(err.kind).toBe('aggregate')\n\t\t\t\texpect(err.failures[0].alias).toBe('total')\n\t\t\t\texpect(err.failures[0].cause).toContain('Undeclared')\n\t\t\t}\n\t\t})\n\n\t\ttest('rejects duplicate aliases', () => {\n\t\t\tconst spec = {\n\t\t\t\taggregates: [\n\t\t\t\t\t{ fn: 'count' as const, alias: 'total' },\n\t\t\t\t\t{ fn: 'count' as const, alias: 'total' },\n\t\t\t\t],\n\t\t\t\tgroupBy: [],\n\t\t\t}\n\t\t\texpect(() => assertNormalisedAggregate(UserSchema, adapter, spec)).toThrow(OrmValidationError)\n\t\t\ttry {\n\t\t\t\tassertNormalisedAggregate(UserSchema, adapter, spec)\n\t\t\t\texpect.unreachable()\n\t\t\t} catch (e) {\n\t\t\t\tconst err = e as OrmValidationError\n\t\t\t\texpect(err.failures.some((f) => f.alias === 'total')).toBe(true)\n\t\t\t}\n\t\t})\n\n\t\ttest('validates where filter against schema', () => {\n\t\t\tconst spec = {\n\t\t\t\taggregates: [{ fn: 'count' as const, alias: 'total' }],\n\t\t\t\tgroupBy: [],\n\t\t\t\twhere: FilterGroup.create().eq('unknownField', 42),\n\t\t\t}\n\t\t\texpect(() => assertNormalisedAggregate(UserSchema, adapter, spec)).toThrow(OrmValidationError)\n\t\t})\n\n\t\ttest('collects multiple failures', () => {\n\t\t\tconst spec = {\n\t\t\t\taggregates: [\n\t\t\t\t\t{ fn: 'sum' as const, alias: 'x', field: 'age' },\n\t\t\t\t\t{ fn: 'avg' as const, alias: 'x', field: 'age' },\n\t\t\t\t],\n\t\t\t\tgroupBy: [],\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tassertNormalisedAggregate(UserSchema, adapter, spec)\n\t\t\t\texpect.unreachable()\n\t\t\t} catch (e) {\n\t\t\t\tconst err = e as OrmValidationError\n\t\t\t\texpect(err.failures.length).toBeGreaterThanOrEqual(2)\n\t\t\t}\n\t\t})\n\n\t\ttest('rejects unknown groupBy field', () => {\n\t\t\tconst allOps = { aggregateOps: ['count', 'sum'] as readonly string[] }\n\t\t\tconst spec = {\n\t\t\t\taggregates: [{ fn: 'count' as const, alias: 'total' }],\n\t\t\t\tgroupBy: ['nonexistent'],\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tassertNormalisedAggregate(UserSchema, allOps, spec)\n\t\t\t\texpect.unreachable()\n\t\t\t} catch (e) {\n\t\t\t\tconst err = e as OrmValidationError\n\t\t\t\texpect(err.kind).toBe('aggregate')\n\t\t\t\texpect(err.failures[0].cause).toContain('groupBy')\n\t\t\t\texpect(err.failures[0].field).toBe('nonexistent')\n\t\t\t}\n\t\t})\n\n\t\ttest('rejects alias colliding with groupBy field name', () => {\n\t\t\tconst allOps = { aggregateOps: ['count'] as readonly string[] }\n\t\t\tconst spec = {\n\t\t\t\taggregates: [{ fn: 'count' as const, alias: 'name' }],\n\t\t\t\tgroupBy: ['name'],\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tassertNormalisedAggregate(UserSchema, allOps, spec)\n\t\t\t\texpect.unreachable()\n\t\t\t} catch (e) {\n\t\t\t\tconst err = e as OrmValidationError\n\t\t\t\texpect(err.kind).toBe('aggregate')\n\t\t\t\texpect(err.failures.some((f) => String(f.cause).includes('collides'))).toBe(true)\n\t\t\t}\n\t\t})\n\n\t\ttest('rejects unknown having field', () => {\n\t\t\tconst allOps = { aggregateOps: ['count'] as readonly string[] }\n\t\t\tconst spec = {\n\t\t\t\taggregates: [{ fn: 'count' as const, alias: 'total' }],\n\t\t\t\tgroupBy: ['name'],\n\t\t\t\thaving: FilterGroup.create().gt('nonexistent', 0),\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tassertNormalisedAggregate(UserSchema, allOps, spec)\n\t\t\t\texpect.unreachable()\n\t\t\t} catch (e) {\n\t\t\t\tconst err = e as OrmValidationError\n\t\t\t\texpect(err.kind).toBe('aggregate')\n\t\t\t\texpect(err.failures[0].cause).toContain('having')\n\t\t\t}\n\t\t})\n\n\t\ttest('having accepts alias and groupBy field names', () => {\n\t\t\tconst allOps = { aggregateOps: ['count'] as readonly string[] }\n\t\t\tconst spec = {\n\t\t\t\taggregates: [{ fn: 'count' as const, alias: 'total' }],\n\t\t\t\tgroupBy: ['name'],\n\t\t\t\thaving: FilterGroup.create().gt('total', 0).eq('name', 'Alice'),\n\t\t\t}\n\t\t\texpect(() => assertNormalisedAggregate(UserSchema, allOps, spec)).not.toThrow()\n\t\t})\n\n\t\ttest('passes valid groupBy with known schema fields', () => {\n\t\t\tconst allOps = { aggregateOps: ['count'] as readonly string[] }\n\t\t\tconst spec = {\n\t\t\t\taggregates: [{ fn: 'count' as const, alias: 'total' }],\n\t\t\t\tgroupBy: ['name', 'age'],\n\t\t\t}\n\t\t\texpect(() => assertNormalisedAggregate(UserSchema, allOps, spec)).not.toThrow()\n\t\t})\n\t})\n}\n","import type { Pipe } from 'valleyed'\n\nimport { EquippedError } from '../errors'\nimport { Instance, type ClassRef } from '../instance'\nimport type { AggregateOpName, FieldTypeName, FilterOpName, UpdateOpName } from './adapter'\nimport type { OrmUse } from './adapters/base'\nimport { FilterGroup } from './filter'\nimport type { DiscoveredSchema } from './migrations/introspection-types'\nimport type { AddFieldChange, AddForeignKeyChange, AddIndexChange, CreateTableChange, DropFieldChange, DropForeignKeyChange, DropIndexChange, DropTableChange, ModifyFieldChange, RenameFieldChange, RenameTableChange } from './migrations/types'\nimport type { IterationQueryOptions, QueryOptions } from './query-options'\nimport type { AnySchema } from './schema'\nimport type { AnyUpdateOp } from './updates'\n\nexport type AggregateSpec = {\n\twhere?: FilterGroup\n\taggregates: ReadonlyArray<{\n\t\tfn: AggregateOpName\n\t\tfield?: string\n\t\talias: string\n\t}>\n\tgroupBy: readonly string[]\n\thaving?: FilterGroup\n}\n\nexport abstract class OrmAdapter {\n\treadonly queryableOps: readonly FilterOpName[] = []\n\treadonly updateOps: readonly UpdateOpName[] = []\n\treadonly aggregateOps: readonly AggregateOpName[] = []\n\treadonly supportedFieldTypes: readonly FieldTypeName[] = []\n\n\tabstract readonly schemaConfigPipe: Pipe<any, any>\n\n\tconnect?(): Promise<void>\n\tdisconnect?(): Promise<void>\n\tfindByPk?(schema: AnySchema, config: unknown, pk: unknown): Promise<Record<string, unknown> | null>\n\tcreateMany?(schema: AnySchema, config: unknown, data: Record<string, unknown>[]): Promise<Record<string, unknown>[]>\n\tupdateByPk?(schema: AnySchema, config: unknown, pk: unknown, ops: AnyUpdateOp[]): Promise<Record<string, unknown> | null>\n\tdeleteByPk?(schema: AnySchema, config: unknown, pk: unknown): Promise<Record<string, unknown> | null>\n\traw?(schema: AnySchema, config: unknown, ...args: any[]): Promise<any>\n\tfindMany?(schema: AnySchema, config: unknown, filter: FilterGroup, options?: QueryOptions): Promise<Record<string, unknown>[]>\n\tcount?(schema: AnySchema, config: unknown, filter: FilterGroup): Promise<number>\n\titerateMany?(schema: AnySchema, config: unknown, filter: FilterGroup, options?: IterationQueryOptions): AsyncGenerator<Record<string, unknown>, void, void>\n\tupdateMany?(schema: AnySchema, config: unknown, filter: FilterGroup, data: Record<string, unknown>): Promise<Record<string, unknown>[]>\n\tdeleteMany?(schema: AnySchema, config: unknown, filter: FilterGroup): Promise<Record<string, unknown>[]>\n\tupsertOne?(\n\t\tschema: AnySchema,\n\t\tconfig: unknown,\n\t\tfilter: FilterGroup,\n\t\tcreate: Record<string, unknown>,\n\t\tops: AnyUpdateOp[],\n\t): Promise<Record<string, unknown>>\n\taggregate?(schema: AnySchema, config: unknown, spec: AggregateSpec): Promise<Array<Record<string, unknown>>>\n\tsession?<T>(fn: () => Promise<T>): Promise<T>\n\n\tloadMigrations?(): Promise<{ id: string; appliedAt: number }[]>\n\trecordMigration?(id: string, appliedAt: number): Promise<void>\n\tacquireMigrationLock?<T>(fn: () => Promise<T>): Promise<T>\n\tapplyCreateTable?(change: CreateTableChange<any>): Promise<void>\n\tapplyDropTable?(change: DropTableChange): Promise<void>\n\tapplyAddField?(change: AddFieldChange<any>): Promise<void>\n\tapplyDropField?(change: DropFieldChange): Promise<void>\n\tapplyModifyField?(change: ModifyFieldChange<any>): Promise<void>\n\tapplyRenameTable?(change: RenameTableChange): Promise<void>\n\tapplyRenameField?(change: RenameFieldChange): Promise<void>\n\tapplyAddIndex?(change: AddIndexChange): Promise<void>\n\tapplyDropIndex?(change: DropIndexChange): Promise<void>\n\tapplyAddForeignKey?(change: AddForeignKeyChange): Promise<void>\n\tapplyDropForeignKey?(change: DropForeignKeyChange): Promise<void>\n\tintrospect?(): Promise<DiscoveredSchema[]>\n\n\tprotected onFatalError(err: unknown): never {\n\t\tconst wrapped = err instanceof EquippedError ? err : new EquippedError('OrmAdapter fatal error', {}, err)\n\t\tInstance.crash(wrapped)\n\t}\n\n\tconstructor() {\n\t\tconst self = this as any\n\t\tif (typeof self.connect === 'function') {\n\t\t\tInstance.on('start', () => self.connect(), { class: this.constructor as ClassRef })\n\t\t}\n\t\tif (typeof self.disconnect === 'function') {\n\t\t\tInstance.on('close', () => self.disconnect(), { class: this.constructor as ClassRef })\n\t\t}\n\t}\n\n\tuse(schema: AnySchema, config: unknown): OrmUse {\n\t\tconst self = this as any\n\t\tconst emptyIterator = async function* (): AsyncGenerator<Record<string, unknown>, void, void> {}\n\t\tconst use: OrmUse = {\n\t\t\tfindMany: (filter, opts) => self.findMany?.(schema, config, filter, opts) ?? Promise.resolve([]),\n\t\t\titerateMany: (filter, opts) => self.iterateMany?.(schema, config, filter, opts) ?? emptyIterator(),\n\t\t\tfindOne: async (filter) => {\n\t\t\t\tconst rows = await use.findMany(filter, { limit: 1 })\n\t\t\t\treturn rows[0] ?? null\n\t\t\t},\n\t\t\tcount: (filter) => self.count?.(schema, config, filter) ?? Promise.reject(new Error('count not implemented')),\n\t\t\tcreateOne: async (d) => {\n\t\t\t\tconst rows = await use.createMany([d])\n\t\t\t\treturn rows[0]\n\t\t\t},\n\t\t\tcreateMany: (d) => self.createMany?.(schema, config, d) ?? Promise.resolve([]),\n\t\t\tupdateMany: (filter, d) => self.updateMany?.(schema, config, filter, d) ?? Promise.resolve([]),\n\t\t\tupdateOne: async (filter, d) => {\n\t\t\t\tconst match = await use.findOne(filter)\n\t\t\t\tif (!match) return null\n\t\t\t\tconst pk = schema.pkField.name\n\t\t\t\tconst pkFilter = FilterGroup.create().eq(pk, match[pk])\n\t\t\t\tconst rows = await use.updateMany(pkFilter, d)\n\t\t\t\treturn rows[0] ?? null\n\t\t\t},\n\t\t\tupsertOne: (filter, create, ops) =>\n\t\t\t\tself.upsertOne?.(schema, config, filter, create, ops) ?? Promise.reject(new Error('upsertOne not implemented')),\n\t\t\tdeleteOne: async (filter) => {\n\t\t\t\tconst row = await use.findOne(filter)\n\t\t\t\tif (!row) return null\n\t\t\t\tconst pk = schema.pkField.name\n\t\t\t\tif (self.deleteByPk) {\n\t\t\t\t\tawait self.deleteByPk(schema, config, row[pk])\n\t\t\t\t} else if (self.deleteMany) {\n\t\t\t\t\tconst pkFilter = FilterGroup.create().eq(pk, row[pk])\n\t\t\t\t\tawait self.deleteMany(schema, config, pkFilter)\n\t\t\t\t}\n\t\t\t\treturn row\n\t\t\t},\n\t\t\tdeleteMany: (filter) => self.deleteMany?.(schema, config, filter) ?? Promise.resolve([]),\n\t\t\traw: (...args: any[]) => self.raw?.(schema, config, ...args) ?? Promise.reject(new Error('raw not implemented')),\n\t\t\taggregate: (spec) => self.aggregate?.(schema, config, spec) ?? Promise.reject(new Error('aggregate not implemented')),\n\t\t\taggregateOps: self.aggregateOps ?? [],\n\t\t}\n\t\treturn use\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect, vi } = import.meta.vitest\n\tconst { v } = await import('valleyed')\n\n\tdescribe('OrmAdapter', () => {\n\t\ttest('subclass with connect/disconnect auto-registers Instance hooks', async () => {\n\t\t\tconst { Instance: Inst } = await import('../instance')\n\t\t\tconst onSpy = vi.spyOn(Inst, 'on').mockImplementation(() => {})\n\n\t\t\tclass TestAdapter 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 connect() {}\n\t\t\t\tasync disconnect() {}\n\t\t\t}\n\n\t\t\tnew (TestAdapter as any)()\n\n\t\t\texpect(onSpy).toHaveBeenCalledWith('start', expect.any(Function), expect.objectContaining({ class: TestAdapter }))\n\t\t\texpect(onSpy).toHaveBeenCalledWith('close', expect.any(Function), expect.objectContaining({ class: TestAdapter }))\n\n\t\t\tonSpy.mockRestore()\n\t\t})\n\n\t\ttest('subclass without connect/disconnect does not register hooks', async () => {\n\t\t\tconst { Instance: Inst } = await import('../instance')\n\t\t\tconst onSpy = vi.spyOn(Inst, 'on').mockImplementation(() => {})\n\n\t\t\tclass MinimalAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({ table: v.string() })\n\t\t\t}\n\n\t\t\tnew (MinimalAdapter as any)()\n\t\t\texpect(onSpy).not.toHaveBeenCalled()\n\n\t\t\tonSpy.mockRestore()\n\t\t})\n\n\t\ttest('capability declarations default to empty arrays', async () => {\n\t\t\tconst { Instance: Inst } = await import('../instance')\n\t\t\tvi.spyOn(Inst, 'on').mockImplementation(() => {})\n\n\t\t\tclass DefaultAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({ table: v.string() })\n\t\t\t}\n\t\t\tconst adapter = new (DefaultAdapter as any)() as DefaultAdapter\n\t\t\texpect(adapter.queryableOps).toEqual([])\n\t\t\texpect(adapter.updateOps).toEqual([])\n\t\t\texpect(adapter.aggregateOps).toEqual([])\n\t\t\texpect(adapter.supportedFieldTypes).toEqual([])\n\n\t\t\tvi.restoreAllMocks()\n\t\t})\n\n\t\ttest('onFatalError wraps non-EquippedError and calls Instance.crash', async () => {\n\t\t\tconst { Instance: Inst } = await import('../instance')\n\t\t\tvi.spyOn(Inst, 'on').mockImplementation(() => {})\n\t\t\tconst crashSpy = vi.spyOn(Inst, 'crash').mockImplementation((() => {\n\t\t\t\tthrow new Error('crash')\n\t\t\t}) as any)\n\n\t\t\tclass FatalAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({ table: v.string() })\n\t\t\t\ttriggerFatal() {\n\t\t\t\t\tthis.onFatalError(new Error('something broke'))\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst adapter = new (FatalAdapter as any)() as FatalAdapter\n\t\t\texpect(() => adapter.triggerFatal()).toThrow('crash')\n\t\t\texpect(crashSpy).toHaveBeenCalled()\n\n\t\t\tvi.restoreAllMocks()\n\t\t})\n\n\t\ttest('onFatalError passes through EquippedError directly', async () => {\n\t\t\tconst { Instance: Inst } = await import('../instance')\n\t\t\tvi.spyOn(Inst, 'on').mockImplementation(() => {})\n\t\t\tlet crashedWith: unknown\n\t\t\tvi.spyOn(Inst, 'crash').mockImplementation(((err: any) => {\n\t\t\t\tcrashedWith = err\n\t\t\t\tthrow err\n\t\t\t}) as any)\n\n\t\t\tclass FatalAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({ table: v.string() })\n\t\t\t\ttriggerFatal() {\n\t\t\t\t\tthis.onFatalError(new EquippedError('equipped error', { detail: 'test' }))\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst adapter = new (FatalAdapter as any)() as FatalAdapter\n\t\t\texpect(() => adapter.triggerFatal()).toThrow()\n\t\t\texpect(crashedWith).toBeInstanceOf(EquippedError)\n\t\t\texpect((crashedWith as EquippedError).message).toBe('equipped error')\n\n\t\t\tvi.restoreAllMocks()\n\t\t})\n\n\t\ttest('use() creates OrmUse bridge that delegates to flat methods', async () => {\n\t\t\tconst { Instance: Inst } = await import('../instance')\n\t\t\tconst { Schema } = await import('./schema')\n\t\t\tvi.spyOn(Inst, 'on').mockImplementation(() => {})\n\n\t\t\tclass BridgeAdapter 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\n\t\t\t\tasync findMany(_s: AnySchema, _c: unknown, _f: any, _o?: any) {\n\t\t\t\t\treturn [{ id: 'found' }]\n\t\t\t\t}\n\t\t\t\tasync count(_s: AnySchema, _c: unknown, _f: any) {\n\t\t\t\t\treturn 1\n\t\t\t\t}\n\t\t\t\tasync *iterateMany(_s: AnySchema, _c: unknown, _f: any, _o?: any) {\n\t\t\t\t\tyield { id: 'iterated' }\n\t\t\t\t}\n\t\t\t\tasync createMany(_s: AnySchema, _c: unknown, data: Record<string, unknown>[]) {\n\t\t\t\t\treturn data\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst TestSchema = Schema.from('bridge_test')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.build()\n\t\t\tconst adapter = new (BridgeAdapter as any)() as BridgeAdapter\n\t\t\tconst ormUse = adapter.use(TestSchema, { table: 'bridge_test' })\n\n\t\t\tconst rows = await ormUse.findMany(FilterGroup.create())\n\t\t\texpect(rows).toEqual([{ id: 'found' }])\n\t\t\tawait expect(ormUse.count(FilterGroup.create())).resolves.toBe(1)\n\n\t\t\tconst iterated: Record<string, unknown>[] = []\n\t\t\tfor await (const row of ormUse.iterateMany(FilterGroup.create())) iterated.push(row)\n\t\t\texpect(iterated).toEqual([{ id: 'iterated' }])\n\n\t\t\tconst created = await ormUse.createOne({ id: 'new' })\n\t\t\texpect(created).toEqual({ id: 'new' })\n\n\t\t\tvi.restoreAllMocks()\n\t\t})\n\n\t\ttest('use() bridge delegates updateMany to flat method', async () => {\n\t\t\tconst { Instance: Inst } = await import('../instance')\n\t\t\tconst { Schema } = await import('./schema')\n\t\t\tvi.spyOn(Inst, 'on').mockImplementation(() => {})\n\n\t\t\tclass UpdateAdapter 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 updateOps = ['set'] as const\n\n\t\t\t\tasync findMany(_s: AnySchema, _c: unknown, _f: any) {\n\t\t\t\t\treturn [{ id: 'u1', name: 'old' }]\n\t\t\t\t}\n\t\t\t\tasync updateMany(_s: AnySchema, _c: unknown, _f: any, data: Record<string, unknown>) {\n\t\t\t\t\treturn [{ id: 'u1', ...data }]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst TestSchema = Schema.from('upd_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 adapter = new (UpdateAdapter as any)() as UpdateAdapter\n\t\t\tconst ormUse = adapter.use(TestSchema, { table: 'upd_test' })\n\n\t\t\tconst updated = await ormUse.updateOne(FilterGroup.create().eq('id', 'u1'), { name: 'new' })\n\t\t\texpect(updated).not.toBeNull()\n\t\t\texpect(updated!.name).toBe('new')\n\n\t\t\tvi.restoreAllMocks()\n\t\t})\n\n\t\ttest('use().iterateMany does not fall back to findMany when adapter omits iterateMany', async () => {\n\t\t\tconst { Instance: Inst } = await import('../instance')\n\t\t\tconst { Schema } = await import('./schema')\n\t\t\tvi.spyOn(Inst, 'on').mockImplementation(() => {})\n\n\t\t\tlet findManyCalls = 0\n\t\t\tclass FindOnlyAdapter 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 findMany() {\n\t\t\t\t\tfindManyCalls += 1\n\t\t\t\t\treturn [{ id: 'found' }]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst TestSchema = Schema.from('iter_no_fallback')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.build()\n\t\t\tconst adapter = new (FindOnlyAdapter as any)() as FindOnlyAdapter\n\t\t\tconst rows: Record<string, unknown>[] = []\n\t\t\tfor await (const row of adapter.use(TestSchema, { table: 'iter_no_fallback' }).iterateMany(FilterGroup.create())) rows.push(row)\n\n\t\t\texpect(rows).toEqual([])\n\t\t\texpect(findManyCalls).toBe(0)\n\n\t\t\tvi.restoreAllMocks()\n\t\t})\n\n\t\ttest('use() bridge delegates deleteByPk via deleteOne', async () => {\n\t\t\tconst { Instance: Inst } = await import('../instance')\n\t\t\tconst { Schema } = await import('./schema')\n\t\t\tvi.spyOn(Inst, 'on').mockImplementation(() => {})\n\n\t\t\tlet deletedPk: unknown = null\n\t\t\tclass DeleteAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({ table: v.string() })\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\n\t\t\t\tasync findMany(_s: AnySchema, _c: unknown, _f: any) {\n\t\t\t\t\treturn [{ id: 'd1' }]\n\t\t\t\t}\n\t\t\t\tasync deleteByPk(_s: AnySchema, _c: unknown, pk: unknown) {\n\t\t\t\t\tdeletedPk = pk\n\t\t\t\t\treturn { id: String(pk) }\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst TestSchema = Schema.from('del_test')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.build()\n\t\t\tconst adapter = new (DeleteAdapter as any)() as DeleteAdapter\n\t\t\tconst ormUse = adapter.use(TestSchema, { table: 'del_test' })\n\n\t\t\tconst deleted = await ormUse.deleteOne(FilterGroup.create().eq('id', 'd1'))\n\t\t\texpect(deleted).toEqual({ id: 'd1' })\n\t\t\texpect(deletedPk).toBe('d1')\n\n\t\t\tvi.restoreAllMocks()\n\t\t})\n\n\t\ttest('use() bridge delegates session to flat method', async () => {\n\t\t\tconst { Instance: Inst } = await import('../instance')\n\t\t\tvi.spyOn(Inst, 'on').mockImplementation(() => {})\n\n\t\t\tlet sessionRan = false\n\t\t\tclass SessionAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({ table: v.string() })\n\t\t\t\tasync session<T>(fn: () => Promise<T>): Promise<T> {\n\t\t\t\t\tsessionRan = true\n\t\t\t\t\treturn fn()\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst adapter = new (SessionAdapter as any)() as SessionAdapter\n\t\t\tconst result = await adapter.session!(async () => 42)\n\t\t\texpect(result).toBe(42)\n\t\t\texpect(sessionRan).toBe(true)\n\n\t\t\tvi.restoreAllMocks()\n\t\t})\n\n\t\ttest('type-level: subclass method override enforces canonical signature', async () => {\n\t\t\tconst { Instance: Inst } = await import('../instance')\n\t\t\tvi.spyOn(Inst, 'on').mockImplementation(() => {})\n\n\t\t\tclass CorrectAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({ table: v.string() })\n\t\t\t\tasync findByPk(_s: AnySchema, _c: unknown, _pk: unknown) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t\tasync createMany(_s: AnySchema, _c: unknown, _d: Record<string, unknown>[]) {\n\t\t\t\t\treturn []\n\t\t\t\t}\n\t\t\t\tasync raw(_s: AnySchema, _c: unknown, ..._args: any[]) {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst adapter = new (CorrectAdapter as any)() as CorrectAdapter\n\t\t\texpect(adapter.findByPk).toBeTypeOf('function')\n\t\t\texpect(adapter.createMany).toBeTypeOf('function')\n\t\t\texpect(adapter.raw).toBeTypeOf('function')\n\n\t\t\tvi.restoreAllMocks()\n\t\t})\n\t})\n}\n","import { AsyncLocalStorage } from 'node:async_hooks'\n\nimport { v, differ } from 'valleyed'\n\nimport { configurable } from '../../../utilities/configurable'\nimport type { FieldTypeName } from '../../adapter'\nimport { Filter, FilterGroup, type FilterChild } from '../../filter'\nimport type { DiscoveredSchema, ForeignKeyAction } from '../../migrations/introspection-types'\nimport type { AddFieldChange, AddForeignKeyChange, AddIndexChange, AnyFieldSpec, CreateTableChange, DropFieldChange, DropForeignKeyChange, DropIndexChange, DropTableChange, ModifyFieldChange, RenameFieldChange, RenameTableChange } from '../../migrations/types'\nimport { OrmAdapter, type AggregateSpec } from '../../orm-adapter'\nimport type { IterationQueryOptions, QueryOptions } from '../../query-options'\nimport type { AnySchema } from '../../schema'\nimport { IncOp, MaxOp, MinOp, MulOp, PatchOp, PullOp, PushOp, SetOp, UnsetOp, type AnyUpdateOp } from '../../updates'\n\nconst inMemoryConnectionPipe = () => v.object({})\n\nexport type InMemoryRepoConfig = {\n\ttable: string\n\tprefix?: string\n}\n\ntype MigrationRecord = { id: string; appliedAt: number }\ntype TableMeta = { pk: { name: string; type: string }; fields: Map<string, AnyFieldSpec> }\ntype IndexMeta = { table: string; on: readonly string[]; unique: boolean }\ntype ForeignKeyMeta = { table: string; on: string; references: { table: string; column: string }; onDelete?: ForeignKeyAction; onUpdate?: ForeignKeyAction }\n\ntype Snapshot = {\n\tstores: Map<string, Map<string, Record<string, unknown>>>\n\tmigrations: Map<string, MigrationRecord>\n\tindexes: Map<string, IndexMeta>\n\ttables: Map<string, TableMeta>\n\tforeignKeys: Map<string, ForeignKeyMeta>\n}\n\nfunction clone<T>(value: T): T {\n\treturn structuredClone(value)\n}\n\nconst replaceMap = <K, V>(target: Map<K, V>, source: Map<K, V>) => {\n\ttarget.clear()\n\tfor (const [k, val] of source.entries()) target.set(k, val)\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction containsSubset(source: unknown, needle: unknown): boolean {\n\tif (Array.isArray(source)) {\n\t\tconst wanted = Array.isArray(needle) ? needle : [needle]\n\t\treturn wanted.every((item) => source.some((existing) => differ.equal(existing, item)))\n\t}\n\n\tif (isPlainObject(source) && isPlainObject(needle)) {\n\t\treturn Object.entries(needle).every(([k, v]) => containsSubset(source[k], v))\n\t}\n\n\treturn differ.equal(source, needle)\n}\n\nfunction compareValues(a: unknown, b: unknown): number {\n\tif (a === b) return 0\n\tif (a == null) return -1\n\tif (b == null) return 1\n\tif (typeof a === 'number' && typeof b === 'number') return a < b ? -1 : 1\n\tif (typeof a === 'string' && typeof b === 'string') return a < b ? -1 : 1\n\tif (typeof a === 'boolean' && typeof b === 'boolean') return a === false ? -1 : 1\n\tconst sa = JSON.stringify(a)\n\tconst sb = JSON.stringify(b)\n\tif (sa === sb) return 0\n\treturn sa < sb ? -1 : 1\n}\n\nfunction resolveConfigName(_: AnySchema, config: InMemoryRepoConfig): string {\n\treturn `${config.prefix ?? ''}${config.table}`\n}\n\nfunction evaluateFilter(doc: Record<string, unknown>, filter: Filter): boolean {\n\tconst fieldValue = doc[filter.field]\n\tconst value = filter.value\n\n\tswitch (filter.op) {\n\t\tcase 'eq':\n\t\t\treturn differ.equal(fieldValue, value)\n\t\tcase 'ne':\n\t\t\treturn !differ.equal(fieldValue, value)\n\t\tcase 'gt':\n\t\t\treturn compareValues(fieldValue, value) > 0\n\t\tcase 'gte':\n\t\t\treturn compareValues(fieldValue, value) >= 0\n\t\tcase 'lt':\n\t\t\treturn compareValues(fieldValue, value) < 0\n\t\tcase 'lte':\n\t\t\treturn compareValues(fieldValue, value) <= 0\n\t\tcase 'in': {\n\t\t\tif (!Array.isArray(value)) return false\n\t\t\treturn value.some((v) => differ.equal(fieldValue, v))\n\t\t}\n\t\tcase 'notIn': {\n\t\t\tif (!Array.isArray(value)) return true\n\t\t\treturn !value.some((v) => differ.equal(fieldValue, v))\n\t\t}\n\t\tcase 'like':\n\t\t\treturn String(fieldValue ?? '')\n\t\t\t\t.toLowerCase()\n\t\t\t\t.includes(String(value ?? '').toLowerCase())\n\t\tcase 'exists':\n\t\t\treturn fieldValue != null\n\t\tcase 'notExists':\n\t\t\treturn fieldValue == null\n\t\tcase 'contains':\n\t\t\treturn containsSubset(fieldValue, value)\n\t\tcase 'notContains':\n\t\t\treturn !containsSubset(fieldValue, value)\n\t\tdefault:\n\t\t\treturn false\n\t}\n}\n\nfunction evaluateChild(doc: Record<string, unknown>, child: FilterChild): boolean {\n\tif (child instanceof Filter) return evaluateFilter(doc, child)\n\tif (child instanceof FilterGroup) {\n\t\tif (child.op === 'and') return child.children.every((c) => evaluateChild(doc, c))\n\t\tif (child.op === 'or') return child.children.some((c) => evaluateChild(doc, c))\n\t}\n\treturn true\n}\n\nfunction matchesFilter(doc: Record<string, unknown>, group: FilterGroup): boolean {\n\tfor (const clause of group.children) {\n\t\tif (!evaluateChild(doc, clause)) return false\n\t}\n\treturn true\n}\n\nfunction applyOptions(rows: Record<string, unknown>[], options?: QueryOptions): Record<string, unknown>[] {\n\tlet results = rows\n\n\tif (options?.orderBy?.length) {\n\t\tresults = [...results].sort((a, b) => {\n\t\t\tfor (const ord of options.orderBy ?? []) {\n\t\t\t\tconst cmp = compareValues(a[ord.field], b[ord.field])\n\t\t\t\tif (cmp !== 0) return ord.direction === 'asc' ? cmp : -cmp\n\t\t\t}\n\t\t\treturn 0\n\t\t})\n\t}\n\n\tif (options?.offset != null) {\n\t\tresults = results.slice(Math.max(0, options.offset))\n\t}\n\n\tif (options?.limit != null) {\n\t\tresults = results.slice(0, options.limit)\n\t}\n\n\tif (options?.select?.length) {\n\t\tconst fields = new Set<string>(options.select)\n\t\tresults = results.map((row) => {\n\t\t\tconst out: Record<string, unknown> = {}\n\t\t\tfor (const key of fields) out[key] = row[key]\n\t\t\treturn out\n\t\t})\n\t}\n\n\treturn results\n}\n\nfunction applyUpdateOp(current: Record<string, unknown>, key: string, value: unknown) {\n\tif (value instanceof IncOp) {\n\t\tconst base = Number(current[key] ?? 0)\n\t\tcurrent[key] = base + value.value\n\t\treturn\n\t}\n\tif (value instanceof MulOp) {\n\t\tconst base = Number(current[key] ?? 0)\n\t\tcurrent[key] = base * value.value\n\t\treturn\n\t}\n\tif (value instanceof MinOp) {\n\t\tconst base = current[key]\n\t\tcurrent[key] = base == null || compareValues(value.value, base) < 0 ? value.value : base\n\t\treturn\n\t}\n\tif (value instanceof MaxOp) {\n\t\tconst base = current[key]\n\t\tcurrent[key] = base == null || compareValues(value.value, base) > 0 ? value.value : base\n\t\treturn\n\t}\n\tif (value instanceof UnsetOp) {\n\t\tcurrent[key] = null\n\t\treturn\n\t}\n\tif (value instanceof PushOp) {\n\t\tconst arr = Array.isArray(current[key]) ? [...(current[key] as unknown[])] : []\n\t\tarr.push(clone(value.value))\n\t\tcurrent[key] = arr\n\t\treturn\n\t}\n\tif (value instanceof PullOp) {\n\t\tconst arr = Array.isArray(current[key]) ? (current[key] as unknown[]) : []\n\t\tcurrent[key] = arr.filter((entry) => !differ.equal(entry, value.value))\n\t\treturn\n\t}\n\tif (value instanceof PatchOp) {\n\t\tconst target = isPlainObject(current[key]) ? clone(current[key] as Record<string, unknown>) : {}\n\t\tif (isPlainObject(value.value)) {\n\t\t\tfor (const [k, v] of Object.entries(value.value as Record<string, unknown>)) {\n\t\t\t\ttarget[k] = clone(v)\n\t\t\t}\n\t\t}\n\t\tcurrent[key] = target\n\t\treturn\n\t}\n\tcurrent[key] = clone(value)\n}\n\nfunction applyUpdateData(doc: Record<string, unknown>, data: Record<string, unknown>): Record<string, unknown> {\n\tconst updated = clone(doc)\n\tfor (const [key, value] of Object.entries(data)) {\n\t\tapplyUpdateOp(updated, key, value)\n\t}\n\treturn updated\n}\n\nfunction applyOps(doc: Record<string, unknown>, ops: AnyUpdateOp[]): Record<string, unknown> {\n\tconst updated = clone(doc)\n\tfor (const op of ops) {\n\t\tif (op instanceof SetOp) {\n\t\t\tfor (const [key, value] of Object.entries(op.values)) {\n\t\t\t\tupdated[key] = clone(value)\n\t\t\t}\n\t\t} else {\n\t\t\tapplyUpdateOp(updated, op.field, op)\n\t\t}\n\t}\n\treturn updated\n}\n\nconst sessionActiveStore = new AsyncLocalStorage<boolean>()\n\nexport class InMemoryAdapter extends configurable(inMemoryConnectionPipe, OrmAdapter) {\n\treadonly schemaConfigPipe = v.object({ table: v.string() })\n\n\treadonly supportedFieldTypes = ['string', 'number', 'boolean', 'null', 'object', 'array', 'date'] as const\n\treadonly queryableOps = ['eq', 'ne', 'gt', 'gte', 'lt', 'lte', 'in', 'notIn', 'like', 'exists', 'notExists', 'contains', 'notContains'] as const\n\treadonly updateOps = ['set', 'inc', 'mul', 'min', 'max', 'unset', 'push', 'pull', 'patch'] as const\n\treadonly aggregateOps = ['count', 'countDistinct', 'sum', 'avg', 'min', 'max'] as const\n\n\treadonly stores = new Map<string, Map<string, Record<string, unknown>>>()\n\treadonly migrations = new Map<string, MigrationRecord>()\n\treadonly indexes = new Map<string, IndexMeta>()\n\treadonly tables = new Map<string, TableMeta>()\n\treadonly foreignKeys = new Map<string, ForeignKeyMeta>()\n\n\t#migrationLock: Promise<void> = Promise.resolve()\n\n\tprotected constructor(config: typeof InMemoryAdapter.Config) {\n\t\tsuper(config)\n\t}\n\n\t#getStore(name: string) {\n\t\tif (!this.stores.has(name)) this.stores.set(name, new Map())\n\t\treturn this.stores.get(name)!\n\t}\n\n\t#resolveStore(schema: AnySchema, config: unknown) {\n\t\treturn this.#getStore(resolveConfigName(schema, config as InMemoryRepoConfig))\n\t}\n\n\t#snapshot(): Snapshot {\n\t\tconst stores = new Map<string, Map<string, Record<string, unknown>>>()\n\t\tfor (const [name, store] of this.stores.entries()) {\n\t\t\tconst storeCopy = new Map<string, Record<string, unknown>>()\n\t\t\tfor (const [id, doc] of store.entries()) {\n\t\t\t\tstoreCopy.set(id, clone(doc))\n\t\t\t}\n\t\t\tstores.set(name, storeCopy)\n\t\t}\n\t\tconst migrations = new Map(this.migrations)\n\t\tconst indexes = new Map(this.indexes)\n\t\tconst tables = new Map<string, TableMeta>()\n\t\tfor (const [name, meta] of this.tables.entries()) {\n\t\t\ttables.set(name, { pk: { ...meta.pk }, fields: new Map(meta.fields) })\n\t\t}\n\t\tconst foreignKeys = new Map(this.foreignKeys)\n\t\treturn { stores, migrations, indexes, tables, foreignKeys }\n\t}\n\n\t#restore(snap: Snapshot) {\n\t\treplaceMap(this.stores, snap.stores)\n\t\treplaceMap(this.migrations, snap.migrations)\n\t\treplaceMap(this.indexes, snap.indexes)\n\t\treplaceMap(this.tables, snap.tables)\n\t\treplaceMap(this.foreignKeys, snap.foreignKeys)\n\t}\n\n\tasync findByPk(schema: AnySchema, config: unknown, pk: unknown) {\n\t\tconst store = this.#resolveStore(schema, config)\n\t\tconst doc = store.get(String(pk))\n\t\treturn doc ? clone(doc) : null\n\t}\n\n\tasync createMany(schema: AnySchema, config: unknown, data: Record<string, unknown>[]) {\n\t\tconst pk = schema.pkField.name\n\t\tconst store = this.#resolveStore(schema, config)\n\t\treturn data.map((d) => {\n\t\t\tconst row = clone(d)\n\t\t\tstore.set(String(row[pk]), row)\n\t\t\treturn clone(row)\n\t\t})\n\t}\n\n\tasync deleteByPk(schema: AnySchema, config: unknown, pk: unknown) {\n\t\tconst store = this.#resolveStore(schema, config)\n\t\tconst pkStr = String(pk)\n\t\tconst doc = store.get(pkStr)\n\t\tif (!doc) return null\n\t\tstore.delete(pkStr)\n\t\treturn clone(doc)\n\t}\n\n\tasync updateByPk(schema: AnySchema, config: unknown, pk: unknown, ops: AnyUpdateOp[]) {\n\t\tconst store = this.#resolveStore(schema, config)\n\t\tconst pkStr = String(pk)\n\t\tconst doc = store.get(pkStr)\n\t\tif (!doc) return null\n\t\tconst updated = applyOps(doc, ops)\n\t\tstore.set(pkStr, updated)\n\t\treturn clone(updated)\n\t}\n\n\tasync findMany(schema: AnySchema, config: unknown, group: FilterGroup, options?: QueryOptions) {\n\t\tconst store = this.#resolveStore(schema, config)\n\t\tconst rows = [...store.values()].filter((doc) => matchesFilter(doc, group)).map((doc) => clone(doc))\n\t\treturn applyOptions(rows, options)\n\t}\n\n\tasync count(schema: AnySchema, config: unknown, group: FilterGroup) {\n\t\tconst store = this.#resolveStore(schema, config)\n\t\treturn [...store.values()].filter((doc) => matchesFilter(doc, group)).length\n\t}\n\n\tasync *iterateMany(schema: AnySchema, config: unknown, group: FilterGroup, options?: IterationQueryOptions) {\n\t\tconst store = this.#resolveStore(schema, config)\n\t\tconst rows = applyOptions([...store.values()].filter((doc) => matchesFilter(doc, group)).map((doc) => clone(doc)), options)\n\t\tconst batchSize = typeof options?.batchSize === 'number' && Number.isSafeInteger(options.batchSize) && options.batchSize > 0 ? options.batchSize : 1\n\t\tfor (let start = 0; start < rows.length; start += batchSize) {\n\t\t\tfor (const row of rows.slice(start, start + batchSize)) {\n\t\t\t\tyield row\n\t\t\t}\n\t\t}\n\t}\n\n\tasync updateMany(schema: AnySchema, config: unknown, group: FilterGroup, data: Record<string, unknown>) {\n\t\tconst store = this.#resolveStore(schema, config)\n\t\tconst ids = [...store.entries()].filter(([, doc]) => matchesFilter(doc, group)).map(([id]) => id)\n\n\t\tconst updated: Record<string, unknown>[] = []\n\t\tfor (const id of ids) {\n\t\t\tconst current = store.get(id)\n\t\t\tif (!current) continue\n\t\t\tconst next = applyUpdateData(current, data)\n\t\t\tstore.set(id, next)\n\t\t\tupdated.push(clone(next))\n\t\t}\n\t\treturn updated\n\t}\n\n\tasync deleteMany(schema: AnySchema, config: unknown, filter: FilterGroup) {\n\t\tconst store = this.#resolveStore(schema, config)\n\t\tconst pk = schema.pkField.name\n\t\tconst rows = [...store.values()].filter((doc) => matchesFilter(doc, filter)).map((doc) => clone(doc))\n\t\tfor (const row of rows) store.delete(String(row[pk]))\n\t\treturn rows\n\t}\n\n\tasync upsertOne(schema: AnySchema, config: unknown, filter: FilterGroup, create: Record<string, unknown>, ops: AnyUpdateOp[]) {\n\t\tconst store = this.#resolveStore(schema, config)\n\t\tconst pk = schema.pkField.name\n\t\tconst rows = [...store.values()].filter((doc) => matchesFilter(doc, filter))\n\t\tconst current = rows[0]\n\t\tif (current) {\n\t\t\tconst next = ops.length > 0 ? applyOps(current, ops) : clone(current)\n\t\t\tstore.set(String(next[pk]), next)\n\t\t\treturn clone(next)\n\t\t}\n\t\tconst base = clone(create)\n\t\tconst result = ops.length > 0 ? applyOps(base, ops) : base\n\t\tstore.set(String(result[pk]), result)\n\t\treturn clone(result)\n\t}\n\n\tasync aggregate(schema: AnySchema, config: unknown, spec: AggregateSpec): Promise<Array<Record<string, unknown>>> {\n\t\tconst store = this.#resolveStore(schema, config)\n\t\tlet rows = [...store.values()]\n\t\tif (spec.where) {\n\t\t\tconst where = spec.where\n\t\t\trows = rows.filter((doc) => matchesFilter(doc, where))\n\t\t}\n\n\t\tconst groups = new Map<string, Record<string, unknown>[]>()\n\t\tif (spec.groupBy.length > 0) {\n\t\t\tfor (const row of rows) {\n\t\t\t\tconst key = JSON.stringify(spec.groupBy.map((f) => row[f]))\n\t\t\t\tif (!groups.has(key)) groups.set(key, [])\n\t\t\t\tgroups.get(key)!.push(row)\n\t\t\t}\n\t\t} else {\n\t\t\tgroups.set('__all__', rows)\n\t\t}\n\n\t\tconst results: Record<string, unknown>[] = []\n\t\tfor (const [, groupRows] of groups) {\n\t\t\tconst result: Record<string, unknown> = {}\n\t\t\tif (spec.groupBy.length > 0) {\n\t\t\t\tfor (const field of spec.groupBy) {\n\t\t\t\t\tresult[field] = groupRows[0][field]\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const agg of spec.aggregates) {\n\t\t\t\tswitch (agg.fn) {\n\t\t\t\t\tcase 'count':\n\t\t\t\t\t\tresult[agg.alias] = groupRows.length\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'countDistinct': {\n\t\t\t\t\t\tconst vals = new Set(groupRows.map((r) => JSON.stringify(r[agg.field!])))\n\t\t\t\t\t\tresult[agg.alias] = vals.size\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tcase 'sum':\n\t\t\t\t\t\tresult[agg.alias] = groupRows.reduce((acc, r) => acc + Number(r[agg.field!] ?? 0), 0)\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'avg': {\n\t\t\t\t\t\tconst sum = groupRows.reduce((acc, r) => acc + Number(r[agg.field!] ?? 0), 0)\n\t\t\t\t\t\tresult[agg.alias] = groupRows.length > 0 ? sum / groupRows.length : 0\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tcase 'min': {\n\t\t\t\t\t\tlet minVal: unknown = null\n\t\t\t\t\t\tfor (const r of groupRows) {\n\t\t\t\t\t\t\tconst val = r[agg.field!]\n\t\t\t\t\t\t\tif (val != null && (minVal === null || compareValues(val, minVal) < 0)) minVal = val\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresult[agg.alias] = minVal\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tcase 'max': {\n\t\t\t\t\t\tlet maxVal: unknown = null\n\t\t\t\t\t\tfor (const r of groupRows) {\n\t\t\t\t\t\t\tconst val = r[agg.field!]\n\t\t\t\t\t\t\tif (val != null && (maxVal === null || compareValues(val, maxVal) > 0)) maxVal = val\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresult[agg.alias] = maxVal\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tresults.push(result)\n\t\t}\n\n\t\tif (spec.having) {\n\t\t\tconst having = spec.having\n\t\t\treturn results.filter((r) => matchesFilter(r, having))\n\t\t}\n\t\treturn results\n\t}\n\n\tasync loadMigrations(): Promise<{ id: string; appliedAt: number }[]> {\n\t\treturn [...this.migrations.values()].map((m) => ({ id: m.id, appliedAt: m.appliedAt }))\n\t}\n\n\tasync recordMigration(id: string, appliedAt: number): Promise<void> {\n\t\tthis.migrations.set(id, { id, appliedAt })\n\t}\n\n\tasync applyCreateTable(change: CreateTableChange<any>): Promise<void> {\n\t\tconst fields = new Map<string, AnyFieldSpec>()\n\t\tfor (const f of change.fields) {\n\t\t\tfields.set(f.name, { ...f })\n\t\t}\n\t\tthis.tables.set(change.name, { pk: { name: change.pk.name, type: change.pk.type }, fields })\n\t}\n\n\tasync applyDropTable(change: DropTableChange): Promise<void> {\n\t\tthis.tables.delete(change.name)\n\t}\n\n\tasync applyAddField(change: AddFieldChange<any>): Promise<void> {\n\t\tconst table = this.tables.get(change.table)\n\t\tif (table) {\n\t\t\ttable.fields.set(change.field.name, { ...change.field })\n\t\t}\n\t}\n\n\tasync applyDropField(change: DropFieldChange): Promise<void> {\n\t\tconst table = this.tables.get(change.table)\n\t\tif (table) {\n\t\t\ttable.fields.delete(change.name)\n\t\t}\n\t}\n\n\tasync applyModifyField(change: ModifyFieldChange<any>): Promise<void> {\n\t\tconst table = this.tables.get(change.table)\n\t\tif (table) {\n\t\t\ttable.fields.delete(change.name)\n\t\t\ttable.fields.set(change.to.name, { ...change.to })\n\t\t}\n\t}\n\n\tasync applyRenameTable(change: RenameTableChange): Promise<void> {\n\t\tconst table = this.tables.get(change.from)\n\t\tif (table) {\n\t\t\tthis.tables.delete(change.from)\n\t\t\tthis.tables.set(change.to, table)\n\t\t}\n\t}\n\n\tasync applyRenameField(change: RenameFieldChange): Promise<void> {\n\t\tconst table = this.tables.get(change.table)\n\t\tif (table) {\n\t\t\tconst field = table.fields.get(change.from)\n\t\t\tif (field) {\n\t\t\t\ttable.fields.delete(change.from)\n\t\t\t\ttable.fields.set(change.to, { ...field, name: change.to })\n\t\t\t}\n\t\t}\n\t}\n\n\tasync applyAddIndex(change: AddIndexChange): Promise<void> {\n\t\tconst indexName = change.name ?? `${change.table}_${change.on.join('_')}_idx`\n\t\tthis.indexes.set(indexName, { table: change.table, on: change.on, unique: change.unique ?? false })\n\t}\n\n\tasync applyDropIndex(change: DropIndexChange): Promise<void> {\n\t\tthis.indexes.delete(change.name)\n\t}\n\n\tasync applyAddForeignKey(change: AddForeignKeyChange): Promise<void> {\n\t\tconst fkName = change.name ?? `${change.table}_${change.on}_fk`\n\t\tthis.foreignKeys.set(fkName, {\n\t\t\ttable: change.table,\n\t\t\ton: change.on,\n\t\t\treferences: { table: change.references.table, column: change.references.column },\n\t\t\tonDelete: change.onDelete as ForeignKeyAction | undefined,\n\t\t\tonUpdate: change.onUpdate as ForeignKeyAction | undefined,\n\t\t})\n\t}\n\n\tasync applyDropForeignKey(change: DropForeignKeyChange): Promise<void> {\n\t\tthis.foreignKeys.delete(change.name)\n\t}\n\n\tasync introspect(): Promise<DiscoveredSchema[]> {\n\t\tconst schemas: DiscoveredSchema[] = []\n\t\tfor (const [name, meta] of this.tables.entries()) {\n\t\t\tconst fields = [...meta.fields.values()].map((f) => ({\n\t\t\t\tname: f.name,\n\t\t\t\ttype: f.type as FieldTypeName,\n\t\t\t\tnullable: f.nullable ?? false,\n\t\t\t\t...(f.default !== undefined ? { default: f.default } : {}),\n\t\t\t\t...(f.unique ? { unique: true } : {}),\n\t\t\t}))\n\t\t\tconst indexes = [...this.indexes.entries()]\n\t\t\t\t.filter(([, idx]) => idx.table === name)\n\t\t\t\t.map(([idxName, idx]) => ({ name: idxName, on: idx.on, unique: idx.unique }))\n\t\t\tconst foreignKeys = [...this.foreignKeys.entries()]\n\t\t\t\t.filter(([, fk]) => fk.table === name)\n\t\t\t\t.map(([fkName, fk]) => ({\n\t\t\t\t\tname: fkName,\n\t\t\t\t\ton: fk.on,\n\t\t\t\t\treferences: { table: fk.references.table, column: fk.references.column },\n\t\t\t\t\t...(fk.onDelete ? { onDelete: fk.onDelete } : {}),\n\t\t\t\t\t...(fk.onUpdate ? { onUpdate: fk.onUpdate } : {}),\n\t\t\t\t}))\n\t\t\tschemas.push({\n\t\t\t\tname,\n\t\t\t\tpk: { name: meta.pk.name, type: meta.pk.type as FieldTypeName },\n\t\t\t\tfields,\n\t\t\t\tindexes,\n\t\t\t\tforeignKeys,\n\t\t\t})\n\t\t}\n\t\treturn schemas\n\t}\n\n\tasync acquireMigrationLock<T>(fn: () => Promise<T>): Promise<T> {\n\t\tlet release!: () => void\n\t\tconst next = new Promise<void>((r) => { release = r })\n\t\tconst prev = this.#migrationLock\n\t\tthis.#migrationLock = next\n\t\tawait prev\n\t\ttry {\n\t\t\treturn await fn()\n\t\t} finally {\n\t\t\trelease()\n\t\t}\n\t}\n\n\tasync session<T>(fn: () => Promise<T>): Promise<T> {\n\t\tif (sessionActiveStore.getStore()) return fn()\n\t\tconst snap = this.#snapshot()\n\t\ttry {\n\t\t\treturn await sessionActiveStore.run(true, fn)\n\t\t} catch (error) {\n\t\t\tthis.#restore(snap)\n\t\t\tthrow error\n\t\t}\n\t}\n}\n\nif (import.meta.vitest) {\n\tconst { describe, test, expect } = import.meta.vitest\n\tconst { FilterGroup } = await import('../../filter')\n\tconst { OrderBy } = await import('../../query-options')\n\tconst { Schema } = await import('../../schema')\n\tconst { IncOp, PatchOp, PullOp, PushOp } = await import('../../updates')\n\n\tconst { v } = await import('valleyed')\n\n\tdescribe('in-memory adapter', () => {\n\t\ttest('InMemoryAdapter.create({}) produces a working adapter', () => {\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\texpect(adapter).toBeInstanceOf(InMemoryAdapter)\n\t\t\texpect(adapter.schemaConfigPipe).toBeDefined()\n\t\t\texpect(adapter.supportedFieldTypes).toEqual(['string', 'number', 'boolean', 'null', 'object', 'array', 'date'])\n\t\t})\n\n\t\ttest('supports nested filters, ordering, select, offset and limit', async () => {\n\t\t\tconst schema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => 'u')\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\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst use = adapter.use(schema, { table: 'users' })\n\t\t\tawait use.createMany([\n\t\t\t\t{ id: 'u1', name: 'Alice', age: 30 },\n\t\t\t\t{ id: 'u2', name: 'Bob', age: 20 },\n\t\t\t\t{ id: 'u3', name: 'Carol', age: 40 },\n\t\t\t])\n\t\t\tconst builtGroup = FilterGroup.create().and([\n\t\t\t\t(q) => q.gt('age', 19),\n\t\t\t\t(q) => q.or([(g) => g.eq('name', 'Alice'), (g) => g.eq('name', 'Carol')]),\n\t\t\t])\n\t\t\tconst options = { orderBy: [new OrderBy('age', 'desc')], offset: 1, limit: 1, select: ['id', 'name'] }\n\t\t\tconst rows = await use.findMany(builtGroup, options)\n\t\t\texpect(rows).toEqual([{ id: 'u1', name: 'Alice' }])\n\t\t})\n\n\t\ttest('count returns the number of matching rows without query options', async () => {\n\t\t\tconst schema = Schema.from('count_users')\n\t\t\t\t.pk('id', v.string(), () => 'u')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst use = adapter.use(schema, { table: 'count_users' })\n\t\t\tawait use.createMany([\n\t\t\t\t{ id: 'u1', name: 'Alice' },\n\t\t\t\t{ id: 'u2', name: 'Bob' },\n\t\t\t\t{ id: 'u3', name: 'Alice' },\n\t\t\t])\n\n\t\t\tawait expect(use.count(FilterGroup.create().eq('name', 'Alice'))).resolves.toBe(2)\n\t\t})\n\n\t\ttest('iterateMany with batchSize matches findMany filtering ordering offset limit and selection', async () => {\n\t\t\tconst schema = Schema.from('iter_users')\n\t\t\t\t.pk('id', v.string(), () => 'u')\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\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst use = adapter.use(schema, { table: 'iter_users' })\n\t\t\tawait use.createMany([\n\t\t\t\t{ id: 'u1', name: 'Alice', age: 30 },\n\t\t\t\t{ id: 'u2', name: 'Bob', age: 20 },\n\t\t\t\t{ id: 'u3', name: 'Carol', age: 40 },\n\t\t\t\t{ id: 'u4', name: 'Dan', age: 50 },\n\t\t\t])\n\n\t\t\tconst filter = FilterGroup.create().gt('age', 19)\n\t\t\tconst options = {\n\t\t\t\torderBy: [new OrderBy('age', 'desc')],\n\t\t\t\toffset: 1,\n\t\t\t\tlimit: 2,\n\t\t\t\tselect: ['id', 'name'],\n\t\t\t}\n\t\t\tconst expected = await use.findMany(filter, options)\n\t\t\tconst rows: Record<string, unknown>[] = []\n\t\t\tfor await (const row of use.iterateMany(filter, { ...options, batchSize: 2 })) {\n\t\t\t\trows.push(row)\n\t\t\t}\n\n\t\t\texpect(rows).toEqual(expected)\n\t\t\texpect(rows).toEqual([\n\t\t\t\t{ id: 'u3', name: 'Carol' },\n\t\t\t\t{ id: 'u1', name: 'Alice' },\n\t\t\t])\n\t\t})\n\n\t\ttest('supports update operators and rollback on failed session', async () => {\n\t\t\tconst schema = Schema.from('docs')\n\t\t\t\t.pk('id', v.string(), () => 'd1')\n\t\t\t\t.field('count', v.number())\n\t\t\t\t.field('tags', v.array(v.string()))\n\t\t\t\t.field('meta', v.object({ a: v.number() }))\n\t\t\t\t.build()\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst use = adapter.use(schema, { table: 'docs' })\n\t\t\tawait use.createOne({ id: 'd1', count: 1, tags: ['x'], meta: { a: 1 } })\n\n\t\t\tawait adapter.session(async () => {\n\t\t\t\tawait use.updateOne(FilterGroup.create().eq('id', 'd1'), {\n\t\t\t\t\tcount: new IncOp('count', 2),\n\t\t\t\t\ttags: new PushOp('tags', 'y'),\n\t\t\t\t\tmeta: new PatchOp('meta', { a: 9 }),\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tawait expect(\n\t\t\t\tadapter.session(async () => {\n\t\t\t\t\tawait use.updateOne(FilterGroup.create().eq('id', 'd1'), { tags: new PullOp('tags', 'x') })\n\t\t\t\t\tthrow new Error('boom')\n\t\t\t\t}),\n\t\t\t).rejects.toThrow('boom')\n\n\t\t\tconst row = await use.findOne(FilterGroup.create().eq('id', 'd1'))\n\t\t\texpect(row).toEqual({ id: 'd1', count: 3, tags: ['x', 'y'], meta: { a: 9 } })\n\t\t})\n\n\t\ttest('notIn filter excludes matching values', async () => {\n\t\t\tconst schema = Schema.from('users')\n\t\t\t\t.pk('id', v.string(), () => 'u')\n\t\t\t\t.field('name', v.string())\n\t\t\t\t.build()\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst use = adapter.use(schema, { table: 'users' })\n\t\t\tawait use.createMany([\n\t\t\t\t{ id: 'u1', name: 'Alice' },\n\t\t\t\t{ id: 'u2', name: 'Bob' },\n\t\t\t\t{ id: 'u3', name: 'Carol' },\n\t\t\t])\n\t\t\tconst rows = await use.findMany(FilterGroup.create().notIn('name', ['Alice', 'Carol']))\n\t\t\texpect(rows).toHaveLength(1)\n\t\t\texpect(rows[0].name).toBe('Bob')\n\t\t})\n\n\t\ttest('exists / notExists filter ops work correctly', async () => {\n\t\t\tconst schema = Schema.from('items')\n\t\t\t\t.pk('id', v.string(), () => 'i')\n\t\t\t\t.field('val', v.optional(v.string()), { onCreate: () => undefined })\n\t\t\t\t.build()\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst use = adapter.use(schema, { table: 'items' })\n\t\t\tawait use.createMany([\n\t\t\t\t{ id: 'i1', val: 'present' },\n\t\t\t\t{ id: 'i2', val: null },\n\t\t\t\t{ id: 'i3', val: undefined },\n\t\t\t])\n\t\t\tconst existsRows = await use.findMany(FilterGroup.create().exists('val'))\n\t\t\texpect(existsRows).toHaveLength(1)\n\t\t\texpect(existsRows[0].id).toBe('i1')\n\n\t\t\tconst notExistsRows = await use.findMany(FilterGroup.create().notExists('val'))\n\t\t\texpect(notExistsRows).toHaveLength(2)\n\t\t})\n\n\t\ttest('findByPk returns seeded document and null for missing', async () => {\n\t\t\tconst schema = Schema.from('test')\n\t\t\t\t.pk('id', v.string(), () => 'gen')\n\t\t\t\t.build()\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\n\t\t\tconst use = adapter.use(schema, { table: 'test' })\n\t\t\tawait use.createOne({ id: 'x' })\n\n\t\t\tconst found = await adapter.findByPk(schema, { table: 'test' }, 'x')\n\t\t\texpect(found).toEqual({ id: 'x' })\n\n\t\t\tconst missing = await adapter.findByPk(schema, { table: 'test' }, 'missing')\n\t\t\texpect(missing).toBeNull()\n\t\t})\n\n\t\ttest('auto-wired Instance hooks register correctly keyed by class', async () => {\n\t\t\tconst { Instance: Inst } = await import('../../../instance')\n\t\t\tconst onSpy = (await import('vitest')).vi.spyOn(Inst, 'on').mockImplementation(() => {})\n\n\t\t\tInMemoryAdapter.create({})\n\n\t\t\texpect(onSpy).not.toHaveBeenCalled()\n\n\t\t\tonSpy.mockRestore()\n\t\t})\n\n\t\ttest('declares aggregateOps with all six canonical ops', () => {\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\texpect(adapter.aggregateOps).toEqual(['count', 'countDistinct', 'sum', 'avg', 'min', 'max'])\n\t\t})\n\n\t\ttest('aggregate count returns correct total', async () => {\n\t\t\tconst schema = Schema.from('items')\n\t\t\t\t.pk('id', v.string(), () => `i-${Math.random().toString(36).slice(2, 8)}`)\n\t\t\t\t.field('category', v.string())\n\t\t\t\t.build()\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst use = adapter.use(schema, { table: 'items' })\n\t\t\tawait use.createMany([\n\t\t\t\t{ id: 'i1', category: 'A' },\n\t\t\t\t{ id: 'i2', category: 'B' },\n\t\t\t\t{ id: 'i3', category: 'A' },\n\t\t\t])\n\t\t\tconst result = await adapter.aggregate(schema, { table: 'items' }, {\n\t\t\t\taggregates: [{ fn: 'count', alias: 'total' }],\n\t\t\t\tgroupBy: [],\n\t\t\t})\n\t\t\texpect(result).toEqual([{ total: 3 }])\n\t\t})\n\n\t\ttest('aggregate count with where filter', async () => {\n\t\t\tconst schema = Schema.from('items')\n\t\t\t\t.pk('id', v.string(), () => `i-${Math.random().toString(36).slice(2, 8)}`)\n\t\t\t\t.field('category', v.string())\n\t\t\t\t.build()\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst use = adapter.use(schema, { table: 'items' })\n\t\t\tawait use.createMany([\n\t\t\t\t{ id: 'i1', category: 'A' },\n\t\t\t\t{ id: 'i2', category: 'B' },\n\t\t\t\t{ id: 'i3', category: 'A' },\n\t\t\t])\n\t\t\tconst result = await adapter.aggregate(schema, { table: 'items' }, {\n\t\t\t\taggregates: [{ fn: 'count', alias: 'total' }],\n\t\t\t\tgroupBy: [],\n\t\t\t\twhere: FilterGroup.create().eq('category', 'A'),\n\t\t\t})\n\t\t\texpect(result).toEqual([{ total: 2 }])\n\t\t})\n\n\t\ttest('aggregate count on empty store returns zero', async () => {\n\t\t\tconst schema = Schema.from('items')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.build()\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst result = await adapter.aggregate(schema, { table: 'items' }, {\n\t\t\t\taggregates: [{ fn: 'count', alias: 'total' }],\n\t\t\t\tgroupBy: [],\n\t\t\t})\n\t\t\texpect(result).toEqual([{ total: 0 }])\n\t\t})\n\n\t\ttest('aggregate sum/avg/min/max compute correctly', async () => {\n\t\t\tconst schema = Schema.from('items')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('price', v.number())\n\t\t\t\t.build()\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst use = adapter.use(schema, { table: 'items' })\n\t\t\tawait use.createMany([\n\t\t\t\t{ id: 'i1', price: 10 },\n\t\t\t\t{ id: 'i2', price: 20 },\n\t\t\t\t{ id: 'i3', price: 30 },\n\t\t\t])\n\t\t\tconst result = await adapter.aggregate(schema, { table: 'items' }, {\n\t\t\t\taggregates: [\n\t\t\t\t\t{ fn: 'sum', field: 'price', alias: 'total' },\n\t\t\t\t\t{ fn: 'avg', field: 'price', alias: 'avg' },\n\t\t\t\t\t{ fn: 'min', field: 'price', alias: 'low' },\n\t\t\t\t\t{ fn: 'max', field: 'price', alias: 'high' },\n\t\t\t\t],\n\t\t\t\tgroupBy: [],\n\t\t\t})\n\t\t\texpect(result).toEqual([{ total: 60, avg: 20, low: 10, high: 30 }])\n\t\t})\n\n\t\ttest('aggregate countDistinct counts unique values', async () => {\n\t\t\tconst schema = Schema.from('items')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('category', v.string())\n\t\t\t\t.build()\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst use = adapter.use(schema, { table: 'items' })\n\t\t\tawait use.createMany([\n\t\t\t\t{ id: 'i1', category: 'A' },\n\t\t\t\t{ id: 'i2', category: 'B' },\n\t\t\t\t{ id: 'i3', category: 'A' },\n\t\t\t])\n\t\t\tconst result = await adapter.aggregate(schema, { table: 'items' }, {\n\t\t\t\taggregates: [{ fn: 'countDistinct', field: 'category', alias: 'unique' }],\n\t\t\t\tgroupBy: [],\n\t\t\t})\n\t\t\texpect(result).toEqual([{ unique: 2 }])\n\t\t})\n\n\t\ttest('aggregate groupBy produces per-group results', async () => {\n\t\t\tconst schema = Schema.from('orders')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('region', v.string())\n\t\t\t\t.field('amount', v.number())\n\t\t\t\t.build()\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst use = adapter.use(schema, { table: 'orders' })\n\t\t\tawait use.createMany([\n\t\t\t\t{ id: 'o1', region: 'US', amount: 10 },\n\t\t\t\t{ id: 'o2', region: 'EU', amount: 20 },\n\t\t\t\t{ id: 'o3', region: 'US', amount: 30 },\n\t\t\t])\n\t\t\tconst result = await adapter.aggregate(schema, { table: 'orders' }, {\n\t\t\t\taggregates: [\n\t\t\t\t\t{ fn: 'count', alias: 'cnt' },\n\t\t\t\t\t{ fn: 'sum', field: 'amount', alias: 'total' },\n\t\t\t\t],\n\t\t\t\tgroupBy: ['region'],\n\t\t\t})\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: 2, total: 40 })\n\t\t\texpect(eu).toEqual({ region: 'EU', cnt: 1, total: 20 })\n\t\t})\n\n\t\ttest('aggregate having filters post-aggregation', async () => {\n\t\t\tconst schema = Schema.from('orders')\n\t\t\t\t.pk('id', v.string(), () => 'x')\n\t\t\t\t.field('region', v.string())\n\t\t\t\t.field('amount', v.number())\n\t\t\t\t.build()\n\t\t\tconst adapter = InMemoryAdapter.create({})\n\t\t\tconst use = adapter.use(schema, { table: 'orders' })\n\t\t\tawait use.createMany([\n\t\t\t\t{ id: 'o1', region: 'US', amount: 10 },\n\t\t\t\t{ id: 'o2', region: 'EU', amount: 20 },\n\t\t\t\t{ id: 'o3', region: 'US', amount: 30 },\n\t\t\t])\n\t\t\tconst result = await adapter.aggregate(schema, { table: 'orders' }, {\n\t\t\t\taggregates: [{ fn: 'sum', field: 'amount', alias: 'total' }],\n\t\t\t\tgroupBy: ['region'],\n\t\t\t\thaving: FilterGroup.create().gt('total', 25),\n\t\t\t})\n\t\t\texpect(result).toHaveLength(1)\n\t\t\texpect(result[0]).toEqual({ region: 'US', total: 40 })\n\t\t})\n\t})\n}\n","import type { PipeInput, PipeOutput } from 'valleyed'\n\nimport type { AnyField, AnySchemaField, SchemaField } from './fields'\nimport { toFieldName } from './fields'\nimport type { AnySchema, SchemaFields } from './schema'\nimport type { Prettify } from './utils'\n\nexport type NumericFieldOf<S extends AnySchema> = {\n\t[K in keyof SchemaFields<S>]: SchemaFields<S>[K] extends SchemaField<any, infer P, any>\n\t\t? PipeOutput<P> extends number\n\t\t\t? SchemaFields<S>[K]\n\t\t\t: never\n\t\t: never\n}[keyof SchemaFields<S>]\n\nexport type ComparableFieldOf<S extends AnySchema> = {\n\t[K in keyof SchemaFields<S>]: SchemaFields<S>[K] extends SchemaField<any, infer P, any>\n\t\t? PipeOutput<P> extends number | string | Date\n\t\t\t? SchemaFields<S>[K]\n\t\t\t: never\n\t\t: never\n}[keyof SchemaFields<S>]\n\nexport type OptionalFieldOf<S extends AnySchema> = {\n\t[K in keyof SchemaFields<S>]: SchemaFields<S>[K] extends SchemaField<any, infer P, any>\n\t\t? undefined extends PipeOutput<P>\n\t\t\t? SchemaFields<S>[K]\n\t\t\t: never\n\t\t: never\n}[keyof SchemaFields<S>]\n\nexport type ArrayFieldOf<S extends AnySchema> = {\n\t[K in keyof SchemaFields<S>]: SchemaFields<S>[K] extends SchemaField<any, infer P, any>\n\t\t? PipeOutput<P> extends readonly any[]\n\t\t\t? SchemaFields<S>[K]\n\t\t\t: never\n\t\t: never\n}[keyof SchemaFields<S>]\n\nexport type ObjectFieldOf<S extends AnySchema> = {\n\t[K in keyof SchemaFields<S>]: SchemaFields<S>[K] extends SchemaField<any, infer P, any>\n\t\t? PipeOutput<P> extends Record<string, any>\n\t\t\t? PipeOutput<P> extends readonly any[]\n\t\t\t\t? never\n\t\t\t\t: SchemaFields<S>[K]\n\t\t\t: never\n\t\t: never\n}[keyof SchemaFields<S>]\n\nexport type SetValues<S extends AnySchema> = Prettify<\n\tPartial<{\n\t\t[K in keyof SchemaFields<S>]: SchemaFields<S>[K] extends AnySchemaField ? PipeInput<SchemaFields<S>[K]['pipe']> : never\n\t}>\n>\n\nexport class SetOp {\n\treadonly kind = 'set' as const\n\tconstructor(readonly values: Record<string, unknown>) {}\n}\nexport class IncOp {\n\treadonly kind = 'inc' as const\n\tconstructor(\n\t\treadonly field: string,\n\t\treadonly value: number,\n\t) {}\n}\nexport class MulOp {\n\treadonly kind = 'mul' as const\n\tconstructor(\n\t\treadonly field: string,\n\t\treadonly value: number,\n\t) {}\n}\nexport class MinOp<T = unknown> {\n\treadonly kind = 'min' as const\n\tconstructor(\n\t\treadonly field: string,\n\t\treadonly value: T,\n\t) {}\n}\nexport class MaxOp<T = unknown> {\n\treadonly kind = 'max' as const\n\tconstructor(\n\t\treadonly field: string,\n\t\treadonly value: T,\n\t) {}\n}\nexport class UnsetOp {\n\treadonly kind = 'unset' as const\n\tconstructor(readonly field: string) {}\n}\nexport class PushOp<T = unknown> {\n\treadonly kind = 'push' as const\n\tconstructor(\n\t\treadonly field: string,\n\t\treadonly value: T,\n\t) {}\n}\nexport class PullOp<T = unknown> {\n\treadonly kind = 'pull' as const\n\tconstructor(\n\t\treadonly field: string,\n\t\treadonly value: T,\n\t) {}\n}\nexport class PatchOp<T = unknown> {\n\treadonly kind = 'patch' as const\n\tconstructor(\n\t\treadonly field: string,\n\t\treadonly value: T,\n\t) {}\n}\n\nexport type AnyUpdateOp = SetOp | IncOp | MulOp | MinOp | MaxOp | UnsetOp | PushOp | PullOp | PatchOp\n\nexport function isUpdateOp(v: unknown): v is AnyUpdateOp {\n\treturn (\n\t\tv instanceof SetOp ||\n\t\tv instanceof IncOp ||\n\t\tv instanceof MulOp ||\n\t\tv instanceof MinOp ||\n\t\tv instanceof MaxOp ||\n\t\tv instanceof UnsetOp ||\n\t\tv instanceof PushOp ||\n\t\tv instanceof PullOp ||\n\t\tv instanceof PatchOp\n\t)\n}\n\nexport type HasOp<A, Op extends string> = A extends { updateOps: readonly (infer U)[] } ? (Op extends U ? true : false) : false\n\nexport type UpdateOp<_S extends AnySchema, A> =\n\t| (HasOp<A, 'set'> extends true ? SetOp : never)\n\t| (HasOp<A, 'inc'> extends true ? IncOp : never)\n\t| (HasOp<A, 'mul'> extends true ? MulOp : never)\n\t| (HasOp<A, 'min'> extends true ? MinOp : never)\n\t| (HasOp<A, 'max'> extends true ? MaxOp : never)\n\t| (HasOp<A, 'unset'> extends true ? UnsetOp : never)\n\t| (HasOp<A, 'push'> extends true ? PushOp : never)\n\t| (HasOp<A, 'pull'> extends true ? PullOp : never)\n\t| (HasOp<A, 'patch'> extends true ? PatchOp : never)\n\nexport function set<S extends AnySchema>(values: SetValues<S>): SetOp {\n\treturn new SetOp(values as Record<string, unknown>)\n}\n\nexport function inc<S extends AnySchema>(field: NumericFieldOf<S>, value: number): IncOp {\n\treturn new IncOp(toFieldName(field as AnyField), value)\n}\n\nexport function mul<S extends AnySchema>(field: NumericFieldOf<S>, value: number): MulOp {\n\treturn new MulOp(toFieldName(field as AnyField), value)\n}\n\nexport function min<S extends AnySchema>(field: ComparableFieldOf<S>, value: unknown): MinOp {\n\treturn new MinOp(toFieldName(field as AnyField), value)\n}\n\nexport function max<S extends AnySchema>(field: ComparableFieldOf<S>, value: unknown): MaxOp {\n\treturn new MaxOp(toFieldName(field as AnyField), value)\n}\n\nexport function unset<S extends AnySchema>(field: OptionalFieldOf<S>): UnsetOp {\n\treturn new UnsetOp(toFieldName(field as AnyField))\n}\n\nexport function push<S extends AnySchema>(field: ArrayFieldOf<S>, value: unknown): PushOp {\n\treturn new PushOp(toFieldName(field as AnyField), value)\n}\n\nexport function pull<S extends AnySchema>(field: ArrayFieldOf<S>, value: unknown): PullOp {\n\treturn new PullOp(toFieldName(field as AnyField), value)\n}\n\nexport function patch<S extends AnySchema>(field: ObjectFieldOf<S>, value: Record<string, unknown>): PatchOp {\n\treturn new PatchOp(toFieldName(field as AnyField), value)\n}\n\nexport function opTouchedFields(op: AnyUpdateOp): string[] {\n\tif (op instanceof SetOp) return Object.keys(op.values)\n\treturn [op.field]\n}\n\nexport function flattenOps(ops: AnyUpdateOp[]): Record<string, unknown> {\n\tconst data: Record<string, unknown> = {}\n\tfor (const op of ops) {\n\t\tif (op instanceof SetOp) Object.assign(data, op.values)\n\t\telse data[op.field] = op\n\t}\n\treturn data\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\tconst TestSchema = Schema.from('test')\n\t\t.pk('id', v.string(), () => 'x')\n\t\t.field('name', v.string())\n\t\t.field('age', v.number())\n\t\t.field('score', v.optional(v.number()))\n\t\t.field('tags', v.array(v.string()))\n\t\t.field('meta', v.object({ a: v.number() }))\n\t\t.field('createdAt', v.number(), { onCreate: () => 1000 })\n\t\t.field('updatedAt', v.number(), { onCreate: () => 1000, onUpdate: () => 2000 })\n\t\t.build()\n\n\tdescribe('op classes', () => {\n\t\ttest('SetOp has kind \"set\" and stores values', () => {\n\t\t\tconst op = new SetOp({ name: 'Alice' })\n\t\t\texpect(op.kind).toBe('set')\n\t\t\texpect(op.values).toEqual({ name: 'Alice' })\n\t\t})\n\t\ttest('IncOp has kind \"inc\" and stores field + value', () => {\n\t\t\tconst op = new IncOp('age', 5)\n\t\t\texpect(op.kind).toBe('inc')\n\t\t\texpect(op.field).toBe('age')\n\t\t\texpect(op.value).toBe(5)\n\t\t})\n\t\ttest('MulOp has kind \"mul\" and stores field + value', () => {\n\t\t\tconst op = new MulOp('score', 3)\n\t\t\texpect(op.kind).toBe('mul')\n\t\t\texpect(op.field).toBe('score')\n\t\t\texpect(op.value).toBe(3)\n\t\t})\n\t\ttest('MinOp has kind \"min\"', () => {\n\t\t\texpect(new MinOp('age', 10).kind).toBe('min')\n\t\t})\n\t\ttest('MaxOp has kind \"max\"', () => {\n\t\t\texpect(new MaxOp('age', 99).kind).toBe('max')\n\t\t})\n\t\ttest('UnsetOp has kind \"unset\"', () => {\n\t\t\texpect(new UnsetOp('score').kind).toBe('unset')\n\t\t})\n\t\ttest('PushOp has kind \"push\"', () => {\n\t\t\texpect(new PushOp('tags', 'x').kind).toBe('push')\n\t\t})\n\t\ttest('PullOp has kind \"pull\"', () => {\n\t\t\texpect(new PullOp('tags', 'x').kind).toBe('pull')\n\t\t})\n\t\ttest('PatchOp has kind \"patch\"', () => {\n\t\t\texpect(new PatchOp('meta', { a: 9 }).kind).toBe('patch')\n\t\t})\n\t})\n\n\tdescribe('isUpdateOp', () => {\n\t\ttest('returns true for every op class', () => {\n\t\t\texpect(isUpdateOp(new SetOp({}))).toBe(true)\n\t\t\texpect(isUpdateOp(new IncOp('f', 1))).toBe(true)\n\t\t\texpect(isUpdateOp(new MulOp('f', 2))).toBe(true)\n\t\t\texpect(isUpdateOp(new MinOp('f', 0))).toBe(true)\n\t\t\texpect(isUpdateOp(new MaxOp('f', 10))).toBe(true)\n\t\t\texpect(isUpdateOp(new UnsetOp('f'))).toBe(true)\n\t\t\texpect(isUpdateOp(new PushOp('f', 'v'))).toBe(true)\n\t\t\texpect(isUpdateOp(new PullOp('f', 'v'))).toBe(true)\n\t\t\texpect(isUpdateOp(new PatchOp('f', {}))).toBe(true)\n\t\t})\n\t\ttest('returns false for non-op values', () => {\n\t\t\texpect(isUpdateOp('string')).toBe(false)\n\t\t\texpect(isUpdateOp(42)).toBe(false)\n\t\t\texpect(isUpdateOp(null)).toBe(false)\n\t\t\texpect(isUpdateOp({})).toBe(false)\n\t\t})\n\t})\n\n\tdescribe('op helper functions', () => {\n\t\ttest('set() creates SetOp with values', () => {\n\t\t\tconst op = set<typeof TestSchema>({ name: 'Alice', age: 30 })\n\t\t\texpect(op).toBeInstanceOf(SetOp)\n\t\t\texpect(op.kind).toBe('set')\n\t\t\texpect(op.values).toEqual({ name: 'Alice', age: 30 })\n\t\t})\n\t\ttest('inc() creates IncOp from field ref', () => {\n\t\t\tconst op = inc<typeof TestSchema>(TestSchema.fields.age, 5)\n\t\t\texpect(op).toBeInstanceOf(IncOp)\n\t\t\texpect(op.field).toBe('age')\n\t\t\texpect(op.value).toBe(5)\n\t\t})\n\t\ttest('mul() creates MulOp from field ref', () => {\n\t\t\tconst op = mul<typeof TestSchema>(TestSchema.fields.age, 3)\n\t\t\texpect(op).toBeInstanceOf(MulOp)\n\t\t\texpect(op.field).toBe('age')\n\t\t\texpect(op.value).toBe(3)\n\t\t})\n\t\ttest('min() creates MinOp from field ref', () => {\n\t\t\tconst op = min<typeof TestSchema>(TestSchema.fields.age, 10)\n\t\t\texpect(op).toBeInstanceOf(MinOp)\n\t\t\texpect(op.field).toBe('age')\n\t\t})\n\t\ttest('max() creates MaxOp from field ref', () => {\n\t\t\tconst op = max<typeof TestSchema>(TestSchema.fields.age, 99)\n\t\t\texpect(op).toBeInstanceOf(MaxOp)\n\t\t\texpect(op.field).toBe('age')\n\t\t})\n\t\ttest('unset() creates UnsetOp from field ref', () => {\n\t\t\tconst op = unset<typeof TestSchema>(TestSchema.fields.score)\n\t\t\texpect(op).toBeInstanceOf(UnsetOp)\n\t\t\texpect(op.field).toBe('score')\n\t\t})\n\t\ttest('push() creates PushOp from field ref', () => {\n\t\t\tconst op = push<typeof TestSchema>(TestSchema.fields.tags, 'new-tag')\n\t\t\texpect(op).toBeInstanceOf(PushOp)\n\t\t\texpect(op.field).toBe('tags')\n\t\t\texpect(op.value).toBe('new-tag')\n\t\t})\n\t\ttest('pull() creates PullOp from field ref', () => {\n\t\t\tconst op = pull<typeof TestSchema>(TestSchema.fields.tags, 'old-tag')\n\t\t\texpect(op).toBeInstanceOf(PullOp)\n\t\t\texpect(op.field).toBe('tags')\n\t\t})\n\t\ttest('patch() creates PatchOp from field ref', () => {\n\t\t\tconst op = patch<typeof TestSchema>(TestSchema.fields.meta, { a: 9 })\n\t\t\texpect(op).toBeInstanceOf(PatchOp)\n\t\t\texpect(op.field).toBe('meta')\n\t\t\texpect(op.value).toEqual({ a: 9 })\n\t\t})\n\t})\n\n\tdescribe('opTouchedFields', () => {\n\t\ttest('SetOp returns all keys from values', () => {\n\t\t\texpect(opTouchedFields(new SetOp({ name: 'A', age: 1 }))).toEqual(['name', 'age'])\n\t\t})\n\t\ttest('IncOp returns the field', () => {\n\t\t\texpect(opTouchedFields(new IncOp('age', 1))).toEqual(['age'])\n\t\t})\n\t\ttest('UnsetOp returns the field', () => {\n\t\t\texpect(opTouchedFields(new UnsetOp('score'))).toEqual(['score'])\n\t\t})\n\t})\n\n\tdescribe('type-level: field-category helpers', () => {\n\t\ttest('NumericFieldOf only matches numeric fields (excludes optional numeric)', () => {\n\t\t\ttype Numeric = NumericFieldOf<typeof TestSchema>\n\t\t\texpectTypeOf<Numeric['name']>().toEqualTypeOf<'age' | 'createdAt' | 'updatedAt'>()\n\t\t})\n\t\ttest('ComparableFieldOf matches numeric and string fields (excludes optional)', () => {\n\t\t\ttype Comparable = ComparableFieldOf<typeof TestSchema>\n\t\t\texpectTypeOf<Comparable['name']>().toEqualTypeOf<'id' | 'name' | 'age' | 'createdAt' | 'updatedAt'>()\n\t\t})\n\t\ttest('OptionalFieldOf only matches optional fields', () => {\n\t\t\ttype Optional = OptionalFieldOf<typeof TestSchema>\n\t\t\texpectTypeOf<Optional['name']>().toEqualTypeOf<'score'>()\n\t\t})\n\t\ttest('ArrayFieldOf only matches array fields', () => {\n\t\t\ttype Arr = ArrayFieldOf<typeof TestSchema>\n\t\t\texpectTypeOf<Arr['name']>().toEqualTypeOf<'tags'>()\n\t\t})\n\t\ttest('ObjectFieldOf only matches object fields (not arrays)', () => {\n\t\t\ttype Obj = ObjectFieldOf<typeof TestSchema>\n\t\t\texpectTypeOf<Obj['name']>().toEqualTypeOf<'meta'>()\n\t\t})\n\t})\n\n\tdescribe('type-level: inc on a string field is a TS error', () => {\n\t\ttest('inc rejects non-numeric fields', () => {\n\t\t\t// @ts-expect-error — name is a string field, not numeric\n\t\t\tinc<typeof TestSchema>(TestSchema.fields.name, 1)\n\t\t})\n\t})\n\n\tdescribe('type-level: push on a non-array field is a TS error', () => {\n\t\ttest('push rejects non-array fields', () => {\n\t\t\t// @ts-expect-error — name is a string field, not array\n\t\t\tpush<typeof TestSchema>(TestSchema.fields.name, 'val')\n\t\t})\n\t})\n\n\tdescribe('type-level: per-op gating via UpdateOp<S, A>', () => {\n\t\ttest('undeclared ops resolve to never', async () => {\n\t\t\tconst { OrmAdapter } = await import('./orm-adapter')\n\t\t\tclass LimitedAdapter 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', 'inc'] as const\n\t\t\t}\n\n\t\t\ttype Limited = UpdateOp<typeof TestSchema, LimitedAdapter>\n\t\t\texpectTypeOf<Limited>().toEqualTypeOf<SetOp | IncOp>()\n\t\t})\n\n\t\ttest('adapter with no updateOps resolves all variants to never', async () => {\n\t\t\tconst { OrmAdapter } = await import('./orm-adapter')\n\t\t\tclass NoOpsAdapter extends OrmAdapter {\n\t\t\t\treadonly schemaConfigPipe = v.object({})\n\t\t\t\treadonly supportedFieldTypes = ['string'] as const\n\t\t\t\treadonly updateOps = [] as const\n\t\t\t}\n\n\t\t\ttype NoOps = UpdateOp<typeof TestSchema, NoOpsAdapter>\n\t\t\texpectTypeOf<NoOps>().toBeNever()\n\t\t})\n\n\t\ttest('adapter with all updateOps includes all variants', async () => {\n\t\t\tconst { OrmAdapter } = await import('./orm-adapter')\n\t\t\tclass FullAdapter 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', 'inc', 'mul', 'min', 'max', 'unset', 'push', 'pull', 'patch'] as const\n\t\t\t}\n\n\t\t\ttype Full = UpdateOp<typeof TestSchema, FullAdapter>\n\t\t\ttype Expected =\n\t\t\t\t| SetOp\n\t\t\t\t| IncOp\n\t\t\t\t| MulOp\n\t\t\t\t| MinOp\n\t\t\t\t| MaxOp\n\t\t\t\t| UnsetOp\n\t\t\t\t| PushOp\n\t\t\t\t| PullOp\n\t\t\t\t| PatchOp\n\t\t\texpectTypeOf<Full>().toEqualTypeOf<Expected>()\n\t\t})\n\t})\n}\n"],"mappings":"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","names":["json_exports","__export","JsonAdapter","__toCommonJS","import_promises","import_valleyed","import_valleyed","configurable","pipeFn","base","pipe","Configurable","config","baseArgs","input","args","r","EquippedError","message","context","cause","import_pino","import_ulid","import_valleyed","registerHook","hooks","record","dep","h","depName","ownerName","tentative","detectCycle","classToAfter","existing","visited","stack","visit","cls","path","cycleStart","names","c","resolveHookDAG","invert","classToHooks","anonymous","list","classes","inDegree","graph","from","to","edges","layers","queue","layer","next","neighbor","d","anonWithDeps","anonWithoutDeps","maxLayer","depLayer","l","r","insertAt","runHooks","onError","error","import_valleyed","instanceSettingsPipe","Instance","_Instance","#id","#instance","#hooks","settings","pino","#registerOnExitHandler","id","EquippedError","name","key","runHooks","error","envsPipe","envValidity","settingsValidity","instanceSettingsPipe","event","cb","options","record","registerHook","signal","code","value","opts","import_valleyed","Field","name","path","toFieldName","field","Filter","field","op","value","toFieldName","FilterGroup","_FilterGroup","children","#withChild","child","facFns","EquippedError","group","fn","c","OrmAdapter","err","wrapped","EquippedError","Instance","self","schema","config","emptyIterator","use","filter","opts","d","match","pk","pkFilter","FilterGroup","create","ops","row","args","spec","import_node_async_hooks","import_valleyed","SetOp","values","IncOp","field","value","MulOp","MinOp","MaxOp","UnsetOp","PushOp","PullOp","PatchOp","inMemoryConnectionPipe","clone","value","replaceMap","target","source","k","val","isPlainObject","containsSubset","needle","item","existing","v","compareValues","a","b","sa","sb","resolveConfigName","_","config","evaluateFilter","doc","filter","fieldValue","evaluateChild","child","Filter","FilterGroup","c","matchesFilter","group","clause","applyOptions","rows","options","results","ord","cmp","fields","row","out","key","applyUpdateOp","current","IncOp","base","MulOp","MinOp","MaxOp","UnsetOp","PushOp","arr","PullOp","entry","PatchOp","applyUpdateData","data","updated","applyOps","ops","op","SetOp","sessionActiveStore","InMemoryAdapter","configurable","OrmAdapter","#migrationLock","#getStore","name","#resolveStore","schema","#snapshot","stores","store","storeCopy","id","migrations","indexes","tables","meta","foreignKeys","#restore","snap","pk","d","pkStr","batchSize","start","ids","next","create","result","spec","where","groups","f","groupRows","field","agg","vals","r","acc","sum","minVal","maxVal","having","m","appliedAt","change","table","indexName","fkName","schemas","idx","idxName","fk","fn","release","prev","error","jsonConnectionPipe","JsonAdapter","configurable","OrmAdapter","#inMemory","InMemoryAdapter","#writeQueue","config","#serialize","obj","migrations","tables","name","meta","indexes","foreignKeys","store","records","pk","doc","#atomicWrite","data","tmpPath","#persistToDisk","next","raw","m","fields","fieldName","fspec","schema","result","ops","group","options","filter","create","spec","fn","id","appliedAt","lockPath","deadline","err","r","change"]}