From 81c2de4a11a3923565ed7cf315bf72b10c299839 Mon Sep 17 00:00:00 2001 From: Andrew Zolotukhin Date: Wed, 30 Sep 2026 18:39:08 +0000 Subject: [PATCH 1/3] feat(knex-schema): add shape-preserving read predicates --- .changeset/schema-aware-read-predicates.md | 10 + docs/assets/schema-aware-read-predicates.png | Bin 0 -> 146640 bytes libs/knex-schema/README.md | 78 ++++ .../integration/read-graphs.test.ts | 17 + .../integration/read-predicates.test.ts | 376 +++++++++++++++++ libs/knex-schema/src/AliasedReadQuery.ts | 69 ++-- libs/knex-schema/src/SchemaReadQuery.ts | 83 ++-- libs/knex-schema/src/index.ts | 6 + libs/knex-schema/src/read-consumer.test.ts | 2 +- .../knex-schema/src/read-predicates.test-d.ts | 92 +++++ libs/knex-schema/src/read-predicates.test.ts | 293 +++++++++++++ libs/knex-schema/src/read-predicates.ts | 386 ++++++++++++++++++ libs/orm/README.md | 8 + websites/docs/app/knex-schema/page.tsx | 37 ++ websites/docs/public/llms.txt | 12 + 15 files changed, 1389 insertions(+), 80 deletions(-) create mode 100644 .changeset/schema-aware-read-predicates.md create mode 100644 docs/assets/schema-aware-read-predicates.png create mode 100644 libs/knex-schema/integration/read-predicates.test.ts create mode 100644 libs/knex-schema/src/read-predicates.test-d.ts create mode 100644 libs/knex-schema/src/read-predicates.test.ts create mode 100644 libs/knex-schema/src/read-predicates.ts diff --git a/.changeset/schema-aware-read-predicates.md b/.changeset/schema-aware-read-predicates.md new file mode 100644 index 00000000..c105dbec --- /dev/null +++ b/.changeset/schema-aware-read-predicates.md @@ -0,0 +1,10 @@ +--- +"@cleverbrush/knex-schema": minor +--- + +Add shape-preserving grouped AND/OR predicates, captured IN/EXISTS subqueries, +bound raw predicates and ordering, and typed SQL column references to ordinary +and aliased schema-aware readers. These capabilities also work in ordinary ORM +reads and nested relation customizers while retaining immutable query plans and +stable row-schema identity. Group callbacks are synchronous and predicate-only; +unrestricted raw query mutation remains unavailable. diff --git a/docs/assets/schema-aware-read-predicates.png b/docs/assets/schema-aware-read-predicates.png new file mode 100644 index 0000000000000000000000000000000000000000..c762f48c585adde7b73546c7b10af67040328371 GIT binary patch literal 146640 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

?qE*1ra0 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 literal 0 HcmV?d00001 diff --git a/libs/knex-schema/README.md b/libs/knex-schema/README.md index 0811875a..d0f086b9 100644 --- a/libs/knex-schema/README.md +++ b/libs/knex-schema/README.md @@ -666,6 +666,7 @@ import { knex } from './database.js'; const UserTable = object({ id: number().primaryKey(), name: string(), + tenantId: number().hasColumnName('tenant_id'), lastSeen: date().optional().hasColumnName('last_seen'), secret: string() }).hasTableName('users'); @@ -757,6 +758,83 @@ sum/average preserve exact text, and empty extrema/sums remain nullable. An expl Framework output schema replaces aggregate decoding and is parsed once; opaque parser objects without schema introspection are rejected in this mode. +### Filtering and ordering without changing the result schema + +Ordinary and aliased readers provide the following shape-preserving operations. +All return an independent reader with the **same `rowSchema` object**; retain the +returned reader when adding conditional filters. Filtering a nullable field does +not implicitly narrow its declared result type. + +| Operation | API | +| --- | --- | +| Comparisons and parenthesized groups | `where`, `andWhere`, `orWhere` | +| SQL null checks | `whereNull`, `whereNotNull`, `orWhereNull`, `orWhereNotNull` | +| Value-list or SELECT-subquery membership | `whereIn`, `whereNotIn`, `orWhereIn`, `orWhereNotIn` | +| SELECT-subquery existence | `whereExists`, `whereNotExists`, `orWhereExists`, `orWhereNotExists` | +| Bound custom predicates | `whereRaw(sql, bindings)`, `orWhereRaw(sql, bindings)` | +| Bound custom ordering | `orderByRaw(sql, bindings)` | +| Quoted mapped column reference | `ref(columnSelector)` | + +Reuse the prepared read from `user-read.ts` in a separate query-composition file: + +```ts +// user-search.ts +import { number, object, query, string } from '@cleverbrush/knex-schema'; +import { knex } from './database.js'; +import { userRead } from './user-read.js'; + +const UserLabel = object({ + userId: number().hasColumnName('user_id'), label: string() +}).hasTableName('user_labels'); + +export function searchUsers(tenantId: number, term: string, priorityUserId: number) { + const labeledUsers = query(knex, UserLabel) + .where(l => l.userId, userRead.ref(u => u.id)) + .where(l => l.label, term) + .select(l => l.userId).toKnexQuery(); + + return userRead.where(u => u.tenantId, tenantId) + .andWhere(group => group + .where(u => u.name, 'ilike', `%${term}%`) + .orWhereExists(labeledUsers)) + .orderByRaw('case when ?? = ? then 0 else 1 end', [ + userRead.ref(u => u.id), priorityUserId + ]) + .orderBy(u => u.name) + .orderBy(u => u.id); +} +``` + +The outer tenant filter applies to the **entire** search group. Group callbacks +are synchronous and run once when the predicate is attached, not during SQL +execution. Their scoped builder accumulates predicates, has no selection, join, +ordering, mutation, or execution methods, and is closed after the callback. +Return the provided group builder or nothing. An empty group adds no condition. +Async callbacks and returned thenables are rejected; thenables are not executed. + +`ref()` resolves mapped columns and generated aliases for ordinary readers, +explicit aliases for joined readers, and the correct child alias inside relation +customizers. Pass references as `??` identifier bindings or as Knex comparison +values when correlating a subquery. Values use `?` bindings. Raw SQL fragments +must be application-authored, not interpolated user input; this API is **not a SQL +sandbox**. Search escaping and application authorization remain caller policies. + +For membership, pass a value array or a single-column Knex SELECT subquery, for +example `read.whereIn(u => u.id, labelPage.toKnexQuery())`. Put ordering and limits +on that subquery to restrict IDs before joining/aggregating. EXISTS accepts a +Knex SELECT subquery; construct it separately instead of passing a Knex callback. +Subquery SQL and bindings are captured on attachment without database execution, +including nested subquery callbacks. Later changes to those builders do not +change the prepared reader. Empty IN lists match no rows; empty NOT IN lists +match all rows, with ordinary SQL null semantics for non-empty lists/subqueries. + +These operations also work in ordinary ORM reads and nested relation customizers. +For polymorphic reads, use them inside `forVariant()`; the union root does not +offer raw filtering or ordering. Numbered pagination retains raw ordering while +its count drops ordering/limits/offsets. `paginateAfter()` uses its explicit +complete `orderBy` specification, replacing prior ordering, including raw order. +No unrestricted `.apply()` or raw result-shape mutation is available in read mode. + ### Nested graphs Declare relations on entities as usual, and return the child query from customizers: diff --git a/libs/knex-schema/integration/read-graphs.test.ts b/libs/knex-schema/integration/read-graphs.test.ts index 7d6b4917..fac586db 100644 --- a/libs/knex-schema/integration/read-graphs.test.ts +++ b/libs/knex-schema/integration/read-graphs.test.ts @@ -136,6 +136,23 @@ afterAll(async () => { }); describe('schema-aware polymorphic graphs', () => { + it('allows schema-preserving predicates inside an explicit variant branch', async () => { + const read = db.assets + .withRowSchema() + .selectVariants(['photo']) + .forVariant('photo', photo => + photo + .where(p => p.where(a => a.id, 1).orWhere(a => a.id, 999)) + .whereRaw('?? > ?', [ + photo.ref(a => a.size), + '9007199254740992' + ]) + .orderByRaw('?? desc', [photo.ref(a => a.id)]) + ); + const rows = await read; + expect(rows.map(row => row.id)).toEqual([1]); + expect(read.rowSchema.validate(rows[0]).valid).toBe(true); + }); it('orders using native values even when branch projections omit sort fields', async () => { const read = db.assets .withRowSchema() diff --git a/libs/knex-schema/integration/read-predicates.test.ts b/libs/knex-schema/integration/read-predicates.test.ts new file mode 100644 index 00000000..ad2c439c --- /dev/null +++ b/libs/knex-schema/integration/read-predicates.test.ts @@ -0,0 +1,376 @@ +import { randomUUID } from 'node:crypto'; +import { mapper } from '@cleverbrush/mapper'; +import { + aggregate, + alias, + array, + createDb, + date, + defineEntity, + eq, + number, + object, + query, + string +} from '@cleverbrush/orm'; +import Knex from 'knex'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const connection = process.env.QUERY_TEST_DATABASE_URL; +if (!connection) throw new Error('QUERY_TEST_DATABASE_URL is required'); +const knex = Knex({ client: 'pg', connection }); +const prefix = `cb_pred_${randomUUID().replaceAll('-', '')}`; +const names = { + projects: `${prefix}_projects`, + tasks: `${prefix}_tasks`, + labels: `${prefix}_labels`, + links: `${prefix}_links` +}; +const Task = object({ + id: number().primaryKey(), + projectId: number().hasColumnName('project_id'), + title: string(), + kind: string(), + amount: number().decimal(24, 6), + done: date().optional() +}).hasTableName(names.tasks); +const Project = object({ + id: number().primaryKey(), + ownerId: number().hasColumnName('owner_id'), + name: string() +}).hasTableName(names.projects); +const Label = object({ + id: number().primaryKey(), + projectId: number().hasColumnName('project_id'), + name: string() +}).hasTableName(names.labels); +const Link = object({ + taskId: number().hasColumnName('task_id'), + labelId: number().hasColumnName('label_id') +}).hasTableName(names.links); +const ProjectEntity = defineEntity( + Project.addProp('tasks', array(Task).optional()) +).hasMany( + p => p.tasks, + p => p.id, + t => t.projectId +); +const timestamp = '2026-09-30T12:00:00.123456Z'; + +beforeAll(async () => { + await knex.schema.createTable(names.projects, t => { + t.integer('id').primary(); + t.integer('owner_id'); + t.text('name'); + }); + await knex.schema.createTable(names.tasks, t => { + t.integer('id').primary(); + t.integer('project_id'); + t.text('title'); + t.text('kind'); + t.decimal('amount', 24, 6); + t.timestamp('done', { useTz: true }); + }); + await knex.schema.createTable(names.labels, t => { + t.integer('id').primary(); + t.integer('project_id'); + t.text('name'); + }); + await knex.schema.createTable(names.links, t => { + t.integer('task_id'); + t.integer('label_id'); + }); + await knex(names.projects).insert([ + { id: 1, owner_id: 1, name: 'Alpha' }, + { id: 2, owner_id: 1, name: 'Beta' }, + { id: 3, owner_id: 2, name: 'Private' } + ]); + await knex(names.tasks).insert([ + { + id: 101, + project_id: 1, + title: 'literal 50%_!', + kind: 'task', + amount: '9007199254740993.000001', + done: timestamp + }, + { + id: 102, + project_id: 1, + title: 'label match', + kind: 'task', + amount: '12.340000', + done: null + }, + { + id: 103, + project_id: 2, + title: 'other project', + kind: 'task', + amount: '1.000000', + done: null + }, + { + id: 104, + project_id: 1, + title: 'milestone', + kind: 'milestone', + amount: '2.000000', + done: null + }, + { + id: 201, + project_id: 3, + title: 'literal 50%_!', + kind: 'task', + amount: '3.000000', + done: null + } + ]); + await knex(names.labels).insert([ + { id: 1, project_id: 1, name: 'Alpha' }, + { id: 2, project_id: 1, name: 'Beta' }, + { id: 3, project_id: 1, name: 'Unused' }, + { id: 4, project_id: 3, name: 'Private' } + ]); + await knex(names.links).insert([ + { task_id: 101, label_id: 1 }, + { task_id: 102, label_id: 1 }, + { task_id: 102, label_id: 2 }, + { task_id: 201, label_id: 2 } + ]); +}); +afterAll(async () => { + for (const name of [names.links, names.labels, names.tasks, names.projects]) + await knex.schema.dropTableIfExists(name); + await knex.destroy(); +}); + +describe('schema-aware read predicates against PostgreSQL', () => { + it('keeps outer access filters around grouped raw search and correlated EXISTS', async () => { + const base = query(knex, alias(Task, 'task')) + .withRowSchema() + .join(alias(Project, 'project'), t => + eq(t.task.projectId, t.project.id) + ); + const linked = query(knex, Link) + .where(l => l.labelId, 2) + .where( + l => l.taskId, + base.ref(t => t.task.id) + ) + .select(l => l.taskId) + .toKnexQuery(); + const filtered = base + .where(t => t.project.ownerId, 1) + .whereRaw('case when ?? = ? then ? else ? end = ?', [ + base.ref(t => t.task.kind), + 'milestone', + 'planned', + 'active', + 'active' + ]) + .andWhere(p => + p + .whereRaw("?? ilike ? escape '!'", [ + p.ref(t => t.task.title), + '%50!%!_!!%' + ]) + .orWhereExists(linked) + ); + const rowsQuery = filtered + .select(t => ({ + id: t.task.id, + amount: t.task.amount, + done: t.task.done + })) + .orderBy(t => t.task.id); + const countQuery = filtered.select(() => ({ + total: aggregate.count() + })); + const [rows, count] = await Promise.all([ + rowsQuery, + countQuery.first() + ]); + expect(rows.map(row => row.id)).toEqual([101, 102]); + expect(count?.total).toBe(2); + expect(rows[0].amount).toBe('9007199254740993.000001'); + expect(rows[0].done).toEqual(new Date(timestamp)); + expect(rows[1].done).toBeNull(); + for (const row of rows) + expect(rowsQuery.rowSchema.validate(row).valid).toBe(true); + expect(await rowsQuery.limit(1).offset(1)).toEqual([rows[1]]); + expect((await rowsQuery).length).toBe(2); + expect((await countQuery.first())?.total).toBe(2); + }); + + it('limits label IDs in a subquery before aggregating matching links', async () => { + const page = query(knex, Label) + .where(l => l.projectId, 1) + .orderBy(l => l.name) + .limit(1) + .offset(1) + .select(l => l.id) + .toKnexQuery(); + const read = query(knex, alias(Label, 'label')) + .withRowSchema() + .leftJoin(alias(Link, 'link'), t => eq(t.label.id, t.link.labelId)) + .whereIn(t => t.label.id, page) + .groupBy( + t => t.label.id, + t => t.label.name + ) + .select(t => ({ + id: t.label.id, + name: t.label.name, + total: aggregate.count(t.link.taskId) + })); + page.clear('limit').where('id', 99); + expect(await read).toEqual([{ id: 2, name: 'Beta', total: 2 }]); + expect(read.rowSchema.validate((await read)[0]).valid).toBe(true); + }); + + it('uses raw conditional ordering with independent ordinary numbered and cursor pages', async () => { + const source = query(knex, Project) + .withRowSchema() + .where(p => p.ownerId, 1) + .select(p => ({ id: p.id, name: p.name })); + const priority = source + .orderByRaw('case when ?? = ? then 0 else 1 end', [ + source.ref(p => p.id), + 2 + ]) + .orderBy(p => p.name); + const first = await priority.paginate({ page: 1, pageSize: 1 }); + const second = await priority.paginate({ page: 2, pageSize: 1 }); + expect(first.data.map(p => p.id)).toEqual([2]); + expect(second.data.map(p => p.id)).toEqual([1]); + expect(first.total).toBe(2); + expect(second.total).toBe(2); + expect(priority.rowSchema).toBe(source.rowSchema); + expect(source.toQuery()).not.toContain('order by'); + const cursor = await priority.paginateAfter({ + limit: 1, + orderBy: [{ column: p => p.id, direction: 'asc' }] + }); + expect(cursor.data.map(p => p.id)).toEqual([1]); + const next = await priority.paginateAfter({ + limit: 1, + cursor: cursor.nextCursor, + orderBy: [{ column: p => p.id, direction: 'asc' }] + }); + expect(next.data.map(p => p.id)).toEqual([2]); + }); + + it('orders aliased reads with bound CASE expressions and retains nullable left joins', async () => { + const source = query(knex, alias(Project, 'project')) + .withRowSchema() + .leftJoin(alias(Task, 'task'), t => + eq(t.project.id, t.task.projectId) + ) + .where(t => t.project.ownerId, 1) + .select(t => ({ id: t.project.id, taskId: t.task.id })); + const result = source + .orderByRaw('case when ?? = ? then 0 else 1 end', [ + source.ref(t => t.project.id), + 2 + ]) + .orderBy(t => t.task.id); + expect((await result)[0]).toEqual({ id: 2, taskId: 103 }); + expect(result.rowSchema).toBe(source.rowSchema); + const empty = query(knex, alias(Project, 'project')) + .withRowSchema() + .leftJoin(alias(Label, 'label'), t => + eq(t.project.id, t.label.projectId) + ) + .where(t => t.project.id, 2) + .select(t => ({ id: t.project.id, label: t.label.name })); + expect(await empty).toEqual([{ id: 2, label: null }]); + expect(empty.rowSchema.validate({ id: 2, label: null }).valid).toBe( + true + ); + }); + + it('filters nested ORM reads, stays detached, and reuses the prepared mapper', async () => { + const db = createDb( + knex, + { projects: ProjectEntity }, + { tracking: true } + ); + const source = db.projects + .withRowSchema() + .select(p => ({ id: p.id })) + .include( + p => p.tasks, + tasks => + tasks + .where(p => + p + .where(t => t.kind, 'task') + .orWhere(t => t.kind, 'note') + ) + .orderByRaw('?? desc', [tasks.ref(t => t.id)]) + .limit(1) + .select(t => ({ + title: t.title, + amount: t.amount, + done: t.done + })) + ); + const Target = object({ + id: number(), + tasks: array( + object({ + title: string(), + amount: string(), + done: date().nullable() + }) + ) + }); + const map = mapper() + .configure(source.rowSchema, Target, m => m) + .getSyncMapper(source.rowSchema, Target); + const read = source.where(p => p.id, 1); + expect(read.rowSchema).toBe(source.rowSchema); + const [row] = await read; + expect(map(row)).toEqual({ + id: 1, + tasks: [{ title: 'label match', amount: '12.340000', done: null }] + }); + row.tasks[0].title = 'detached mutation'; + await db.saveChanges(); + expect((await knex(names.tasks).where('id', 102).first()).title).toBe( + 'label match' + ); + }); + + it('preserves captured predicates in transaction clones without touching the source', async () => { + const read = query(knex, Task) + .withRowSchema() + .where(t => t.projectId, 1); + const noLabels = knex(names.links) + .select('task_id') + .where( + 'task_id', + read.ref(t => t.id) + ); + const filtered = read + .whereNotExists(noLabels) + .select(t => ({ id: t.id })); + await knex.transaction(async trx => { + await trx(names.tasks).insert({ + id: 105, + project_id: 1, + title: 'transaction', + kind: 'task', + amount: '1', + done: null + }); + const transactional = filtered.transacting(trx).orderBy(t => t.id); + expect(transactional.rowSchema).toBe(filtered.rowSchema); + expect((await transactional).map(t => t.id)).toEqual([104, 105]); + await trx.rollback(); + }); + expect((await filtered).map(t => t.id)).toEqual([104]); + }); +}); diff --git a/libs/knex-schema/src/AliasedReadQuery.ts b/libs/knex-schema/src/AliasedReadQuery.ts index 8b34cb1f..dd60da2b 100644 --- a/libs/knex-schema/src/AliasedReadQuery.ts +++ b/libs/knex-schema/src/AliasedReadQuery.ts @@ -11,6 +11,12 @@ import { type AliasedColumn, COLUMN } from './expressions.js'; +import { + captureReadRaw, + type ReadPredicate, + type ReadPredicateContext, + ReadPredicates +} from './read-predicates.js'; import { compileReadProjection, type ReadField } from './read-projection.js'; import { compileReadSchema, @@ -36,11 +42,17 @@ type Selection = Record | AggregateExpression>; type Selector = (tables: ReadAliasTables) => AliasedColumn; /** Immutable flat joined read. Supply select() before accessing rowSchema or executing. */ -export class AliasedReadQuery { +export class AliasedReadQuery< + T, + Row extends ReadObject = never +> extends ReadPredicates> { private fields?: Record; private schema?: Row; + private predicates: readonly ReadPredicate[] = []; /** @internal Enter through an aliased query's withRowSchema() method. */ - constructor(private planner: AliasedQueryBuilder) {} + constructor(private planner: AliasedQueryBuilder) { + super(); + } /** Exact structural schema, stable across operations that do not change selection. */ get rowSchema(): Row { @@ -113,33 +125,27 @@ export class AliasedReadQuery { ) as unknown as Row; return copy as any; } - /** Add a bound equality predicate. */ - where(column: Selector, value: unknown): this; - /** Add a bound predicate with an explicit supported operator. */ - where(column: Selector, operator: string, value: unknown): this; - /** Return a filtered clone without changing metadata identity. */ - where(column: Selector, ...args: [unknown] | [string, unknown]): this { - const copy = this.copy(); - if (args.length === 1) copy.planner.where(column as any, args[0]); - else copy.planner.where(column as any, args[0], args[1]); - return copy; - } - /** Match SQL null, including absent outer-joined rows. */ - whereNull(column: Selector): this { - const copy = this.copy(); - copy.planner.whereNull(column as any); - return copy; - } - /** Exclude SQL null without silently narrowing result types. */ - whereNotNull(column: Selector): this { - const copy = this.copy(); - copy.planner.whereNotNull(column as any); - return copy; + protected readPredicateContext(): ReadPredicateContext> { + const { knex, columns } = this.planner.readContext(); + const entries = Object.values( + columns as Record>> + ).flatMap(table => Object.values(table)); + return { + knex, + column: selector => { + const column = selector(columns as ReadAliasTables); + if (!entries.includes(column)) + throw new ReadSchemaError( + 'Predicate column does not belong to this query' + ); + const info = column[COLUMN]; + return `${info.alias}.${info.column}`; + } + }; } - /** Match a bound value list. */ - whereIn(column: Selector, values: readonly unknown[]): this { + protected addReadPredicate(predicate: ReadPredicate): this { const copy = this.copy(); - copy.planner.whereIn(column as any, values); + copy.predicates = [...this.predicates, predicate]; return copy; } /** Order by native database values before decoding. */ @@ -148,6 +154,14 @@ export class AliasedReadQuery { copy.planner.orderBy(column as any, direction); return copy; } + /** Append trusted raw ordering with captured bindings; ref() quotes mapped aliased columns. */ + orderByRaw(sql: string, bindings: readonly Knex.RawBinding[] = []): this { + const { knex } = this.planner.readContext(); + const captured = captureReadRaw(knex, sql, bindings)().toSQL(); + const copy = this.copy(); + copy.planner.orderByRaw(captured.sql, captured.bindings); + return copy; + } /** Group native columns for an aggregate projection. */ groupBy(...columns: Selector[]): this { const copy = this.copy(); @@ -192,6 +206,7 @@ export class AliasedReadQuery { compile(): Knex.QueryBuilder { void this.rowSchema; const { sql, knex } = this.planner.readContext(); + for (const predicate of this.predicates) predicate(sql); return sql .clearSelect() .select( diff --git a/libs/knex-schema/src/SchemaReadQuery.ts b/libs/knex-schema/src/SchemaReadQuery.ts index c980565b..ddaa13b6 100644 --- a/libs/knex-schema/src/SchemaReadQuery.ts +++ b/libs/knex-schema/src/SchemaReadQuery.ts @@ -26,13 +26,19 @@ import { compositeCursor } from './operations/composite-cursor.js'; import { - ALLOWED_OPS, getEffectiveBaseQuery, getSchemaQueryBuilderCtor } from './operations/helpers.js'; import { getState } from './operations/state.js'; import { PolymorphicReadQuery } from './PolymorphicReadQuery.js'; import type { ReadRelations, ReadVariantMetadata } from './read-entity.js'; +import { + captureReadRaw, + type ReadPredicate, + type ReadPredicateContext, + type ReadPredicateSelector, + ReadPredicates +} from './read-predicates.js'; import { compileReadProjection, type ReadField } from './read-projection.js'; import { type ColumnReadSchema, @@ -156,7 +162,7 @@ export class SchemaReadQuery< S extends ReadObject, Row extends ReadObject = ObjectReadSchema>, Relations extends Record = ReadRelations -> { +> extends ReadPredicates> { /** @internal Nominal identity for typed child-query customizers. */ declare readonly [READ_QUERY]: true; private readonly alias = `__schema_read_${readAliasSequence++}`; @@ -175,6 +181,7 @@ export class SchemaReadQuery< private readonly source: S, base: Knex.QueryBuilder ) { + super(); this.base = knex.queryBuilder().from(base.clone().as(this.alias)); const relations = (source.introspect().extensions?.relations ?? []) as RelationSpec[]; @@ -233,16 +240,19 @@ export class SchemaReadQuery< } private column( - selector: Selector> + selector: ReadPredicateSelector> ): ReadColumn { const column = selector( this.columns as ReadColumns ); - if (!column || !Object.values(this.columns).includes(column)) + if ( + !column || + !Object.values(this.columns).some(candidate => candidate === column) + ) throw new ReadSchemaError( 'Column does not belong to this read query' ); - return column; + return column as ReadColumn; } private name(column: ReadColumn): string { @@ -328,55 +338,17 @@ export class SchemaReadQuery< ) as any; } - /** Add a bound comparison, returning an independent query with the same row schema. */ - where( - column: Selector>, - value: unknown - ): this; - /** Add a bound comparison using a supported SQL operator. */ - where( - column: Selector>, - operator: string, - value: unknown - ): this; - /** Add a bound comparison using a supported SQL operator. */ - where( - column: Selector>, - ...args: [unknown] | [string, unknown] - ): this { - const operator = args.length === 1 ? '=' : args[0].toLowerCase(); - if (!ALLOWED_OPS.has(operator)) - throw new ReadSchemaError( - `Unsupported comparison operator: ${operator}` - ); - const copy = this.copy(); - copy.base.where( - this.name(this.column(column)), - operator, - (args.length === 1 ? args[0] : args[1]) as any - ); - return copy; - } - - /** Filter SQL null values without changing the declared read shape. */ - whereNull(column: Selector>): this { - const copy = this.copy(); - copy.base.whereNull(this.name(this.column(column))); - return copy; - } - /** Exclude SQL null values without implicitly narrowing schema nullability. */ - whereNotNull(column: Selector>): this { - const copy = this.copy(); - copy.base.whereNotNull(this.name(this.column(column))); - return copy; + protected readPredicateContext(): ReadPredicateContext< + ReadColumns + > { + return { + knex: this.knex, + column: selector => this.name(this.column(selector)) + }; } - /** Filter against a bound list of values. An empty list produces no rows. */ - whereIn( - column: Selector>, - values: readonly unknown[] - ): this { + protected addReadPredicate(predicate: ReadPredicate): this { const copy = this.copy(); - copy.base.whereIn(this.name(this.column(column)), [...values] as any[]); + predicate(copy.base); return copy; } /** Order parent rows independently of any child relation's ordering. */ @@ -388,6 +360,13 @@ export class SchemaReadQuery< copy.base.orderBy(this.name(this.column(column)), direction); return copy; } + /** Append trusted raw ordering with captured positional bindings; ref() supplies quoted columns. */ + orderByRaw(sql: string, bindings: readonly Knex.RawBinding[] = []): this { + const captured = captureReadRaw(this.knex, sql, bindings); + const copy = this.copy(); + copy.base.orderByRaw(captured()); + return copy; + } /** Group rows before typed aggregate projection. */ groupBy(...columns: Selector>[]): this { const copy = this.copy(); diff --git a/libs/knex-schema/src/index.ts b/libs/knex-schema/src/index.ts index 90904f0b..90d301f6 100644 --- a/libs/knex-schema/src/index.ts +++ b/libs/knex-schema/src/index.ts @@ -110,6 +110,12 @@ export type { WithReadVariant } from './read-entity.js'; export { READ_ENTITY } from './read-entity.js'; +export type { + ReadMembership, + ReadPredicateBuilder, + ReadPredicateGroup, + ReadPredicateSelector +} from './read-predicates.js'; export type { ColumnReadSchema, ObjectReadSchema, diff --git a/libs/knex-schema/src/read-consumer.test.ts b/libs/knex-schema/src/read-consumer.test.ts index 5cfb3d9f..d0cc1587 100644 --- a/libs/knex-schema/src/read-consumer.test.ts +++ b/libs/knex-schema/src/read-consumer.test.ts @@ -42,7 +42,7 @@ test('documentation and multi-file metadata consumers compile against published ) ]; expect(namedBlocks(llms)).toHaveLength(4); - expect(namedBlocks(queryDocs)).toHaveLength(3); + expect(namedBlocks(queryDocs)).toHaveLength(4); for (const [, name, content] of [ ...namedBlocks(llms), ...namedBlocks(queryDocs) diff --git a/libs/knex-schema/src/read-predicates.test-d.ts b/libs/knex-schema/src/read-predicates.test-d.ts new file mode 100644 index 00000000..c3fcec0c --- /dev/null +++ b/libs/knex-schema/src/read-predicates.test-d.ts @@ -0,0 +1,92 @@ +import type { InferType } from '@cleverbrush/schema'; +import Knex from 'knex'; +import { expectTypeOf, test } from 'vitest'; +import { alias, eq } from './aliased-query.js'; +import { number, object, string } from './extension.js'; +import type { ReadPredicateBuilder } from './read-predicates.js'; +import { query } from './SchemaQueryBuilder.js'; + +const knex = Knex({ client: 'pg' }); +const Item = object({ + id: number().primaryKey(), + name: string(), + amount: number().decimal(18, 2).optional() +}).hasTableName('items'); + +test('ordinary reader predicates retain projection types and contextual groups', async () => { + const read = query(knex, Item) + .withRowSchema() + .select(t => ({ name: t.name, amount: t.amount })); + const filtered = read + .where(p => p.where(t => t.id, 1).orWhere(t => t.name, 'two')) + .whereIn(t => t.id, knex('links').select('item_id')) + .whereExists( + knex('links') + .select('item_id') + .where( + 'item_id', + read.ref(t => t.id) + ) + ) + .whereRaw('?? = ?', [read.ref(t => t.name), 'name']) + .orderByRaw('?? asc', [read.ref(t => t.name)]); + expectTypeOf(filtered.rowSchema).toEqualTypeOf(read.rowSchema); + expectTypeOf(await filtered).toEqualTypeOf< + { name: string; amount: string | null }[] + >(); + // @ts-expect-error only projected fields are available on rows + const _id: number = (await filtered)[0].id; + // @ts-expect-error unknown selector properties are rejected + read.where(p => p.where(t => t.missing, 1)); + // @ts-expect-error refs use the same typed column context + read.ref(t => t.missing); + // @ts-expect-error grouped callbacks cannot be async + read.where(async p => { + p.where(t => t.id, 1); + }); + read.where(p => { + expectTypeOf(p).toExtend>(); + // @ts-expect-error groups cannot shape rows + p.select(t => t.id); + // @ts-expect-error groups cannot order rows + p.orderByRaw('id'); + // @ts-expect-error groups cannot execute SQL + p.execute(); + // @ts-expect-error group lifecycle is owned by the reader + p.finish(); + }); + // @ts-expect-error unrestricted mutation is still unavailable + read.apply(q => q.select('*')); +}); + +test('aliased selectors preserve joined nullability and exact storage types', async () => { + const read = query(knex, alias(Item, 'item')) + .withRowSchema() + .leftJoin(alias(Item, 'parent'), t => eq(t.item.id, t.parent.id)) + .select(t => ({ + id: t.item.id, + parentName: t.parent.name, + amount: t.parent.amount + })); + const filtered = read + .where(t => t.item.id, 1) + .andWhere(p => + p + .whereNull(t => t.parent.name) + .orWhere(n => n.where(t => t.parent.id, 2)) + ) + .orWhereNotExists(knex('links').select('item_id')) + .orderByRaw('?? asc', [read.ref(t => t.item.id)]); + expectTypeOf>().toEqualTypeOf<{ + id: number; + parentName: string | null; + amount: string | null; + }>(); + expectTypeOf(await filtered).toEqualTypeOf< + InferType[] + >(); + // @ts-expect-error unknown aliases are rejected inside groups + filtered.where(p => p.where(t => t.unknown.id, 1)); + // @ts-expect-error async group callbacks are rejected on aliased readers too + filtered.orWhere(async p => p.where(t => t.item.id, 1)); +}); diff --git a/libs/knex-schema/src/read-predicates.test.ts b/libs/knex-schema/src/read-predicates.test.ts new file mode 100644 index 00000000..6b00a8d8 --- /dev/null +++ b/libs/knex-schema/src/read-predicates.test.ts @@ -0,0 +1,293 @@ +import Knex, { type Knex as KnexTypes } from 'knex'; +import { describe, expect, it, vi } from 'vitest'; +import { alias, eq } from './aliased-query.js'; +import type { AliasedColumn } from './expressions.js'; +import { date, number, object, string } from './extension.js'; +import type { + ReadPredicateBuilder, + ReadPredicateGroup +} from './read-predicates.js'; +import { query } from './SchemaQueryBuilder.js'; + +const Task = object({ + id: number().primaryKey(), + projectId: number().hasColumnName('project_id'), + title: string(), + amount: number().decimal(24, 6).optional(), + completedAt: date().optional().hasColumnName('completed_at') +}).hasTableName('tasks'); +const knex = Knex({ client: 'pg' }); +const readTask = () => query(knex, Task).withRowSchema(); + +describe('shape-preserving read predicates', () => { + it('groups AND/OR conditions without mutating the source or its schema', () => { + const source = readTask().select(t => ({ id: t.id, amount: t.amount })); + const filtered = source + .where(t => t.projectId, 1) + .andWhere(p => + p + .where(t => t.title, 'one') + .orWhere(n => + n + .where(t => t.id, '>', 2) + .whereNotNull(t => t.completedAt) + ) + ); + expect(filtered.rowSchema).toBe(source.rowSchema); + expect(filtered.toQuery()).toMatch( + /"project_id" = 1 and \(.*"title" = 'one' or \(.*"id" > 2 and .*"completed_at" is not null\)\)/ + ); + expect(source.toQuery()).not.toContain(' where '); + expect(source.where(t => t.projectId, 2).toQuery()).not.toContain( + "'one'" + ); + expect(Object.keys(filtered.rowSchema.introspect().properties)).toEqual( + ['id', 'amount'] + ); + }); + + it('supports comparisons, null variants and empty membership lists', () => { + const read = readTask(); + expect(read.where(t => t.completedAt, null).toQuery()).toContain( + 'is null' + ); + expect( + read.where(t => t.completedAt, 'is not', null).toQuery() + ).toContain('is not null'); + expect(read.whereIn(t => t.id, []).toQuery()).toContain('1 = 0'); + expect(read.whereNotIn(t => t.id, []).toQuery()).toContain('1 = 1'); + const sql = read + .where(t => t.id, 1) + .orWhereIn(t => t.id, [2, 3]) + .orWhereNotIn(t => t.id, [4]) + .orWhereNull(t => t.completedAt) + .orWhereNotNull(t => t.amount) + .toQuery(); + expect(sql).toContain('in (2, 3)'); + expect(sql).toContain('not in (4)'); + expect(sql).toContain('or '); + expect(() => read.where(t => t.id, 'unsafe operator', 1)).toThrow( + /Unsupported comparison operator/ + ); + }); + + it('captures value lists, dates and raw bindings before external mutation', () => { + const read = readTask(); + const ids = [1, 2]; + const time = new Date('2026-01-01T00:00:00Z'); + const bindings: KnexTypes.RawBinding[] = [ + read.ref(t => t.title), + 'old' + ]; + const filtered = read + .whereIn(t => t.id, ids) + .where(t => t.completedAt, '>=', time) + .whereRaw('lower(??) = ?', bindings) + .orderByRaw('case when ?? = ? then 0 else 1 end', bindings); + const before = filtered.toQuery(); + ids.push(3); + time.setUTCFullYear(2030); + bindings[1] = 'new'; + expect(filtered.toQuery()).toBe(before); + expect(filtered.rowSchema).toBe(read.rowSchema); + const compiled = filtered.compile().toSQL(); + expect(compiled.sql).not.toContain("'old'"); + expect(compiled.bindings.filter(x => x === 'old')).toHaveLength(2); + }); + + it('quotes references for mapped columns and rejects foreign descriptors', () => { + const read = readTask(); + expect(read.ref(t => t.projectId).toSQL().sql).toMatch( + /^"__schema_read_\d+"\."project_id"$/ + ); + let other!: AliasedColumn; + readTask().ref(t => { + other = t.id; + return t.id; + }); + expect(() => read.ref(() => other)).toThrow(/does not belong/); + expect(() => read.ref(() => undefined as any)).toThrow( + /does not belong/ + ); + }); + + it('snapshots nested subquery callbacks once without executing SQL', () => { + const event = vi.fn(); + knex.on('query', event); + try { + const source = readTask(); + const inner = knex('links').select('task_id').where('label', 'old'); + const callback = vi.fn((q: KnexTypes.QueryBuilder) => { + q.whereIn('task_id', inner); + }); + const subquery = knex('labels').select('task_id').where(callback); + const filtered = source.whereIn(t => t.id, subquery); + expect(callback).toHaveBeenCalledTimes(1); + const before = filtered.toQuery(); + inner.where('label', 'changed'); + subquery.where('id', 99); + expect(filtered.toQuery()).toBe(before); + expect(callback).toHaveBeenCalledTimes(1); + expect(filtered.rowSchema).toBe(source.rowSchema); + expect(event).not.toHaveBeenCalled(); + } finally { + knex.removeListener('query', event); + } + }); + + it('supports correlated EXISTS and all OR/negative subquery forms', () => { + const read = readTask(); + const linked = knex('links') + .select('task_id') + .where( + 'task_id', + read.ref(t => t.id) + ); + const sql = read + .whereExists(linked) + .whereNotExists(linked) + .orWhereExists(linked) + .orWhereNotExists(linked) + .whereNotIn(t => t.id, linked) + .orWhereIn(t => t.id, linked) + .orWhereNotIn(t => t.id, linked) + .toQuery(); + expect(sql).toContain('exists (select'); + expect(sql).toContain('not exists (select'); + expect(sql).toContain('or exists (select'); + expect(sql).toContain('or not exists (select'); + expect(sql).toContain('not in (select'); + expect(sql).toContain('or '); + expect(sql).toMatch(/"task_id" = "__schema_read_\d+"\."id"/); + expect(() => read.whereExists(knex('links').delete())).toThrow( + /SELECT subquery/ + ); + expect(() => + read.whereIn(t => t.id, knex('links').update({ label: 'bad' })) + ).toThrow(/SELECT subquery/); + }); + + it('supports grouped raw predicates and escaped question-mark operators', () => { + const read = readTask(); + const value = "' OR 1=1 --"; + const filtered = read.where(p => + p + .whereRaw('?? = ?', [p.ref(t => t.title), value]) + .orWhereRaw('?::jsonb \\? ?', ['{"key":1}', 'key']) + ); + const compiled = filtered.compile().toSQL(); + expect(compiled.sql).not.toContain(value); + expect(compiled.bindings).toEqual([value, '{"key":1}', 'key']); + expect(compiled.toNative().sql).toContain('::jsonb ?'); + }); + + it('runs group callbacks once and seals retained group builders', () => { + const read = readTask(); + let retained!: ReadPredicateBuilder; + const callback = vi.fn((p: ReadPredicateBuilder) => { + retained = p; + p.where(t => t.id, 1); + for (const method of [ + 'select', + 'join', + 'orderBy', + 'orderByRaw', + 'apply', + 'finish', + 'execute', + 'then' + ]) + expect(method in p).toBe(false); + }); + const filtered = read.where(callback); + filtered.toQuery(); + filtered.toQuery(); + expect(callback).toHaveBeenCalledTimes(1); + expect(() => retained.where(t => t.id, 2)).toThrow(/already closed/); + expect(filtered.toQuery()).toContain('= 1'); + expect(read.where(() => {}).toQuery()).toBe(read.toQuery()); + }); + + it('rejects async or throwing groups without changing their parent', async () => { + const read = readTask(); + const asyncGroup = async (p: ReadPredicateBuilder) => { + await Promise.resolve(); + p.where(t => t.id, 1); + }; + expect(() => + read.where(asyncGroup as unknown as ReadPredicateGroup) + ).toThrow(/must be synchronous/); + expect(() => + read.where(p => { + p.where(t => t.id, 1); + throw new Error('stop'); + }) + ).toThrow('stop'); + await Promise.resolve(); + expect(read.toQuery()).not.toContain(' where '); + expect(() => read.where(p => (p as any).select('id'))).toThrow(); + }); + + it('rejects returned thenables without invoking them or executing a returned query', () => { + const read = readTask(); + const then = vi.fn(); + expect(() => read.where((() => ({ then })) as any)).toThrow( + /must be synchronous/ + ); + expect(then).not.toHaveBeenCalled(); + const foreign = knex('tasks').select('id'); + const execute = vi.spyOn(foreign, 'then'); + expect(() => read.where((() => foreign) as any)).toThrow( + /must be synchronous/ + ); + expect(execute).not.toHaveBeenCalled(); + }); + + it('applies the same predicates and binding snapshots to aliased joins', () => { + const base = query(knex, alias(Task, 'task')) + .withRowSchema() + .leftJoin(alias(Task, 'other'), t => + eq(t.task.id, t.other.projectId) + ); + const source = base.select(t => ({ + id: t.task.id, + otherId: t.other.id, + amount: t.other.amount + })); + const subquery = knex('links') + .select('task_id') + .where( + 'task_id', + source.ref(t => t.task.id) + ); + const bindings: KnexTypes.RawBinding[] = [ + source.ref(t => t.task.projectId), + 1 + ]; + const filtered = source + .where(t => t.task.projectId, 1) + .andWhere(p => + p.where(t => t.task.title, 'text').orWhereExists(subquery) + ) + .whereIn(t => t.task.id, subquery) + .orderByRaw('case when ?? = ? then 0 else 1 end', bindings); + const before = filtered.toQuery(); + bindings[1] = 2; + subquery.where('label', 'late'); + expect(filtered.toQuery()).toBe(before); + expect(before).toContain('"task"."project_id" = 1'); + expect(before).toContain('or exists (select'); + expect(filtered.rowSchema).toBe(source.rowSchema); + expect( + source.rowSchema.validate({ id: 1, otherId: null, amount: null }) + .valid + ).toBe(true); + expect(source.toQuery()).not.toContain(' where '); + expect('apply' in source).toBe(false); + expect(() => + query(knex, alias(Task, 'task')) + .apply(q => q.where('id', 1)) + .withRowSchema() + ).toThrow(/before select\/apply/); + }); +}); diff --git a/libs/knex-schema/src/read-predicates.ts b/libs/knex-schema/src/read-predicates.ts new file mode 100644 index 00000000..9c907720 --- /dev/null +++ b/libs/knex-schema/src/read-predicates.ts @@ -0,0 +1,386 @@ +import type { Knex } from 'knex'; +import type { AliasedColumn } from './expressions.js'; +import { ALLOWED_OPS } from './operations/helpers.js'; +import { ReadSchemaError } from './read-schema.js'; + +const finishGroup = Symbol('finishReadPredicateGroup'); + +/** Select a column from the current reader's ordinary or aliased table context. */ +export type ReadPredicateSelector = (columns: C) => AliasedColumn; + +/** A synchronous, parenthesized predicate group. The callback cannot shape or execute a query. */ +export type ReadPredicateGroup = ( + predicates: ReadPredicateBuilder + // biome-ignore lint/suspicious/noConfusingVoidType: Accept existing void-returning callbacks while rejecting async callbacks via the explicit return union. +) => ReadPredicateBuilder | void; + +/** A bound value list or a caller-built SELECT subquery; captured without executing it. */ +export type ReadMembership = readonly unknown[] | Knex.QueryBuilder; + +/** @internal Predicate application contains only library-owned, already captured operations. */ +export type ReadPredicate = (query: Knex.QueryBuilder) => void; + +/** @internal Resolve references without exposing the parent's mutable SQL builder. */ +export interface ReadPredicateContext { + knex: Knex; + column: (selector: ReadPredicateSelector) => string; +} + +/** @internal Snapshot common mutable binding values independently of query builders. */ +function copyValue(value: any): any { + if (value instanceof Date) return new Date(value.getTime()); + if (Buffer.isBuffer(value)) return Buffer.from(value); + if (Array.isArray(value)) return value.map(copyValue); + if (value && typeof value === 'object') { + const prototype = Object.getPrototypeOf(value); + if (prototype === Object.prototype || prototype === null) { + return Object.fromEntries( + Object.entries(value).map(([key, item]) => [ + key, + copyValue(item) + ]) + ); + } + } + return value; +} + +/** @internal Compiled SQL is recreated per use, so externally owned builders are never retained. */ +function captureSql( + knex: Knex, + source: Knex.Raw | Knex.QueryBuilder +): () => Knex.Raw { + const compiled = source.toSQL(); + if (Array.isArray(compiled)) + throw new ReadSchemaError( + 'Read predicates require a single SQL expression' + ); + const sql = compiled.sql; + const bindings = compiled.bindings?.map(copyValue) ?? []; + return () => knex.raw(sql, bindings.map(copyValue)); +} + +/** @internal Capture trusted SQL and positional bindings now, including refs and nested raw expressions. */ +export function captureReadRaw( + knex: Knex, + sql: string, + bindings: readonly Knex.RawBinding[] = [] +): () => Knex.Raw { + return captureSql(knex, knex.raw(sql, [...bindings])); +} + +function captureSubquery(knex: Knex, query: Knex.QueryBuilder): () => Knex.Raw { + if ( + !query || + typeof query.toSQL !== 'function' || + typeof query.clone !== 'function' + ) + throw new ReadSchemaError('Expected a Knex SELECT subquery'); + const compiled = query.clone().toSQL(); + if ( + Array.isArray(compiled) || + !['select', 'first'].includes(compiled.method) + ) + throw new ReadSchemaError('Read predicates require a SELECT subquery'); + // Rewrap the compiled statement, not the original builder or its callbacks. + return captureSql( + knex, + knex.raw(compiled.sql, [...(compiled.bindings ?? [])]) + ); +} + +function captureValue(knex: Knex, value: any): () => any { + if (value && typeof value.toSQL === 'function') { + return typeof value.clone === 'function' + ? captureSubquery(knex, value) + : captureSql(knex, value); + } + if (typeof value === 'function') + throw new ReadSchemaError('Predicate values cannot be callbacks'); + const captured = copyValue(value); + return () => copyValue(captured); +} + +/** + * Shared shape-preserving predicate methods for immutable readers and scoped groups. + * @internal Consumers obtain these methods through withRowSchema(), not inheritance. + */ +export abstract class ReadPredicates { + protected abstract readPredicateContext(): ReadPredicateContext; + protected abstract addReadPredicate(predicate: ReadPredicate): this; + + /** + * Quote a schema-backed column for raw bindings or correlated subqueries. + * The reference uses this reader's actual SQL alias and never executes SQL. + * @example read.whereRaw('lower(??) = ?', [read.ref(t => t.name), 'alice']) + */ + ref(selector: ReadPredicateSelector): Knex.Ref { + const { knex, column } = this.readPredicateContext(); + return knex.ref(column(selector)); + } + + /** Add a parenthesized AND group using a synchronous predicate-only callback. */ + where(group: ReadPredicateGroup): this; + /** Add a bound equality comparison. Null uses SQL IS NULL. */ + where(column: ReadPredicateSelector, value: unknown): this; + /** Add a bound comparison using a supported SQL operator. */ + where( + column: ReadPredicateSelector, + operator: string, + value: unknown + ): this; + where( + first: ReadPredicateSelector | ReadPredicateGroup, + ...args: [] | [unknown] | [string, unknown] + ): this { + return this.comparison('and', first, args); + } + + /** Explicit AND spelling of where(), including nested groups. */ + andWhere(group: ReadPredicateGroup): this; + /** Add a bound AND equality comparison. */ + andWhere(column: ReadPredicateSelector, value: unknown): this; + /** Add a bound AND comparison. */ + andWhere( + column: ReadPredicateSelector, + operator: string, + value: unknown + ): this; + andWhere( + first: ReadPredicateSelector | ReadPredicateGroup, + ...args: [] | [unknown] | [string, unknown] + ): this { + return this.comparison('and', first, args); + } + + /** Add a parenthesized OR group. Use an enclosing AND group beside authorization filters. */ + orWhere(group: ReadPredicateGroup): this; + /** Add a bound OR equality comparison. */ + orWhere(column: ReadPredicateSelector, value: unknown): this; + /** Add a bound OR comparison. */ + orWhere( + column: ReadPredicateSelector, + operator: string, + value: unknown + ): this; + orWhere( + first: ReadPredicateSelector | ReadPredicateGroup, + ...args: [] | [unknown] | [string, unknown] + ): this { + return this.comparison('or', first, args); + } + + private comparison( + boolean: 'and' | 'or', + first: ReadPredicateSelector | ReadPredicateGroup, + args: [] | [unknown] | [string, unknown] + ): this { + const context = this.readPredicateContext(); + const method = boolean === 'and' ? 'where' : 'orWhere'; + if (!args.length) { + const group = new ReadPredicateBuilder(context); + let operations: readonly ReadPredicate[]; + try { + const result: unknown = (first as ReadPredicateGroup)(group); + if ( + result && + typeof (result as PromiseLike).then === 'function' + ) { + // Consume native async rejection without assimilating foreign + // thenables (a Knex query's then() would execute SQL). + if (result instanceof Promise) void result.catch(() => {}); + throw new ReadSchemaError( + 'Read predicate groups must be synchronous' + ); + } + operations = group[finishGroup](); + } finally { + group[finishGroup](); + } + return this.addReadPredicate(query => { + query[method](nested => { + for (const operation of operations) operation(nested); + }); + }); + } + const operator = args.length === 1 ? '=' : args[0]; + if ( + typeof operator !== 'string' || + !ALLOWED_OPS.has(operator.toLowerCase()) + ) + throw new ReadSchemaError( + `Unsupported comparison operator: ${operator}` + ); + const column = context.column(first as ReadPredicateSelector); + const value = captureValue( + context.knex, + args.length === 1 ? args[0] : args[1] + ); + return this.addReadPredicate(query => { + if (args.length === 1) query[method](column, value()); + else query[method](column, operator, value()); + }); + } + + private nullPredicate( + column: ReadPredicateSelector, + method: 'whereNull' | 'whereNotNull' | 'orWhereNull' | 'orWhereNotNull' + ): this { + const name = this.readPredicateContext().column(column); + return this.addReadPredicate(query => { + query[method](name); + }); + } + /** Match SQL null without changing the row schema. */ + whereNull(column: ReadPredicateSelector): this { + return this.nullPredicate(column, 'whereNull'); + } + /** Exclude SQL null without narrowing the declared row schema. */ + whereNotNull(column: ReadPredicateSelector): this { + return this.nullPredicate(column, 'whereNotNull'); + } + /** Add an OR SQL-null condition. */ + orWhereNull(column: ReadPredicateSelector): this { + return this.nullPredicate(column, 'orWhereNull'); + } + /** Add an OR SQL-not-null condition. */ + orWhereNotNull(column: ReadPredicateSelector): this { + return this.nullPredicate(column, 'orWhereNotNull'); + } + + private membership( + column: ReadPredicateSelector, + values: ReadMembership, + method: 'whereIn' | 'whereNotIn' | 'orWhereIn' | 'orWhereNotIn' + ): this { + const context = this.readPredicateContext(); + const name = context.column(column); + const captured = Array.isArray(values) + ? values.map(value => captureValue(context.knex, value)) + : captureSubquery(context.knex, values as Knex.QueryBuilder); + return this.addReadPredicate(query => { + if (typeof captured === 'function') { + const rawMethod = method.startsWith('or') + ? 'orWhereRaw' + : 'whereRaw'; + const operator = method.includes('Not') ? 'not in' : 'in'; + query[rawMethod](`?? ${operator} (?)`, [name, captured()]); + } else { + query[method]( + name, + captured.map(value => value()) + ); + } + }); + } + /** Match captured values or a SELECT subquery. An empty list matches no rows. */ + whereIn(column: ReadPredicateSelector, values: ReadMembership): this { + return this.membership(column, values, 'whereIn'); + } + /** Exclude captured values or a SELECT subquery. SQL NOT IN null semantics apply. */ + whereNotIn(column: ReadPredicateSelector, values: ReadMembership): this { + return this.membership(column, values, 'whereNotIn'); + } + /** Add an OR membership condition. */ + orWhereIn(column: ReadPredicateSelector, values: ReadMembership): this { + return this.membership(column, values, 'orWhereIn'); + } + /** Add an OR negative membership condition. */ + orWhereNotIn( + column: ReadPredicateSelector, + values: ReadMembership + ): this { + return this.membership(column, values, 'orWhereNotIn'); + } + + private exists( + subquery: Knex.QueryBuilder, + method: + | 'whereExists' + | 'whereNotExists' + | 'orWhereExists' + | 'orWhereNotExists' + ): this { + const captured = captureSubquery( + this.readPredicateContext().knex, + subquery + ); + return this.addReadPredicate(query => { + const rawMethod = method.startsWith('or') + ? 'orWhereRaw' + : 'whereRaw'; + const operator = method.includes('Not') ? 'not exists' : 'exists'; + query[rawMethod](`${operator} (?)`, [captured()]); + }); + } + /** Require a row in a captured SELECT subquery; use ref() to correlate it. */ + whereExists(subquery: Knex.QueryBuilder): this { + return this.exists(subquery, 'whereExists'); + } + /** Require no rows in a captured SELECT subquery. */ + whereNotExists(subquery: Knex.QueryBuilder): this { + return this.exists(subquery, 'whereNotExists'); + } + /** Add an OR EXISTS predicate. */ + orWhereExists(subquery: Knex.QueryBuilder): this { + return this.exists(subquery, 'orWhereExists'); + } + /** Add an OR NOT EXISTS predicate. */ + orWhereNotExists(subquery: Knex.QueryBuilder): this { + return this.exists(subquery, 'orWhereNotExists'); + } + + /** Trusted SQL predicate with positional value (?) and identifier (??) bindings; not a SQL sandbox. */ + whereRaw(sql: string, bindings: readonly Knex.RawBinding[] = []): this { + const captured = captureReadRaw( + this.readPredicateContext().knex, + sql, + bindings + ); + return this.addReadPredicate(query => { + query.whereRaw(captured()); + }); + } + /** Add an OR trusted SQL predicate with captured positional bindings. */ + orWhereRaw(sql: string, bindings: readonly Knex.RawBinding[] = []): this { + const captured = captureReadRaw( + this.readPredicateContext().knex, + sql, + bindings + ); + return this.addReadPredicate(query => { + query.orWhereRaw(captured()); + }); + } +} + +/** + * Predicate-only builder supplied to grouped where/andWhere/orWhere callbacks. + * Group methods accumulate synchronously; outer readers remain immutable. + * No select, join, order, raw-query escape hatch, then, or execution method exists. + * Retaining this builder and mutating it after the callback throws. + */ +export class ReadPredicateBuilder extends ReadPredicates { + #context: ReadPredicateContext; + #operations: ReadPredicate[] = []; + #closed = false; + /** @internal Created only for grouped predicates. */ + constructor(context: ReadPredicateContext) { + super(); + this.#context = context; + } + protected readPredicateContext(): ReadPredicateContext { + return this.#context; + } + protected addReadPredicate(predicate: ReadPredicate): this { + if (this.#closed) + throw new ReadSchemaError('Predicate group is already closed'); + this.#operations.push(predicate); + return this; + } + /** @internal Close the scoped builder and snapshot its predicate list. */ + [finishGroup](): readonly ReadPredicate[] { + this.#closed = true; + return [...this.#operations]; + } +} diff --git a/libs/orm/README.md b/libs/orm/README.md index 5d78ac10..6573d147 100644 --- a/libs/orm/README.md +++ b/libs/orm/README.md @@ -232,6 +232,14 @@ decoded in one SQL statement. STI/CTI readers expose `variantRowSchemas` for explicit application mapping. See the [read-schema consumer guide](../knex-schema/README.md#projection-aware-reads) for numeric/null/date rules, examples and compatibility boundaries. +Detached ordinary readers also support grouped `where`/`andWhere`/`orWhere`, +IN/EXISTS subqueries, bound `whereRaw`/`orderByRaw`, and `ref(selector)` for quoted +column references. These operations preserve the reader's `rowSchema` identity +and work in nested relation customizers; polymorphic branches use `forVariant()`. +Group callbacks are synchronous and predicate-only. See +[filtering and ordering](../knex-schema/README.md#filtering-and-ordering-without-changing-the-result-schema) +for scoped search, subquery snapshots, pagination, and raw-SQL boundaries. + --- ## Polymorphic entities (STI / CTI) diff --git a/websites/docs/app/knex-schema/page.tsx b/websites/docs/app/knex-schema/page.tsx index 917f8b4a..1be30d69 100644 --- a/websites/docs/app/knex-schema/page.tsx +++ b/websites/docs/app/knex-schema/page.tsx @@ -412,6 +412,43 @@ const rows = await read.where(t => t.id, taskId);`) +

+

Filtering and ordering schema-aware reads

+

+ Group AND/OR search predicates without changing the + selected row schema. IN/EXISTS subqueries, null checks, + and bound raw predicates and ordering remain explicit. + Every outer operation returns an independent reader. +

+
+                         ({ id: t.id, title: t.title }));
+const read = base.where(t => t.projectId, projectId)
+    .andWhere(group => group
+        .where(t => t.title, 'ilike', pattern)
+        .orWhereExists(labelMatches))
+    .orderByRaw('case when ?? = ? then 0 else 1 end', [
+        base.ref(t => t.id), priorityTaskId
+    ]);
+// read.rowSchema === base.rowSchema`)
+                            }}
+                        />
+                    
+

+ Group callbacks run synchronously once and expose only + predicates. Subquery SQL and bindings are captured on + attachment, with no database execution. Use ref() for + quoted columns and generated aliases; keep values in + bindings and raw SQL fragments application-authored. + Unrestricted apply() remains unavailable. +

+ + Multi-file examples and compatibility boundaries + +
+ {/* ── API Reference ────────────────────────────────── */}

API Reference

diff --git a/websites/docs/public/llms.txt b/websites/docs/public/llms.txt index a43783c6..d1bc3fa1 100644 --- a/websites/docs/public/llms.txt +++ b/websites/docs/public/llms.txt @@ -184,6 +184,18 @@ belong to these packages. Knex is a peer dependency; PostgreSQL also needs `pg`. decode to `Date` throughout nested graphs, and declared decimal/bigint columns retain exact strings. Input defaults and preprocessors are not replayed. [Read schemas](/knex-schema#row-schemas), [detached ORM reads](/orm#detached-read-schemas). +- **Shape-preserving predicates:** ordinary/aliased readers and ordinary ORM reads + support grouped `where`/`andWhere`/`orWhere`, null checks, IN/NOT IN value arrays + or SELECT subqueries, EXISTS/NOT EXISTS, and OR variants. Use `whereRaw` / + `orWhereRaw` / `orderByRaw` with positional bindings and `ref(selector)` for + quoted mapped columns, including generated child aliases. Group callbacks run + synchronously once and receive only predicate methods; retained groups are + closed after the callback. Subquery SQL/bindings are captured without executing + SQL. These operations retain rowSchema identity; assign their returned reader. + Raw fragments are trusted application SQL, not a sandbox. Keep authorization + outside grouped search ORs. No unrestricted apply() exists in read mode. + Numbered pagination retains ordering; composite cursors replace it with their + explicit orderBy specification. [Read predicates](/knex-schema#read-predicates). - **Nested and polymorphic reads:** child customizers return their configured queries. Nested includes execute in one SQL statement. STI/CTI readers expose a union `rowSchema` and per-variant `variantRowSchemas`; DTO selection and From d91656675294046de81edf164b0c2e8e30b79391 Mon Sep 17 00:00:00 2001 From: Andrew Zolotukhin Date: Wed, 30 Sep 2026 22:12:45 +0000 Subject: [PATCH 2/3] feat(framework)!: make query builders immutable with automatic row schemas --- .changeset/immutable-query-major.md | 27 + demos/e2e/README.md | 3 +- demos/e2e/src/api/telemetry.smoke.test.ts | 8 +- demos/e2e/src/api/todos.api.test.ts | 9 +- demos/e2e/src/support/playwright.ts | 1 + demos/e2e/src/ui/todo-crud.ui.test.ts | 6 + demos/todo-backend/src/api/handlers/todos.ts | 6 +- demos/todo-backend/src/api/mappers.ts | 13 +- demos/todo-backend/src/db/schemas.ts | 40 +- .../src/features/todos/TodoDetailPage.tsx | 6 +- docs/assets/immutable-query-demo.png | Bin 0 -> 82574 bytes docs/assets/immutable-query-docs.png | Bin 0 -> 157494 bytes libs/knex-schema/MIGRATION-v5.md | 212 ++ libs/knex-schema/README.md | 134 +- .../integration/immutable-queries.test.ts | 375 +++ libs/knex-schema/integration/queries.test.ts | 80 +- .../integration/read-graphs.test.ts | 29 +- .../integration/read-predicates.test.ts | 23 +- ...sedReadQuery.ts => AliasedQueryBuilder.ts} | 67 +- libs/knex-schema/src/OpaqueQuery.ts | 130 + ...eadQuery.ts => PolymorphicQueryBuilder.ts} | 501 ++- libs/knex-schema/src/QuerySource.ts | 1389 ++++++++ libs/knex-schema/src/SchemaQueryBuilder.ts | 2896 +++++++++-------- libs/knex-schema/src/SchemaReadQuery.ts | 864 ----- libs/knex-schema/src/aliased-query.ts | 51 +- .../src/composable-query.test-d.ts | 16 +- libs/knex-schema/src/composable-query.test.ts | 4 +- libs/knex-schema/src/entity.ts | 2 +- libs/knex-schema/src/extension.ts | 30 +- .../knex-schema/src/immutable-query.test-d.ts | 60 + libs/knex-schema/src/immutable-query.test.ts | 163 + libs/knex-schema/src/index.ts | 27 +- libs/knex-schema/src/mappers.ts | 2 +- libs/knex-schema/src/operations/aggregate.ts | 12 +- .../src/operations/composite-cursor.ts | 4 +- libs/knex-schema/src/operations/delete.ts | 19 +- libs/knex-schema/src/operations/helpers.ts | 82 +- libs/knex-schema/src/operations/insert.ts | 39 +- libs/knex-schema/src/operations/join.ts | 24 +- libs/knex-schema/src/operations/pagination.ts | 18 +- libs/knex-schema/src/operations/select.ts | 29 +- libs/knex-schema/src/operations/state.ts | 21 +- libs/knex-schema/src/operations/update.ts | 14 +- libs/knex-schema/src/operations/where.ts | 54 +- libs/knex-schema/src/orm-extensions.test.ts | 6 +- libs/knex-schema/src/orm.test.ts | 248 +- libs/knex-schema/src/public-api-docs.test.ts | 2 +- libs/knex-schema/src/query-scope.ts | 24 + libs/knex-schema/src/query.ts | 74 + libs/knex-schema/src/raw.test.ts | 194 +- libs/knex-schema/src/raw.ts | 89 +- libs/knex-schema/src/read-consumer.test.ts | 2 +- libs/knex-schema/src/read-graph.test-d.ts | 24 +- libs/knex-schema/src/read-mapping.test.ts | 2 +- .../knex-schema/src/read-predicates.test-d.ts | 10 +- libs/knex-schema/src/read-predicates.test.ts | 27 +- libs/knex-schema/src/read-predicates.ts | 184 +- libs/knex-schema/src/read-projection.ts | 5 +- libs/knex-schema/src/read-schema.test-d.ts | 22 +- libs/knex-schema/src/read-schema.test.ts | 82 +- libs/knex-schema/src/read-schema.ts | 30 +- libs/knex-schema/src/types.ts | 34 +- libs/knex-schema/src/unit.test.ts | 387 ++- libs/knex-schema/src/validate.ts | 2 +- libs/mapper/README.md | 2 +- libs/orm/README.md | 23 +- libs/orm/src/change-tracker.ts | 124 +- libs/orm/src/dbset.ts | 520 ++- libs/orm/src/index.ts | 9 +- libs/orm/src/orm.test.ts | 223 +- libs/orm/src/query-types.test-d.ts | 1 + libs/orm/src/result-types.ts | 14 +- libs/orm/src/save-graph.ts | 4 +- libs/orm/src/variant-write.ts | 68 +- websites/docs/app/knex-schema/page.tsx | 61 +- websites/docs/app/mapper/page.tsx | 2 +- websites/docs/app/orm/page.tsx | 16 +- websites/docs/public/llms.txt | 13 +- 78 files changed, 6218 insertions(+), 3800 deletions(-) create mode 100644 .changeset/immutable-query-major.md create mode 100644 docs/assets/immutable-query-demo.png create mode 100644 docs/assets/immutable-query-docs.png create mode 100644 libs/knex-schema/MIGRATION-v5.md create mode 100644 libs/knex-schema/integration/immutable-queries.test.ts rename libs/knex-schema/src/{AliasedReadQuery.ts => AliasedQueryBuilder.ts} (79%) create mode 100644 libs/knex-schema/src/OpaqueQuery.ts rename libs/knex-schema/src/{PolymorphicReadQuery.ts => PolymorphicQueryBuilder.ts} (53%) create mode 100644 libs/knex-schema/src/QuerySource.ts delete mode 100644 libs/knex-schema/src/SchemaReadQuery.ts create mode 100644 libs/knex-schema/src/immutable-query.test-d.ts create mode 100644 libs/knex-schema/src/immutable-query.test.ts create mode 100644 libs/knex-schema/src/query-scope.ts create mode 100644 libs/knex-schema/src/query.ts diff --git a/.changeset/immutable-query-major.md b/.changeset/immutable-query-major.md new file mode 100644 index 00000000..d792313b --- /dev/null +++ b/.changeset/immutable-query-major.md @@ -0,0 +1,27 @@ +--- +"@cleverbrush/async": major +"@cleverbrush/auth": major +"@cleverbrush/client": major +"@cleverbrush/deep": major +"@cleverbrush/di": major +"@cleverbrush/env": major +"@cleverbrush/knex-clickhouse": major +"@cleverbrush/knex-schema": major +"@cleverbrush/log": major +"@cleverbrush/mapper": major +"@cleverbrush/orm-cli": major +"@cleverbrush/orm": major +"@cleverbrush/otel": major +"@cleverbrush/react-form": major +"@cleverbrush/scheduler": major +"@cleverbrush/schema-json": major +"@cleverbrush/schema": major +"@cleverbrush/server-openapi": major +"@cleverbrush/server": major +--- + +Make Framework query builders immutable and infer row schemas automatically. + +This breaking release requires consumers to retain returned query builders, return synchronous builders from scopes and grouped predicates, and supply an explicit Framework object output schema for opaque raw SELECTs. Remove withRowSchema() calls: ordinary, aliased, polymorphic and ORM queries now expose their row schemas directly. Projections replace scalar selections, and projected/aggregate/raw queries cannot perform entity writes. Reads and write-returning rows consistently preserve exact decimal/bigint strings, Date objects and SQL nulls. + +All published Framework packages advance together to the next major version. See the knex-schema and ORM migration guides before upgrading; tracked entity objects remain mutable. diff --git a/demos/e2e/README.md b/demos/e2e/README.md index 94ed7f84..8fad4eb8 100644 --- a/demos/e2e/README.md +++ b/demos/e2e/README.md @@ -27,7 +27,7 @@ npx vitest --run src/api/todos.api.test.ts # single file ### One-time browser install -The UI project requires Chromium. After `npm install`, run: +The UI project requires Chromium. After `npm ci`, run: ```bash cd demos/e2e && npx playwright install chromium @@ -41,6 +41,7 @@ cd demos/e2e && npx playwright install chromium | `RESET` | `0` | If `1`, run `docker compose down -v` before bringing the stack up (wipes Postgres). | | `CI` | unset | Setting `CI=true` flips `KEEP_STACK` default to `0` and forces full teardown. | | `HEADED` | `0` | If `1`, launch Chromium headed so you can watch UI tests run. | +| `E2E_BROWSER_EXECUTABLE_PATH` | unset | Optional path to an existing Chromium executable; otherwise uses Playwright's managed browser. | | `SLOWMO` | `0` | Slow-motion delay (ms) for Playwright actions — useful with `HEADED=1`. | | `E2E_API_URL` | `http://localhost:3000` | Backend HTTP base URL. | | `E2E_WS_URL` | `ws://localhost:3000` | Backend WebSocket base URL. | diff --git a/demos/e2e/src/api/telemetry.smoke.test.ts b/demos/e2e/src/api/telemetry.smoke.test.ts index 0fb18998..55c78690 100644 --- a/demos/e2e/src/api/telemetry.smoke.test.ts +++ b/demos/e2e/src/api/telemetry.smoke.test.ts @@ -41,8 +41,6 @@ describe('Telemetry smoke — ClickHouse logs & traces correlation', () => { TraceId: string; }>( `SELECT body AS Body, trace_id AS TraceId FROM signoz_logs.distributed_logs_v2 WHERE trace_id = '${traceId}' FORMAT JSON`, -ace_id = '${traceId}' FORMAT JSON`, - 45_000, 1_000 ); @@ -55,10 +53,8 @@ ace_id = '${traceId}' FORMAT JSON`, const spans = await waitForRows<{ SpanName: string; ServiceName: string; - }>(name AS SpanName, resources_string['service.name'] AS ServiceName FROM signoz_traces.distributed_signoz_index_v3 WHERE trace_id = '${traceId}' FORMAT JSON`, - + }>( `SELECT name AS SpanName, resources_string['service.name'] AS ServiceName FROM signoz_traces.distributed_signoz_index_v3 WHERE trace_id = '${traceId}' FORMAT JSON`, - 45_000, 1_000 ); @@ -67,7 +63,7 @@ ace_id = '${traceId}' FORMAT JSON`, expect(services.has('todo-backend')).toBe(true); }); - it('ClickHouse is reachable and reports recent losignoz_logs.distributed_logs_v2 WHERE toDateTime(intDiv(timestamp, 1000000000)) + it('ClickHouse is reachable and reports recent logs', async () => { const { rows } = await clickhouseQuery<{ recent: string }>( `SELECT toString(count()) AS recent FROM signoz_logs.distributed_logs_v2 WHERE toDateTime(intDiv(timestamp, 1000000000)) >= now() - INTERVAL 1 HOUR FORMAT JSON` ); diff --git a/demos/e2e/src/api/todos.api.test.ts b/demos/e2e/src/api/todos.api.test.ts index fdf28824..edb420cb 100644 --- a/demos/e2e/src/api/todos.api.test.ts +++ b/demos/e2e/src/api/todos.api.test.ts @@ -228,10 +228,17 @@ describe('Todos — attachment & legacyReplace', () => { body: { title: uniqueTitle('attach') } }) ); + const boundary = 'framework-attachment-fixture'; + const uploaded = await r('POST', `/api/todos/${created.id}/attachment`, { + raw: true, + headers: { 'content-type': `multipart/form-data; boundary=${boundary}` }, + body: `--${boundary}\r\nContent-Disposition: form-data; name="attachment"; filename="example.txt"\r\nContent-Type: text/plain\r\n\r\nImmutable query demo\r\n--${boundary}--\r\n` + }); + expect(uploaded.status).toBe(201); const res = await r('GET', `/api/todos/${created.id}/attachment`); expect(res.status).toBe(200); expect(res.headers['content-type']).toMatch(/text\/plain/); - expect(res.body.length).toBeGreaterThan(0); + expect(res.body).toBe('Immutable query demo'); }); it('legacyReplace (PUT) returns a redirect', async () => { diff --git a/demos/e2e/src/support/playwright.ts b/demos/e2e/src/support/playwright.ts index c01df835..bf5239d5 100644 --- a/demos/e2e/src/support/playwright.ts +++ b/demos/e2e/src/support/playwright.ts @@ -11,6 +11,7 @@ let browser: Browser | null = null; async function getBrowser(): Promise { if (!browser) { browser = await chromium.launch({ + executablePath: process.env.E2E_BROWSER_EXECUTABLE_PATH || undefined, headless: !config.headed, slowMo: config.slowMo }); diff --git a/demos/e2e/src/ui/todo-crud.ui.test.ts b/demos/e2e/src/ui/todo-crud.ui.test.ts index 4a93b440..8c252195 100644 --- a/demos/e2e/src/ui/todo-crud.ui.test.ts +++ b/demos/e2e/src/ui/todo-crud.ui.test.ts @@ -62,6 +62,12 @@ describe('UI — todo CRUD', () => { // Detail page after creation await page.waitForURL(/\/todos\/\d+$/, { timeout: 10_000 }); + // Stay on the detail page until its requests settle: an admin-only + // picker lookup must not sign out an ordinary user after creation. + await page.getByRole('button', { name: 'Save Changes', exact: true }).waitFor(); + await page.waitForLoadState('networkidle'); + expect(page.url()).toMatch(/\/todos\/\d+$/); + expect(await page.locator('input').first().inputValue()).toBe(title); const url = page.url(); const todoId = Number(url.match(/\/todos\/(\d+)$/)![1]); diff --git a/demos/todo-backend/src/api/handlers/todos.ts b/demos/todo-backend/src/api/handlers/todos.ts index 0c60aea1..04bc0eef 100644 --- a/demos/todo-backend/src/api/handlers/todos.ts +++ b/demos/todo-backend/src/api/handlers/todos.ts @@ -452,13 +452,13 @@ export const uploadAttachmentHandler: Handler< return ActionResult.created({ id: updated.id, title: updated.title, - description: updated.description, + description: updated.description ?? undefined, completed: updated.completed, userId: updated.userId, createdAt: updated.createdAt, updatedAt: updated.updatedAt, - attachmentName: updated.attachmentName, - attachmentMimeType: updated.attachmentMimeType, + attachmentName: updated.attachmentName ?? undefined, + attachmentMimeType: updated.attachmentMimeType ?? undefined, attachmentSize: file.size }); }; diff --git a/demos/todo-backend/src/api/mappers.ts b/demos/todo-backend/src/api/mappers.ts index eaba2470..74a74b47 100644 --- a/demos/todo-backend/src/api/mappers.ts +++ b/demos/todo-backend/src/api/mappers.ts @@ -7,7 +7,6 @@ import type { TodoActivityResponse } from './schemas.js'; const UserRowSchema = object({ id: number(), email: string(), - passwordHash: string().optional(), role: string(), authProvider: string(), createdAt: date() @@ -16,13 +15,13 @@ const UserRowSchema = object({ const TodoRowSchema = object({ id: number(), title: string(), - description: string().optional(), + description: string().nullable(), completed: boolean(), userId: number(), createdAt: date(), updatedAt: date(), - attachmentName: string().optional(), - attachmentMimeType: string().optional() + attachmentName: string().nullable(), + attachmentMimeType: string().nullable() }); export const mappingRegistry = mapper() @@ -33,6 +32,10 @@ export const mappingRegistry = mapper() m .for(t => t.description) .compute(f => f.description ?? undefined) + .for(t => t.attachmentName) + .compute(f => f.attachmentName ?? undefined) + .for(t => t.attachmentMimeType) + .compute(f => f.attachmentMimeType ?? undefined) .for(t => t.attachmentSize) .ignore() ); @@ -40,7 +43,7 @@ export const mappingRegistry = mapper() const _mapUserFn = mappingRegistry.getMapper(UserRowSchema, UserResponseSchema); const _mapTodoFn = mappingRegistry.getMapper(TodoRowSchema, TodoResponseSchema); -export const mapUser = (row: UserDb) => _mapUserFn(row); +export const mapUser = (row: Omit) => _mapUserFn(row); export const mapTodo = (row: TodoDb) => _mapTodoFn(row); export function mapTodoActivity(row: ActivityDb & Record): TodoActivityResponse { diff --git a/demos/todo-backend/src/db/schemas.ts b/demos/todo-backend/src/db/schemas.ts index 277907e2..6333b074 100644 --- a/demos/todo-backend/src/db/schemas.ts +++ b/demos/todo-backend/src/db/schemas.ts @@ -1,4 +1,5 @@ import { + type EntityResult, array, boolean, date, @@ -144,12 +145,7 @@ const TodoSchema = object({ 'attachmentMimeType' ) .projection('ownership', 'id', 'userId') - .scope( - 'recentFirst', - (q: { - orderBy: (column: string, direction: 'asc' | 'desc') => unknown; - }) => q.orderBy('created_at', 'desc') - ); + .scope('recentFirst', q => q.orderBy('createdAt', 'desc')); export const TodoEntity = defineEntity(TodoSchema) .belongsTo( @@ -187,32 +183,6 @@ export const entityMap: AppEntityMap = { // ── Plain row types (used by mappers) ─────────────────────────────────────── -export type ActivityDb = { - id: number; - todoId: number; - type: string; - actorUserId?: number; - completedAt?: Date | null; - createdAt: Date; -}; - -export type UserDb = { - id: number; - email: string; - passwordHash?: string; - role: string; - authProvider: string; - createdAt: Date; -}; - -export type TodoDb = { - id: number; - title: string; - description?: string; - completed: boolean; - userId: number; - createdAt: Date; - updatedAt: Date; - attachmentName?: string; - attachmentMimeType?: string; -}; +export type ActivityDb = EntityResult; +export type UserDb = EntityResult; +export type TodoDb = EntityResult; diff --git a/demos/todo-frontend/src/features/todos/TodoDetailPage.tsx b/demos/todo-frontend/src/features/todos/TodoDetailPage.tsx index e095545c..c1e58291 100644 --- a/demos/todo-frontend/src/features/todos/TodoDetailPage.tsx +++ b/demos/todo-frontend/src/features/todos/TodoDetailPage.tsx @@ -18,6 +18,7 @@ import { Field, useSchemaForm } from '@cleverbrush/react-form'; import { UpdateTodoBodySchema } from '@cleverbrush/todo-backend/contract'; import { ApiError, isTimeoutError, isNetworkError } from '@cleverbrush/client'; import { client } from '../../api/client'; +import { useAuth } from '../../lib/auth-context'; type TodoEvent = Parameters[0]['body']; import { ConfirmDialog } from '../../components/ConfirmDialog'; @@ -25,6 +26,7 @@ import { ConfirmDialog } from '../../components/ConfirmDialog'; type TodoWithAuthor = Awaited>; export function TodoDetailPage() { + const { isAdmin } = useAuth(); const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); @@ -79,11 +81,13 @@ export function TodoDetailPage() { // Load user list once for the "assigned" picker useEffect(() => { + // The user directory is admin-only. Regular users retain the ID input. + if (!isAdmin) return; client.users .list({ query: { page: 1, limit: 100 } }) .then(rows => setUsers(rows.map(u => ({ id: u.id, email: u.email })))) .catch(() => {/* ignore — picker will fall back to text input */}); - }, []); + }, [isAdmin]); useEffect(() => { load(); }, [load]); diff --git a/docs/assets/immutable-query-demo.png b/docs/assets/immutable-query-demo.png new file mode 100644 index 0000000000000000000000000000000000000000..e73fa294eb084e6b0fa6789725d363dc432f82ae GIT binary patch literal 82574 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%0Dutj9raD3%WEuLGGrn(c@Lb+y)0S3cOjP z2L?9xKUTIBvU%X*xL-{SOJ`t}Xf)&tS}1*LOS>(3%K+YO306f_fwEeR4_t)F)ukxs zSd5e3&&_KqfNgK)or1Oi$%~B8W^^(z+HY5#W8o`)*cg$^XX-=i?Z@W zVcjXTb`)4Q6Y6*$S`2 z>E4RoqP?bIMsbXRhJAj1ddD{jtX=j>1$;@=mYjQ-_@2hEtVd0vf)=X=Q);KvH&3S+ zw5yGuPy1No=XTgauHiVx>&vy@iMJPCgcZ(Yv=v3gqyzzuMmh&Gh`}^*QmL-Hj2ko&@!jNITo+QXRs=abrGGG3986 z_6{&FMUAUf#67MG%Bd)xNG)7K6?}+@iOZl~M~P1nXYA+2m^6lomEPF6sUA+%6Xu~| ztJwrEs7)!BY;B#jSM)+YP3t!sKlc!UW*k+h^;aO6d4aeI1*bS(D#P|2`b+Z;8-$_} z5zS&~lV5{-21?3f-<}EFGftDnd0ZdKdbCIMeaYO;b}!Cc5ktPOUaT9nYr@ z`H}=L^GYF`pCGCd8im``&=2Wsd+KFMWHtb@rU_x<_<-XVSY(d?9HXKero zbvgtyUz$-9x}7Q!7q5jOJ9G?Y&ZFO3?;L`!9h{_Ylp3a@aft~P#d&jiX0m1>tE;rJUYKXr9WP3Wx|rJNe;Fk(FPQ&V4jC(u$NApdaW0hK!x8ASBf}`1k0H-8MbCxCG{4#XI-B9IV$|h zDqR1SAjHjKvrz|&a7$a{G-$Lm**KqG90&;oq>158$ z{Wf=FdpJ?pWA>gWV(Rt%C->e8Si{`Jgy(QIPy0Q;7(<-*Z8sa&pklPg%?7fI+C9I` zzyLL9k}vz>^%E%+Usr@jfQ$=fF8;>At2(v>q=hgwu_J`isNbNOc9yK|aoA6xn!0A6 zC2Tx#k#fRS)_gm4ts+Dz#%goYe_cA8wQ364Uu;F>EGlVg@)q#^{p0X}vO&qx{5UKk z!$J;rA;z{DMd7TNQ9hOZQY>{#zUZ;z`W5ssId=Yv>mEXZI0ZJ1-Bq2w?GaH*;&g|u{2UtR4jgaf7x3Ph_Q-eWQTn_XF%)JkrF z#n?KFJLR1skd%ohmphHLe;w=NVpsPdkB;owT27!-YHO4>d&Y%pd_TAv8OAWB=}$ zW`YkmakAZGUtQ0_X|Q%Rjb$oEoRWWsDI$yRw6~a#H*~I1QS`X$XgUr@t1yqxZ0S+) zdJ!R2YOWU2M4Yf?{|hm$C)KV>1UsBeu+@F@Z?Rh!-L$XALes&UnzTV^YOgdPd*=~9 zjo)wPldC-0a0GYLj{0cUM(a~AD!h)QLPBucU9hX}54-7T}=RbLe0FZ-p-)T}=c zbLX8yjbFpIub|$}a5d%jrDt>Ii}{?s>e1drS4)ef(xUOoto-uw=o=(tt7ZJxra6La zC3`d!E|Oew1tG@DHA}&mo?%i8^NYa?^{g`Ql$;NZ*YxGrHh{c~Ly6`6q+2_5_Txi8 zhr&)vnRe&K6LWL4-T7Sw{oj#uk1S<<{(6v3l;I zqLjteLAj?eAF&dtLVw-7@eZNZ(Q3TPXxz86k!zQ}1wo#>H;@d{l3~ zUfvN|3CzGq;u)(8DAH#zA2a?nB>W&GcF?_jwoPwPV#=gPPZ1C~Xe%qnbtP+Sa+2PV z@fU0udj=;fJ~+Y@gbVF_$Q4xzdJGLgg<(Np(R|U-P zNKk7Zd%ovHjjRsCH2v$C9)&WboXR@unarvPeBAnOl#83gJ_iQX%1!df#MtWnU-ZvH z%96PqkO_eCI-0aC#@ix zK}>`!<#cTf_}8D4xk~#Ai3krYjRZ!s^VwZ|VAY;teO&UyvlS`@@u+vO;N5M4LX8QmSC9o4w#d zw^AtH11;4kK-rg`NFGna(c*WDdI7Ndm&v&DvFKKto@Srz*Lus!MvF`BF^&Ef9HZguS8g*Lp zfs)euO@F?Ow7)9%ZOjs&0 zl38{$o`mbd^jn<61)>VQ44J-3e^7IpwCJpk7)Y5#ipk03!qYSCid@d(f`&nPTV;n3 z+xj~OTAm%?ZKo;-C}6kWDl}%eSbJCsD80~7cKB8MT0$OdKMpw1x7T}b(+mz+O}`6# zx?3W>r(KO2cV+=ACNR5-Ktt^#Qojo~)#5w|FajMo1|XjKD`?wL?MZAoJBN!z2vmx{B|k@ z?_2A>$8^5QRG08alTFI0K}?o7rm6NPxG~GL{^q}|r{hx}`qgr} zUMr0jQ-N+qF{A5AL^SeWRFMiG?Sij)*+dmcakOiiQ;Lcn-`0Ho#>q+XS+v^2ZKw5p zl(|_a-m>(RO^(kCcseIxxCB>QySSR~g}#$Z;(ESQj|yWaC1%ksMU+7lmtvFTAR!vA zbQewSMBE+5V*CWAMDL9s0l@z4@to7p(oifUtO?vtf*PKt*J5_OF&fByRwQelr{hZQ z*=~s3kl-`PZQ7+sw{riU?cN#HgO5f?PR`MeUb$!_wQE|KM-ok1-slT?NWWFmX5;Ym zf#ZP;I|zDHuwX_B`qB8P?QNap*mFw#)UUON)7t{o8^qJ5V~Gu>@}uQ;zz$Y!NN9|1 z`Of(&Rh@~l@?_zxFX+N3=M{6oha66NN6n3cVQ%S~N4c;3#*{U@#DmaGkF_DQG>~1r zhON?~45c?FYirU^t_Y$n#ukXD(T5XwKw%~+oC@i!3zh3|NeG2$!$;4go)ys-H6uvx z%9J6~kA27ttWtWXy_WY_AcL7%-`=R)j91M#TXZ<%g3N7eg`;A6QcBXCbNw487pGct zObkw#*b)5cX^4hjwSv&730-KYie+F7FGiojkW*q>nR}yj0)vBf{Jw*I`n_sZDlGwuYwG$sfD9YfL=W7hS89x!EU37E`0ME35i1>#7;%oH_jp#@NZG zzJ@B~PWAYt#Ww$pxwR^?qaO8iqQ0z*?7BGjS*n)yvOhj9X^5tR?VUf(mEK|AZ#bm9 zC2r8T&rZo8Eu@=l|K(D#*6g_|H*oB1GvP6H%bNHTp$@n3n*b-;spDv3L%3Q&yQ%8Y?{QBSN;VSJw{gPw~Sln zGTihEexWwF9S7ZK5!*X}ySMrrUyL$4mGrP;`7A@wiX$h*v*cMBw)m;SG4pkB>6Zbq zKe|Nd`s0El5^gc>0h(dhHwCHE9m~;crOlRuA)D-VwLNkvxB-$U9h;=6d^)#iTpIRE zqzmmq3+1x2_T%xO&Y`Guc8qZMkMeOg;v9C1Y!R$|Y9?^R4vxT9L=ff<_+nO#x&o$T;)HOOqBd|-KZod_m6pX>c)b~#>Zc#|Dr4$7eY`hgB4ZD104{$|n$J!;jiJ@@N3|wg zRB-2HV!Cd-3rDOBt&}RY3eDQz z?p$`JC`g^H%`=yR=g*ycKir?is_x0<}Vn0v{lJe{T$=MD)B8H)6)A_hUo3BWv z-)uI=DA%G1`Q2*kXcvlC)8L>K+w2^h{n~fZ@B|=}n_GKBOsARO!}s#2Xl&vOxpWbQ z$!!2;Fh>G0kXQ^?_Q$Mm({q9p^k1CgY!fC# zHiFx`1uw0uW~iK;CWnUPlH^9ziC-g%HBv4w>(-dE+56gIzp^H5LGOEr!B{9fJC30bOF)XQn~ zA1*v2+(q5PHki1H3}*K~mgw&KS?d|cw-kSfn|4Cs-HQ;E)_$S)_{-G>xCFq7l&SXw zXx{+_mO@~~x-SHG^${9&BbLu^c!#E&UaAP^dkPwN)siBkCJ`@e${uT_la`27_ouRD{kY`L=;x#&d zK>BHBV2kx0Qm|S@s$beQnc!dp#mJ*AnLTCQYU9aD_+<|lJ0bUp>-!@X-PD`Y;ioHx)7(7yxC)uRGy($!H1-6EzG<{v|K{KYHb z*c*>MsGh}FET8TV$QO(lw0u~+2addY6>i6+7}9u+a6IHLi{myOdsZk5tUPX4as3&i;Mk|%d z924}uIyYuoVHrf4VuGF7rxooYC&Q1{T?DXLmjELJDr;M6$+yW|<#aHI7MJJZ7DWU?o_;(M zB724>f&wq^F;bXy4Dvd0zV3{PE!;7bnYe7VR+=zF`}z;l0Xy2~38ifx_sR6YkTMV2 z5k0#Dm7qJ+t6dq1IMA$ZUSl?aev8>CaERmRbl7$W%3im)v@TS0DSO)t0qDBR8)g{& z;9<3pW!$LF>4kI>Z%stFP#x(Z3B=_@t-`spUn0DN=r_??dbo&F-LlT(Z z!p@4bj+W(sWN5_P1rid2-IN=Au6a!Q(Ih!U9&&7NC`49WX+!T?_kfH-QRTXEXJ>hg z^jaSvw!I4I(d0r(2ET?!)K!=D98caaOxq*{OQQiP>0bw{fpn5SoT4>%U6ER)22T%# zxwSX+moHnn9+~Imvo+XTFl!}y9wd%vwY&>oBjVg)-YwH6uo1|#dY+R=&{MU(D-ARh zeDAfgv^(%D;4_aKLs~t~D(yQf*8RtS7+Io5Vg>Ms+ACHdcWUnqrREL-UKLWjb9oX& zj?m<|Jkx4iWvQ5PO_vo;xZ<;TL^(w)eyL%GPpgcqiWJqD++s=nSYrU0p597M&O{}SNPyvyW8jmq8eCi#2)w5C9ZH0^0xPqv$ms&my5Wn4k8Gg_54ogdyme+<_2o*A^= zI)H*%RkjrMlt(AmOHRoN2{&VEozujjEe7GYfGy;88=jiiGUFk^C%PnEBXesAcn3QkR<}Fb z&ZSYNYX#Ye;KY}K3eP93Hs9|w^$c>We{iv$uZ2nagDgT2yx%O^*nBI>+V+@wZVf_0 z39?^y`Qol$hyZiHliHJrL6bh` zSVYgnl_QDrnGDdj11}CTP**BR)*tgA)y6t1(Z!9=ww#XE!a37Ik1xoxm@3*8bm+y8 zbCl2)eEi51KXfKYNo+;PV|UBp75D4(Th1Il=|@P8X9e9piGt}wGb$GMGG6y45*I>F zr-8bQylw(5wQj&}`)x*oiY4-Asyk=Twj}Eh2X7%@bi%5tfruIJvnn3GqPRPSR2fhs z4>gCX{=6F7s*}?QQK^vK0)eE&06w$K$dGEJm&*;yWz==~y?|DPN0ZtkB>r0LK@ed$K|#=Da>4)vCp@gk)sVSz>ad z>BDoa+^yL;HeN8hMtEz1>H#{0*kaJfvYIWg7eUUBMBl1;1%nzIWX`AH(1Qp1L|D6*?2vgJb&cQ zacIEjNNbHfd*f0+0k29^sCS>!>Gg9$YL@o##*U&N z+C2rdnH~Ad7uQgl6ICyC+*X9m72lOKm*wWFvlvt;R@(PB?rV&asC?Ga6ezIyL%5GUzeYvpuX#ZF}1(j=U#8P z_Z4sZd7tH(Gv`x7f)9Z(h^HB&xz-+l6G#Y$LALh=wzh~Y>wTl0!Noz>;0bmyE(q_< z)k~W2=h&FEs0KTQeuDMfWkeqw_Ds=dy1Xt@-{UM)rq;RB{T?f=zSLC1SQnmvVtpXw zZa$p99Mn)@7?PaONs)3dz~Qm*hVx!SgkrrNY<0R+u{TY>@rls?^A{4Ab>(APmDEVD z=-l0%tmEs$s8*cn@*U*NpO58LG|loHNtvy&+{P`DT+B zGIc8@P)g0OhX)6J>`D6Z-DQBBXX=w!Ze^7u-)${#S2i)x%^b5@`P$w^&?U_ISTZ`q{D?Q+iI<9^=;%9p%b;LuEH-v|%@b&7iN(j6YI3&4oF zw2=uprcI{<2ez`(@+m^+=zgOO2#U?NAg_(PI31=Gh$X24dIz7JW#K`@COyFF!Rx~% zsx$PK0>!d|WM=gy7JJ~(0s1zqt87;Cl+HQDy)dZd3&fS=8vF`}r4NMk8 zrjQ@`YNl1Hlc6raLh`%uD$#gml@64L8SwQJE<4BIp)@lrV2A-i?>VN-OuOi!Suy^= zbb;zGwDN7U*KL^oM3_q^$E7Fs7}7I7v@Ju*c1vSUpIhyW5-b10Hxyr>59$PUeT9qpC3OkfUofjl@Y4JqzlGg&2%dUi`m?1S0yq!BY*MOczxf?02O6rOB;Lp z5ghE}y-QANW>An5x<@o6HFX?`xw(0mR4RRxc_g9ckHio{$|@-aM(ry~ zp%J_y2kcRF=DxxQJtd^C4l~sX6HTYI#I^2DFM=vyl|w7M*m431W(>&0d~m_PQ5G-0 zlyw$+W-CHvgZjT!vxN~z&_tWk$5l#oe%(OJ`}UVj6(7aQTT6)0P*;f>F(d@nl(mKB zL#DlXOWVx+K5O}ktUjrSV@GC1D1qVb@`~@bI=el78`a`TU(&P;nDL)a&A)UWChin`n8X$8i`cZTdabfOjT>i)2pzF!3BcMw@$-$H z_VUHU1kji@x<}9A8-ITJL#fO=#Yl>=y}gF_+4uxl1PFSfU(q-(3^dH2?}Sgp(Xu|^ z&*5TqzASbJD7GWp4ek5j(WwI%92{ptzjG&xML)vyB#pU~SCRWHcn@3`xJz$0voS$LV3l?d;zj#w13$NmKlF}gEqsv+&+`{!Xk*YuUeQi4zDnwZ2I zIx($eUaZi5=TG(^%QjJ!;M+hcqa@u8Ro9|WB7`R$d0|_7!_8AgQtyYiQ&-0@Y%tY{ zamw82A1e5seapWvyHIccoFMA4_#Rk`N*AmM`xMSK%VE9W4bm~pZ%~X-)D0e3q*h8L zpj;1f&$#qi^N(2Y`l41<8LXLmc-|h?{LFmn2MhTllRsrojwtBf;DX7tUa&HHGPBTC zK2oPw9Gk8@gZAT?gI}4-{wbwMMmJa5;n}XI`p6DL+V%|qAEMhaS(xU8B`GyP*^*%- zJg6Nf4-7TL$HT($6Nsfk)_-8?t4as24aW^bCqL~pgpS_rZLCK zhp~P)*{MNebRAku{7m?0xf&7aeNmRI&z=K``PtTrXrY#=>O;B2)OzPOa&Cr5h`7lj z96&&Mv|_)c&lu+FgAOcL4=efwqYS6n`!-rpXgm|paYA^^p`FC}62&Yx7Ro|L^aMnT zls}T(*GPE-O(g+dI`<`L{c;Ie2LI8o2}wid@&#sZnxWY}9jXOfE&M<4{ocI;cB2wN zmD+ccR}29JEE3q-&`&)5@@^?=CiJ0eir!;pgVXN0`LwkDS>C@TU*KnE%ptQH&j9=Lv)DG7;per$onAN?m-vu)WCGas)rBSDW!8=14mi}dos4uw{Rc}q1jdEWG zXi`PfDNH3H{x{f+&kg#GikMEmwUxy{522%Uu`a#;H(WN>nEO6I+{jKdq(4|I3`F>z zK^`2L-B<4{a^sbI5tbjw2Zux4uO(-k-z7)It^8BdZLmn!NB{!^$Y3ERlI1a1BN{Lv zlB8%#^^~<<;w3BF-%34Ua-BcS7lBP#FX^!Fl3Wc8bAas_B$o=XR}#{>Xrq9;dzki7WSvvMw^D^xNBHi+|%~ zxR-ji>*HDCB}~Jkf7TtUi5cS4xu9_&@%|S8MG>o_;+4n7ut%;pb?>vNZ%-N9A0{Fe z>r_ilBEBoSe=U$luysS zHCk1o^k_-e4eHK1whTVwXEp1cB()8Um<|Fp4s({{2P*hZi02z-vlG0cQ5lXb99#+I ztir$qut(_fYN`2=iL%wxuOTbH{tKM68rXuf{OqX-gOcL&w0`t^L&KjP2)ewM!xYKs z7kuw1$}C?-XKp`w=su!vSVz<@nvPPuIXWqj8_LrVo*DV#fIPdq8i&dYH`jY!rSiWp zS^!#Wabh`yFhF%kQ7jFb^xB;aMFs^BkMLz~!oxApqyedK-+shD`)lSIzM$$)43ldj z%u!XgM$sqm!z2O95<#b5i)hIV#P)e}{tN$A#W`0N^>wxMYbkBcdVw!39WKxlD-M-* zUj+mcQ->j`DmadZE~ALWr@F;tRhqNSXC$NOdwB{M!J)qo&Q<_n-xKh}SU@fQ-E2?& zRKv5X>a3AY2zt&aYbFu^5qmo>cWo_bEo4GSiLY{j`(V=xluWE=TgKD+_xT@C*&lS6?DFtNhr4yNpAiE3 z`^Q;Hgj1fiTO>0GLvA6pmjs>vK-eA-Pp^&c$2gFI8@vsX06P%D_B~zgV-xWeLE8Ud z-ToBJ+ZmOdL?vJZAgV~(HAo_^=mnYW#c%{$J~d*j~&DfIV`ZIgbQ6 zv3v&f5#Fzf-+v-mISEo(WkS-iuKyeHM)UfBS)K#xLi!j;0t84=8?s;g^QnRVz`gze zzlcWEGvfJKgaLW=NDqvXKVd|s3{45+SsEu<<8{7vXS>t{`cy640InRmz2S!i% z)M6X1Mu^8pK4>Nb!yF-j3>X_*$Q^G8hf~_iV}7LnK>_N6(MQ=oE26Mv(0>aY4Vv*G zmw!XfLhW|#(Wi1z0}%s-@%fVHeF4;69q%)$eCB)t(ueioYxFcZzcM~A*csa!@3i#C zOAJ3Qg5PVjw)n5I2H?ucP(Z5w1^NE*SKgmyTUbPN{9_mc`ew(RU;7rYoBv(v|MvX8 zh=S#aT;)Nh!O=a>R5Pdtbga^sb2!Jp3~YwOaXE;D{JevnA<-Cd_V4Y-vABO=O#txN zYmE58+v(s#Ouh~bqhk^Xh3^T}22Le#ZY&A*GwrgchI&+gJn$AAM(`L%fSP)-RIY7F zjgC=Jvd%dz9pV3j7q=O;5gTi*Z?|&{r7$7%rxFmzF%^Adc=P5;;xsrIgYMmv=|4^9 zVIfs^DmBM!&Duc{>~bc_o07U5?^oA_)a_1My&_mKFqruB#BDwKd%D}iXdet(#kYpj zC2U_};d>;sh92J^3Us+f5b}JX$0?b05c+gcdpdOr++AUT0atgTnV8b<{yv!15V+qp zl=4eZcslHIv@UJ2VQ=MGEm^vVyD8ctNUxfTQe8$Q5xehtl zqV@S&D!dvxrTghkBr(CTMd>AY7&h58VQFeKZ>2*-9y+YgJy85@{Q-wBiJdmreD%lq zuT=*6&3l9G2iqorqc`pdJTF1t!B*pr&!&O+8q?L%je|KL1vZc@TgKHi;$Y!A#{&NK za&coSU-f!K+2*l}PQ~lAI*@PYad#wGs5x#lmnjI`dYahM)ANe^ zr#^H?TKK(GVoi*R*Xz;#b;;R6{cj(wFh_GoB|SLRl7;tY&-qV|wP%DV=&fK~LPtv; z1kdn{?96tx22D0j;L{fBCWthG!qd{FsZAxNWcaQ~TVJUhs0fWCP-Yo2x_5 zE3sF|rz&VYpcfZm3*UH)0u*Utm~L&LyG{vWqLj9FWyIWt*Q>wUz7cON%xyY?&p1=U zGk6tPw9rgS>==j`jJhP0V_Z7Mixl z+FP(&u`nY-W#2nGX7+*22P_0vT;^L*!&6i5hOsv^@79NUzkN@rSopL*0{!u0BT!Hu zA*1lYkxL2Hi2Rd(i?AAt}h01XJK#@qrj(|+W5qGu>JTQoh{-}Lgx`0oUSznuuH>~2bzK&+%Bqgwv zbHx_%4i2%%1URi0bh!#01B?%QedzrJn!Ey|4celLP^cpg`LGpbJO~L$h!YfthwDVV zDx$g}9y7(Aq|Z<8Ht!7LC*d#N>7}O8iPDj(JCzI_ZDvDY-=4QzkqLg-T5G+H&|~1a zJU(A(R1h>J=K(bgMR>|&`#@X43&VWV#p?Nz>1?p|jaVERRgLmxw1}_PGe0b@hUnFF z88(T}Awn;HGw++53=w0;HY?T4(HE>Y&y=lvDaKx;3=NecO2%$=Uh~!gA%2HTS(mVG z+FggOsmITPzqr{PL4-)4U~V97)q#R_QH-wV$C?`XI^C|oea129hTL$x1ZvyQ zghdq*f-}5X*PD+?N48hj;UIkPZ#K5-+Br*)#RU^8mhYS=Tw@*Q5Tzt0K&$H(A8l`9 z1bo*snD`Q4QOks<9KSZRLF$91XTj?dyU$se&k+5^@EF0N!-}0%P8&ow+{kJkee{X zBz~ENn%CG{*Zr2|L+yk~Nx4e%y^w_Gf>*7dPKKiFOnb~?yKcrE25WkJ-kk)4i=p`u`xydF>%Wn52QsnEvoo>~h;cJ-#X&lZ0 zs^IC8#>2U~P@8yP)OB@9n6g@e%|#SAC|Ke+0|CQDUF zW^aDl3POKDw(tKn&X#eX`-$>+;!_s@?cq%!k+%KgmAWjBT+Ey8E!@5G>7J(t7<|k7 zix-3JJ?~U1O7x}=d*>#Ln;JO>@(SEy4&wxV#jXAxm6(P*z-H@A%a5ouN1M{QC2(nW z)(+kt4vuPMKwhlY&{?)#6(r?ax!NL2fD-JD$zpdxS6S|YoYPGgjdycR{h#N$uW#)c zR4Zz2m$Lg&ZAp?bMJ{1Imv=OeQnd?QBaq%rv4IC0CamT{n%ewWWE|=!u7G%*ic#HO2MRs~^ii7cQptc>V~R zYKpppH5#3{=_Kl;<@_kOrOKR;s`OMMT|%WTc>a$4$Pl|+IMA>$T#MWO_feB4>@zOD z8>c+yK=g$I>9owFuLKr$c04ATO851^yS;@yU#p2%)Q002BFTmQT37m3(RK@3(`?0P z^2%aLFH6<7pBuLLY_6rlTyoXl6OVhnQjX{=mVB_3S0iaj?K|B{%wZ=lG zFgkjR5!G<=+m#;ASj>E|*Qo7OCiOP`oRm2DcdkiQ!HIrhb?htOmvkf)KpxL_hD;R$ zo3MZFRFP=@C!Xw`M)8Q0f$K@@ml@ma0g}&KvAu>1-wN^`r^E@8wUf1GEq^mFu84ke zesQ`1-ncaWV1YAh_NM&O5UXImXWKu$#QE}XNfJ0Ar7AWlz6%(2s-ET3L*+!DGht#Mwy!6i*CchmkOL=<= zQZL}~yl3e$)1h^$*%L`F?VGH=KL(=P25oH(^ti)QG+zBsLHSMh_mk7!GHGdE>yEMr z2*T!<4?DekYF|N{KpB1Nr=henMj5W!zcp z+}mP(h!bT*}>iq z2!l?L^Y(~`2V>^C&RyX}HT5HdygPa6m@Ny-s_4_GM>vDTmUYEniYbW>Y^3hRetCB~ zOhGXyn1~xSfcGNFaC!!A6xVZH@w4LigUAC33yr$zxSA(W=u8j2zy{xGOgHoPAxE)G z@l~@VVnp|6Nvn{x`_FrAj)wyAsA#m(fkP?k*sl@xJiFM&y2fO(Bg`)$hOD*`^L?^L`m!gIVaMuz3+3*b7Len}@UQ#}cEB!~ox()$4`pLgrb z`!PoX<$tw+_kYEkm9%>gNEzoC-i^3JI|xhfZQWEiOs9};CaYdkXtD4KR7K7d;{?Xccs@E;(=Nq>o2Y{<6AXgA^<^KoR&L9 z8ql-~51Qhdj+TADUhlEyDRAYD+0K;`JX*2lg;kiLw&cjiw0#Ij;gf4b@X7J4&NUja z^3LUab<{1V=v0I-MneXP@ed1Z?EqA8dhqv_z59CG|QkW2NWjK~$3R;$kf zU^Ln-HPRffsYdKgA2_{Mzk8HWqYjBDa^%}D)MNEj1eHTu@k8LoOD6Prt~~n}rMn%^ zu$zgT+x3d ze) zoL)9``wOYC=0E0AeNrFV2GFVH-ykJ3ExDqFb-g%O3SXFb=;*$(#yNxY4O zp;#s&%zuZr;!MX&4Zpcs7~?+aY`te#e#DjU;)CkuOBQuZdM*-ycM0%?G6CsBY3Zws zR16!whVcp{etJbfh4r>;X)?dZX$$PobAt?UzTk6n`ycR8E8b2{T=|L)frS}`aSmTi zrtoom%)R@FCCIg{zfL*0m2|R&QXIc@K&=uw54q@r=R^>$yT$B?>EczkCw?JOXcv@< z;?I*tki7Ox`*YRSp8m=}ACbXiySOWkRTV%WmndC&H8#)VI;;qNssUoXOh+Bfyl_w=3_{yqDe0_ zCM7Lqc;qS%_{l!l6>;J?l?!w1aFY#5RUj&@x~jL!HAWk*(EZytD3Yv7HLCkdEFr>y zJu$j*avv@Tmmks2HDJUml5o){9^T&N$>8 z-eU~FWb{815b?@sqz;>TH>M_v-_0VYu3jdDht{)?Q|iIj`3(iB2(Py_D9-8aAWWZk z=|Wo}C~t22RT>ksjT*W+4yl##(N_#3jS%aF0*P&r#Jl6$HNMz_VO+dN6!6dKco~Mc8VoHrNB+JB7(dnryg41$+oXQTkywBD-O35qsV~?(5&wn- zB1L`KAA(OUl*blqD{whUA$U$|sC6%zPB z57Tb0G76;J#h~63JFmlWNr3>Dl>VrUCctnR^L*my?ebe(M9JTqM6FYk#@BXYY2*xvD* z8>12t|6{U;EswFT$Cl%`#tLn)!(*VZiApYjGdg$6ohXKCa%U!u3EotZXrSxX1QHzgC!z> z>g}k5<%&bFHwhaQ-s}k-naTGN8fBx>a)Zr_?tAmq!VWenOb_jS%a**>x0XU9FVg1g zQ;&BgYl*T*Bd%x!Gu=X5S$S;cyaz{7qE~Yg^ZZcM<45>ixmc%^2(PVisp|MoEDTxd|13hC zPHX4dTSH2P&3jDtoL;O-_qa}&9kidTwZzx=9}nslxKCo1-m@DoeKE(?pW(c8O6P>S z@f>u43IOZ232}ZTKK^Aful+5I&msIVs=WVUplL0F zTH3l@cn4CQd7aizaR!vECW5Z^Lf!Y~4QPIHj+A6V7SST9_)Os6@v)ut{~H1rEE8cQ z{5~;JO{yqpVMW@8zimckC88;@&;XhyOtWQnd;8{44aO zZG&rwGXSti0WhI^;2$s{=TqqacdxY5nZ*Aq>=O*eFpUEeQ2v!RGkeZ`h6$w2l$g@C z{p(_99>{VOU-$x%v@CGw-OV5B>1G)_?!Q5ixW2$8{6f_nbqOb6Pli`7!9{wBbxXR|@rooSfuWK7^ISmo}`Ka9<+ zoTU9{?%OZg|KFgaFOVY!1ir+d@zv>+31g?{sh=_IulW9VoEkh|5%^1#{3mYq|EAR) zlhL*hX~H293zysoZJzTaAzMncfj5qgYe3FB31}G)?~#I>j9ZCL|I+YgU>i*J%NOEz z)~m4nmpR5hX((JYxG`#+%bMppyentrR=%Fj)Pw$eGREyAu{Z+ytk zqn7Z=XD0aY_L5TA+hC9;T=eB}=r+6Rk=8pIX#J?g)I6YCaTlwT2^hTxN`%P7UNX|! zTy`kqXS8H)SQ*<&5lKu2>mfW`*Jzwz9g{$D8hXp)(C?u|05Gkk(kT7VIzy+l>z|$H-E~TYWc6R0fyTON3^?A#6 z3~0T1Wume&d~O#MplZcC?a#RUJF=>YL9;b$42}@c>YviEe4Wmy5J#Ejga}TqYRLLK zhl53+90V#$Io!g{!=n}lqNF+`Q?z-uddvKzv9>P29m#w(Fx(8tygH{{Dx=9qvRPMO zc9ZiX_FVR7Lz!M0t*sRdBc~S^Yiz^BU943Vq@~=+pEP)^vju$^2dkT3{L1jvcUv7O zvrE(RK1O=Zs1XA!gQ>7D$5+?aTnsa(g#)CI0VZOZ^6YnR^eeid1!XNuYQLB0fLHp4 zo7+xtH1_^;c60WngYmrK4Fx9wiM^>};mgE$g$yi@=?jBpzL(hmKq2PWQ~$M}PRc3M z)vxP8%a}fbaoYSfb_0!J93KvBN+s}iyZqL2Yo2m|XY5uEuk{Mlx`fl(E{}JCp<{}T zF=rrkkLy!!Su;LiY>YZ9qAj2dfI`-K%Wz_dc-;gLwVNz3oC{bURHW-JRAB)eo26Rg zL3Y&TAiFQJ+GE$I>kTvBE+m3!@L1+oJysCUS1cd}_Cs!NJ}x!aVV7v@yR$eQHkzxG zDS)&=ab^f4lMexD^I&fnfvV$laf9rYNrn0FUbv&+_S~Uo!;eC|u&DbtcP(HOYHl1s!y!;0L`EvHZT7W|E_wE5LCxA#M zOgY8%4;!$xsZI!>fr8Fu0NkHvG*Av#x>RL;3k4VAmQI{2g3IQ;ZjW-nzA465Rv$#% z4uqPFVJFiv=HrI3X0L%P?>C-NsqO@%MwW&%0QB$=LD29a9#*CGAbct03s6E%8z@rz{wY-Xio0+T@=07-2iF74 zat^OX0_N{>V1d8|6#yCf1YqPcqLA%5T12jipvzC9!6)dil@Da4+(QHiN1@StyIo@qB#G3J1tH45b3Hw@IAghxk zAfV{+GwFPdh2)!_OZ-_~r7-Ip@elFOOB!{sBf+d8u8{f%_Xj|Nd<5T~7Ud-PF_G@q9l4(#0MWY& zX3z*wwSI)q^Bhh=0>sZ~NtGO}crga{0h20u+hm`&8{W*mb~j4};ge;``wGFA#!z&^)sKO+xU8`_7_V5p|vM8_ytQ)CH_#EU6sQVfQ-^y2@Jp(6I!;$OT zHTy%|6oVuq#|~~RABopj@ZD%{7xJmF?5~!h0CK@PK(&=0&vx}1+pqYdyp4i`r35Z% z3JN&5xU(h>)>jd;MA8^9MGZ)_i&K)4U2P_(%Bq~&r0Ag4@YR(Qr~gHkGQHro-ubk- z+&I`~(wI#R>JfIy4+nUqodMp}JWxsa0F8HUZ9_F&5D! z#r0+gny1g$rO?F<7_|5iWAOA7)Uu7;f8vMNPBd!4cWt~nQH9FujhQ5_8*lB;{bIk< zAnK4#8tUs{1k=wEZe^xNDg%Bs)^k9&aGs;=rucy&kr|sj37|h2+^7B`Yc-mr{JKn+ zC$V3<(oV0@k6BR{_J)lux{jXS-Reo5j_F+8{&$Jmfz%t*%G$c@ebfzP3wR9N7biyW zz;rhtHv%U7)ymLL+z<$4G&l@R7Tw28jW}tIUAMW+;2D(` z_w_~>vU3<;*vc~foYop8vT(5k_cYM&2u=<=)aEusV4S|YmxuH8<&%JCX4S#xZ2Fh$ zUWM;uWMm31=o)=*4(+WXeNpEH6zX?7o0`TqEzqKPaiqBTL=8L~c;j*RCi4_7@sb(P>B|zgro*NI+pG)EYET9Ef#r%F;y9F?9 zXRG$Mi}b^Dx0X&_1XMiZIx>SF2Dx6;d;!b@d4V6oA*wV*eiy`-Z9na`&`P1n{5r`RVUnW#2;M&)1x*V4phZg?rHIbnb)b@=UjUp8MW`{Qr^3q0rk{#)kjw% zAOmx;=Glz$u9t;BhKgTJ_=m`+>VsWiv3`lqNNX2_zMkFzm4EK;9z(zG9!=Zg_-K2c z%-gz>FqvB=PkL|r-16nvIN6w;dg?#@VKSPf43rz&Y6yd+#L7)e>PNI&n%p3D@M?fm z>5NglyUP)yMEl~vrfm0qZ{!Y&p2uz+woiu3SOXlEgpdN&ajRRPa5FpJVRyt{((tj6 zD5iJnnVR@Y#@Vded=J%a(eRs#ocp=gMW!vcD@73kc+VJXXusCox%Ksy5W7S_1~7ZC zskL3H=zW{!fs#^26$uo=`aO*8=65kj#xh7H8-#9eDusAXT{A5hMr{QYuf?ZQsw4Qr z$Q6Kk`z4U{ap&g_3CJxSE{B(5R0N9JWD*6Kk40(HNxH1mZwZe95;6$d1TC`V8i<*a zID_1zO+A@vVK)_<@SkGTZ?mAUl_;W3dbArnAnFjS}Y>bgXpOg@vSf#GuhcD zj~C~fit6N^q7$LuyT&ZdSL`0Q04xvXKF5Ew_MkBapKQ#G{jMadp1~#7`;8eRQz0D- zkJ^_n`NS`RB80Y(Irj#v;h}RGQ#N^D3pHEG-J-iiWXm=`$EPR0{5GZXqUsl00%#Wg zzx=-{k=hlSO%r##%7K+Sk9FVHrkbTY$O8{i6xBQT=}V9U?ZbmL>GMtSP)&91jroEP zJvjHJS3ita|qIXlbTRLw%8=67RAny1W6ml3mc`-OD@$-hRH)fM!2c7}Wc+(ciaF{Bjn{6v%W$@D7U7ymKUSenrfo0bxuJBjYsIc4;lIr~(*$`U~M%N|H?GQVqF zZ0gL}FMCtm1}?boukur=k>n}y8z&?zuJBveC?f+k3v~QS0aLk5!u@d8(lqSTXn;KH za7nhzht`PcMIGejv<6N2yvkICYT5hwdzn*z!d!Y7K}uK^^E5Uzf%nUqi@Evit&5tcQ1U^sP^?`pg4%F3+nm`$Ht^rL@HhIdXE zko@~UPG-q*)p95r2W=3aD{22qI^Uhil4ord|E9$2OQa5Ph=en#^_d6yPsLjcQb)2J z-3J;7rskb$>TNZkvHY(!eXHD+WPs%_>Rm{?m}YW{^7iI(S3%K_YDWZkif_JJ#tzI~ zD_4a7>jr0ee|7+y0Yo)C*SdISe%Z8lp&pS6sg0e5+@Q?a`iTkzx)CKURrX zy6V4@wUzcrftwXE({&H>fOiI_))XXVvn7mGWMTj~P1Q{9595&Nyq)8sFny#wkhe3c z*U$-gkVoPwbIPUiH_l%N^M$*xE^#`F+^my%kDHz~}UT&`G zRH{Q9WK|I8go8WG!9S#Fys!W&aO46vu(0i5k!T{S#VrC=J~EPZtd?4 zlUm8=>l*+?F-9(B`5}gVCxh7TAH*Vuhgd?aE|x;HkKJ_5{Z>uso~d}Jjg3#B;bH{~ zZ64eAt+3pV9n)w=TSyf_ZR$5z35yzzGn1zPh3=#Dc{Upy@%7es|9fQRa6KVLz; zAQNM+;_$FVUpm%g^7*ZjqU7vqEQz`7_3Z4dq&4Z)qhF87O@t4jC0s(>-GH#D&dF&Z z6m~$7;VbyfQ6gn4qF4Gj=oL<^cdRj(V(P zLS&+{iElZsAVU5TiQBsG42u&WRC=y{nnjw=@RE>Ui2j`B^L_#qkJL%KlpKF>_^b|p zE?Y3>xs|da8P&6wVY>l#OU0UKoJD*p6*=m63sF#&LW7d?zO9ITW$Ap9r7Rcag3yan z50dZb{<^e}uJW2fJV6m!&x; z>6GjRRILL3o#exQlT+%n1&)AS9d+hSF{%CKE^}Z|WJ*W8JZ>%*jtCujrQaADZ!`Y@ zR;fj#zE@@(XI)o@vbaMDXYly0gRy4QMPI&t8TvtQJS$*}Fo_hvI6gzrCyYn!%r?kC z?te{m{JmI_Tg7UNiEq!Y03xTSv!&QMQ}@&9XNTInbCs8*Zuh{?*PeWpxS1NC z@&pl2!NbG7x`0VV$d_!x4R#XFl%Z(-&2j%K3ah!f(eb=!=D2$7Q%?l>$IWX1BNt78 zTqOEuVu>w%JqG|_cJ**ugs|YdA2eY}%)XD*FYm>%kv_nUv(QB^sj|w`w{lhP_uQR7 z^!IL3%5&Vkp|bk+rueBQ?bBL|y)J55J6IF9#_*65gLx#Q3#psU0{ozx0!?MohfN~Xl-5J})JuZUT_(K&CGh)fjC;x1|a z5~#v5tI_;UKq-T_u}H?}|6&X-k~RWXSS;s@^G+l!4p(b3!+rm$e27aZ{assNJH(Kt zaDz++(0DXPdvIGP)bg>~@bY-DEw!HZOfmW%Gy1W~)V@n1n_Djm&JP|TF%H&k*ptyE z1-wac+AT$Gp5%|u#b56ZOo4_24Pm}Fqv!M>d`d2Q z&hKLGAZ+se)h00!q4s!NKzf#(jDm>6U-F+xYA%DSc#(_nK6Ed1IzUf41HJr*`LI=L ziXJ6}%e6R0Nc$~a;A8H(e6RZN)D^zv@&`Ki%8m(-Q+U6a?Bu_0uMK7*v=kZyU_7yu zpKMJn?Tpi!gEMOL=&arIUrQ|j4snC7$>YoB^0#;);vbdQ<0;x0l;2-+thU6ZI@noYPiGyp zwXS-$H+-O^tFB}@(Jr%6`rvbp$HhJ4ftWaCnUlDX;_1W3cjjCzz}`15u`G} z@b-1g=YEC@22+;eNc|GQYP3Qo|QdgAdVHBu-3ne)h-_wdWFg3~G+gTWlwu3Dq<6XW;ms!M%3NBXv3|ImzswE9o zcy*S&YLuOpXnqqIfYL>vfgZ*zvKJ2aTE}~2Fd=E;Yf$hs`eAPIJ_4WN4Bxe)etprz zabpoM(1g6!UXqa(1>ViGqIP4&OP1*9o0GcVvQF)aG6z@;dBI<-UQ6a&;k$Z~`+<>6eU7c@~zELQoI7XY?x1{g>E8MyWv%8C5N{qPgS16F~ zz_v6l{#;FlIg-|;#Bik>+OKjob?0Xh*gh*5%j~tF!tAe!cyOPXf2>Z`H zy;{9Pce;6{$aG-Ab9ek%yt8$)rEyy3cXixc#uU1!!}3My!}GL#>AX7@hQd8|A9L*U z+7k>8b?9cwre*@yD?%6qz(F|No`D;kIF_e?r_^}RM zS<<8I=|7by!Q4cUMOzAK%B6uU->)A28TJ6Xt83J#UnVpmVaw*P zz01-gz84gn{%3##t-m)ELe%7Z?#^MquD;1Y5QXLApH*iTw6vANoQ~ipcdzCiRVFv6 zm&w|;agl%~cb(NHgZI`0hUDamSl;r{MVuMRn3OpP?W;gjm=i56=j%+u)j0BLFW(3w)jIh6f>oBz|Ns?;!^gYwDLMbd#^2^ zIvB_wPb2JYXKno{n4N#DSw4b0#Z?(CXs^Jmxb}TfHXY#`UsEb8UNE#3&2puH{Q=YI zG+R15hKGJr!#@DW14)sWoLaXWjexT7x!Lt@=S3;mi`Se=E6HShfn!&*#WvhO`jaco zyLN^FOCuxi*zLF`q)s!UTpyWO{@j-c zej_J)H^koSxU~jkUE*ET$*$7;mS(lv6+KW*o|YrD(d-EvRm_D zy7>EMFM2DI29 zPy@apSz*&{{-oW;szN$bmytT4z{T%`dwDeL;V?04$QFOfXe-@Bi{X;Sb{BbCmV*(% z{DfM(S39SK2;?<3Z8Tn;IT`Y@=;%J9FpxBid~2_hfgS^03Yq#v4Unz(rMlm!(yA8X z4ORvPe-gLWC5z9~E>Hs$(MMt5k7o0Ww#Ml9I&(pj4EM|5Wi;bEGDt-K^z7Q__9A=M z+4v7h5k&PfDV~U$>@d^VPCJUtws6kvc($=vRi}6}e0CL}woAzCdXbS<8D8I^@KUd= z5#R`~JqNX;+o%T(h991_NEd%jKC)v22t4^digm`q{Pa9;1&b4{KKI6Mm(LXI#RrH+ z!~h|Kg0|~F^us78KwbenN1Y72v3(Ab?a&7W57*TiCjxs%os3nvu|Z8%2X?Pu{DA&i zTUAq9pp}vEy8VS=G{g%Q-5O`he7)R=>ROv#d{Fm#>h=jmVmq@5_G8o<&?zXsfAj8h zvX)W9bC?Xv6z5ibVPwX*9fq8=eBQ^wythb0v*P8;xKv_SFC!({)C5f_5F3$H*0qRN zj}TI5`f1KhNJ4sK_8@<6f+TN~>mZaCyc(xv>;kW73u;hl`!jB%u>CJ(Rsl%YUs_=+ z#0UNx1{B-~LKSGL%ih{;Fwafav@s#FUiTL!kgW@^xxiMr>J$b5);+VvFM-xaeu*77`l8RDIv+k|n|7fiTCJAG@hCXppd&7R z7#>mcHshO2!OBmEfkvC44@*zNt*GuJ?_-pyy1e)~;*zI?)x6j;gr}2jZbDL&|9pHG zSnZq_Wy}*$$N~{58FoNbu+PqIg-`Z>=9^z@#VZ4v-FN`bZdIBi)^9pmJ0z?^p;c>o z5zbnbs!DXUUcf%P9pAr!pCMR3@`G0w7Wp#6Bj^h8(Am1`XyW9-1C5nG0t61a>T|)S zWv$v4cK&y7@1mx)86v3?V2qePVVkNB7V=@A$^+zmm)n}KGOyZSYP^@y(&|aOjhaR_ z&_7R#(c(G3c7y)-_B;2YtXjRkAfEz(XmB0|#5|QE8bKesfuW$wuVv!gK-f&0rzA|> z-VJhffr;ZnxZL3F+XeJBkm`4??H$!~Y+fvBnivJXDRw&@!0CWVwo%tu_`nTW1#5d{ zUKk`H9-rZZ@Z1fRTco7s)=6WDIW!#vbtMn#Z{8}Z%>DF-Pe!8^Vv-AIGPu}+mMg*p zqNn<{(y)rUZm;H8rkwXPL-YJ$Zh3{;0Mbp>m=|$0`s-4_GWdvjy}jrRvdyaLeK;e8 zeQ~aRDWBRrKU;w3DeWQN1GBD+p}TO=_&v|+nLn&ao~I?E3}NaFC%-`g#wx&Ml`{V< zV0=AK1;~(}T640f;=$}dblbD4U67E0wARV5W* z)!Pqcr5#i?pSC%S7Q0#Wyn;jGQ|uNmM4Z>|K!F81XC<$3;B7S@{NZQnn88}Hf9Q|h zUM6u(d^H}b>Q=sEuaqJE02mVBqS}5-deMp%RSl&^Ohp)S4D9igz~AaX-`hcvY^@%i>>h#tTaJ+yxtjb{qO zqH7I@j*$zlG=_=t(?s~;RkuAsO&qRN@1zdLof7TsW_>a(#mc?ppbvmx=i%n=$?aYl zkZ_0<^lezLA>bw#v!pF*0;Fm@vEbyZNk(wq1LQPi5t1}kqdU^kc(9)(g7_RyUlLKN zXxJ`!|L&fPPQ#iVYv!SwC-HxwXJf9MMh--QO%m)?^usVl0oR53sBc7UQyh>e)o6`Q z01*z5heNd%o(W>p?(UgkT8mU>1i zrGv8hir6aZ`Y_aAVUdq(8|4EiKm*;tozl^*=+@Jt*#7q=j?puTYIWIufw8&<09Z8VBNB=vTrWUKb}vmm%DHlLKi(k&fBAsl4VBqzm1n(VZH`QBbBIXakK)!Q z6I!NHb$^9jM6&N3EBy18`;T;i4P zv)=E(azw&>1|3{on?_P22-Vk~#?*5AqUJa*AvFD=o8#5$NT?4;V}Nt)Gq}$Ee&yY@ zbJTNHEG<6*;tE!YHopOMwGw!Ul;Nk--%B`CFSZaI6p5oAS3B3^)uoW8bM9XXh2qw8*~H(!p69^|FE*BY=RuiX2%%^gLcv0!47i+NE;CoOD)MgwiW*t0F* z8?5iRDEs2wI*n8C)1HJqMZj=$^=#jb62mFBF5e1*#~N~B+&V?z9GKIrt`FXC@(A;m zw@XioMh2U5P0vNqpEiHwh_D^Px7Eih9^BL{d5^H0%wGK#jn>CxDJ3Q5ZCfF&nUH)w z6oLDm)y6>eRQ{Qw{R~?=9--i`pbifOYIUwRQ%iMbAYtvG;C6f)O^?k;E)}ey-fDVW z<2JGs1|ajvt|w|IF+RTa3*-x~3(;8Dtt;#yPIB!6K6^Sh9*?Gl{R&Xp8sO4%h!tg+ z1vcm*HRr|Wcx_^0I+`SCbhuVz&L#k-;leNd%|$;e`q1DeXVz_JuFn3;YEK~kvd;o! zghr?a|E)S!fq5nmBMVc&^$5tQm%r5PWSx&BCfE|tFsbcMKiT)yHpnA&&MVD8n2g~ zeCn*bKAc+Ejk4LMQF|T;JKI5Sf`%M3FCh;NWVQJ{1*hhxFenU}8l8{6efzetB*7+| z4o3c7u2l(|t@Bvc?d`pd)P1myNjjj$p8P@Y0<9Zwuof$7v3FPtcMy^1ME_c=I;95t zerwDl(weL&N@Xvav<-_U$ARMRt3N29@vcZWLD<#pYFlpr`_)y*v%aBCiY68SH@Q@U(|Ih48Q@aUt7J z2FJ8AiyrUH_3!84?@S23da4+OTPo{jukjUfvik$4e*J08`!fO(60|(G-zzaB;12PP zwqY6Srnavar%O(wm+tP@ncpsV7R}znMkvesb--t&XHdY|7menm3p0;`y1`#s_!*eh z*g_td@xvx4u+XQLo+My`9(B#j-#T%W)Y(`Gd06Q0xgD3}aE^@soC;)MtB%#OSZL!b zCD!xsews>szZR1SSDOf5cWIq4*3kFKP3?+=4E7wtVhdehmLO1`% znKU=oh7>Jdb`J@bVE-kY{NblB`F&h!;^Y^GpZT@Op9MUB$nHulNJve_WjeHSgUQc( z)I>{5A4rv6Ed6X_6G+i;Ug}U)*8P+PN^JwFZ60~QdKkbVia}cBnBD~Db31QK2;QsX zcIqCy6YUQbItIg{yo|=PlW!b`u90V)!o%~?M#CIY!v;eb-xY01X~n`1GvLKpS#!< znF=dLi~Hh#_x^JXD~!T>z-j*3{=D{qir>F?XKw$WO_F8zhz$OFhp(6 zEblQ$|G6UIe-TmUgYf?x@L|9dw&H!z(K8YtBD2XT!8h zu$?WuO&tvlm9lv$E%B{u>q|5Cg?Zm0!Dgv2lCmZ5)6TNe1Im2VPQy3jZMz}e%2w!3 zW9(L0;NC;rsBE!^*m!|ntzKu9%oB}YF5gT9i*>&8#7o&-N3|NQs<7~JDJwd+-51@& z?nRtqO&AVR*w@Cynu+;%i;twYdM#v4s*7qYJ1~DbT7l<6j*|otP8r2t4i9~!CnpDJ zg2iU*P*VZZ0w}>$>^{SWK)&zAYj=2Q4Lhf-y;Zj)&+#L^2gGrN3=DfJQ1Ut9V%q-n zJ-_Xtl9$w=y<|E%<~`uAbM9&bYa$PpdG|-VM6baN5?1Ji_aKjp7mx{olapx|<|S-* z$!(*TkhP_n+lE2qQ7|z~Kf!+19iD#Xw0_g%R1p5_O$R8tc20~J->j>7|LW_&-MQ!s z0$qVRomvA&Ff~<&ka0ipb}zN*Bb0AkY-maC?dD}_#+&_0k9?=8pL*Krqnh<5T2b<4 zu`x>KtP6GfV~thc#I`}jy=3b*9zfis(BUdRMLx2!uygeegHHrGjU0^P4CX3SAYtKp zRg69HC=yt{X101B+zk#6;!%w0YBr{?952+`m@I%A{T#k~&2G?Dm@_}8j8jiYZ7#H4 z;D1uI^<7a?F0<*--usq7EpHV9_ih93>~f_dKjJk?tHoEDI`P-WI!%r9$Q8h%p7=mO zV3^2|4osj%M%jRu!@#!a?=rfkxxPQe)L2nqyMWsqIiJyFWowz8RjlLx6&0DPqEoiS zj{g|DEY6`=1yoQr-aZj0>EUYWxsS|=C4sfO%0iE*&_n*YQ7&V&E7f;*19A+#8Y-&} z$beKDloe`If~>1@A6lk^9ArZgJWY>}M>BRYGU60LVV^o0hn(m6kO(^^uaMa57&%&L z1+O?L>M@l#y!?W>W-fJr=!%x7Ue`cEH9i#5#MKKRQAUkU0|R? zOA!(f)Xft~&n?t7QG>i%MLdSrB$h6mO=q|mZxFh%-`Wn2=(S=U;SQQTMcIw&!-Tz% zGjl>pF;91=3j(e-$$Jb-m2)xFMh>vNkn) z*vTKB#mdWz|CVt!Nq)3%YFa6SZ+v8`&oB3dAZM@+MrAnpM2b$gZXNKpQu8&KJKMCJ z5su>0CnUyogp#ax-*4xs zNIny+7Oe+!nA44Y6XkSY=HKpR&mudvHm08kpHGa+L*4e)KcO zhd1weHEUdXYo=LCw#Eztq$p$R?ayMq7tT7V!@SQ?c#aQUEx;h+{9wv!kCmId3AtXP zGkPjvCsukMQMpf+v+YNKZ+c(kCsU|zhUt-$Q-gcLYClfBWXK_)t<~i-Pl}9svFbIa zP`}5s)3vh592{>a9wujCc!j$v_HZm5;II&@E3TTueQsVJnuTg9!GIJ6iaf-RaRBqy_>#WL?e*k5A1pgTK=-iW_90~ zpuv5H{c%M}-7IXm;)(OuH4zR*lcP{7iiBZ{`LP)2uk{r;e89F~IxN5Qow1z}(9Y>R z{8xvRX2ULHQq^Q(NO6D z71pTN#iMUaNy1C>j~?p3_sQB&&}|aq?b}@sneYy6POAuW{tQg&h6ydO)5pPaL0mnl zO<%G2RjPxDm3#1fj;@jEXIx{h#K!4)k+n`hYl7$xT)v|){EH^#p0PSm3@N@PyUbSn?_ z%l#GBIpst-OEgT#TMm|Yc*Iayw4%;2w`#} zz{uJ8mDNf&hsOQ(oFLHScMk)8>jkN9sZmr7LQ_fIH{{f`TOwMwwX5Pd4BlLH3YHrpP!A`$^zl8ivJ_XiyT ztazeELvi;rq;)~vuo~;H#U{G(w58asPgueFhL z9K(qRD0v88*J-4HY_d5!RIlZvy{GeQcgv5!8)sKv#Og~(R|zFV*uneDe#g304(2>e z1626jZL13HaQgy{_gA_hf$#1tj|QAl$P=xT4B7 z#3a(=KpHnpK){u9tSCx$#l~k%d3iKSHWazs*-yzdC7Uuh)>8GcwLim+eZeeC6BCGyB=|f$7CjD0gRGFBf}o zIuS{;ySB3p1V^bPcG*1AfF`@A!)rRxDHmUm!N=LopYRqEJVX(3mz48ri9q_vOHN0$ z#lg{dsdCgmG{#r$eqDZzn6(#Qx~Qqto%|^5AE4iXgOQ*I_T-Stmb3knaww~Cw>i@t zAurk3JRf234}Kq7Q4}925_)h_=X7tWj_GV764j?o&x@Qz^NouSttNFQMslbVK;d)Q zKqxKmB+cP4#f61ZRh=P} z1(d`PJO`KA2MpceI2(uBj7IcBe}f{C50>$$L++^ z5Jn{%dr{}UWO9`XYR}iGo=kx5w!=8Q+YwTleE?$|R7xifjBV?Sx9Vj(X|&6QfPSW1 z`WFj$y0p~B)THy-+?RBgj=0{H!TaFyj#?5Vkl;;9KBi!YlEPqWvxOWe3IP~fk2!z< zbM#=u^!|GUL86e+#HNkB?Zm?OpuW9tjo%(o*%3yr2_XDC=A>yptD3` zVw{RqI4FEk&MfdHVQ}KUfA&h#Df3u1HUsu~iS8cGq(aO|g%@we>G=bh82>5;!%q7g z3|BpH-4iYKIq_GF9&4~d!lXwcs;uB8sRME9mKc;ME&0@-^$=2ExBKU@MP_T4gZ76y z$^3iR7k0Y`{GMqqpRJ$F;~;%!v*j4GGy0<+&-HZY7JP56mQrp>ib`lmmTU0qt=sNA zn-w13p0s_#d4}{881Zx3IKCV?>{3l%N9?C(7L&V4M7lubQhZu~@9};|T?toZKJoL| zV&}mVQKw%2b+-Z{lT)yl4|EQGJcB=X-RPt{5r%Q*6zB>%$S1#{RM-CY;GdPLCGa3{ zymf}cNmq&rAVOg4A#~h4eccd)s1+$bM}PI91t1f-I+;A_T&I(J%XY%!_Pj#S2_1kf zlg8WNV+wX~6I1j*rsGUau;%@d|5nqQ3pri6kQNTAKUW?~FUsZdWopT2)Sql5TIiBwhVb85-nKRWtex%}VLkXCK&ep! z@D=s6qPK8+|A*G!KM&DfD-BHNe+Qb}`-vbmuR#t5HMi+4b2}eUF-ZBeNCsh7GD6Uh zZ-sp}fjCapy_0K{8!P2Arur4anC&1?8NKiXRW>U^~ z?ZQ>5Y7%NB^#W;pgZw>N_-l`MYo7J58^cGlj;pGbHVBvi7=s>C!h7FM^w}fz%t%74K+)Ah zG=5xg4Se8ykH^=-_o*WbMbO*kL+vZ0Q|kxOwW)` zjRa)6{dF`u>A*gHC^zr1orqEzvd9N(D zFvOEs0aEyR`c!ifJAxXRP6Lb@vY5slwqWQ*f0YZ=n}O;H4Cffy`K+i=I>fWnE(hyQ z%D~*A42W-BuZ~#u&~ukE$1@Z5O^fYw{jKue8`dk}zPpbjpw=ur2PUsoEyM-Dt7%m% z*}=m2kriM*uHcy-7|H=aJuD0^KF~-g(feN2=25=9;BDO-tG$-@(0MpZm8%Hu(3ycg zwkU#$Jg(h^`$Iimt)e0}AnF-9d2%Fl55asfKmVNCTtoE*_;=LfDYd_q=Ru|XM9ZzV zXo)s)m{_I5;d*dExe(Y`(Oh3tnC$zGbjQDRlp+Xm67f={D)u1<)&O&&y|XTocAL|+ z7ruN8>Ok%Fh^eS@^6cm&6cj#BCCPHFHJf;A5VYcXbHKBwg-QVijc5>8rjUjZy^u?z z5k?0nSIVRH>{53PWH#m9JNDqi_z z7%%l(c%CX|CX|pV!&mR=R<$)xp&sR$$Y+vmF(eqQwPc8Wv*B;#gBTc!?O&i2{2grgZ9eVB6r}5~|Zk$4Co;o{q5XF-**SK)?RY@p2vAaaL9w-cJ!Z zn+5}+dy$#^%6;TUtBJY6g-(CuR9$%cjO~4rPhvnBXR+ZCm+Cdba+rBqDHu)+-W1a? z3wGR@BYsCLmR-*WQItyA4qh#k|B?Jd7pO8h6ulN(4ea|BfInN9nW>Om19`CH-*&6O z#;ir3aYf4N`^rj=eKd(xob+1c73lDL-n<(Al+Z_93Gx*l*u=5cyRNb6#SLu*WzS*I zjoJ7gzD1-W2`#y*WmXa|7eGGbu*uCcX>anU5#rj#0YTBm`Xa(0s+{@@;~rfd?wfXo z#&YpAy1FhcLr2O<6D_iO9-Ihc>ZvWUSzE%GK_NlktT_{N!od#o&KMi_Uhg+gG+e@% zg!nHdi=_`0m!3ldR_0-zR-H)2rj5Px{2V2l0QXHHEy?K5&OPhrqxSO8bwDT*5Yh8+SKFrvpBx(k!G0DHxF~`l z?QnxnxIG}v(Qo;$Bsm*HImzR0V?jnmt2br%T#c^n>12f!`e+l;)RT1si9$2yOW%-7 zWTYiDSoW~w@b#brQTaCj%WsNFNS%dSR-4NpsafQ0=SKm7UfDFY-mZdN=v4`a(40ZI zE;N)2r;K;A2a$vAIv1qdjJ^btinuamgMOQbbYfaRzgl}0X)6ZEb~@|r<}W1&+8OgX#R8z9Vv1xxg)KPXU3s!_QewNPZ5g(bVo;%xE}rJ_etuVSz}$#*bT}; z!mKDsBy@K-IZ&k$dPS7rO-yqgjWQ0uR~3=?LifzJNo*EsNJ-Aj9m*`c0f#xd95e=y zS!;|5Spl?X90?{W7TS$NU}%5a-+SH%FLF&DGaj!tW!~aVq>fNQ=(hl!gL!8-8(jjNjNJTUC9S|+RWmk2|JC%*;4iyyM3L$B`Oo#+a)<@=s{!OJSJip; zy=Vkn7{+WqoSgPO9dXaB+A2H?^`3hN#8?Cs#kqG6&k1=oeey@ocb=N@oK0;FyH8IC z-%Y5+L;m0(d_xk18wLiD3kr=FL)uokT@vCi9-q@rM13!N-o=_gksTUOh7+ZxPmZ`u zcMeou8Ri3XG2UsYqAbpgH|gQz6c#4de#SSW9*AMMkNR+h{9~;eGGnd0K#h%!KI2^q z?XK+a+3CPup+MoI>Eg{uhi~_K1}4jXzua%RYfsUUth=9B$JlaU0JN@5_Bub{ zF(o3l=ABXeNqKsO&b{+yJazBP-TAxoarw~Jgdc!cbfGSXl-)5-MJJ_6%hD2V)_dEL zHPh*x=LDc{lscvV_#sZ)&YYc18s2jG87`lT9?Wm^m<44R|6K!C@-{l&a5PF`3CKYz z)?mfYttqAYSy6qsjoKlWx%A|-3UbIPXsY$DKS#F6S@uj$E=4CPRy*@=#Qpf;yI}Yb zU1S)s3JGH=&}8&Kx_c;Trb{Hp zK3B$Smow~ZE37@6=r+GB?-69|jp&8S66byV=pS%sXuLgsaTOkOEHdu2#_m38gLJK{ zb6oPCJ|QWohbhf*dz*dhBXdC0Mdkbc{$b;X0%q6squ_ zF%s9v)lSz<0ahuQabog8=`2i0SlIR65-g}A3vG;hb-C#@Kc|Sb@)w;byF*cY>*vh5 z^@P;qn~RY?Nj4(q`3xuv8bcpU)%h-noX?g_QA9IANiKFwHE$BvJ?=J8hJNgC9{JD9 znoLB|BSf-sQR3jgQ`0u(D0QQZAbyKrqbG;UtSW{z=ikx)X{^CaEBLvWXs$nlA^SDs z3K8V$gZ1&6EAi#pv2I6(h{bhyO;-1*V#!wL*XG{6PzuMBy+f=^y^3t7&yc?2>p&P= zw7MyfIccCZX16RAerOFsIoc<{1txkivMvoS;(w>*x!M(*COxIYRUCNqL?&9oQ!4RSQ&y9h0A;HtomY=dnA8g>0Qls=OO0=>-gW=1 z_RrAd%)Zc+iMDVZMXqJ3GxXSG3biZn(XCk5!a3JnM;+Dm8qW@4xuTd$g7jk`xA_w) zF72-;jCxH+{ANERSDQ{VC@;96xfRP7nV(FXz0L6K4@+w=E6<0_f1{>`no)V*WN_(2 zd1B2zNkRPqxe0;D*k<4Ulk5F`Wo3LU-Ml-jFpQKMX2tV@L2nJS-WdRU3QF=mdXVu> z&)+7Biv_b(GS^ix_~Zbg;M032(Ejh=cf!b`)*1W^aMaW@%8EsBub@XXiLI=4c|&pt ztHfBno5>j^%BpsGWnp-K%)=d?HdR|f4!eSioFrX2{sO}K_>N%c#gfW}hQmAPY_ju6 zf*BNTq9?s_L|3J+lWbLzbsS#gdcX zs&r#^<^8#yl`oPV=X2%f*-1HCNr|^|6468#d8E;PAtAb2!<&sIw>xDE5Qm_AZVJ}) z^5AkJ&7GYcA|CtJXirIV+x^(#AQ_ziVI7pkddNXh8&5`ZJ>8Mdo+p@*e1c?yI*0%q z@B?R&|3;I=g28O-japR?B{|cxP}loqe=T8W3{JxS9e_R|yVS~AZzeIwz)jN|_l%@) zp)zS?PucnDotvck1$o05c7pxY8?m4~^4zX?a^2w-2&8w(*vp%khl3bc6W2+US6|Mc zq>)QI07o2PEM*(1=c|D;MW^=%Ak!DSe`2WwvfMQaTE~lS_MU1Z&%D*_zBI0M&y21D z;z!3@UI>9&=-*I8Wf`sE#C`k_!QqjLs>%j|nOXUQ)glR5>5=)7@tn0fEXqb--l1o_ z!6Qzp@-sslKH_L}E&aJ?>WlnlKR07LC#v!p&IqO~QY@7OyhXKsF+-nvJl|@ z#_aCuJ`OE_5=B{Vw7Xw?`1Yzs6PEP`({&Z<0hreTL??xmB~ZQd)#un>cfloINZk(3 zURO-*XG8$w-km(mJFu9A|2ERQB&v zw~?`zey;#16QboMpoaD-oyE##^>dJ{F4-l3NYz8qhI*X_|Cam19Ec{$FXlQhx9IeR zy?KmPP{?p^<8t5DnF_?N9HQz5q1(-QTdAJ1>o(n=ZQ_8tOv-&A;r%}KfG(z9HO~Ox zj@oP=)=gs#Hui0J@n>0`w#O|=h0hc;#d%o=4_8=fPkI{Kzn3{nAcv!yhk+MSX<8okLu;JuBraOhKD)kJ8D^*!gbw9TqIz90dm((C%^Beto~R zz1UWnAOKmu!EU!@wbB1V&%Nqbp7JB^c=OeBXeCy!-&b6)K_(4xmc(+-^*8+)T=(|Y z&iW?F?-s_P!M+Nmq;8$u*B;*)uX3=Ef;#4d`UDmGg z$mi#_rK?Doa}Jk=3={!?vWQ6E;Lt!cYcB|VNGvzoxJk4Crp3iI`8A(8A!=p2QZ3y!h!`KK5~cp^GD{& z!xz%$f7MmRcoK@(-Ns?(yK#A)k9t@JF?jkry{%ij>nFlz^0&FK2TD7!tveK0aQkT4 z{$gxC8!)d9Y(NR?!@7gz)VsmK&Fj9CGz|=R%$=1SoTv;i{4|70Lyk#BSa7gfGQo2_ z(WGccS=i&-si%#)=Cq8KEnZ2^qa<2qePP6H*@Uvf;9zf$jyI5q4wdfwKtaRu^;|MK zA$V^NNDTPNDL&SGqKndX`99}(G!jIQ%6?@qYf_PkXP+y;f4D&f0rBnam==)KOnu+k znaXLZGRcIJ4??gl?){ z*->S@m{pi~m-XBfQ-eBN6z067(-L@ob2RNPM+9WO-vZy1r5Pe|x*I#)NLEA*&7Ase z$y@UHzl*}L=JpOJb= z-gqr?ddqM{)a4L8VU_@|*FEwL*BYkM?G6uRrhwpQ=voX2GI&Lq=AoEoXA-c4xcIm( z?Z*c2i_7}M^H!}x;fVKX7BJ>~{cWRkm`KG6D9{zD+UsP2=K<;dNul$@gu$xp*2z6_ zAn!>_dp-)Y@MElM7WF~ZEQjIK>s1^Jq_XwQ+1Z8RAfaTNazsB8RNST0uEnrm>DX0#i^5%@5?_x&?Mxqa@tnep9u*lP~~%C}8)ea6{x zqa6CSRavcu=|=5KAo%EDa$Ut@;>izmV+iveKrHu-*aFI)Z623qdZ(l48wQy-d*&rP zHjaFt6N+~o3j%X=#YAW{cU3NfFRmJ;N{`p^OJ%k8WqYp;06=dZD@0 zaItDu8XwRoHi7PDu`c@4kRwHomod&Voa{VM7}&#gmE|yG$3%Sv00>Mo6PLmYDohOS zk9f4Qg%_8$9M->3KO|IUA2uUxVcN=V(swT!-#7E%{$8(`#nT`U8 z(0tKEreZ}!e6*0sq|E&tp5L=-bz9tI7Z`qH)-p0o`}+7MA)3Bc7I|&gV=yO1t-M@c z*I|zkA}YTz>5-rg~H@CZ`DCeC+9lP5vZ%(Z7ruT|^_Im(PyS6tdYN^@56rv1A313d{%8|K$ zHYKSl5f86@WggKfDBuerZ{?$8g>2;r^gMJ#q;6ZLDnR9ifkllUk=D$S?28xZK--ld zm>Ul1x|(Z3rAp0Y)@j`Sn#?%Y-nVca0I8o3SWw9AgSJciVC18`Zm>6_Kc2CIUFg(i zfpGY!^VH_R<8)??r{I3% zy)|8G{BAF|Ue3`_opFer!3&~trDRgeeF6szr*U447g=OJZEbDI5U}}Q;i$2Bo!yYF zjW@lOmuwuQX&_cW!gL0HHUW*5> z-*49+rwDy<8Fhfv_ygK8WUcvf*GaQ;^?xuB{w{;Pnh00SISbpcmA%) z{4r-ROqE3GCl)}_lPZ@MlkllQGzs?!eM3%jCTco1`aST<$WtYOrHR+>bRjFRI)>DB zAIDOo5&WQMm^n4RaCFUO-3+m;M-lKPC~LT0`_f^Y$?NzUwOUZSF<$z+9(zQDtep)A z9}f%e^1U;cxR_KR@dVgJw7PGQm{Ekk6F zD~Q%WGAR&k>NQtn|G;2-Ka|xTW{icG^ysN-Z|v5gaE0=k+Q^puiZ~coL~6Vd{Kd@k@b$XrD9& zA6<~}!`Trfm8dISf~=Z19$0ptF4MT9K>DL+*udFy}vHXnBZo+cmoJ>9h^q3^r7n=Rv|hbldzE zuc%1f=eDkur-F)?b$QrJ*VBKo0LEATHiHce-PY$8*D?4?msk{)=cs)MA*w|D%2s8P zMr;1@$Fa6hgp0aH!gk30hu`+66NtweEEEgY+kvu$Htaxn{SS+}u1u#`4EbhVeW)HV zpb(F&a~d6`NZ9h9NPfP8?Q#QsvDaw)o2?nhYg2N_7>Sm!0W$I3dHZ#v-&74iOapH5 zK$IhaX&d00=lq9JosU(c6n7~9wo`Abb{imYO!c80FSbG{YpPe>w~Jc80=7M2B#r9k zkaGTo&bAW+L4JV`(dx)Bha)fi{(+ZA8aT; zzGEOh4;JO#K#OweCIg=KT@VAi_Xj=>z*LQaNxWjz2)1&_Az0oMVQZR*0G>X+I*RTp z036(}XLtbgwQttblFq~b_szebdj>a9=NkWKsaL2V^aW|>k5(L0ki$o2QtPOI9FsWVzSktFs9J7yrOhb~npI z`M<4-VEHvC1^A-7cQ#mpZg2VEkMp_Ef2BU+>HV+0`Oh}JKB8s(*&vvPCdDC9?EmfE zDtuyK*`$IWrc*R{tJnVB)BpV6^qJTP7K|2{CIss8T+K}Ce<|d}81fuFE{G1&(AGZx zlaB6!$gF@S*hvZ$=vwTTJtP4l@t}>3o88k>S&%An+#yY6Dha3Kbx+AH0ElU-jAj)F z{vrXF&pRbHsQzJHxeD3j8X2y*uHBr(TDvHfHY66-=O-6stps!^bK;s zj@PMk4c(r7pC>wzxT0W#M4U{Q4dqBQKYYmQ-)jJThj4Fqg*JDD%UsjzZ^W_NWzIVM z$zLU@r4FVpSI0q7r_J1}4Ic5fp4yzLK$-w#T37osJ`cJ@qjmpM?XduUzCXQ>K-QBC zb^lOA)uE8GBzmmkI(w$G$wH|HR{ww)TO~6L0X1ESq}0VZiUts(1kFgBWd#RU4q=S7 zw0JNw$}>p#-Eg^2o&goxn3@rMb;tcVyOlvAfQZ6T?;cTU6EZMc?|FGSOYC4erKF;f z{P0@%|2hMNsBl0Lv7-8V+SAjMFY+Mt=vc?VqSfMbtNQw${jqy_2(vCBhKvENTNd3p7S`-_7ASXwP z1R`yz^>NVhn&?u3HRsIi)W+MI^3jI*TtT6{Uu}3) zOXGCJ*G$oFc+u;H1{&-Wv_0QlH~Ag@BGC_Z+#U``qKL%LR8viNMXdGK2;AVbc|xy6 zr9+x-TV4?D>23rPF1uAugul{_V!S)_(gozb!m!z6G;Fk$7K51^mPr`q+Rrh}C3hy`BtauWyhu%5a;0_J+TJCZZ*>DyY4_tCLGT)-6wTRWW;J;^%2%DQsQ~n?#uR3A zegnI~o9BW7#q)a4H!hdw*N^nW1qpw}Ik^wshrbtOzA8zXYlwhPi1>%5rzW%*JdcV4Vi6Hs zJqT&p*sKBb!q9GBRAQGp*gu@i_Pw^)j)#H4X@8uQof%!pG}#0-%bS#fnC#9oAD$a4jnYE31g^=>&TN1d(-FLo?@A_TAW-AB)1e?i!t3@U z{YRt6%132#VIsQg`zaAWqQ%Q&(a51zUJr;mU|fv6Udhk6c*twq(w~``lf>;aNInF! zvrsVC2Sf?P_1~Lnt>Jl_oh+x@9AitIQ;WVpHSIl+Paz>WwD0jacJwkbsrul1 zg>&<@PS<9P>?xw4q3Hct8)R|h6sMS&_OD7cnDc}&2pCd?w1Y!J09^{v(F(w|&Po+{ z-Hi-3p`*;pb$bL6FctiBH~-F=}xs}-nw+@FW>*|Jr$wmz@f zT6!3S^{UKZQ;5JeJ(1Q`F;gR{YY0i>9mTHQoTETOIr6}`+9A|_VZq7+z>rKzrV1qF zZ|ZV%(nw|@d1lX1x=^5 z1_R>G_L=9UOHb3U4#e(!kYY08WMfzsg+=2ixNMbQs2k)e^{z*6%96c_aBl=GKS?!w z146$lOQhB8T7QA0U#x7Q!I3$CWoEe#J_t77MU}o9uEys@meT`{6YRFAE}>ws(GT&I z?iu$ZW`i28E=V&mE>=-V>Epm1Bf0l0adyWlP)wl zEg6hC4SW09+XE|$U@HIssHTl$GUu6*JH5AIAm;V8~h&cwmq@3a2V*ZGngbJEDx* zheW*KTH%d(H0A`)ZUH6zN)nUNS26dMznB=4t&v$7+lheTf0@N*X zclxyY`>D2i6WhT7CUgZ=n=#R-1>|x={}tO!V*pA!33(l>-_a{T;BbZXuIcn?>E*Yw z5ERjlsw++4PyerJoPzW796)V>dx4IEVj;(#;0`88g!|IN`LHb|GJm0crG9BW*Ti^| z)5c9C9$z!|XF@Cs%OnZcQGzq(Y+USiLw$M6C#?%fQ@o^Mt|~LkgKB>O*_@SA#YUw_ zv;iC>e;5U)iIom!RJB6Tx-u{z!37R%mn!U#H^Wc zqQ8l))ts&T_T#veZSMW&Ev#0&zJlK;Q*wvBl^RF-B%SFW!Y#9wHt^7-VP6oL%x!#D zK>wn8!d|gxd-aqxqJ-z2ojJ$JO{YKe<*(>ai57^b0bAx!dZ;LC6O97{NW9T3BQk#~ zc)gp`2Ha|NN;JFOUrI0sbG1 zvaTo-9Zy$pXRc2k^*!}W98j3rY*5h^g^K)BsOet3O^J-hcm9NLPLmnyM8sW33A>5W_FD$XDdC?ZRN2F&gxP86?USRo zc3^8jnI&g=Xj@{8L=lDmR>D%y4nY}KX-*fOVw=Cp3HqW{62tGWdU6`6_RXwQjN;@C zaVst%_I$IZyPP^8^dcoCH88V+E?}NkegebziOb2V#ulRjVl6v60ODUED6Pvp_q`F@ z>U2EtZhHH021Fe!EK0g0h>i+?(Cnq^Jg@uJ>bQ*}TFu-~brYFJt zw*)K=vjbty?fORgPZNu$8`nVq#VkJ@Z}x}k8nKtB>F4wmejNj&T1sSb_jjL8tZuF6)E-sf5!NM-LiWG9zQlHwOr& z&Njsb-&5K%$CWjY**>L+D+dFaJUMy63Ac-iyg-}cqA?&~T5!I~4i09ZN=i`~Ua;Tl z>|_CkfqxZX(`5hl?)h3Hxii~993Ef^2OjZyfX3Dln=}qG`(m8sh9o2b7Usi)_UyFg zk-gpJaQM}SAA}3^`ilpy&O{`qTu#)VNb%owmf%QN>9t^l4Mc;qk*=5luFY{RUqM;X zLUqygUY2KL>qycUgISz5tbeh9=g6Fto7CtZ@#d559=%_=Na2=U#`1fRBV|uGd0?0< ztv(-~?A6oZOXG0`C;rg)(tn=B1IRG|YwBSb3rqNRAeDL=L_L~}6+u~2 zd4M6aYG>dE7#{s=K$U^tA}`GJDHx=#K2P9G1X!Z|rFwNhZuz12pKb|}Wc~m`1uz1M z|7i?BBM}M+>0PWu&b|$#p2J1z1{V9f5sufy&WdJaMx%cNNoj2WQOh3*_n(In3V_id zQstVjfwt)t1)IYp!5&?7vN%6}N26ZdNM+KZi`QAvUST~oeSXL-wzswyr|m0gUXSYrfAi;i z-FD5)64VwL{J+8bi})G#9zFsH7XJSt#QvX&_J3Uwa4P>l>Ar)14mqo+U=!x$<`x!1 zbcz1!{`0*bBqI<>=-U6T+V{O!89x=}FK@x(z>N(92mmM{Bz#2|Z3@l}^hXW&YyHF= zNofky>fdXM6&jX}uCK-0H{6Ry7sLL6Owu`-{y}B_1!zuQmjOD)H~`<++#;p(1hKQf zkR#XrD{U8Jfu?I`!33=7ySrnitp8n!{`&Y|cS68CtvV=s-zP_OD?rtMv%*=ZeUGiVx6U)lO8w!$Bs^4Q7 z_zTImY@(x+c2sT&=Glp8^(a-Lk}C}}5!z9Ui#XMK-b< zFotPAXMOo?$Ji4brc0L)&%LVgIiE^^oVwd|A-ut*Glv^*exsaaR1J?OAa3W&Ev6lv zv+R2CA?7FgU%LP^Uk%-4sZ}UfviVbaIK6|C)1#}V*i5OB2 zy8PHNHKk|tsj*x{Msxwni1ncU02fy0EZso!52HKn;Rz2cI!+IbfrO(phWQZ|8f&BU zCQ`rY@3))cok1K^Hx?i-an1RQN2bp&_jKpod|>czoQx-gxZw{<0PA(jD^Yi+p_2;? z&Ob9b37{JN@HJSrU;_((e?yR4B3*Q!QTC_KP#SBf{lX)Ql+z*=kt}rXae_-&5#K01 z^((?w!b4uE%Wi_~*hA84%)LDS0%*Ui2lKX3_i_j(ia|U%EC6~2W|yiXN%w=u>+Wm~ zd*>&!9O0h(#(;f}fRLj5^(4V8p}^ZjM*Hm0>I+>>rY>J~9 zBs@C8FUaB3kW(%NJ-MTuzm5@)JGdqw3ew zyuOji^E4XxAhOS)do+SauG)nT$^sSX19wBX&-)D$L8}HaG@PHm>`XEtePexJh57m^ z-heZ6vMe-4O=1_(sTkKbE}lOGB;0pxe?=b=Ld;Jsf`@>`q{`RBXR?I;$~Sd$_{E!N zHCDayipI&5{5ZH~vN9xg-T--yttPGgJLYAF}@ZZ*qqFR_a z9|J(~AFnOMUy@l5OtvwZ%l$VsdlwpzzruA&N^P?XR>Bl}F;T3uTp7oL7mqxyvvJ{0Cgf)jWxrd&5P zNA+TlI{aZ{FAg(9M}6TY(MWujK1xUYSwnZp?9pglW0;T;bTc@qqMFRr^ikZ-YX-mP zo5!O%qyzE38oNYwZG)tNNuL-a^VVc2O;N|B@;7mFNxO%kjf_!T$#>B|{Lv_SEboSCo*JX%nsoVRF*BYN!k-Xqw-aclNkb)RV@-f- zl0X$CIn~Ps7Sy`W8;U0b3-*AK#lxQz^6sD2>oIfMaPRYQ@9B18b(_xPYsp1G)V%jt z8yTxH2>M&U5@kVN`<4y}`#zf{a0dC&6ECiT6jwDXUS>&xf)3i_aRZ-tLJj=yEn1l* zDjeR>?U#Y`%W_MDy$%v|6c^CEus6oid$_=YPnVLm+kcL>--Nmms~h@5_XF>Jp2Vf( z`M@bXT`VmaTsu4&MlAvgXt%`5uPDP2F{{bRvZo$roWmCKhpI&4o@ zx(c63|M4j-X!WC-CbQ*!Y*tIpT!$t=!2#&85M)MDo}E*$dk9DhW{Swd)A0V<^sGwe zHO|hV_%g5a4RYn{64QS^Ur?Q{fNI0-c%UFJ&u9+*=g?vifzO7z6OHz&Ma8Ko9UTX#Be?w8u!3UX*+1VM?S;xei{I^m8G`o@GMYK^~WM%aD_ce2b zLh$bfpsMmO`ub($FVOpu?IOhtP-~GF zT1p@*u@1e`v{Dh@&N-5V-o^3WgKm3s0#^4gJaAT8t>Vh)f`Wn=1yXzCMSe|z8fQ__VV#OU?GygW=TnCc}SWooED~KRtlRy&dI6MHlBM0*cF_U*GDqxxlak6&RH= zZQGr#`F3yqJGQ+?l;R6q9xJ7c7f-9z1|n>H6)ml$fqD#OfQSNO22HXfNnaQCoBqwM zsQG}siwm#Mo(RScyZR|@e_6vDi zPMrS!XIyL&x1*h(2y3#2hHX_7%l83@->ZK1SyH`g*mwKxZm{1ewUmY_W-o4dtnp+n(Zlxw%~^sGY97)SzB3%OEFE zkd)N+R_5=cNCp3zC?9s zZ?7etJLw5AIsJB*J}D`Qdn!MxsOoim<@S`@tnSMBKc*Jg@Vc`35H_Mk7fvvCkT;tAh&? zijqt$@<=T?aMK{1#vVpN-RES-;umsLkyd3$t(3kvpHGFVot zT8~-_(j65F7R7X~(5IzE%f+Spo(U(N7bMb0j0XH+PYl#w_Sob6Ekx!%tueKW8IjVf zTRW~YRhebcS|cxC=t;CytYBhwgqyAEOcP?T&T62uCN)GJnT zZ=d!r1^m8rbslJFD1dV&%9jNl{dqs>Tsh6Wvkhp%9#Ul(qhYT6!N%eacKNY;7ks+T z`7ai5ZUzCNWNSN|BWg~>6`zWYu3$FBx-b%PW36v=PR9^z^7ZQ@FsdfS6b!9sUz4`z zM3-1I*Wd8c{(&4A6PCeJW_C&O=tP2R#MZk?ueZB$42+u0RI=9iWi}_q#pCJ8;o9gL zwnN0_(9*JSYW*WVzR~V{a1G=XgiKgf`-OyaDY74K5vkfrug2^-o_G0yh|@+*N-A|z za2zyit^Uisk9Qojz6#9wdJwt(Fux=}oXS8U?Q%Sr#tvlmUl$Wg=a(gNIj)mK?M&UFamcxaIN}Xxz`R%LW0U% z4yZq3BJG^=TT2S53JMDByA`ywK2g%6EL3DvtmZqWBo(9*vwObW;SuUsuS~Nszwc~& zek|2)T-k&fVnAG=C=AdW^8f}$B#h~n>o!)IA78AIbU=V0epxN$)1;8yhz56!OpkO9 zwiuP{B#i3%dL2?zQ*=}EL$)8^wTJv*nVDGVHQx{I0S3J~*nXl8ttl9_T4-AL3GDDPiCB)|eq`qp@TD6{~IbO@+|QNWV0-V{RwV*^n_Aftrfuj_}4#CSf^~RW#4v1>}XBfGz$E!RPDC zp>vd5o@F&P-fPt@rx6pICPXL{gt9;1928kzo`^d{_8PZvC@m=sb)0Y)@)ji_ zCI%bzn~NIzQ{lz?qT*yMEa9chI%83hw>R`TC1jsyCXag%^W`K&DQQ22H%$5YhI)-S z_THWrwY4SlxWf?hmKs~6+}%in%xOGTzA7>E@kNngUx-jqA><#3y?4H^_LUwRAD4p* zN^eVVXt=A_)=XSsW?>25{C{|R%do26^;=jPr9=g!1VkF?P63hbZUN~A>F(}sq#L9= z6{Nenk?xN3VEp!f?`xlPKD_68zrtE`%{Awm&vTD)k9&;OuQH{HgH?PpOP(L3<=*wk zzx@!5YoKpT`nE40%e6Ve`FZHZD=0V=7U!j?0gTiZx0Y*Ul-!8IuOU4O)EMZ+&(SdX zeW8?ZTev(6O$rmMjcIL{lBBc+D3i)r*x6MSBv3wog!j_h2}dOoW8mcEQ~PL;nVs!E zkFeJ;+i=4n@4m6NCL~-&*+@r6_dG{je=NJQuFeoNwA~cd$NJ*S7ag(F~)NVo=*e6tN-Lv{dycj}0fO+zS?Mk1o2fZA;F7 zE@1agWU;QrdX>d|*q}-d6rreGHKUai@XOlkS7Dgw?zr0NqL&;6hn3La$~!hXj*N_i zx{d!+N0UR7gN^T5B-Z$FDSI^s0im**dTI^7_jkF~iWYcEmfV}R8*0>&#f_V_<&>om zpM3d!-jt{$@*il$$2p-uAzBOOI?_2QI>XS9$; z21X=A#E;>dA*Bt`$n5kl4dQs?}pCFxhG{ODh`vkpPVm}f@`zH>_ zu|EF(*hGOsxF8@aNjgeL$_aJxW6@l8v?dA*d)-*XtVdEc_bUW=0eN1{_kl25 zM|H!?WhTMbXlNMF-<`7tJ`mqahRMrBNaftQ6~vZQZV%e5#)YTN0qmeOsB6R_Hy9rewNH9S{+1rw)8NUS#qUm@Toi^mv8NyL)z@y!Q=d0eqU z;U8;S6%i2;Bacop@M_}Y0wkyX^m`&A7@7f(G0xYoEq``@??vchZ=bF*n7YA?*ENydkZjm{w-XCuOfAJ)Q(8e&~_V z+?$x+!JGVa_07hJ_c@deM^d;#93g&^M;k}71M!T{JS@AppvS&?Y;Bhkw@J>zO2ci& z!Y<}v82OA|m>uNS=6LYThoDznnnyKWUYXy#?2ZsOem>91dP5|U%pISce0=!e!>Hls zD(NCmrPgTP)c~G)Z*QL_o#wq<%PxQaL-qP;{b@u`TI~!xZlO!%XVy~URSWZtK9R)n zy-8?4FK9RiQ=3-j()o3d*Og0D;FK+N_4V^pHI!ZlSU6@*7pqO;_D@LG)^Q|o8DYI7 zxOKAkf?BiM89Y=v)~)4s6;glG5!ntGy1Kq5mcV2%m7gUQ9{%`o7g$i_nZ|7heTozt zUULSYMiljJS0~kfu5&Ee>qilpXD5357Akw)?|?Kvz2VGByLo@`s&BhBIVx^*d1p9H z6Uy?*mg!MNSK2u7x6bAhJL}a&slA@m8qS}Cd2?maBAp>GQRW@af99oUlarA_$RC~- zFgMxD9cktBU8Ah6uZpE`*@PeeO4I#wVzia%#+zR`c?Hu zsu+)rtM}BTBV^R;nlt~98cztKB30fI$nnv3mM1m~zeIsRx4IX>`K94qbIh!2&^J;;$m^380%Lt z8|I4mAjJd{mOo0O)qT(EcZ4^fne^um&=0o3|3}|nK=^EEw?}XO!xN#YiV)%~&V=T( z-=rj#P%{5%#@!SwDq}4@0<;~J%e81U(VuounCoLzZbA+~9QC;^aLMR&{XG*6$cX`X z3DClsYu!NqeY>7!YCRky%?W+6u#l1k%n3=!LHP_IaaFthq1@0i@+XG>a4|sP$}TEr z`~DyPN|m3GgDe%MX6v&pVk+Jz-@L7m-wYi5(f*;W{Lnw|TdJOsRkHj;?p&Vd)}7K9 zXBjc%KKjiz#x#r%kIpywPN-bO_oj%*(T?bf&6Mik$|e6p&#>-8#ItE1c^7QVrqoNi zc8n({Dio+0hmjp73+2Jr5kA3Kv-aj9($WzRTlSqBpbJ-&>puK4k4;t2eLdfwur^v~ zFuXA!tCPlUC|jRcZI>b~C5NfSym{hZj&oE-Atl$^%Sum`+?{9FWocf+{67gu|=-oKO-*ZcH}5?Xn!b!eG4r`WPdhdd3$*U6u@U4FGbmG!jKyFC>Bug@Y|5Gf67M%kg^!7HdEJS~wF;2pM{8U~Nvu=2D$85!41 zbq9w_TSQAc7q58i;#fr%oOvbHv8IJt@L?$7-=O4^Y(6&3Kl^$QXynGyyk zJ32HD6;au`fE$V`?+Go}`|OKEuTdJ7w7YbQ9unT)KW`*I5?f(91GY!z-h+LvhOEp& z#E>kC*cKEVJ~%|rgIr5Urrgodio^-i4+5Q0+il`i$t}wX@$p>*7@7;|O0qYOR%>CJ zo*Ptge6t^GJTnT8Gmwm!JhTswz60C?;d{)tA+}xpl=})67Na zc~OzFh*GyV-qhGQDkjEdVW~AfQZMgF#yuBCcJPuy4`~bAP5XKsH|))_#yf)pK2cTU z?uss4wn_!b+{P5E(!X55_V!A(_IB|>sd+wc5GHpWe*ro%aiR3p@@~1sE-cLswQ5;Q zZ(D;pCrqhqv;Aq^ecB!M94B+p4B{OT5zz*93S6vTV4(BWg~7%+=i|de{8R?(*YG64 z$)b9nT9f$IOB&zJms$7JOe!YAGASu3m6j}Bv^?f826c`0#@RQkbM?8U8mk#o-x*|C z*1UHxUEN)xDQvYogxF$l-wI9=VlbD@yvCnMP)-PpdFzD^(|0!&x#4?;_qv*Uhr3~4 zU@-Ky9>uNRo@v9Z%=jRx7`k;>FMPRrnSbNZsXZ*ue&b3!=<%&%^c5IuYSDlQg2&3GnI99 z$v9PC9a2-{X8vY$ED?t#?&8k#nua}fcZQ+veDD>EUy}6p?3CsAZoy990XXD?sU|g5 zDO9g73MFieMq92QU3C>-vMhFvx)8m26C4zL<9r)0b+XTxD^_mf?=O`0-W|%tqf$eF zS>OnV>=|G2l@Q=}lwP_HxTs7T*v8jY0Q<#WH|g#o$2us?b0zk$I~6x`ni!_Ra-@ z{oEAe`6ML_{@b@!Ys=BCw>!wVELsLST%YZP_TGu8mRmLk1OW0BN#U*9Tvp=SuLlyz ztXV=M9oX_EsZ18SoZlTNddw+GzDPN9Yuq1ZkTjhfB$a<;sxW+7tlpF$%dqn~Ox0@B zU6QB%Z1=`w5iJ*A3U#P0z0JA)3oD#RP=@Q=hln-{13-1f^7H_fXAH zB`-b8H?-^v^?;OgWPm}wD_T^@S2>wPIJWv=ZN)KkFQl(NktlL6+iYL zwgL+o!M@LJ+BKx%@n0YT)DpSB*qZB|a+7?tahn`ux@U+WHgZr=y-H zC!bC2N7nm1T%i>rAsX*nnh09Ikc5==?$Zv@Ucp|?)y>Gav?y}^oM|4Ij^6Ha9!+CC z-yCby%z(wQeG(P^pA<~_D}LmIM> zgj%|tS(LH1vE6AQB&`+Ulg;+)(ToCpxC~@f!iOEC{WuS5{cZ%WJI~d7a)bIaEQ*2k zl2&&tHDEANk!^WMGfJo_spjn_OtU4(#Ka($1Pu(y6qO?q;~y(YNoD0QXr*#}>GrIt zd5Lp;bd<}z7`=0FsiB)2jRB~q|Z5)#TNd@!O~*LVfLOYFby%CuzOC;o9XR zi|}ww!LSrv#L3=n!Q+*+prF={0(QI7LQUu9IU9ZH?L+M(GS!6mAyZ{7+Uc=IKZ^h z6^&==+OEWq}NnK+QfHl#QZtUR(}7Ztt1XbjZO*>S~VzWT2ePnO2Zyv6zgj#G`{d(|w!TiLsB$dh7I<{Db>m=bA^*wCVZxNRt?j#N zYH?RUPy{pm3Z}TGcySm_sI?8R69=V9Yug#1bwgzAYioI}yHB2SHLIk}R2b1aWhQ$^ ze@~oHPaHR)s$a%BxdluilP^sR_rYFK{Weq4+v_5TTGm?Y8ZHxZx1AjvmM6#Lq%>TC z0e8=2Wj|$_D5z*^E(u@ygt_GcYpB)LZ69bjs~I*}1FQoj{^Y zED!3|{Jp_zQMuLPFQ9&&thI%cNoTgqw7I{t{`P&F(Tu>%G<>VQj_6s`12dW*i!caq;FogoGJIg_OB;2i?b& z*H$PY8sg$Mz9G9ew~#(4SYJQ4mRZJ|EHpT%EewXKAd}-z*7JXV-}6#jUoaQ29#tVW zn75u?Y$WXUJngWvIc>bWfW?3vH&(X1URT3ZibpewnP4uYCLmzdxmx8HaZOn3xqDW) z@PX5^_ebDFzmnw`{}hEEei}nt+v~d1O=1cXkvo{^yLnt+u+drn)J1@O=XbR_wSpC- zl188r#{Of)j8IHi7$^!E=yb=j#rP6qij;58o>dEk!XaS}W*QIv=6*E zZ!w!@V_V%7cJ8_H-YOk5=gYVXL)eqiae9pYMHLM?Q5djFX)irz0E?}2Mo|y*2pg%i zncip*F*jM>MEP`QZf@)nmGzWxQy@||cx`nJV9;q>%5BaWNXtGHR8%QT1tz%QM23^o zLlKI`xrA)cMQxc4<&J7JdBAXT)h-_p%!6Tn*@-ox-|V&K#wA@TkaqXcConZ8*883& zjFD+HU+e?Nhda1)0Zyln945+o69OMG`v-oI{e;fN%-LdKGg024T`f040Y66FKdSH2v88xfTX3a!mwGmS>r^D7${fI;nZn@LF zU0+Arz5U7hx!qck!E-#>DK}L?At6qOJ;++!rtJELr~(gw!)`A#m@CkKRE88IS6@aY zR#Hr?VZ2DGHMi^v;15-uqe^Ogw_0sB8v>8ee7wlCISCL!UYV4;>I5n}6)EXrjP1_s z(BU1QsXbt5Kyx-BrQ@=!uB;mAVHE&s7k!B0A^5dbbVxvP31Hmgb%sC*&Z))yxkikTgmZ;_BDt0 zHh}`C69l*vFK_3fVY$!0^}i-h*3Hd($kffE-iaJ7zyT#na=xP}#Py8E%Ei+2w%0n1 zB^UeVx81=PHPA~MsY*z2GLwDvAEBI<594_6NYbQ(#Db|)8U+dNq4lUz>RdLaHzZOu zIIK2UhzJP2x(@K{<;*fxGKAoLB>a4G@6^#ehU0UY_!NRv?2W;$(_Hdod0{M)Bhu_%O4)+ zPli;OMsAMmK>N0LbS078TZ7>W)Hf2MszHBR7GajJrKuJGL^O|zh)B!e1?`RcsZ zdBsLiU(v$Cf@DVguYgJxX-&qhV7khG=^jd-0xMYmtDwXb12gxwRZS=V0PdytCH}rH zYgu4cvPYDqzE;a+EIaQrC!WdbrOXb8-IbB_K8D2ARUP&7GS}mFZ?6%_95s~0p)29_ z<@AVDUe2?cUIg`YZ?@jGC3tya*r>6mIXl?Esz_~rxYain4>>`@jQ?;tG z7LZdba_&3}2zUYv-)e0)UQyU`)`#M7ipt0kl9Ri!8*cyQ0=ma8553DVCcg%X#WU6N z<(#FtUteA9PS#OyP$uOY+Y$R~i~CkPjqgGr;=FKc%6Zh zqBGc9MNVcXB_o5qfCB(``{zo|s_equ?T4VU$gn6&Yn!QRBW_f3G73H_ET%Fr&g$}W zuh#K8WoKvSe=8j^0Fb45__y|sjPMsbc!>EilP)~Au)1zPezIaPn%URXo`#5oTO<>C zTd5D0&BT`}(dv3G{4&;&{|(IBJp*cDN*=1AJUy%z=Q`hmTle`=LX7HpWfh}-kXS2j zU|-tIuk96>A_s~hzi!KMVeZ5+`+T={mb?1YyhXz83}F>CLf6-om6a?~w&x#%03$%MhoPr$7VCXDBg8ML)(6q zI-)0LG~LkEGza%#D){gm0TfW|q^pp;HlM9d4jft!M+QeP)B^0@fcsTuSq7-|9`v`V z45o%|u8R%)$8X-e;o#k>ij0_F-7aRCdXeOJg*Y)3J8CAO~1IeuSi}O&PzQ`V&2(M?W zjEq1<51zR=W+N{ByTDlL*Z&*E20STJThe8D!EyQE@8wPq25{Hm-QULBs2JG~3p>$| z_M;~p{WFqbK;}J>0@!V5&>~&HUFe|yt?&LqQ>4tCqqXDq+UepvpSfrHWS$5~ zX>5cm%*w{L>)|ZFxm^S4S8^0grq!uPy;v$$2wN@Q<&Dr({|2*rE`VoA<^c&Zo1tJ! z!_;Jd>Z9=kd&2lB>e^0(kbjr#W#l*Nt%;OEyu)(6jiyd#2jwZnCg)>x205C>rq8aA zkHCd7oh%C>$HtKYo)Ek!?k8claoQwQPJDTJ32HLvi_3!C zJhvnb1IfR85AfI!)JZ2Uc^K^Mdpyn9CYGwGf?3(qDX3Sc$EaaG5$^V0R zLe38?+Vw28Os3Pha*6WSJLeph&K0ibJYfa-`Ax3Z?iXFXy+I)XL!(!XMQHej*w4X^ z4t7v0_|ucqZ{NRH?@qV9Fnq$W(RAb_CI=t#=CL1E;q1Sw3Z5CT0N}#ud=zwncy*Ud zPLCsHWMtHy4V7EJx&T-)SR~v>$O|P+w{T}r0@vcZV`F1H5KMG*Ct3vwr5ZG)1NcVR z+v{&tdevC;Mki4bHYof1`^U#dpl~NQ0#OF+T%lo}3w-!cmy|@w^7*rMOY;mutj2Ig z29m&_<5FB~Vr*(>T@fcWwFFR~zB71=yu%64{YU+H>H9De^a%{$cCR)6Ujz>CKYZvo z&Y@n^1I_5=ov(1nAv_D7q+DdwRCoM}IUzK*`ZvXEy33PYKyxEIW&YVJDsrU($#K(7 zQz~4_gL*aoO^VdOKT?6c0nWwz9O_hay;9YKpadvVoc#ff)z!6=lT!dEn_cfUb+mOp zX?H)j`&1!J{#P!L(Gbnh5jqH7uWL1A4rm}#xn}h9k&(g@`x0;#? zX^}g08EY@aEz_)6*x&sj>Ar^qO;IiBc4aJ^jTn+o?+bdipI7IW)&wM>zjTLzi`Iy{ zCt5}W2}Y{3^s_^fV_Ry1fUY}2u=L)RT|GRF+k6>to2(+~rmd}A`I*6ARaseCjW2jJ z9xyDRK!|!x-3rESZcR$1!iQ$5W06r127c)S$m=0hmsHYvUNIU&%+`ZQufcdN>|%bA z3=uz(!Qt`OdsjX_vzi`z2@y$25fMj;x$W&dxQvYSr^h;T@M&frpW z+ktgCyaPa8;QW;u|NHk%=1R6-dPk&X!1ZduMMsAX_?i{HR^39Ul-bVPzPjDb$HwR3 znOD|Y!tl3Ag$7`jbbQUFYILMz(x6RcdPRPX-)cojA`!pRc~ld;dFi<6NOvh*67HO# zkSS093|=UV7a_$Ln)L6G*7j-!7E@Inw*dhN9?MO`dW4a#q=E%xUAZzkAFor2s?5mv(X6A+;`GCH2=0dA)dlnd|0oN z*7~CBkWiu`1_44UCp-J)DDxvRP`ep#1(EUd6Rn?F?=-xr{&) zxkB23LxXNMpwHRd`ss4h;^2oBrRmF#tW6T)y%iZ2W;|B67WFd{=Kb#JG=i2!r73@8 zcJRB$`{U*Hw?j}KGCd^>@Y*CnhX)Q2dFg*|2TX+>ezcIG&2Wd~ia>>8h0Z~6l1Jf> z%ruYJF}SBPao3g1*KkbnTwmuB2EV!CqUf!nt;56Tq|r#^q;yq%scuEaiCC=?|VyILaMygDZo;RH}9x#to2?l3gEZ5%2k`a-jM(%0sQs@l0ZindqpfM=J+cW7t2>y>O>pNtsE^j!wFO5)29HvDHUc3*kp17)5cMW*zy~ z6q+-yDH&$(ZL4fuvUtD*9K?+E`wO!@?myjN!>7EgwJ4g7Ld> zs@Hi0QcVJeJ%LqK-la5qU?iFZG~1zZU4DA#{I0wiga4zyzQEu1yT6Y0KlZjgT^-{$`3-ycR4&G-Kf8FwQWr#{Ry)abhXg?I4MHs-fMzvmzRnTZGsaDO-` zfQPpKdNVm}aTY4_LqGk$w8bB2UOt<+{5`*$b}%1cf^C_Y?{cy^HaFB4p8phxeLxGd zDx~J6+$n>>_{G_6VtP8`g?M}sJ9CM0y~n~7cb=c{C!mA?jq7ys?(aqz9pW$f?&~*N ziov7(Ab}Dr`iDF{m@&zxv=_^q-m)GGj_SP_eD7*{=^kEOgy<;!V?T~nuOsU?_V^ef zj<=OuCtBJ)x1EO*CjP4l2yyqEB`Bph2s?d?%5V zDvhl*H8rKRj?7A~4(_zYZESKtJi%P8z=L+9GVXu&+HK;}LgnVd^NlOefR+L|5WC z)qE+HZpJWbFww4C(G*czle?ND8Oljz^=@*=_~4?7Xg&K_hO1?W^We^3&j^OZpwDM4 zJzYzq0X%ctMP_OH7cB-oK1KL=j5e|h5YAg;GKk1exJviK&T|GpBY%6U{(4h$B`(%% zx(aoCByrq~mX0ZjO*_BVJ3gsFRZ;(9*t@5#y)CbsPxZZ}`3R;ime|

9gSsbr(mw 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 { + group.where('name', 'Alice'); + group.orWhere('name', 'Bob'); +}); +``` + +After: + +```ts +query(knex, User).where(group => + group.where('name', 'Alice').orWhere('name', 'Bob')); + +db.projects.include(r => r.tasks, tasks => + tasks.where('done', false).orderBy('id').limit(3)); +``` + +Grouped predicates expose predicates only. Scopes expose filters, ordering and +paging, not selection, writes or execution. All Framework customizers must return +a configured builder derived from the supplied one; void, async and unrelated +results are rejected. Configuration callbacks run once when attached. An empty +group must return its input unchanged. Later branches from a retained group cannot +modify the attached query. Transaction work callbacks remain asynchronous. + +Default scopes run once when the source is created. Cloning, inspecting metadata, +rendering SQL and repeated execution do not rerun them. `.unscoped()` removes only +the default scope; explicit predicates remain. It **does not** disable soft deletion: +use `.withDeleted()` or `.onlyDeleted()` explicitly. A default scope and caller OR +groups are combined independently, so OR cannot bypass the default scope. + +## 3. Remove `.withRowSchema()` and use the actual result schema + +Before: + +```ts +const userRead = query(knex, User).withRowSchema() + .select(u => ({ id: u.id, name: u.name })); +``` + +After: + +```ts +export const userRead = query(knex, User) + .select(u => ({ id: u.id, name: u.name })); +export const UserRow = userRead.rowSchema; +``` + +`userRead` is a prepared query, not a schema or a row. `UserRow` describes exactly +the decoded result. Export both from a query module and import the schema into +separate mapper modules; no monolithic query/mapper expression is needed. The +[README](./README.md#definitions-can-live-in-separate-files) has a compiled +four-file example. Filters, ordering, paging and transaction clones retain schema +identity, allowing reuse of prepared mapper registrations. + +`select()` and `projected()` replace the scalar selection on a new query; existing +queries are unchanged. Included relation fields remain independent. Aliased flat +joins need an explicit non-empty selection before reading metadata or executing. +Polymorphic roots expose a union `rowSchema` and per-variant `variantRowSchemas`. +Use `forVariant()` for typed branch projections and retain the discriminator. +Use `selectVariants()` for union narrowing, and common root filters/order/pagination +for the combined result. Table-only operations are not advertised on ORM unions. + +## 4. Adopt one storage representation for reads and writes + +| Storage | Framework row value | +| --- | --- | +| Integer / floating-point column | finite `number` | +| Declared decimal / numeric / bigint | exact `string` | +| Optional / nullable SQL column | present property with a value or `null` | +| SQL date / timestamp | `Date` | +| Missing optional JSON property | absent property | +| Optional object relation / missing collection | `null` / `[]` | + +These rules apply to ordinary reads, nested graphs, ORM reloads, inserts, upserts +and other returning writes. SQL text casts protect exact numbers before driver +parsing. Timezone-less timestamps are interpreted as UTC; JavaScript dates still +have millisecond precision. Input defaults, coercers and preprocessors are not +replayed on persisted rows. Separate request/input schemas from storage schemas; +optional storage fields with input defaults are ambiguous and rejected. + +Before, application code often relied on an omitted optional property: + +```ts +// Previous entity output type: { amount?: number; deletedAt?: Date } +``` + +After, derive the row type from `rowSchema` (or `EntityResult` / `InferDatabaseRow`): + +```ts +// Decimal optional storage: { amount: string | null; deletedAt: Date | null } +// Map SQL null to undefined explicitly when an HTTP DTO omits optional fields. +``` + +Do not blindly convert exact strings to numbers. Use explicit domain conversion +only where rounding is acceptable. Insert/update payloads accept exact storage +values, and bigint optimistic-concurrency versions increment without rounding. + +## 5. Declare complete raw output contracts + +Implicit raw base-query overloads and untyped shape-changing escapes are removed. +Before: + +```ts +const report = query(knex, User, knex('users').count({ total: '*' })); +``` + +After: + +```ts +const Report = object({ total: number().coerce() }); +const report = query(knex, User).apply( + sql => sql.clearSelect().count({ total: '*' }), + { output: Report } +); +// report.rowSchema === Report +const rows = await report; + +const rawRows = await rawQuery(knex, Report, + 'select count(*)::text as total from users where enabled = ?', [true]); +``` + +`selectRaw(sql, bindings, { output })` uses the same contract. The output must be +a synchronous, introspectable Framework object schema. It parses each raw row +**once**, with no entity decoding pass or implicit column-to-property remapping. +Alias SQL columns to output property names and cast exact numeric expressions to +text yourself. SQL and bindings are captured; modifying a retained native builder +after configuration does not affect the query. Async raw callbacks are rejected. +Native Knex configuration is the explicit mutable boundary; return that builder +or `undefined`. Raw queries are read-only and detached. Raw SQL remains trusted +application code, not a sandbox or an authorization mechanism. + +## 6. Keep entity writes and projection reads separate + +Full ORM entity reads still use the identity map when tracking is enabled. Their +objects remain mutable, including the familiar `saveChanges()`, `reload()` and +concurrency flow. Repeated queries can return the same tracked object even though +the query builders are independent. Generated IDs/versions are applied to tracked +objects only after the write transaction commits. + +Selected, grouped, distinct and raw rows are detached. Selecting every scalar +field explicitly is still a projection; it is not implicitly promoted to an entity. +Page containers and scalar results are not tracked. A partial row cannot overwrite +a full tracked entity. Writes through projections, relation-loaded queries or +aggregate queries are rejected both statically and at runtime. Start writes from +an unprojected table query; use ORM `ofVariant()` for polymorphic writes. Paginated +writes require a declared primary key. + +Replace `joinOne` / `joinMany` `foreignQuery`, `mappers` and `orderBy` spec properties +with typed child customizers. Use application mappers after decoding for DTO changes. +Child default scopes and soft deletion apply automatically; disable them explicitly +on the child when that is the intended policy. + +## Upgrade checklist + +- Upgrade the fixed Framework package group together; remove `.withRowSchema()` calls. +- Retain returned queries and return synchronous configuration results. +- Replace raw base queries and shape-changing SQL with explicit output contracts. +- Audit API DTOs for exact numbers, dates, SQL nulls and storage/input separation. +- Migrate relation customizers and polymorphic branch projections. +- Verify identity tracking, writes, transaction rollback and concurrency in the app. +- Run TypeScript, unit and real PostgreSQL tests; test representative endpoint flows. + +This release does not change native Knex itself, make entity objects immutable, +introduce automatic DTO mapping, or guarantee equivalent graph SQL on other dialects. diff --git a/libs/knex-schema/README.md b/libs/knex-schema/README.md index d0f086b9..d820d92c 100644 --- a/libs/knex-schema/README.md +++ b/libs/knex-schema/README.md @@ -2,6 +2,9 @@ Type-safe, schema-driven query builder for [Knex](https://knexjs.org/). Use `@cleverbrush/schema` object builders to describe your PostgreSQL tables — column name mapping, eager loading, and full CRUD are handled automatically with complete TypeScript inference. +This major release makes every Framework query immutable and exposes `.rowSchema` automatically. +See the [migration guide](./MIGRATION-v5.md) before upgrading from v4 or an opt-in read beta. + ## Installation ```bash @@ -32,7 +35,7 @@ const db = knex({ client: 'pg', connection: process.env.DB_URL }); const adults = await query(db, UserSchema) .where(t => t.age, '>', 18) .orderBy(t => t.lastName); -// → typed as Array<{ id: number; firstName: string; lastName: string; age?: number; createdAt: Date }> +// → typed as Array<{ id: number; firstName: string; lastName: string; age: number | null; createdAt: Date }> ``` ## Schema Definition @@ -217,7 +220,7 @@ query(db, UserSchema) ## Eager Loading (No N+1) -Related rows are loaded in a **single query** using PostgreSQL CTEs and `jsonb_agg`. +Related rows are loaded in a **single query** using correlated PostgreSQL subqueries and `jsonb_agg`. ### `joinOne` — one-to-one / many-to-one @@ -248,27 +251,32 @@ const users = await query(db, UserSchema) foreignColumn: t => t.authorId, as: 'posts', limit: 5, - orderBy: { column: t => t.id, direction: 'desc' }, - }); + }, posts => posts.orderBy(t => t.id, 'desc')); // users[0].posts — typed as Array<{ id: number; title: string; authorId: number }> ✓ ``` -The `joinMany` spec supports: -- `limit` / `offset` — per-parent pagination using `row_number()` window functions -- `orderBy` — `{ column, direction }` for the sub-collection -- `foreignQuery` — pre-filtered `Knex.QueryBuilder` (e.g. for soft-delete scopes) -- `required` (`joinOne` only) — `true` = inner join, `false` = left join (nullable result) +The `joinMany` spec accepts per-parent `limit` / `offset`. Return the configured +child from its second argument for filtering, ordering and projection. Child +default scopes and soft-delete filters apply automatically. `joinOne` additionally +accepts `required: false` for nullable related objects; required relations filter +parents without a matching child. Raw `foreignQuery`, `mappers` and `orderBy` spec +properties are replaced by the typed child customizer. --- ## Escape Hatch -When you need a Knex feature not exposed by this API, use `.apply()`: +When Framework cannot infer a SQL shape, declare a complete object output schema. +The Knex callback runs once on an isolated builder. The output parser receives +each raw row once; it does not get an additional entity-decoding pass: ```typescript -const rows = await query(db, UserSchema) - .apply(qb => qb.forUpdate().noWait()) - .where(t => t.id, id); +const Totals = object({ count: number().coerce() }); +const totals = query(db, UserSchema) + .where(t => t.age, '>', 18) + .apply(qb => qb.clearSelect().count({ count: '*' }), { output: Totals }); +const rows = await totals; +// totals.rowSchema === Totals ``` --- @@ -295,7 +303,7 @@ const posts = await query(db, PostSchema) .scoped('published') .scoped('recent'); -// Bypass default scope (also skips soft-delete filter if present) +// Bypass only the default scope; use .withDeleted() separately for soft deletes const all = await query(db, PostSchema).unscoped(); ``` @@ -340,10 +348,12 @@ The accessor receives the schema's property-descriptor tree; each element resolv property name at runtime. This form is more refactor-safe but does not provide the compile-time `Pick<>` narrowing that the tuple form offers. -### Conflict rules +### Projection replacement -`.projected()` cannot be combined with `.select()`, `.distinct()`, or any aggregate -(`.count()`, `.min()`, etc.) on the same query. Attempting to do so throws at runtime. +Each `.select()`, `.projected()`, or aggregate projection returns a new query and +replaces its scalar selection. Previously prepared queries keep their shape. +Included relations remain independent of scalar projections. Projected, grouped, +distinct and joined queries are read-only; begin writes from an unprojected table query. ### Column-name mapping @@ -382,7 +392,7 @@ export const PostEntity = defineEntity(PostSchema) .belongsTo(t => t.author, l => l.authorId, r => r.id); ``` -The returned `Entity` carries the relation map in its type, so downstream `query(db, entity)` +The returned `Entity` carries the relation map in its type, so downstream `query(db, entity.schema)` calls (and `@cleverbrush/orm`'s `DbSet.include()`) get full inference. For many-to-many replacement flows, use the link table directly: delete the @@ -520,16 +530,16 @@ identifier quoting. Existing table/column metadata APIs are not restricted by it The read-only aliased builder requires an explicit, non-empty projection and supports `where`, `whereIn`, `whereNull`, `whereNotNull`, `orderBy`, `orderByRaw`, `groupBy`, `having`, `limit`, `offset`, `first`, `execute`, `transacting`, and -awaiting the query. `apply`/`toKnexQuery` remain raw escape hatches whose effects -on result shape/cardinality are the caller's responsibility. Values remain bound +awaiting the query. `apply` requires `{ output }`; `toKnexQuery()` returns an independent native SQL +snapshot whose execution bypasses Framework decoding. Values remain bound and identifiers quoted. Flat collection joins can repeat parents; they do not deduplicate or fetch one related row at a time. Choose ORM eager loading for nested related objects instead. For reusable connection/transaction handling, ordinary and aliased schemas retain the same inference through `createQuery(knex)`, `withTransaction(trx)`, and -`transaction(callback)`. Only ordinary-schema calls accept a custom Knex base -query. These APIs and their JSDoc are also available through `@cleverbrush/orm`. +`transaction(callback)`. Custom Knex base-query overloads are removed; use `rawQuery(knex, Output, sql)` +or `.apply(configure, { output })` for an explicit raw output contract. These APIs and their JSDoc are also available through `@cleverbrush/orm`. ### Aggregates with optional output schemas @@ -571,9 +581,10 @@ use native SQL values, not decoded/text-formatted values. Counts reject malformed results and integers outside JavaScript's safe range. Sum/average preserve PostgreSQL's result as text without changing global driver parsers. This does not make floating-point source columns exact. Numeric/decimal/ -bigint extrema retain strings; because existing SQL overrides are not fully -represented in schema types, numeric extrema are conservatively typed as -`number | string | null`. Date extrema return `Date | null`. +bigint extrema retain strings; known SQL-type metadata controls their inferred +representation. Ordinary number extrema are `number | null`, exact numeric extrema +are `string | null`, and Date extrema are `Date | null`. Dynamic widened SQL hints +may conservatively infer `number | string | null`. An optional **output schema replaces the default decoder**. Its synchronous `parse` receives the raw driver value, including null, before default conversion. @@ -587,8 +598,8 @@ Scalar helpers clone the source, ignore its limit/offset/order, and retain filters, semantic joins, scopes, and transactions. They reject grouped, HAVING, distinct, and already aggregated queries; use aggregate projections for those. An empty scalar count is zero; other empty/all-null aggregates are null. An -empty grouped query returns no rows. Legacy `.count()`, `.sum()`, etc. keep their -existing behavior and signatures. +empty grouped query returns no rows. `.count()`, `.sum()`, etc. now produce typed +single-field projections with automatically updated row schemas. ### Eager-loading order @@ -653,9 +664,11 @@ URL is missing. CI runs PostgreSQL 16 integration tests alongside unit/type test ## Projection-aware reads -`withRowSchema()` is an **opt-in PostgreSQL read API**. Schema-aware queries are immutable, -detached read plans: capture the returned value when adding filters or includes. -They do not track entities, save changes, or run SQL when inspecting metadata. +Every Framework query is immutable and exposes its decoded `.rowSchema` +automatically. There is no `.withRowSchema()` call or mutable compatibility mode. +Capture returned queries when adding filters, projections or includes. Inspecting +metadata never runs SQL. Ordinary table queries also support writes; projected +results are detached, while full ORM entities can still use identity tracking. ### Definitions can live in separate files @@ -671,7 +684,7 @@ const UserTable = object({ secret: string() }).hasTableName('users'); -export const userRead = createQuery(knex)(UserTable).withRowSchema() +export const userRead = createQuery(knex)(UserTable) .select(u => ({ id: u.id, name: u.name, lastSeen: u.lastSeen })); export const UserRow = userRead.rowSchema; ``` @@ -725,10 +738,10 @@ are rejected. A widened dynamic numeric SQL type has a conservative `number | string` read type. Driver custom parsers must still honor the declared representation, or decoding fails. -Timezone-less SQL dates/timestamps are interpreted as UTC in this opt-in mode; +Timezone-less SQL dates/timestamps are interpreted as UTC; explicit offsets preserve their instant. Native date fields are projected as text before decoding, so root and nested values do not depend on a driver's local-time -date parser. This is a deliberate opt-in convention, not a change to legacy reads. +date parser. This applies to ordinary reads, nested graphs and write-returning rows. Input defaults, preprocessors and input-only validators are not replayed on stored rows. Read schemas are structural output schemas. Optional schemas with input @@ -743,7 +756,7 @@ in private text columns, independently of the public date values. ### Projections, aliases and aggregates ```ts -const read = query(knex, alias(UserTable, 'user')).withRowSchema() +const read = query(knex, alias(UserTable, 'user')) .leftJoin(alias(ProfileTable, 'profile'), t => eq(t.user.id, t.profile.userId)) .select(t => ({ id: t.user.id, displayName: t.profile.displayName })); // displayName is nullable even if ProfileTable declares it required. @@ -756,7 +769,7 @@ Aliased reads require an explicit selection before execution or accessing readers. Typed aggregates work in object selections: count returns a safe number, sum/average preserve exact text, and empty extrema/sums remain nullable. An explicit Framework output schema replaces aggregate decoding and is parsed once; opaque -parser objects without schema introspection are rejected in this mode. +parser objects without schema introspection cannot supply projection metadata. ### Filtering and ordering without changing the result schema @@ -807,10 +820,11 @@ export function searchUsers(tenantId: number, term: string, priorityUserId: numb The outer tenant filter applies to the **entire** search group. Group callbacks are synchronous and run once when the predicate is attached, not during SQL -execution. Their scoped builder accumulates predicates, has no selection, join, -ordering, mutation, or execution methods, and is closed after the callback. -Return the provided group builder or nothing. An empty group adds no condition. -Async callbacks and returned thenables are rejected; thenables are not executed. +execution. Their predicate-only builder is immutable too: it has no selection, +join, ordering, write or execution methods. Return the configured group; a void +return, async result or unrelated builder is rejected. An empty group returned +unchanged adds no condition. Retaining a group and deriving another branch later +cannot change the already-attached predicates. `ref()` resolves mapped columns and generated aliases for ordinary readers, explicit aliases for joined readers, and the correct child alias inside relation @@ -829,18 +843,20 @@ change the prepared reader. Empty IN lists match no rows; empty NOT IN lists match all rows, with ordinary SQL null semantics for non-empty lists/subqueries. These operations also work in ordinary ORM reads and nested relation customizers. -For polymorphic reads, use them inside `forVariant()`; the union root does not -offer raw filtering or ordering. Numbered pagination retains raw ordering while +Polymorphic roots also offer common-property predicates, including bound raw +filters. Use `forVariant()` for branch-specific predicates, projections and raw +ordering; root `orderBy()` orders the union globally. Numbered pagination retains raw ordering while its count drops ordering/limits/offsets. `paginateAfter()` uses its explicit complete `orderBy` specification, replacing prior ordering, including raw order. -No unrestricted `.apply()` or raw result-shape mutation is available in read mode. +Opaque SQL changes require `.apply(configure, { output })` or +`.selectRaw(sql, bindings, { output })`; the declared schema owns raw row parsing. ### Nested graphs Declare relations on entities as usual, and return the child query from customizers: ```ts -const read = db.projects.withRowSchema() +const read = db.projects .select(p => ({ id: p.id, name: p.name })) .include(r => r.tasks, tasks => tasks .select(t => ({ title: t.title, createdAt: t.createdAt })) @@ -871,7 +887,7 @@ default. Use `selectVariants(['photo'])` to narrow the returned union, or `forVariant()` to project a branch or load its relations: ```ts -const read = db.assets.withRowSchema() +const read = db.assets .forVariant('photo', q => q.include(r => r.tags)) .forVariant('text', q => q.select(a => ({ id: a.id, kind: a.kind, body: a.body }))); @@ -894,20 +910,22 @@ branch separately. Use `forVariant()` for branch-specific filtering/includes. ### API boundaries -- Enter read mode **before** legacy select/include/join, ordering/pagination, - raw callbacks or variant-specific operations. Continue configuration on the - immutable reader. Default scopes may filter rows but must not preselect, - order or paginate them. -- The opt-in surface is deliberately read-only. It is not a replacement for - entity writes or tracked queries, and has no raw SQL shape escape hatch. -- Reads are PostgreSQL-oriented; this does not promise equivalent JSON/CTI SQL - behavior on other Knex dialects. -- Declared graph metadata must match storage. Unsupported opaque shapes throw - rather than pretending an entity schema describes their output. Explicit - TypeScript assertions such as `hasType()` remain the caller's responsibility. -- Ordinary queries are mutable and retain their driver-value behavior. - Read mode is opt-in; it does not change application nulls or numeric values - globally. +- Query configuration is immutable across tables, aliases, polymorphic roots and + ORM entry points. Native Knex remains mutable only inside explicit raw callbacks + or separately obtained snapshots. +- Scopes are synchronous, shape-preserving callbacks. Return their configured + query. They can filter, order and paginate; they cannot select, load relations, + execute or write. Defaults are captured once when creating the query, not on + every render/execution. `unscoped()` preserves explicit filters and soft deletion. +- Writes require an unprojected, ungrouped, non-distinct table query with no loaded + relations. A primary key is required to target a limited/offset write safely. + Polymorphic writes use ORM `ofVariant()` instead of a union query. +- Reads and JSON/CTI SQL are PostgreSQL-oriented; equivalent behavior is not + promised on other Knex dialects. Schema metadata must match actual storage. +- Raw outputs require synchronous, introspectable Framework object schemas. They + are detached and read-only. Explicit assertions such as `hasType()` remain the + caller's responsibility. Use SQL aliases matching output property names and + text casts for exact numbers before driver parsing. - `getSyncMapper()` accepts only complete mappings with synchronous final steps and nested mappings. It never probes callbacks. Known async mappings are rejected; a disguised thenable throws when invoked. Keep `getMapper()` for diff --git a/libs/knex-schema/integration/immutable-queries.test.ts b/libs/knex-schema/integration/immutable-queries.test.ts new file mode 100644 index 00000000..c519f797 --- /dev/null +++ b/libs/knex-schema/integration/immutable-queries.test.ts @@ -0,0 +1,375 @@ +import { randomUUID } from 'node:crypto'; +import { + alias, + boolean, + ConcurrencyError, + createDb, + date, + defineEntity, + number, + object, + query, + rawQuery, + string +} from '@cleverbrush/orm'; +import Knex from 'knex'; +import { + afterAll, + beforeAll, + beforeEach, + describe, + expect, + it, + vi +} from 'vitest'; + +const connection = process.env.QUERY_TEST_DATABASE_URL; +if (!connection) throw new Error('QUERY_TEST_DATABASE_URL is required'); +const knex = Knex({ client: 'pg', connection, pool: { min: 0, max: 4 } }); +const table = `cb_immutable_${randomUUID().replaceAll('-', '')}`; +const firstId = '9007199254740993'; +const secondId = '9007199254740994'; +const otherId = '9007199254740995'; +const scope = vi.fn(q => q.where('tenantId', 1).where('enabled', true)); +const Account = object({ + id: number().bigint().primaryKey(), + tenantId: number().hasColumnName('tenant_id'), + enabled: boolean(), + name: string(), + balance: number().decimal(30, 6), + version: number().bigint().rowVersion(), + createdAt: date().hasColumnName('created_at'), + deletedAt: date().optional().hasColumnName('deleted_at') +}) + .hasTableName(table) + .softDelete() + .defaultScope(scope); +const Entity = defineEntity(Account); + +beforeAll(async () => { + await knex.schema.createTable(table, t => { + t.bigInteger('id').primary(); + t.integer('tenant_id').notNullable(); + t.boolean('enabled').notNullable(); + t.text('name').notNullable(); + t.decimal('balance', 30, 6).notNullable(); + t.bigInteger('version').notNullable().defaultTo('9007199254740993'); + t.timestamp('created_at', { useTz: true }) + .notNullable() + .defaultTo(knex.fn.now()); + t.timestamp('deleted_at', { useTz: true }); + }); +}); +beforeEach(async () => { + await knex(table).delete(); + await knex(table).insert([ + { + id: firstId, + tenant_id: 1, + enabled: true, + name: 'first', + balance: '12345678901234567890.012345' + }, + { + id: secondId, + tenant_id: 1, + enabled: true, + name: 'second', + balance: '0.000001' + }, + { + id: otherId, + tenant_id: 2, + enabled: false, + name: 'other', + balance: '9.000001' + } + ]); + scope.mockClear(); +}); +afterAll(async () => { + await knex.schema.dropTableIfExists(table); + await knex.destroy(); +}); + +describe('immutable public query semantics against PostgreSQL', () => { + it('inserts exact CTI keys and filters mapped polymorphic columns without losing the discriminator', async () => { + const baseName = `${table}_poly`; + const detailsName = `${table}_details`; + await knex.transaction(async trx => { + await trx.schema.createTable(baseName, t => { + t.bigInteger('asset_id').primary(); + t.text('asset_kind').notNullable(); + t.text('display_name').notNullable(); + }); + await trx.schema.createTable(detailsName, t => { + t.bigInteger('asset_id').primary(); + t.bigInteger('size').notNullable(); + }); + const Asset = defineEntity( + object({ + id: number() + .bigint() + .primaryKey() + .hasColumnName('asset_id'), + kind: string().hasColumnName('asset_kind'), + name: string().hasColumnName('display_name') + }).hasTableName(baseName) + ) + .discriminator('kind') + .ctiVariant( + 'photo', + defineEntity( + object({ + assetId: number() + .bigint() + .hasColumnName('asset_id'), + size: number().bigint() + }).hasTableName(detailsName) + ), + p => p.assetId + ); + const db = createDb(trx, { assets: Asset }, { tracking: true }); + const inserted = await db.assets + .ofVariant('photo') + .insert({ id: firstId, name: 'photo', size: secondId }); + expect(inserted).toMatchObject({ + id: firstId, + kind: 'photo', + name: 'photo', + size: secondId + }); + expect( + await db.assets + .where('name', 'photo') + .where('kind', 'photo') + .first() + ).toBe(inserted); + const page = await db.assets + .orderBy('id') + .paginate({ page: 1, pageSize: 1 }); + expect(page.total).toBe(1); + expect(page.data[0]).toBe(inserted); + expect(() => db.entry(page as any)).toThrow(/not tracked/i); + const variantPage = await db.assets + .ofVariant('photo') + .paginate({ page: 1, pageSize: 1 }); + expect(variantPage.data[0]).toBe(inserted); + expect(() => db.entry(variantPage as any)).toThrow(/not tracked/i); + const bindings = ['id']; + const rawOrdered = db.assets.orderByRaw( + '(__read_poly ->> ?)::numeric desc', + bindings + ); + bindings[0] = 'missing'; + expect((await rawOrdered)[0]).toBe(inserted); + expect(db.entry(inserted).isModified()).toBe(false); + inserted.size = otherId; + expect(db.entry(inserted).isModified()).toBe(true); + expect((await db.saveChanges()).updated).toBe(1); + expect((await trx(detailsName).first()).size).toBe(otherId); + expect(db.entry(inserted).isModified()).toBe(false); + await trx.schema.dropTable(detailsName); + await trx.schema.dropTable(baseName); + }); + }); + + it('captures scopes once, isolates branches and does not let OR bypass visibility', async () => { + const root = query(knex, Account); + const first = root.where('id', firstId); + const withAlternative = first.orWhere('id', otherId); + expect((await withAlternative).map(row => row.id)).toEqual([firstId]); + expect((await root).map(row => row.id)).toEqual([firstId, secondId]); + expect((await withAlternative.unscoped()).map(row => row.id)).toEqual([ + firstId, + otherId + ]); + expect(scope).toHaveBeenCalledTimes(1); + expect(first.rowSchema).toBe(root.rowSchema); + }); + + it('keeps scoped writes independent and applies scope/predicates to bulk updates', async () => { + const root = query(knex, Account); + const first = root.where('id', firstId); + expect( + await first.bulkUpdate([ + { where: { id: firstId }, set: { name: 'changed' } }, + { where: { id: secondId }, set: { name: 'wrong sibling' } }, + { where: { id: otherId }, set: { name: 'wrong tenant' } } + ]) + ).toBe(1); + expect((await root.orderBy('id')).map(row => row.name)).toEqual([ + 'changed', + 'second' + ]); + expect((await root.unscoped().where('id', otherId).first())?.name).toBe( + 'other' + ); + expect( + (await first.update({ balance: '12345678901234567890.999999' }))[0] + .balance + ).toBe('12345678901234567890.999999'); + expect(await root.where('id', secondId).delete()).toBe(1); + expect((await root).map(row => row.id)).toEqual([firstId]); + const restored = await root.onlyDeleted().restore(); + expect(restored[0].id).toBe(secondId); + expect(restored[0].deletedAt).toBeNull(); + }); + + it('decodes insert, conflict and bulk returning rows identically to SELECT', async () => { + const root = query(knex, Account); + const inserted = await root.insert({ + id: '9007199254740996', + tenantId: 1, + enabled: true, + name: 'inserted', + balance: '4.123456' + }); + expect(inserted).toMatchObject({ + id: '9007199254740996', + balance: '4.123456', + version: firstId, + deletedAt: null + }); + expect(inserted.createdAt).toBeInstanceOf(Date); + expect(await root.where('id', inserted.id).first()).toEqual(inserted); + const merged = await root.onConflict('id').merge({ + id: inserted.id, + tenantId: 1, + enabled: true, + name: 'merged', + balance: '5.123456' + }); + expect(merged).toMatchObject({ + id: inserted.id, + balance: '5.123456', + deletedAt: null + }); + const rows = await root.bulkInsert([ + { + id: '9007199254740997', + tenantId: 1, + enabled: true, + name: 'bulk', + balance: '6.123456' + } + ]); + expect(rows[0].createdAt).toBeInstanceOf(Date); + expect(rows[0].version).toBe(firstId); + expect(await root.insertMany([])).toEqual([]); + }); + + it('maintains tracked identity through immutable branches and page data only', async () => { + const tracked = createDb( + knex, + { accounts: Entity }, + { tracking: true } + ); + const root = tracked.accounts.query(); + const one = root.where('id', firstId); + const a = (await one)[0]; + expect((await one)[0]).toBe(a); + expect(await root.find(firstId)).toBe(a); + const page = await root + .orderBy('id') + .paginate({ page: 1, pageSize: 1 }); + expect(page.data[0]).toBe(a); + expect(() => tracked.entry(page as any)).toThrow(/not tracked/i); + const projected = await one + .select(t => ({ id: t.id, name: t.name })) + .first(); + expect(projected).not.toBe(a); + expect(() => tracked.entry(projected!)).toThrow(/not tracked/i); + const raw = await one + .selectRaw('id::text as id', [], { + output: object({ id: string() }) + }) + .first(); + expect(() => tracked.entry(raw!)).toThrow(/not tracked/i); + expect(tracked.entry(a).state).toBe('Unchanged'); + }); + + it('increments exact bigint versions and reloads using the same decoded shape', async () => { + const tracked = createDb( + knex, + { accounts: Entity }, + { tracking: true } + ); + const a = await tracked.accounts.findOrFail(firstId); + a.name = 'saved'; + await tracked.saveChanges(); + expect(a.version).toBe(secondId); + expect(tracked.entry(a).state).toBe('Unchanged'); + await knex(table).where('id', firstId).update({ + balance: '12345678901234567890.999998', + deleted_at: knex.fn.now() + }); + await tracked.reload(a); + expect(a.balance).toBe('12345678901234567890.999998'); + expect(a.createdAt).toBeInstanceOf(Date); + expect(a.deletedAt).toBeInstanceOf(Date); + }); + + it('rolls back writes and generated in-memory versions on a later concurrency failure', async () => { + const tracked = createDb( + knex, + { accounts: Entity }, + { tracking: true } + ); + const a = await tracked.accounts.findOrFail(firstId); + const b = await tracked.accounts.findOrFail(secondId); + a.name = 'pending first'; + b.name = 'pending second'; + await knex(table).where('id', secondId).update({ version: secondId }); + await expect(tracked.saveChanges()).rejects.toBeInstanceOf( + ConcurrencyError + ); + expect(a.version).toBe(firstId); + expect(b.version).toBe(firstId); + expect( + (await query(knex, Account).where('id', firstId).first())?.name + ).toBe('first'); + expect(tracked.entry(a).isModified()).toBe(true); + await tracked.reload(b); + await tracked.saveChanges(); + expect(a.version).toBe(secondId); + }); + + it('captures raw aliases and bindings while applying a parser exactly once', async () => { + const output = object({ count: number().coerce() }); + const parse = vi.spyOn(output, 'parse'); + const root = query(knex, alias(Account, 'account')).where( + t => t.account.id, + firstId + ); + const raw = root.selectRaw('count(*)::text as count', [], { output }); + expect(await raw).toEqual([{ count: 1 }]); + expect(parse).toHaveBeenCalledTimes(1); + expect( + await rawQuery( + knex, + object({ id: string() }), + knex(table) + .select(knex.raw('id::text as id')) + .where('id', firstId) + ) + ).toEqual([{ id: firstId }]); + }); + + it('allows filtered writes without a declared primary key when not paginated', async () => { + const NoKey = object({ name: string() }).hasTableName(table); + expect( + await query(knex, NoKey) + .where('name', 'other') + .update({ name: 'updated without key' }) + ).toEqual([{ name: 'updated without key' }]); + expect( + await query(knex, NoKey) + .where('name', 'updated without key') + .delete() + ).toBe(1); + await expect( + query(knex, NoKey).limit(1).update({ name: 'unsafe' }) + ).rejects.toThrow(/primary key/); + }); +}); diff --git a/libs/knex-schema/integration/queries.test.ts b/libs/knex-schema/integration/queries.test.ts index 0609dd02..c033de8b 100644 --- a/libs/knex-schema/integration/queries.test.ts +++ b/libs/knex-schema/integration/queries.test.ts @@ -74,7 +74,7 @@ const taskEntity = defineEntity(Task) const db = createDb(knex, { tasks: taskEntity }); it('transaction clones retain row schemas and raw-query safety guards', async () => { - const read = db.tasks.withRowSchema().select(t => ({ title: t.title })); + const read = db.tasks.select(t => ({ title: t.title })); await knex.transaction(async trx => { const inTransaction = read.transacting(trx); expect(inTransaction.rowSchema).toBe(read.rowSchema); @@ -82,13 +82,12 @@ it('transaction clones retain row schemas and raw-query safety guards', async () await read.where(t => t.id, 104).first() ); expect(() => - query(knex, Task) + query(knex, taskEntity.schema) .apply(q => { q.whereRaw('true'); }) .transacting(trx) - .withRowSchema() - ).toThrow(/before raw/); + ).toThrow(/output/); }); }); @@ -98,9 +97,7 @@ it('schema-aware reads stay detached in a tracked context', async () => { const listener = (sql: unknown) => calls.push(sql); knex.on('query', listener); try { - const read = tracked.tasks - .withRowSchema() - .select(t => ({ id: t.id, title: t.title })); + const read = tracked.tasks.select(t => ({ id: t.id, title: t.title })); expect(calls).toHaveLength(0); const row = await read.where(t => t.id, 102).first(); expect(calls).toHaveLength(1); @@ -112,8 +109,8 @@ it('schema-aware reads stay detached in a tracked context', async () => { }); it('schema-aware explicit joins preserve nullable objects and custom collections', async () => { - const read = query(knex, Task) - .withRowSchema() + const read = query(knex, taskEntity.schema) + .select(t => ({ id: t.id })) .joinOne( { @@ -152,7 +149,7 @@ it('schema-aware optional belongs-to joins keep unmatched parents', async () => { optional: true } ); const read = query(knex, optional.schema) - .withRowSchema() + .select(t => ({ id: t.id })) .include( r => r.owner, @@ -166,7 +163,7 @@ it('schema-aware optional belongs-to joins keep unmatched parents', async () => it('schema-aware flat joins preserve precision and scoped outer join nulls', async () => { const read = query(knex, alias(Task, 'task')) - .withRowSchema() + .leftJoin(alias(User, 'owner'), t => eq(t.task.ownerId, t.owner.id)) .select(t => ({ id: t.task.id, @@ -190,7 +187,7 @@ it('schema-aware flat joins preserve precision and scoped outer join nulls', asy it('schema-aware cursor pages keep microsecond ordering private', async () => { const read = db.tasks - .withRowSchema() + .select(t => ({ title: t.title })) .include(r => r.notes) .where(t => t.projectId, 1); @@ -321,7 +318,7 @@ afterAll(async () => { describe('flat joins', () => { it('reads exact values and selected relation schemas without hidden queries', async () => { const read = db.tasks - .withRowSchema() + .select(t => ({ id: t.id, amount: t.amount, @@ -353,14 +350,12 @@ describe('flat joins', () => { ]); expect(read.rowSchema.validate(rows[0]).valid).toBe(true); const exact = await db.tasks - .withRowSchema() + .where(t => t.id, 102) .select(t => ({ amount: t.amount })) .first(); expect(exact).toEqual({ amount: '9007199254740993.000001' }); - const AmountRow = db.tasks - .withRowSchema() - .select(t => ({ amount: t.amount })); + const AmountRow = db.tasks.select(t => ({ amount: t.amount })); const PublicAmount = object({ amount: string().optional() }); const toPublic = mapper() .configure(AmountRow.rowSchema, PublicAmount, m => @@ -377,7 +372,7 @@ describe('flat joins', () => { it('decodes typed aggregate outputs once in schema-aware reads', async () => { const read = db.tasks - .withRowSchema() + .where(t => t.projectId, 1) .select(t => ({ count: aggregate.count(), @@ -476,7 +471,10 @@ describe('eager ordering', () => { .orderBy(t => t.title) .orderBy(t => t.id, 'desc') .include(t => t.notes) - .include(t => t.owner) + .include( + t => t.owner, + owner => owner.unscoped().withDeleted() + ) .limit(2); expect(rows.map(row => row.id)).toEqual([104, 103]); expect(rows[0].notes).toHaveLength(2); @@ -488,7 +486,7 @@ describe('eager ordering', () => { it('handles projected-away sort/FK fields, mapped aliases, raw bindings, offset and transactions', async () => { await knex.transaction(async trx => { - const rows = await query(knex, Task) + const rows = await query(knex, taskEntity.schema) .where(t => t.projectId, 1) .orderByRaw('case when ?? = ? then 0 else 1 end, ?? desc', [ 'id', @@ -513,7 +511,7 @@ describe('eager ordering', () => { it('retains distinct/grouped parent cardinality', async () => { for (const distinct of [true, false]) { - let q = query(knex, Task) + let q = query(knex, taskEntity.schema) .where(t => t.projectId, 1) .select(t => ({ owner_id: t.ownerId })) .orderBy(t => t.ownerId); @@ -533,7 +531,7 @@ describe('eager ordering', () => { '"taskId" desc', '1 desc' ])('retains ordering by a projected alias or position: %s', async order => { - const rows = await query(knex, Task) + const rows = await query(knex, taskEntity.schema) .where(t => t.projectId, 1) .select(t => ({ taskId: t.id })) .orderByRaw(order) @@ -563,11 +561,10 @@ describe('aggregate results', () => { .include(t => t.notes) .countValue() ).toBe(4); - const visibleOwner = query(knex, Task) + const visibleOwner = query(knex, taskEntity.schema) .where(t => t.projectId, 1) .joinOne({ foreignSchema: User, - foreignQuery: query(knex, User), localColumn: t => t.ownerId, foreignColumn: t => t.id, as: 'owner', @@ -580,20 +577,22 @@ describe('aggregate results', () => { }); it('retains default-scope filters while ignoring default-scope pagination', async () => { - const Scoped = object({ id: number().primaryKey() }) + const Scoped = object({ + id: number().primaryKey(), + projectId: number().hasColumnName('project_id'), + deletedAt: date().optional().hasColumnName('deleted_at') + }) .hasTableName(tables.tasks) .defaultScope((q: any) => q - .where('project_id', 1) - .whereNull('deleted_at') + .where('projectId', 1) + .whereNull('deletedAt') .limit(1) .offset(1) ); expect(await query(knex, Scoped).countValue()).toBe(4); const Grouped = Scoped.defaultScope((q: any) => q.groupBy('id')); - await expect(query(knex, Grouped).countValue()).rejects.toThrow( - 'ungrouped' - ); + expect(() => query(knex, Grouped)).toThrow(/Scopes/); }); it('preserves sum/average/decimal extrema and returns date/string extrema', async () => { @@ -615,7 +614,7 @@ describe('aggregate results', () => { }); it('handles empty/all-null inputs and caller-supplied parsers', async () => { - const empty = () => query(knex, Task).where(t => t.id, -1); + const empty = () => query(knex, taskEntity.schema).where(t => t.id, -1); expect(await empty().countValue()).toBe(0); for (const method of [ 'sumValue', @@ -626,12 +625,12 @@ describe('aggregate results', () => { expect(await empty()[method]('amount')).toBe(null); } expect( - await query(knex, Task) + await query(knex, taskEntity.schema) .where(t => t.id, 103) .sumValue(t => t.amount) ).toBe(null); expect( - await query(knex, Task) + await query(knex, taskEntity.schema) .where(t => t.id, 105) .sumValue(t => t.amount, { output: number().isFloat().coerce() @@ -643,7 +642,7 @@ describe('aggregate results', () => { }); it('decodes every grouped aggregate without treating rows as entities', async () => { - const rows = await query(knex, Task) + const rows = await query(knex, taskEntity.schema) .where(t => t.projectId, 1) .groupBy(t => t.ownerId) .orderBy(t => t.ownerId) @@ -671,14 +670,14 @@ describe('aggregate results', () => { max: 'A' }); await expect( - query(knex, Task) + query(knex, taskEntity.schema) .groupBy(t => t.ownerId) .countValue() ).rejects.toThrow('ungrouped'); }); it('retains source state and transaction visibility', async () => { - const base = query(knex, Task) + const base = query(knex, taskEntity.schema) .where(t => t.projectId, 1) .select(t => ({ id: t.id })) .limit(1); @@ -745,11 +744,10 @@ describe('aggregate results', () => { describe('composite cursor pages', () => { it('applies required relation filters before testing whether another page exists', async () => { const base = () => - query(knex, Task) + query(knex, taskEntity.schema) .where(t => t.projectId, 1) .joinOne({ foreignSchema: User, - foreignQuery: query(knex, User), localColumn: t => t.ownerId, foreignColumn: t => t.id, as: 'owner', @@ -769,7 +767,7 @@ describe('composite cursor pages', () => { it('does not skip tied timestamps and retains microsecond precision with projections and includes', async () => { const base = () => - query(knex, Task) + query(knex, taskEntity.schema) .where(t => t.projectId, 1) .select(t => ({ taskId: t.id })) .joinMany({ @@ -813,7 +811,7 @@ describe('composite cursor pages', () => { .where(t => t.projectId, -1) .paginateAfter({ limit: 1, orderBy }); expect(empty).toEqual({ data: [], hasMore: false, nextCursor: null }); - const projected = await query(knex, Task) + const projected = await query(knex, taskEntity.schema) .where(t => t.projectId, 1) .select(t => t.id) .paginateAfter({ limit: 2, orderBy }); @@ -847,7 +845,7 @@ describe('composite cursor pages', () => { it('groups an existing OR filter before applying continuation predicates', async () => { const base = () => - query(knex, Task) + query(knex, taskEntity.schema) .where(t => t.id, 105) .orWhere(t => t.id, 104); const first = await base().paginateAfter({ limit: 1, orderBy }); diff --git a/libs/knex-schema/integration/read-graphs.test.ts b/libs/knex-schema/integration/read-graphs.test.ts index fac586db..2cf4763e 100644 --- a/libs/knex-schema/integration/read-graphs.test.ts +++ b/libs/knex-schema/integration/read-graphs.test.ts @@ -138,7 +138,7 @@ afterAll(async () => { describe('schema-aware polymorphic graphs', () => { it('allows schema-preserving predicates inside an explicit variant branch', async () => { const read = db.assets - .withRowSchema() + .selectVariants(['photo']) .forVariant('photo', photo => photo @@ -155,7 +155,7 @@ describe('schema-aware polymorphic graphs', () => { }); it('orders using native values even when branch projections omit sort fields', async () => { const read = db.assets - .withRowSchema() + .forVariant('photo', q => q.select(a => ({ id: a.id, kind: a.kind })) ) @@ -171,14 +171,12 @@ describe('schema-aware polymorphic graphs', () => { { id: 1, kind: 'photo' } ]); expect(() => - db.assets - .withRowSchema() - .forVariant('text', q => q.select(a => ({ id: a.id }))) + db.assets.forVariant('text', q => q.select(a => ({ id: a.id }))) ).toThrow(/retain.*discriminator/); }); it('exposes exact branch schemas, dates and numeric ordering in one statement', async () => { const read = db.assets - .withRowSchema() + .forVariant('photo', q => q.include( r => r.labels, @@ -221,7 +219,7 @@ describe('schema-aware polymorphic graphs', () => { it('decodes nested polymorphic relations and empty arrays without hidden fetches', async () => { const read = db.albums - .withRowSchema() + .include( r => r.assets, assets => @@ -259,25 +257,20 @@ describe('schema-aware polymorphic graphs', () => { ]); try { await expect( - db.assets - .withRowSchema() - .where(a => a.id, 3) - .execute() + db.assets.where(a => a.id, 3).execute() ).rejects.toThrow('unknown polymorphic discriminator'); await expect( - db.assets - .withRowSchema() - .where(a => a.id, 4) - .execute() + db.assets.where(a => a.id, 4).execute() ).rejects.toThrow('missing CTI variant body'); const tolerant = defineEntity(Asset.schema) .discriminator(a => a.kind) .ctiVariant('photo', Photo, p => p.assetId, { allowOrphan: true }); - const reader = createDb(knex, { assets: tolerant }) - .assets.withRowSchema() - .where(a => a.id, 4); + const reader = createDb(knex, { assets: tolerant }).assets.where( + a => a.id, + 4 + ); const orphan = await reader.first(); expect(orphan).toMatchObject({ id: 4, diff --git a/libs/knex-schema/integration/read-predicates.test.ts b/libs/knex-schema/integration/read-predicates.test.ts index ad2c439c..8e7af343 100644 --- a/libs/knex-schema/integration/read-predicates.test.ts +++ b/libs/knex-schema/integration/read-predicates.test.ts @@ -148,11 +148,10 @@ afterAll(async () => { describe('schema-aware read predicates against PostgreSQL', () => { it('keeps outer access filters around grouped raw search and correlated EXISTS', async () => { - const base = query(knex, alias(Task, 'task')) - .withRowSchema() - .join(alias(Project, 'project'), t => - eq(t.task.projectId, t.project.id) - ); + const base = query(knex, alias(Task, 'task')).join( + alias(Project, 'project'), + t => eq(t.task.projectId, t.project.id) + ); const linked = query(knex, Link) .where(l => l.labelId, 2) .where( @@ -213,7 +212,7 @@ describe('schema-aware read predicates against PostgreSQL', () => { .select(l => l.id) .toKnexQuery(); const read = query(knex, alias(Label, 'label')) - .withRowSchema() + .leftJoin(alias(Link, 'link'), t => eq(t.label.id, t.link.labelId)) .whereIn(t => t.label.id, page) .groupBy( @@ -232,7 +231,7 @@ describe('schema-aware read predicates against PostgreSQL', () => { it('uses raw conditional ordering with independent ordinary numbered and cursor pages', async () => { const source = query(knex, Project) - .withRowSchema() + .where(p => p.ownerId, 1) .select(p => ({ id: p.id, name: p.name })); const priority = source @@ -264,7 +263,7 @@ describe('schema-aware read predicates against PostgreSQL', () => { it('orders aliased reads with bound CASE expressions and retains nullable left joins', async () => { const source = query(knex, alias(Project, 'project')) - .withRowSchema() + .leftJoin(alias(Task, 'task'), t => eq(t.project.id, t.task.projectId) ) @@ -279,7 +278,7 @@ describe('schema-aware read predicates against PostgreSQL', () => { expect((await result)[0]).toEqual({ id: 2, taskId: 103 }); expect(result.rowSchema).toBe(source.rowSchema); const empty = query(knex, alias(Project, 'project')) - .withRowSchema() + .leftJoin(alias(Label, 'label'), t => eq(t.project.id, t.label.projectId) ) @@ -298,7 +297,7 @@ describe('schema-aware read predicates against PostgreSQL', () => { { tracking: true } ); const source = db.projects - .withRowSchema() + .select(p => ({ id: p.id })) .include( p => p.tasks, @@ -345,9 +344,7 @@ describe('schema-aware read predicates against PostgreSQL', () => { }); it('preserves captured predicates in transaction clones without touching the source', async () => { - const read = query(knex, Task) - .withRowSchema() - .where(t => t.projectId, 1); + const read = query(knex, Task).where(t => t.projectId, 1); const noLabels = knex(names.links) .select('task_id') .where( diff --git a/libs/knex-schema/src/AliasedReadQuery.ts b/libs/knex-schema/src/AliasedQueryBuilder.ts similarity index 79% rename from libs/knex-schema/src/AliasedReadQuery.ts rename to libs/knex-schema/src/AliasedQueryBuilder.ts index dd60da2b..d7cebc8c 100644 --- a/libs/knex-schema/src/AliasedReadQuery.ts +++ b/libs/knex-schema/src/AliasedQueryBuilder.ts @@ -1,7 +1,7 @@ import { type InferType, object } from '@cleverbrush/schema'; import type { Knex } from 'knex'; import type { - AliasedQueryBuilder, + AliasedQuerySource, AliasTables, JoinPredicate, TableAlias @@ -11,8 +11,10 @@ import { type AliasedColumn, COLUMN } from './expressions.js'; +import { OpaqueQuery, type QueryOutput } from './OpaqueQuery.js'; import { captureReadRaw, + captureValue, type ReadPredicate, type ReadPredicateContext, ReadPredicates @@ -26,7 +28,7 @@ import { type ReadValue, type SchemaForValue } from './read-schema.js'; -import type { ReadColumn, ReadProjection } from './SchemaReadQuery.js'; +import type { ReadColumn, ReadProjection } from './SchemaQueryBuilder.js'; /** Schema-backed aliases whose exact numeric and outer-join values match decoded rows. */ export type ReadAliasTables = { @@ -42,15 +44,15 @@ type Selection = Record | AggregateExpression>; type Selector = (tables: ReadAliasTables) => AliasedColumn; /** Immutable flat joined read. Supply select() before accessing rowSchema or executing. */ -export class AliasedReadQuery< +export class AliasedQueryBuilder< T, Row extends ReadObject = never > extends ReadPredicates> { private fields?: Record; private schema?: Row; private predicates: readonly ReadPredicate[] = []; - /** @internal Enter through an aliased query's withRowSchema() method. */ - constructor(private planner: AliasedQueryBuilder) { + /** @internal Create through query(knex, alias(schema, name)). */ + constructor(private planner: AliasedQuerySource) { super(); } @@ -71,7 +73,7 @@ export class AliasedReadQuery< join( table: N extends keyof T ? never : TableAlias, on: (tables: ReadAliasTables>) => JoinPredicate - ): AliasedReadQuery, Row> { + ): AliasedQueryBuilder, Row> { const copy = this.copy(); copy.planner = copy.planner.join(table, on as any) as any; return copy as any; @@ -82,7 +84,7 @@ export class AliasedReadQuery< on: ( tables: ReadAliasTables> ) => JoinPredicate - ): AliasedReadQuery, Row> { + ): AliasedQueryBuilder, Row> { const copy = this.copy(); copy.planner = copy.planner.leftJoin(table, on as any) as any; return copy as any; @@ -90,11 +92,7 @@ export class AliasedReadQuery< /** Select exact columns and aggregates; opaque raw expressions are deliberately unsupported. */ select

( select: (tables: ReadAliasTables) => P - ): AliasedReadQuery> { - if (this.fields) - throw new ReadSchemaError( - 'Only one projection is allowed per read query' - ); + ): AliasedQueryBuilder> { const { knex, columns } = this.planner.readContext(); const entries = Object.values( columns as Record>> @@ -133,6 +131,10 @@ export class AliasedReadQuery< return { knex, column: selector => { + if (typeof selector !== 'function') + throw new ReadSchemaError( + 'Aliased predicates require a column selector' + ); const column = selector(columns as ReadAliasTables); if (!entries.includes(column)) throw new ReadSchemaError( @@ -177,7 +179,11 @@ export class AliasedReadQuery< right: unknown ): this { const copy = this.copy(); - copy.planner.having(value as any, operator, right); + copy.planner.having( + value as any, + operator, + captureValue(this.planner.readContext().knex, right)() + ); return copy; } /** Limit the flat row count, including repeated parents produced by joins. */ @@ -222,6 +228,41 @@ export class AliasedReadQuery< toQuery(): string { return this.compile().toQuery(); } + /** Return an independent mutable Knex snapshot. */ + toKnexQuery(): Knex.QueryBuilder { + return this.compile(); + } + /** Configure raw SQL once and declare its complete output contract. */ + apply( + configure: (query: Knex.QueryBuilder) => Knex.QueryBuilder | undefined, + options: QueryOutput + ): OpaqueQuery { + const { knex, sql: source } = this.planner.readContext(); + const sql = this.fields ? this.compile() : source; + if (!this.fields) + for (const predicate of this.predicates) predicate(sql); + const result = configure(sql); + if (result !== undefined && result !== sql) { + if (result instanceof Promise) void result.catch(() => {}); + throw new ReadSchemaError( + 'Raw configuration must synchronously configure the supplied Knex builder' + ); + } + return OpaqueQuery.capture(knex, sql, options); + } + /** Replace the projection with trusted SQL and an explicit output contract. */ + selectRaw( + sql: string, + bindings: readonly Knex.RawBinding[], + options: QueryOutput + ): OpaqueQuery { + const { knex } = this.planner.readContext(); + const captured = captureReadRaw(knex, sql, bindings); + return this.apply( + query => query.clearSelect().select(captured()), + options + ); + } /** Execute one statement and validate/decode its detached results. */ async execute(): Promise[]> { const nodes = Object.fromEntries( diff --git a/libs/knex-schema/src/OpaqueQuery.ts b/libs/knex-schema/src/OpaqueQuery.ts new file mode 100644 index 00000000..72b1a842 --- /dev/null +++ b/libs/knex-schema/src/OpaqueQuery.ts @@ -0,0 +1,130 @@ +import type { InferType } from '@cleverbrush/schema'; +import type { Knex } from 'knex'; +import { captureReadRaw } from './read-predicates.js'; +import { type ReadObject, ReadSchemaError } from './read-schema.js'; + +/** Explicit output contract required when Framework cannot infer the SQL row shape. */ +export interface QueryOutput { + /** Synchronous, introspectable Framework object schema; parses each raw row once. */ + output: S; +} + +/** + * Immutable raw SELECT with an explicit output contract. Knex remains mutable only + * inside apply(); its compiled SQL and bindings are captured before this object is returned. + */ +export class OpaqueQuery { + /** The supplied output schema, without a second decoding or input-parser pass. */ + readonly rowSchema: S; + /** @internal Use a query's apply() or selectRaw() method. */ + constructor( + private readonly knex: Knex, + private readonly sql: Knex.QueryBuilder, + options: QueryOutput + ) { + if ( + !options?.output || + typeof options.output.introspect !== 'function' || + options.output.introspect().type !== 'object' + ) + throw new ReadSchemaError( + 'Raw query output requires an introspectable Framework object schema' + ); + this.rowSchema = options.output; + } + /** @internal Capture the completed SELECT; external builders and callbacks are not retained. */ + static capture( + knex: Knex, + sql: Knex.QueryBuilder, + options: QueryOutput + ): OpaqueQuery { + const compiled = sql.toSQL(); + if (Array.isArray(compiled) || compiled.method !== 'select') + throw new ReadSchemaError( + 'Raw query configuration must produce a SELECT' + ); + const raw = captureReadRaw( + knex, + compiled.sql, + compiled.bindings as Knex.RawBinding[] + ); + return new OpaqueQuery( + knex, + knex + .queryBuilder() + .from(raw().wrap('(', ') as __opaque')) + .select('*'), + options + ); + } + /** Configure isolated Knex SQL once; every opaque change must declare its resulting output. */ + apply( + configure: (query: Knex.QueryBuilder) => Knex.QueryBuilder | undefined, + options: QueryOutput + ): OpaqueQuery { + const sql = this.toKnexQuery(); + const result = configure(sql); + if (result !== undefined && result !== sql) { + if (result instanceof Promise) void result.catch(() => {}); + throw new ReadSchemaError( + 'Raw configuration must synchronously configure the supplied Knex builder' + ); + } + return OpaqueQuery.capture(this.knex, sql, options); + } + /** Return an independent mutable SQL snapshot. */ + toKnexQuery(): Knex.QueryBuilder { + return this.sql.clone(); + } + /** Render debug SQL without execution. */ + toQuery(): string { + return this.sql.toQuery(); + } + /** Limit an independent query while keeping schema identity. */ + limit(count: number): OpaqueQuery { + if (!Number.isInteger(count) || count < 0) + throw new ReadSchemaError('Limit must be a non-negative integer'); + return new OpaqueQuery(this.knex, this.sql.clone().limit(count), { + output: this.rowSchema + }); + } + /** Offset an independent query while keeping schema identity. */ + offset(count: number): OpaqueQuery { + if (!Number.isInteger(count) || count < 0) + throw new ReadSchemaError('Offset must be a non-negative integer'); + return new OpaqueQuery(this.knex, this.sql.clone().offset(count), { + output: this.rowSchema + }); + } + /** Bind an independent query to an existing transaction. */ + transacting(trx: Knex.Transaction): OpaqueQuery { + return new OpaqueQuery(trx, this.sql.clone().transacting(trx), { + output: this.rowSchema + }); + } + /** Execute again on every call and synchronously parse each raw row exactly once. */ + async execute(): Promise[]> { + return (await this.sql.clone()).map((row: unknown) => { + const result = this.rowSchema.parse(row); + if (result && typeof (result as any).then === 'function') { + if (result instanceof Promise) void result.catch(() => {}); + throw new ReadSchemaError( + 'Raw output schemas must parse synchronously' + ); + } + return result; + }); + } + /** Fetch the first row, or undefined. */ + async first(): Promise | undefined> { + return (await this.limit(1).execute())[0]; + } + /** Awaiting executes this query; results are not cached. */ + // biome-ignore lint/suspicious/noThenProperty: query builders intentionally support await + then[], E = never>( + resolve?: ((rows: InferType[]) => R | PromiseLike) | null, + reject?: ((error: any) => E | PromiseLike) | null + ): Promise { + return this.execute().then(resolve, reject); + } +} diff --git a/libs/knex-schema/src/PolymorphicReadQuery.ts b/libs/knex-schema/src/PolymorphicQueryBuilder.ts similarity index 53% rename from libs/knex-schema/src/PolymorphicReadQuery.ts rename to libs/knex-schema/src/PolymorphicQueryBuilder.ts index e813f368..b412ec7a 100644 --- a/libs/knex-schema/src/PolymorphicReadQuery.ts +++ b/libs/knex-schema/src/PolymorphicQueryBuilder.ts @@ -10,18 +10,21 @@ import { import type { Knex } from 'knex'; import { buildColumnMap, getPrimaryKeyColumns } from './columns.js'; import type { SchemaProps } from './entity.js'; -import { COLUMN } from './expressions.js'; -import { getVariants } from './extension.js'; -import { - getEffectiveBaseQuery, - getSchemaQueryBuilderCtor -} from './operations/helpers.js'; +import { type AliasedColumn, COLUMN } from './expressions.js'; +import { getTableName, getVariants } from './extension.js'; +import { OpaqueQuery, type QueryOutput } from './OpaqueQuery.js'; import { privateColumn } from './operations/ordering.js'; import type { EntityReadSchema, ReadRelations, ReadVariantMetadata } from './read-entity.js'; +import { + captureReadRaw, + type ReadPredicate, + type ReadPredicateContext, + ReadPredicates +} from './read-predicates.js'; import { type ObjectReadSchema, type ReadObject, @@ -34,8 +37,12 @@ import { type ReadColumns, type ReadCorrelation, type ReadQueryShape, - SchemaReadQuery -} from './SchemaReadQuery.js'; + type Related, + type RelationField, + type SchemaAwareQuery, + SchemaQueryBuilder +} from './SchemaQueryBuilder.js'; +import type { PaginationResult } from './types.js'; type VariantMap = ReadVariantMetadata extends { variants: infer V } ? V : {}; @@ -109,7 +116,7 @@ type BranchQueries< S extends ReadObject, B extends Record > = { - [K in keyof B & keyof VariantMap & string]: SchemaReadQuery< + [K in keyof B & keyof VariantMap & string]: SchemaQueryBuilder< BranchSource, B[K], ReadRelations & ReadRelations> @@ -118,35 +125,69 @@ type BranchQueries< type Selector = ( columns: ReadColumns> ) => { readonly [COLUMN]: { column: string } }; +let polymorphicAliasSequence = 0; +type PolymorphicOrder = + | { key: string; direction: 'asc' | 'desc' } + | { raw: () => Knex.Raw }; /** * Immutable polymorphic read graph. Branches are combined in one PostgreSQL statement; * rowSchema is a real discriminated union and variantRowSchemas supplies object schemas * for separately configured mappers. Framework does not choose application DTO mappings. */ -export class PolymorphicReadQuery< +export class PolymorphicQueryBuilder< S extends ReadObject, B extends Record = VariantReadSchemas -> { +> extends ReadPredicates>> { /** @internal Nominal identity for typed child-query customizers. */ declare readonly [READ_QUERY]: true; /** Runtime union matching decoded results, including selected variant bodies. */ readonly rowSchema: PolymorphicRowSchema; /** Stable object schemas keyed by discriminator, suitable for mapper.configure(). */ readonly variantRowSchemas: Readonly; - private branches: Record>; - private fallback: SchemaReadQuery; - private orders: Array<{ key: string; direction: 'asc' | 'desc' }> = []; + private branches: Record< + string, + SchemaQueryBuilder + >; + private fallback: SchemaQueryBuilder; + private orders: PolymorphicOrder[] = []; private rowLimit?: number; private rowOffset?: number; private includeUnknown = true; + private predicates: readonly ReadPredicate[] = []; + private defaults?: { + predicates: readonly ReadPredicate[]; + orders: PolymorphicOrder[]; + limit?: number; + offset?: number; + }; + private skipDefaults = false; + private deleted: 'exclude' | 'include' | 'only' = 'exclude'; + private readonly predicateAlias = + `__polymorphic_${polymorphicAliasSequence++}`; + private readonly columns: Record>; - /** @internal Use withRowSchema() instead of constructing polymorphic readers. */ + /** @internal Create through query() or an ORM DbSet. */ constructor( private readonly knex: Knex, private readonly source: S, private readonly base: Knex.QueryBuilder ) { + super(); + this.columns = Object.fromEntries( + Object.entries(source.introspect().properties).map( + ([key, schema]) => [ + key, + { + [COLUMN]: { + alias: this.predicateAlias, + column: key, + schema + } + } + ] + ) + ); const config = getVariants(source); if (!config) throw new ReadSchemaError('No polymorphic variants are declared'); @@ -158,16 +199,27 @@ export class PolymorphicReadQuery< buildColumnMap(source).propToCol.get(config.discriminatorKey) ?? config.discriminatorKey; // Invert only the discriminator guard, not caller/default-scope filters. - this.fallback = new SchemaReadQuery( + this.fallback = new SchemaQueryBuilder( knex, common, knex .from(base.clone().as('__read_unknown')) + .select( + Object.fromEntries( + Object.keys(common.introspect().properties).map(key => [ + key, + knex.ref( + `__read_unknown.${buildColumnMap(source).propToCol.get(key) ?? key}` + ) + ]) + ) + ) .where(q => q .whereNotIn(discriminator, Object.keys(config.variants)) .orWhereNull(discriminator) - ) + ), + this.predicateAlias ); const schemas = Object.fromEntries( Object.entries(this.branches).map(([key, q]) => [key, q.rowSchema]) @@ -176,11 +228,47 @@ export class PolymorphicReadQuery< this.rowSchema = this.unionSchema( schemas ) as unknown as PolymorphicRowSchema; + const scope = source.introspect().extensions?.defaultScope; + if (typeof scope === 'function') { + const configured = scope(this.copy()); + if ( + !this.sameSource(configured) || + configured.rowSchema !== this.rowSchema || + configured.deleted !== this.deleted || + configured.skipDefaults !== this.skipDefaults || + configured.knex !== this.knex + ) { + if (configured instanceof Promise) + void configured.catch(() => {}); + throw new ReadSchemaError( + 'Scopes must synchronously return a shape-preserving query' + ); + } + this.defaults = { + predicates: configured.predicates, + orders: configured.orders, + limit: configured.rowLimit, + offset: configured.rowOffset + }; + } } private commonSource(): ReadObject { const info = this.source.introspect(); - return (object(info.properties) as any) + const relationNames = new Set( + ((info.extensions?.relations ?? []) as { name: string }[]).map( + relation => relation.name + ) + ); + const properties = Object.fromEntries( + Object.entries(info.properties) + .filter(([key]) => !relationNames.has(key)) + .map(([key, schema]) => [ + key, + (schema as ReadSchema).withExtension('columnName', key) + ]) + ); + return (object(properties) as any) .withExtension('tableName', info.extensions?.tableName) .withExtension('relations', info.extensions?.relations ?? []); } @@ -197,7 +285,10 @@ export class PolymorphicReadQuery< ); } - private branch(key: string, body: boolean): SchemaReadQuery { + private branch( + key: string, + body: boolean + ): SchemaQueryBuilder { const config = getVariants(this.source)!; const variant = config.variants[key]; const baseInfo = this.source.introspect(); @@ -237,10 +328,11 @@ export class PolymorphicReadQuery< throw new ReadSchemaError( 'CTI read graphs require a single-column primary key' ); - const Constructor = getSchemaQueryBuilderCtor(); - const bodyQuery = getEffectiveBaseQuery( - new Constructor(this.knex, variant.schema) - ).clone(); + const bodyQuery = new SchemaQueryBuilder( + this.knex, + variant.schema, + this.knex(getTableName(variant.schema)) + ).storageQuery(); query.leftJoin( bodyQuery.as(bodyAlias), `${bodyAlias}.${variant.foreignKey}`, @@ -310,10 +402,11 @@ export class PolymorphicReadQuery< '__read_cti_present' ); } - return new SchemaReadQuery( + return new SchemaQueryBuilder( this.knex, schema, - query.select(columns) + query.select(columns), + this.predicateAlias ); } @@ -327,7 +420,7 @@ export class PolymorphicReadQuery< /** @internal Check the identity of the original read source, retained by clones. */ sameSource(other: unknown): boolean { return ( - other instanceof PolymorphicReadQuery && + other instanceof PolymorphicQueryBuilder && this.base === other.base && this.source === other.source ); @@ -343,37 +436,189 @@ export class PolymorphicReadQuery< }); } - /** Add a comparison to every branch without changing its declared result schema. */ - where(selector: Selector, value: unknown): this; - /** Add an explicit supported comparison operator to every branch. */ - where(selector: Selector, operator: string, value: unknown): this; - /** Add a bound comparison; existing reader instances remain unchanged. */ - where(selector: Selector, ...args: unknown[]): this { + protected readPredicateContext(): ReadPredicateContext< + ReadColumns> + > { + return { + knex: this.knex, + column: selector => { + const column = + typeof selector === 'string' + ? this.columns[selector] + : selector(this.columns as any); + if (!column || !Object.values(this.columns).includes(column)) + throw new ReadSchemaError( + 'Column does not belong to this polymorphic query' + ); + return `${this.predicateAlias}.${column[COLUMN].column}`; + } + }; + } + protected addReadPredicate(predicate: ReadPredicate): this { + const copy = this.copy(); + copy.predicates = [...this.predicates, predicate]; + return copy; + } + /** Remove the default scope while preserving explicit predicates. */ + unscoped(): this { + const copy = this.copy(); + copy.skipDefaults = true; + return copy; + } + /** Include soft-deleted entities in every branch. */ + withDeleted(): this { + const copy = this.copy(); + copy.deleted = 'include'; + return copy; + } + /** Match only soft-deleted entities in every branch. */ + onlyDeleted(): this { const copy = this.copy(); - for (const [key, branch] of Object.entries(copy.branches)) - copy.branches[key] = (branch.where as Function)(selector, ...args); - copy.fallback = (copy.fallback.where as Function)(selector, ...args); + copy.deleted = 'only'; return copy; } + /** Apply a named immutable scope once. */ + scoped(name: string): this { + const scope = ( + this.source.introspect().extensions?.scopes as + | Record + | undefined + )?.[name]; + if (!scope) throw new ReadSchemaError(`Unknown scope: ${name}`); + const configured = scope(this.copy()); + if ( + !this.sameSource(configured) || + configured.rowSchema !== this.rowSchema || + configured.deleted !== this.deleted || + configured.skipDefaults !== this.skipDefaults || + configured.knex !== this.knex + ) { + if (configured instanceof Promise) void configured.catch(() => {}); + throw new ReadSchemaError( + 'Scopes must synchronously return a shape-preserving query' + ); + } + return configured; + } + /** True when all branches retain complete entity rows. */ + get returnsEntityRows(): boolean { + return Object.values(this.branches).every( + branch => branch.returnsEntityRows + ); + } + /** Customize a relation on one discriminator branch. */ + includeVariant( + key: keyof B & keyof VariantMap & string, + relation: string, + customize?: (query: SchemaQueryBuilder) => ReadQueryShape + ): this { + return this.forVariant(key, query => + query.include(() => relation as any, customize as any) + ) as unknown as this; + } + /** Load a common relation on every branch, configuring the child exactly once. */ + include< + K extends keyof ReadRelations & string, + Child extends ReadQueryShape = SchemaAwareQuery< + Related[K]> + > + >( + selector: K | ((relations: { [P in keyof ReadRelations]: P }) => K), + customize?: ( + query: SchemaAwareQuery[K]>> + ) => Child + ): PolymorphicQueryBuilder< + S, + { + [P in keyof B]: ObjectSchemaBuilder< + SchemaProps & + Record< + K, + RelationField[K], Child['rowSchema']> + > + >; + } + > { + const relations = (this.source.introspect().extensions?.relations ?? + []) as { name: string }[]; + const name = + typeof selector === 'string' + ? selector + : selector( + Object.fromEntries( + relations.map(relation => [ + relation.name, + relation.name + ]) + ) as any + ); + if (!relations.some(relation => relation.name === name)) { + const variants = getVariants(this.source)!.variants; + const candidates = Object.entries(variants).filter(([, variant]) => + variant.relations.some(relation => relation.name === name) + ); + if (candidates.length > 1) + throw new ReadSchemaError( + `Ambiguous relation: ${name}; use includeVariant` + ); + if (candidates.length === 1) + return this.includeVariant( + candidates[0][0] as any, + name, + customize as any + ) as any; + throw new ReadSchemaError(`Unknown relation: ${name}`); + } + const copy = this.copy(); + const entries = Object.entries(copy.branches); + const [firstKey, first] = entries[0]; + const prepared = first.include(name, customize as any); + copy.branches[firstKey] = prepared; + for (const [key, branch] of entries.slice(1)) + copy.branches[key] = branch.includeFrom(name, prepared); + copy.fallback = copy.fallback.includeFrom(name, prepared); + copy.refresh(); + return copy as any; + } + /** Filter one branch using schema property names; other variants remain unaffected. */ + whereVariant( + key: keyof B & keyof VariantMap & string, + selector: string | ((columns: any) => any), + operator: string, + value: unknown + ): this { + return this.forVariant(key, query => + query.where(selector as any, operator, value) + ) as unknown as this; + } /** Order all variants together, not independently within each branch. */ - orderBy(selector: Selector, direction: 'asc' | 'desc' = 'asc'): this { + orderBy( + selector: + | Selector + | (keyof ReadColumns> & string), + direction: 'asc' | 'desc' = 'asc' + ): this { if (direction !== 'asc' && direction !== 'desc') throw new ReadSchemaError('Invalid ordering direction'); - const properties = this.source.introspect().properties; - const key = selector( - Object.fromEntries( - Object.keys(properties).map(k => [ - k, - { [COLUMN]: { column: k } } - ]) - ) as any - )[COLUMN].column; - if (!Object.hasOwn(properties, key)) - throw new ReadSchemaError('Unknown polymorphic ordering column'); + const column = + typeof selector === 'string' + ? this.columns[selector] + : selector(this.columns as any); + if (!column || !Object.values(this.columns).includes(column as any)) + throw new ReadSchemaError( + 'Column does not belong to this polymorphic query' + ); + const key = column[COLUMN].column; const copy = this.copy(); copy.orders.push({ key, direction }); return copy; } + /** Order the combined JSON-envelope SQL using trusted SQL and captured bindings. */ + orderByRaw(sql: string, bindings: readonly Knex.RawBinding[] = []): this { + const copy = this.copy(); + copy.orders.push({ raw: captureReadRaw(this.knex, sql, bindings) }); + return copy; + } /** Limit the combined result across all variants. */ limit(count: number): this { if (!Number.isInteger(count) || count < 0) @@ -385,7 +630,7 @@ export class PolymorphicReadQuery< /** Restrict returned discriminator branches and narrow both runtime and inferred schemas. */ selectVariants( keys: K - ): PolymorphicReadQuery> { + ): PolymorphicQueryBuilder> { if ( !keys.length || new Set(keys).size !== keys.length || @@ -421,14 +666,16 @@ export class PolymorphicReadQuery< >( key: K, configure: (query: BranchQueries[K]) => Q - ): PolymorphicReadQuery & Record> { + ): PolymorphicQueryBuilder & Record> { const current = this.branches[key]; if (!current) throw new ReadSchemaError(`Unknown variant: ${key}`); const configured = configure(current as any); - if (!current.sameSource(configured)) + if (!current.sameSource(configured)) { + if (configured instanceof Promise) void configured.catch(() => {}); throw new ReadSchemaError( 'Variant customizer must return its configured read query' ); + } const discriminator = getVariants(this.source)!.discriminatorKey; if ( configured.rowSchema @@ -439,7 +686,7 @@ export class PolymorphicReadQuery< 'Variant projections must retain the original discriminator' ); const copy = this.copy(); - copy.branches[key] = configured as unknown as SchemaReadQuery< + copy.branches[key] = configured as unknown as SchemaQueryBuilder< any, any, any @@ -450,36 +697,71 @@ export class PolymorphicReadQuery< /** @internal Compile one UNION ALL statement; JSON preserves distinct branch shapes. */ compile(correlate?: ReadCorrelation): Knex.QueryBuilder { + const defaults = this.skipDefaults ? undefined : this.defaults; const reserved = Object.values(this.branches).flatMap(branch => Object.keys(branch.rowSchema.introspect().properties) ); - const order = this.orders.map(item => { - const hidden = privateColumn(reserved, 'read_order'); - reserved.push(hidden); - return { ...item, hidden }; - }); + const order = [...(defaults?.orders ?? []), ...this.orders].map( + item => { + if ('raw' in item) return item; + const hidden = privateColumn(reserved, 'read_order'); + reserved.push(hidden); + return { ...item, hidden }; + } + ); const queries = [ ...Object.values(this.branches), ...(this.includeUnknown ? [this.fallback] : []) - ].map(branch => - this.knex + ].map(original => { + let branch = original; + for (const predicates of [ + defaults?.predicates ?? [], + this.predicates + ]) { + if (predicates.length) + branch = branch.withPredicate(query => { + query.where(nested => { + for (const predicate of predicates) + predicate(nested); + }); + }); + } + const softDelete = this.source.introspect().extensions + ?.softDelete as { column: string } | undefined; + if (softDelete && this.deleted !== 'include') { + const key = + buildColumnMap(this.source).colToProp.get( + softDelete.column + ) ?? softDelete.column; + branch = branch.withPredicate(query => { + query[ + this.deleted === 'only' ? 'whereNotNull' : 'whereNull' + ](key); + }); + } + return this.knex .from( branch .compile((sql, alias, source) => { correlate?.(sql, alias, source); const columns = buildColumnMap(source).propToCol; - for (const { key, hidden } of order) + for (const item of order) { + if ('raw' in item) continue; + const { key, hidden } = item; sql.select({ [hidden]: this.knex.raw( 'cast(?? as text)', [`${alias}.${columns.get(key) ?? key}`] ) }); + } }) .as('__read_branch') ) - .select(this.knex.raw('to_jsonb(__read_branch) as __read_poly')) - ); + .select( + this.knex.raw('to_jsonb(__read_branch) as __read_poly') + ); + }); const query = this.knex .from( this.knex @@ -488,7 +770,12 @@ export class PolymorphicReadQuery< .as('__read_variants') ) .select('__read_poly'); - for (const { key, direction, hidden } of order) { + for (const item of order) { + if ('raw' in item) { + query.orderByRaw(item.raw()); + continue; + } + const { key, direction, hidden } = item; const info = this.source.introspect().properties[key].introspect(); const type = info.type === 'number' @@ -509,8 +796,10 @@ export class PolymorphicReadQuery< [hidden] ); } - if (this.rowLimit !== undefined) query.limit(this.rowLimit); - if (this.rowOffset !== undefined) query.offset(this.rowOffset); + const limit = this.rowLimit ?? defaults?.limit; + const offset = this.rowOffset ?? defaults?.offset; + if (limit !== undefined) query.limit(limit); + if (offset !== undefined) query.offset(offset); return query; } /** @internal Decode using exactly the selected branch's schema and codecs. */ @@ -528,6 +817,90 @@ export class PolymorphicReadQuery< toQuery(): string { return this.compile().toQuery(); } + /** Return a separately mutable Knex snapshot of the union statement. */ + toKnexQuery(): Knex.QueryBuilder { + return this.compile(); + } + /** Configure a captured union SELECT and declare its complete raw output shape. */ + apply( + configure: (query: Knex.QueryBuilder) => Knex.QueryBuilder | undefined, + options: QueryOutput + ): OpaqueQuery { + const sql = this.compile(); + const result = configure(sql); + if (result !== undefined && result !== sql) { + if (result instanceof Promise) void result.catch(() => {}); + throw new ReadSchemaError( + 'Raw configuration must synchronously configure the supplied Knex builder' + ); + } + return OpaqueQuery.capture(this.knex, sql, options); + } + /** Select trusted SQL from the union envelope with an explicit output contract. */ + selectRaw( + sql: string, + bindings: readonly Knex.RawBinding[], + options: QueryOutput + ): OpaqueQuery { + const captured = captureReadRaw(this.knex, sql, bindings); + return this.apply( + query => query.clearSelect().select(captured()), + options + ); + } + /** Count matching rows across variants, excluding global pagination and ordering. */ + async countValue(): Promise { + const source = this.compile() + .clearOrder() + .clear('limit') + .clear('offset'); + const row = await this.knex + .from(source.as('__variant_count')) + .count({ count: '*' }) + .first(); + const count = Number(row?.count ?? 0); + if (!Number.isSafeInteger(count)) + throw new ReadSchemaError('Count exceeds the safe integer range'); + return count; + } + /** Globally paginate the discriminated union, leaving the source and its metadata unchanged. */ + async paginate({ + page, + pageSize + }: { + page: number; + pageSize: number; + }): Promise>>> { + if ( + !Number.isInteger(page) || + page < 1 || + !Number.isInteger(pageSize) || + pageSize < 1 + ) + throw new ReadSchemaError( + 'Page and pageSize must be positive integers' + ); + const total = await this.countValue(); + const data = await this.offset((page - 1) * pageSize) + .limit(pageSize) + .execute(); + const totalPages = Math.ceil(total / pageSize); + return { + data, + total, + page, + pageSize, + totalPages, + hasNextPage: page < totalPages, + hasPreviousPage: page > 1 + }; + } + /** Read a property common to the selected variants; returned scalar values are detached. */ + async pluck>>( + key: K + ): Promise>[K][]> { + return (await this.execute()).map(row => (row as any)[key]); + } /** Execute and decode one statement containing every requested branch. */ async execute(): Promise>[]> { return (await this.compile()).map((row: any) => this.decode(row)); diff --git a/libs/knex-schema/src/QuerySource.ts b/libs/knex-schema/src/QuerySource.ts new file mode 100644 index 00000000..2d14ae23 --- /dev/null +++ b/libs/knex-schema/src/QuerySource.ts @@ -0,0 +1,1389 @@ +// @cleverbrush/knex-schema — QuerySource + +import type { InferType } from '@cleverbrush/schema'; +import { + EXTRA_TYPE_BRAND, + METHOD_LITERAL_BRAND, + type ObjectSchemaBuilder +} from '@cleverbrush/schema'; +import type { Knex } from 'knex'; +import { buildColumnMap } from './columns.js'; +import type { + AggregateOptions, + AggregateResult, + ExtremumResult, + ExtremumValue, + OutputSchema +} from './expressions.js'; +import { getTableName } from './extension.js'; +import { scalarAggregate } from './operations/aggregate.js'; +import { + type CompositeCursorOptions, + compositeCursor +} from './operations/composite-cursor.js'; +// Operations +import { + avgImpl, + countDistinctImpl, + countImpl, + distinctImpl, + maxImpl, + minImpl, + projectedImpl, + scopedImpl, + selectImpl, + selectRawImpl, + sumImpl, + unscopedImpl +} from './operations/select.js'; +import type { + ColumnRef, + CursorPaginationResult, + InsertType, + JoinManySpec, + JoinOneSpec, + PaginationResult, + SelectProjection, + SelectSelector +} from './types.js'; + +export { + OnConflictBuilder, + type OnConflictMergeHelpers, + type OnConflictMergeOptions, + type OnConflictUpdateData, + type OnConflictUpdateValue +} from './operations/insert.js'; + +import { + deleteImpl, + hardDeleteImpl, + onlyDeletedImpl, + restoreImpl, + withDeletedImpl +} from './operations/delete.js'; +import { + ALLOWED_OPS, + buildQuery, + cleanAndMapRow, + getQuery, + getVariantConfig, + invalidateCache, + registerQuerySource, + resolveColumn +} from './operations/helpers.js'; +import { + bulkInsertImpl, + bulkUpsertImpl, + insertImpl, + insertManyImpl, + onConflictImpl, + upsertImpl +} from './operations/insert.js'; +import { + includeImpl, + includeVariantImpl, + joinManyImpl, + joinOneImpl +} from './operations/join.js'; +import { + executeImpl, + limitImpl, + offsetImpl, + paginateAfterImpl, + paginateImpl +} from './operations/pagination.js'; +import { getState, setState } from './operations/state.js'; +import { bulkUpdateImpl, updateImpl } from './operations/update.js'; +import { + andWhereImpl, + groupByImpl, + groupByRawImpl, + havingImpl, + havingRawImpl, + orderByImpl, + orderByRawImpl, + orWhereImpl, + orWhereInImpl, + orWhereNotInImpl, + orWhereNotNullImpl, + orWhereNullImpl, + whereBetweenImpl, + whereExistsImpl, + whereILikeImpl, + whereImpl, + whereInImpl, + whereJsonPathImpl, + whereLikeImpl, + whereNotBetweenImpl, + whereNotExistsImpl, + whereNotImpl, + whereNotInImpl, + whereNotNullImpl, + whereNullImpl, + whereRawImpl +} from './operations/where.js'; + +// --------------------------------------------------------------------------- +// Type-level helpers +// --------------------------------------------------------------------------- + +type ScopesOf = S extends { + readonly [METHOD_LITERAL_BRAND]?: infer N; +} + ? Extract + : never; + +type ProjectionsOf = S extends { + readonly [EXTRA_TYPE_BRAND]?: infer P; +} + ? P extends Record + ? P + : Record + : Record; + +type ProjectionKeysOf< + S, + K extends keyof ProjectionsOf & string +> = ProjectionsOf[K] extends readonly (infer T extends string)[] + ? T + : string; + +// --------------------------------------------------------------------------- +// QuerySource +// --------------------------------------------------------------------------- + +/** + * Build schema-aware SQL with mapped columns, projections and eager relations. + * Fluent configuration methods mutate this builder; create a fresh query for each + * independent operation. Await the builder or call execute() to obtain mapped rows. + * @internal Private native SQL/write planner; public consumers use immutable query(). + */ +export class QuerySource< + TLocalSchema extends ObjectSchemaBuilder, + TResult +> { + /** + * Create a query over the schema's configured table. + * @param knex - Database connection or transaction used to execute the query. + * @param localSchema - Schema containing property/column and relation metadata. + * @param baseQuery - Optional existing Knex query to configure; it is not cloned. + */ + constructor( + knex: Knex, + localSchema: TLocalSchema, + baseQuery?: Knex.QueryBuilder + ) { + const tableName = getTableName(localSchema); + setState(this, { + knex, + baseQuery: baseQuery ?? knex(tableName), + localSchema, + specs: [], + tableName, + explicitSelects: null, + selectionMode: null, + appliedProjection: null, + projectionColumns: null, + projectionDecoders: {}, + hiddenColumns: new Set(), + includeDeleted: false, + onlyDeleted: false, + skipDefaultScope: false, + variantConfig: undefined, + enabledVariants: null, + variantWhereFilters: [], + variantRelationIncludes: [], + cachedBuiltQuery: null + }); + } + + // ======================================================================= + // SELECT / DISTINCT / AGGREGATES + // ======================================================================= + + /** + * Choose columns, or use an object selector to infer a flat result shape. + * Object values can be schema descriptors or aggregate expressions. Column-list + * selection retains the existing result type; raw SQL cannot infer a new shape. + * @returns This builder, narrowed to the object projection when one is supplied. + */ + select(...columns: (ColumnRef | Knex.Raw)[]): this; + /** + * Choose columns, or use an object selector to infer a flat result shape. + * Object values can be schema descriptors or aggregate expressions. Column-list + * selection retains the existing result type; raw SQL cannot infer a new shape. + * @returns This builder, narrowed to the object projection when one is supplied. + */ + select>( + selector: TSel + ): QuerySource>>; + /** + * Choose columns, or use an object selector to infer a flat result shape. + * Object values can be schema descriptors or aggregate expressions. Column-list + * selection retains the existing result type; raw SQL cannot infer a new shape. + * @returns This builder, narrowed to the object projection when one is supplied. + */ + select(...args: unknown[]): any { + return selectImpl(this as any, ...args); + } + + /** + * Apply SQL DISTINCT to the selected columns, optionally adding columns. + * Property selectors are mapped to database names; SQL decides row equality. + */ + distinct(...columns: (ColumnRef | Knex.Raw)[]): this { + return (distinctImpl as any)(this, ...columns); + } + + /** + * Append a legacy SQL COUNT selection without executing the query. + * The driver controls the result shape/value type. Prefer countValue() for a + * checked scalar number, or aggregate.count() in a typed object projection. + */ + count(column?: ColumnRef | Knex.Raw): this { + return (countImpl as any)(this, column); + } + + /** + * Append a legacy COUNT(DISTINCT column) selection. + * Prefer countDistinctValue() for a checked scalar or aggregate.countDistinct() + * for an inferred grouped result; this legacy method retains the builder type. + */ + countDistinct(column?: ColumnRef | Knex.Raw): this { + return (countDistinctImpl as any)(this, column); + } + + /** + * Append a legacy MIN selection without changing the result type. + * Use minValue() for a scalar with explicit decoding, or aggregate.min() in a + * typed projection. SQL returns null for an empty/all-null input. + */ + min(column: ColumnRef | Knex.Raw): this { + return (minImpl as any)(this, column); + } + + /** + * Append a legacy MAX selection without changing the result type. + * Use maxValue() for a scalar with explicit decoding, or aggregate.max() in a + * typed projection. SQL returns null for an empty/all-null input. + */ + max(column: ColumnRef | Knex.Raw): this { + return (maxImpl as any)(this, column); + } + + /** + * Append a legacy SUM selection, leaving numeric conversion to the driver. + * Prefer sumValue() or aggregate.sum() to preserve exact numeric text by default. + */ + sum(column: ColumnRef | Knex.Raw): this { + return (sumImpl as any)(this, column); + } + + /** + * Append a legacy AVG selection, leaving numeric conversion to the driver. + * Prefer avgValue() or aggregate.avg() for a typed, precision-preserving result. + */ + avg(column: ColumnRef | Knex.Raw): this { + return (avgImpl as any)(this, column); + } + + /** + * Count matching rows, or non-null column values, without mutating this query. + * Ignores source ordering/limits/offsets while retaining filters and transactions. + * @param options - Optional output parser; receives the raw driver value. + * @returns A safe integer by default, including zero for an empty source. + * @throws If the default count overflows, the source is grouped/distinct, or parsing fails. + */ + countValue | undefined = undefined>( + options?: AggregateOptions + ): Promise>; + /** + * Count non-null column values without mutating the source query. + * Source paging/order is ignored; filters, scopes and transactions remain. + * @param column - Mapped schema property to count. + * @param options - Optional parser replacing safe-integer decoding. + * @throws If the default result overflows or the source is grouped/distinct. + */ + countValue | undefined = undefined>( + column: ColumnRef, + options?: AggregateOptions + ): Promise>; + /** + * Count matching rows, or non-null column values, without mutating this query. + * Ignores source ordering/limits/offsets while retaining filters and transactions. + * @param options - Optional output parser; receives the raw driver value. + * @returns A safe integer by default, including zero for an empty source. + * @throws If the default count overflows, the source is grouped/distinct, or parsing fails. + */ + countValue( + columnOrOptions?: ColumnRef | AggregateOptions, + options?: AggregateOptions + ): Promise { + const hasColumn = + typeof columnOrOptions === 'string' || + typeof columnOrOptions === 'function'; + return scalarAggregate( + this, + 'count', + hasColumn ? columnOrOptions : undefined, + hasColumn ? options : columnOrOptions + ); + } + + /** + * Count distinct non-null values in an unpaginated clone of this query. + * @param column - Schema property to count; SQL nulls do not contribute. + * @param options - Optional parser replacing default safe-integer conversion. + * @returns A safe integer, or the parser's inferred output type. + * @throws If the count is unsafe, the source is grouped/distinct, or parsing fails. + */ + countDistinctValue | undefined = undefined>( + column: ColumnRef, + options?: AggregateOptions + ): Promise> { + return scalarAggregate(this, 'countDistinct', column, options); + } + + /** + * Sum non-null values in an unpaginated clone, preserving numeric precision. + * @param column - Numeric schema property to sum. + * @param options - Optional parser receiving the raw driver value, including null. + * @returns Database numeric text, or null for empty/all-null input by default. + * An output parser replaces default decoding and controls the result type. + */ + sumValue | undefined = undefined>( + column: ColumnRef, + options?: AggregateOptions + ): Promise> { + return scalarAggregate(this, 'sum', column, options); + } + + /** + * Average non-null values in an unpaginated clone of this query. + * @param column - Numeric schema property to aggregate. + * @param options - Optional parser receiving the raw driver result, including null. + * @returns Exact database numeric text or null by default; a parser overrides this. + * @remarks Text preserves database precision, not precision already lost in floating-point storage. + */ + avgValue | undefined = undefined>( + column: ColumnRef, + options?: AggregateOptions + ): Promise> { + return scalarAggregate(this, 'avg', column, options); + } + + /** + * Find the smallest non-null column value without retaining source paging. + * @param column - Schema property to aggregate. + * @param options - Optional parser replacing default decoding, including null handling. + * @returns Null for empty/all-null input; otherwise the column representation. + * Dates return Date; numeric SQL overrides may return exact strings. + */ + minValue< + C extends ColumnRef, + S extends OutputSchema | undefined = undefined + >( + column: C, + options?: AggregateOptions + ): Promise< + AggregateResult< + S, + C extends (...args: any[]) => infer D + ? ExtremumValue + : C extends keyof InferType + ? ExtremumResult[C]> + : unknown + > + > { + return scalarAggregate(this, 'min', column, options); + } + + /** + * Find the largest non-null column value without retaining source paging. + * @param column - Schema property to aggregate. + * @param options - Optional parser replacing default decoding, including null handling. + * @returns Null for empty/all-null input; otherwise the column representation. + * Dates return Date; numeric SQL overrides may return exact strings. + */ + maxValue< + C extends ColumnRef, + S extends OutputSchema | undefined = undefined + >( + column: C, + options?: AggregateOptions + ): Promise< + AggregateResult< + S, + C extends (...args: any[]) => infer D + ? ExtremumValue + : C extends keyof InferType + ? ExtremumResult[C]> + : unknown + > + > { + return scalarAggregate(this, 'max', column, options); + } + + /** + * Append a raw SELECT expression with optional Knex value/identifier bindings. + * The caller owns its SQL and result shape; this does not infer a new result type. + */ + selectRaw(sql: string, bindings?: any[]): this { + return selectRawImpl(this as any, sql, bindings); + } + + /** + * Apply a named schema projection and narrow the selected property type. + * @param name - Projection registered with the schema's projection() extension. + * @throws If the projection is unknown or conflicts with a prior selection. + */ + projected & string>( + name: K + ): QuerySource< + TLocalSchema, + Pick & keyof TResult> + > { + return projectedImpl(this as any, name); + } + + /** + * Apply a named schema scope to this query. + * @param name - Scope registered with the schema's scope() extension. + * @throws If the requested scope is not registered. + */ + scoped>(name: K): this { + return scopedImpl(this as any, name as string); + } + + /** + * Disable the default read scope and include soft-deleted rows. + * Explicit filters already added to this builder remain in place. + */ + unscoped(): this { + return unscopedImpl(this as any); + } + + // ======================================================================= + // WHERE + // ======================================================================= + + /** + * Add an AND filter using mapped schema columns, a record, or raw Knex SQL. + * Selector/key and record forms map property names to database columns. Grouped + * callbacks receive a Knex builder and therefore use database column names. + * Values are bound; use the operator form for comparisons other than equality. + */ + where(column: ColumnRef, operator: string, value: any): this; + /** + * Add an AND filter using mapped schema columns, a record, or raw Knex SQL. + * Selector/key and record forms map property names to database columns. Grouped + * callbacks receive a Knex builder and therefore use database column names. + * Values are bound; use the operator form for comparisons other than equality. + */ + where(column: ColumnRef, value: any): this; + /** + * Add an AND filter using mapped schema columns, a record, or raw Knex SQL. + * Selector/key and record forms map property names to database columns. Grouped + * callbacks receive a Knex builder and therefore use database column names. + * Values are bound; use the operator form for comparisons other than equality. + */ + where(raw: Knex.Raw, operator: string, value: any): this; + /** + * Add an AND filter using mapped schema columns, a record, or raw Knex SQL. + * Selector/key and record forms map property names to database columns. Grouped + * callbacks receive a Knex builder and therefore use database column names. + * Values are bound; use the operator form for comparisons other than equality. + */ + where(callback: (builder: Knex.QueryBuilder) => void): this; + /** + * Add an AND filter using mapped schema columns, a record, or raw Knex SQL. + * Selector/key and record forms map property names to database columns. Grouped + * callbacks receive a Knex builder and therefore use database column names. + * Values are bound; use the operator form for comparisons other than equality. + */ + where(record: Record): this; + /** + * Add an AND filter using mapped schema columns, a record, or raw Knex SQL. + * Selector/key and record forms map property names to database columns. Grouped + * callbacks receive a Knex builder and therefore use database column names. + * Values are bound; use the operator form for comparisons other than equality. + */ + where(raw: Knex.Raw): this; + /** + * Add an AND filter using mapped schema columns, a record, or raw Knex SQL. + * Selector/key and record forms map property names to database columns. Grouped + * callbacks receive a Knex builder and therefore use database column names. + * Values are bound; use the operator form for comparisons other than equality. + */ + where(columnOrRaw: any, ...args: any[]): this { + return whereImpl(this as any, columnOrRaw, ...args); + } + + /** + * Add an AND condition; an explicit synonym for where(). + * Property references and record keys are mapped; raw callbacks use Knex columns. + */ + andWhere( + column: ColumnRef, + operator: string, + value: any + ): this; + /** + * Add an AND condition; an explicit synonym for where(). + * Property references and record keys are mapped; raw callbacks use Knex columns. + */ + andWhere(column: ColumnRef, value: any): this; + /** + * Add an AND condition; an explicit synonym for where(). + * Property references and record keys are mapped; raw callbacks use Knex columns. + */ + andWhere(record: Record): this; + /** + * Add an AND condition; an explicit synonym for where(). + * Property references and record keys are mapped; raw callbacks use Knex columns. + */ + andWhere(callback: (builder: Knex.QueryBuilder) => void): this; + /** + * Add an AND condition; an explicit synonym for where(). + * Property references and record keys are mapped; raw callbacks use Knex columns. + */ + andWhere(raw: Knex.Raw): this; + /** + * Add an AND condition; an explicit synonym for where(). + * Property references and record keys are mapped; raw callbacks use Knex columns. + */ + andWhere(columnOrRaw: any, ...args: any[]): this { + return andWhereImpl(this as any, columnOrRaw, ...args); + } + + /** + * Add an OR condition using a mapped property, record, raw SQL or Knex group. + * Group mixed AND/OR conditions explicitly when SQL precedence would change intent. + */ + orWhere( + column: ColumnRef, + operator: string, + value: any + ): this; + /** + * Add an OR condition using a mapped property, record, raw SQL or Knex group. + * Group mixed AND/OR conditions explicitly when SQL precedence would change intent. + */ + orWhere(column: ColumnRef, value: any): this; + /** + * Add an OR condition using a mapped property, record, raw SQL or Knex group. + * Group mixed AND/OR conditions explicitly when SQL precedence would change intent. + */ + orWhere(record: Record): this; + /** + * Add an OR condition using a mapped property, record, raw SQL or Knex group. + * Group mixed AND/OR conditions explicitly when SQL precedence would change intent. + */ + orWhere(callback: (builder: Knex.QueryBuilder) => void): this; + /** + * Add an OR condition using a mapped property, record, raw SQL or Knex group. + * Group mixed AND/OR conditions explicitly when SQL precedence would change intent. + */ + orWhere(raw: Knex.Raw): this; + /** + * Add an OR condition using a mapped property, record, raw SQL or Knex group. + * Group mixed AND/OR conditions explicitly when SQL precedence would change intent. + */ + orWhere(columnOrRaw: any, ...args: any[]): this { + return orWhereImpl(this as any, columnOrRaw, ...args); + } + + /** + * Add a negated condition using a mapped property, record or Knex group. + * Use whereNotIn()/whereNotBetween() for their dedicated SQL operators. + */ + whereNot( + column: ColumnRef, + operator: string, + value: any + ): this; + /** + * Add a negated condition using a mapped property, record or Knex group. + * Use whereNotIn()/whereNotBetween() for their dedicated SQL operators. + */ + whereNot(column: ColumnRef, value: any): this; + /** + * Add a negated condition using a mapped property, record or Knex group. + * Use whereNotIn()/whereNotBetween() for their dedicated SQL operators. + */ + whereNot(record: Record): this; + /** + * Add a negated condition using a mapped property, record or Knex group. + * Use whereNotIn()/whereNotBetween() for their dedicated SQL operators. + */ + whereNot(callback: (builder: Knex.QueryBuilder) => void): this; + /** + * Add a negated condition using a mapped property, record or Knex group. + * Use whereNotIn()/whereNotBetween() for their dedicated SQL operators. + */ + whereNot(raw: Knex.Raw): this; + /** + * Add a negated condition using a mapped property, record or Knex group. + * Use whereNotIn()/whereNotBetween() for their dedicated SQL operators. + */ + whereNot(columnOrRaw: any, ...args: any[]): this { + return whereNotImpl(this as any, columnOrRaw, ...args); + } + + /** + * Require the mapped column to match a value list or a single-column subquery. + * An empty list matches no rows. Subqueries use Knex's database column names. + */ + whereIn( + column: ColumnRef, + values: readonly any[] | Knex.QueryBuilder + ): this { + return (whereInImpl as any)(this, column, values); + } + + /** + * Exclude values returned by a list or single-column subquery. + * SQL null semantics apply; a null in the set is not equivalent to a missing value. + */ + whereNotIn( + column: ColumnRef, + values: readonly any[] | Knex.QueryBuilder + ): this { + return (whereNotInImpl as any)(this, column, values); + } + + /** + * Add an OR membership condition against a list or single-column subquery. + */ + orWhereIn( + column: ColumnRef, + values: readonly any[] | Knex.QueryBuilder + ): this { + return (orWhereInImpl as any)(this, column, values); + } + + /** + * Add an OR non-membership condition; SQL NOT IN null semantics apply. + */ + orWhereNotIn( + column: ColumnRef, + values: readonly any[] | Knex.QueryBuilder + ): this { + return (orWhereNotInImpl as any)(this, column, values); + } + + /** + * Add an AND IS NULL condition for a mapped schema property. + */ + whereNull(column: ColumnRef): this { + return (whereNullImpl as any)(this, column); + } + + /** + * Add an AND IS NOT NULL condition for a mapped schema property. + */ + whereNotNull(column: ColumnRef): this { + return (whereNotNullImpl as any)(this, column); + } + + /** + * Add an OR IS NULL condition for a mapped schema property. + */ + orWhereNull(column: ColumnRef): this { + return (orWhereNullImpl as any)(this, column); + } + + /** + * Add an OR IS NOT NULL condition for a mapped schema property. + */ + orWhereNotNull(column: ColumnRef): this { + return (orWhereNotNullImpl as any)(this, column); + } + + /** + * Require the mapped column to lie within an inclusive [lower, upper] range. + */ + whereBetween( + column: ColumnRef, + range: readonly [any, any] + ): this { + return (whereBetweenImpl as any)(this, column, range); + } + + /** + * Exclude the inclusive [lower, upper] range from a mapped column. + */ + whereNotBetween( + column: ColumnRef, + range: readonly [any, any] + ): this { + return (whereNotBetweenImpl as any)(this, column, range); + } + + /** + * Match a mapped column against a SQL LIKE pattern. + * Percent and underscore remain wildcards; values are bound, not wildcard-escaped. + */ + whereLike(column: ColumnRef, value: string): this { + return (whereLikeImpl as any)(this, column, value); + } + + /** + * Match a mapped column using PostgreSQL's case-insensitive ILIKE operator. + * Percent and underscore remain pattern wildcards. + */ + whereILike(column: ColumnRef, value: string): this { + return (whereILikeImpl as any)(this, column, value); + } + + /** + * Append raw WHERE SQL with Knex bindings. Never interpolate untrusted values. + * Raw SQL uses database names and is outside schema-level result/type checking. + */ + whereRaw(sql: string, ...bindings: any[]): this { + return (whereRawImpl as any)(this, sql, ...bindings); + } + + /** + * Add an EXISTS filter from a Knex subquery or query-building callback. + * Use qualified database columns to correlate it with the parent query. + */ + whereExists(callback: Knex.QueryCallback | Knex.QueryBuilder): this { + return (whereExistsImpl as any)(this, callback); + } + + /** + * Add a NOT EXISTS filter from a Knex subquery or query-building callback. + */ + whereNotExists(callback: Knex.QueryCallback | Knex.QueryBuilder): this { + return (whereNotExistsImpl as any)(this, callback); + } + + /** + * Compare a JSON-path value inside a mapped JSON column. + * @param path - JSON path understood by the Knex database dialect. + * @param operator - SQL comparison operator forwarded to Knex. + * @param value - Bound comparison value. + */ + whereJsonPath( + column: ColumnRef, + path: string, + operator?: string, + value?: any + ): this { + return (whereJsonPathImpl as any)(this, column, path, operator, value); + } + + // ======================================================================= + // ORDER BY + // ======================================================================= + + /** + * Append ordering by a mapped property or raw expression (ascending by default). + * Add a unique tie-breaker for stable pages. Eager loading retains parent order. + */ + orderBy( + column: ColumnRef | Knex.Raw, + direction?: 'asc' | 'desc' + ): this { + return (orderByImpl as any)(this, column, direction); + } + + /** + * Append a raw ORDER BY expression with Knex bindings. + * Use database column names; parent ordering is retained during eager loading. + */ + orderByRaw(sql: string, ...bindings: any[]): this { + return (orderByRawImpl as any)(this, sql, ...bindings); + } + + // ======================================================================= + // GROUP BY / HAVING + // ======================================================================= + + /** + * Group rows by mapped schema properties or raw expressions. + * Combine with aggregate expressions in select() to infer grouped DTO results. + */ + groupBy(...columns: (ColumnRef | Knex.Raw)[]): this { + return (groupByImpl as any)(this, ...columns); + } + + /** + * Append raw GROUP BY SQL with optional Knex bindings. + */ + groupByRaw(sql: string, ...bindings: any[]): this { + return (groupByRawImpl as any)(this, sql, ...bindings); + } + + /** + * Filter SQL groups by a mapped column/raw expression, operator and bound value. + */ + having( + column: ColumnRef | Knex.Raw, + operator: string, + value: any + ): this { + return (havingImpl as any)(this, column, operator, value); + } + + /** + * Append raw HAVING SQL with Knex bindings, for example aggregate comparisons. + */ + havingRaw(sql: string, ...bindings: any[]): this { + return havingRawImpl(this as any, sql, ...bindings); + } + + // ======================================================================= + // PAGINATION + // ======================================================================= + + /** + * Set the maximum number of parent rows to select; mutates this query. + * Included collections do not consume the parent limit. + */ + limit(n: number): this { + return limitImpl(this as any, n); + } + + /** + * Skip this many parent rows before applying the limit; mutates this query. + * Use deterministic ordering when navigating offset-based pages. + */ + offset(n: number): this { + return offsetImpl(this as any, n); + } + + /** + * Execute a one-based offset page and a separate matching-source count query. + * Mutates this builder's limit/offset and returns mapped rows plus page metadata. + * The count and page are separate reads, not a snapshot unless your transaction provides one. + */ + async paginate(opts: { + /** One-based page number. */ + page: number; + /** Maximum parent rows in a page. */ + pageSize: number; + }): Promise> { + return paginateImpl(this as any, opts) as Promise< + PaginationResult + >; + } + + /** + * Read a cursor page without running a total-count query. + * The orderBy form clones the source and preserves exact composite sort values; + * its non-null sort must contain a declared unique key. The legacy column form + * mutates this builder and defaults to id descending. Reapply access filters on + * every request: cursors are positions, not authorization or snapshots. + * @returns Mapped data, hasMore, and a nextCursor that is null on the last page. + * @throws For invalid composite cursors or unsupported composite query shapes. + */ + paginateAfter( + opts: CompositeCursorOptions + ): Promise>; + /** + * Read a cursor page without running a total-count query. + * The orderBy form clones the source and preserves exact composite sort values; + * its non-null sort must contain a declared unique key. The legacy column form + * mutates this builder and defaults to id descending. Reapply access filters on + * every request: cursors are positions, not authorization or snapshots. + * @returns Mapped data, hasMore, and a nextCursor that is null on the last page. + * @throws For invalid composite cursors or unsupported composite query shapes. + */ + paginateAfter(opts: { + /** Previous raw single-column position; omit for the first page. */ + cursor?: any; + /** Maximum parent rows to return; one extra row determines hasMore. */ + limit: number; + /** Unique sort property; defaults to id in the legacy API. */ + column?: ColumnRef; + /** Sort/continuation direction; defaults to descending. */ + direction?: 'asc' | 'desc'; + }): Promise>; + /** + * Read a cursor page without running a total-count query. + * The orderBy form clones the source and preserves exact composite sort values; + * its non-null sort must contain a declared unique key. The legacy column form + * mutates this builder and defaults to id descending. Reapply access filters on + * every request: cursors are positions, not authorization or snapshots. + * @returns Mapped data, hasMore, and a nextCursor that is null on the last page. + * @throws For invalid composite cursors or unsupported composite query shapes. + */ + async paginateAfter(opts: any): Promise> { + if ('orderBy' in opts) return compositeCursor(this as any, opts); + return (paginateAfterImpl as any)(this, opts) as Promise< + CursorPaginationResult + >; + } + + // ======================================================================= + // WRITE OPERATIONS + // ======================================================================= + + /** + * Insert one schema-shaped row and return its mapped database representation. + * Applies configured insert hooks, column mappings and timestamp defaults. + */ + async insert(data: InsertType): Promise { + return insertImpl(this as any, data) as Promise; + } + + /** + * Insert an array of schema-shaped rows and return their mapped representations. + * Returns an empty array for empty input; use bulkInsert() to control chunking. + */ + async insertMany(data: InsertType[]): Promise { + return insertManyImpl(this as any, data) as Promise; + } + + /** + * Configure an upsert conflict target using mapped properties. + * Call merge() or ignore() on the returned builder to insert the row. + */ + onConflict( + ...conflictColumns: ColumnRef[] + ): import('./operations/insert.js').OnConflictBuilder< + TLocalSchema, + TResult + > { + return (onConflictImpl as any)(this, ...conflictColumns); + } + + /** + * Insert one row or update it when the chosen conflict target already exists. + * @param opts - Conflict properties and optional subset of properties to update. + * @returns The inserted or updated row mapped to schema property names. + */ + async upsert( + data: InsertType, + opts: { + /** Properties identifying an existing row on conflict. */ + conflictColumns: ColumnRef[]; + /** Properties to update on conflict; omit to merge insert values. */ + updateColumns?: ColumnRef[]; + } + ): Promise { + return (upsertImpl as any)(this, data, opts); + } + + /** + * Insert rows in chunks, optionally ignoring or merging conflicts. + * @param opts - Chunk size (default 500), conflict policy and conflict properties. + * @returns Mapped rows returned by PostgreSQL; ignored conflicts produce no row. + */ + async bulkInsert( + rows: InsertType[], + opts?: { + /** Requested rows per statement, capped by parameter limits; default 500. */ + chunkSize?: number; + /** Optional PostgreSQL conflict policy applied to each chunk. */ + onConflict?: 'ignore' | 'merge'; + /** Conflict target properties when a conflict policy is supplied. */ + conflictColumns?: ColumnRef[]; + } + ): Promise { + return (bulkInsertImpl as any)(this, rows, opts); + } + + /** + * Insert/update rows in chunks using the specified conflict properties. + * @param opts - Required conflict target and optional chunk size (default 500). + * @returns The database rows produced by each chunk, mapped to schema properties. + */ + async bulkUpsert( + rows: InsertType[], + opts: { + /** Properties identifying an existing row on conflict. */ + conflictColumns: ColumnRef[]; + /** Requested rows per statement, capped by parameter limits; default 500. */ + chunkSize?: number; + } + ): Promise { + return (bulkUpsertImpl as any)(this, rows, opts); + } + + // ======================================================================= + // UPDATE + // ======================================================================= + + /** + * Update rows matching this query's explicit filters and return mapped rows. + * Applies update hooks and timestamp metadata. Add a WHERE clause to avoid a + * table-wide update; this method does not track entity identity. + */ + async update(data: Partial>): Promise { + return updateImpl(this as any, data) as Promise; + } + + /** + * Apply per-row where/set pairs and return the total affected-row count. + * Maps both filter and update property names and applies configured update hooks. + */ + async bulkUpdate( + updates: ReadonlyArray<{ + /** Equality filters identifying the rows for this update. */ + where: Partial>; + /** Schema properties to assign to those rows. */ + set: Partial>; + }> + ): Promise { + return bulkUpdateImpl(this as any, updates as any); + } + + // ======================================================================= + // DELETE / SOFT DELETE + // ======================================================================= + + /** + * Delete rows matching explicit filters and return the affected-row count. + * Runs beforeDelete hooks; with soft-delete metadata it sets the deletion timestamp + * instead of removing rows. Add filters to avoid a table-wide write. + */ + async delete(): Promise { + return deleteImpl(this as any); + } + + /** + * Include soft-deleted rows in read results without removing explicit filters. + */ + withDeleted(): this { + return withDeletedImpl(this as any); + } + + /** + * Restrict reads to rows whose configured soft-delete column is non-null. + */ + onlyDeleted(): this { + return onlyDeletedImpl(this as any); + } + + /** + * Permanently delete rows matching explicit filters, even on a soft-delete schema. + * Runs beforeDelete hooks and returns the affected count. This cannot be undone + * without a transaction rollback or backup. + */ + async hardDelete(): Promise { + return hardDeleteImpl(this as any); + } + + /** + * Clear the deletion timestamp on rows matching explicit filters and return them. + * @throws If the schema has no soft-delete configuration. + */ + async restore(): Promise { + return restoreImpl(this as any) as Promise; + } + + // ======================================================================= + // EAGER LOADING (JOIN) + // ======================================================================= + + /** + * Eager-load a related object under spec.as using mapped join columns. + * Required joins remove unmatched parents; optional joins return null. Unlike a + * flat join, related fields remain nested and are mapped with the foreign schema. + */ + joinOne< + TForeignSchema extends ObjectSchemaBuilder< + any, + any, + any, + any, + any, + any, + any + >, + TFieldName extends string, + TRequired extends boolean = true + >( + spec: JoinOneSpec + ): QuerySource< + TLocalSchema, + import('./types.js').WithJoinedOne< + TResult, + TFieldName, + TForeignSchema, + TRequired + > + > { + return joinOneImpl(this as any, spec); + } + + /** + * Eager-load a nested array without multiplying parent rows. + * The relation's own order/limit/offset controls children separately from parent paging. + */ + joinMany< + TForeignSchema extends ObjectSchemaBuilder< + any, + any, + any, + any, + any, + any, + any + >, + TFieldName extends string + >( + spec: JoinManySpec + ): QuerySource< + TLocalSchema, + import('./types.js').WithJoinedMany + > { + return joinManyImpl(this as any, spec); + } + + /** + * Eager-load a relation registered on the schema by name. + * @param customize - Optional callback configuring the related query. + * Use an ORM DbSet when you need typed relation names and customization fields. + */ + include( + relationName: string, + customize?: (q: QuerySource) => void + ): this { + return includeImpl(this as any, relationName, customize); + } + + /** + * Eager-load a relation declared for one polymorphic discriminator value. + * Other variants are not populated with this relation. Use ORM entity declarations + * to retain known relation customization types. + */ + includeVariant( + variantKey: string, + relationName: string, + customize?: (q: QuerySource) => void + ): this { + return includeVariantImpl( + this as any, + variantKey, + relationName, + customize + ); + } + + // ======================================================================= + // POLYMORPHIC VARIANTS + // ======================================================================= + + /** + * Add a filter applying only to the named polymorphic branch. + * Other discriminator values remain eligible. Maps the variant property to its + * CTI/STI storage column; throws for unknown variants or unsupported operators. + */ + whereVariant( + key: string, + column: string, + operator: string, + value: any + ): this { + const state = getState(this); + const variantConfig = getVariantConfig(this); + if (!variantConfig) { + throw new Error( + 'whereVariant() can only be used on a polymorphic schema (created with .withVariants())' + ); + } + + const spec = variantConfig.variants[key]; + if (!spec) { + throw new Error( + `whereVariant: unknown variant key "${key}". ` + + `Valid keys: ${Object.keys(variantConfig.variants).join(', ')}` + ); + } + + const op = operator.toLowerCase(); + if (!ALLOWED_OPS.has(op)) { + throw new Error( + `whereVariant: operator "${operator}" is not allowed. ` + + `Allowed operators: ${[...ALLOWED_OPS].join(', ')}` + ); + } + + const { propToCol } = buildColumnMap(spec.schema); + const colName = propToCol.get(column) ?? column; + + let qualifiedColumn: string; + if (spec.storage === 'cti') { + qualifiedColumn = `__v_${key}.${colName}`; + } else { + qualifiedColumn = `${state.tableName}.${colName}`; + } + + state.variantWhereFilters.push({ key, qualifiedColumn, op, value }); + invalidateCache(this); + return this; + } + + /** + * Choose which polymorphic variant bodies are loaded. + * This controls variant joins/selection, not a discriminator filter on base rows. + * @throws If the schema is not polymorphic. + */ + selectVariants(keys: string[]): this { + const state = getState(this); + if (!getVariantConfig(this)) { + throw new Error( + 'selectVariants() can only be used on a polymorphic schema (created with .withVariants())' + ); + } + state.enabledVariants = new Set(keys); + invalidateCache(this); + return this; + } + + // ======================================================================= + // ESCAPE HATCH + // ======================================================================= + + /** + * Configure the underlying mutable Knex query as an escape hatch. + * Raw changes do not infer a new result type; the caller owns column names, + * result shape and cardinality introduced by the callback. + */ + apply(fn: (builder: Knex.QueryBuilder) => void): this { + const state = getState(this); + state.opaqueReadShape = true; + invalidateCache(this); + fn(state.baseQuery); + return this; + } + + // ======================================================================= + // TRANSACTION + // ======================================================================= + + /** + * Clone this query and its eager-relation queries onto an existing transaction. + * The source builder is unchanged; transaction commit/rollback stays with the caller. + */ + transacting(trx: Knex.Transaction): QuerySource { + const state = getState(this); + const builder = new QuerySource( + trx as unknown as Knex, + state.localSchema as TLocalSchema, + state.baseQuery.clone().transacting(trx) + ); + const builderState = getState(builder); + for (const spec of state.specs) { + builderState.specs.push({ + ...spec, + foreignQuery: spec.foreignQuery.clone().transacting(trx) + }); + } + builderState.explicitSelects = state.explicitSelects + ? [...state.explicitSelects] + : null; + builderState.selectionMode = state.selectionMode; + builderState.appliedProjection = state.appliedProjection; + builderState.projectionColumns = state.projectionColumns + ? { ...state.projectionColumns } + : null; + builderState.projectionDecoders = { ...state.projectionDecoders }; + builderState.hiddenColumns = new Set(state.hiddenColumns); + builderState.includeDeleted = state.includeDeleted; + builderState.onlyDeleted = state.onlyDeleted; + builderState.skipDefaultScope = state.skipDefaultScope; + builderState.opaqueReadShape = state.opaqueReadShape; + builderState.variantConfig = state.variantConfig; + builderState.enabledVariants = + state.enabledVariants !== null + ? new Set(state.enabledVariants) + : null; + builderState.variantWhereFilters = [...state.variantWhereFilters]; + builderState.variantRelationIncludes = [ + ...state.variantRelationIncludes + ]; + return builder; + } + + // ======================================================================= + // EXECUTION + // ======================================================================= + + /** + * Render SQL for debugging without executing it. + * Bindings may appear as literal values; avoid logging sensitive inputs. + */ + toQuery(): string { + return getQuery(this).toQuery(); + } + + /** @internal ORM tracking must never attach aggregate or DTO rows as entities. */ + get returnsEntityRows(): boolean { + return getState(this).selectionMode === null; + } + + /** + * Expose the built Knex query without executing Framework row mapping. + * Direct execution returns raw database rows, potentially including internal fields. + * Treat mutations as an escape hatch rather than typed Framework configuration. + */ + toKnexQuery(): Knex.QueryBuilder { + return getQuery(this); + } + + /** + * Render the query as a debugging SQL string; equivalent to toQuery(). + */ + toString(): string { + return getQuery(this).toString(); + } + + /** + * Execute SQL and map all rows, including eager relations and aggregate decoders. + * @returns An empty array when no rows match. + * @throws Database errors and output-parser validation failures. + */ + async execute(): Promise { + return executeImpl(this) as Promise; + } + + /** + * Execute this query for its first mapped row, or undefined if none matches. + * Set ordering when the choice of first row matters. + */ + async first(): Promise { + const query = getQuery(this).first(); + const row = await query; + + if (!row) return undefined; + return cleanAndMapRow(this, row) as TResult; + } + + /** + * Execute a selection and collect one mapped column's values into an array. + * @param column - Schema property whose database column should be read. + */ + async pluck( + column: ColumnRef + ): Promise { + const _state = getState(this); + const col = resolveColumn(this, column, 'pluck') as string; + const rows = await buildQuery(this).select(col); + return rows.map( + (row: any) => row[col] ?? row[column as string] + ) as TResult[K][]; + } + + /** + * Promise-compatible execution hook enabling await query(...). + * Runs execute() and forwards fulfillment/rejection; repeated awaits may execute again. + */ + // biome-ignore lint/suspicious/noThenProperty: intentional thenable + then( + onfulfilled?: + | ((value: TResult[]) => TReturn1 | PromiseLike) + | null, + onrejected?: ((reason: any) => TReturn2 | PromiseLike) | null + ): Promise { + return this.execute().then(onfulfilled, onrejected); + } +} + +// Register the constructor for circular-dependency-safe access +registerQuerySource(QuerySource); diff --git a/libs/knex-schema/src/SchemaQueryBuilder.ts b/libs/knex-schema/src/SchemaQueryBuilder.ts index 104a8724..197c057a 100644 --- a/libs/knex-schema/src/SchemaQueryBuilder.ts +++ b/libs/knex-schema/src/SchemaQueryBuilder.ts @@ -1,47 +1,67 @@ -// @cleverbrush/knex-schema — SchemaQueryBuilder - -import type { InferType } from '@cleverbrush/schema'; import { + type ArraySchemaBuilder, + array, EXTRA_TYPE_BRAND, - METHOD_LITERAL_BRAND, - type ObjectSchemaBuilder + type InferType, + type ObjectSchemaBuilder, + object, + SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR } from '@cleverbrush/schema'; import type { Knex } from 'knex'; import { - AliasedQueryBuilder, - type AliasTables, - isTableAlias, - type TableAlias -} from './aliased-query.js'; -import { buildColumnMap } from './columns.js'; -import type { - AggregateOptions, - AggregateResult, - ExtremumResult, - ExtremumValue, - OutputSchema + buildColumnMap, + getPrimaryKeyColumns, + resolvePropertyKey +} from './columns.js'; +import type { RelationInfo, SchemaProps } from './entity.js'; +import { + type AggregateExpression, + type AggregateKind, + type AggregateOptions, + type AggregateResult, + type AliasedColumn, + COLUMN, + compileAggregate, + createAggregate, + isAggregate, + type OutputSchema } from './expressions.js'; -import { getTableName, POLYMORPHIC_TYPE_BRAND } from './extension.js'; -import { scalarAggregate } from './operations/aggregate.js'; +import { getProjections, getTableName, getVariants } from './extension.js'; +import { OpaqueQuery, type QueryOutput } from './OpaqueQuery.js'; import { type CompositeCursorOptions, compositeCursor } from './operations/composite-cursor.js'; -// Operations +import type { ScopesOf } from './operations/helpers.js'; +import { getQuerySourceCtor } from './operations/helpers.js'; +import { getState } from './operations/state.js'; +import { PolymorphicQueryBuilder } from './PolymorphicQueryBuilder.js'; +import { QuerySource } from './QuerySource.js'; +import type { ReadRelations, ReadVariantMetadata } from './read-entity.js'; +import { + captureReadRaw, + captureValue, + type ReadPredicate, + type ReadPredicateContext, + type ReadPredicateSelector, + ReadPredicates +} from './read-predicates.js'; +import { compileReadProjection, type ReadField } from './read-projection.js'; import { - avgImpl, - countDistinctImpl, - countImpl, - distinctImpl, - maxImpl, - minImpl, - projectedImpl, - scopedImpl, - selectImpl, - selectRawImpl, - sumImpl, - unscopedImpl -} from './operations/select.js'; + type ColumnReadSchema, + compileReadSchema, + decodeObject, + type ObjectReadSchema, + type ReadNode, + type ReadObject, + type ReadSchema, + ReadSchemaError, + readExpression, + type SchemaForValue +} from './read-schema.js'; + +export { type BoundQuery, createQuery, query } from './query.js'; + import type { ColumnRef, CursorPaginationResult, @@ -49,264 +69,429 @@ import type { JoinManySpec, JoinOneSpec, PaginationResult, - SelectProjection, - SelectSelector + RelationSpec } from './types.js'; -export { - OnConflictBuilder, - type OnConflictMergeHelpers, - type OnConflictMergeOptions, - type OnConflictUpdateData, - type OnConflictUpdateValue -} from './operations/insert.js'; - -import { - deleteImpl, - hardDeleteImpl, - onlyDeletedImpl, - restoreImpl, - withDeletedImpl -} from './operations/delete.js'; -import { - ALLOWED_OPS, - buildQuery, - cleanAndMapRow, - getQuery, - getVariantConfig, - invalidateCache, - registerSchemaQueryBuilder, - resolveColumn -} from './operations/helpers.js'; -import { - bulkInsertImpl, - bulkUpsertImpl, - insertImpl, - insertManyImpl, - onConflictImpl, - upsertImpl -} from './operations/insert.js'; -import { - includeImpl, - includeVariantImpl, - joinManyImpl, - joinOneImpl -} from './operations/join.js'; -import { - executeImpl, - limitImpl, - offsetImpl, - paginateAfterImpl, - paginateImpl -} from './operations/pagination.js'; -import { getState, setState } from './operations/state.js'; -import { bulkUpdateImpl, updateImpl } from './operations/update.js'; -import { - andWhereImpl, - groupByImpl, - groupByRawImpl, - havingImpl, - havingRawImpl, - orderByImpl, - orderByRawImpl, - orWhereImpl, - orWhereInImpl, - orWhereNotInImpl, - orWhereNotNullImpl, - orWhereNullImpl, - whereBetweenImpl, - whereExistsImpl, - whereILikeImpl, - whereImpl, - whereInImpl, - whereJsonPathImpl, - whereLikeImpl, - whereNotBetweenImpl, - whereNotExistsImpl, - whereNotImpl, - whereNotInImpl, - whereNotNullImpl, - whereNullImpl, - whereRawImpl -} from './operations/where.js'; -import { type SchemaAwareQuery, schemaReadQuery } from './SchemaReadQuery.js'; - -// --------------------------------------------------------------------------- -// Type-level helpers -// --------------------------------------------------------------------------- - -type ScopesOf = S extends { - readonly [METHOD_LITERAL_BRAND]?: infer N; +const READ_COLUMN = Symbol('schema-read-column'); +const JSON_PATH = Symbol('schema-json-path'); +/** @internal Nominal read-query identity used without recursively comparing fluent APIs. */ +export const READ_QUERY = Symbol('schema-read-query'); +/** Minimal type surface for a customizer's strongly inferred result schema. */ +export interface ReadQueryShape { + /** @internal Nominal marker; constructing a plain schema object is not a query. */ + readonly [READ_QUERY]: true; + /** Decoded result schema inferred from the returned query. */ + readonly rowSchema: R; } - ? Extract - : never; - -type ProjectionsOf = S extends { - readonly [EXTRA_TYPE_BRAND]?: infer P; +let readAliasSequence = 0; +/** A typed SQL column; selectors receive descriptions, not row values. */ +export interface ReadColumn + extends AliasedColumn> { + /** @internal Decoding and projection metadata shared by the query compiler. */ + readonly [READ_COLUMN]: ReadNode; + /** @internal Captured JSON path beneath a storage column. */ + readonly [JSON_PATH]?: readonly string[]; + /** Type-only property identity for column-list projections. */ + readonly __property?: K; } - ? P extends Record - ? P - : Record - : Record; - -type ProjectionKeysOf< +/** A JSON object column also exposes its known nested properties. */ +export type NestedReadColumn = ReadColumn< + ColumnReadSchema, + Key +> & + (S extends ReadObject + ? { + [K in keyof SchemaProps & string]: NestedReadColumn< + SchemaProps[K], + `${Key}.${K}` + >; + } + : {}); +/** Columns available for typed projections and filters. */ +export type ReadColumns< + S extends ReadObject, + Relations extends PropertyKey = never +> = { + [K in Exclude, Relations> & string]: NestedReadColumn< + SchemaProps[K], + K + >; +}; +type SelectedValue = + | (Selector extends (...args: any[]) => AliasedColumn + ? Value + : Selector extends keyof Columns + ? Columns[Selector] extends AliasedColumn + ? Value + : never + : never) + | null; +type Selection = Record | AggregateExpression>; +type MergeProps = { + [K in keyof A | keyof B]: K extends keyof B + ? B[K] + : K extends keyof A + ? A[K] + : never; +}; +type NamedProjections = S extends { readonly [EXTRA_TYPE_BRAND]?: infer P } + ? P + : {}; +type NamedKeys< S, - K extends keyof ProjectionsOf & string -> = ProjectionsOf[K] extends readonly (infer T extends string)[] - ? T - : string; - -type QueryResultType = TLocalSchema extends { - readonly [POLYMORPHIC_TYPE_BRAND]?: infer U; -} - ? NonNullable - : InferType; - -// --------------------------------------------------------------------------- -// SchemaQueryBuilder -// --------------------------------------------------------------------------- + K extends keyof NamedProjections +> = NamedProjections[K] extends readonly (infer Key extends string)[] + ? Key + : never; +/** Structural schema inferred from a typed projection. */ +export type ReadProjection = ObjectSchemaBuilder<{ + [K in keyof S & string]: S[K] extends ReadColumn + ? R + : S[K] extends AggregateExpression + ? SchemaForValue + : never; +}>; +type AddField< + S extends ReadObject, + K extends string, + F extends ReadSchema +> = ObjectSchemaBuilder, K> & Record>; +/** @internal Foreign schema retained by a declared relation. */ +export type Related = R extends RelationInfo ? S : never; +/** @internal Output cardinality and nullability of a loaded relation. */ +export type RelationField = + R extends RelationInfo<'hasMany' | 'belongsToMany', any> + ? ArraySchemaBuilder + : R extends RelationInfo<'belongsTo', any> + ? R extends { optional: infer O } + ? true extends O + ? SchemaForValue | null> + : S + : S + : SchemaForValue | null>; +type AnyReadQuery = + | SchemaQueryBuilder + | PolymorphicQueryBuilder; +type Loaded = { + name: string; + query: AnyReadQuery; + relation: RelationSpec; + required: boolean; +}; +/** Query factory result: a table query or a declared polymorphic union. */ +export type SchemaAwareQuery = + ReadVariantMetadata extends { + discriminator: string; + variants: Record; + } + ? PolymorphicQueryBuilder + : SchemaQueryBuilder; +/** @internal Apply parent correlation before child selection, ordering and pagination. */ +export type ReadCorrelation = ( + query: Knex.QueryBuilder, + alias: string, + source: ReadObject +) => void; /** - * Build schema-aware SQL with mapped columns, projections and eager relations. - * Fluent configuration methods mutate this builder; create a fresh query for each - * independent operation. Await the builder or call execute() to obtain mapped rows. - * Use query() to infer both the schema and result types automatically. + * Immutable table query whose row schema follows its decoded selection and relations. + * Configuration returns independent lazy builders; metadata access never executes SQL. + * Only unprojected table queries can write. Full ORM rows may participate in tracking. */ export class SchemaQueryBuilder< - TLocalSchema extends ObjectSchemaBuilder, - TResult -> { - /** - * Enter immutable, detached schema-aware read mode before selecting/loading fields. - * Row metadata never runs SQL; existing query behavior remains unchanged. - * @throws ReadSchemaError if result-shaping operations were already applied. - */ - withRowSchema(): SchemaAwareQuery { - return schemaReadQuery(this); - } - /** - * Create a query over the schema's configured table. - * @param knex - Database connection or transaction used to execute the query. - * @param localSchema - Schema containing property/column and relation metadata. - * @param baseQuery - Optional existing Knex query to configure; it is not cloned. - */ + S extends ReadObject, + Row extends ReadObject = ObjectReadSchema>, + Relations extends Record = ReadRelations, + Writable extends boolean = true +> extends ReadPredicates> { + /** @internal Nominal identity for typed child-query customizers. */ + declare readonly [READ_QUERY]: true; + private declare readonly writable: Writable; + private readonly alias = `__schema_read_${readAliasSequence++}`; + private fields: Record; + private loaded: Loaded[] = []; + private base: Knex.QueryBuilder; + private selected = false; + private grouped = false; + private distinctRows = false; + private defaults?: Knex.QueryBuilder; + private skipDefaults = false; + private deleted: 'exclude' | 'include' | 'only' = 'exclude'; + private readonly columns: Record>; + private readonly columnSet = new Set>(); + /** Runtime structural schema; stable across filters, pagination and transaction clones. */ + readonly rowSchema: Row; + + /** @internal Use query() or createQuery() instead of constructing directly. */ constructor( - knex: Knex, - localSchema: TLocalSchema, - baseQuery?: Knex.QueryBuilder + private readonly knex: Knex, + private readonly source: S, + base: Knex.QueryBuilder, + alias?: string ) { - const tableName = getTableName(localSchema); - setState(this, { - knex, - baseQuery: baseQuery ?? knex(tableName), - localSchema, - specs: [], - tableName, - explicitSelects: null, - selectionMode: null, - appliedProjection: null, - projectionColumns: null, - projectionDecoders: {}, - hiddenColumns: new Set(), - includeDeleted: false, - onlyDeleted: false, - skipDefaultScope: false, - variantConfig: undefined, - enabledVariants: null, - variantWhereFilters: [], - variantRelationIncludes: [], - cachedBuiltQuery: null + super(); + if (alias !== undefined) this.alias = alias; + this.base = knex.queryBuilder().from(base.clone().as(this.alias)); + const relations = (source.introspect().extensions?.relations ?? + []) as RelationSpec[]; + const excluded = new Set(relations.map(r => r.name)); + const { propToCol } = buildColumnMap(source); + this.fields = Object.create(null); + this.columns = Object.create(null); + for (const [key, schema] of Object.entries( + source.introspect().properties ?? {} + )) { + if (excluded.has(key)) continue; + const node = compileReadSchema(schema as ReadSchema); + const column = `${this.alias}.${propToCol.get(key) ?? key}`; + this.columns[key] = this.describeColumn( + schema as ReadSchema, + propToCol.get(key) ?? key + ); + this.fields[key] = { + node, + expression: knex => readExpression(knex, node, column) + }; + } + this.rowSchema = this.schema() as Row; + const defaultScope = source.introspect().extensions?.defaultScope; + if (typeof defaultScope === 'function') { + const scope = this.copy(); + scope.base = knex.queryBuilder(); + this.defaults = this.checkScope( + scope, + defaultScope(scope) + ).base.clone(); + } + } + + private describeColumn( + schema: ReadSchema, + column: string, + path: readonly string[] = [] + ): ReadColumn { + const described: ReadColumn = { + [COLUMN]: { + alias: this.alias, + column, + schema + }, + [READ_COLUMN]: compileReadSchema(schema), + [JSON_PATH]: path + }; + this.columnSet.add(described); + const info = schema.introspect(); + if (info.type === 'object') { + for (const [key, child] of Object.entries((info as any).properties)) + Object.defineProperty(described, key, { + value: this.describeColumn(child as ReadSchema, column, [ + ...path, + key + ]), + enumerable: true + }); + } + return described; + } + + private schema(): ReadObject { + return object( + Object.fromEntries( + Object.entries(this.fields).map(([key, field]) => [ + key, + field.node.schema + ]) + ) + ); + } + + private copy(): this { + const copy = Object.create(Object.getPrototypeOf(this)) as this; + Object.assign(copy, this, { + base: this.base.clone(), + fields: { ...this.fields }, + loaded: [...this.loaded] }); + return copy; } - // ======================================================================= - // SELECT / DISTINCT / AGGREGATES - // ======================================================================= + /** @internal Prevent customizers from substituting an unrelated query source. */ + sameSource(other: unknown): boolean { + return ( + other instanceof SchemaQueryBuilder && + this.columns === other.columns + ); + } - /** - * Choose columns, or use an object selector to infer a flat result shape. - * Object values can be schema descriptors or aggregate expressions. Column-list - * selection retains the existing result type; raw SQL cannot infer a new shape. - * @returns This builder, narrowed to the object projection when one is supplied. - */ - select(...columns: (ColumnRef | Knex.Raw)[]): this; - /** - * Choose columns, or use an object selector to infer a flat result shape. - * Object values can be schema descriptors or aggregate expressions. Column-list - * selection retains the existing result type; raw SQL cannot infer a new shape. - * @returns This builder, narrowed to the object projection when one is supplied. - */ - select>( - selector: TSel - ): SchemaQueryBuilder>>; - /** - * Choose columns, or use an object selector to infer a flat result shape. - * Object values can be schema descriptors or aggregate expressions. Column-list - * selection retains the existing result type; raw SQL cannot infer a new shape. - * @returns This builder, narrowed to the object projection when one is supplied. - */ - select(...args: unknown[]): any { - return selectImpl(this as any, ...args); + private column( + selector: ReadPredicateSelector> + ): ReadColumn { + const column = + typeof selector === 'string' + ? selector + .split('.') + .reduce((node: any, key) => node?.[key], this.columns) + : selector(this.columns as ReadColumns); + if (!column || !this.columnSet.has(column as ReadColumn)) + throw new ReadSchemaError( + 'Column does not belong to this read query' + ); + return column as ReadColumn; + } + + private name( + column: ReadColumn, + alias = column[COLUMN].alias + ): string | Knex.Raw { + const name = `${alias}.${column[COLUMN].column}`; + const path = column[JSON_PATH]; + if (!path?.length) return name; + const type = column[READ_COLUMN].schema.introspect().type; + const json = type === 'array' || type === 'object'; + const extracted = this.knex.raw(`?? ${json ? '#>' : '#>>'} ?::text[]`, [ + name, + [...path] + ]); + if (type === 'number') + return this.knex.raw('cast(? as numeric)', [extracted]); + if (type === 'boolean') + return this.knex.raw('cast(? as boolean)', [extracted]); + return extracted; + } + + private checkScope(input: this, result: unknown): this { + if ( + !(result instanceof SchemaQueryBuilder) || + !input.sameSource(result) || + result.rowSchema !== input.rowSchema || + result.grouped !== input.grouped || + result.selected !== input.selected || + result.distinctRows !== input.distinctRows || + result.deleted !== input.deleted || + result.skipDefaults !== input.skipDefaults || + result.knex !== input.knex + ) { + if (result instanceof Promise) void result.catch(() => {}); + throw new ReadSchemaError( + 'Scopes must synchronously return the supplied query with filters, ordering or pagination only' + ); + } + return result as this; } - /** - * Apply SQL DISTINCT to the selected columns, optionally adding columns. - * Property selectors are mapped to database names; SQL decides row equality. - */ - distinct(...columns: (ColumnRef | Knex.Raw)[]): this { - return (distinctImpl as any)(this, ...columns); + /** Apply a named, synchronous shape-preserving scope once to an independent query. */ + scoped(name: ScopesOf): this { + const scope = ( + this.source.introspect().extensions?.scopes as + | Record + | undefined + )?.[name]; + if (!scope) throw new ReadSchemaError(`Unknown scope: ${name}`); + const copy = this.copy(); + return this.checkScope(copy, scope(copy)); } - /** - * Append a legacy SQL COUNT selection without executing the query. - * The driver controls the result shape/value type. Prefer countValue() for a - * checked scalar number, or aggregate.count() in a typed object projection. - */ - count(column?: ColumnRef | Knex.Raw): this { - return (countImpl as any)(this, column); + /** Exclude only the default scope; keep explicitly configured predicates. */ + unscoped(): this { + const copy = this.copy(); + copy.skipDefaults = true; + return copy; } - /** - * Append a legacy COUNT(DISTINCT column) selection. - * Prefer countDistinctValue() for a checked scalar or aggregate.countDistinct() - * for an inferred grouped result; this legacy method retains the builder type. - */ - countDistinct(column?: ColumnRef | Knex.Raw): this { - return (countDistinctImpl as any)(this, column); + /** Include soft-deleted rows without changing the source query. */ + withDeleted(): this { + const copy = this.copy(); + copy.deleted = 'include'; + return copy; } - /** - * Append a legacy MIN selection without changing the result type. - * Use minValue() for a scalar with explicit decoding, or aggregate.min() in a - * typed projection. SQL returns null for an empty/all-null input. - */ - min(column: ColumnRef | Knex.Raw): this { - return (minImpl as any)(this, column); + /** Match only soft-deleted rows. */ + onlyDeleted(): this { + const copy = this.copy(); + copy.deleted = 'only'; + return copy; } - /** - * Append a legacy MAX selection without changing the result type. - * Use maxValue() for a scalar with explicit decoding, or aggregate.max() in a - * typed projection. SQL returns null for an empty/all-null input. - */ - max(column: ColumnRef | Knex.Raw): this { - return (maxImpl as any)(this, column); + /** True only when rows retain their complete entity shape. */ + get returnsEntityRows(): boolean { + return !this.selected && !this.grouped && !this.distinctRows; } - /** - * Append a legacy SUM selection, leaving numeric conversion to the driver. - * Prefer sumValue() or aggregate.sum() to preserve exact numeric text by default. - */ - sum(column: ColumnRef | Knex.Raw): this { - return (sumImpl as any)(this, column); + /** @internal Build an independent statement containing defaults and explicit filters. */ + private filtered(): Knex.QueryBuilder { + const query = this.base.clone(); + const explicitWhere = (query as any)._statements.filter( + (statement: any) => statement.grouping === 'where' + ); + (query as any)._statements = (query as any)._statements.filter( + (statement: any) => statement.grouping !== 'where' + ); + if (this.defaults && !this.skipDefaults) { + const defaults = this.defaults.clone() as any; + const where = defaults._statements.filter( + (statement: any) => statement.grouping === 'where' + ); + (query as any)._statements = [ + ...defaults._statements.filter( + (statement: any) => statement.grouping !== 'where' + ), + ...(query as any)._statements + ]; + (query as any)._single = { + ...defaults._single, + ...(query as any)._single + }; + if (where.length) + query.where(nested => { + (nested as any)._statements = [...where]; + }); + } + if (explicitWhere.length) + query.where(nested => { + (nested as any)._statements = [...explicitWhere]; + }); + const softDelete = this.source.introspect().extensions?.softDelete as + | { column: string } + | undefined; + if (softDelete && this.deleted !== 'include') { + query[this.deleted === 'only' ? 'whereNotNull' : 'whereNull']( + `${this.alias}.${softDelete.column}` + ); + } + return query; } - /** - * Append a legacy AVG selection, leaving numeric conversion to the driver. - * Prefer avgValue() or aggregate.avg() for a typed, precision-preserving result. - */ - avg(column: ColumnRef | Knex.Raw): this { - return (avgImpl as any)(this, column); + private async scalar( + kind: AggregateKind, + selector?: ReadPredicateSelector>, + options?: AggregateOptions + ): Promise { + if ( + this.grouped || + this.distinctRows || + Object.values(this.fields).some(field => field.aggregate) + ) + throw new ReadSchemaError( + 'Scalar aggregates require an ungrouped source' + ); + const column = + selector === undefined ? undefined : this.column(selector); + const compiled = compileAggregate( + this.knex, + createAggregate(kind, column, options), + () => this.name(column!, '__aggregate') + ); + const source = this.compile() + .clearSelect() + .clearOrder() + .clear('limit') + .clear('offset') + .select(this.knex.raw('??.*', [this.alias])); + const row = await this.knex + .from(source.as('__aggregate')) + .select({ value: compiled.sql }) + .first(); + return compiled.decode(row?.value); } /** @@ -316,9 +501,9 @@ export class SchemaQueryBuilder< * @returns A safe integer by default, including zero for an empty source. * @throws If the default count overflows, the source is grouped/distinct, or parsing fails. */ - countValue | undefined = undefined>( - options?: AggregateOptions - ): Promise>; + countValue | undefined = undefined>( + options?: AggregateOptions + ): Promise>; /** * Count non-null column values without mutating the source query. * Source paging/order is ignored; filters, scopes and transactions remain. @@ -326,10 +511,10 @@ export class SchemaQueryBuilder< * @param options - Optional parser replacing safe-integer decoding. * @throws If the default result overflows or the source is grouped/distinct. */ - countValue | undefined = undefined>( - column: ColumnRef, - options?: AggregateOptions - ): Promise>; + countValue | undefined = undefined>( + column: ReadPredicateSelector>, + options?: AggregateOptions + ): Promise>; /** * Count matching rows, or non-null column values, without mutating this query. * Ignores source ordering/limits/offsets while retaining filters and transactions. @@ -338,14 +523,15 @@ export class SchemaQueryBuilder< * @throws If the default count overflows, the source is grouped/distinct, or parsing fails. */ countValue( - columnOrOptions?: ColumnRef | AggregateOptions, + columnOrOptions?: + | ReadPredicateSelector> + | AggregateOptions, options?: AggregateOptions ): Promise { const hasColumn = typeof columnOrOptions === 'string' || typeof columnOrOptions === 'function'; - return scalarAggregate( - this, + return this.scalar( 'count', hasColumn ? columnOrOptions : undefined, hasColumn ? options : columnOrOptions @@ -359,11 +545,11 @@ export class SchemaQueryBuilder< * @returns A safe integer, or the parser's inferred output type. * @throws If the count is unsafe, the source is grouped/distinct, or parsing fails. */ - countDistinctValue | undefined = undefined>( - column: ColumnRef, - options?: AggregateOptions - ): Promise> { - return scalarAggregate(this, 'countDistinct', column, options); + countDistinctValue | undefined = undefined>( + column: ReadPredicateSelector>, + options?: AggregateOptions + ): Promise> { + return this.scalar('countDistinct', column, options); } /** @@ -373,11 +559,11 @@ export class SchemaQueryBuilder< * @returns Database numeric text, or null for empty/all-null input by default. * An output parser replaces default decoding and controls the result type. */ - sumValue | undefined = undefined>( - column: ColumnRef, - options?: AggregateOptions - ): Promise> { - return scalarAggregate(this, 'sum', column, options); + sumValue | undefined = undefined>( + column: ReadPredicateSelector>, + options?: AggregateOptions + ): Promise> { + return this.scalar('sum', column, options); } /** @@ -387,11 +573,11 @@ export class SchemaQueryBuilder< * @returns Exact database numeric text or null by default; a parser overrides this. * @remarks Text preserves database precision, not precision already lost in floating-point storage. */ - avgValue | undefined = undefined>( - column: ColumnRef, - options?: AggregateOptions - ): Promise> { - return scalarAggregate(this, 'avg', column, options); + avgValue | undefined = undefined>( + column: ReadPredicateSelector>, + options?: AggregateOptions + ): Promise> { + return this.scalar('avg', column, options); } /** @@ -402,22 +588,15 @@ export class SchemaQueryBuilder< * Dates return Date; numeric SQL overrides may return exact strings. */ minValue< - C extends ColumnRef, - S extends OutputSchema | undefined = undefined + C extends ReadPredicateSelector>, + O extends OutputSchema | undefined = undefined >( column: C, - options?: AggregateOptions + options?: AggregateOptions ): Promise< - AggregateResult< - S, - C extends (...args: any[]) => infer D - ? ExtremumValue - : C extends keyof InferType - ? ExtremumResult[C]> - : unknown - > + AggregateResult, C>> > { - return scalarAggregate(this, 'min', column, options); + return this.scalar('min', column, options); } /** @@ -428,1177 +607,1038 @@ export class SchemaQueryBuilder< * Dates return Date; numeric SQL overrides may return exact strings. */ maxValue< - C extends ColumnRef, - S extends OutputSchema | undefined = undefined + C extends ReadPredicateSelector>, + O extends OutputSchema | undefined = undefined >( column: C, - options?: AggregateOptions + options?: AggregateOptions ): Promise< - AggregateResult< - S, - C extends (...args: any[]) => infer D - ? ExtremumValue - : C extends keyof InferType - ? ExtremumResult[C]> - : unknown - > + AggregateResult, C>> > { - return scalarAggregate(this, 'max', column, options); + return this.scalar('max', column, options); } - /** - * Append a raw SELECT expression with optional Knex value/identifier bindings. - * The caller owns its SQL and result shape; this does not infer a new result type. - */ - selectRaw(sql: string, bindings?: any[]): this { - return selectRawImpl(this as any, sql, bindings); + /** Select an exact flat row shape, retaining per-field runtime schemas. */ + select & string>( + ...columns: Array< + | K + | ((columns: ReadColumns) => ReadColumn) + > + ): SchemaQueryBuilder< + S, + ObjectSchemaBuilder>, K>>, + Relations, + false + >; + /** Select named output fields and aggregates, replacing the previous scalar projection. */ + select

( + selector: (columns: ReadColumns) => P + ): SchemaQueryBuilder< + S, + ObjectSchemaBuilder< + MergeProps< + SchemaProps>, + Pick< + SchemaProps, + Extract, keyof Relations> + > + > + >, + Relations, + false + >; + select(...selectors: any[]): any { + const selections = selectors.map(selector => + typeof selector === 'function' + ? selector(this.columns) + : this.columns[selector] + ); + const selection = + selections.length === 1 && + selections[0] && + !(COLUMN in selections[0]) + ? selections[0] + : Object.fromEntries( + selections.map(column => { + const key = Object.keys(this.columns).find( + key => this.columns[key] === column + ); + if (!key) + throw new ReadSchemaError( + 'Selection must reference columns from this query' + ); + return [key, column]; + }) + ); + if (this.loaded.length && Object.values(selection).some(isAggregate)) + throw new ReadSchemaError( + 'Aggregate projections cannot contain relations' + ); + const copy = this.copy(); + copy.fields = compileReadProjection( + this.knex, + selection, + expression => { + if (!this.columnSet.has(expression as ReadColumn)) + throw new ReadSchemaError( + 'Projection column does not belong to this query' + ); + const column = expression as ReadColumn; + return { node: column[READ_COLUMN], name: this.name(column) }; + } + ); + copy.selected = true; + // Included relations are independent of the scalar projection. + for (const relation of copy.loaded) + copy.fields[relation.name] = this.fields[relation.name]; + Object.assign(copy, { rowSchema: copy.schema() }); + return copy as any; } - /** - * Apply a named schema projection and narrow the selected property type. - * @param name - Projection registered with the schema's projection() extension. - * @throws If the projection is unknown or conflicts with a prior selection. - */ - projected & string>( + /** Select a typed COUNT result on a new query. */ + count( + column?: ReadPredicateSelector> + ): SchemaQueryBuilder< + S, + ReadProjection<{ count: AggregateExpression }>, + Relations, + false + > { + return this.select(() => ({ + count: createAggregate( + 'count', + column === undefined ? undefined : this.column(column) + ) + })) as any; + } + /** Select a typed COUNTDISTINCT result on a new query. */ + countDistinct( + column: ReadPredicateSelector> + ): SchemaQueryBuilder< + S, + ReadProjection<{ countDistinct: AggregateExpression }>, + Relations, + false + > { + return this.select(() => ({ + countDistinct: createAggregate( + 'countDistinct', + column === undefined ? undefined : this.column(column) + ) + })) as any; + } + /** Select a typed SUM result on a new query. */ + sum( + column: ReadPredicateSelector> + ): SchemaQueryBuilder< + S, + ReadProjection<{ sum: AggregateExpression }>, + Relations, + false + > { + return this.select(() => ({ + sum: createAggregate( + 'sum', + column === undefined ? undefined : this.column(column) + ) + })) as any; + } + /** Select a typed AVG result on a new query. */ + avg( + column: ReadPredicateSelector> + ): SchemaQueryBuilder< + S, + ReadProjection<{ avg: AggregateExpression }>, + Relations, + false + > { + return this.select(() => ({ + avg: createAggregate( + 'avg', + column === undefined ? undefined : this.column(column) + ) + })) as any; + } + /** Select a typed MIN result on a new query. */ + min>>( + column: C + ): SchemaQueryBuilder< + S, + ReadProjection<{ + min: AggregateExpression< + SelectedValue, C> + >; + }>, + Relations, + false + > { + return this.select(() => ({ + min: createAggregate< + SelectedValue, C> + >('min', this.column(column)) + })) as any; + } + /** Select a typed MAX result on a new query. */ + max>>( + column: C + ): SchemaQueryBuilder< + S, + ReadProjection<{ + max: AggregateExpression< + SelectedValue, C> + >; + }>, + Relations, + false + > { + return this.select(() => ({ + max: createAggregate< + SelectedValue, C> + >('max', this.column(column)) + })) as any; + } + + /** Keep only distinct selected rows, preserving the row schema. */ + distinct(): SchemaQueryBuilder; + /** Select a property subset and eliminate duplicate rows on that immutable projection. */ + distinct & string>( + ...columns: Array< + | K + | ((columns: ReadColumns) => ReadColumn) + > + ): SchemaQueryBuilder< + S, + ObjectSchemaBuilder>, K>>, + Relations, + false + >; + distinct(...columns: any[]): any { + const copy = columns.length ? this.select(...columns) : this.copy(); + copy.distinctRows = true; + return copy as any; + } + /** Filter grouped results using trusted SQL and captured bindings. */ + havingRaw( + sql: string, + bindings: readonly Knex.RawBinding[] = [] + ): SchemaQueryBuilder { + const copy = this.copy(); + copy.base.havingRaw(captureReadRaw(this.knex, sql, bindings)()); + copy.grouped = true; + return copy as any; + } + /** Filter groups by a mapped column comparison. */ + having( + column: ReadPredicateSelector>, + operator: string, + value: unknown + ): SchemaQueryBuilder { + const copy = this.copy(); + copy.base.having( + this.name(this.column(column)), + operator, + captureValue(this.knex, value)() + ); + copy.grouped = true; + return copy as any; + } + /** Group using trusted SQL without changing the explicit projection. */ + groupByRaw( + sql: string, + bindings: readonly Knex.RawBinding[] = [] + ): SchemaQueryBuilder { + const copy = this.copy(); + copy.base.groupByRaw(captureReadRaw(this.knex, sql, bindings)()); + copy.grouped = true; + return copy as any; + } + + /** Apply a named schema projection with the same exact row-schema guarantees. */ + projected & string>( name: K ): SchemaQueryBuilder< - TLocalSchema, - Pick & keyof TResult> + S, + ObjectSchemaBuilder< + Pick< + SchemaProps>, + Extract, keyof SchemaProps>> + > & + Pick< + SchemaProps, + Extract, keyof Relations> + > + >, + Relations, + false > { - return projectedImpl(this as any, name); - } - - /** - * Apply a named schema scope to this query. - * @param name - Scope registered with the schema's scope() extension. - * @throws If the requested scope is not registered. - */ - scoped>(name: K): this { - return scopedImpl(this as any, name as string); - } - - /** - * Disable the default read scope and include soft-deleted rows. - * Explicit filters already added to this builder remain in place. - */ - unscoped(): this { - return unscopedImpl(this as any); - } - - // ======================================================================= - // WHERE - // ======================================================================= - - /** - * Add an AND filter using mapped schema columns, a record, or raw Knex SQL. - * Selector/key and record forms map property names to database columns. Grouped - * callbacks receive a Knex builder and therefore use database column names. - * Values are bound; use the operator form for comparisons other than equality. - */ - where(column: ColumnRef, operator: string, value: any): this; - /** - * Add an AND filter using mapped schema columns, a record, or raw Knex SQL. - * Selector/key and record forms map property names to database columns. Grouped - * callbacks receive a Knex builder and therefore use database column names. - * Values are bound; use the operator form for comparisons other than equality. - */ - where(column: ColumnRef, value: any): this; - /** - * Add an AND filter using mapped schema columns, a record, or raw Knex SQL. - * Selector/key and record forms map property names to database columns. Grouped - * callbacks receive a Knex builder and therefore use database column names. - * Values are bound; use the operator form for comparisons other than equality. - */ - where(raw: Knex.Raw, operator: string, value: any): this; - /** - * Add an AND filter using mapped schema columns, a record, or raw Knex SQL. - * Selector/key and record forms map property names to database columns. Grouped - * callbacks receive a Knex builder and therefore use database column names. - * Values are bound; use the operator form for comparisons other than equality. - */ - where(callback: (builder: Knex.QueryBuilder) => void): this; - /** - * Add an AND filter using mapped schema columns, a record, or raw Knex SQL. - * Selector/key and record forms map property names to database columns. Grouped - * callbacks receive a Knex builder and therefore use database column names. - * Values are bound; use the operator form for comparisons other than equality. - */ - where(record: Record): this; - /** - * Add an AND filter using mapped schema columns, a record, or raw Knex SQL. - * Selector/key and record forms map property names to database columns. Grouped - * callbacks receive a Knex builder and therefore use database column names. - * Values are bound; use the operator form for comparisons other than equality. - */ - where(raw: Knex.Raw): this; - /** - * Add an AND filter using mapped schema columns, a record, or raw Knex SQL. - * Selector/key and record forms map property names to database columns. Grouped - * callbacks receive a Knex builder and therefore use database column names. - * Values are bound; use the operator form for comparisons other than equality. - */ - where(columnOrRaw: any, ...args: any[]): this { - return whereImpl(this as any, columnOrRaw, ...args); + const definition = getProjections(this.source)[name]; + if (!definition) + throw new ReadSchemaError(`Unknown projection: ${name}`); + return this.select(() => + Object.fromEntries( + definition.keys.map(key => [key, this.columns[key]]) + ) + ) as any; + } + + protected readPredicateContext(): ReadPredicateContext< + ReadColumns + > { + return { + knex: this.knex, + column: selector => this.name(this.column(selector)) + }; } - - /** - * Add an AND condition; an explicit synonym for where(). - * Property references and record keys are mapped; raw callbacks use Knex columns. - */ - andWhere( - column: ColumnRef, - operator: string, - value: any - ): this; - /** - * Add an AND condition; an explicit synonym for where(). - * Property references and record keys are mapped; raw callbacks use Knex columns. - */ - andWhere(column: ColumnRef, value: any): this; - /** - * Add an AND condition; an explicit synonym for where(). - * Property references and record keys are mapped; raw callbacks use Knex columns. - */ - andWhere(record: Record): this; - /** - * Add an AND condition; an explicit synonym for where(). - * Property references and record keys are mapped; raw callbacks use Knex columns. - */ - andWhere(callback: (builder: Knex.QueryBuilder) => void): this; - /** - * Add an AND condition; an explicit synonym for where(). - * Property references and record keys are mapped; raw callbacks use Knex columns. - */ - andWhere(raw: Knex.Raw): this; - /** - * Add an AND condition; an explicit synonym for where(). - * Property references and record keys are mapped; raw callbacks use Knex columns. - */ - andWhere(columnOrRaw: any, ...args: any[]): this { - return andWhereImpl(this as any, columnOrRaw, ...args); + protected addReadPredicate(predicate: ReadPredicate): this { + const copy = this.copy(); + predicate(copy.base); + return copy; } - - /** - * Add an OR condition using a mapped property, record, raw SQL or Knex group. - * Group mixed AND/OR conditions explicitly when SQL precedence would change intent. - */ - orWhere( - column: ColumnRef, - operator: string, - value: any - ): this; - /** - * Add an OR condition using a mapped property, record, raw SQL or Knex group. - * Group mixed AND/OR conditions explicitly when SQL precedence would change intent. - */ - orWhere(column: ColumnRef, value: any): this; - /** - * Add an OR condition using a mapped property, record, raw SQL or Knex group. - * Group mixed AND/OR conditions explicitly when SQL precedence would change intent. - */ - orWhere(record: Record): this; - /** - * Add an OR condition using a mapped property, record, raw SQL or Knex group. - * Group mixed AND/OR conditions explicitly when SQL precedence would change intent. - */ - orWhere(callback: (builder: Knex.QueryBuilder) => void): this; - /** - * Add an OR condition using a mapped property, record, raw SQL or Knex group. - * Group mixed AND/OR conditions explicitly when SQL precedence would change intent. - */ - orWhere(raw: Knex.Raw): this; - /** - * Add an OR condition using a mapped property, record, raw SQL or Knex group. - * Group mixed AND/OR conditions explicitly when SQL precedence would change intent. - */ - orWhere(columnOrRaw: any, ...args: any[]): this { - return orWhereImpl(this as any, columnOrRaw, ...args); + /** @internal Apply an already captured Framework predicate to an independent query. */ + withPredicate(predicate: ReadPredicate): this { + return this.addReadPredicate(predicate); } - - /** - * Add a negated condition using a mapped property, record or Knex group. - * Use whereNotIn()/whereNotBetween() for their dedicated SQL operators. - */ - whereNot( - column: ColumnRef, - operator: string, - value: any - ): this; - /** - * Add a negated condition using a mapped property, record or Knex group. - * Use whereNotIn()/whereNotBetween() for their dedicated SQL operators. - */ - whereNot(column: ColumnRef, value: any): this; - /** - * Add a negated condition using a mapped property, record or Knex group. - * Use whereNotIn()/whereNotBetween() for their dedicated SQL operators. - */ - whereNot(record: Record): this; - /** - * Add a negated condition using a mapped property, record or Knex group. - * Use whereNotIn()/whereNotBetween() for their dedicated SQL operators. - */ - whereNot(callback: (builder: Knex.QueryBuilder) => void): this; - /** - * Add a negated condition using a mapped property, record or Knex group. - * Use whereNotIn()/whereNotBetween() for their dedicated SQL operators. - */ - whereNot(raw: Knex.Raw): this; - /** - * Add a negated condition using a mapped property, record or Knex group. - * Use whereNotIn()/whereNotBetween() for their dedicated SQL operators. - */ - whereNot(columnOrRaw: any, ...args: any[]): this { - return whereNotImpl(this as any, columnOrRaw, ...args); + /** @internal Native storage columns for composing CTI table sources. */ + storageQuery(): Knex.QueryBuilder { + return this.filtered().select(this.knex.raw('??.*', [this.alias])); } - - /** - * Require the mapped column to match a value list or a single-column subquery. - * An empty list matches no rows. Subqueries use Knex's database column names. - */ - whereIn( - column: ColumnRef, - values: readonly any[] | Knex.QueryBuilder + /** Order parent rows independently of any child relation's ordering. */ + orderBy( + column: ReadPredicateSelector>, + direction: 'asc' | 'desc' = 'asc' ): this { - return (whereInImpl as any)(this, column, values); + const copy = this.copy(); + copy.base.orderBy(this.name(this.column(column)), direction); + return copy; + } + /** Append trusted raw ordering with captured positional bindings; ref() supplies quoted columns. */ + orderByRaw(sql: string, bindings: readonly Knex.RawBinding[] = []): this { + const captured = captureReadRaw(this.knex, sql, bindings); + const copy = this.copy(); + copy.base.orderByRaw(captured()); + return copy; + } + /** Group rows before typed aggregate projection. */ + groupBy( + ...columns: ReadPredicateSelector>[] + ): SchemaQueryBuilder { + const copy = this.copy(); + copy.base.groupBy(columns.map(c => this.name(this.column(c)))); + copy.grouped = true; + return copy as any; + } + /** Limit parent rows; relation limits apply independently within each parent. */ + limit(count: number): this { + if (!Number.isInteger(count) || count < 0) + throw new ReadSchemaError('Limit must be a non-negative integer'); + const copy = this.copy(); + copy.base.limit(count); + return copy; + } + /** Skip parent rows; use a deterministic order for pagination. */ + offset(count: number): this { + if (!Number.isInteger(count) || count < 0) + throw new ReadSchemaError('Offset must be a non-negative integer'); + const copy = this.copy(); + copy.base.offset(count); + return copy; + } + + /** + * Load a declared relation in the same SQL statement. Return the customized child + * query so its selected fields and nested includes remain strongly typed. + */ + include< + K extends keyof Relations & string, + Child extends ReadQueryShape = SchemaAwareQuery> + >( + selector: K | ((relations: { [P in keyof Relations]: P }) => K), + customize?: (query: SchemaAwareQuery>) => Child + ): SchemaQueryBuilder< + S, + AddField>, + Relations, + false + > { + if (Object.values(this.fields).some(f => f.aggregate) || this.grouped) + throw new ReadSchemaError( + 'Grouped/aggregate reads cannot load entity relations' + ); + const definitions = (this.source.introspect().extensions?.relations ?? + []) as RelationSpec[]; + const key = + typeof selector === 'string' + ? selector + : selector( + Object.fromEntries( + definitions.map(r => [r.name, r.name]) + ) as any + ); + const relation = definitions.find(r => r.name === key); + if (!relation) throw new ReadSchemaError(`Unknown relation: ${key}`); + if (this.loaded.some(r => r.name === key)) + throw new ReadSchemaError(`Duplicate relation: ${key}`); + const foreign = + typeof relation.schema === 'function' + ? relation.schema() + : relation.schema; + return this.load( + relation, + this.child(foreign, customize as any), + relation.type === 'belongsTo' && !relation.optional + ) as any; + } + + private child( + foreign: ReadObject, + customize?: (query: any) => ReadQueryShape + ): AnyReadQuery { + let child: AnyReadQuery = createReadQuery( + this.knex, + foreign, + this.knex(getTableName(foreign)) + ); + if (customize) { + const customized = customize(child as any); + if (!child.sameSource(customized)) { + if (customized instanceof Promise) + void customized.catch(() => {}); + throw new ReadSchemaError( + 'Relation customizer must return its configured read query' + ); + } + child = customized as any; + } + return child; } - /** - * Exclude values returned by a list or single-column subquery. - * SQL null semantics apply; a null in the set is not equivalent to a missing value. - */ - whereNotIn( - column: ColumnRef, - values: readonly any[] | Knex.QueryBuilder + private load( + relation: RelationSpec, + child: AnyReadQuery, + required: boolean ): this { - return (whereNotInImpl as any)(this, column, values); - } - - /** - * Add an OR membership condition against a list or single-column subquery. - */ - orWhereIn( - column: ColumnRef, - values: readonly any[] | Knex.QueryBuilder + const key = relation.name; + if (!key || typeof key !== 'string') + throw new ReadSchemaError('A non-empty relation alias is required'); + if (Object.values(this.fields).some(f => f.aggregate)) + throw new ReadSchemaError( + 'Grouped/aggregate reads cannot load entity relations' + ); + if ( + this.loaded.some(r => r.name === key) || + Object.hasOwn(this.fields, key) + ) + throw new ReadSchemaError(`Duplicate result field: ${key}`); + const many = + relation.type === 'hasMany' || relation.type === 'belongsToMany'; + const schema = many + ? array(child.rowSchema) + : required + ? child.rowSchema + : child.rowSchema.nullable(); + const node: ReadNode = { + schema, + exact: false, + decode: (value, path) => { + if (value === null && !required && !many) return null; + if (many) { + if (!Array.isArray(value)) + throw new ReadSchemaError( + `${path}: expected a relation array` + ); + return value.map((row, i) => + child.decode(row, `${path}[${i}]`) + ); + } + return child.decode(value, path); + } + }; + const copy = this.copy(); + copy.loaded.push({ name: key, query: child, relation, required }); + copy.fields[key] = { + node, + expression: () => { + throw new ReadSchemaError( + 'Relation expression must be compiled in context' + ); + } + }; + Object.assign(copy, { rowSchema: copy.schema() }); + return copy as any; + } + + /** @internal Reuse a captured child query across polymorphic parent branches. */ + includeFrom( + name: string, + prepared: SchemaQueryBuilder ): this { - return (orWhereInImpl as any)(this, column, values); + const loaded = prepared.loaded.find(relation => relation.name === name); + if (!loaded) + throw new ReadSchemaError(`Unknown prepared relation: ${name}`); + return this.load(loaded.relation, loaded.query, loaded.required); } - /** - * Add an OR non-membership condition; SQL NOT IN null semantics apply. - */ - orWhereNotIn( - column: ColumnRef, - values: readonly any[] | Knex.QueryBuilder - ): this { - return (orWhereNotInImpl as any)(this, column, values); + /** Join one typed nested object with explicit property keys, including nullable joins. */ + joinOne< + F extends ReadObject, + K extends string, + Required extends boolean = true, + Child extends ReadQueryShape = SchemaAwareQuery + >( + spec: Omit, 'foreignQuery' | 'mappers'>, + customize?: (query: SchemaAwareQuery) => Child + ): SchemaQueryBuilder< + S, + AddField< + Row, + K, + Required extends true + ? Child['rowSchema'] + : SchemaForValue | null> + >, + Relations & Record>, + false + > { + if ('foreignQuery' in spec || 'mappers' in spec) + throw new ReadSchemaError( + 'Use the typed child customizer instead of foreignQuery/mappers in schema-aware mode' + ); + return this.load( + { + name: spec.as, + type: 'hasOne', + schema: spec.foreignSchema, + localKey: resolvePropertyKey( + spec.localColumn as any, + this.source, + 'joinOne' + ), + remoteKey: resolvePropertyKey( + spec.foreignColumn as any, + spec.foreignSchema, + 'joinOne' + ) + }, + this.child(spec.foreignSchema, customize as any), + spec.required !== false + ) as any; + } + + /** Join a typed collection, applying child projection and pagination independently per parent. */ + joinMany< + F extends ReadObject, + K extends string, + Child extends ReadQueryShape = SchemaAwareQuery + >( + spec: Omit< + JoinManySpec, + 'orderBy' | 'foreignQuery' | 'mappers' + >, + customize?: (query: SchemaAwareQuery) => Child + ): SchemaQueryBuilder< + S, + AddField>, + Relations & Record>, + false + > { + if ('foreignQuery' in spec || 'mappers' in spec || 'orderBy' in spec) + throw new ReadSchemaError( + 'Use the typed child customizer for ordering instead of raw foreignQuery/mappers' + ); + let child = this.child(spec.foreignSchema, customize as any); + if (spec.limit !== undefined) child = child.limit(spec.limit); + if (spec.offset !== undefined) child = child.offset(spec.offset); + return this.load( + { + name: spec.as, + type: 'hasMany', + schema: spec.foreignSchema, + localKey: resolvePropertyKey( + spec.localColumn as any, + this.source, + 'joinMany' + ), + remoteKey: resolvePropertyKey( + spec.foreignColumn as any, + spec.foreignSchema, + 'joinMany' + ) + }, + child, + false + ) as any; + } + + /** @internal Decode a SQL/JSON row using the same metadata exposed to consumers. */ + decode(row: any, path = 'row'): any { + if ( + this.source.introspect().extensions?.readOrphanColumn && + row.__read_cti_present == null + ) + throw new ReadSchemaError(`${path}: missing CTI variant body`); + return decodeObject( + Object.fromEntries( + Object.entries(this.fields).map(([key, field]) => [ + key, + field.node + ]) + ), + row, + path + ); } - /** - * Add an AND IS NULL condition for a mapped schema property. - */ - whereNull(column: ColumnRef): this { - return (whereNullImpl as any)(this, column); + /** @internal Compile a bound SQL statement; callers never receive the mutable builder. */ + compile(correlate?: ReadCorrelation): Knex.QueryBuilder { + const query = this.filtered().clearSelect(); + correlate?.(query, this.alias, this.source); + const expressions: Record = Object.create(null); + for (const [key, field] of Object.entries(this.fields)) { + if (!this.loaded.some(r => r.name === key)) + expressions[key] = field.expression(this.knex); + } + const orphanColumn = + this.source.introspect().extensions?.readOrphanColumn; + if (orphanColumn) + expressions.__read_cti_present = this.knex.raw('??', [ + `${this.alias}.${orphanColumn}` + ]); + for (const loaded of this.loaded) { + const { relation, query: child } = loaded; + const parentTable = this.alias; + const parentPk = getPrimaryKeyColumns(this.source).columnNames; + const foreignKey = relation.foreignKey; + const resolveKey = (schema: ReadObject, key: any) => { + if (typeof key === 'function') { + const descriptor = key( + ObjectSchemaBuilderValue.getPropertiesFor(schema) + ); + key = + descriptor[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR] + .propertyName; + } + return buildColumnMap(schema).propToCol.get(key) ?? key; + }; + const childSql = child.compile((sql, childTable, childSource) => { + const childPk = getPrimaryKeyColumns(childSource).columnNames; + if (relation.localKey && relation.remoteKey) { + sql.where( + `${childTable}.${resolveKey(childSource, relation.remoteKey)}`, + this.knex.ref( + `${parentTable}.${resolveKey(this.source, relation.localKey)}` + ) + ); + return; + } + if (parentPk.length !== 1) + throw new ReadSchemaError( + 'Automatic relation reads require single-column primary keys' + ); + if (childPk.length !== 1) + throw new ReadSchemaError( + 'Automatic relation reads require single-column primary keys' + ); + if (relation.type === 'belongsTo') + sql.where( + `${childTable}.${childPk[0]}`, + this.knex.ref( + `${parentTable}.${resolveKey(this.source, foreignKey)}` + ) + ); + else if (relation.type === 'belongsToMany') { + const through = relation.through!; + sql.join( + through.table, + `${through.table}.${through.foreignKey}`, + `${childTable}.${childPk[0]}` + ).where( + `${through.table}.${through.localKey}`, + this.knex.ref(`${parentTable}.${parentPk[0]}`) + ); + } else + sql.where( + `${childTable}.${resolveKey(childSource, foreignKey)}`, + this.knex.ref(`${parentTable}.${parentPk[0]}`) + ); + }); + const many = + relation.type === 'hasMany' || + relation.type === 'belongsToMany'; + if (!many) childSql.limit(1); + const wrapped = this.knex + .queryBuilder() + .from(childSql.clone().as('__read_relation')); + expressions[loaded.name] = many + ? this.knex.raw( + "(select coalesce(jsonb_agg(to_jsonb(__read_relation)), '[]'::jsonb) from (?) as __read_relation)", + [childSql] + ) + : this.knex.raw( + '(select to_jsonb(__read_relation) from (?) as __read_relation)', + [childSql] + ); + if (loaded.required) + query.whereExists(wrapped.clone().select(this.knex.raw('1'))); + } + return (this.distinctRows ? query.distinct() : query).select( + expressions + ); } - /** - * Add an AND IS NOT NULL condition for a mapped schema property. - */ - whereNotNull(column: ColumnRef): this { - return (whereNotNullImpl as any)(this, column); + /** Render debugging SQL without execution; bound values may be sensitive. */ + toQuery(): string { + return this.compile().toQuery(); } - - /** - * Add an OR IS NULL condition for a mapped schema property. - */ - orWhereNull(column: ColumnRef): this { - return (orWhereNullImpl as any)(this, column); + /** Return an independent mutable Knex snapshot, never the query's owned state. */ + toKnexQuery(): Knex.QueryBuilder { + return this.compile(); + } + + /** Configure an isolated Knex SELECT once, declaring the complete raw row output. */ + apply( + configure: (query: Knex.QueryBuilder) => Knex.QueryBuilder | undefined, + options: QueryOutput + ): OpaqueQuery { + const sql = this.compile(); + const result = configure(sql); + if (result !== undefined && result !== sql) { + if (result instanceof Promise) void result.catch(() => {}); + throw new ReadSchemaError( + 'Raw configuration must synchronously configure the supplied Knex builder' + ); + } + return OpaqueQuery.capture(this.knex, sql, options); + } + /** Replace the projection with trusted raw SQL and an explicit output schema. */ + selectRaw( + sql: string, + bindings: readonly Knex.RawBinding[], + options: QueryOutput + ): OpaqueQuery { + const captured = captureReadRaw(this.knex, sql, bindings); + return this.apply( + query => query.clearSelect().select(captured()), + options + ); } - /** - * Add an OR IS NOT NULL condition for a mapped schema property. - */ - orWhereNotNull(column: ColumnRef): this { - return (orWhereNotNullImpl as any)(this, column); + private writer(filtered = true): QuerySource> { + if (!this.returnsEntityRows || this.loaded.length) + throw new ReadSchemaError( + 'Writes require an unprojected table query without relations or aggregation' + ); + let base = this.knex(getTableName(this.source)); + if (filtered) { + const keys = getPrimaryKeyColumns(this.source).columnNames; + if (keys.length) { + base.whereIn( + keys, + this.filtered() + .clearSelect() + .select(keys.map(key => `${this.alias}.${key}`)) + ); + } else { + const captured = this.filtered(); + if ( + (captured as any)._single.limit !== undefined || + (captured as any)._single.offset !== undefined + ) + throw new ReadSchemaError( + 'Paginated writes require a primary key' + ); + base = captured + .from({ [this.alias]: getTableName(this.source) }) + .clearSelect() + .clearOrder(); + } + } + const writer = new QuerySource>( + this.knex, + this.source, + base + ); + const state = getState(writer); + state.skipDefaultScope = true; + state.includeDeleted = true; + state.decodeRow = row => this.decode(row); + state.hookQuery = this; + return writer; } - /** - * Require the mapped column to lie within an inclusive [lower, upper] range. - */ - whereBetween( - column: ColumnRef, - range: readonly [any, any] - ): this { - return (whereBetweenImpl as any)(this, column, range); + /** Configure immutable conflict handling for one-row inserts. */ + onConflict( + this: Writable extends true ? this : never, + ...columns: ColumnRef[] + ): Pick< + ReturnType>['onConflict']>, + 'merge' | 'ignore' + > { + const conflict = this.writer(false).onConflict(...columns); + return { + merge: (async (...args: any[]) => { + const row = await (conflict.merge as Function)(...args); + return row; + }) as typeof conflict.merge, + ignore: async data => { + const row = await conflict.ignore(data); + return row; + } + }; + } + + /** Insert one row, returning the same decoded storage representation as reads. */ + async insert( + this: Writable extends true ? this : never, + data: InsertType + ): Promise> { + return this.writer(false).insert(data); + } + /** Insert multiple rows and decode each returned row. */ + async insertMany( + this: Writable extends true ? this : never, + data: InsertType[] + ): Promise[]> { + const writer = this.writer(false); + if (data.length === 0) return []; + return writer.insertMany(data); + } + /** Update matching entities; projections and relations cannot be written. */ + async update( + this: Writable extends true ? this : never, + data: Partial | InferType> + ): Promise[]> { + return this.writer().update(data as any); + } + /** Delete matching rows, respecting configured soft deletion. */ + delete(this: Writable extends true ? this : never): Promise { + return this.writer().delete(); + } + /** Permanently delete matching rows. */ + hardDelete(this: Writable extends true ? this : never): Promise { + return this.writer().hardDelete(); + } + /** Restore matching soft-deleted rows. */ + async restore( + this: Writable extends true ? this : never + ): Promise[]> { + return this.writer().restore(); + } + /** Insert in parameter-safe chunks, retaining schema hooks and conflict handling. */ + async bulkInsert( + this: Writable extends true ? this : never, + ...args: Parameters>['bulkInsert']> + ): Promise[]> { + return this.writer(false).bulkInsert(...args); } - - /** - * Exclude the inclusive [lower, upper] range from a mapped column. - */ - whereNotBetween( - column: ColumnRef, - range: readonly [any, any] - ): this { - return (whereNotBetweenImpl as any)(this, column, range); + /** Upsert a row using mapped conflict keys. */ + async upsert( + this: Writable extends true ? this : never, + ...args: Parameters>['upsert']> + ): Promise> { + return this.writer(false).upsert(...args); } - - /** - * Match a mapped column against a SQL LIKE pattern. - * Percent and underscore remain wildcards; values are bound, not wildcard-escaped. + /** Upsert rows in parameter-safe chunks. */ + async bulkUpsert( + this: Writable extends true ? this : never, + ...args: Parameters>['bulkUpsert']> + ): Promise[]> { + return this.writer(false).bulkUpsert(...args); + } + /** Update rows by their declared keys in parameter-safe chunks. */ + bulkUpdate( + this: Writable extends true ? this : never, + updates: ReadonlyArray<{ + where: Partial>; + set: Partial | InferType>; + }> + ): Promise { + return this.writer().bulkUpdate(updates as any); + } + + /** Read values of one selected property without altering this query. */ + async pluck & string>( + key: K + ): Promise[K]>[]> { + return (await this.execute()).map(row => (row as any)[key]); + } + /** Execute one statement and decode its selected row graph. */ + async execute(): Promise[]> { + return (await this.compile()).map((row: unknown) => this.decode(row)); + } + /** Execute a limited copy, returning undefined when no row matches. */ + async first(): Promise | undefined> { + return (await this.limit(1).execute())[0]; + } + /** Awaiting executes the query; repeated awaits deliberately execute again. */ + // biome-ignore lint/suspicious/noThenProperty: query readers intentionally support await + then[], E = never>( + resolve?: ((rows: InferType[]) => T | PromiseLike) | null, + reject?: ((error: any) => E | PromiseLike) | null + ): Promise { + return this.execute().then(resolve, reject); + } + /** Bind an independent query graph to a caller-owned transaction. */ + transacting(trx: Knex.Transaction): this { + const copy = this.copy(); + copy.base.transacting(trx); + Object.assign(copy, { knex: trx }); + copy.loaded = this.loaded.map(r => ({ + ...r, + query: r.query.transacting(trx) + })); + return copy; + } + /** + * Read a lossless composite cursor page using native SQL ordering. Cursor sort + * values remain private text columns, independent of projections and Date decoding. + * Requires non-null scalar order columns containing a declared unique key. */ - whereLike(column: ColumnRef, value: string): this { - return (whereLikeImpl as any)(this, column, value); + paginateAfter( + options: CompositeCursorOptions + ): Promise>>; + /** Read a page using a single raw cursor value; choose a unique, non-null sort column. */ + paginateAfter(options: { + cursor?: unknown; + limit: number; + column?: ColumnRef; + direction?: 'asc' | 'desc'; + }): Promise>>; + /** Execute a cursor page without changing this query's filters, ordering or schema. */ + async paginateAfter( + options: + | CompositeCursorOptions + | { + cursor?: unknown; + limit: number; + column?: ColumnRef; + direction?: 'asc' | 'desc'; + } + ): Promise>> { + if (this.grouped || Object.values(this.fields).some(f => f.aggregate)) + throw new ReadSchemaError( + 'Cursor pagination cannot be used for grouped or aggregate reads' + ); + if (!('orderBy' in options)) { + if (!Number.isInteger(options.limit) || options.limit < 1) + throw new ReadSchemaError( + 'Cursor page limit must be a positive integer' + ); + const key = + options.column === undefined + ? 'id' + : resolvePropertyKey( + options.column as any, + this.source, + 'cursor' + ); + const column = this.columns[key]; + if (!column) + throw new ReadSchemaError(`Unknown cursor column: ${key}`); + const direction = options.direction ?? 'desc'; + if (direction !== 'asc' && direction !== 'desc') + throw new ReadSchemaError('Invalid cursor direction'); + const name = this.name(column); + let hidden = '__cursor_value'; + while (Object.hasOwn(this.fields, hidden)) hidden += '_'; + const sql = this.compile() + .clearOrder() + .clear('offset') + .limit(options.limit + 1) + .orderBy(name, direction) + .select({ + [hidden]: readExpression( + this.knex, + column[READ_COLUMN], + name + ) + }); + if (options.cursor != null) + sql.where( + name as any, + direction === 'desc' ? '<' : '>', + options.cursor as any + ); + const rows = await sql; + const hasMore = rows.length > options.limit; + const page = rows.slice(0, options.limit); + return { + data: page.map((row: any) => this.decode(row)), + hasMore, + nextCursor: hasMore + ? String(page[page.length - 1][hidden]) + : null + }; + } + const Constructor = getQuerySourceCtor(); + const source = this.source.withExtension('tableName', this.alias); + const legacy = new Constructor(this.knex, source, this.compile()); + const state = getState(legacy); + state.skipDefaultScope = true; + state.includeDeleted = true; + // The query is already projected; reserve its aliases against cursor fields. + state.hiddenColumns = new Set(Object.keys(this.fields)); + return compositeCursor( + legacy, + options, + row => this.decode(row), + this.source.introspect().extensions?.tableName as string + ); } - - /** - * Match a mapped column using PostgreSQL's case-insensitive ILIKE operator. - * Percent and underscore remain pattern wildcards. - */ - whereILike(column: ColumnRef, value: string): this { - return (whereILikeImpl as any)(this, column, value); - } - - /** - * Append raw WHERE SQL with Knex bindings. Never interpolate untrusted values. - * Raw SQL uses database names and is outside schema-level result/type checking. - */ - whereRaw(sql: string, ...bindings: any[]): this { - return (whereRawImpl as any)(this, sql, ...bindings); - } - - /** - * Add an EXISTS filter from a Knex subquery or query-building callback. - * Use qualified database columns to correlate it with the parent query. - */ - whereExists(callback: Knex.QueryCallback | Knex.QueryBuilder): this { - return (whereExistsImpl as any)(this, callback); - } - - /** - * Add a NOT EXISTS filter from a Knex subquery or query-building callback. - */ - whereNotExists(callback: Knex.QueryCallback | Knex.QueryBuilder): this { - return (whereNotExistsImpl as any)(this, callback); - } - - /** - * Compare a JSON-path value inside a mapped JSON column. - * @param path - JSON path understood by the Knex database dialect. - * @param operator - SQL comparison operator forwarded to Knex. - * @param value - Bound comparison value. - */ - whereJsonPath( - column: ColumnRef, - path: string, - operator?: string, - value?: any - ): this { - return (whereJsonPathImpl as any)(this, column, path, operator, value); - } - - // ======================================================================= - // ORDER BY - // ======================================================================= - - /** - * Append ordering by a mapped property or raw expression (ascending by default). - * Add a unique tie-breaker for stable pages. Eager loading retains parent order. - */ - orderBy( - column: ColumnRef | Knex.Raw, - direction?: 'asc' | 'desc' - ): this { - return (orderByImpl as any)(this, column, direction); - } - - /** - * Append a raw ORDER BY expression with Knex bindings. - * Use database column names; parent ordering is retained during eager loading. - */ - orderByRaw(sql: string, ...bindings: any[]): this { - return (orderByRawImpl as any)(this, sql, ...bindings); - } - - // ======================================================================= - // GROUP BY / HAVING - // ======================================================================= - - /** - * Group rows by mapped schema properties or raw expressions. - * Combine with aggregate expressions in select() to infer grouped DTO results. - */ - groupBy(...columns: (ColumnRef | Knex.Raw)[]): this { - return (groupByImpl as any)(this, ...columns); - } - - /** - * Append raw GROUP BY SQL with optional Knex bindings. - */ - groupByRaw(sql: string, ...bindings: any[]): this { - return (groupByRawImpl as any)(this, sql, ...bindings); - } - - /** - * Filter SQL groups by a mapped column/raw expression, operator and bound value. - */ - having( - column: ColumnRef | Knex.Raw, - operator: string, - value: any - ): this { - return (havingImpl as any)(this, column, operator, value); - } - - /** - * Append raw HAVING SQL with Knex bindings, for example aggregate comparisons. - */ - havingRaw(sql: string, ...bindings: any[]): this { - return havingRawImpl(this as any, sql, ...bindings); - } - - // ======================================================================= - // PAGINATION - // ======================================================================= - - /** - * Set the maximum number of parent rows to select; mutates this query. - * Included collections do not consume the parent limit. - */ - limit(n: number): this { - return limitImpl(this as any, n); - } - - /** - * Skip this many parent rows before applying the limit; mutates this query. - * Use deterministic ordering when navigating offset-based pages. - */ - offset(n: number): this { - return offsetImpl(this as any, n); - } - - /** - * Execute a one-based offset page and a separate matching-source count query. - * Mutates this builder's limit/offset and returns mapped rows plus page metadata. - * The count and page are separate reads, not a snapshot unless your transaction provides one. - */ - async paginate(opts: { - /** One-based page number. */ + /** Fetch a numbered page and a count without mutating the source query. */ + async paginate(options: { page: number; - /** Maximum parent rows in a page. */ pageSize: number; - }): Promise> { - return paginateImpl(this as any, opts) as Promise< - PaginationResult - >; - } - - /** - * Read a cursor page without running a total-count query. - * The orderBy form clones the source and preserves exact composite sort values; - * its non-null sort must contain a declared unique key. The legacy column form - * mutates this builder and defaults to id descending. Reapply access filters on - * every request: cursors are positions, not authorization or snapshots. - * @returns Mapped data, hasMore, and a nextCursor that is null on the last page. - * @throws For invalid composite cursors or unsupported composite query shapes. - */ - paginateAfter( - opts: CompositeCursorOptions - ): Promise>; - /** - * Read a cursor page without running a total-count query. - * The orderBy form clones the source and preserves exact composite sort values; - * its non-null sort must contain a declared unique key. The legacy column form - * mutates this builder and defaults to id descending. Reapply access filters on - * every request: cursors are positions, not authorization or snapshots. - * @returns Mapped data, hasMore, and a nextCursor that is null on the last page. - * @throws For invalid composite cursors or unsupported composite query shapes. - */ - paginateAfter(opts: { - /** Previous raw single-column position; omit for the first page. */ - cursor?: any; - /** Maximum parent rows to return; one extra row determines hasMore. */ - limit: number; - /** Unique sort property; defaults to id in the legacy API. */ - column?: ColumnRef; - /** Sort/continuation direction; defaults to descending. */ - direction?: 'asc' | 'desc'; - }): Promise>; - /** - * Read a cursor page without running a total-count query. - * The orderBy form clones the source and preserves exact composite sort values; - * its non-null sort must contain a declared unique key. The legacy column form - * mutates this builder and defaults to id descending. Reapply access filters on - * every request: cursors are positions, not authorization or snapshots. - * @returns Mapped data, hasMore, and a nextCursor that is null on the last page. - * @throws For invalid composite cursors or unsupported composite query shapes. - */ - async paginateAfter(opts: any): Promise> { - if ('orderBy' in opts) return compositeCursor(this as any, opts); - return (paginateAfterImpl as any)(this, opts) as Promise< - CursorPaginationResult - >; - } - - // ======================================================================= - // WRITE OPERATIONS - // ======================================================================= - - /** - * Insert one schema-shaped row and return its mapped database representation. - * Applies configured insert hooks, column mappings and timestamp defaults. - */ - async insert(data: InsertType): Promise { - return insertImpl(this as any, data) as Promise; - } - - /** - * Insert an array of schema-shaped rows and return their mapped representations. - * Returns an empty array for empty input; use bulkInsert() to control chunking. - */ - async insertMany(data: InsertType[]): Promise { - return insertManyImpl(this as any, data) as Promise; - } - - /** - * Configure an upsert conflict target using mapped properties. - * Call merge() or ignore() on the returned builder to insert the row. - */ - onConflict( - ...conflictColumns: ColumnRef[] - ): import('./operations/insert.js').OnConflictBuilder< - TLocalSchema, - TResult - > { - return (onConflictImpl as any)(this, ...conflictColumns); - } - - /** - * Insert one row or update it when the chosen conflict target already exists. - * @param opts - Conflict properties and optional subset of properties to update. - * @returns The inserted or updated row mapped to schema property names. - */ - async upsert( - data: InsertType, - opts: { - /** Properties identifying an existing row on conflict. */ - conflictColumns: ColumnRef[]; - /** Properties to update on conflict; omit to merge insert values. */ - updateColumns?: ColumnRef[]; - } - ): Promise { - return (upsertImpl as any)(this, data, opts); - } - - /** - * Insert rows in chunks, optionally ignoring or merging conflicts. - * @param opts - Chunk size (default 500), conflict policy and conflict properties. - * @returns Mapped rows returned by PostgreSQL; ignored conflicts produce no row. - */ - async bulkInsert( - rows: InsertType[], - opts?: { - /** Requested rows per statement, capped by parameter limits; default 500. */ - chunkSize?: number; - /** Optional PostgreSQL conflict policy applied to each chunk. */ - onConflict?: 'ignore' | 'merge'; - /** Conflict target properties when a conflict policy is supplied. */ - conflictColumns?: ColumnRef[]; - } - ): Promise { - return (bulkInsertImpl as any)(this, rows, opts); - } - - /** - * Insert/update rows in chunks using the specified conflict properties. - * @param opts - Required conflict target and optional chunk size (default 500). - * @returns The database rows produced by each chunk, mapped to schema properties. - */ - async bulkUpsert( - rows: InsertType[], - opts: { - /** Properties identifying an existing row on conflict. */ - conflictColumns: ColumnRef[]; - /** Requested rows per statement, capped by parameter limits; default 500. */ - chunkSize?: number; - } - ): Promise { - return (bulkUpsertImpl as any)(this, rows, opts); - } - - // ======================================================================= - // UPDATE - // ======================================================================= - - /** - * Update rows matching this query's explicit filters and return mapped rows. - * Applies update hooks and timestamp metadata. Add a WHERE clause to avoid a - * table-wide update; this method does not track entity identity. - */ - async update(data: Partial>): Promise { - return updateImpl(this as any, data) as Promise; - } - - /** - * Apply per-row where/set pairs and return the total affected-row count. - * Maps both filter and update property names and applies configured update hooks. - */ - async bulkUpdate( - updates: ReadonlyArray<{ - /** Equality filters identifying the rows for this update. */ - where: Partial>; - /** Schema properties to assign to those rows. */ - set: Partial>; - }> - ): Promise { - return bulkUpdateImpl(this as any, updates as any); - } - - // ======================================================================= - // DELETE / SOFT DELETE - // ======================================================================= - - /** - * Delete rows matching explicit filters and return the affected-row count. - * Runs beforeDelete hooks; with soft-delete metadata it sets the deletion timestamp - * instead of removing rows. Add filters to avoid a table-wide write. - */ - async delete(): Promise { - return deleteImpl(this as any); - } - - /** - * Include soft-deleted rows in read results without removing explicit filters. - */ - withDeleted(): this { - return withDeletedImpl(this as any); - } - - /** - * Restrict reads to rows whose configured soft-delete column is non-null. - */ - onlyDeleted(): this { - return onlyDeletedImpl(this as any); - } - - /** - * Permanently delete rows matching explicit filters, even on a soft-delete schema. - * Runs beforeDelete hooks and returns the affected count. This cannot be undone - * without a transaction rollback or backup. - */ - async hardDelete(): Promise { - return hardDeleteImpl(this as any); - } - - /** - * Clear the deletion timestamp on rows matching explicit filters and return them. - * @throws If the schema has no soft-delete configuration. - */ - async restore(): Promise { - return restoreImpl(this as any) as Promise; - } - - // ======================================================================= - // EAGER LOADING (JOIN) - // ======================================================================= - - /** - * Eager-load a related object under spec.as using mapped join columns. - * Required joins remove unmatched parents; optional joins return null. Unlike a - * flat join, related fields remain nested and are mapped with the foreign schema. - */ - joinOne< - TForeignSchema extends ObjectSchemaBuilder< - any, - any, - any, - any, - any, - any, - any - >, - TFieldName extends string, - TRequired extends boolean = true - >( - spec: JoinOneSpec - ): SchemaQueryBuilder< - TLocalSchema, - import('./types.js').WithJoinedOne< - TResult, - TFieldName, - TForeignSchema, - TRequired - > - > { - return joinOneImpl(this as any, spec); - } - - /** - * Eager-load a nested array without multiplying parent rows. - * The relation's own order/limit/offset controls children separately from parent paging. - */ - joinMany< - TForeignSchema extends ObjectSchemaBuilder< - any, - any, - any, - any, - any, - any, - any - >, - TFieldName extends string - >( - spec: JoinManySpec - ): SchemaQueryBuilder< - TLocalSchema, - import('./types.js').WithJoinedMany - > { - return joinManyImpl(this as any, spec); - } - - /** - * Eager-load a relation registered on the schema by name. - * @param customize - Optional callback configuring the related query. - * Use an ORM DbSet when you need typed relation names and customization fields. - */ - include( - relationName: string, - customize?: (q: SchemaQueryBuilder) => void - ): this { - return includeImpl(this as any, relationName, customize); - } - - /** - * Eager-load a relation declared for one polymorphic discriminator value. - * Other variants are not populated with this relation. Use ORM entity declarations - * to retain known relation customization types. - */ - includeVariant( - variantKey: string, - relationName: string, - customize?: (q: SchemaQueryBuilder) => void - ): this { - return includeVariantImpl( - this as any, - variantKey, - relationName, - customize - ); - } - - // ======================================================================= - // POLYMORPHIC VARIANTS - // ======================================================================= - - /** - * Add a filter applying only to the named polymorphic branch. - * Other discriminator values remain eligible. Maps the variant property to its - * CTI/STI storage column; throws for unknown variants or unsupported operators. - */ - whereVariant( - key: string, - column: string, - operator: string, - value: any - ): this { - const state = getState(this); - const variantConfig = getVariantConfig(this); - if (!variantConfig) { - throw new Error( - 'whereVariant() can only be used on a polymorphic schema (created with .withVariants())' + }): Promise>> { + const { page, pageSize } = options; + if ( + !Number.isInteger(page) || + page < 1 || + !Number.isInteger(pageSize) || + pageSize < 1 + ) + throw new ReadSchemaError( + 'Page and pageSize must be positive integers' ); - } - - const spec = variantConfig.variants[key]; - if (!spec) { - throw new Error( - `whereVariant: unknown variant key "${key}". ` + - `Valid keys: ${Object.keys(variantConfig.variants).join(', ')}` - ); - } - - const op = operator.toLowerCase(); - if (!ALLOWED_OPS.has(op)) { - throw new Error( - `whereVariant: operator "${operator}" is not allowed. ` + - `Allowed operators: ${[...ALLOWED_OPS].join(', ')}` - ); - } - - const { propToCol } = buildColumnMap(spec.schema); - const colName = propToCol.get(column) ?? column; - - let qualifiedColumn: string; - if (spec.storage === 'cti') { - qualifiedColumn = `__v_${key}.${colName}`; - } else { - qualifiedColumn = `${state.tableName}.${colName}`; - } - - state.variantWhereFilters.push({ key, qualifiedColumn, op, value }); - invalidateCache(this); - return this; - } - - /** - * Choose which polymorphic variant bodies are loaded. - * This controls variant joins/selection, not a discriminator filter on base rows. - * @throws If the schema is not polymorphic. - */ - selectVariants(keys: string[]): this { - const state = getState(this); - if (!getVariantConfig(this)) { - throw new Error( - 'selectVariants() can only be used on a polymorphic schema (created with .withVariants())' + const countQuery = this.compile() + .clearOrder() + .clear('limit') + .clear('offset'); + const countRow = await this.knex + .from(countQuery.as('__read_count')) + .count({ count: '*' }) + .first(); + const total = Number(countRow?.count ?? 0); + if (!Number.isSafeInteger(total)) + throw new ReadSchemaError( + 'Pagination count exceeds the safe integer range' ); - } - state.enabledVariants = new Set(keys); - invalidateCache(this); - return this; - } - - // ======================================================================= - // ESCAPE HATCH - // ======================================================================= - - /** - * Configure the underlying mutable Knex query as an escape hatch. - * Raw changes do not infer a new result type; the caller owns column names, - * result shape and cardinality introduced by the callback. - */ - apply(fn: (builder: Knex.QueryBuilder) => void): this { - const state = getState(this); - state.opaqueReadShape = true; - invalidateCache(this); - fn(state.baseQuery); - return this; - } - - // ======================================================================= - // TRANSACTION - // ======================================================================= - - /** - * Clone this query and its eager-relation queries onto an existing transaction. - * The source builder is unchanged; transaction commit/rollback stays with the caller. - */ - transacting( - trx: Knex.Transaction - ): SchemaQueryBuilder { - const state = getState(this); - const builder = new SchemaQueryBuilder( - trx as unknown as Knex, - state.localSchema as TLocalSchema, - state.baseQuery.clone().transacting(trx) - ); - const builderState = getState(builder); - for (const spec of state.specs) { - builderState.specs.push({ - ...spec, - foreignQuery: spec.foreignQuery.clone().transacting(trx) - }); - } - builderState.explicitSelects = state.explicitSelects - ? [...state.explicitSelects] - : null; - builderState.selectionMode = state.selectionMode; - builderState.appliedProjection = state.appliedProjection; - builderState.projectionColumns = state.projectionColumns - ? { ...state.projectionColumns } - : null; - builderState.projectionDecoders = { ...state.projectionDecoders }; - builderState.hiddenColumns = new Set(state.hiddenColumns); - builderState.includeDeleted = state.includeDeleted; - builderState.onlyDeleted = state.onlyDeleted; - builderState.skipDefaultScope = state.skipDefaultScope; - builderState.opaqueReadShape = state.opaqueReadShape; - builderState.variantConfig = state.variantConfig; - builderState.enabledVariants = - state.enabledVariants !== null - ? new Set(state.enabledVariants) - : null; - builderState.variantWhereFilters = [...state.variantWhereFilters]; - builderState.variantRelationIncludes = [ - ...state.variantRelationIncludes - ]; - return builder; - } - - // ======================================================================= - // EXECUTION - // ======================================================================= - - /** - * Render SQL for debugging without executing it. - * Bindings may appear as literal values; avoid logging sensitive inputs. - */ - toQuery(): string { - return getQuery(this).toQuery(); - } - - /** @internal ORM tracking must never attach aggregate or DTO rows as entities. */ - get returnsEntityRows(): boolean { - return getState(this).selectionMode === null; - } - - /** - * Expose the built Knex query without executing Framework row mapping. - * Direct execution returns raw database rows, potentially including internal fields. - * Treat mutations as an escape hatch rather than typed Framework configuration. - */ - toKnexQuery(): Knex.QueryBuilder { - return getQuery(this); - } - - /** - * Render the query as a debugging SQL string; equivalent to toQuery(). - */ - toString(): string { - return getQuery(this).toString(); - } - - /** - * Execute SQL and map all rows, including eager relations and aggregate decoders. - * @returns An empty array when no rows match. - * @throws Database errors and output-parser validation failures. - */ - async execute(): Promise { - return executeImpl(this) as Promise; - } - - /** - * Execute this query for its first mapped row, or undefined if none matches. - * Set ordering when the choice of first row matters. - */ - async first(): Promise { - const query = getQuery(this).first(); - const row = await query; - - if (!row) return undefined; - return cleanAndMapRow(this, row) as TResult; - } - - /** - * Execute a selection and collect one mapped column's values into an array. - * @param column - Schema property whose database column should be read. - */ - async pluck( - column: ColumnRef - ): Promise { - const _state = getState(this); - const col = resolveColumn(this, column, 'pluck') as string; - const rows = await buildQuery(this).select(col); - return rows.map( - (row: any) => row[col] ?? row[column as string] - ) as TResult[K][]; - } - - /** - * Promise-compatible execution hook enabling await query(...). - * Runs execute() and forwards fulfillment/rejection; repeated awaits may execute again. - */ - // biome-ignore lint/suspicious/noThenProperty: intentional thenable - then( - onfulfilled?: - | ((value: TResult[]) => TReturn1 | PromiseLike) - | null, - onrejected?: ((reason: any) => TReturn2 | PromiseLike) | null - ): Promise { - return this.execute().then(onfulfilled, onrejected); + const data = await this.offset((page - 1) * pageSize) + .limit(pageSize) + .execute(); + const totalPages = Math.ceil(total / pageSize); + return { + data, + total, + page, + pageSize, + totalPages, + hasNextPage: page < totalPages, + hasPreviousPage: page > 1 + }; } } -// Register the constructor for circular-dependency-safe access -registerSchemaQueryBuilder(SchemaQueryBuilder); - -// --------------------------------------------------------------------------- -// query() — main entry point -// --------------------------------------------------------------------------- - -/** - * Create a schema-aware query while preserving its schema and inferred row type. - * Pass a table alias for a flat multi-table query requiring an explicit projection; - * pass an ordinary schema for nested eager loading and schema-aware writes. - * @param knex - Knex connection or transaction. - * @param schema - Table schema or immutable alias(schema, name). - * @returns The appropriately typed, unexecuted query builder. - */ -export function query< - S extends ObjectSchemaBuilder, - N extends string ->(knex: Knex, schema: TableAlias): AliasedQueryBuilder>; -/** - * Create a schema-aware query while preserving its schema and inferred row type. - * Pass a table alias for a flat multi-table query requiring an explicit projection; - * pass an ordinary schema for nested eager loading and schema-aware writes. - * @param knex - Knex connection or transaction. - * @param schema - Table schema or immutable alias(schema, name). - * @returns The appropriately typed, unexecuted query builder. - */ -export function query< - TLocalSchema extends ObjectSchemaBuilder ->( - knex: Knex, - schema: TLocalSchema -): SchemaQueryBuilder>; - -/** - * Create a schema-aware query while preserving its schema and inferred row type. - * Pass a table alias for a flat multi-table query requiring an explicit projection; - * pass an ordinary schema for nested eager loading and schema-aware writes. - * @param knex - Knex connection or transaction. - * @param schema - Table schema or immutable alias(schema, name). - * @returns The appropriately typed, unexecuted query builder. - */ -export function query< - TLocalSchema extends ObjectSchemaBuilder ->( - knex: Knex, - schema: TLocalSchema, - baseQuery: Knex.QueryBuilder -): SchemaQueryBuilder>; +import { ObjectSchemaBuilder as ObjectSchemaBuilderValue } from '@cleverbrush/schema'; -/** - * Create a schema-aware query while preserving its schema and inferred row type. - * Pass a table alias for a flat multi-table query requiring an explicit projection; - * pass an ordinary schema for nested eager loading and schema-aware writes. - * @param knex - Knex connection or transaction. - * @param schema - Table schema or immutable alias(schema, name). - * @returns The appropriately typed, unexecuted query builder. - */ -export function query< - S extends ObjectSchemaBuilder, - N extends string ->( +/** @internal Shared reader factory for roots and nested relations. */ +export function createReadQuery( knex: Knex, - schema: S | TableAlias, - baseQuery?: Knex.QueryBuilder -): - | SchemaQueryBuilder> - | AliasedQueryBuilder> { - if (isTableAlias(schema)) - return new AliasedQueryBuilder>(knex, schema); - return new SchemaQueryBuilder>( - knex, - schema, - baseQuery - ); -} - -// --------------------------------------------------------------------------- -// createQuery() — knex-bound factory -// --------------------------------------------------------------------------- - -/** - * A query factory bound to a connection or transaction. - * Ordinary schemas retain schema/result inference; aliases retain the table context. - * Use withTransaction() to reuse a transaction or transaction() to create one. - */ -export interface BoundQuery { - /** - * Start a typed query on the bound connection using a schema or table alias. - * Only ordinary schema calls accept an existing Knex base query. - */ - < - S extends ObjectSchemaBuilder, - N extends string - >( - schema: TableAlias - ): AliasedQueryBuilder>; - /** - * Start a typed query on the bound connection using a schema or table alias. - * Only ordinary schema calls accept an existing Knex base query. - */ - < - TLocalSchema extends ObjectSchemaBuilder< - any, - any, - any, - any, - any, - any, - any - > - >( - schema: TLocalSchema - ): SchemaQueryBuilder>; - /** - * Start a typed query on the bound connection using a schema or table alias. - * Only ordinary schema calls accept an existing Knex base query. - */ - < - TLocalSchema extends ObjectSchemaBuilder< - any, - any, - any, - any, - any, - any, - any - > - >( - schema: TLocalSchema, - baseQuery: Knex.QueryBuilder - ): SchemaQueryBuilder>; - /** - * Create a factory bound to an existing transaction without committing it. - */ - withTransaction(trx: Knex.Transaction): BoundQuery; - /** - * Run a callback with a transaction-bound factory. - * Resolves to the callback result on commit; rejects and rolls back on failure. - */ - transaction(callback: (db: BoundQuery) => Promise): Promise; -} - -/** - * Bind query() to a connection, retaining all schema/alias overloads. - * @param knexInstance - Connection or existing transaction to bind. - * @returns A callable factory with transaction helpers. - * @example - * const db = createQuery(knex); - * const rows = await db(TaskSchema).select(t => ({ id: t.id })); - */ -export function createQuery(knexInstance: Knex): BoundQuery { - function boundQuery< - S extends ObjectSchemaBuilder, - N extends string - >(schema: TableAlias): AliasedQueryBuilder>; - function boundQuery< - TLocalSchema extends ObjectSchemaBuilder< - any, - any, - any, - any, - any, - any, - any - > - >( - schema: TLocalSchema, - baseQuery?: Knex.QueryBuilder - ): SchemaQueryBuilder>; - function boundQuery< - S extends ObjectSchemaBuilder, - N extends string - >( - schema: S | TableAlias, - baseQuery?: Knex.QueryBuilder - ): - | SchemaQueryBuilder> - | AliasedQueryBuilder> { - if (isTableAlias(schema)) return query(knexInstance, schema); - return baseQuery - ? query(knexInstance, schema, baseQuery) - : query(knexInstance, schema); - } - - return Object.assign(boundQuery, { - withTransaction(trx: Knex.Transaction): BoundQuery { - return createQuery(trx); - }, - transaction(callback: (db: BoundQuery) => Promise): Promise { - return knexInstance.transaction(trx => callback(createQuery(trx))); - } - }) satisfies BoundQuery; + schema: S, + base: Knex.QueryBuilder +): SchemaAwareQuery { + return ( + getVariants(schema) + ? new PolymorphicQueryBuilder(knex, schema, base) + : new SchemaQueryBuilder(knex, schema, base) + ) as SchemaAwareQuery; } diff --git a/libs/knex-schema/src/SchemaReadQuery.ts b/libs/knex-schema/src/SchemaReadQuery.ts deleted file mode 100644 index ddaa13b6..00000000 --- a/libs/knex-schema/src/SchemaReadQuery.ts +++ /dev/null @@ -1,864 +0,0 @@ -import { - type ArraySchemaBuilder, - array, - EXTRA_TYPE_BRAND, - type InferType, - type ObjectSchemaBuilder, - object, - SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR -} from '@cleverbrush/schema'; -import type { Knex } from 'knex'; -import { - buildColumnMap, - getPrimaryKeyColumns, - resolvePropertyKey -} from './columns.js'; -import type { RelationInfo, SchemaProps } from './entity.js'; -import { - type AggregateExpression, - type AliasedColumn, - COLUMN, - isAggregate -} from './expressions.js'; -import { getProjections, getVariants } from './extension.js'; -import { - type CompositeCursorOptions, - compositeCursor -} from './operations/composite-cursor.js'; -import { - getEffectiveBaseQuery, - getSchemaQueryBuilderCtor -} from './operations/helpers.js'; -import { getState } from './operations/state.js'; -import { PolymorphicReadQuery } from './PolymorphicReadQuery.js'; -import type { ReadRelations, ReadVariantMetadata } from './read-entity.js'; -import { - captureReadRaw, - type ReadPredicate, - type ReadPredicateContext, - type ReadPredicateSelector, - ReadPredicates -} from './read-predicates.js'; -import { compileReadProjection, type ReadField } from './read-projection.js'; -import { - type ColumnReadSchema, - compileReadSchema, - decodeObject, - type ObjectReadSchema, - type ReadNode, - type ReadObject, - type ReadSchema, - ReadSchemaError, - readExpression, - type SchemaForValue -} from './read-schema.js'; -import type { SchemaQueryBuilder } from './SchemaQueryBuilder.js'; -import type { - CursorPaginationResult, - JoinManySpec, - JoinOneSpec, - PaginationResult, - RelationSpec -} from './types.js'; - -const READ_COLUMN = Symbol('schema-read-column'); -/** @internal Nominal read-query identity used without recursively comparing fluent APIs. */ -export const READ_QUERY = Symbol('schema-read-query'); -/** Minimal type surface for a customizer's strongly inferred result schema. */ -export interface ReadQueryShape { - /** @internal Nominal marker; constructing a plain schema object is not a query. */ - readonly [READ_QUERY]: true; - /** Decoded result schema inferred from the returned query. */ - readonly rowSchema: R; -} -let readAliasSequence = 0; -/** A typed SQL column; selectors receive descriptions, not row values. */ -export interface ReadColumn - extends AliasedColumn> { - /** @internal Decoding and projection metadata shared by the query compiler. */ - readonly [READ_COLUMN]: ReadNode; -} -/** Columns available for typed projections and filters. */ -export type ReadColumns< - S extends ReadObject, - Relations extends PropertyKey = never -> = { - [K in Exclude, Relations> & string]: ReadColumn< - ColumnReadSchema[K]> - >; -}; -type Selection = Record | AggregateExpression>; -type MergeProps = { - [K in keyof A | keyof B]: K extends keyof B - ? B[K] - : K extends keyof A - ? A[K] - : never; -}; -type NamedProjections = S extends { readonly [EXTRA_TYPE_BRAND]?: infer P } - ? P - : {}; -type NamedKeys< - S, - K extends keyof NamedProjections -> = NamedProjections[K] extends readonly (infer Key extends string)[] - ? Key - : never; -/** Structural schema inferred from a typed projection. */ -export type ReadProjection = ObjectSchemaBuilder<{ - [K in keyof S & string]: S[K] extends ReadColumn - ? R - : S[K] extends AggregateExpression - ? SchemaForValue - : never; -}>; -type AddField< - S extends ReadObject, - K extends string, - F extends ReadSchema -> = ObjectSchemaBuilder, K> & Record>; -type Related = R extends RelationInfo ? S : never; -type RelationField = - R extends RelationInfo<'hasMany' | 'belongsToMany', any> - ? ArraySchemaBuilder - : R extends RelationInfo<'belongsTo', any> - ? R extends { optional: infer O } - ? true extends O - ? SchemaForValue | null> - : S - : S - : SchemaForValue | null>; -type Selector = (columns: C) => ReadColumn; -type AnyReadQuery = - | SchemaReadQuery - | PolymorphicReadQuery; -type Loaded = { - name: string; - query: AnyReadQuery; - relation: RelationSpec; - required: boolean; -}; -/** Result of entering opt-in read mode: an object reader or a declared variant union. */ -export type SchemaAwareQuery = - ReadVariantMetadata extends { - discriminator: string; - variants: Record; - } - ? PolymorphicReadQuery - : SchemaReadQuery; -/** @internal Apply parent correlation before child selection, ordering and pagination. */ -export type ReadCorrelation = ( - query: Knex.QueryBuilder, - alias: string, - source: ReadObject -) => void; - -/** - * Immutable, detached read query whose row schema matches its decoded SQL result. - * Enter through withRowSchema() before projections/includes. Ordinary legacy queries - * remain mutable and keep their existing values. Reading metadata never executes SQL. - */ -export class SchemaReadQuery< - S extends ReadObject, - Row extends ReadObject = ObjectReadSchema>, - Relations extends Record = ReadRelations -> extends ReadPredicates> { - /** @internal Nominal identity for typed child-query customizers. */ - declare readonly [READ_QUERY]: true; - private readonly alias = `__schema_read_${readAliasSequence++}`; - private fields: Record; - private loaded: Loaded[] = []; - private base: Knex.QueryBuilder; - private selected = false; - private grouped = false; - private readonly columns: Record>; - /** Runtime structural schema; stable across filters, pagination and transaction clones. */ - readonly rowSchema: Row; - - /** @internal Use withRowSchema() on a query/DbSet instead of constructing directly. */ - constructor( - private readonly knex: Knex, - private readonly source: S, - base: Knex.QueryBuilder - ) { - super(); - this.base = knex.queryBuilder().from(base.clone().as(this.alias)); - const relations = (source.introspect().extensions?.relations ?? - []) as RelationSpec[]; - const excluded = new Set(relations.map(r => r.name)); - const { propToCol } = buildColumnMap(source); - this.fields = Object.create(null); - this.columns = Object.create(null); - for (const [key, schema] of Object.entries( - source.introspect().properties ?? {} - )) { - if (excluded.has(key)) continue; - const node = compileReadSchema(schema as ReadSchema); - const column = `${this.alias}.${propToCol.get(key) ?? key}`; - this.columns[key] = { - [COLUMN]: { - alias: this.alias, - column: propToCol.get(key) ?? key, - schema - }, - [READ_COLUMN]: node - }; - this.fields[key] = { - node, - expression: knex => readExpression(knex, node, column) - }; - } - this.rowSchema = this.schema() as Row; - } - - private schema(): ReadObject { - return object( - Object.fromEntries( - Object.entries(this.fields).map(([key, field]) => [ - key, - field.node.schema - ]) - ) - ); - } - - private copy(): this { - const copy = Object.create(Object.getPrototypeOf(this)) as this; - Object.assign(copy, this, { - base: this.base.clone(), - fields: { ...this.fields }, - loaded: [...this.loaded] - }); - return copy; - } - - /** @internal Prevent customizers from substituting an unrelated query source. */ - sameSource(other: unknown): boolean { - return ( - other instanceof SchemaReadQuery && this.columns === other.columns - ); - } - - private column( - selector: ReadPredicateSelector> - ): ReadColumn { - const column = selector( - this.columns as ReadColumns - ); - if ( - !column || - !Object.values(this.columns).some(candidate => candidate === column) - ) - throw new ReadSchemaError( - 'Column does not belong to this read query' - ); - return column as ReadColumn; - } - - private name(column: ReadColumn): string { - return `${column[COLUMN].alias}.${column[COLUMN].column}`; - } - - /** Select an exact flat row shape, retaining per-field runtime schemas. */ - select

( - selector: (columns: ReadColumns) => P - ): SchemaReadQuery< - S, - ObjectSchemaBuilder< - MergeProps< - SchemaProps>, - Pick< - SchemaProps, - Extract, keyof Relations> - > - > - >, - Relations - > { - if (this.selected) - throw new ReadSchemaError( - 'Only one projection is allowed per read query' - ); - const selection = selector( - this.columns as ReadColumns - ); - if (this.loaded.length && Object.values(selection).some(isAggregate)) - throw new ReadSchemaError( - 'Aggregate projections cannot contain relations' - ); - const copy = this.copy(); - copy.fields = compileReadProjection( - this.knex, - selection, - expression => { - if ( - !Object.values(this.columns).includes( - expression as ReadColumn - ) - ) - throw new ReadSchemaError( - 'Projection column does not belong to this query' - ); - const column = expression as ReadColumn; - return { node: column[READ_COLUMN], name: this.name(column) }; - } - ); - copy.selected = true; - // Included relations are independent of the scalar projection. - for (const relation of copy.loaded) - copy.fields[relation.name] = this.fields[relation.name]; - Object.assign(copy, { rowSchema: copy.schema() }); - return copy as any; - } - - /** Apply a named schema projection with the same exact row-schema guarantees. */ - projected & string>( - name: K - ): SchemaReadQuery< - S, - ObjectSchemaBuilder< - Pick< - SchemaProps>, - Extract, keyof SchemaProps>> - > & - Pick< - SchemaProps, - Extract, keyof Relations> - > - >, - Relations - > { - const definition = getProjections(this.source)[name]; - if (!definition) - throw new ReadSchemaError(`Unknown projection: ${name}`); - return this.select(() => - Object.fromEntries( - definition.keys.map(key => [key, this.columns[key]]) - ) - ) as any; - } - - protected readPredicateContext(): ReadPredicateContext< - ReadColumns - > { - return { - knex: this.knex, - column: selector => this.name(this.column(selector)) - }; - } - protected addReadPredicate(predicate: ReadPredicate): this { - const copy = this.copy(); - predicate(copy.base); - return copy; - } - /** Order parent rows independently of any child relation's ordering. */ - orderBy( - column: Selector>, - direction: 'asc' | 'desc' = 'asc' - ): this { - const copy = this.copy(); - copy.base.orderBy(this.name(this.column(column)), direction); - return copy; - } - /** Append trusted raw ordering with captured positional bindings; ref() supplies quoted columns. */ - orderByRaw(sql: string, bindings: readonly Knex.RawBinding[] = []): this { - const captured = captureReadRaw(this.knex, sql, bindings); - const copy = this.copy(); - copy.base.orderByRaw(captured()); - return copy; - } - /** Group rows before typed aggregate projection. */ - groupBy(...columns: Selector>[]): this { - const copy = this.copy(); - copy.base.groupBy(columns.map(c => this.name(this.column(c)))); - copy.grouped = true; - return copy; - } - /** Limit parent rows; relation limits apply independently within each parent. */ - limit(count: number): this { - if (!Number.isInteger(count) || count < 0) - throw new ReadSchemaError('Limit must be a non-negative integer'); - const copy = this.copy(); - copy.base.limit(count); - return copy; - } - /** Skip parent rows; use a deterministic order for pagination. */ - offset(count: number): this { - if (!Number.isInteger(count) || count < 0) - throw new ReadSchemaError('Offset must be a non-negative integer'); - const copy = this.copy(); - copy.base.offset(count); - return copy; - } - - /** - * Load a declared relation in the same SQL statement. Return the customized child - * query so its selected fields and nested includes remain strongly typed. - */ - include< - K extends keyof Relations & string, - Child extends ReadQueryShape = SchemaAwareQuery> - >( - selector: (relations: { [P in keyof Relations]: P }) => K, - customize?: (query: SchemaAwareQuery>) => Child - ): SchemaReadQuery< - S, - AddField>, - Relations - > { - if (Object.values(this.fields).some(f => f.aggregate) || this.grouped) - throw new ReadSchemaError( - 'Grouped/aggregate reads cannot load entity relations' - ); - const definitions = (this.source.introspect().extensions?.relations ?? - []) as RelationSpec[]; - const key = selector( - Object.fromEntries(definitions.map(r => [r.name, r.name])) as any - ); - const relation = definitions.find(r => r.name === key); - if (!relation) throw new ReadSchemaError(`Unknown relation: ${key}`); - if (this.loaded.some(r => r.name === key)) - throw new ReadSchemaError(`Duplicate relation: ${key}`); - const foreign = - typeof relation.schema === 'function' - ? relation.schema() - : relation.schema; - return this.load( - relation, - this.child(foreign, customize as any), - relation.type === 'belongsTo' && !relation.optional - ) as any; - } - - private child( - foreign: ReadObject, - customize?: (query: any) => ReadQueryShape - ): AnyReadQuery { - const Constructor = getSchemaQueryBuilderCtor(); - let child: AnyReadQuery = createReadQuery( - this.knex, - foreign, - getEffectiveBaseQuery(new Constructor(this.knex, foreign)).clone() - ); - if (customize) { - const customized = customize(child as any); - if (!child.sameSource(customized)) - throw new ReadSchemaError( - 'Relation customizer must return its configured read query' - ); - child = customized as any; - } - return child; - } - - private load( - relation: RelationSpec, - child: AnyReadQuery, - required: boolean - ): this { - const key = relation.name; - if (this.grouped || Object.values(this.fields).some(f => f.aggregate)) - throw new ReadSchemaError( - 'Grouped/aggregate reads cannot load entity relations' - ); - if ( - this.loaded.some(r => r.name === key) || - Object.hasOwn(this.fields, key) - ) - throw new ReadSchemaError(`Duplicate result field: ${key}`); - const many = - relation.type === 'hasMany' || relation.type === 'belongsToMany'; - const schema = many - ? array(child.rowSchema) - : required - ? child.rowSchema - : child.rowSchema.nullable(); - const node: ReadNode = { - schema, - exact: false, - decode: (value, path) => { - if (value === null && !required && !many) return null; - if (many) { - if (!Array.isArray(value)) - throw new ReadSchemaError( - `${path}: expected a relation array` - ); - return value.map((row, i) => - child.decode(row, `${path}[${i}]`) - ); - } - return child.decode(value, path); - } - }; - const copy = this.copy(); - copy.loaded.push({ name: key, query: child, relation, required }); - copy.fields[key] = { - node, - expression: () => { - throw new ReadSchemaError( - 'Relation expression must be compiled in context' - ); - } - }; - Object.assign(copy, { rowSchema: copy.schema() }); - return copy as any; - } - - /** Join one typed nested object with explicit property keys, including nullable joins. */ - joinOne< - F extends ReadObject, - K extends string, - Required extends boolean = true, - Child extends ReadQueryShape = SchemaAwareQuery - >( - spec: JoinOneSpec, - customize?: (query: SchemaAwareQuery) => Child - ): SchemaReadQuery< - S, - AddField< - Row, - K, - Required extends true - ? Child['rowSchema'] - : SchemaForValue | null> - >, - Relations & Record> - > { - if (spec.foreignQuery || spec.mappers) - throw new ReadSchemaError( - 'Use the typed child customizer instead of foreignQuery/mappers in schema-aware mode' - ); - return this.load( - { - name: spec.as, - type: 'hasOne', - schema: spec.foreignSchema, - localKey: resolvePropertyKey( - spec.localColumn as any, - this.source, - 'joinOne' - ), - remoteKey: resolvePropertyKey( - spec.foreignColumn as any, - spec.foreignSchema, - 'joinOne' - ) - }, - this.child(spec.foreignSchema, customize as any), - spec.required !== false - ) as any; - } - - /** Join a typed collection, applying child projection and pagination independently per parent. */ - joinMany< - F extends ReadObject, - K extends string, - Child extends ReadQueryShape = SchemaAwareQuery - >( - spec: Omit, 'orderBy'>, - customize?: (query: SchemaAwareQuery) => Child - ): SchemaReadQuery< - S, - AddField>, - Relations & Record> - > { - if (spec.foreignQuery || spec.mappers || 'orderBy' in spec) - throw new ReadSchemaError( - 'Use the typed child customizer for ordering instead of raw foreignQuery/mappers' - ); - let child = this.child(spec.foreignSchema, customize as any); - if (spec.limit !== undefined) child = child.limit(spec.limit); - if (spec.offset !== undefined) child = child.offset(spec.offset); - return this.load( - { - name: spec.as, - type: 'hasMany', - schema: spec.foreignSchema, - localKey: resolvePropertyKey( - spec.localColumn as any, - this.source, - 'joinMany' - ), - remoteKey: resolvePropertyKey( - spec.foreignColumn as any, - spec.foreignSchema, - 'joinMany' - ) - }, - child, - false - ) as any; - } - - /** @internal Decode a SQL/JSON row using the same metadata exposed to consumers. */ - decode(row: any, path = 'row'): any { - if ( - this.source.introspect().extensions?.readOrphanColumn && - row.__read_cti_present == null - ) - throw new ReadSchemaError(`${path}: missing CTI variant body`); - return decodeObject( - Object.fromEntries( - Object.entries(this.fields).map(([key, field]) => [ - key, - field.node - ]) - ), - row, - path - ); - } - - /** @internal Compile a bound SQL statement; callers never receive the mutable builder. */ - compile(correlate?: ReadCorrelation): Knex.QueryBuilder { - const query = this.base.clone().clearSelect(); - correlate?.(query, this.alias, this.source); - const expressions: Record = Object.create(null); - for (const [key, field] of Object.entries(this.fields)) { - if (!this.loaded.some(r => r.name === key)) - expressions[key] = field.expression(this.knex); - } - const orphanColumn = - this.source.introspect().extensions?.readOrphanColumn; - if (orphanColumn) - expressions.__read_cti_present = this.knex.raw('??', [ - `${this.alias}.${orphanColumn}` - ]); - for (const loaded of this.loaded) { - const { relation, query: child } = loaded; - const parentTable = this.alias; - const parentPk = getPrimaryKeyColumns(this.source).columnNames; - const foreignKey = relation.foreignKey; - const resolveKey = (schema: ReadObject, key: any) => { - if (typeof key === 'function') { - const descriptor = key( - ObjectSchemaBuilderValue.getPropertiesFor(schema) - ); - key = - descriptor[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR] - .propertyName; - } - return buildColumnMap(schema).propToCol.get(key) ?? key; - }; - const childSql = child.compile((sql, childTable, childSource) => { - const childPk = getPrimaryKeyColumns(childSource).columnNames; - if (relation.localKey && relation.remoteKey) { - sql.where( - `${childTable}.${resolveKey(childSource, relation.remoteKey)}`, - this.knex.ref( - `${parentTable}.${resolveKey(this.source, relation.localKey)}` - ) - ); - return; - } - if (parentPk.length !== 1) - throw new ReadSchemaError( - 'Automatic relation reads require single-column primary keys' - ); - if (childPk.length !== 1) - throw new ReadSchemaError( - 'Automatic relation reads require single-column primary keys' - ); - if (relation.type === 'belongsTo') - sql.where( - `${childTable}.${childPk[0]}`, - this.knex.ref( - `${parentTable}.${resolveKey(this.source, foreignKey)}` - ) - ); - else if (relation.type === 'belongsToMany') { - const through = relation.through!; - sql.join( - through.table, - `${through.table}.${through.foreignKey}`, - `${childTable}.${childPk[0]}` - ).where( - `${through.table}.${through.localKey}`, - this.knex.ref(`${parentTable}.${parentPk[0]}`) - ); - } else - sql.where( - `${childTable}.${resolveKey(childSource, foreignKey)}`, - this.knex.ref(`${parentTable}.${parentPk[0]}`) - ); - }); - const many = - relation.type === 'hasMany' || - relation.type === 'belongsToMany'; - if (!many) childSql.limit(1); - const wrapped = this.knex - .queryBuilder() - .from(childSql.clone().as('__read_relation')); - expressions[loaded.name] = many - ? this.knex.raw( - "(select coalesce(jsonb_agg(to_jsonb(__read_relation)), '[]'::jsonb) from (?) as __read_relation)", - [childSql] - ) - : this.knex.raw( - '(select to_jsonb(__read_relation) from (?) as __read_relation)', - [childSql] - ); - if (loaded.required) - query.whereExists(wrapped.clone().select(this.knex.raw('1'))); - } - return query.select(expressions); - } - - /** Render debugging SQL without execution; bound values may be sensitive. */ - toQuery(): string { - return this.compile().toQuery(); - } - /** Execute one statement and decode its selected row graph. */ - async execute(): Promise[]> { - return (await this.compile()).map((row: unknown) => this.decode(row)); - } - /** Execute a limited copy, returning undefined when no row matches. */ - async first(): Promise | undefined> { - return (await this.limit(1).execute())[0]; - } - /** Awaiting executes the query; repeated awaits deliberately execute again. */ - // biome-ignore lint/suspicious/noThenProperty: query readers intentionally support await - then[], E = never>( - resolve?: ((rows: InferType[]) => T | PromiseLike) | null, - reject?: ((error: any) => E | PromiseLike) | null - ): Promise { - return this.execute().then(resolve, reject); - } - /** Bind an independent query graph to a caller-owned transaction. */ - transacting(trx: Knex.Transaction): this { - const copy = this.copy(); - copy.base.transacting(trx); - Object.assign(copy, { knex: trx }); - copy.loaded = this.loaded.map(r => ({ - ...r, - query: r.query.transacting(trx) - })); - return copy; - } - /** - * Read a lossless composite cursor page using native SQL ordering. Cursor sort - * values remain private text columns, independent of projections and Date decoding. - * Requires non-null scalar order columns containing a declared unique key. - */ - async paginateAfter( - options: CompositeCursorOptions - ): Promise>> { - if (this.grouped || Object.values(this.fields).some(f => f.aggregate)) - throw new ReadSchemaError( - 'Cursor pagination cannot be used for aggregate reads' - ); - const Constructor = getSchemaQueryBuilderCtor(); - const source = this.source.withExtension('tableName', this.alias); - const legacy = new Constructor(this.knex, source, this.compile()); - const state = getState(legacy); - state.skipDefaultScope = true; - state.includeDeleted = true; - // The query is already projected; reserve its aliases against cursor fields. - state.hiddenColumns = new Set(Object.keys(this.fields)); - return compositeCursor( - legacy, - options, - row => this.decode(row), - this.source.introspect().extensions?.tableName as string - ); - } - /** Fetch a numbered page and a count without mutating the source query. */ - async paginate(options: { - page: number; - pageSize: number; - }): Promise>> { - const { page, pageSize } = options; - if ( - !Number.isInteger(page) || - page < 1 || - !Number.isInteger(pageSize) || - pageSize < 1 - ) - throw new ReadSchemaError( - 'Page and pageSize must be positive integers' - ); - const countQuery = this.compile() - .clearOrder() - .clear('limit') - .clear('offset'); - const countRow = await this.knex - .from(countQuery.as('__read_count')) - .count({ count: '*' }) - .first(); - const total = Number(countRow?.count ?? 0); - if (!Number.isSafeInteger(total)) - throw new ReadSchemaError( - 'Pagination count exceeds the safe integer range' - ); - const data = await this.offset((page - 1) * pageSize) - .limit(pageSize) - .execute(); - const totalPages = Math.ceil(total / pageSize); - return { - data, - total, - page, - pageSize, - totalPages, - hasNextPage: page < totalPages, - hasPreviousPage: page > 1 - }; - } -} - -import { ObjectSchemaBuilder as ObjectSchemaBuilderValue } from '@cleverbrush/schema'; - -/** @internal Enter schema-aware mode only before result-shaping legacy operations. */ -export function schemaReadQuery( - builder: SchemaQueryBuilder -): SchemaAwareQuery { - const state = getState(builder); - if ( - state.selectionMode !== null || - state.specs.length || - state.variantRelationIncludes.length - ) - throw new ReadSchemaError( - 'Call withRowSchema() before select/include/join operations' - ); - if ( - state.opaqueReadShape || - state.enabledVariants || - state.variantWhereFilters.length - ) - throw new ReadSchemaError( - 'Call withRowSchema() before raw or variant-specific operations' - ); - const base = getEffectiveBaseQuery(builder).clone(); - const statements = (base as any)._statements as Array<{ grouping: string }>; - if ( - statements.some(s => s.grouping === 'order') || - (base as any)._single.limit !== undefined || - (base as any)._single.offset !== undefined - ) - throw new ReadSchemaError( - 'Call withRowSchema() before ordering or pagination' - ); - if ( - statements.some(s => - ['columns', 'join', 'group', 'having', 'union'].includes(s.grouping) - ) - ) - throw new ReadSchemaError( - 'Existing raw projections, joins and aggregates cannot declare a read schema' - ); - return createReadQuery(state.knex, state.localSchema as S, base); -} - -/** @internal Shared reader factory for roots and nested relations. */ -export function createReadQuery( - knex: Knex, - schema: S, - base: Knex.QueryBuilder -): SchemaAwareQuery { - return ( - getVariants(schema) - ? new PolymorphicReadQuery(knex, schema, base) - : new SchemaReadQuery(knex, schema, base) - ) as SchemaAwareQuery; -} diff --git a/libs/knex-schema/src/aliased-query.ts b/libs/knex-schema/src/aliased-query.ts index 455b16dd..1b98e8c9 100644 --- a/libs/knex-schema/src/aliased-query.ts +++ b/libs/knex-schema/src/aliased-query.ts @@ -1,6 +1,6 @@ import type { InferType, ObjectSchemaBuilder } from '@cleverbrush/schema'; import type { Knex } from 'knex'; -import { AliasedReadQuery } from './AliasedReadQuery.js'; + import { buildColumnMap } from './columns.js'; import type { SchemaProps } from './entity.js'; import { @@ -10,11 +10,9 @@ import { compileAggregate, isAggregate } from './expressions.js'; -import { - ALLOWED_OPS, - getEffectiveBaseQuery, - getSchemaQueryBuilderCtor -} from './operations/helpers.js'; +import { getTableName } from './extension.js'; +import { ALLOWED_OPS } from './operations/helpers.js'; +import { SchemaQueryBuilder } from './SchemaQueryBuilder.js'; import { isSqlIdentifier } from './sql-identifiers.js'; type TableSchema = ObjectSchemaBuilder; @@ -135,8 +133,8 @@ export function or(...items: JoinPredicate[]): JoinPredicate { return { [PREDICATE]: { op: 'or', items } }; } -/** Read-only flat query builder. Explicit projections avoid ambiguous SELECT *. */ -export class AliasedQueryBuilder { +/** @internal Mutable native SQL planner, never returned by a public factory. */ +export class AliasedQuerySource { private sql: Knex.QueryBuilder; private tables = new Map(); private selected = false; @@ -144,17 +142,8 @@ export class AliasedQueryBuilder { private nullableTables = new Set(); private opaqueReadShape = false; - /** Enter immutable read mode before select/raw changes; an explicit projection is required. */ - withRowSchema(): AliasedReadQuery { - if (this.selected || this.opaqueReadShape) - throw new Error( - 'Call withRowSchema() before select/apply operations' - ); - return new AliasedReadQuery(this.cloneReadSource()); - } - /** @internal Snapshot the SQL planner without sharing mutable query state. */ - cloneReadSource(): AliasedQueryBuilder { + cloneReadSource(): AliasedQuerySource { const copy = Object.assign( Object.create(Object.getPrototypeOf(this)), this @@ -166,7 +155,7 @@ export class AliasedQueryBuilder { return copy; } - /** @internal Schema-backed state used by the immutable opt-in adapter. */ + /** @internal Schema-backed state used by the immutable public builder. */ readContext(): { knex: Knex; sql: Knex.QueryBuilder; columns: TTables } { return { knex: this.knex, sql: this.sql.clone(), columns: this.tree() }; } @@ -185,9 +174,12 @@ export class AliasedQueryBuilder { } private source(table: TableAlias): Knex.QueryBuilder { - const Constructor = getSchemaQueryBuilderCtor(); - return getEffectiveBaseQuery(new Constructor(this.knex, table.schema)) - .clone() + return new SchemaQueryBuilder( + this.knex, + table.schema, + this.knex(getTableName(table.schema)) + ) + .storageQuery() .as(table.name); } @@ -257,7 +249,7 @@ export class AliasedQueryBuilder { join( table: N extends keyof TTables ? never : TableAlias, on: (tables: TTables & AliasTables) => JoinPredicate - ): AliasedQueryBuilder, TResult> { + ): AliasedQuerySource, TResult> { this.addJoin(table, on as any, false); return this as any; } @@ -270,7 +262,7 @@ export class AliasedQueryBuilder { leftJoin( table: N extends keyof TTables ? never : TableAlias, on: (tables: TTables & AliasTables) => JoinPredicate - ): AliasedQueryBuilder, TResult> { + ): AliasedQuerySource, TResult> { this.addJoin(table, on as any, true); return this as any; } @@ -421,7 +413,7 @@ export class AliasedQueryBuilder { */ select( selector: (tables: TTables) => S - ): AliasedQueryBuilder> { + ): AliasedQuerySource> { if (this.selected) throw new Error('Only one object projection per query'); const columns: Record = {}; @@ -467,12 +459,9 @@ export class AliasedQueryBuilder { * Clone this builder onto an existing transaction, preserving its tables/projection. * Does not modify the source or commit/roll back the transaction. */ - transacting(trx: Knex.Transaction): AliasedQueryBuilder { - const [name, schema] = this.tables.entries().next().value!; - const copy = new AliasedQueryBuilder( - trx as unknown as Knex, - alias(schema, name) - ); + transacting(trx: Knex.Transaction): AliasedQuerySource { + const copy = this.cloneReadSource(); + Object.assign(copy, { knex: trx }); copy.sql = this.sql.clone().transacting(trx); copy.tables = new Map(this.tables); copy.selected = this.selected; diff --git a/libs/knex-schema/src/composable-query.test-d.ts b/libs/knex-schema/src/composable-query.test-d.ts index a03fa6f4..77dc4b84 100644 --- a/libs/knex-schema/src/composable-query.test-d.ts +++ b/libs/knex-schema/src/composable-query.test-d.ts @@ -35,8 +35,12 @@ test('factories retain schema, alias and result inference through every call sha expectTypeOf(await plain.select(t => ({ id: t.id }))).toEqualTypeOf< { id: number }[] >(); + // @ts-expect-error implicit raw base queries cannot declare an output contract + query(db, Task, db('tasks')); expectTypeOf( - await query(db, Task, db('tasks')).select(t => ({ amount: t.amount })) + await query(db, Task).selectRaw('1 as amount', [], { + output: object({ amount: number() }) + }) ).toEqualTypeOf<{ amount: number }[]>(); for (const factory of [bound, transactional]) { const ordinary = factory(Task); @@ -44,8 +48,12 @@ test('factories retain schema, alias and result inference through every call sha expectTypeOf( await ordinary.select(t => ({ createdAt: t.createdAt })) ).toEqualTypeOf<{ createdAt: Date }[]>(); + // @ts-expect-error bound factories cannot accept an opaque raw base query + factory(Task, db('tasks')); expectTypeOf( - await factory(Task, db('tasks')).select(t => ({ id: t.id })) + await factory(Task).selectRaw('1 as id', [], { + output: object({ id: number() }) + }) ).toEqualTypeOf<{ id: number }[]>(); const aliased = factory(alias(Task, 'task')); expectTypeOf(aliased).not.toBeAny(); @@ -113,10 +121,10 @@ test('aggregate defaults and supplied output schemas infer accurately', async () await query(db, Task).maxValue('createdAt') ).toEqualTypeOf(); expectTypeOf(await query(db, Task).minValue('amount')).toEqualTypeOf< - number | string | null + number | null >(); expectTypeOf(await query(db, Task).minValue(t => t.amount)).toEqualTypeOf< - number | string | null + number | null >(); expectTypeOf( await query(db, Task).countValue({ output: string() }) diff --git a/libs/knex-schema/src/composable-query.test.ts b/libs/knex-schema/src/composable-query.test.ts index b3a3dbed..9a9e2f6e 100644 --- a/libs/knex-schema/src/composable-query.test.ts +++ b/libs/knex-schema/src/composable-query.test.ts @@ -118,8 +118,8 @@ describe('typed aliases and aggregate SQL', () => { count: aggregate.countDistinct(t.id) })) .toQuery(); - expect(sql).toContain('count(distinct "id")'); - expect(sql).toContain('group by "owner_id"'); + expect(sql).toMatch(/count\(distinct "__schema_read_\d+"\."id"\)/); + expect(sql).toMatch(/group by "__schema_read_\d+"\."owner_id"/); }); }); diff --git a/libs/knex-schema/src/entity.ts b/libs/knex-schema/src/entity.ts index fe6ea98e..d226d506 100644 --- a/libs/knex-schema/src/entity.ts +++ b/libs/knex-schema/src/entity.ts @@ -910,7 +910,7 @@ export type EntityVariantUnion = /** * Type-level helper: union of relation key names declared on an entity. - * Used by `SchemaQueryBuilder.insert()/update()/upsert()` to omit relation + * Used by `QuerySource.insert()/update()/upsert()` to omit relation * navigation properties from accepted input. * @public */ diff --git a/libs/knex-schema/src/extension.ts b/libs/knex-schema/src/extension.ts index e5875aac..f24b14d5 100644 --- a/libs/knex-schema/src/extension.ts +++ b/libs/knex-schema/src/extension.ts @@ -1,4 +1,5 @@ // @cleverbrush/knex-schema — Schema extension: hasColumnName / hasTableName + import type { AnySchemaBuilder, ArraySchemaBuilder, @@ -28,6 +29,7 @@ import { stringExtensions, withExtensions } from '@cleverbrush/schema'; +import type { QueryScope } from './query-scope.js'; import type { ResolvedVariantConfig, ResolvedVariantRelationSpec, @@ -731,12 +733,15 @@ export const ddlExtension = defineExtension({ }, /** Register a named query scope. * @param name - Scope name to use with `.scoped(name)`. - * @param fn - Function that receives a `SchemaQueryBuilder` and applies filters. + * @param fn - Synchronous callback returning its configured immutable query (filters/order/paging only). */ - scope( - this: ObjectSchemaBuilder, + scope< + N extends string, + S extends ObjectSchemaBuilder + >( + this: S, name: N, - fn: Function + fn: (query: QueryScope) => QueryScope ): typeof this & { readonly [METHOD_LITERAL_BRAND]?: N } { const existing = (this.getExtension('scopes') as Record) ?? {}; @@ -886,12 +891,11 @@ export const ddlExtension = defineExtension({ }; }, /** Set a default scope applied to all queries unless `.unscoped()` is called. - * @param fn - Function that receives a `SchemaQueryBuilder` and applies filters. + * @param fn - Synchronous function that returns its configured immutable query scope. */ - defaultScope( - this: ObjectSchemaBuilder, - fn: Function - ) { + defaultScope< + S extends ObjectSchemaBuilder + >(this: S, fn: (query: QueryScope) => QueryScope) { return this.withExtension('defaultScope', fn); }, /** Register a before-insert lifecycle hook. @@ -928,7 +932,7 @@ export const ddlExtension = defineExtension({ return this.withExtension('beforeUpdate', [...existing, fn]); }, /** Register a before-delete lifecycle hook. - * @param fn - Async function `(query)` called before deleting. + * @param fn - Observational async function `(query)` called before deleting; query configuration is immutable. Apply delete filters before calling delete(). */ beforeDelete( this: ObjectSchemaBuilder, @@ -1004,7 +1008,7 @@ export const ddlExtension = defineExtension({ // The internal worker {@link applyVariantsToSchema} (below this // `defineExtension` block) is invoked by the Entity layer and stores // the same `'variants'` / `'polymorphicVariants'` extensions that - // `SchemaQueryBuilder` reads at runtime. + // `QuerySource` reads at runtime. } }); @@ -1035,7 +1039,7 @@ export interface VariantInputForResolver { /** * @internal Validate + apply a fully-resolved variant config to a base * schema. Stores the `'variants'` and `'polymorphicVariants'` extensions - * read by {@link SchemaQueryBuilder}. + * read by {@link QuerySource}. * * Called by the {@link Entity} chain (`.discriminator().ctiVariant().stiVariant()`). * Replaces the previous schema-level `.withVariants()` method. @@ -1333,7 +1337,7 @@ export function getProjections( * Retrieve the resolved variant configuration stored by `.withVariants()`. * Returns `null` when the schema is not polymorphic. * - * @internal — used by {@link SchemaQueryBuilder}. + * @internal — used by {@link QuerySource}. */ export function getVariants( schema: ObjectSchemaBuilder diff --git a/libs/knex-schema/src/immutable-query.test-d.ts b/libs/knex-schema/src/immutable-query.test-d.ts new file mode 100644 index 00000000..014aba57 --- /dev/null +++ b/libs/knex-schema/src/immutable-query.test-d.ts @@ -0,0 +1,60 @@ +import type { InferType } from '@cleverbrush/schema'; +import Knex from 'knex'; +import { expectTypeOf, test } from 'vitest'; +import { number, object, string } from './extension.js'; +import { query } from './query.js'; + +const db = Knex({ client: 'pg' }); +const User = object({ + id: number().bigint().primaryKey(), + name: string(), + settings: object({ enabled: number() }) +}).hasTableName('users'); + +test('automatic projections preserve exact structural row types', async () => { + const users = query(db, User); + const selected = users.select(u => ({ userId: u.id, displayName: u.name })); + type Row = InferType; + expectTypeOf().toEqualTypeOf<{ + userId: string; + displayName: string; + }>(); + expectTypeOf(await selected.first()).toEqualTypeOf(); + const ids = users.select(u => u.id); + expectTypeOf>().toEqualTypeOf<{ + id: string; + }>(); + const nested = users.select(u => ({ enabled: u.settings.enabled })); + expectTypeOf>().toEqualTypeOf<{ + enabled: number; + }>(); + // @ts-expect-error projected queries cannot update entities + selected.update({ name: 'unsafe' }); + // @ts-expect-error projected queries cannot insert entities + selected.insert({ name: 'unsafe' }); + // @ts-expect-error grouped predicate callbacks must return their configured builder + users.where(group => { + group.where('name', 'discarded'); + }); + // @ts-expect-error raw SQL cannot infer its output shape + users.apply(sql => sql.select('*')); + // @ts-expect-error removed opt-in method has no compatibility alias + users.withRowSchema(); + // Lossless storage representations are valid update values. + users.update({ id: '9007199254740993' }); + // @ts-expect-error grouped sources cannot write entities + users.groupBy(u => u.name).update({ name: 'unsafe' }); + // @ts-expect-error distinct sources cannot write entities + users.distinct().delete(); + // @ts-expect-error HAVING queries are grouped read-only sources + users.havingRaw('count(*) > 0').delete(); + User.scope('named', scope => { + // @ts-expect-error scopes cannot change projections + scope.where('name', 'x').select('id'); + return scope.where('name', 'x'); + }); + // @ts-expect-error scope callbacks cannot discard their immutable result + User.defaultScope(scope => { + scope.where('name', 'x'); + }); +}); diff --git a/libs/knex-schema/src/immutable-query.test.ts b/libs/knex-schema/src/immutable-query.test.ts new file mode 100644 index 00000000..bb2cee50 --- /dev/null +++ b/libs/knex-schema/src/immutable-query.test.ts @@ -0,0 +1,163 @@ +import { + number as outputNumber, + object as outputObject +} from '@cleverbrush/schema'; +import Knex from 'knex'; +import { describe, expect, it, vi } from 'vitest'; +import { defineEntity } from './entity.js'; +import { number, object, string } from './extension.js'; +import { createQuery, query } from './query.js'; + +const knex = Knex({ client: 'pg' }); +const Account = object({ + id: number().primaryKey(), + name: string(), + active: number() +}).hasTableName('accounts'); + +describe('immutable public queries', () => { + it('owns polymorphic ordering columns and rejects visibility-changing scopes', () => { + const Asset = defineEntity( + object({ id: number().primaryKey(), kind: string() }).hasTableName( + 'assets' + ) + ) + .discriminator('kind') + .stiVariant('note', object({ text: string() })); + const one = query(knex, Asset.schema); + const two = query(knex, Asset.schema); + let foreign: any; + one.orderBy(c => { + foreign = c.id; + return c.id; + }); + expect(() => two.orderBy(() => foreign)).toThrow(/does not belong/); + const invalid = Asset.schema.defaultScope(((q: any) => + q.withDeleted()) as any); + expect(() => query(knex, invalid)).toThrow(/shape-preserving/); + }); + it('rejects removed raw-source overloads instead of silently dropping SQL', () => { + expect(() => (query as any)(knex, Account, knex('accounts'))).toThrow( + /output/ + ); + expect(() => + (createQuery(knex) as any)(Account, knex('accounts')) + ).toThrow(/output/); + }); + it('derives metadata automatically and preserves it across filters and paging', () => { + const base = query(knex, Account); + const one = base.where('id', 1).limit(1); + const two = base.where('id', 2).offset(3); + expect(one).not.toBe(base); + expect(one.rowSchema).toBe(base.rowSchema); + expect(two.rowSchema).toBe(base.rowSchema); + expect(base.toQuery()).not.toContain('where'); + expect(one.toQuery()).toContain('= 1'); + expect(two.toQuery()).toContain('= 2'); + expect('withRowSchema' in base).toBe(false); + }); + + it('replaces projections without mutating either source', () => { + const base = query(knex, Account); + const named = base.select(t => ({ name: t.name })); + const ids = named.select(t => ({ key: t.id })); + expect(Object.keys(base.rowSchema.introspect().properties)).toEqual([ + 'id', + 'name', + 'active' + ]); + expect(Object.keys(named.rowSchema.introspect().properties)).toEqual([ + 'name' + ]); + expect(Object.keys(ids.rowSchema.introspect().properties)).toEqual([ + 'key' + ]); + expect(ids.rowSchema).not.toBe(named.rowSchema); + }); + + it('runs groups once and ignores discarded immutable branches', () => { + let retained: any; + const callback = vi.fn(group => { + retained = group; + group.where('id', 99); + return group.where('id', 1).orWhere('id', 2); + }); + const filtered = query(knex, Account).where(callback); + retained.where('id', 3); + expect(filtered.toQuery()).toContain('= 1 or'); + expect(filtered.toQuery()).not.toContain('99'); + expect(filtered.toQuery()).not.toContain('= 3'); + expect(callback).toHaveBeenCalledTimes(1); + }); + + it('rejects void, async and unrelated group results without executing them', () => { + const base = query(knex, Account); + expect(() => base.where((() => undefined) as any)).toThrow( + /must return/ + ); + expect(() => base.where((async () => undefined) as any)).toThrow( + /synchronous/ + ); + expect(() => base.where((() => base) as any)).toThrow( + /synchronous|must return/ + ); + }); + + it('captures default scopes once and removes only their effects', () => { + const scope = vi.fn(q => q.where('active', 1)); + const schema = Account.defaultScope(scope); + const base = query(knex, schema); + const explicit = base.where('id', 7); + const unscoped = explicit.unscoped(); + expect(explicit.toQuery()).toMatch(/"active" = 1/); + expect(unscoped.toQuery()).not.toMatch(/"active" = 1/); + expect(unscoped.toQuery()).toMatch(/"id" = 7/); + expect(scope).toHaveBeenCalledTimes(1); + expect(unscoped.rowSchema).toBe(base.rowSchema); + }); + + it('isolates mutable Knex snapshots', () => { + const base = query(knex, Account).where('active', 1); + const snapshot = base.toKnexQuery(); + snapshot.where('id', 9).limit(1); + expect(base.toQuery()).not.toContain('= 9'); + expect(base.toQuery()).not.toContain('limit'); + }); + + it('captures opaque SQL once and requires an explicit object output', () => { + const output = outputObject({ total: outputNumber().coerce() }); + let retained: any; + const configure = vi.fn(sql => { + retained = sql; + return sql.clearSelect().count({ total: '*' }); + }); + const raw = query(knex, Account).apply(configure, { output }); + retained.where('id', 99); + expect(raw.rowSchema).toBe(output); + expect(raw.toQuery()).toContain('count(*)'); + expect(raw.toQuery()).not.toContain('99'); + raw.toQuery(); + expect(configure).toHaveBeenCalledTimes(1); + expect(() => + (query(knex, Account) as any).selectRaw('1 as total', []) + ).toThrow(/output/); + }); + + it('rejects writes through projected query instances at runtime too', async () => { + const selected = query(knex, Account).select(t => ({ name: t.name })); + await expect( + (selected as any).insert({ name: 'unsafe' }) + ).rejects.toThrow(/unprojected/); + await expect( + (selected as any).update({ name: 'unsafe' }) + ).rejects.toThrow(/unprojected/); + await expect((selected as any).insertMany([])).rejects.toThrow( + /unprojected/ + ); + await expect( + (query(knex, Account).havingRaw('count(*) > 0') as any).update({ + name: 'unsafe' + }) + ).rejects.toThrow(/unprojected/); + }); +}); diff --git a/libs/knex-schema/src/index.ts b/libs/knex-schema/src/index.ts index 90d301f6..e727ed58 100644 --- a/libs/knex-schema/src/index.ts +++ b/libs/knex-schema/src/index.ts @@ -1,7 +1,7 @@ // @cleverbrush/knex-schema — Type-safe schema-driven query builder for Knex -export type { ReadAliasTables } from './AliasedReadQuery.js'; -export { AliasedReadQuery } from './AliasedReadQuery.js'; +export type { ReadAliasTables } from './AliasedQueryBuilder.js'; +export { AliasedQueryBuilder } from './AliasedQueryBuilder.js'; export type { AliasTables, JoinedProjection, @@ -9,7 +9,7 @@ export type { TableAlias } from './aliased-query.js'; // Types -export { AliasedQueryBuilder, alias, and, eq, or } from './aliased-query.js'; +export { alias, and, eq, or } from './aliased-query.js'; export type { PrimaryKeyColumns, RowVersionColumn, @@ -92,13 +92,18 @@ export { tableExistsInDb, validateEntitiesAgainstDatabase } from './migration.js'; +export { OpaqueQuery, type QueryOutput } from './OpaqueQuery.js'; export type { CompositeCursorOptions } from './operations/composite-cursor.js'; export type { PolymorphicRowSchema, VariantReadSchema, VariantReadSchemas -} from './PolymorphicReadQuery.js'; -export { PolymorphicReadQuery } from './PolymorphicReadQuery.js'; +} from './PolymorphicQueryBuilder.js'; +export { PolymorphicQueryBuilder } from './PolymorphicQueryBuilder.js'; +export type { BoundQuery } from './query.js'; +// Main entry point +export { createQuery, query } from './query.js'; +export type { QueryScope } from './query-scope.js'; // Raw query execution export { rawQuery } from './raw.js'; export type { @@ -125,20 +130,14 @@ export type { SchemaForValue } from './read-schema.js'; export { ReadSchemaError } from './read-schema.js'; -export type { BoundQuery } from './SchemaQueryBuilder.js'; -// Main entry point -export { - createQuery, - query, - SchemaQueryBuilder -} from './SchemaQueryBuilder.js'; export type { ReadColumn, ReadColumns, ReadProjection, + ReadQueryShape, SchemaAwareQuery -} from './SchemaReadQuery.js'; -export { SchemaReadQuery } from './SchemaReadQuery.js'; +} from './SchemaQueryBuilder.js'; +export { SchemaQueryBuilder } from './SchemaQueryBuilder.js'; // Snapshot-based migration export { entitiesToSnapshot, diff --git a/libs/knex-schema/src/mappers.ts b/libs/knex-schema/src/mappers.ts index 050c3058..c59d3586 100644 --- a/libs/knex-schema/src/mappers.ts +++ b/libs/knex-schema/src/mappers.ts @@ -98,7 +98,7 @@ export function mapObject>( * the `mappers` defined on each spec to the nested data, and returns the * mutated row. * - * This is an internal helper used by {@link SchemaQueryBuilder}'s result + * This is an internal helper used by {@link QuerySource}'s result * mapping pipeline. Exported to allow custom post-processing if needed. * * @param row - The raw result row (mutated in place). diff --git a/libs/knex-schema/src/operations/aggregate.ts b/libs/knex-schema/src/operations/aggregate.ts index 063ee36c..964fa5fd 100644 --- a/libs/knex-schema/src/operations/aggregate.ts +++ b/libs/knex-schema/src/operations/aggregate.ts @@ -8,21 +8,21 @@ import { createAggregate, type SelectableColumn } from '../expressions.js'; -import type { SchemaQueryBuilder } from '../SchemaQueryBuilder.js'; +import type { QuerySource } from '../QuerySource.js'; import type { ColumnRef } from '../types.js'; import { buildQuery, getEffectiveBaseQuery, - getSchemaQueryBuilderCtor + getQuerySourceCtor } from './helpers.js'; import { getState } from './state.js'; /** Clone Framework metadata as well as Knex state; terminal helpers never mutate the source. */ export function cloneQuery( - builder: SchemaQueryBuilder -): SchemaQueryBuilder { + builder: QuerySource +): QuerySource { const state = getState(builder); - const Constructor = getSchemaQueryBuilderCtor(); + const Constructor = getQuerySourceCtor(); const copy = new Constructor( state.knex, state.localSchema, @@ -78,7 +78,7 @@ export function assertScalarSource(query: Knex.QueryBuilder): void { export async function scalarAggregate< S extends ObjectSchemaBuilder >( - builder: SchemaQueryBuilder, + builder: QuerySource, kind: AggregateKind, column?: ColumnRef, options?: AggregateOptions diff --git a/libs/knex-schema/src/operations/composite-cursor.ts b/libs/knex-schema/src/operations/composite-cursor.ts index 342bda95..243d2ef9 100644 --- a/libs/knex-schema/src/operations/composite-cursor.ts +++ b/libs/knex-schema/src/operations/composite-cursor.ts @@ -5,7 +5,7 @@ import { getPrimaryKeyColumns, resolvePropertyKey } from '../columns.js'; -import type { SchemaQueryBuilder } from '../SchemaQueryBuilder.js'; +import type { QuerySource } from '../QuerySource.js'; import type { ColumnRef, CursorPaginationResult } from '../types.js'; import { cloneQuery, statements } from './aggregate.js'; import { cleanAndMapRow, getEffectiveBaseQuery, getQuery } from './helpers.js'; @@ -30,7 +30,7 @@ export interface CompositeCursorOptions< } export async function compositeCursor( - builder: SchemaQueryBuilder, + builder: QuerySource, options: CompositeCursorOptions, decode?: (row: Record) => any, sourceIdentity?: string diff --git a/libs/knex-schema/src/operations/delete.ts b/libs/knex-schema/src/operations/delete.ts index 5f8a7962..506d275f 100644 --- a/libs/knex-schema/src/operations/delete.ts +++ b/libs/knex-schema/src/operations/delete.ts @@ -1,11 +1,12 @@ // @cleverbrush/knex-schema — DELETE / soft-delete / restore operations -import type { SchemaQueryBuilder } from '../SchemaQueryBuilder.js'; +import type { QuerySource } from '../QuerySource.js'; +import { returningReadColumns } from '../read-schema.js'; import { getSoftDelete, invalidateCache, mapRow } from './helpers.js'; import { getState } from './state.js'; export async function deleteImpl( - builder: SchemaQueryBuilder + builder: QuerySource ): Promise { const state = getState(builder); @@ -14,7 +15,7 @@ export async function deleteImpl( | Function[] | undefined) ?? []; for (const hook of hooks) { - await hook(builder); + await hook(state.hookQuery ?? builder); } const softDelete = getSoftDelete(builder); @@ -26,14 +27,14 @@ export async function deleteImpl( return state.baseQuery.delete(); } -export function withDeletedImpl(builder: SchemaQueryBuilder): any { +export function withDeletedImpl(builder: QuerySource): any { const state = getState(builder); invalidateCache(builder); state.includeDeleted = true; return builder; } -export function onlyDeletedImpl(builder: SchemaQueryBuilder): any { +export function onlyDeletedImpl(builder: QuerySource): any { const state = getState(builder); invalidateCache(builder); state.onlyDeleted = true; @@ -42,7 +43,7 @@ export function onlyDeletedImpl(builder: SchemaQueryBuilder): any { } export async function hardDeleteImpl( - builder: SchemaQueryBuilder + builder: QuerySource ): Promise { const state = getState(builder); const hooks = @@ -50,13 +51,13 @@ export async function hardDeleteImpl( | Function[] | undefined) ?? []; for (const hook of hooks) { - await hook(builder); + await hook(state.hookQuery ?? builder); } return state.baseQuery.delete(); } export async function restoreImpl( - builder: SchemaQueryBuilder + builder: QuerySource ): Promise { const state = getState(builder); const softDelete = getSoftDelete(builder); @@ -67,6 +68,6 @@ export async function restoreImpl( } const rows = await state.baseQuery .update({ [softDelete.column]: null }) - .returning('*'); + .returning(returningReadColumns(state.knex, state.localSchema)); return rows.map((row: any) => mapRow(builder, row)); } diff --git a/libs/knex-schema/src/operations/helpers.ts b/libs/knex-schema/src/operations/helpers.ts index f47c5ad9..a39c1c01 100644 --- a/libs/knex-schema/src/operations/helpers.ts +++ b/libs/knex-schema/src/operations/helpers.ts @@ -1,4 +1,4 @@ -// @cleverbrush/knex-schema — Extracted helper functions from SchemaQueryBuilder +// @cleverbrush/knex-schema — Extracted helper functions from QuerySource import type { InferType } from '@cleverbrush/schema'; import { @@ -19,7 +19,7 @@ import { POLYMORPHIC_TYPE_BRAND } from '../extension.js'; import { clearRow } from '../mappers.js'; -import type { SchemaQueryBuilder } from '../SchemaQueryBuilder.js'; +import type { QuerySource } from '../QuerySource.js'; import type { ColumnRef, ResolvedVariantConfig, @@ -65,7 +65,7 @@ export type QueryResultType = TLocalSchema extends { // --------------------------------------------------------------------------- export function resolveColumn( - builder: SchemaQueryBuilder, + builder: QuerySource, ref: any, label = 'column' ): string | Knex.Raw { @@ -78,12 +78,12 @@ export function resolveColumn( ); } -export function invalidateCache(builder: SchemaQueryBuilder): void { +export function invalidateCache(builder: QuerySource): void { getState(builder).cachedBuiltQuery = null; } export function getSoftDelete( - builder: SchemaQueryBuilder + builder: QuerySource ): { column: string } | null { const state = getState(builder); const ext = (state.localSchema as any).getExtension?.('softDelete'); @@ -91,7 +91,7 @@ export function getSoftDelete( } export function getDefaultScope( - builder: SchemaQueryBuilder + builder: QuerySource ): Function | null { const state = getState(builder); const fn = (state.localSchema as any).getExtension?.('defaultScope'); @@ -99,7 +99,7 @@ export function getDefaultScope( } export function getTimestamps( - builder: SchemaQueryBuilder + builder: QuerySource ): { createdAt: string; updatedAt: string } | null { const state = getState(builder); const ts = (state.localSchema as any).getExtension?.('timestamps'); @@ -127,7 +127,7 @@ const ALLOWED_OPS = new Set([ export { ALLOWED_OPS }; export function getVariantConfig( - builder: SchemaQueryBuilder + builder: QuerySource ): ResolvedVariantConfig | null { const state = getState(builder); if (state.variantConfig !== undefined) return state.variantConfig; @@ -149,7 +149,7 @@ export function getVariantConfig( } export function applyVariantJoins( - builder: SchemaQueryBuilder, + builder: QuerySource, base: Knex.QueryBuilder, variantConfig: ResolvedVariantConfig ): Knex.QueryBuilder { @@ -289,24 +289,22 @@ export function applyVariantJoins( return qb; } -// Circular-dependency-safe SchemaQueryBuilder constructor reference -// Set by SchemaQueryBuilder.ts after the class is defined. -let SchemaQueryBuilderCtor: new (...args: any[]) => any = null!; -export function registerSchemaQueryBuilder( - ctor: new (...args: any[]) => any -): void { - SchemaQueryBuilderCtor = ctor; +// Circular-dependency-safe QuerySource constructor reference +// Set by QuerySource.ts after the class is defined. +let QuerySourceCtor: new (...args: any[]) => any = null!; +export function registerQuerySource(ctor: new (...args: any[]) => any): void { + QuerySourceCtor = ctor; } -export function getSchemaQueryBuilderCtor(): new (...args: any[]) => any { - return SchemaQueryBuilderCtor; +export function getQuerySourceCtor(): new (...args: any[]) => any { + return QuerySourceCtor; } export function buildVariantRelationSelect( - builder: SchemaQueryBuilder, + builder: QuerySource, foreignSchema: ObjectSchemaBuilder, relAlias: string, foreignTableName: string, - customize?: (q: SchemaQueryBuilder) => void + customize?: (q: QuerySource) => void ): Knex.Raw[] { const state = getState(builder); const knex = state.knex; @@ -318,7 +316,7 @@ export function buildVariantRelationSelect( let columnsToSelect: string[]; if (customize) { - const probe = new (SchemaQueryBuilderCtor as any)( + const probe = new (QuerySourceCtor as any)( state.knex, foreignSchema, state.knex(foreignTableName) @@ -348,7 +346,7 @@ export function buildVariantRelationSelect( } export function mapPolymorphicRow( - builder: SchemaQueryBuilder, + builder: QuerySource, row: Record, variantConfig: ResolvedVariantConfig ): Record { @@ -458,14 +456,14 @@ export function mapPolymorphicRow( } export function resolveSchema( - _builder: SchemaQueryBuilder, + _builder: QuerySource, schema: any ): ObjectSchemaBuilder { return typeof schema === 'function' ? schema() : schema; } export function findPrimaryKeyColumn( - _builder: SchemaQueryBuilder, + _builder: QuerySource, schema: ObjectSchemaBuilder ): string { const pk = getPrimaryKeyColumns(schema); @@ -473,7 +471,7 @@ export function findPrimaryKeyColumn( return 'id'; } -export function resolvePkColumns(builder: SchemaQueryBuilder): { +export function resolvePkColumns(builder: QuerySource): { propertyKeys: readonly string[]; columnNames: readonly string[]; } { @@ -488,7 +486,7 @@ export function resolvePkColumns(builder: SchemaQueryBuilder): { } export function getEffectiveBaseQuery( - builder: SchemaQueryBuilder + builder: QuerySource ): Knex.QueryBuilder { const state = getState(builder); let effectiveBase = state.baseQuery; @@ -516,7 +514,7 @@ export function getEffectiveBaseQuery( effectiveBase = effectiveBase.clone(); cloned = true; } - const proxy = new (SchemaQueryBuilderCtor as any)( + const proxy = new (QuerySourceCtor as any)( state.knex, state.localSchema, effectiveBase @@ -531,7 +529,7 @@ export function getEffectiveBaseQuery( } export function buildJoinOne( - builder: SchemaQueryBuilder, + builder: QuerySource, resultQuery: Knex.QueryBuilder, spec: ValidatedSpec & { type: 'one' }, relationAlias: string @@ -592,7 +590,7 @@ export function buildJoinOne( } export function buildJoinMany( - builder: SchemaQueryBuilder, + builder: QuerySource, resultQuery: Knex.QueryBuilder, spec: ValidatedSpec & { type: 'many' }, relationAlias: string, @@ -738,9 +736,7 @@ export function buildJoinMany( }); } -export function buildQuery( - builder: SchemaQueryBuilder -): Knex.QueryBuilder { +export function buildQuery(builder: QuerySource): Knex.QueryBuilder { const state = getState(builder); const effectiveBase = getEffectiveBaseQuery(builder); @@ -853,9 +849,7 @@ export function buildQuery( return resultQuery; } -export function getQuery( - builder: SchemaQueryBuilder -): Knex.QueryBuilder { +export function getQuery(builder: QuerySource): Knex.QueryBuilder { const state = getState(builder); if (!state.cachedBuiltQuery) { state.cachedBuiltQuery = buildQuery(builder); @@ -864,7 +858,7 @@ export function getQuery( } export function mapRow( - builder: SchemaQueryBuilder, + builder: QuerySource, row: Record ): Record { if (!row) return row; @@ -887,11 +881,11 @@ export function mapRow( } } - return result; + return state.decodeRow ? state.decodeRow(result) : result; } export function cleanAndMapRow( - builder: SchemaQueryBuilder, + builder: QuerySource, row: Record ): Record { const state = getState(builder); @@ -911,7 +905,7 @@ export function cleanAndMapRow( } export function mapObjectToColumns( - builder: SchemaQueryBuilder, + builder: QuerySource, obj: Record ): Record { const state = getState(builder); @@ -931,14 +925,14 @@ export function mapObjectToColumns( } export function mapRecordToColumns( - builder: SchemaQueryBuilder, + builder: QuerySource, record: Record ): Record { return mapObjectToColumns(builder, record); } export function resolveColumnArg( - builder: SchemaQueryBuilder, + builder: QuerySource, col: any ): string | Knex.Raw { if (typeof col === 'string') { @@ -951,7 +945,7 @@ export function resolveColumnArg( } export function isColumnAccessor( - builder: SchemaQueryBuilder, + builder: QuerySource, fn: Function ): boolean { const state = getState(builder); @@ -972,7 +966,7 @@ export function isColumnAccessor( } export function assertNotProjection( - builder: SchemaQueryBuilder, + builder: QuerySource, method: string ): void { const state = getState(builder); @@ -986,7 +980,7 @@ export function assertNotProjection( } export function assertNotExplicitSelect( - builder: SchemaQueryBuilder, + builder: QuerySource, method: string ): void { const state = getState(builder); diff --git a/libs/knex-schema/src/operations/insert.ts b/libs/knex-schema/src/operations/insert.ts index 83b18033..8595f497 100644 --- a/libs/knex-schema/src/operations/insert.ts +++ b/libs/knex-schema/src/operations/insert.ts @@ -4,7 +4,8 @@ import type { InferType, ObjectSchemaBuilder } from '@cleverbrush/schema'; import type { Knex } from 'knex'; import { buildColumnMap, resolveColumnRef } from '../columns.js'; import { getTableName } from '../extension.js'; -import type { SchemaQueryBuilder } from '../SchemaQueryBuilder.js'; +import type { QuerySource } from '../QuerySource.js'; +import { returningReadColumns } from '../read-schema.js'; import type { ColumnRef, InsertType } from '../types.js'; import { getTimestamps, @@ -82,12 +83,13 @@ export interface OnConflictMergeOptions { } /** - * Configure one-row conflict handling after SchemaQueryBuilder.onConflict(); merge()/ignore() execute the insert. + * Configure one-row conflict handling after QuerySource.onConflict(); merge()/ignore() execute the insert. */ export class OnConflictBuilder { readonly #knex: Knex; readonly #localSchema: TLocalSchema; readonly #conflictColumns: string[]; + readonly #decodeRow?: (row: Record) => Record; /** * Create conflict handling for a schema and resolved conflict columns. @@ -96,12 +98,13 @@ export class OnConflictBuilder { constructor( knex: Knex, localSchema: TLocalSchema, - _parent: SchemaQueryBuilder, + _parent: QuerySource, conflictColumns: string[] ) { this.#knex = knex; this.#localSchema = localSchema; this.#conflictColumns = conflictColumns; + this.#decodeRow = getState(_parent).decodeRow; } /** @@ -221,7 +224,9 @@ export class OnConflictBuilder { options?.where?.(qb as unknown as Knex.QueryBuilder, helpers); } - const rows = await (qb as any).returning('*'); + const rows = await (qb as any).returning( + returningReadColumns(this.#knex, this.#localSchema) + ); if (!rows || rows.length === 0) return undefined; const { colToProp } = buildColumnMap(this.#localSchema as any); @@ -229,7 +234,7 @@ export class OnConflictBuilder { for (const [col, val] of Object.entries(rows[0])) { result[colToProp.get(col) ?? col] = val; } - return result as TResult; + return (this.#decodeRow ? this.#decodeRow(result) : result) as TResult; } #createMergeHelpers(): OnConflictMergeHelpers { @@ -273,7 +278,7 @@ function isMergeOptions( // --------------------------------------------------------------------------- export async function insertImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, data: InsertType ): Promise { const state = getState(builder); @@ -299,7 +304,7 @@ export async function insertImpl( const [row] = await state .knex(state.tableName) .insert(mapped) - .returning('*'); + .returning(returningReadColumns(state.knex, state.localSchema)); const result = mapRow(builder, row); const afterHooks = @@ -314,7 +319,7 @@ export async function insertImpl( } export async function insertManyImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, data: InsertType[] ): Promise { const state = getState(builder); @@ -341,7 +346,7 @@ export async function insertManyImpl( const rows = await state .knex(state.tableName) .insert(mapped) - .returning('*'); + .returning(returningReadColumns(state.knex, state.localSchema)); const results = rows.map((row: any) => mapRow(builder, row)); const afterHooks = @@ -358,7 +363,7 @@ export async function insertManyImpl( } export function onConflictImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, ...conflictColumns: ColumnRef[] ): OnConflictBuilder { const state = getState(builder); @@ -369,7 +374,7 @@ export function onConflictImpl( } export async function upsertImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, data: InsertType, opts: { conflictColumns: ColumnRef[]; @@ -395,12 +400,14 @@ export async function upsertImpl( (qb as any).merge(); } - const [row] = await (qb as any).returning('*'); + const [row] = await (qb as any).returning( + returningReadColumns(state.knex, state.localSchema) + ); return mapRow(builder, row); } export async function bulkInsertImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, rows: InsertType[], opts?: { chunkSize?: number; @@ -485,7 +492,9 @@ export async function bulkInsertImpl( } } - const inserted: any[] = await qb.returning('*'); + const inserted: any[] = await qb.returning( + returningReadColumns(state.knex, state.localSchema) + ); for (const row of inserted) { const mappedRow = mapRow(builder, row); for (const hook of afterHooks) { @@ -499,7 +508,7 @@ export async function bulkInsertImpl( } export async function bulkUpsertImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, rows: InsertType[], opts: { conflictColumns: ColumnRef[]; diff --git a/libs/knex-schema/src/operations/join.ts b/libs/knex-schema/src/operations/join.ts index b1390aae..617107c7 100644 --- a/libs/knex-schema/src/operations/join.ts +++ b/libs/knex-schema/src/operations/join.ts @@ -3,7 +3,7 @@ import type { Knex } from 'knex'; import { resolveColumnRef } from '../columns.js'; import { getTableName } from '../extension.js'; -import type { SchemaQueryBuilder } from '../SchemaQueryBuilder.js'; +import type { QuerySource } from '../QuerySource.js'; import type { JoinManySpec, JoinOneSpec, @@ -17,7 +17,7 @@ import { } from '../validate.js'; import { findPrimaryKeyColumn, - getSchemaQueryBuilderCtor, + getQuerySourceCtor, getVariantConfig, invalidateCache, resolveSchema @@ -25,7 +25,7 @@ import { import { getState } from './state.js'; export function joinOneImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, spec: JoinOneSpec ): any { const state = getState(builder); @@ -37,7 +37,7 @@ export function joinOneImpl( } export function joinManyImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, spec: JoinManySpec ): any { const state = getState(builder); @@ -49,9 +49,9 @@ export function joinManyImpl( } export function includeImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, relationName: string, - customize?: (q: SchemaQueryBuilder) => void + customize?: (q: QuerySource) => void ): any { const state = getState(builder); invalidateCache(builder); @@ -110,7 +110,7 @@ export function includeImpl( const foreignQuery1: Knex.QueryBuilder = state.knex(foreignTableName); if (customize) { - const SQB = getSchemaQueryBuilderCtor(); + const SQB = getQuerySourceCtor(); const proxy = new SQB(state.knex, foreignSchema, foreignQuery1); customize(proxy); } @@ -138,7 +138,7 @@ export function includeImpl( const foreignQuery2: Knex.QueryBuilder = state.knex(foreignTableName); if (customize) { - const SQB = getSchemaQueryBuilderCtor(); + const SQB = getQuerySourceCtor(); const proxy = new SQB(state.knex, foreignSchema, foreignQuery2); customize(proxy); } @@ -167,7 +167,7 @@ export function includeImpl( const foreignQuery3: Knex.QueryBuilder = state.knex(foreignTableName); if (customize) { - const SQB = getSchemaQueryBuilderCtor(); + const SQB = getQuerySourceCtor(); const proxy = new SQB(state.knex, foreignSchema, foreignQuery3); customize(proxy); } @@ -201,7 +201,7 @@ export function includeImpl( ); if (customize) { - const SQB = getSchemaQueryBuilderCtor(); + const SQB = getQuerySourceCtor(); const proxy = new SQB(state.knex, foreignSchema, foreignQuery); customize(proxy); } @@ -221,10 +221,10 @@ export function includeImpl( } export function includeVariantImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, variantKey: string, relationName: string, - customize?: (q: SchemaQueryBuilder) => void + customize?: (q: QuerySource) => void ): any { const state = getState(builder); invalidateCache(builder); diff --git a/libs/knex-schema/src/operations/pagination.ts b/libs/knex-schema/src/operations/pagination.ts index 5968b16b..b3ad95d6 100644 --- a/libs/knex-schema/src/operations/pagination.ts +++ b/libs/knex-schema/src/operations/pagination.ts @@ -1,7 +1,7 @@ // @cleverbrush/knex-schema — Pagination (offset & cursor-based) import { resolvePropertyKey } from '../columns.js'; -import type { SchemaQueryBuilder } from '../SchemaQueryBuilder.js'; +import type { QuerySource } from '../QuerySource.js'; import type { ColumnRef, CursorPaginationResult, @@ -16,20 +16,14 @@ import { import { getState } from './state.js'; import { orderByImpl, whereImpl } from './where.js'; -export function limitImpl( - builder: SchemaQueryBuilder, - n: number -): any { +export function limitImpl(builder: QuerySource, n: number): any { const state = getState(builder); invalidateCache(builder); state.baseQuery.limit(n); return builder; } -export function offsetImpl( - builder: SchemaQueryBuilder, - n: number -): any { +export function offsetImpl(builder: QuerySource, n: number): any { const state = getState(builder); invalidateCache(builder); state.baseQuery.offset(n); @@ -37,7 +31,7 @@ export function offsetImpl( } export async function paginateImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, opts: { page: number; pageSize: number; @@ -74,7 +68,7 @@ export async function paginateImpl( } export async function paginateAfterImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, opts: { cursor?: any; limit: number; @@ -112,7 +106,7 @@ export async function paginateAfterImpl( } export async function executeImpl( - builder: SchemaQueryBuilder + builder: QuerySource ): Promise { const query = getQuery(builder); const rows = await query; diff --git a/libs/knex-schema/src/operations/select.ts b/libs/knex-schema/src/operations/select.ts index 223631bc..5c62e018 100644 --- a/libs/knex-schema/src/operations/select.ts +++ b/libs/knex-schema/src/operations/select.ts @@ -8,7 +8,7 @@ import type { Knex } from 'knex'; import { buildColumnMap } from '../columns.js'; import { compileAggregate, isAggregate } from '../expressions.js'; import { getProjections } from '../extension.js'; -import type { SchemaQueryBuilder } from '../SchemaQueryBuilder.js'; +import type { QuerySource } from '../QuerySource.js'; import type { ColumnRef } from '../types.js'; import { assertNotExplicitSelect, @@ -20,7 +20,7 @@ import { import { getState } from './state.js'; export function selectImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, ...args: unknown[] ): any { const state = getState(builder); @@ -104,7 +104,7 @@ export function selectImpl( } export function distinctImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, ...columns: (ColumnRef | Knex.Raw)[] ): any { invalidateCache(builder); @@ -114,7 +114,7 @@ export function distinctImpl( } export function countImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, column?: ColumnRef | Knex.Raw ): any { const state = getState(builder); @@ -130,7 +130,7 @@ export function countImpl( } export function countDistinctImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, column?: ColumnRef | Knex.Raw ): any { const state = getState(builder); @@ -148,7 +148,7 @@ export function countDistinctImpl( } export function minImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, column: ColumnRef | Knex.Raw ): any { const state = getState(builder); @@ -160,7 +160,7 @@ export function minImpl( } export function maxImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, column: ColumnRef | Knex.Raw ): any { const state = getState(builder); @@ -172,7 +172,7 @@ export function maxImpl( } export function sumImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, column: ColumnRef | Knex.Raw ): any { const state = getState(builder); @@ -184,7 +184,7 @@ export function sumImpl( } export function avgImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, column: ColumnRef | Knex.Raw ): any { const state = getState(builder); @@ -196,7 +196,7 @@ export function avgImpl( } export function selectRawImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, sql: string, bindings?: any[] ): any { @@ -211,7 +211,7 @@ export function selectRawImpl( } export function projectedImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, name: string ): any { const state = getState(builder); @@ -245,10 +245,7 @@ export function projectedImpl( return builder; } -export function scopedImpl( - builder: SchemaQueryBuilder, - name: string -): any { +export function scopedImpl(builder: QuerySource, name: string): any { const state = getState(builder); invalidateCache(builder); const scopes = (state.localSchema as any).getExtension?.('scopes') as @@ -264,7 +261,7 @@ export function scopedImpl( return builder; } -export function unscopedImpl(builder: SchemaQueryBuilder): any { +export function unscopedImpl(builder: QuerySource): any { const state = getState(builder); invalidateCache(builder); state.skipDefaultScope = true; diff --git a/libs/knex-schema/src/operations/state.ts b/libs/knex-schema/src/operations/state.ts index c71645fa..42c8a211 100644 --- a/libs/knex-schema/src/operations/state.ts +++ b/libs/knex-schema/src/operations/state.ts @@ -1,8 +1,8 @@ -// @cleverbrush/knex-schema — Shared mutable state store for SchemaQueryBuilder +// @cleverbrush/knex-schema — Shared mutable state store for QuerySource import type { ObjectSchemaBuilder } from '@cleverbrush/schema'; import type { Knex } from 'knex'; -import type { SchemaQueryBuilder } from '../SchemaQueryBuilder.js'; +import type { QuerySource } from '../QuerySource.js'; import type { ResolvedVariantConfig, ValidatedSpec, @@ -10,6 +10,8 @@ import type { } from '../types.js'; export interface QueryBuilderState { + /** Decode write-returning property rows before lifecycle hooks observe them. */ + decodeRow?: (row: Record) => Record; /** Raw callbacks have no statically declared result shape for opt-in reads. */ opaqueReadShape?: boolean; knex: Knex; @@ -31,6 +33,9 @@ export interface QueryBuilderState { projectionDecoders: Record unknown>; hiddenColumns: Set; + /** Immutable public query snapshot supplied to observational delete hooks. */ + hookQuery?: unknown; + /** When true, soft-delete filter is NOT applied. */ includeDeleted: boolean; @@ -53,29 +58,27 @@ export interface QueryBuilderState { variantRelationIncludes: Array<{ variantKey: string; relationName: string; - customize?: (q: SchemaQueryBuilder) => void; + customize?: (q: QuerySource) => void; }>; /** Memoized result of buildQuery(). null = needs rebuild. */ cachedBuiltQuery: Knex.QueryBuilder | null; } -const STATE = new WeakMap, QueryBuilderState>(); +const STATE = new WeakMap, QueryBuilderState>(); -export function getState( - builder: SchemaQueryBuilder -): QueryBuilderState { +export function getState(builder: QuerySource): QueryBuilderState { const s = STATE.get(builder); if (!s) { throw new Error( - 'SchemaQueryBuilder state not found — builder was not properly initialized' + 'QuerySource state not found — builder was not properly initialized' ); } return s; } export function setState( - builder: SchemaQueryBuilder, + builder: QuerySource, state: QueryBuilderState ): void { STATE.set(builder, state); diff --git a/libs/knex-schema/src/operations/update.ts b/libs/knex-schema/src/operations/update.ts index ba89037a..c65461de 100644 --- a/libs/knex-schema/src/operations/update.ts +++ b/libs/knex-schema/src/operations/update.ts @@ -3,7 +3,8 @@ import type { InferType } from '@cleverbrush/schema'; import type { Knex } from 'knex'; import { buildColumnMap } from '../columns.js'; -import type { SchemaQueryBuilder } from '../SchemaQueryBuilder.js'; +import type { QuerySource } from '../QuerySource.js'; +import { returningReadColumns } from '../read-schema.js'; import { getTimestamps, mapObjectToColumns, @@ -13,7 +14,7 @@ import { import { getState } from './state.js'; export async function updateImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, data: Partial> ): Promise { const state = getState(builder); @@ -35,12 +36,14 @@ export async function updateImpl( mapped[timestamps.updatedAt] = state.knex.fn.now(); } - const rows = await state.baseQuery.update(mapped).returning('*'); + const rows = await state.baseQuery + .update(mapped) + .returning(returningReadColumns(state.knex, state.localSchema)); return rows.map((row: any) => mapRow(builder, row)); } export async function bulkUpdateImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, updates: ReadonlyArray<{ where: Partial>; set: Partial>; @@ -124,7 +127,8 @@ export async function bulkUpdateImpl( ] as any); } - let qb: any = knex(state.tableName).update(updateExpr); + // Preserve the immutable caller's captured visibility and predicates. + let qb: any = state.baseQuery.clone().update(updateExpr); if (pk.columnNames.length === 1) { qb = qb.whereIn( pk.columnNames[0], diff --git a/libs/knex-schema/src/operations/where.ts b/libs/knex-schema/src/operations/where.ts index 51c77f74..2163c113 100644 --- a/libs/knex-schema/src/operations/where.ts +++ b/libs/knex-schema/src/operations/where.ts @@ -1,7 +1,7 @@ // @cleverbrush/knex-schema — WHERE / ORDER BY / GROUP BY / HAVING operations import type { Knex } from 'knex'; -import type { SchemaQueryBuilder } from '../SchemaQueryBuilder.js'; +import type { QuerySource } from '../QuerySource.js'; import type { ColumnRef } from '../types.js'; import { invalidateCache, @@ -13,7 +13,7 @@ import { import { getState } from './state.js'; export function whereImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, columnOrRaw: any, ...args: any[] ): any { @@ -42,7 +42,7 @@ export function whereImpl( } export function andWhereImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, columnOrRaw: any, ...args: any[] ): any { @@ -71,7 +71,7 @@ export function andWhereImpl( } export function orWhereImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, columnOrRaw: any, ...args: any[] ): any { @@ -100,7 +100,7 @@ export function orWhereImpl( } export function whereNotImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, columnOrRaw: any, ...args: any[] ): any { @@ -129,7 +129,7 @@ export function whereNotImpl( } export function whereInImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, column: ColumnRef, values: readonly any[] | Knex.QueryBuilder ): any { @@ -143,7 +143,7 @@ export function whereInImpl( } export function whereNotInImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, column: ColumnRef, values: readonly any[] | Knex.QueryBuilder ): any { @@ -157,7 +157,7 @@ export function whereNotInImpl( } export function orWhereInImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, column: ColumnRef, values: readonly any[] | Knex.QueryBuilder ): any { @@ -171,7 +171,7 @@ export function orWhereInImpl( } export function orWhereNotInImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, column: ColumnRef, values: readonly any[] | Knex.QueryBuilder ): any { @@ -185,7 +185,7 @@ export function orWhereNotInImpl( } export function whereNullImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, column: ColumnRef ): any { const state = getState(builder); @@ -197,7 +197,7 @@ export function whereNullImpl( } export function whereNotNullImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, column: ColumnRef ): any { const state = getState(builder); @@ -209,7 +209,7 @@ export function whereNotNullImpl( } export function orWhereNullImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, column: ColumnRef ): any { const state = getState(builder); @@ -221,7 +221,7 @@ export function orWhereNullImpl( } export function orWhereNotNullImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, column: ColumnRef ): any { const state = getState(builder); @@ -233,7 +233,7 @@ export function orWhereNotNullImpl( } export function whereBetweenImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, column: ColumnRef, range: readonly [any, any] ): any { @@ -247,7 +247,7 @@ export function whereBetweenImpl( } export function whereNotBetweenImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, column: ColumnRef, range: readonly [any, any] ): any { @@ -261,7 +261,7 @@ export function whereNotBetweenImpl( } export function whereLikeImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, column: ColumnRef, value: string ): any { @@ -275,7 +275,7 @@ export function whereLikeImpl( } export function whereILikeImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, column: ColumnRef, value: string ): any { @@ -289,7 +289,7 @@ export function whereILikeImpl( } export function whereRawImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, sql: string, ...bindings: any[] ): any { @@ -300,7 +300,7 @@ export function whereRawImpl( } export function whereExistsImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, callback: Knex.QueryCallback | Knex.QueryBuilder ): any { const state = getState(builder); @@ -310,7 +310,7 @@ export function whereExistsImpl( } export function whereNotExistsImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, callback: Knex.QueryCallback | Knex.QueryBuilder ): any { const state = getState(builder); @@ -320,7 +320,7 @@ export function whereNotExistsImpl( } export function whereJsonPathImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, column: ColumnRef, path: string, operator?: string, @@ -356,7 +356,7 @@ export function whereJsonPathImpl( } export function orderByImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, column: ColumnRef | Knex.Raw, direction?: 'asc' | 'desc' ): any { @@ -368,7 +368,7 @@ export function orderByImpl( } export function orderByRawImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, sql: string, ...bindings: any[] ): any { @@ -379,7 +379,7 @@ export function orderByRawImpl( } export function groupByImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, ...columns: (ColumnRef | Knex.Raw)[] ): any { const state = getState(builder); @@ -390,7 +390,7 @@ export function groupByImpl( } export function groupByRawImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, sql: string, ...bindings: any[] ): any { @@ -401,7 +401,7 @@ export function groupByRawImpl( } export function havingImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, column: ColumnRef | Knex.Raw, operator: string, value: any @@ -414,7 +414,7 @@ export function havingImpl( } export function havingRawImpl( - builder: SchemaQueryBuilder, + builder: QuerySource, sql: string, ...bindings: any[] ): any { diff --git a/libs/knex-schema/src/orm-extensions.test.ts b/libs/knex-schema/src/orm-extensions.test.ts index bc7d113e..f946a764 100644 --- a/libs/knex-schema/src/orm-extensions.test.ts +++ b/libs/knex-schema/src/orm-extensions.test.ts @@ -76,12 +76,14 @@ describe('SchemaQueryBuilder.select(selector) — DTO projection', () => { query(knex, User).select(_t => ({ bogus: 'not-a-descriptor' as any })) - ).toThrow(/property descriptor/); + ).toThrow(/does not belong/); }); it('still supports the existing column-list overload', () => { const sql = query(knex, User).select('id', 'name').toQuery(); - expect(sql).toContain('select "id", "name"'); + expect(sql).toMatch( + /select "__schema_read_\d+"\."id" as "id", "__schema_read_\d+"\."name" as "name"/ + ); }); }); diff --git a/libs/knex-schema/src/orm.test.ts b/libs/knex-schema/src/orm.test.ts index 5d9e00b8..174df58a 100644 --- a/libs/knex-schema/src/orm.test.ts +++ b/libs/knex-schema/src/orm.test.ts @@ -3,6 +3,7 @@ import Knex from 'knex'; import { afterAll, describe, expect, expectTypeOf, it, vi } from 'vitest'; import { + array, boolean, date, defineEntity, @@ -317,7 +318,9 @@ describe('DDL extensions', () => { }); it('string.jsonb() shorthand stores columnType', () => { - const schema = object({ meta: string().jsonb() }).hasTableName('test'); + const schema = object({ + meta: object({ status: string() }).jsonb() + }).hasTableName('test'); const ext = (schema.introspect() as any).properties.meta.introspect() .extensions; expect(ext.columnType).toBe('jsonb'); @@ -484,7 +487,7 @@ describe('generateCreateTable', () => { id: number().primaryKey(), count: number().bigint(), externalId: string().asUuid(), - meta: string().jsonb(), + meta: object({ status: string() }).jsonb(), price: number().decimal(10, 2), createdAt: date().timestamptz(), birthDate: date().dateOnly() @@ -599,55 +602,64 @@ describe('relation metadata', () => { // ═══════════════════════════════════════════════════════════════════════════ describe('include()', () => { - const PostWithRelations = Post.belongsTo('author', { - schema: User, - foreignKey: (t: any) => t.authorId - }); + const PostWithRelations = defineEntity( + Post.addProp('author', User.optional()) + ).belongsTo( + t => t.author, + t => t.authorId, + u => u.id + ).schema; it('include belongsTo generates joinOne SQL', () => { const sql = query(knex, PostWithRelations).include('author').toQuery(); - expect(sql).toContain('originalQuery'); + expect(sql).toContain('to_jsonb'); expect(sql).toContain('"users"'); expect(sql).toContain('"author"'); }); it('include category belongsTo generates joinOne SQL', () => { - const PostWithCategory = Post.belongsTo('category', { - schema: Category, - foreignKey: (t: any) => t.categoryId - }); + const PostWithCategory = defineEntity( + Post.addProp('category', Category.optional()) + ).belongsTo( + t => t.category, + t => t.categoryId, + c => c.id + ).schema; const sql = query(knex, PostWithCategory).include('category').toQuery(); - expect(sql).toContain('originalQuery'); + expect(sql).toContain('to_jsonb'); expect(sql).toContain('"categories"'); expect(sql).toContain('"category"'); }); it('include throws for unknown relation', () => { expect(() => + // @ts-expect-error runtime guard for an undeclared relation query(knex, PostWithRelations).include('nonexistent') - ).toThrow('Unknown relation "nonexistent"'); + ).toThrow('Unknown relation: nonexistent'); }); it('hasMany include generates joinMany SQL', () => { - const UserWithPosts = User.hasMany('posts', { - schema: Post, - foreignKey: (t: any) => t.authorId - }); + const UserWithPosts = defineEntity( + User.addProp('posts', array(Post).optional()) + ).hasMany( + u => u.posts, + u => u.id, + p => p.authorId + ).schema; const sql = query(knex, UserWithPosts).include('posts').toQuery(); - expect(sql).toContain('originalQuery'); + expect(sql).toContain('to_jsonb'); expect(sql).toContain('"posts"'); }); it('belongsToMany include generates pivot join SQL', () => { - const PostWithTags = Post.belongsToMany('tags', { - schema: Tag, - through: { - table: 'post_tags', - localKey: 'post_id', - foreignKey: 'tag_id' - } - }); + const PostWithTags = defineEntity( + Post.addProp('tags', array(Tag).optional()) + ).belongsToMany(p => p.tags, { + table: 'post_tags', + localKey: 'post_id', + foreignKey: 'tag_id' + }).schema; const sql = query(knex, PostWithTags).include('tags').toQuery(); expect(sql).toContain('post_tags'); @@ -698,9 +710,9 @@ describe('soft delete', () => { expect(sql).toContain('"deleted_at" is not null'); }); - it('unscoped() removes soft delete filter', () => { + it('unscoped() preserves the independent soft delete filter', () => { const sql = query(knex, SoftPost).unscoped().toQuery(); - expect(sql).not.toContain('deleted_at'); + expect(sql).toContain('"deleted_at" is null'); }); it('delete() generates UPDATE for soft-delete schemas', () => { @@ -807,7 +819,7 @@ describe('scopes', () => { expect(() => // @ts-expect-error — 'nonexistent' is not a registered scope name query(knex, ScopedPost).scoped('nonexistent') - ).toThrow('Unknown scope "nonexistent"'); + ).toThrow('Unknown scope: nonexistent'); }); it('scoped() type: only registered scope names are accepted', () => { @@ -834,7 +846,7 @@ describe('scopes', () => { it('unscoped() bypasses default scope', () => { const sql = query(knex, ScopedPost).unscoped().toQuery(); - expect(sql).not.toContain('is_active'); + expect(sql).not.toContain('"is_active" = true'); }); }); @@ -912,7 +924,7 @@ describe('projections', () => { expect(() => // @ts-expect-error — 'bogus' is not a registered projection name query(knex, PostSchema).projected('bogus') - ).toThrow('Unknown projection "bogus"'); + ).toThrow('Unknown projection: bogus'); }); it('projected() type: only registered names are accepted', () => { @@ -927,40 +939,46 @@ describe('projections', () => { void _check3; }); - it('projected() after select() throws', () => { - expect(() => - query(knex, PostSchema) - .select(t => t.id) - .projected('summary') - ).toThrow(/projected.*select|select.*projected/i); - }); - - it('select() after projected() throws', () => { - expect(() => - query(knex, PostSchema) - .projected('summary') - .select(t => t.id) - ).toThrow(/select.*projected|projected.*select/i); - }); - - it('count() after projected() throws', () => { - expect(() => - query(knex, PostSchema).projected('summary').count() - ).toThrow(/count.*projected|projected.*count/i); - }); - - it('projected() after count() throws', () => { - expect(() => - query(knex, PostSchema).count().projected('summary') - ).toThrow(/projected.*aggregate|aggregate.*projected/i); - }); - - it('two projected() calls throw', () => { - expect(() => - query(knex, PostSchema).projected('summary').projected('withStatus') - ).toThrow( - /Cannot call .projected\(\).*projected\(|Only one projection/i - ); + it.each([ + [ + 'selected to named', + (q: any) => q.select('id').projected('summary'), + ['id', 'title'] + ], + [ + 'named to selected', + (q: any) => q.projected('summary').select('id'), + ['id'] + ], + [ + 'named to aggregate', + (q: any) => q.projected('summary').count(), + ['count'] + ], + [ + 'aggregate to named', + (q: any) => q.count().projected('summary'), + ['id', 'title'] + ], + [ + 'named to named', + (q: any) => q.projected('summary').projected('withStatus'), + ['id', 'status'] + ] + ])('replaces a projection: %s', (_name, configure, keys) => { + const root = query(knex, PostSchema); + const projected = configure(root); + expect( + Object.keys(projected.rowSchema.introspect().properties) + ).toEqual(keys); + expect(Object.keys(root.rowSchema.introspect().properties)).toEqual([ + 'id', + 'title', + 'body', + 'status', + 'isActive' + ]); + expect(projected).not.toBe(root); }); it('projection() throws on duplicate name', () => { @@ -1035,8 +1053,9 @@ describe('selectRaw', () => { it('selectRaw() adds raw SQL to select clause', () => { const sql = query(knex, Post) .selectRaw( - '*, ts_rank(search_vector, plainto_tsquery(?)) AS rank', - ['search term'] + 'ts_rank(search_vector, plainto_tsquery(?)) AS rank', + ['search term'], + { output: object({ rank: number() }) } ) .toQuery(); expect(sql).toContain('ts_rank'); @@ -1619,21 +1638,25 @@ describe('extension method chaining', () => { describe('Phase 2 query methods', () => { it('whereNotExists generates correct SQL', () => { - const sql = query(knex, User) + const root = query(knex, User); + const sql = root .whereNotExists( knex .queryBuilder() .from('posts') - .where('posts.author_id', knex.raw('users.id')) + .where( + 'posts.author_id', + root.ref(t => t.id) + ) ) .toQuery(); - expect(sql).toContain('where not exists'); + expect(sql).toContain('not exists'); }); it('whereJsonPath generates jsonb_path_query_first SQL', () => { const DataSchema = object({ id: number().primaryKey(), - meta: string().jsonb() + meta: object({ status: string() }).jsonb() }).hasTableName('data_items'); const sql = query(knex, DataSchema) @@ -1646,7 +1669,7 @@ describe('Phase 2 query methods', () => { it('whereJsonPath throws on non-pg client', () => { const DataSchema = object({ id: number().primaryKey(), - meta: string().jsonb() + meta: object({ status: string() }).jsonb() }).hasTableName('data_items'); // Create a separate pg knex instance and override the client config @@ -1669,7 +1692,7 @@ describe('Phase 2 query methods', () => { it('whereJsonPath with @? operator generates existence check SQL', () => { const DataSchema = object({ id: number().primaryKey(), - tags: string().jsonb() + tags: object({ tags: array(string()) }).jsonb() }).hasTableName('data_items'); const sql = query(knex, DataSchema) @@ -1751,16 +1774,16 @@ describe('Phase 2 query methods', () => { expect(typeof q.pluck).toBe('function'); }); - it('toQuery() result is memoized and invalidated by mutations', () => { + it('toQuery() is stable and configuration creates an independent branch', () => { const q = query(knex, User).where(t => t.name, 'Alice'); const sql1 = q.toQuery(); const sql2 = q.toQuery(); // Same instance, same result — should be identical strings expect(sql1).toBe(sql2); - // Mutating invalidates the cache — new SQL includes the extra condition - q.where(t => t.role, 'admin'); - const sql3 = q.toQuery(); + const branch = q.where(t => t.role, 'admin'); + const sql3 = branch.toQuery(); + expect(q.toQuery()).toBe(sql1); expect(sql3).not.toBe(sql1); expect(sql3).toContain('admin'); }); @@ -1832,19 +1855,19 @@ describe('nested object jsonb columns', () => { }).jsonb() }).hasTableName('people'); - it('accessor t => t.address.city generates ->? SQL', () => { + it('accessor t => t.address.city generates typed JSON path SQL', () => { const sql = query(knex, PersonSchema) .where(t => (t as any).address.city, '=', 'NYC') .toQuery(); - expect(sql).toContain('->'); + expect(sql).toContain('#>>'); expect(sql).toContain('city'); }); - it('dotted string path address.city generates ->? SQL', () => { + it('dotted string path address.city generates typed JSON path SQL', () => { const sql = query(knex, PersonSchema) .where('address.city' as any, '=', 'NYC') .toQuery(); - expect(sql).toContain('->'); + expect(sql).toContain('#>>'); expect(sql).toContain('city'); }); @@ -2091,9 +2114,10 @@ describe('withVariants (polymorphic schemas)', () => { it('documents the expected result shape via SQL analysis', () => { // Integration-style: verify the SELECT aliases are generated const sql = query(knex, FileSchema).toQuery(); - // Each CTI column should appear as __v_image__ - expect(sql).toContain('__v_image'); - expect(sql).toContain('__v_document'); + // Branch shapes stay separate inside the union's JSON envelope. + expect(sql).toContain('to_jsonb(__read_branch)'); + expect(sql).toContain('image_file'); + expect(sql).toContain('document_file'); }); }); @@ -2188,23 +2212,23 @@ describe('withVariants — per-variant relations', () => { // SQL generation — includeVariant // ------------------------------------------------------------------------- - it('includeVariant generates LEFT JOIN with namespaced alias', () => { + it('includeVariant generates a CTI body join and a correlated relation', () => { const sql = query(knex, AssetSchema) .includeVariant('licensed', 'owner') .toQuery(); expect(sql.toLowerCase()).toContain('left join'); expect(sql.toLowerCase()).toContain('owners'); - expect(sql).toContain('__v_licensed__rel_owner'); + expect(sql).toContain('as "owner"'); }); - it('includeVariant selects foreign columns with prefix', () => { + it('includeVariant aliases foreign properties inside a nested object', () => { const sql = query(knex, AssetSchema) .includeVariant('licensed', 'owner') .toQuery(); - expect(sql).toContain('__v_licensed__rel_owner__id'); - expect(sql).toContain('__v_licensed__rel_owner__email'); + expect(sql).toContain('"id" as "id"'); + expect(sql).toContain('"email" as "email"'); }); it('includeVariant with projection selects only projected columns', () => { @@ -2233,9 +2257,9 @@ describe('withVariants — per-variant relations', () => { ) .toQuery(); - expect(sql).toContain('__v_licensed__rel_owner__id'); - expect(sql).toContain('__v_licensed__rel_owner__email'); - expect(sql).not.toContain('__v_licensed__rel_owner__name'); + expect(sql).toContain('"id" as "id"'); + expect(sql).toContain('"email" as "email"'); + expect(sql).not.toContain('"name" as "name"'); }); // ------------------------------------------------------------------------- @@ -2243,13 +2267,15 @@ describe('withVariants — per-variant relations', () => { // ------------------------------------------------------------------------- it('include() auto-routes to includeVariant when relation is unambiguous', () => { - const sql = query(knex, AssetSchema).include('owner').toQuery(); - expect(sql).toContain('__v_licensed__rel_owner'); + const sql = (query(knex, AssetSchema) as any) + .include('owner') + .toQuery(); + expect(sql).toContain('as "owner"'); }); it('include() throws for truly unknown relations on polymorphic schema', () => { expect(() => { - query(knex, AssetSchema).include('nonexistent').toQuery(); + (query(knex, AssetSchema) as any).include('nonexistent').toQuery(); }).toThrow(/Unknown relation/); }); @@ -2280,7 +2306,7 @@ describe('withVariants — per-variant relations', () => { }).schema; expect(() => { - query(knex, AmbigSchema).include('owner').toQuery(); + (query(knex, AmbigSchema) as any).include('owner').toQuery(); }).toThrow(/[Aa]mbiguous/); }); @@ -2291,39 +2317,40 @@ describe('withVariants — per-variant relations', () => { it('includeVariant throws for non-polymorphic schema', () => { const Plain = object({ id: number().primaryKey() }).hasTableName('t'); expect(() => { + // @ts-expect-error non-polymorphic queries do not expose variant operations query(knex, Plain).includeVariant('x', 'y'); - }).toThrow(/not polymorphic/); + }).toThrow(/includeVariant is not a function/); }); it('includeVariant throws for unknown variant key', () => { expect(() => { + // @ts-expect-error runtime guard for an unknown variant query(knex, AssetSchema).includeVariant('unknown_variant', 'owner'); - }).toThrow(/unknown variant key/); + }).toThrow(/Unknown variant/); }); it('includeVariant throws for unknown relation name', () => { expect(() => { query(knex, AssetSchema).includeVariant('licensed', 'nonexistent'); - }).toThrow(/unknown relation/); + }).toThrow(/Unknown relation/); }); // ------------------------------------------------------------------------- // Row mapping — Pass 3 (observable via SQL + alias presence) // ------------------------------------------------------------------------- - it('includeVariant generates relation column aliases in SQL (Pass 3 input)', () => { - // Verify all 3 Owner columns are aliased into the query result so that - // Pass 3 of #mapPolymorphicRow can read them. + it('includeVariant includes every related property inside its JSON object', () => { + // All three Owner properties are selected inside the related JSON object. const sql = query(knex, AssetSchema) .includeVariant('licensed', 'owner') .toQuery(); - expect(sql).toContain('__v_licensed__rel_owner__id'); - expect(sql).toContain('__v_licensed__rel_owner__email'); - expect(sql).toContain('__v_licensed__rel_owner__name'); + expect(sql).toContain('"id" as "id"'); + expect(sql).toContain('"email" as "email"'); + expect(sql).toContain('"name" as "name"'); }); - it('STI variant with includeVariant generates correct ON discriminator gate', () => { + it('STI relation is confined to the matching discriminator branch', () => { // STI variant: no separate table alias, gate uses base table discriminator const StiSchema = defineEntity(AssetBase) .discriminator('kind') @@ -2344,7 +2371,7 @@ describe('withVariants — per-variant relations', () => { // Should join owners and gate ON discriminator = 'free' expect(sql.toLowerCase()).toContain('owners'); - expect(sql).toContain('__v_free__rel_owner'); + expect(sql).toContain('as "owner"'); expect(sql).toContain('free'); }); }); @@ -2358,7 +2385,7 @@ describe('joinOne / joinMany validation errors', () => { foreignColumn: (t: any) => t.authorId, as: '' as any }) - ).toThrow('as must be a non-empty string'); + ).toThrow(/non-empty/); }); it('joinMany throws when as is missing', () => { @@ -2369,7 +2396,7 @@ describe('joinOne / joinMany validation errors', () => { foreignColumn: (t: any) => t.authorId, as: '' as any }) - ).toThrow('as must be a non-empty string'); + ).toThrow(/non-empty/); }); it('joinOne throws when mappers is not an object', () => { @@ -2379,8 +2406,9 @@ describe('joinOne / joinMany validation errors', () => { localColumn: (t: any) => t.id, foreignColumn: (t: any) => t.authorId, as: 'posts', + // @ts-expect-error removed raw mapper escape cannot supply a row schema mappers: 'invalid' as any }) - ).toThrow('mappers must be an object'); + ).toThrow(/typed child customizer/); }); }); diff --git a/libs/knex-schema/src/public-api-docs.test.ts b/libs/knex-schema/src/public-api-docs.test.ts index 506bc058..bc3039e4 100644 --- a/libs/knex-schema/src/public-api-docs.test.ts +++ b/libs/knex-schema/src/public-api-docs.test.ts @@ -109,7 +109,7 @@ describe('published query/ORM API documentation', () => { it('documents aggregate expressions, bound factory overloads and cursor options', () => { for (const [path, name] of [ ['../dist/expressions.d.ts', 'aggregate'], - ['../dist/SchemaQueryBuilder.d.ts', 'BoundQuery'], + ['../dist/query.d.ts', 'BoundQuery'], [ '../dist/operations/composite-cursor.d.ts', 'CompositeCursorOptions' diff --git a/libs/knex-schema/src/query-scope.ts b/libs/knex-schema/src/query-scope.ts new file mode 100644 index 00000000..1e815379 --- /dev/null +++ b/libs/knex-schema/src/query-scope.ts @@ -0,0 +1,24 @@ +import type { Knex } from 'knex'; +import type { ReadRelations } from './read-entity.js'; +import type { + ReadPredicateSelector, + ReadPredicates +} from './read-predicates.js'; +import type { ReadObject } from './read-schema.js'; +import type { ReadColumns } from './SchemaQueryBuilder.js'; + +/** Shape-preserving immutable API supplied to named and default scopes. */ +export interface QueryScope + extends ReadPredicates>> { + /** Append native column ordering. Return the new query. */ + orderBy( + column: ReadPredicateSelector>>, + direction?: 'asc' | 'desc' + ): this; + /** Append trusted ordering with captured bindings. */ + orderByRaw(sql: string, bindings?: readonly Knex.RawBinding[]): this; + /** Limit a new query without changing its row shape. */ + limit(count: number): this; + /** Offset a new query without changing its row shape. */ + offset(count: number): this; +} diff --git a/libs/knex-schema/src/query.ts b/libs/knex-schema/src/query.ts new file mode 100644 index 00000000..7c5e517e --- /dev/null +++ b/libs/knex-schema/src/query.ts @@ -0,0 +1,74 @@ +import type { Knex } from 'knex'; +import { AliasedQueryBuilder } from './AliasedQueryBuilder.js'; +import { + AliasedQuerySource, + type AliasTables, + isTableAlias, + type TableAlias +} from './aliased-query.js'; +import { getTableName } from './extension.js'; +import { QuerySource } from './QuerySource.js'; +import type { ReadObject } from './read-schema.js'; +import { + createReadQuery, + type SchemaAwareQuery +} from './SchemaQueryBuilder.js'; + +// Register the private SQL/write planner before creating relation queries. +void QuerySource; + +/** Create an immutable, lazy query with an automatically inferred row schema. */ +export function query( + knex: Knex, + schema: TableAlias +): AliasedQueryBuilder>; +/** Create an immutable table or polymorphic query. Raw output requires apply(..., { output }). */ +export function query( + knex: Knex, + schema: S +): SchemaAwareQuery; +export function query( + knex: Knex, + schema: S | TableAlias, + ...unsupported: unknown[] +): SchemaAwareQuery | AliasedQueryBuilder> { + if (unsupported.length) + throw new TypeError( + 'Raw query sources are not supported; use apply(..., { output })' + ); + if (isTableAlias(schema)) + return new AliasedQueryBuilder(new AliasedQuerySource(knex, schema)); + return createReadQuery(knex, schema, knex(getTableName(schema))); +} + +/** Connection-bound query factory. Query configuration is lazy and immutable. */ +export interface BoundQuery { + /** Start a flat multi-table query. Select its output before execution. */ + ( + schema: TableAlias + ): AliasedQueryBuilder>; + /** Start a schema-backed table query. */ + (schema: S): SchemaAwareQuery; + /** Reuse an existing transaction without committing it. */ + withTransaction(trx: Knex.Transaction): BoundQuery; + /** Run work atomically with a transaction-bound factory. */ + transaction(callback: (db: BoundQuery) => Promise): Promise; +} + +/** Bind query() to a connection while preserving schema and alias inference. */ +export function createQuery(knex: Knex): BoundQuery { + return Object.assign( + (schema: any, ...unsupported: unknown[]) => { + if (unsupported.length) + throw new TypeError( + 'Raw query sources are not supported; use apply(..., { output })' + ); + return query(knex, schema); + }, + { + withTransaction: (trx: Knex.Transaction) => createQuery(trx), + transaction: (callback: (db: BoundQuery) => Promise) => + knex.transaction(trx => callback(createQuery(trx))) + } + ) as BoundQuery; +} diff --git a/libs/knex-schema/src/raw.test.ts b/libs/knex-schema/src/raw.test.ts index 087f0f09..10019a40 100644 --- a/libs/knex-schema/src/raw.test.ts +++ b/libs/knex-schema/src/raw.test.ts @@ -1,123 +1,115 @@ -// @cleverbrush/knex-schema — rawQuery() tests - -import Knex from 'knex'; -import { afterAll, describe, expect, it, vi } from 'vitest'; +import Knex, { type Knex as Connection } from 'knex'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { number, object, string } from './index.js'; import { rawQuery } from './raw.js'; -const Post = object({ - id: number(), - title: string(), - authorId: number().hasColumnName('author_id'), - createdAt: string().hasColumnName('created_at') -}).hasTableName('posts'); - -const knex = Knex({ client: 'pg' }); - -afterAll(async () => { - await knex.destroy(); -}); - -describe('rawQuery', () => { - it('runs a raw SQL string and maps column names back to property names', async () => { - const rawSpy = vi.spyOn(knex, 'raw').mockResolvedValueOnce({ - rows: [ - { id: 1, title: 'Hi', author_id: 7, created_at: '2024-01-01' } - ] - } as any); +// Stub only the driver boundary: raw() must still build real captured SQL. +describe('rawQuery explicit output contract', () => { + let knex: Connection; + let response: unknown[]; + let statements: Array<{ sql: string; bindings: unknown[] }>; + beforeEach(() => { + knex = Knex({ client: 'pg' }); + response = []; + statements = []; + const client = knex.client as any; + client.acquireConnection = async () => ({}); + client.releaseConnection = async () => {}; + client._query = async (_connection: unknown, statement: any) => { + statements.push(statement); + return { rows: response }; + }; + client.processResponse = (result: any) => result.rows; + }); + afterEach(async () => { + await knex.destroy(); + }); + it('parses SQL-aliased properties once, preserving the caller output contract', async () => { + const output = object({ + id: number(), + authorId: number(), + total: number().coerce() + }); + const parse = vi.spyOn(output, 'parse'); + response = [{ id: 1, authorId: 7, total: '12' }]; const rows = await rawQuery( knex, - Post, - 'SELECT * FROM posts WHERE id = ?', - [1] - ); - - expect(rawSpy).toHaveBeenCalledWith( - 'SELECT * FROM posts WHERE id = ?', + output, + 'select id, author_id as "authorId", total from posts where id = ?', [1] ); - expect(rows).toEqual([ - { id: 1, title: 'Hi', authorId: 7, createdAt: '2024-01-01' } - ]); - - rawSpy.mockRestore(); + expect(rows).toEqual([{ id: 1, authorId: 7, total: 12 }]); + expect(parse).toHaveBeenCalledExactlyOnceWith(response[0]); + expect(statements[0].bindings).toEqual([1]); }); - it('defaults bindings to [] when omitted', async () => { - const rawSpy = vi - .spyOn(knex, 'raw') - .mockResolvedValueOnce({ rows: [] } as any); - - await rawQuery(knex, Post, 'SELECT 1'); - - expect(rawSpy).toHaveBeenCalledWith('SELECT 1', []); - - rawSpy.mockRestore(); + it('does not silently remap raw output or accept a missing selected column', async () => { + response = [{ author_id: 7 }]; + await expect( + rawQuery( + knex, + object({ authorId: number() }), + 'select author_id from posts' + ) + ).rejects.toThrow(); }); - it('handles drivers that return an array directly (no .rows wrapper)', async () => { - const rawSpy = vi - .spyOn(knex, 'raw') - .mockResolvedValueOnce([ - { id: 2, title: 'X', author_id: 3, created_at: '2024-02-02' } - ] as any); - - const rows = await rawQuery(knex, Post, 'SELECT *'); - - expect(rows).toEqual([ - { id: 2, title: 'X', authorId: 3, createdAt: '2024-02-02' } - ]); - - rawSpy.mockRestore(); + it('returns an empty result without invoking the row parser', async () => { + const output = object({ id: number() }); + const parse = vi.spyOn(output, 'parse'); + expect(await rawQuery(knex, output, 'select id from posts')).toEqual( + [] + ); + expect(parse).not.toHaveBeenCalled(); + expect(statements[0].bindings).toEqual([]); }); - it('returns [] when rows is not an array', async () => { - const rawSpy = vi - .spyOn(knex, 'raw') - .mockResolvedValueOnce({ rows: { not: 'an array' } } as any); - - const rows = await rawQuery(knex, Post, 'SELECT *'); - expect(rows).toEqual([]); - - rawSpy.mockRestore(); + it('snapshots a caller-owned Knex SELECT without mutating or retaining it', async () => { + response = [{ id: 9 }]; + const source = knex('posts').select('id').where('id', 9); + const pending = rawQuery(knex, object({ id: number() }), source); + source.where('id', 100); + expect(await pending).toEqual([{ id: 9 }]); + expect(statements[0].bindings).toEqual([9]); }); - it('passes through extra columns not in the schema unchanged', async () => { - const rawSpy = vi.spyOn(knex, 'raw').mockResolvedValueOnce({ - rows: [ - { - id: 1, - title: 'Hi', - author_id: 7, - created_at: '2024-01-01', - extra_count: 42 - } - ] - } as any); - - const rows = await rawQuery(knex, Post, 'SELECT *'); - - expect(rows[0]).toMatchObject({ - id: 1, - authorId: 7, - createdAt: '2024-01-01', - extra_count: 42 // unmapped column passes through - }); - - rawSpy.mockRestore(); + it('keeps exact numeric text exact with an explicit text output', async () => { + response = [{ amount: '12345678901234567890.012345' }]; + expect( + await rawQuery( + knex, + object({ amount: string() }), + 'select amount::text as amount from invoices' + ) + ).toEqual(response); }); - it('awaits a Knex query builder directly', async () => { - const result = [ - { id: 9, title: 'Q', author_id: 1, created_at: '2024-03-03' } - ]; - const qb = Promise.resolve(result) as any; + it('rejects invalid raw rows', async () => { + response = [{ id: 'not a number' }]; + await expect( + rawQuery(knex, object({ id: number() }), 'select id from posts') + ).rejects.toThrow(); + }); - const rows = await rawQuery(knex, Post, qb); + it('rejects non-object output and non-SELECT builders before execution', () => { + expect(() => + rawQuery(knex, string() as any, 'select id from posts') + ).toThrow(/object schema/); + expect(() => + rawQuery(knex, object({ id: number() }), knex('posts').delete()) + ).toThrow(/SELECT/); + expect(statements).toEqual([]); + }); - expect(rows).toEqual([ - { id: 9, title: 'Q', authorId: 1, createdAt: '2024-03-03' } - ]); + it('rejects asynchronous output parsers instead of returning promises as rows', async () => { + response = [{ id: 1 }]; + const output = object({ id: number() }); + vi.spyOn(output, 'parse').mockImplementation((async () => ({ + id: 1 + })) as any); + await expect( + rawQuery(knex, output, 'select id from posts') + ).rejects.toThrow(/synchronously/); }); }); diff --git a/libs/knex-schema/src/raw.ts b/libs/knex-schema/src/raw.ts index d2a21dc8..d1550fe3 100644 --- a/libs/knex-schema/src/raw.ts +++ b/libs/knex-schema/src/raw.ts @@ -1,68 +1,43 @@ -// @cleverbrush/knex-schema — Raw query execution with schema result mapping - -import type { InferType, ObjectSchemaBuilder } from '@cleverbrush/schema'; +import type { InferType } from '@cleverbrush/schema'; import type { Knex } from 'knex'; -import { buildColumnMap } from './columns.js'; +import { OpaqueQuery } from './OpaqueQuery.js'; +import { captureReadRaw } from './read-predicates.js'; +import type { ReadObject } from './read-schema.js'; /** - * Execute a raw SQL query or Knex query builder and map the result rows - * through the schema's column→property name mapping. - * - * This is the escape hatch for complex queries that can't be expressed with - * the typed `SchemaQueryBuilder` API. The schema is used only for result - * mapping — column names in the result are converted back to property names. - * Extra columns (not in the schema) are passed through unchanged. - * - * @param knex - A configured Knex instance. - * @param schema - The `ObjectSchemaBuilder` for result mapping. - * @param queryOrSql - A raw SQL string or a `Knex.QueryBuilder`. - * @param bindings - Optional bindings for parameterised SQL queries. - * @returns Mapped result rows. + * Execute a captured raw SELECT with an explicit complete output schema. + * SQL must alias columns to the output property names. Each driver row is parsed + * exactly once; there is no implicit column mapping or entity decoding. Cast + * exact numeric values to text in SQL before a driver can lose their precision. * + * @param knex - Connection or transaction used for execution. + * @param output - Synchronous Framework object schema describing every returned row. + * @param queryOrSql - Trusted SELECT SQL or an independently captured Knex SELECT. + * @param bindings - Bound values for the trusted SQL string. + * @returns Parsed rows, never attached to an ORM identity map. * @example * ```ts - * // Raw SQL with schema result mapping - * const results = await rawQuery(knex, PostSchema, ` - * SELECT p.*, COUNT(c.id) AS comment_count - * FROM posts p - * LEFT JOIN comments c ON c.post_id = p.id - * GROUP BY p.id - * ORDER BY comment_count DESC - * LIMIT ? - * `, [10]); - * - * // Knex query builder as the source - * const subQuery = knex('posts').select('author_id', knex.raw('COUNT(*) as post_count')).groupBy('author_id'); - * const results = await rawQuery(knex, UserSchema, subQuery); + * const totals = await rawQuery(knex, object({ total: string() }), + * 'select sum(amount)::text as total from invoices where owner_id = ?', [ownerId]); * ``` */ -export async function rawQuery< - TSchema extends ObjectSchemaBuilder ->( +export function rawQuery( knex: Knex, - schema: TSchema, + output: S, queryOrSql: string | Knex.QueryBuilder, - bindings?: any[] -): Promise<(InferType & Record)[]> { - let rows: any[]; - - if (typeof queryOrSql === 'string') { - const result = await knex.raw(queryOrSql, bindings ?? []); - rows = result.rows ?? result; - } else { - rows = await queryOrSql; - } - - if (!rows || !Array.isArray(rows)) return []; - - const { colToProp } = buildColumnMap(schema); - - return rows.map(row => { - const mapped: Record = {}; - for (const [key, value] of Object.entries(row)) { - const propName = colToProp.get(key); - mapped[propName ?? key] = value; - } - return mapped; - }) as any; + bindings: readonly Knex.RawBinding[] = [] +): Promise[]> { + const sql = + typeof queryOrSql === 'string' + ? knex + .queryBuilder() + .from( + captureReadRaw(knex, queryOrSql, bindings)().wrap( + '(', + ') as __raw_output' + ) + ) + .select('*') + : queryOrSql; + return OpaqueQuery.capture(knex, sql, { output }).execute(); } diff --git a/libs/knex-schema/src/read-consumer.test.ts b/libs/knex-schema/src/read-consumer.test.ts index d0cc1587..fc612086 100644 --- a/libs/knex-schema/src/read-consumer.test.ts +++ b/libs/knex-schema/src/read-consumer.test.ts @@ -91,7 +91,7 @@ const value: InferType = undefined; const invalidUnit: InferExtensionMetadata['unit'] = 'seconds'; // @ts-expect-error importing database libraries cannot install global storage methods number().bigint(); -export const read = query(knex, Account).withRowSchema().select(a => ({ id: a.id, balance: a.balance })); +export const read = query(knex, Account).select(a => ({ id: a.id, balance: a.balance })); const PublicAccount = object({ id: string(), balance: string().nullable() }); export const convert = mapper().configure(read.rowSchema, PublicAccount, m => m) .getSyncMapper(read.rowSchema, PublicAccount); diff --git a/libs/knex-schema/src/read-graph.test-d.ts b/libs/knex-schema/src/read-graph.test-d.ts index 25ec2482..14de479c 100644 --- a/libs/knex-schema/src/read-graph.test-d.ts +++ b/libs/knex-schema/src/read-graph.test-d.ts @@ -46,14 +46,12 @@ const Album = defineEntity( const knex = Knex({ client: 'pg' }); test('polymorphic read branches retain discriminators and nested relation types', async () => { - const read = query(knex, Asset.schema) - .withRowSchema() - .forVariant('photo', q => - q.include( - r => r.labels, - labels => labels.select(l => ({ text: l.label })) - ) - ); + const read = query(knex, Asset.schema).forVariant('photo', q => + q.include( + r => r.labels, + labels => labels.select(l => ({ text: l.label })) + ) + ); type PhotoRow = InferType; type Expected = { id: number; @@ -80,12 +78,10 @@ test('polymorphic read branches retain discriminators and nested relation types' }); test('polymorphic reads compose inside an ordinary relation', async () => { - const read = query(knex, Album.schema) - .withRowSchema() - .include( - r => r.assets, - assets => assets.forVariant('photo', q => q.include(r => r.labels)) - ); + const read = query(knex, Album.schema).include( + r => r.assets, + assets => assets.forVariant('photo', q => q.include(r => r.labels)) + ); const rows = await read; const asset = rows[0].assets[0]; if (asset.kind === 'photo') diff --git a/libs/knex-schema/src/read-mapping.test.ts b/libs/knex-schema/src/read-mapping.test.ts index c138e1bc..e5dd4333 100644 --- a/libs/knex-schema/src/read-mapping.test.ts +++ b/libs/knex-schema/src/read-mapping.test.ts @@ -25,7 +25,7 @@ describe('query schemas as reusable mapping sources', () => { n => n.taskId ); const read = query(Knex({ client: 'pg' }), Task.schema) - .withRowSchema() + .select(t => ({ id: t.id, amount: t.amount, done: t.done })) .include( r => r.notes, diff --git a/libs/knex-schema/src/read-predicates.test-d.ts b/libs/knex-schema/src/read-predicates.test-d.ts index c3fcec0c..bee3769a 100644 --- a/libs/knex-schema/src/read-predicates.test-d.ts +++ b/libs/knex-schema/src/read-predicates.test-d.ts @@ -14,9 +14,10 @@ const Item = object({ }).hasTableName('items'); test('ordinary reader predicates retain projection types and contextual groups', async () => { - const read = query(knex, Item) - .withRowSchema() - .select(t => ({ name: t.name, amount: t.amount })); + const read = query(knex, Item).select(t => ({ + name: t.name, + amount: t.amount + })); const filtered = read .where(p => p.where(t => t.id, 1).orWhere(t => t.name, 'two')) .whereIn(t => t.id, knex('links').select('item_id')) @@ -54,6 +55,7 @@ test('ordinary reader predicates retain projection types and contextual groups', p.execute(); // @ts-expect-error group lifecycle is owned by the reader p.finish(); + return p; }); // @ts-expect-error unrestricted mutation is still unavailable read.apply(q => q.select('*')); @@ -61,7 +63,7 @@ test('ordinary reader predicates retain projection types and contextual groups', test('aliased selectors preserve joined nullability and exact storage types', async () => { const read = query(knex, alias(Item, 'item')) - .withRowSchema() + .leftJoin(alias(Item, 'parent'), t => eq(t.item.id, t.parent.id)) .select(t => ({ id: t.item.id, diff --git a/libs/knex-schema/src/read-predicates.test.ts b/libs/knex-schema/src/read-predicates.test.ts index 6b00a8d8..c6371f3c 100644 --- a/libs/knex-schema/src/read-predicates.test.ts +++ b/libs/knex-schema/src/read-predicates.test.ts @@ -17,7 +17,7 @@ const Task = object({ completedAt: date().optional().hasColumnName('completed_at') }).hasTableName('tasks'); const knex = Knex({ client: 'pg' }); -const readTask = () => query(knex, Task).withRowSchema(); +const readTask = () => query(knex, Task); describe('shape-preserving read predicates', () => { it('groups AND/OR conditions without mutating the source or its schema', () => { @@ -181,12 +181,12 @@ describe('shape-preserving read predicates', () => { expect(compiled.toNative().sql).toContain('::jsonb ?'); }); - it('runs group callbacks once and seals retained group builders', () => { + it('runs group callbacks once and isolates retained immutable builders', () => { const read = readTask(); let retained!: ReadPredicateBuilder; const callback = vi.fn((p: ReadPredicateBuilder) => { retained = p; - p.where(t => t.id, 1); + const configured = p.where(t => t.id, 1); for (const method of [ 'select', 'join', @@ -198,14 +198,16 @@ describe('shape-preserving read predicates', () => { 'then' ]) expect(method in p).toBe(false); + return configured; }); const filtered = read.where(callback); filtered.toQuery(); filtered.toQuery(); expect(callback).toHaveBeenCalledTimes(1); - expect(() => retained.where(t => t.id, 2)).toThrow(/already closed/); + expect(retained.where(t => t.id, 2)).not.toBe(retained); + expect(filtered.toQuery()).not.toContain('= 2'); expect(filtered.toQuery()).toContain('= 1'); - expect(read.where(() => {}).toQuery()).toBe(read.toQuery()); + expect(read.where(p => p).toQuery()).toBe(read.toQuery()); }); it('rejects async or throwing groups without changing their parent', async () => { @@ -244,11 +246,10 @@ describe('shape-preserving read predicates', () => { }); it('applies the same predicates and binding snapshots to aliased joins', () => { - const base = query(knex, alias(Task, 'task')) - .withRowSchema() - .leftJoin(alias(Task, 'other'), t => - eq(t.task.id, t.other.projectId) - ); + const base = query(knex, alias(Task, 'task')).leftJoin( + alias(Task, 'other'), + t => eq(t.task.id, t.other.projectId) + ); const source = base.select(t => ({ id: t.task.id, otherId: t.other.id, @@ -283,11 +284,11 @@ describe('shape-preserving read predicates', () => { .valid ).toBe(true); expect(source.toQuery()).not.toContain(' where '); - expect('apply' in source).toBe(false); + expect('apply' in source).toBe(true); expect(() => query(knex, alias(Task, 'task')) + // @ts-expect-error opaque SQL requires its output contract .apply(q => q.where('id', 1)) - .withRowSchema() - ).toThrow(/before select\/apply/); + ).toThrow(/output/); }); }); diff --git a/libs/knex-schema/src/read-predicates.ts b/libs/knex-schema/src/read-predicates.ts index 9c907720..48f15f9f 100644 --- a/libs/knex-schema/src/read-predicates.ts +++ b/libs/knex-schema/src/read-predicates.ts @@ -6,13 +6,14 @@ import { ReadSchemaError } from './read-schema.js'; const finishGroup = Symbol('finishReadPredicateGroup'); /** Select a column from the current reader's ordinary or aliased table context. */ -export type ReadPredicateSelector = (columns: C) => AliasedColumn; +export type ReadPredicateSelector = + | ((columns: C) => AliasedColumn) + | (keyof C & string); /** A synchronous, parenthesized predicate group. The callback cannot shape or execute a query. */ export type ReadPredicateGroup = ( predicates: ReadPredicateBuilder - // biome-ignore lint/suspicious/noConfusingVoidType: Accept existing void-returning callbacks while rejecting async callbacks via the explicit return union. -) => ReadPredicateBuilder | void; +) => ReadPredicateBuilder; /** A bound value list or a caller-built SELECT subquery; captured without executing it. */ export type ReadMembership = readonly unknown[] | Knex.QueryBuilder; @@ -23,7 +24,7 @@ export type ReadPredicate = (query: Knex.QueryBuilder) => void; /** @internal Resolve references without exposing the parent's mutable SQL builder. */ export interface ReadPredicateContext { knex: Knex; - column: (selector: ReadPredicateSelector) => string; + column: (selector: ReadPredicateSelector) => string | Knex.Raw; } /** @internal Snapshot common mutable binding values independently of query builders. */ @@ -89,7 +90,8 @@ function captureSubquery(knex: Knex, query: Knex.QueryBuilder): () => Knex.Raw { ); } -function captureValue(knex: Knex, value: any): () => any { +/** @internal Capture mutable bindings without retaining caller-owned values. */ +export function captureValue(knex: Knex, value: any): () => any { if (value && typeof value.toSQL === 'function') { return typeof value.clone === 'function' ? captureSubquery(knex, value) @@ -103,7 +105,7 @@ function captureValue(knex: Knex, value: any): () => any { /** * Shared shape-preserving predicate methods for immutable readers and scoped groups. - * @internal Consumers obtain these methods through withRowSchema(), not inheritance. + * @internal Consumers obtain these methods through query factories, not inheritance. */ export abstract class ReadPredicates { protected abstract readPredicateContext(): ReadPredicateContext; @@ -114,13 +116,16 @@ export abstract class ReadPredicates { * The reference uses this reader's actual SQL alias and never executes SQL. * @example read.whereRaw('lower(??) = ?', [read.ref(t => t.name), 'alice']) */ - ref(selector: ReadPredicateSelector): Knex.Ref { + ref(selector: ReadPredicateSelector): Knex.Ref | Knex.Raw { const { knex, column } = this.readPredicateContext(); - return knex.ref(column(selector)); + const resolved = column(selector); + return typeof resolved === 'string' ? knex.ref(resolved) : resolved; } /** Add a parenthesized AND group using a synchronous predicate-only callback. */ where(group: ReadPredicateGroup): this; + /** Match a record of property names and bound equality values. */ + where(values: Partial>): this; /** Add a bound equality comparison. Null uses SQL IS NULL. */ where(column: ReadPredicateSelector, value: unknown): this; /** Add a bound comparison using a supported SQL operator. */ @@ -130,14 +135,30 @@ export abstract class ReadPredicates { value: unknown ): this; where( - first: ReadPredicateSelector | ReadPredicateGroup, + first: + | ReadPredicateSelector + | ReadPredicateGroup + | Partial>, ...args: [] | [unknown] | [string, unknown] ): this { - return this.comparison('and', first, args); + if (typeof first === 'object' && first !== null && !args.length) { + return Object.entries(first).reduce( + (query, [key, value]) => + query.where(key as keyof C & string, value), + this + ); + } + return this.comparison( + 'and', + first as ReadPredicateSelector | ReadPredicateGroup, + args + ); } /** Explicit AND spelling of where(), including nested groups. */ andWhere(group: ReadPredicateGroup): this; + /** Match all property/value pairs with AND semantics. */ + andWhere(values: Partial>): this; /** Add a bound AND equality comparison. */ andWhere(column: ReadPredicateSelector, value: unknown): this; /** Add a bound AND comparison. */ @@ -147,14 +168,25 @@ export abstract class ReadPredicates { value: unknown ): this; andWhere( - first: ReadPredicateSelector | ReadPredicateGroup, + first: + | ReadPredicateSelector + | ReadPredicateGroup + | Partial>, ...args: [] | [unknown] | [string, unknown] ): this { - return this.comparison('and', first, args); + if (typeof first === 'object' && first !== null && !args.length) + return this.where(first); + return this.comparison( + 'and', + first as ReadPredicateSelector | ReadPredicateGroup, + args + ); } /** Add a parenthesized OR group. Use an enclosing AND group beside authorization filters. */ orWhere(group: ReadPredicateGroup): this; + /** Match a parenthesized AND record as one alternative to the preceding predicates. */ + orWhere(values: Partial>): this; /** Add a bound OR equality comparison. */ orWhere(column: ReadPredicateSelector, value: unknown): this; /** Add a bound OR comparison. */ @@ -164,10 +196,19 @@ export abstract class ReadPredicates { value: unknown ): this; orWhere( - first: ReadPredicateSelector | ReadPredicateGroup, + first: + | ReadPredicateSelector + | ReadPredicateGroup + | Partial>, ...args: [] | [unknown] | [string, unknown] ): this { - return this.comparison('or', first, args); + if (typeof first === 'object' && first !== null && !args.length) + return this.orWhere(group => group.where(first)); + return this.comparison( + 'or', + first as ReadPredicateSelector | ReadPredicateGroup, + args + ); } private comparison( @@ -180,7 +221,7 @@ export abstract class ReadPredicates { if (!args.length) { const group = new ReadPredicateBuilder(context); let operations: readonly ReadPredicate[]; - try { + { const result: unknown = (first as ReadPredicateGroup)(group); if ( result && @@ -193,9 +234,14 @@ export abstract class ReadPredicates { 'Read predicate groups must be synchronous' ); } - operations = group[finishGroup](); - } finally { - group[finishGroup](); + if ( + !(result instanceof ReadPredicateBuilder) || + !result.sameSource(group) + ) + throw new ReadSchemaError( + 'Predicate callbacks must return a builder from the supplied group' + ); + operations = result[finishGroup](); } return this.addReadPredicate(query => { query[method](nested => { @@ -217,8 +263,8 @@ export abstract class ReadPredicates { args.length === 1 ? args[0] : args[1] ); return this.addReadPredicate(query => { - if (args.length === 1) query[method](column, value()); - else query[method](column, operator, value()); + if (args.length === 1) query[method](column as any, value()); + else query[method](column as any, operator, value()); }); } @@ -228,7 +274,7 @@ export abstract class ReadPredicates { ): this { const name = this.readPredicateContext().column(column); return this.addReadPredicate(query => { - query[method](name); + query[method](name as any); }); } /** Match SQL null without changing the row schema. */ @@ -267,7 +313,7 @@ export abstract class ReadPredicates { query[rawMethod](`?? ${operator} (?)`, [name, captured()]); } else { query[method]( - name, + name as any, captured.map(value => value()) ); } @@ -352,18 +398,94 @@ export abstract class ReadPredicates { query.orWhereRaw(captured()); }); } + + /** Negated bound equality (null uses SQL IS NOT NULL). */ + whereNot(column: ReadPredicateSelector, value: unknown): this { + const context = this.readPredicateContext(); + const name = context.column(column); + const captured = captureValue(context.knex, value); + return this.addReadPredicate(query => { + query.whereNot(name as any, captured()); + }); + } + /** Match an inclusive range of captured values. */ + whereBetween( + column: ReadPredicateSelector, + range: readonly [unknown, unknown] + ): this { + return this.range(column, range, false); + } + /** Exclude an inclusive range of captured values. */ + whereNotBetween( + column: ReadPredicateSelector, + range: readonly [unknown, unknown] + ): this { + return this.range(column, range, true); + } + private range( + column: ReadPredicateSelector, + range: readonly [unknown, unknown], + not: boolean + ): this { + const context = this.readPredicateContext(); + const name = context.column(column); + const values = range.map(value => captureValue(context.knex, value)); + return this.addReadPredicate(query => { + query[not ? 'whereNotBetween' : 'whereBetween'](name as any, [ + values[0](), + values[1]() + ]); + }); + } + /** Match a SQL LIKE pattern. Wildcards retain their SQL meaning. */ + whereLike(column: ReadPredicateSelector, value: string): this { + return this.where(column, 'like', value); + } + /** Match a case-insensitive PostgreSQL pattern. */ + whereILike(column: ReadPredicateSelector, value: string): this { + return this.where(column, 'ilike', value); + } + /** Compare a JSON path while preserving the declared output schema. */ + whereJsonPath( + column: ReadPredicateSelector, + path: string, + operator = '=', + value?: unknown + ): this { + const context = this.readPredicateContext(); + const name = context.column(column); + const client = context.knex.client.config.client; + if (!['pg', 'postgres', 'postgresql'].includes(client)) + throw new ReadSchemaError( + 'whereJsonPath() is only supported on PostgreSQL' + ); + if (operator === '@?' || operator === '@@') + return this.whereRaw(`?? ${operator === '@?' ? '@\\?' : '@@'} ?`, [ + name, + path + ]); + if (!ALLOWED_OPS.has(operator.toLowerCase())) + throw new ReadSchemaError('Unsupported JSON comparison operator'); + return this.whereRaw( + `jsonb_path_query_first(??, ?) ${operator} ?::jsonb`, + [ + name, + path.startsWith('$') ? path : `$.${path}`, + JSON.stringify(value) + ] + ); + } } /** * Predicate-only builder supplied to grouped where/andWhere/orWhere callbacks. - * Group methods accumulate synchronously; outer readers remain immutable. + * Group methods return independent builders; attachment snapshots the returned group. * No select, join, order, raw-query escape hatch, then, or execution method exists. * Retaining this builder and mutating it after the callback throws. */ export class ReadPredicateBuilder extends ReadPredicates { #context: ReadPredicateContext; #operations: ReadPredicate[] = []; - #closed = false; /** @internal Created only for grouped predicates. */ constructor(context: ReadPredicateContext) { super(); @@ -373,14 +495,16 @@ export class ReadPredicateBuilder extends ReadPredicates { return this.#context; } protected addReadPredicate(predicate: ReadPredicate): this { - if (this.#closed) - throw new ReadSchemaError('Predicate group is already closed'); - this.#operations.push(predicate); - return this; + const copy = new ReadPredicateBuilder(this.#context); + copy.#operations = [...this.#operations, predicate]; + return copy as this; + } + /** @internal Verify that a returned builder belongs to the supplied group. */ + sameSource(other: ReadPredicateBuilder): boolean { + return this.#context === other.#context; } - /** @internal Close the scoped builder and snapshot its predicate list. */ + /** @internal Snapshot the immutable predicate list. */ [finishGroup](): readonly ReadPredicate[] { - this.#closed = true; return [...this.#operations]; } } diff --git a/libs/knex-schema/src/read-projection.ts b/libs/knex-schema/src/read-projection.ts index 3d1251ff..923d5e2d 100644 --- a/libs/knex-schema/src/read-projection.ts +++ b/libs/knex-schema/src/read-projection.ts @@ -25,7 +25,10 @@ export type ReadField = { export function compileReadProjection( knex: Knex, selection: Record | AggregateExpression>, - resolve: (column: AliasedColumn) => { name: string; node: ReadNode } + resolve: (column: AliasedColumn) => { + name: string | Knex.Raw; + node: ReadNode; + } ): Record { if (!Object.keys(selection).length) throw new ReadSchemaError('A non-empty projection is required'); diff --git a/libs/knex-schema/src/read-schema.test-d.ts b/libs/knex-schema/src/read-schema.test-d.ts index 21da993e..5add4404 100644 --- a/libs/knex-schema/src/read-schema.test-d.ts +++ b/libs/knex-schema/src/read-schema.test-d.ts @@ -13,9 +13,11 @@ const Task = object({ done: date().optional() }).hasTableName('tasks'); test('projection row type equals its runtime schema inference', async () => { - const read = query(Knex({ client: 'pg' }), Task) - .withRowSchema() - .select(t => ({ id: t.id, amount: t.amount, done: t.done })); + const read = query(Knex({ client: 'pg' }), Task).select(t => ({ + id: t.id, + amount: t.amount, + done: t.done + })); type Row = { id: number; amount: string | null; done: Date | null }; expectTypeOf>().toEqualTypeOf(); expectTypeOf(await read).toEqualTypeOf(); @@ -25,7 +27,7 @@ test('projection row type equals its runtime schema inference', async () => { test('flat joins retain exact storage and left join nullability', async () => { const read = query(Knex({ client: 'pg' }), alias(Task, 'task')) - .withRowSchema() + .leftJoin(alias(Task, 'other'), t => eq(t.task.id, t.other.id)) .select(t => ({ amount: t.task.amount, @@ -56,18 +58,16 @@ test('optional declared joins and explicit relation customizers retain their sha u => u.id, { optional: true } ); - const read = query(Knex({ client: 'pg' }), entity.schema) - .withRowSchema() - .include( - r => r.owner, - q => q.select(u => ({ name: u.name })) - ); + const read = query(Knex({ client: 'pg' }), entity.schema).include( + r => r.owner, + q => q.select(u => ({ name: u.name })) + ); const row = (await read)[0]; expectTypeOf(row.owner).toExtend<{ name: string } | null>(); // @ts-expect-error the relation may be null const _required: { name: string } = row.owner; const explicit = query(Knex({ client: 'pg' }), Task) - .withRowSchema() + .select(t => ({ title: t.title })) .joinMany( { diff --git a/libs/knex-schema/src/read-schema.test.ts b/libs/knex-schema/src/read-schema.test.ts index 41db89f3..bb9a215d 100644 --- a/libs/knex-schema/src/read-schema.test.ts +++ b/libs/knex-schema/src/read-schema.test.ts @@ -16,13 +16,11 @@ const knex = Knex({ client: 'pg' }); describe('schema-aware read metadata', () => { it('describes only projected fields, SQL nulls and exact storage', () => { - const read = query(knex, Task) - .withRowSchema() - .select(t => ({ - title: t.title, - amount: t.amount, - done: t.completedAt - })); + const read = query(knex, Task).select(t => ({ + title: t.title, + amount: t.amount, + done: t.completedAt + })); const properties = read.rowSchema.introspect().properties; expect(Object.keys(properties)).toEqual(['title', 'amount', 'done']); expect( @@ -41,7 +39,7 @@ describe('schema-aware read metadata', () => { expect(read.toQuery()).not.toContain('"id" as'); }); it('preserves schema identity across immutable filters and transactions', () => { - const read = query(knex, Task).withRowSchema(); + const read = query(knex, Task); const one = read.where(t => t.id, 1).limit(1); const two = read.where(t => t.id, 2).offset(1); expect(one.rowSchema).toBe(read.rowSchema); @@ -88,58 +86,48 @@ describe('schema-aware read metadata', () => { ).toBe('2026-01-02T01:04:05.000Z'); }); it('retains aggregate output schemas and rejects opaque parsers', () => { - const read = query(knex, Task) - .withRowSchema() - .select(t => ({ - count: aggregate.count(), - total: aggregate.sum(t.amount) - })); + const read = query(knex, Task).select(t => ({ + count: aggregate.count(), + total: aggregate.sum(t.amount) + })); expect(read.rowSchema.validate({ count: 3, total: null }).valid).toBe( true ); expect(() => - query(knex, Task) - .withRowSchema() - .select(() => ({ - count: aggregate.count(undefined, { - output: { parse: () => 1 } - }) - })) + query(knex, Task).select(() => ({ + count: aggregate.count(undefined, { + output: { parse: () => 1 } + }) + })) ).toThrow(/introspectable/); }); - it('rejects entry after legacy shape changes', () => { - expect(() => + it('exposes metadata automatically and requires schemas for opaque SQL', () => { + expect( query(knex, Task) .orderBy(t => t.id) - .withRowSchema() - ).toThrow(/before ordering/); - expect(() => query(knex, Task).limit(1).withRowSchema()).toThrow( - /before ordering/ - ); - expect(() => query(knex, Task).offset(1).withRowSchema()).toThrow( - /before ordering/ - ); - expect(() => - query(knex, Task) - .select(t => ({ id: t.id })) - .withRowSchema() - ).toThrow(/before/); + .limit(1).rowSchema + ).toBeDefined(); + expect( + Object.keys( + query(knex, Task) + .select(t => ({ id: t.id })) + .rowSchema.introspect().properties + ) + ).toEqual(['id']); expect(() => - query(knex, Task).selectRaw('1 as other').withRowSchema() - ).toThrow(); + (query(knex, Task) as any).selectRaw('1 as other', []) + ).toThrow(/output/); expect(() => - query(knex, Task) - .apply(q => q.whereRaw('true')) - .withRowSchema() - ).toThrow(/before/); + (query(knex, Task) as any).apply((q: any) => q.whereRaw('true')) + ).toThrow(/output/); expect(() => - query(knex, alias(Task, 'task')) - .apply(q => q.select('id')) - .withRowSchema() - ).toThrow(/before/); + (query(knex, alias(Task, 'task')) as any).apply((q: any) => + q.select('id') + ) + ).toThrow(/output/); }); it('describes immutable flat left joins with SQL nulls', () => { - const base = query(knex, alias(Task, 'task')).withRowSchema(); + const base = query(knex, alias(Task, 'task')); expect(() => base.rowSchema).toThrow(/explicit select/); const joined = base .leftJoin(alias(Task, 'other'), t => eq(t.task.id, t.other.id)) diff --git a/libs/knex-schema/src/read-schema.ts b/libs/knex-schema/src/read-schema.ts index a075e2f2..16e8399f 100644 --- a/libs/knex-schema/src/read-schema.ts +++ b/libs/knex-schema/src/read-schema.ts @@ -13,6 +13,7 @@ import { union } from '@cleverbrush/schema'; import type { Knex } from 'knex'; +import { buildColumnMap } from './columns.js'; /** A schema builder accepted by the database-read schema compiler. */ export type ReadSchema = SchemaBuilder; @@ -298,9 +299,36 @@ export function decodeObject( export function readExpression( knex: Knex, node: ReadNode, - column: string + column: string | Knex.Raw ): Knex.Raw { return node.exact || node.schema.introspect().type === 'date' ? knex.raw('cast(?? as text)', [column]) : knex.raw('??', [column]); } + +/** @internal Preserve exact numerics and dates before driver parsing of write-returning rows. */ +export function returningReadColumns( + knex: Knex, + source: ReadObject +): Knex.Raw[] { + const info = source.introspect(); + const excluded = new Set( + ((info.extensions?.relations ?? []) as { name: string }[]).map( + relation => relation.name + ) + ); + const { propToCol } = buildColumnMap(source); + return Object.entries(info.properties) + .filter(([key]) => !excluded.has(key)) + .map(([key, schema]) => { + const column = propToCol.get(key) ?? key; + return knex.raw('? as ??', [ + readExpression( + knex, + compileReadSchema(schema as ReadSchema), + column + ), + column + ]); + }); +} diff --git a/libs/knex-schema/src/types.ts b/libs/knex-schema/src/types.ts index cd070d25..2b74183d 100644 --- a/libs/knex-schema/src/types.ts +++ b/libs/knex-schema/src/types.ts @@ -7,7 +7,10 @@ import type { PropertyDescriptorTree } from '@cleverbrush/schema'; import type { Knex } from 'knex'; +import type { SchemaProps } from './entity.js'; import type { AggregateExpression } from './expressions.js'; +import type { ReadRelations } from './read-entity.js'; +import type { ObjectReadSchema, ReadValue } from './read-schema.js'; // --------------------------------------------------------------------------- // Utility: extract string keys from an ObjectSchemaBuilder's inferred type @@ -258,18 +261,16 @@ export type ValidatedSpec = */ export type InsertType< T extends ObjectSchemaBuilder -> = InferType>; +> = { + [K in Exclude, keyof ReadRelations>]?: + | InferType[K]> + | ReadValue[K]>; +}; // --------------------------------------------------------------------------- // Database row helpers // --------------------------------------------------------------------------- -type OptionalKeys = { - [K in keyof T]-?: undefined extends T[K] ? K : never; -}[keyof T]; - -type RequiredKeys = Exclude>; - /** * Normalize a schema-inferred property type to the value shape commonly * returned by database rows. @@ -290,11 +291,7 @@ export type InferDatabaseValue = undefined extends T */ export type InferDatabaseRow< T extends ObjectSchemaBuilder -> = { - [K in RequiredKeys>]: InferDatabaseValue[K]>; -} & { - [K in OptionalKeys>]?: InferDatabaseValue[K]>; -}; +> = InferType>>; // --------------------------------------------------------------------------- // Primary-key type helpers (driven by PRIMARY_KEY_BRAND / COMPOSITE_PRIMARY_KEY_BRAND) @@ -379,7 +376,12 @@ export type PrimaryKeyValueOf< ? PkTupleValue> : never : PrimaryKeyOf extends string - ? InferType[PrimaryKeyOf & keyof InferType] + ? + | InferType[PrimaryKeyOf & keyof InferType] + | ReadValue< + SchemaPropsForPk[PrimaryKeyOf & + keyof SchemaPropsForPk] + > : never; /** @@ -392,7 +394,11 @@ type PkTupleValue< TKeys extends readonly string[] > = { [I in keyof TKeys]: TKeys[I] extends keyof InferType - ? InferType[TKeys[I]] + ? + | InferType[TKeys[I]] + | ReadValue< + SchemaPropsForPk[TKeys[I] & keyof SchemaPropsForPk] + > : unknown; }; diff --git a/libs/knex-schema/src/unit.test.ts b/libs/knex-schema/src/unit.test.ts index 18a70a4e..8987ff3c 100644 --- a/libs/knex-schema/src/unit.test.ts +++ b/libs/knex-schema/src/unit.test.ts @@ -18,6 +18,19 @@ import { string } from './index.js'; +import { QuerySource } from './QuerySource.js'; +import type { ReadObject } from './read-schema.js'; + +// Preserve low-level SQL planner regressions independently of public immutable +// queries (covered by immutable-query, read-predicates and PostgreSQL suites). +function privateSource( + connection: KnexType, + schema: S, + base?: KnexType.QueryBuilder +) { + return new QuerySource(connection, schema, base); +} + // ═══════════════════════════════════════════════════════════════════════════ // Test schemas // ═══════════════════════════════════════════════════════════════════════════ @@ -180,43 +193,43 @@ describe('resolveColumnRef', () => { // Query builder — SQL snapshot tests // ═══════════════════════════════════════════════════════════════════════════ -describe('SchemaQueryBuilder', () => { +describe('private QuerySource planner', () => { describe('basic SELECT', () => { it('produces SELECT * FROM table', () => { - const sql = query(knex, User).toQuery(); + const sql = privateSource(knex, User).toQuery(); expect(sql).toBe('select * from "users"'); }); it('produces SELECT with SimpleTag (no column mapping needed)', () => { - const sql = query(knex, SimpleTag).toQuery(); + const sql = privateSource(knex, SimpleTag).toQuery(); expect(sql).toBe('select * from "tags"'); }); }); describe('WHERE', () => { it('.where with property descriptor', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .where(t => t.fullName, '=', 'John') .toQuery(); expect(sql).toContain('"full_name" = \'John\''); }); it('.where with string property key', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .where('fullName', '=', 'John') .toQuery(); expect(sql).toContain('"full_name" = \'John\''); }); it('.where with default column name', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .where('email', '=', 'test@test.com') .toQuery(); expect(sql).toContain('"email" = \'test@test.com\''); }); it('.where with record syntax', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .where({ fullName: 'John', role: 'admin' }) .toQuery(); expect(sql).toContain('"full_name" = \'John\''); @@ -224,7 +237,7 @@ describe('SchemaQueryBuilder', () => { }); it('.where with callback (knex sub-builder)', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .where((builder: KnexType.QueryBuilder) => { builder.where('role', 'admin'); }) @@ -233,42 +246,42 @@ describe('SchemaQueryBuilder', () => { }); it('.whereIn with property descriptor', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .whereIn(t => t.role, ['admin', 'user']) .toQuery(); expect(sql).toContain("\"role\" in ('admin', 'user')"); }); it('.whereNotIn', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .whereNotIn('role', ['banned']) .toQuery(); expect(sql).toContain('"role" not in (\'banned\')'); }); it('.whereNull with descriptor', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .whereNull(t => t.managerId) .toQuery(); expect(sql).toContain('"manager_id" is null'); }); it('.whereNotNull', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .whereNotNull(t => t.managerId) .toQuery(); expect(sql).toContain('"manager_id" is not null'); }); it('.whereBetween', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .whereBetween(t => t.departmentId, [1, 10]) .toQuery(); expect(sql).toContain('"department_id" between 1 and 10'); }); it('.andWhere / .orWhere chaining', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .where('role', '=', 'admin') .andWhere(t => t.departmentId, '>', 5) .orWhere('email', 'like', '%@co.com') @@ -279,7 +292,7 @@ describe('SchemaQueryBuilder', () => { }); it('.whereRaw passthrough', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .whereRaw('full_name ILIKE ?', '%smith%') .toQuery(); expect(sql).toContain("full_name ILIKE '%smith%'"); @@ -288,21 +301,24 @@ describe('SchemaQueryBuilder', () => { describe('ORDER BY', () => { it('.orderBy with descriptor', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .orderBy(t => t.createdAt, 'desc') .toQuery(); expect(sql).toContain('order by "created_at" desc'); }); it('.orderBy with string key', () => { - const sql = query(knex, User).orderBy('fullName').toQuery(); + const sql = privateSource(knex, User).orderBy('fullName').toQuery(); expect(sql).toContain('order by "full_name"'); }); }); describe('LIMIT / OFFSET', () => { it('.limit and .offset', () => { - const sql = query(knex, User).limit(10).offset(20).toQuery(); + const sql = privateSource(knex, User) + .limit(10) + .offset(20) + .toQuery(); expect(sql).toContain('limit 10'); expect(sql).toContain('offset 20'); }); @@ -310,14 +326,14 @@ describe('SchemaQueryBuilder', () => { describe('GROUP BY / HAVING', () => { it('.groupBy with descriptor', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .groupBy(t => t.role) .toQuery(); expect(sql).toContain('group by "role"'); }); it('.having', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .groupBy('role') .having('role', '!=', 'banned') .toQuery(); @@ -328,7 +344,7 @@ describe('SchemaQueryBuilder', () => { describe('SELECT / DISTINCT', () => { it('.select with descriptors', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .select( t => t.fullName, t => t.email @@ -339,7 +355,7 @@ describe('SchemaQueryBuilder', () => { }); it('.distinct with descriptor', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .distinct(t => t.role) .toQuery(); expect(sql).toContain('distinct "role"'); @@ -348,7 +364,7 @@ describe('SchemaQueryBuilder', () => { describe('escape hatch', () => { it('.apply passes through to knex builder', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .apply(qb => { qb.where('id', '>', 100); }) @@ -359,7 +375,7 @@ describe('SchemaQueryBuilder', () => { describe('chained queries', () => { it('complex chained query', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .where(t => t.role, '=', 'admin') .andWhere(t => t.departmentId, '>', 5) .whereNotNull(t => t.managerId) @@ -385,7 +401,7 @@ describe('SchemaQueryBuilder', () => { describe('eager loading', () => { it('joinOne produces CTE with jsonb_agg', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .joinOne({ localColumn: t => t.departmentId, foreignColumn: t => t.id, @@ -401,7 +417,7 @@ describe('eager loading', () => { }); it('joinMany produces CTE with jsonb_agg and coalesce', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .joinMany({ localColumn: t => t.id, foreignColumn: t => t.authorId, @@ -417,7 +433,7 @@ describe('eager loading', () => { }); it('joinOne with string column refs', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .joinOne({ localColumn: 'departmentId', foreignColumn: 'id', @@ -430,7 +446,7 @@ describe('eager loading', () => { }); it('joinMany with limit and orderBy', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .joinMany({ localColumn: t => t.id, foreignColumn: t => t.authorId, @@ -446,7 +462,7 @@ describe('eager loading', () => { }); it('chained joinOne + joinMany', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .joinOne({ localColumn: t => t.departmentId, foreignColumn: t => t.id, @@ -468,7 +484,7 @@ describe('eager loading', () => { }); it('joinOne with explicit foreignQuery as raw knex', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .joinOne({ localColumn: t => t.departmentId, foreignColumn: t => t.id, @@ -482,13 +498,13 @@ describe('eager loading', () => { }); it('joinOne with SchemaQueryBuilder as foreignQuery', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .joinOne({ localColumn: t => t.departmentId, foreignColumn: t => t.id, as: 'department', foreignSchema: Department, - foreignQuery: query(knex, Department).where( + foreignQuery: privateSource(knex, Department).where( t => t.budget, '>', 1000 @@ -500,7 +516,7 @@ describe('eager loading', () => { }); it('SchemaQueryBuilder foreignQuery produces same SQL as raw knex foreignQuery', () => { - const rawSql = query(knex, User) + const rawSql = privateSource(knex, User) .joinOne({ localColumn: t => t.departmentId, foreignColumn: t => t.id, @@ -510,13 +526,13 @@ describe('eager loading', () => { }) .toQuery(); - const schemaSql = query(knex, User) + const schemaSql = privateSource(knex, User) .joinOne({ localColumn: t => t.departmentId, foreignColumn: t => t.id, as: 'department', foreignSchema: Department, - foreignQuery: query(knex, Department).where( + foreignQuery: privateSource(knex, Department).where( t => t.budget, '>', 1000 @@ -528,13 +544,17 @@ describe('eager loading', () => { }); it('joinMany with SchemaQueryBuilder as foreignQuery', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .joinMany({ localColumn: t => t.id, foreignColumn: t => t.authorId, as: 'posts', foreignSchema: Post, - foreignQuery: query(knex, Post).where(t => t.categoryId, '=', 5) + foreignQuery: privateSource(knex, Post).where( + t => t.categoryId, + '=', + 5 + ) }) .toQuery(); @@ -542,7 +562,7 @@ describe('eager loading', () => { }); it('joinMany SchemaQueryBuilder foreignQuery matches raw knex', () => { - const rawSql = query(knex, User) + const rawSql = privateSource(knex, User) .joinMany({ localColumn: t => t.id, foreignColumn: t => t.authorId, @@ -552,13 +572,17 @@ describe('eager loading', () => { }) .toQuery(); - const schemaSql = query(knex, User) + const schemaSql = privateSource(knex, User) .joinMany({ localColumn: t => t.id, foreignColumn: t => t.authorId, as: 'posts', foreignSchema: Post, - foreignQuery: query(knex, Post).where(t => t.categoryId, '=', 5) + foreignQuery: privateSource(knex, Post).where( + t => t.categoryId, + '=', + 5 + ) }) .toQuery(); @@ -567,7 +591,7 @@ describe('eager loading', () => { it('throws on duplicate field names', () => { expect(() => { - query(knex, User) + privateSource(knex, User) .joinOne({ localColumn: t => t.departmentId, foreignColumn: t => t.id, @@ -586,7 +610,7 @@ describe('eager loading', () => { it('joinOne with .select() still includes localColumn in CTE', () => { // If the caller uses .select() and omits the join key (departmentId), // the generated SQL should still include it in the CTE so the join works. - const sql = query(knex, User) + const sql = privateSource(knex, User) .select(t => t.fullName) // intentionally omit departmentId .joinOne({ localColumn: t => t.departmentId, @@ -603,7 +627,7 @@ describe('eager loading', () => { }); it('joinMany with .select() still includes localColumn in CTE', () => { - const sql = query(knex, User) + const sql = privateSource(knex, User) .select(t => t.fullName) // intentionally omit id (localColumn for joinMany) .joinMany({ localColumn: t => t.id, @@ -683,7 +707,7 @@ describe('mappers', () => { describe('validateMappers via joinOne/joinMany', () => { it('accepts a function mapper in joinOne spec', () => { expect(() => - query(knex, User).joinOne({ + privateSource(knex, User).joinOne({ localColumn: t => t.departmentId, foreignColumn: t => t.id, as: 'department', @@ -695,7 +719,7 @@ describe('mappers', () => { it('accepts a built-in string mapper name in joinOne spec', () => { expect(() => - query(knex, Post).joinOne({ + privateSource(knex, Post).joinOne({ localColumn: t => t.authorId, foreignColumn: t => t.id, as: 'author', @@ -707,7 +731,7 @@ describe('mappers', () => { it('rejects an unknown built-in string mapper name in joinOne spec', () => { expect(() => - query(knex, User).joinOne({ + privateSource(knex, User).joinOne({ localColumn: t => t.departmentId, foreignColumn: t => t.id, as: 'department', @@ -719,7 +743,7 @@ describe('mappers', () => { it('accepts a built-in string mapper name in joinMany spec', () => { expect(() => - query(knex, User).joinMany({ + privateSource(knex, User).joinMany({ localColumn: t => t.id, foreignColumn: t => t.authorId, as: 'posts', @@ -731,7 +755,7 @@ describe('mappers', () => { it('rejects a non-function, non-string mapper value', () => { expect(() => - query(knex, User).joinOne({ + privateSource(knex, User).joinOne({ localColumn: t => t.departmentId, foreignColumn: t => t.id, as: 'department', @@ -750,16 +774,18 @@ describe('mappers', () => { // keys → column names; all other knex behaviour is preserved unchanged. // ═══════════════════════════════════════════════════════════════════════════ -describe('SQL parity with raw knex', () => { +describe('private planner SQL parity with raw knex', () => { // ── SELECT ──────────────────────────────────────────────────────────── it('SELECT *', () => { - expect(query(knex, User).toQuery()).toBe(knex('users').toQuery()); + expect(privateSource(knex, User).toQuery()).toBe( + knex('users').toQuery() + ); }); it('SELECT specific columns (descriptor)', () => { expect( - query(knex, User) + privateSource(knex, User) .select( t => t.fullName, t => t.email, @@ -772,38 +798,38 @@ describe('SQL parity with raw knex', () => { }); it('SELECT specific columns (string key)', () => { - expect(query(knex, User).select('fullName', 'email').toQuery()).toBe( - knex('users').select('full_name', 'email').toQuery() - ); + expect( + privateSource(knex, User).select('fullName', 'email').toQuery() + ).toBe(knex('users').select('full_name', 'email').toQuery()); }); it('SELECT — identity mapping (no hasColumnName)', () => { - expect(query(knex, SimpleTag).select('id', 'name').toQuery()).toBe( - knex('tags').select('id', 'name').toQuery() - ); + expect( + privateSource(knex, SimpleTag).select('id', 'name').toQuery() + ).toBe(knex('tags').select('id', 'name').toQuery()); }); // ── DISTINCT ────────────────────────────────────────────────────────── it('DISTINCT (descriptor)', () => { expect( - query(knex, User) + privateSource(knex, User) .distinct(t => t.role) .toQuery() ).toBe(knex('users').distinct('role').toQuery()); }); it('DISTINCT (string key)', () => { - expect(query(knex, User).distinct('departmentId').toQuery()).toBe( - knex('users').distinct('department_id').toQuery() - ); + expect( + privateSource(knex, User).distinct('departmentId').toQuery() + ).toBe(knex('users').distinct('department_id').toQuery()); }); // ── WHERE ───────────────────────────────────────────────────────────── it('.where (operator, descriptor)', () => { expect( - query(knex, User) + privateSource(knex, User) .where(t => t.fullName, '=', 'Alice') .toQuery() ).toBe(knex('users').where('full_name', '=', 'Alice').toQuery()); @@ -811,19 +837,19 @@ describe('SQL parity with raw knex', () => { it('.where (operator, string key)', () => { expect( - query(knex, User).where('fullName', '=', 'Alice').toQuery() + privateSource(knex, User).where('fullName', '=', 'Alice').toQuery() ).toBe(knex('users').where('full_name', '=', 'Alice').toQuery()); }); it('.where (operator, identity column)', () => { - expect(query(knex, User).where('email', '=', 'a@b.com').toQuery()).toBe( - knex('users').where('email', '=', 'a@b.com').toQuery() - ); + expect( + privateSource(knex, User).where('email', '=', 'a@b.com').toQuery() + ).toBe(knex('users').where('email', '=', 'a@b.com').toQuery()); }); it('.where (record)', () => { expect( - query(knex, User) + privateSource(knex, User) .where({ fullName: 'Alice', role: 'admin' }) .toQuery() ).toBe( @@ -833,7 +859,7 @@ describe('SQL parity with raw knex', () => { it('.where (callback)', () => { expect( - query(knex, User) + privateSource(knex, User) .where((b: KnexType.QueryBuilder) => { b.where('role', 'admin'); }) @@ -849,7 +875,7 @@ describe('SQL parity with raw knex', () => { it('.andWhere (descriptor)', () => { expect( - query(knex, User) + privateSource(knex, User) .where('role', '=', 'admin') .andWhere(t => t.departmentId, '>', 5) .toQuery() @@ -863,7 +889,7 @@ describe('SQL parity with raw knex', () => { it('.orWhere (descriptor)', () => { expect( - query(knex, User) + privateSource(knex, User) .where('role', '=', 'admin') .orWhere(t => t.role, '=', 'editor') .toQuery() @@ -877,21 +903,21 @@ describe('SQL parity with raw knex', () => { it('.whereNot (descriptor)', () => { expect( - query(knex, User) + privateSource(knex, User) .whereNot(t => t.role, 'banned') .toQuery() ).toBe(knex('users').whereNot('role', 'banned').toQuery()); }); it('.whereNot (string key)', () => { - expect(query(knex, User).whereNot('departmentId', 99).toQuery()).toBe( - knex('users').whereNot('department_id', 99).toQuery() - ); + expect( + privateSource(knex, User).whereNot('departmentId', 99).toQuery() + ).toBe(knex('users').whereNot('department_id', 99).toQuery()); }); it('.whereIn (descriptor)', () => { expect( - query(knex, User) + privateSource(knex, User) .whereIn(t => t.role, ['admin', 'editor']) .toQuery() ).toBe(knex('users').whereIn('role', ['admin', 'editor']).toQuery()); @@ -899,13 +925,15 @@ describe('SQL parity with raw knex', () => { it('.whereIn (string key, mapped column)', () => { expect( - query(knex, User).whereIn('departmentId', [1, 2, 3]).toQuery() + privateSource(knex, User) + .whereIn('departmentId', [1, 2, 3]) + .toQuery() ).toBe(knex('users').whereIn('department_id', [1, 2, 3]).toQuery()); }); it('.whereNotIn (descriptor)', () => { expect( - query(knex, User) + privateSource(knex, User) .whereNotIn(t => t.role, ['banned']) .toQuery() ).toBe(knex('users').whereNotIn('role', ['banned']).toQuery()); @@ -913,7 +941,7 @@ describe('SQL parity with raw knex', () => { it('.whereNull (descriptor)', () => { expect( - query(knex, User) + privateSource(knex, User) .whereNull(t => t.managerId) .toQuery() ).toBe(knex('users').whereNull('manager_id').toQuery()); @@ -921,7 +949,7 @@ describe('SQL parity with raw knex', () => { it('.whereNotNull (descriptor)', () => { expect( - query(knex, User) + privateSource(knex, User) .whereNotNull(t => t.managerId) .toQuery() ).toBe(knex('users').whereNotNull('manager_id').toQuery()); @@ -929,7 +957,7 @@ describe('SQL parity with raw knex', () => { it('.orWhereNull (descriptor)', () => { expect( - query(knex, User) + privateSource(knex, User) .whereNull(t => t.managerId) .orWhereNull(t => t.departmentId) .toQuery() @@ -943,7 +971,7 @@ describe('SQL parity with raw knex', () => { it('.orWhereNotNull (descriptor)', () => { expect( - query(knex, User) + privateSource(knex, User) .whereNull(t => t.managerId) .orWhereNotNull(t => t.departmentId) .toQuery() @@ -957,7 +985,7 @@ describe('SQL parity with raw knex', () => { it('.whereBetween (descriptor)', () => { expect( - query(knex, User) + privateSource(knex, User) .whereBetween(t => t.departmentId, [1, 10]) .toQuery() ).toBe(knex('users').whereBetween('department_id', [1, 10]).toQuery()); @@ -965,7 +993,7 @@ describe('SQL parity with raw knex', () => { it('.whereNotBetween (string key)', () => { expect( - query(knex, User) + privateSource(knex, User) .whereNotBetween('departmentId', [20, 30]) .toQuery() ).toBe( @@ -975,7 +1003,9 @@ describe('SQL parity with raw knex', () => { it('.whereRaw passthrough', () => { expect( - query(knex, User).whereRaw('full_name ILIKE ?', '%smith%').toQuery() + privateSource(knex, User) + .whereRaw('full_name ILIKE ?', '%smith%') + .toQuery() ).toBe( knex('users').whereRaw('full_name ILIKE ?', '%smith%').toQuery() ); @@ -985,21 +1015,21 @@ describe('SQL parity with raw knex', () => { it('.orderBy asc (descriptor)', () => { expect( - query(knex, User) + privateSource(knex, User) .orderBy(t => t.fullName, 'asc') .toQuery() ).toBe(knex('users').orderBy('full_name', 'asc').toQuery()); }); it('.orderBy desc (string key)', () => { - expect(query(knex, User).orderBy('createdAt', 'desc').toQuery()).toBe( - knex('users').orderBy('created_at', 'desc').toQuery() - ); + expect( + privateSource(knex, User).orderBy('createdAt', 'desc').toQuery() + ).toBe(knex('users').orderBy('created_at', 'desc').toQuery()); }); it('.orderByRaw passthrough', () => { expect( - query(knex, User) + privateSource(knex, User) .orderByRaw('"created_at" DESC NULLS LAST') .toQuery() ).toBe( @@ -1010,19 +1040,19 @@ describe('SQL parity with raw knex', () => { // ── LIMIT / OFFSET ──────────────────────────────────────────────────── it('.limit', () => { - expect(query(knex, User).limit(25).toQuery()).toBe( + expect(privateSource(knex, User).limit(25).toQuery()).toBe( knex('users').limit(25).toQuery() ); }); it('.offset', () => { - expect(query(knex, User).offset(50).toQuery()).toBe( + expect(privateSource(knex, User).offset(50).toQuery()).toBe( knex('users').offset(50).toQuery() ); }); it('.limit + .offset', () => { - expect(query(knex, User).limit(10).offset(20).toQuery()).toBe( + expect(privateSource(knex, User).limit(10).offset(20).toQuery()).toBe( knex('users').limit(10).offset(20).toQuery() ); }); @@ -1031,33 +1061,33 @@ describe('SQL parity with raw knex', () => { it('.groupBy (descriptor)', () => { expect( - query(knex, User) + privateSource(knex, User) .groupBy(t => t.role) .toQuery() ).toBe(knex('users').groupBy('role').toQuery()); }); it('.groupBy (string key, mapped column)', () => { - expect(query(knex, User).groupBy('departmentId').toQuery()).toBe( - knex('users').groupBy('department_id').toQuery() - ); + expect( + privateSource(knex, User).groupBy('departmentId').toQuery() + ).toBe(knex('users').groupBy('department_id').toQuery()); }); it('.groupBy multiple columns', () => { expect( - query(knex, User).groupBy('role', 'departmentId').toQuery() + privateSource(knex, User).groupBy('role', 'departmentId').toQuery() ).toBe(knex('users').groupBy('role', 'department_id').toQuery()); }); it('.groupByRaw passthrough', () => { - expect(query(knex, User).groupByRaw('"role"').toQuery()).toBe( + expect(privateSource(knex, User).groupByRaw('"role"').toQuery()).toBe( knex('users').groupByRaw('"role"').toQuery() ); }); it('.having (string key)', () => { expect( - query(knex, User) + privateSource(knex, User) .groupBy('role') .having('role', '!=', 'banned') .toQuery() @@ -1071,7 +1101,7 @@ describe('SQL parity with raw knex', () => { it('.havingRaw passthrough', () => { expect( - query(knex, User) + privateSource(knex, User) .groupBy('role') .havingRaw('count(*) > 5') .toQuery() @@ -1083,14 +1113,14 @@ describe('SQL parity with raw knex', () => { // ── AGGREGATES ──────────────────────────────────────────────────────── it('.count()', () => { - expect(query(knex, User).count().toQuery()).toBe( + expect(privateSource(knex, User).count().toQuery()).toBe( knex('users').count().toQuery() ); }); it('.count(column, descriptor)', () => { expect( - query(knex, User) + privateSource(knex, User) .count(t => t.id) .toQuery() ).toBe(knex('users').count('id').toQuery()); @@ -1098,7 +1128,7 @@ describe('SQL parity with raw knex', () => { it('.countDistinct(column, descriptor)', () => { expect( - query(knex, User) + privateSource(knex, User) .countDistinct(t => t.departmentId) .toQuery() ).toBe(knex('users').countDistinct('department_id').toQuery()); @@ -1106,7 +1136,7 @@ describe('SQL parity with raw knex', () => { it('.min (descriptor)', () => { expect( - query(knex, User) + privateSource(knex, User) .min(t => t.createdAt) .toQuery() ).toBe(knex('users').min('created_at').toQuery()); @@ -1114,20 +1144,20 @@ describe('SQL parity with raw knex', () => { it('.max (descriptor)', () => { expect( - query(knex, User) + privateSource(knex, User) .max(t => t.createdAt) .toQuery() ).toBe(knex('users').max('created_at').toQuery()); }); it('.sum (string key)', () => { - expect(query(knex, User).sum('departmentId').toQuery()).toBe( + expect(privateSource(knex, User).sum('departmentId').toQuery()).toBe( knex('users').sum('department_id').toQuery() ); }); it('.avg (string key)', () => { - expect(query(knex, User).avg('departmentId').toQuery()).toBe( + expect(privateSource(knex, User).avg('departmentId').toQuery()).toBe( knex('users').avg('department_id').toQuery() ); }); @@ -1136,7 +1166,7 @@ describe('SQL parity with raw knex', () => { it('combined: SELECT + WHERE + ORDER BY + LIMIT + OFFSET', () => { expect( - query(knex, User) + privateSource(knex, User) .select( t => t.fullName, t => t.email, @@ -1162,7 +1192,7 @@ describe('SQL parity with raw knex', () => { it('combined: WHERE complex + GROUP BY + HAVING', () => { expect( - query(knex, User) + privateSource(knex, User) .select(t => t.role) .whereIn(t => t.role, ['admin', 'editor']) .groupBy(t => t.role) @@ -1182,7 +1212,7 @@ describe('SQL parity with raw knex', () => { describe('knex.raw() as column argument', () => { it('.where(knex.raw()) — raw as full WHERE expression', () => { expect( - query(knex, User) + privateSource(knex, User) .where(knex.raw('status = ?', ['active'])) .toQuery() ).toBe( @@ -1194,7 +1224,7 @@ describe('SQL parity with raw knex', () => { it('.where(knex.raw(), operator, value) — raw as LHS column', () => { expect( - query(knex, User) + privateSource(knex, User) .where(knex.raw('"full_name"'), '=', 'Alice') .toQuery() ).toBe( @@ -1206,7 +1236,7 @@ describe('SQL parity with raw knex', () => { it('.andWhere(knex.raw())', () => { expect( - query(knex, User) + privateSource(knex, User) .where('role', '=', 'admin') .andWhere(knex.raw('deleted_at IS NULL')) .toQuery() @@ -1220,7 +1250,7 @@ describe('SQL parity with raw knex', () => { it('.orWhere(knex.raw())', () => { expect( - query(knex, User) + privateSource(knex, User) .where('role', '=', 'admin') .orWhere(knex.raw('role = ?', ['superuser'])) .toQuery() @@ -1234,7 +1264,7 @@ describe('SQL parity with raw knex', () => { it('.whereNot(knex.raw())', () => { expect( - query(knex, User) + privateSource(knex, User) .whereNot(knex.raw('deleted_at IS NULL')) .toQuery() ).toBe( @@ -1244,7 +1274,7 @@ describe('SQL parity with raw knex', () => { it('.select(knex.raw()) — computed expression', () => { expect( - query(knex, User) + privateSource(knex, User) .select(knex.raw('count(*) as total')) .toQuery() ).toBe( @@ -1254,7 +1284,7 @@ describe('SQL parity with raw knex', () => { it('.select() mixing schema column and knex.raw()', () => { expect( - query(knex, User) + privateSource(knex, User) .select(t => t.role, knex.raw('count(*) as total')) .toQuery() ).toBe( @@ -1266,13 +1296,13 @@ describe('SQL parity with raw knex', () => { it('.distinct(knex.raw())', () => { expect( - query(knex, User).distinct(knex.raw('"role"')).toQuery() + privateSource(knex, User).distinct(knex.raw('"role"')).toQuery() ).toBe(knex('users').distinct(knex.raw('"role"')).toQuery()); }); it('.orderBy(knex.raw())', () => { expect( - query(knex, User) + privateSource(knex, User) .orderBy(knex.raw('"created_at" DESC NULLS LAST')) .toQuery() ).toBe( @@ -1284,7 +1314,7 @@ describe('SQL parity with raw knex', () => { it('.groupBy(knex.raw())', () => { expect( - query(knex, User) + privateSource(knex, User) .groupBy(knex.raw("date_trunc('day', created_at)")) .toQuery() ).toBe( @@ -1296,7 +1326,7 @@ describe('SQL parity with raw knex', () => { it('.groupBy() mixing schema column and knex.raw()', () => { expect( - query(knex, User) + privateSource(knex, User) .groupBy( t => t.role, knex.raw("date_trunc('day', created_at)") @@ -1311,7 +1341,7 @@ describe('SQL parity with raw knex', () => { it('.having(knex.raw(), operator, value)', () => { expect( - query(knex, User) + privateSource(knex, User) .groupBy(t => t.role) .having(knex.raw('count(*)'), '>', 5) .toQuery() @@ -1335,7 +1365,15 @@ describe('createQuery factory', () => { const q = createQuery(knex); it('produces same SQL as query(knex, schema) for SELECT *', () => { - expect(q(User).toQuery()).toBe(query(knex, User).toQuery()); + expect( + q(User) + .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read') + ).toBe( + query(knex, User) + .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read') + ); }); it('produces same SQL for WHERE via descriptor', () => { @@ -1343,10 +1381,12 @@ describe('createQuery factory', () => { q(User) .where(t => t.fullName, '=', 'Alice') .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read') ).toBe( query(knex, User) .where(t => t.fullName, '=', 'Alice') .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read') ); }); @@ -1357,27 +1397,22 @@ describe('createQuery factory', () => { .orderBy(t => t.createdAt, 'desc') .limit(10) .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read') ).toBe( query(knex, User) .where(t => t.role, '=', 'admin') .orderBy(t => t.createdAt, 'desc') .limit(10) .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read') ); }); - it('the baseQuery overload works', () => { - const base1 = knex('users').where('deleted_at', null); - const base2 = knex('users').where('deleted_at', null); - expect( - q(User, base1) - .where(t => t.role, '=', 'admin') - .toQuery() - ).toBe( - query(knex, User, base2) - .where(t => t.role, '=', 'admin') - .toQuery() - ); + it('raw SQL extensions declare their output explicitly', () => { + const schema = object({ role: string() }); + const configured = q(User).selectRaw('role', [], { output: schema }); + expect(configured.rowSchema).toBe(schema); + expect(configured.toQuery()).toContain('select role'); }); it('works with joinOne', () => { @@ -1390,6 +1425,7 @@ describe('createQuery factory', () => { foreignSchema: Department }) .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read') ).toBe( query(knex, User) .joinOne({ @@ -1399,6 +1435,7 @@ describe('createQuery factory', () => { foreignSchema: Department }) .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read') ); }); @@ -1412,6 +1449,7 @@ describe('createQuery factory', () => { foreignSchema: Post }) .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read') ).toBe( query(knex, User) .joinMany({ @@ -1421,6 +1459,7 @@ describe('createQuery factory', () => { foreignSchema: Post }) .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read') ); }); @@ -1430,7 +1469,15 @@ describe('createQuery factory', () => { // Both produce the same SQL — knex client config doesn't affect SQL // generation without a real connection, but they must be independent objects expect(q(User)).not.toBe(q2(User)); - expect(q(User).toQuery()).toBe(q2(User).toQuery()); + expect( + q(User) + .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read') + ).toBe( + q2(User) + .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read') + ); knex2.destroy(); }); }); @@ -1462,7 +1509,8 @@ describe('transaction support', () => { const sql = query(knex, User) .where(t => t.role, '=', 'admin') .transacting(trx) - .toQuery(); + .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read'); expect(sql).toContain('"role" = \'admin\''); }); @@ -1470,8 +1518,9 @@ describe('transaction support', () => { const sql = query(knex, User) .orderBy(t => t.createdAt, 'desc') .transacting(trx) - .toQuery(); - expect(sql).toContain('order by "created_at" desc'); + .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read'); + expect(sql).toContain('order by "__schema_read"."created_at" desc'); }); it('preserves LIMIT / OFFSET after transacting()', () => { @@ -1479,12 +1528,13 @@ describe('transaction support', () => { .limit(10) .offset(5) .transacting(trx) - .toQuery(); + .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read'); expect(sql).toContain('limit 10'); expect(sql).toContain('offset 5'); }); - it('transacting() after joinOne preserves CTE structure', () => { + it('transacting() after joinOne preserves correlated relation SQL', () => { const sql = query(knex, User) .joinOne({ localColumn: t => t.departmentId, @@ -1493,15 +1543,16 @@ describe('transaction support', () => { foreignSchema: Department }) .transacting(trx) - .toQuery(); + .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read'); - expect(sql).toContain('with "originalQuery" as'); + expect(sql).toContain('to_jsonb'); expect(sql).toContain('"departments"'); - expect(sql).toContain('jsonb_agg'); + expect(sql).toContain('limit 1'); expect(sql).toContain('"department"'); }); - it('transacting() after joinMany preserves CTE structure', () => { + it('transacting() after joinMany preserves correlated relation SQL', () => { const sql = query(knex, User) .joinMany({ localColumn: t => t.id, @@ -1510,9 +1561,10 @@ describe('transaction support', () => { foreignSchema: Post }) .transacting(trx) - .toQuery(); + .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read'); - expect(sql).toContain('with "originalQuery" as'); + expect(sql).toContain('to_jsonb'); expect(sql).toContain('"posts"'); expect(sql).toContain('jsonb_agg'); expect(sql).toContain('coalesce'); @@ -1522,13 +1574,15 @@ describe('transaction support', () => { const plain = query(knex, User) .where(t => t.departmentId, '>', 3) .orderBy(t => t.fullName) - .toQuery(); + .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read'); const transacted = query(knex, User) .where(t => t.departmentId, '>', 3) .orderBy(t => t.fullName) .transacting(trx) - .toQuery(); + .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read'); expect(transacted).toBe(plain); }); @@ -1538,7 +1592,8 @@ describe('transaction support', () => { .transacting(trx) .where(t => t.role, '=', 'editor') .limit(20) - .toQuery(); + .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read'); expect(sql).toContain('"role" = \'editor\''); expect(sql).toContain('limit 20'); @@ -1556,7 +1611,15 @@ describe('transaction support', () => { it('withTransaction() factory produces same SQL as query(trx, schema)', () => { const dbTrx = db.withTransaction(trx); - expect(dbTrx(User).toQuery()).toBe(query(knex, User).toQuery()); + expect( + dbTrx(User) + .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read') + ).toBe( + query(knex, User) + .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read') + ); }); it('withTransaction() factory supports chaining', () => { @@ -1565,17 +1628,24 @@ describe('transaction support', () => { .where(t => t.role, '=', 'admin') .orderBy(t => t.createdAt, 'desc') .limit(5) - .toQuery(); + .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read'); expect(sql).toContain('"role" = \'admin\''); - expect(sql).toContain('order by "created_at" desc'); + expect(sql).toContain('order by "__schema_read"."created_at" desc'); expect(sql).toContain('limit 5'); }); it('withTransaction() does not affect the original bound factory', () => { - const plainSql = db(User).toQuery(); + const plainSql = db(User) + .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read'); db.withTransaction(trx); // should not mutate db - expect(db(User).toQuery()).toBe(plainSql); + expect( + db(User) + .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read') + ).toBe(plainSql); }); it('withTransaction() supports joinOne', () => { @@ -1587,7 +1657,8 @@ describe('transaction support', () => { as: 'department', foreignSchema: Department }) - .toQuery(); + .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read'); expect(sql).toContain('"departments"'); expect(sql).toContain('"department"'); @@ -1650,7 +1721,8 @@ describe('transaction support', () => { await db.transaction(async dbTrx => { sql = dbTrx(User) .where(t => t.role, '=', 'admin') - .toQuery(); + .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read'); }); } finally { spy.mockRestore(); @@ -1672,7 +1744,8 @@ describe('default extensions', () => { }).hasTableName('test'); const sql = db(Schema) .where(t => t.id, '=', '1234') - .toQuery(); + .toQuery() + .replace(/__schema_read_\d+/g, '__schema_read'); expect(sql).toContain('"id" = \'1234\''); }); }); diff --git a/libs/knex-schema/src/validate.ts b/libs/knex-schema/src/validate.ts index d54a9bf2..5a46cb73 100644 --- a/libs/knex-schema/src/validate.ts +++ b/libs/knex-schema/src/validate.ts @@ -15,7 +15,7 @@ import type { /** * Resolve foreignQuery: use the provided one, or auto-derive from * the foreign schema's tableName extension. - * Also normalizes SchemaQueryBuilder instances to raw Knex.QueryBuilder + * Also normalizes QuerySource instances to raw Knex.QueryBuilder * by calling `.toKnexQuery()` if available. */ function resolveForeignQuery( diff --git a/libs/mapper/README.md b/libs/mapper/README.md index 4f0a7a5a..f2e54b7a 100644 --- a/libs/mapper/README.md +++ b/libs/mapper/README.md @@ -246,7 +246,7 @@ typed as synchronous that returns a promise/thenable throws when invoked. `configureSync()` API. Configure once and reuse the same source/target schema instances. Queries can -provide their projection schema through `.withRowSchema().rowSchema`; see the +provide their projection schema through `.rowSchema`; see the [projection-aware read guide](../knex-schema/README.md#projection-aware-reads), including separate definition/mapping/service files and explicit polymorphic dispatch. The mapper performs no database calls or application enrichment. diff --git a/libs/orm/README.md b/libs/orm/README.md index 6573d147..3037edee 100644 --- a/libs/orm/README.md +++ b/libs/orm/README.md @@ -13,6 +13,8 @@ EF-Core-like typed ORM layer on top of [`@cleverbrush/knex-schema`](../knex-sche --- +For the breaking immutable-query release, read the [v5 migration guide](../knex-schema/MIGRATION-v5.md). All published Framework packages advance together. + ## Installation ```sh @@ -80,7 +82,7 @@ const updated = await db.users.save({ id: 1, email: 'alice@example.com', name: ' | `.include(t => t.rel)` | Eager-loads a relation (chainable) | | `.save(graph)` | Insert or update a row graph (transactional) | | `.ofVariant(key)` | Return a typed `VariantDbSet` scoped to a polymorphic variant | -| `.withRowSchema()` | Creates a detached read-only query with a matching result schema | +| `.rowSchema` | Automatically describes the decoded result; reading metadata runs no SQL | | `.query()` | Returns the underlying `EntityQuery` for advanced querying | | `.withTransaction(trx)` | Returns a new `DbSet` bound to an existing transaction | @@ -211,15 +213,17 @@ async function updateUser(userId: number) { --- -## Detached reads with result schemas +## Immutable queries and projection schemas -`db.users.withRowSchema()` enters an immutable read-only API whose `rowSchema` -matches its decoded selection and includes. Results remain **detached even when -the context uses `{ tracking: true }`**. This avoids attaching partial projections -as incomplete tracked entities. Ordinary entity queries keep their old behavior. +Every DbSet/query chain is immutable and automatically exposes `rowSchema`. +Full entity reads still participate in the identity map when `{ tracking: true }`. +Selected, grouped, distinct and raw results are detached; partial rows never +replace tracked entities. Entity objects themselves remain mutable: edit a full +entity and call `saveChanges()` as before. Reads, reloads and write-returning rows +now consistently use exact bigint/decimal strings, `Date` objects and SQL `null`. ```ts -const read = db.users.withRowSchema() +const read = db.users .select(u => ({ id: u.id, name: u.name })); const Source = read.rowSchema; const toDto = mapper().configure(Source, UserDto, m => m) @@ -232,11 +236,12 @@ decoded in one SQL statement. STI/CTI readers expose `variantRowSchemas` for explicit application mapping. See the [read-schema consumer guide](../knex-schema/README.md#projection-aware-reads) for numeric/null/date rules, examples and compatibility boundaries. -Detached ordinary readers also support grouped `where`/`andWhere`/`orWhere`, +Ordinary queries also support grouped `where`/`andWhere`/`orWhere`, IN/EXISTS subqueries, bound `whereRaw`/`orderByRaw`, and `ref(selector)` for quoted column references. These operations preserve the reader's `rowSchema` identity and work in nested relation customizers; polymorphic branches use `forVariant()`. -Group callbacks are synchronous and predicate-only. See +Group callbacks are synchronous, immutable and predicate-only; always return +the configured group. Conditional filters must reassign the returned query. See [filtering and ordering](../knex-schema/README.md#filtering-and-ordering-without-changing-the-result-schema) for scoped search, subquery snapshots, pagination, and raw-SQL boundaries. diff --git a/libs/orm/src/change-tracker.ts b/libs/orm/src/change-tracker.ts index 84726117..edbd7657 100644 --- a/libs/orm/src/change-tracker.ts +++ b/libs/orm/src/change-tracker.ts @@ -15,10 +15,13 @@ import { buildColumnMap, getPrimaryKeyColumns, - getRowVersionColumn + getRowVersionColumn, + getVariants, + query as schemaQuery } from '@cleverbrush/knex-schema'; import type { Knex } from 'knex'; import { ConcurrencyError, InvariantViolationError } from './errors.js'; +import { insertVariant } from './variant-write.js'; // --------------------------------------------------------------------------- // Types @@ -210,6 +213,12 @@ function snapshotEntity(entity: object, schema: any): Record { properties?: Record; }; const propKeys = new Set(Object.keys(introspected?.properties ?? {})); + const variants = getVariants(schema); + const variant = + variants?.variants[(entity as any)[variants.discriminatorKey]]; + if (variant) + for (const key of Object.keys(variant.schema.introspect().properties)) + propKeys.add(key); const snap: Record = {}; for (const [k, v] of Object.entries(entity as Record)) { if (propKeys.has(k)) snap[k] = v; @@ -498,16 +507,15 @@ export class ChangeTracker { const tableName = config.schema.getExtension?.('tableName') as string; if (!tableName || pkInfo.propertyKeys.length === 0) return; - const { propToCol, colToProp } = buildColumnMap(config.schema); - const qb = knex(tableName); + let qb = schemaQuery(knex, config.schema as any) + .unscoped() + .withDeleted(); const pkValues = extractPkValues( config.schema, entity as Record ); for (let i = 0; i < pkInfo.propertyKeys.length; i++) { - const colName = - propToCol.get(pkInfo.propertyKeys[i]) ?? pkInfo.propertyKeys[i]; - qb.andWhere(colName, pkValues[i] as any); + qb = qb.andWhere(pkInfo.propertyKeys[i], pkValues[i]); } const row = await qb.first(); if (!row) return; @@ -517,7 +525,7 @@ export class ChangeTracker { for (const [col, val] of Object.entries( row as Record )) { - mapped[colToProp.get(col) ?? col] = val; + mapped[col] = val; } // Refresh snapshot and rowVersion entry.originalSnapshot = snapshotEntity(entity, config.schema); @@ -667,6 +675,9 @@ export class ChangeTracker { } } + // Keep generated values local until every statement has committed. + // A later concurrency failure must not advance in-memory IDs/versions. + const committedValues = new Map>(); // 4. Execute all changes in a single transaction await knex.transaction(async (trx: Knex.Transaction) => { // Inserts (Added) @@ -678,27 +689,20 @@ export class ChangeTracker { ) as string; if (!tableName) continue; - const { propToCol } = buildColumnMap(config.schema); const current = entry.entity as Record; - const row: Record = {}; - for (const [propKey, val] of Object.entries(current)) { - const colName = propToCol.get(propKey) ?? propKey; - if (val !== undefined) row[colName] = val; - } - const result = await trx(tableName).insert(row).returning('*'); - const returned = - Array.isArray(result) && result.length > 0 - ? result[0] - : null; - if (returned && typeof returned === 'object') { - const { colToProp } = buildColumnMap(config.schema); - const mapped = entry.entity as Record; - for (const [col, val] of Object.entries( - returned as Record - )) { - mapped[colToProp.get(col) ?? col] = val; - } - } + const variants = getVariants(config.schema as any); + const returned = variants + ? await insertVariant( + trx, + config.schema, + String(current[variants.discriminatorKey]), + current, + trx + ) + : await schemaQuery(trx, config.schema as any).insert( + current as any + ); + if (returned) committedValues.set(current, returned); inserted++; } @@ -718,6 +722,23 @@ export class ChangeTracker { // Build the SET clause — only changed columns, excluding PK const pkPropSet = new Set(pkInfo.propertyKeys); const updateData: Record = {}; + const variant = getVariants(config.schema)?.variants[ + entry.variantKey ?? '' + ]; + const variantColumns = variant + ? buildColumnMap(variant.schema).propToCol + : new Map(); + const variantData: Record = {}; + const put = (key: string, value: unknown) => { + const variantColumn = + !propToCol.has(key) && variantColumns.get(key); + if (variantColumn && variant?.storage === 'cti') { + if (variantColumn !== variant.foreignKey) + variantData[variantColumn] = value; + } else + updateData[variantColumn || propToCol.get(key) || key] = + value; + }; for (const propKey of Object.keys(entry.originalSnapshot)) { if (pkPropSet.has(propKey)) continue; if ( @@ -726,8 +747,7 @@ export class ChangeTracker { current[propKey] ) ) { - updateData[propToCol.get(propKey) ?? propKey] = - current[propKey]; + put(propKey, current[propKey]); } } // Also pick up new keys not in snapshot @@ -737,7 +757,7 @@ export class ChangeTracker { !(propKey in entry.originalSnapshot) && val !== undefined ) { - updateData[propToCol.get(propKey) ?? propKey] = val; + put(propKey, val); } } @@ -747,18 +767,34 @@ export class ChangeTracker { const rvCol = propToCol.get(rv.propertyKey) ?? rv.propertyKey; if (rv.strategy === 'increment') { - const newVal = Number(rv.snapshotValue ?? 0) + 1; + const newVal = + typeof rv.snapshotValue === 'string' + ? (BigInt(rv.snapshotValue) + 1n).toString() + : Number(rv.snapshotValue ?? 0) + 1; + if ( + typeof newVal === 'number' && + !Number.isSafeInteger(newVal) + ) + throw new Error( + 'Row-version increment exceeds the safe integer range; use a bigint storage column' + ); updateData[rvCol] = newVal; - current[rv.propertyKey] = newVal; + committedValues.set(current, { + [rv.propertyKey]: newVal + }); } else if (rv.strategy === 'timestamp') { const now = new Date(); updateData[rvCol] = now; - current[rv.propertyKey] = now; + committedValues.set(current, { [rv.propertyKey]: now }); } // 'manual': caller already set the new value in current } - if (Object.keys(updateData).length === 0) continue; + if ( + Object.keys(updateData).length === 0 && + Object.keys(variantData).length === 0 + ) + continue; // Build WHERE clause with PK + optional rowVersion check let qb = trx(tableName); @@ -778,7 +814,11 @@ export class ChangeTracker { qb = qb.andWhere(rvCol, rv.snapshotValue as any) as any; } - const affected = await qb.update(updateData); + const affected = Object.keys(updateData).length + ? await qb.update(updateData) + : (await qb.forUpdate().first()) + ? 1 + : 0; if (affected === 0 && entry.rowVersion) { const tableName2 = config.schema.getExtension?.( 'tableName' @@ -789,6 +829,17 @@ export class ChangeTracker { entry.rowVersion.snapshotValue ); } + if ( + affected && + variant?.storage === 'cti' && + Object.keys(variantData).length + ) + await trx(variant.tableName!) + .where( + variant.foreignKey!, + current[pkInfo.propertyKeys[0]] as any + ) + .update(variantData); updated++; } @@ -836,6 +887,9 @@ export class ChangeTracker { } }); + for (const [entity, values] of committedValues) + Object.assign(entity, values); + // 5. Refresh snapshots for inserted and updated entries; detach deleted for (const entry of added) { entry.originalSnapshot = snapshotEntity( diff --git a/libs/orm/src/dbset.ts b/libs/orm/src/dbset.ts index 35110ac4..ff5c5787 100644 --- a/libs/orm/src/dbset.ts +++ b/libs/orm/src/dbset.ts @@ -8,15 +8,18 @@ // Calling any query method (`.where()`, `.include()`, `.first()`, `.insert()`, // etc.) on a `DbSet` allocates a fresh `SchemaQueryBuilder` and forwards the // call to it. Subsequent calls on the returned `EntityQuery` reuse that -// same underlying builder. +// independent immutable builders. import type { - ColumnRef, Entity, EntityRelations, EntitySchema, + PolymorphicQueryBuilder, PrimaryKeyValueOf, - SchemaAwareQuery + ReadQueryShape, + SchemaAwareQuery, + SchemaForValue, + VariantReadSchemas } from '@cleverbrush/knex-schema'; import { getPrimaryKeyColumns, @@ -24,7 +27,7 @@ import { type SchemaQueryBuilder, query as schemaQuery } from '@cleverbrush/knex-schema'; -import type { InferType } from '@cleverbrush/schema'; +import type { InferType, ObjectSchemaBuilder } from '@cleverbrush/schema'; import type { Knex } from 'knex'; import { EntityNotFoundError } from './errors.js'; @@ -37,7 +40,6 @@ import type { VariantInsertPayload, VariantResult, VariantUpdatePayload, - WithIncluded, WithVariantIncluded } from './result-types.js'; import { saveGraph } from './save-graph.js'; @@ -47,9 +49,45 @@ import { updateVariant as _updateVariant } from './variant-write.js'; +type EntityRowSchema = ObjectSchemaBuilder< + { [K in keyof T & string]-?: SchemaForValue }, + true, + false, + T +>; +type WriteMethod = + | 'insert' + | 'insertMany' + | 'update' + | 'delete' + | 'hardDelete' + | 'restore' + | 'bulkInsert' + | 'bulkUpdate' + | 'bulkUpsert' + | 'upsert' + | 'onConflict'; +type EntityWrites< + TEntity extends Entity, + TResult, + Writable extends boolean +> = { + [K in WriteMethod]: Writable extends true + ? OmitThisParameter< + SchemaQueryBuilder< + EntitySchema, + EntityRowSchema + >[K] + > + : never; +}; + // These methods return scalars or detached read plans, not tracked entity rows. const untrackedResultMethods = new Set([ - 'withRowSchema', + 'apply', + 'selectRaw', + 'toKnexQuery', + 'pluck', 'countValue', 'countDistinctValue', 'sumValue', @@ -73,30 +111,79 @@ const untrackedResultMethods = new Set([ * * @public */ -export interface EntityQuery, TResult> - extends Omit< - SchemaQueryBuilder, TResult>, - 'include' | 'includeVariant' | 'withRowSchema' - > { - /** - * Start an immutable detached read model with inferred runtime row schemas. - * Enable before select/include operations; results never attach to tracking. - */ - withRowSchema(): SchemaAwareQuery>; +export type EntityQuery< + TEntity extends Entity, + TResult, + Writable extends boolean = true +> = + SchemaAwareQuery> extends PolymorphicQueryBuilder< + any, + any + > + ? PolymorphicEntityQuery + : TableEntityQuery; + +/** A tracked-capable polymorphic read; projections use forVariant(). */ +export interface PolymorphicEntityQuery> + extends PolymorphicQueryBuilder>, + Pick< + TableEntityQuery>, + 'find' | 'findOrFail' | 'findMany' + > {} + +/** Table query with ORM identity tracking and primary-key lookup helpers. */ +export interface TableEntityQuery< + TEntity extends Entity, + TResult, + Writable extends boolean = true +> extends Omit< + SchemaQueryBuilder< + EntitySchema, + EntityRowSchema, + EntityRelations, + Writable + >, + 'include' | 'includeVariant' | WriteMethod + >, + EntityWrites { + /** Configure a discriminator branch with the canonical strongly typed query API. */ + forVariant: SchemaAwareQuery> extends { + forVariant: infer F; + } + ? F + : never; + /** Restrict a polymorphic query to declared variants and narrow its row schema. */ + selectVariants: SchemaAwareQuery> extends { + selectVariants: infer F; + } + ? F + : never; /** * Eager-load a relation declared on `TEntity` via `.hasOne()` / * `.hasMany()` / `.belongsTo()` / `.belongsToMany()`. Selector returns * the relation key as a string literal. */ - include & string>( + include< + K extends keyof EntityRelations & string, + Child extends ReadQueryShape = SchemaAwareQuery< + RelatedSchema + > + >( sel: (t: RelKeyTree) => K, customize?: ( - q: SchemaQueryBuilder< - RelatedSchema, - InferType> - > - ) => void - ): EntityQuery>; + query: SchemaAwareQuery> + ) => Child + ): EntityQuery< + TEntity, + TResult & { + [P in K]: EntityRelations[K] extends { + kind: 'hasMany' | 'belongsToMany'; + } + ? InferType[] + : InferType | null; + }, + false + >; /** * Eager-load a relation declared inside a polymorphic variant (CTI/STI). @@ -112,10 +199,10 @@ export interface EntityQuery, TResult> q: TRel extends keyof EntityRelations ? SchemaQueryBuilder< RelatedSchema, - InferType> + EntityRowSchema>> > : SchemaQueryBuilder - ) => void + ) => ReadQueryShape ): EntityQuery< TEntity, TRel extends keyof EntityRelations & string @@ -178,8 +265,14 @@ export interface EntityQuery, TResult> * * @public */ -export interface DbSet> - extends EntityQuery> { +export type DbSet> = EntityQuery< + TEntity, + EntityResult +> & + DbSetOperations; + +/** Entity registration, transactions and graph/variant write entry points. */ +export interface DbSetOperations> { /** The wrapped entity definition. */ readonly entity: TEntity; @@ -258,54 +351,13 @@ export interface DbSet> export interface VariantDbSet< TEntity extends Entity, K extends string -> extends Omit< - SchemaQueryBuilder, VariantResult>, - 'include' | 'includeVariant' | 'insert' | 'update' | 'delete' | 'where' +> extends PolymorphicQueryBuilder< + EntitySchema, + Pick< + VariantReadSchemas>, + Extract>> + > > { - // Re-declared so that `this` resolves to `VariantDbSet` - // rather than the raw `SchemaQueryBuilder` (Omit doesn't preserve `this`). - /** - * Add an AND filter on this polymorphic branch using a mapped property, record or Knex callback. - */ - where( - column: ColumnRef>, - operator: string, - value: any - ): this; - /** - * Add an AND filter on this polymorphic branch using a mapped property, record or Knex callback. - */ - where(column: ColumnRef>, value: any): this; - /** - * Add an AND filter on this polymorphic branch using a mapped property, record or Knex callback. - */ - where(raw: Knex.Raw, operator: string, value: any): this; - /** - * Add an AND filter on this polymorphic branch using a mapped property, record or Knex callback. - */ - where(callback: (builder: Knex.QueryBuilder) => void): this; - /** - * Add an AND filter on this polymorphic branch using a mapped property, record or Knex callback. - */ - where(record: Record): this; - /** - * Add an AND filter on this polymorphic branch using a mapped property, record or Knex callback. - */ - where(raw: Knex.Raw): this; - /** - * Eager-load a relation declared on `TEntity`. Identical to - * {@link EntityQuery.include}. - */ - include & string>( - sel: (t: RelKeyTree) => R, - customize?: ( - q: SchemaQueryBuilder< - RelatedSchema, - InferType> - > - ) => void - ): VariantDbSet; - /** Look up a single row by PK, typed to this variant. */ find( pk: PrimaryKeyValueOf> @@ -347,33 +399,6 @@ export interface VariantDbSet< // Runtime construction // --------------------------------------------------------------------------- -const ENTITY_KEY_TREE_CACHE = new WeakMap>(); - -function getEntityKeyTree( - entity: Entity -): Record { - const cached = ENTITY_KEY_TREE_CACHE.get(entity); - if (cached) return cached; - const props = - ( - entity.schema as { - introspect?: () => { properties?: Record }; - } - ).introspect?.()?.properties ?? {}; - const tree: Record = {}; - for (const k of Object.keys(props)) tree[k] = k; - ENTITY_KEY_TREE_CACHE.set(entity, tree); - return tree; -} - -/** - * Extract the Knex instance from a `SchemaQueryBuilder`. - * SchemaQueryBuilder stores it as `#knex` (private), but the `query()` - * factory function passes it as the first constructor argument. - * We retrieve it via the `.toQuery()` builder's knex client reference. - * @internal - */ - /** * Wrap a `SchemaQueryBuilder` in a Proxy that overlays typed * `.include()` / `.includeVariant()` methods and re-wraps `this`-returning @@ -382,7 +407,7 @@ function getEntityKeyTree( * @internal */ function wrapQuery, TResult>( - sqb: SchemaQueryBuilder, TResult>, + sqb: SchemaQueryBuilder, EntityRowSchema>, entity: TEntity, _knexInst: Knex, onResults?: (items: unknown[]) => unknown[] | undefined @@ -391,45 +416,6 @@ function wrapQuery, TResult>( sqb as unknown as object, { get(target, prop, receiver) { - if (prop === 'include') { - return ( - sel: (t: Record) => string, - customize?: (q: SchemaQueryBuilder) => void - ) => { - const name = sel(getEntityKeyTree(entity)); - ( - sqb as unknown as { - include: ( - n: string, - c?: ( - q: SchemaQueryBuilder - ) => void - ) => void; - } - ).include(name, customize); - return proxy; - }; - } - if (prop === 'includeVariant') { - return ( - variantKey: string, - relationName: string, - customize?: (q: SchemaQueryBuilder) => void - ) => { - ( - sqb as unknown as { - includeVariant: ( - v: string, - r: string, - c?: ( - q: SchemaQueryBuilder - ) => void - ) => void; - } - ).includeVariant(variantKey, relationName, customize); - return proxy; - }; - } if (prop === '_sqb') return sqb; if (prop === '_entity') return entity; @@ -466,9 +452,27 @@ function wrapQuery, TResult>( const value = Reflect.get(target, prop, receiver); if (typeof value !== 'function') return value; return function (this: unknown, ...args: unknown[]) { + if (prop === 'then') { + const [resolve, reject] = args; + return sqb + .execute() + .then(rows => { + if (!onResults || !sqb.returnsEntityRows) + return rows; + return onResults(rows) ?? rows; + }) + .then(resolve as any, reject as any); + } const result = (value as Function).apply(sqb, args); // Re-wrap `this`-returning methods so chains keep typing. if (result === sqb) return proxy; + if ( + result && + typeof result.execute === 'function' && + typeof result.sameSource === 'function' && + sqb.sameSource(result) + ) + return wrapQuery(result, entity, _knexInst, onResults); // Wrap Promise results to auto-attach tracked entities. if ( onResults != null && @@ -479,6 +483,21 @@ function wrapQuery, TResult>( ) { return (result as Promise).then( (resolved: unknown) => { + if ( + (prop === 'paginate' || + prop === 'paginateAfter') && + resolved && + typeof resolved === 'object' && + 'data' in resolved && + Array.isArray(resolved.data) + ) { + return { + ...resolved, + data: + onResults(resolved.data) ?? + resolved.data + }; + } if (Array.isArray(resolved)) { const entities = resolved.filter( r => @@ -544,9 +563,9 @@ function wrapQuery, TResult>( */ function makeFindMethod>( method: 'find' | 'findOrFail' | 'findMany', - sqb: SchemaQueryBuilder, + _sqb: SchemaQueryBuilder, entity: TEntity, - proxy: EntityQuery + proxy: any ): (...args: unknown[]) => Promise { return async (...args: unknown[]): Promise => { const pkInfo = getPrimaryKeyColumns( @@ -568,87 +587,51 @@ function makeFindMethod>( ).getExtension?.('tableName') ?? ''; const entityLabel = String(tableName); - const applyPkFilter = (pk: unknown): void => { + const tupleFor = (pk: unknown) => { const tuple = normalisePkTuple(pk, isComposite, method); - if (tuple.length !== propertyKeys.length) { + if (tuple.length !== propertyKeys.length) throw new Error( `${method}(): expected ${propertyKeys.length} primary-key value(s), got ${tuple.length}.` ); - } - for (let i = 0; i < propertyKeys.length; i++) { - ( - proxy as unknown as { - andWhere: ( - col: string, - op: string, - val: unknown - ) => void; - } - ).andWhere(propertyKeys[i], '=', tuple[i]); - } + return tuple; }; - if (method === 'findMany') { - const pks = (args[0] ?? []) as ReadonlyArray; - if (!Array.isArray(pks)) { + const pks = args[0] ?? []; + if (!Array.isArray(pks)) throw new Error( 'findMany(): expected an array of primary-key values.' ); - } - if (pks.length === 0) return []; - if (!isComposite) { - const propKey = propertyKeys[0]; - const tuples = pks.map(p => - normalisePkTuple(p, false, 'findMany') - ); - ( - proxy as unknown as { - whereIn: ( - col: string, - vals: readonly unknown[] - ) => void; - } - ).whereIn( - propKey, - tuples.map(t => t[0]) - ); - return await ( - proxy as unknown as { execute: () => Promise } - ).execute(); - } - // Composite PK — emit OR-grouped predicates. We must use - // COLUMN names (not property names) here because the inner - // knex `apply()` callback bypasses SchemaQueryBuilder's - // property-to-column translation. - const columnNames = pkInfo.columnNames; - ( - sqb as unknown as { - apply: (fn: (qb: Knex.QueryBuilder) => void) => void; - } - ).apply(qb => { - qb.andWhere(function (this: Knex.QueryBuilder) { - for (const pk of pks) { - const tuple = normalisePkTuple(pk, true, 'findMany'); - this.orWhere(function (this: Knex.QueryBuilder) { - for (let i = 0; i < columnNames.length; i++) { - this.andWhere(columnNames[i], tuple[i] as any); - } - }); - } - }); - }); - return await ( - proxy as unknown as { execute: () => Promise } - ).execute(); + if (!pks.length) return []; + const tuples = pks.map(tupleFor); + if (!isComposite) + return (proxy as any) + .whereIn( + propertyKeys[0], + tuples.map(tuple => tuple[0]) + ) + .execute(); + return (proxy as any) + .where((group: any) => + tuples.reduce( + (outer, tuple) => + outer.orWhere((inner: any) => + propertyKeys.reduce( + (query, key, i) => + query.where(key, tuple[i]), + inner + ) + ), + group + ) + ) + .execute(); } - - // find / findOrFail - applyPkFilter(args[0]); - const row = await ( - proxy as unknown as { - first: () => Promise; - } - ).first(); + const tuple = tupleFor(args[0]); + const filtered = propertyKeys.reduce( + (query, key, i) => query.andWhere(key, '=', tuple[i]), + proxy as any + ); + const row = await filtered.first(); if (row === undefined && method === 'findOrFail') { throw new EntityNotFoundError(entityLabel, args[0]); } @@ -700,9 +683,9 @@ export function makeDbSet>( entity.schema as EntitySchema ); return wrapQuery( - sqb as SchemaQueryBuilder< + sqb as unknown as SchemaQueryBuilder< EntitySchema, - EntityResult + EntityRowSchema> >, entity, knex, @@ -754,9 +737,9 @@ export function makeDbSet>( entity.schema as EntitySchema ); const query = wrapQuery( - fresh as SchemaQueryBuilder< + fresh as unknown as SchemaQueryBuilder< EntitySchema, - EntityResult + EntityRowSchema> >, entity, knex, @@ -789,7 +772,10 @@ function wrapVariantQuery< TEntity extends Entity, K extends string >( - sqb: SchemaQueryBuilder, VariantResult>, + sqb: SchemaQueryBuilder< + EntitySchema, + EntityRowSchema> + >, entity: TEntity, variantKey: K, knexInst: Knex, @@ -799,47 +785,6 @@ function wrapVariantQuery< sqb as unknown as object, { get(target, prop, receiver) { - // --- Typed include override (same as wrapQuery) --- - if (prop === 'include') { - return ( - sel: (t: Record) => string, - customize?: (q: SchemaQueryBuilder) => void - ) => { - const name = sel(getEntityKeyTree(entity)); - ( - sqb as unknown as { - include: ( - n: string, - c?: ( - q: SchemaQueryBuilder - ) => void - ) => void; - } - ).include(name, customize); - return proxy; - }; - } - if (prop === 'includeVariant') { - return ( - vk: string, - relationName: string, - customize?: (q: SchemaQueryBuilder) => void - ) => { - ( - sqb as unknown as { - includeVariant: ( - v: string, - r: string, - c?: ( - q: SchemaQueryBuilder - ) => void - ) => void; - } - ).includeVariant(vk, relationName, customize); - return proxy; - }; - } - if (prop === '_sqb') return sqb; if (prop === '_entity') return entity; @@ -963,8 +908,32 @@ function wrapVariantQuery< const value = Reflect.get(target, prop, receiver); if (typeof value !== 'function') return value; return function (this: unknown, ...args: unknown[]) { + if (prop === 'then') { + const [resolve, reject] = args; + return sqb + .execute() + .then(rows => { + if (!onResults || !sqb.returnsEntityRows) + return rows; + return onResults(rows) ?? rows; + }) + .then(resolve as any, reject as any); + } const result = (value as Function).apply(sqb, args); if (result === sqb) return proxy; + if ( + result && + typeof result.execute === 'function' && + typeof result.sameSource === 'function' && + sqb.sameSource(result) + ) + return wrapVariantQuery( + result, + entity, + variantKey, + knexInst, + onResults + ); if ( onResults != null && sqb.returnsEntityRows && @@ -974,6 +943,21 @@ function wrapVariantQuery< ) { return (result as Promise).then( (resolved: unknown) => { + if ( + (prop === 'paginate' || + prop === 'paginateAfter') && + resolved && + typeof resolved === 'object' && + 'data' in resolved && + Array.isArray(resolved.data) + ) { + return { + ...resolved, + data: + onResults(resolved.data) ?? + resolved.data + }; + } if (Array.isArray(resolved)) { const entities = resolved.filter( r => @@ -1055,19 +1039,13 @@ function makeVariantDbSet< } // Allocate a fresh SQB with the variant filter baked in, then // wrap it in the variant-aware query proxy. - const fresh = schemaQuery( - knex, - entity.schema as EntitySchema - ); - ( - fresh as unknown as { - selectVariants?: (keys: string[]) => void; - } - ).selectVariants?.([variantKey]); + const fresh = ( + schemaQuery(knex, entity.schema as any) as any + ).selectVariants([variantKey]); const query = wrapVariantQuery( - fresh as SchemaQueryBuilder< + fresh as unknown as SchemaQueryBuilder< EntitySchema, - VariantResult + EntityRowSchema> >, entity, variantKey, diff --git a/libs/orm/src/index.ts b/libs/orm/src/index.ts index e58fda81..ca290237 100644 --- a/libs/orm/src/index.ts +++ b/libs/orm/src/index.ts @@ -18,7 +18,14 @@ export type { TrackedDbContext } from './dbcontext.js'; export { createDb } from './dbcontext.js'; -export type { DbSet, EntityQuery, VariantDbSet } from './dbset.js'; +export type { + DbSet, + DbSetOperations, + EntityQuery, + PolymorphicEntityQuery, + TableEntityQuery, + VariantDbSet +} from './dbset.js'; export { ConcurrencyError, EntityNotFoundError, diff --git a/libs/orm/src/orm.test.ts b/libs/orm/src/orm.test.ts index 6e0149fe..ae5d3fe1 100644 --- a/libs/orm/src/orm.test.ts +++ b/libs/orm/src/orm.test.ts @@ -324,16 +324,16 @@ describe('EntityQuery proxy', () => { expect(typeof chain.toQuery).toBe('function'); }); - it('.include(selector) forwards the relation name and emits a JOIN', () => { + it('.include(selector) emits a correlated nested row', () => { const db = createDb(mock.knex, { todos: TodoEntity }); const sql = db.todos.include(t => t.author).toQuery(); - expect(sql.toLowerCase()).toContain('join'); + expect(sql.toLowerCase()).toContain('to_jsonb'); expect(sql).toContain('users'); }); it('.include(selector) accepts a customize callback', () => { const db = createDb(mock.knex, { todos: TodoEntity }); - const customize = vi.fn(); + const customize = vi.fn(q => q); db.todos.include(t => t.author, customize); expect(customize).toHaveBeenCalledOnce(); }); @@ -356,7 +356,7 @@ describe('EntityQuery proxy', () => { // than swallowing the call. const db = createDb(mock.knex, { todos: TodoEntity }); expect(() => db.todos.includeVariant('foo', 'author')).toThrow( - /not polymorphic/i + /includeVariant is not a function/i ); }); }); @@ -377,10 +377,12 @@ describe('DbSet.find / findOrFail / findMany', () => { // ---- single-PK happy paths --------------------------------------------- it('find(scalar) emits WHERE id = ? and returns the first row', async () => { - mock.responses.push([{ id: 42, email: 'a@b', name: 'A' }]); + mock.responses.push([ + { id: 42, email: 'a@b', name: 'A', createdAt: null } + ]); const db = createDb(mock.knex, { users: UserEntity }); const u = await db.users.find(42); - expect(u).toEqual({ id: 42, email: 'a@b', name: 'A' }); + expect(u).toEqual({ id: 42, email: 'a@b', name: 'A', createdAt: null }); expect(mock.captured).toHaveLength(1); expect(mock.captured[0].sql).toContain('"id" = $1'); expect(mock.captured[0].bindings).toContain(42); @@ -394,10 +396,12 @@ describe('DbSet.find / findOrFail / findMany', () => { }); it('findOrFail returns the row when present', async () => { - mock.responses.push([{ id: 1, email: 'x', name: 'Y' }]); + mock.responses.push([ + { id: 1, email: 'x', name: 'Y', createdAt: null } + ]); const db = createDb(mock.knex, { users: UserEntity }); const u = await db.users.findOrFail(1); - expect(u).toEqual({ id: 1, email: 'x', name: 'Y' }); + expect(u).toEqual({ id: 1, email: 'x', name: 'Y', createdAt: null }); }); it('findOrFail throws EntityNotFoundError when no row matches', async () => { @@ -427,8 +431,8 @@ describe('DbSet.find / findOrFail / findMany', () => { it('findMany on single-PK emits WHERE id IN (...)', async () => { mock.responses.push([ - { id: 1, email: 'a', name: 'A' }, - { id: 2, email: 'b', name: 'B' } + { id: 1, email: 'a', name: 'A', createdAt: null }, + { id: 2, email: 'b', name: 'B', createdAt: null } ]); const db = createDb(mock.knex, { users: UserEntity }); const rows = await db.users.findMany([1, 2]); @@ -440,10 +444,10 @@ describe('DbSet.find / findOrFail / findMany', () => { // ---- composite-PK paths ----------------------------------------------- it('find on composite-PK accepts a tuple', async () => { - mock.responses.push([{ postId: 1, tagId: 9 }]); + mock.responses.push([{ postId: 1, tagId: 9, addedAt: null }]); const db = createDb(mock.knex, { postTags: PostTagEntity }); const r = await db.postTags.find([1, 9]); - expect(r).toEqual({ postId: 1, tagId: 9 }); + expect(r).toEqual({ postId: 1, tagId: 9, addedAt: null }); expect(mock.captured[0].sql).toContain('"post_id"'); expect(mock.captured[0].sql).toContain('"tag_id"'); expect(mock.captured[0].bindings).toEqual( @@ -468,8 +472,8 @@ describe('DbSet.find / findOrFail / findMany', () => { it('findMany on composite-PK emits OR-grouped predicates', async () => { mock.responses.push([ - { postId: 1, tagId: 9 }, - { postId: 2, tagId: 9 } + { postId: 1, tagId: 9, addedAt: null }, + { postId: 2, tagId: 9, addedAt: null } ]); const db = createDb(mock.knex, { postTags: PostTagEntity }); const rows = await db.postTags.findMany([ @@ -636,7 +640,9 @@ describe('DbSet.save — graph persistence', () => { it('update path: PK present → emits UPDATE, no INSERT for root', async () => { stubTransaction(); // The update returns the updated row. - mock.responses.push([{ id: 5, name: 'X', email: 'x@y' }]); + mock.responses.push([ + { id: 5, name: 'X', email: 'x@y', createdAt: null } + ]); const db = createDb(mock.knex, { users: UserEntity }); const out = await db.users.save({ id: 5, @@ -657,7 +663,7 @@ describe('DbSet.save — graph persistence', () => { stubTransaction(); // Root todo insert. mock.responses.push([ - { id: 21, title: 'T', userId: null, completed: false } + { id: 21, title: 'T', userId: 0, completed: false } ]); // Pivot insert (todo_tags) returns []; we don't read it. mock.responses.push([]); @@ -687,7 +693,9 @@ describe('DbSet.save — graph persistence', () => { (mock.knex as any).isTransaction = true; const txSpy = vi.spyOn(mock.knex, 'transaction'); - mock.responses.push([{ id: 9, email: 'x', name: 'Y' }]); + mock.responses.push([ + { id: 9, email: 'x', name: 'Y', createdAt: null } + ]); const db = createDb(mock.knex, { users: UserEntity }); await db.users .withTransaction(mock.knex as unknown as KnexT.Transaction) @@ -748,7 +756,7 @@ describe('DbSet.save — graph persistence', () => { stubTransaction(); // Root todo insert. mock.responses.push([ - { id: 41, title: 'T', userId: null, completed: false } + { id: 41, title: 'T', userId: 0, completed: false } ]); // Tag insert (because PK was not supplied → create new row). mock.responses.push([{ id: 77, name: 'urgent' }]); @@ -885,8 +893,18 @@ describe('DbSet.ofVariant — insert', () => { stubTransaction(); // Base row insert returns generated PK. mock.responses.push([{ id: 5, type: 'assigned', todo_id: 42 }]); - // Variant row insert. + // Variant row insert, then read-back of the completed branch. mock.responses.push([]); + mock.responses.push([ + { + __read_poly: { + id: 5, + type: 'assigned', + todoId: 42, + assigneeId: 9 + } + } + ]); const db = createDb(mock.knex, { activities: ActivityEntityCTI }); const result = await db.activities.ofVariant('assigned').insert({ @@ -914,6 +932,9 @@ describe('DbSet.ofVariant — insert', () => { stubTransaction(); mock.responses.push([{ id: 7, type: 'commented', todo_id: 1 }]); mock.responses.push([]); + mock.responses.push([ + { __read_poly: { id: 7, type: 'commented', todoId: 1, body: 'hi' } } + ]); const db = createDb(mock.knex, { activities: ActivityEntityCTI }); await db.activities.ofVariant('commented').insert({ @@ -939,9 +960,9 @@ describe('DbSet.ofVariant — insert', () => { it('throws when the variant key is unknown', async () => { stubTransaction(); const db = createDb(mock.knex, { activities: ActivityEntitySTI }); - await expect( + expect(() => db.activities.ofVariant('nonexistent' as any).insert({}) - ).rejects.toThrow(/unknown/i); + ).toThrow(/declared variants/i); }); }); @@ -967,7 +988,15 @@ describe('VariantDbSet.update', () => { it('STI: emits an UPDATE on the base table filtered by discriminator', async () => { // First query: execute() to collect PKs → returns matching rows. mock.responses.push([ - { id: 3, type: 'assigned', todo_id: 10, user_id: 1, assignee_id: 4 } + { + __read_poly: { + id: 3, + type: 'assigned', + todoId: 10, + userId: 1, + assigneeId: 4 + } + } ]); // Second query: the UPDATE itself. mock.responses.push([]); @@ -987,7 +1016,16 @@ describe('VariantDbSet.update', () => { it('CTI: emits an UPDATE on the variant table', async () => { stubTransaction(); // execute() returns matched base-table rows. - mock.responses.push([{ id: 5, type: 'assigned', todo_id: 1 }]); + mock.responses.push([ + { + __read_poly: { + id: 5, + type: 'assigned', + todoId: 1, + assigneeId: 4 + } + } + ]); // UPDATE on the variant table. mock.responses.push([]); @@ -1040,7 +1078,17 @@ describe('VariantDbSet.delete', () => { it('STI: emits a DELETE on the base table with discriminator filter', async () => { stubTransaction(); // execute() to collect PKs. - mock.responses.push([{ id: 2, type: 'commented', todo_id: 1 }]); + mock.responses.push([ + { + __read_poly: { + id: 2, + type: 'commented', + todoId: 1, + userId: 7, + body: 'hi' + } + } + ]); // The DELETE. mock.responses.push([]); @@ -1058,7 +1106,16 @@ describe('VariantDbSet.delete', () => { it('CTI: deletes variant row first then base row', async () => { stubTransaction(); // execute() → matched base rows. - mock.responses.push([{ id: 7, type: 'assigned', todo_id: 3 }]); + mock.responses.push([ + { + __read_poly: { + id: 7, + type: 'assigned', + todoId: 3, + assigneeId: 4 + } + } + ]); // DELETE from variant table. mock.responses.push([]); // DELETE from base table. @@ -1107,7 +1164,15 @@ describe('VariantDbSet.find', () => { it('returns the matched row', async () => { mock.responses.push([ - { id: 3, type: 'assigned', todo_id: 5, user_id: 1, assignee_id: 2 } + { + __read_poly: { + id: 3, + type: 'assigned', + todoId: 5, + userId: 1, + assigneeId: 2 + } + } ]); const db = createDb(mock.knex, { activities: ActivityEntitySTI }); @@ -1184,8 +1249,12 @@ describe('Tracked DbContext', () => { // ------------------------------------------------------------------------- it('querying the same PK twice returns the same object reference', async () => { - mock.responses.push([{ id: 1, email: 'a@b', name: 'A' }]); - mock.responses.push([{ id: 1, email: 'a@b', name: 'A' }]); + mock.responses.push([ + { id: 1, email: 'a@b', name: 'A', createdAt: null } + ]); + mock.responses.push([ + { id: 1, email: 'a@b', name: 'A', createdAt: null } + ]); const db = createDb( mock.knex, @@ -1200,8 +1269,8 @@ describe('Tracked DbContext', () => { it('rows returned from all() are attached to the tracker', async () => { mock.responses.push([ - { id: 1, email: 'a@b', name: 'A' }, - { id: 2, email: 'c@d', name: 'B' } + { id: 1, email: 'a@b', name: 'A', createdAt: null }, + { id: 2, email: 'c@d', name: 'B', createdAt: null } ]); const db = createDb( @@ -1212,7 +1281,9 @@ describe('Tracked DbContext', () => { const rows = (await db.users.execute()) as any[]; // Querying one of the same PKs should return the existing object. - mock.responses.push([{ id: 1, email: 'a@b', name: 'A' }]); + mock.responses.push([ + { id: 1, email: 'a@b', name: 'A', createdAt: null } + ]); const reloaded = await db.users.find(1); expect(reloaded).toBe(rows[0]); }); @@ -1227,7 +1298,7 @@ describe('Tracked DbContext', () => { { users: UserEntity }, { tracking: true } ); - const user = { id: 10, email: 'x@y', name: 'X' }; + const user = { id: 10, email: 'x@y', name: 'X', createdAt: null }; db.attach('users', user); const e = db.entry(user); expect(e.state).toBe('Unchanged'); @@ -1240,8 +1311,8 @@ describe('Tracked DbContext', () => { { users: UserEntity }, { tracking: true } ); - const u1 = { id: 5, email: 'a@b', name: 'A' }; - const u2 = { id: 5, email: 'c@d', name: 'C' }; // same PK, different object + const u1 = { id: 5, email: 'a@b', name: 'A', createdAt: null }; + const u2 = { id: 5, email: 'c@d', name: 'C', createdAt: null }; // same PK, different object db.attach('users', u1); const returned = db.attach('users', u2); expect(returned).toBe(u1); // identity-map: existing wins @@ -1253,7 +1324,12 @@ describe('Tracked DbContext', () => { { users: UserEntity }, { tracking: true } ); - const user = { id: 1, email: 'a@b.com', name: 'Alice' }; + const user = { + id: 1, + email: 'a@b.com', + name: 'Alice', + createdAt: null + }; db.attach('users', user); const original = db.entry(user).originalValues; @@ -1267,7 +1343,7 @@ describe('Tracked DbContext', () => { { users: UserEntity }, { tracking: true } ); - const user = { id: 1, email: 'a@b', name: 'A' }; + const user = { id: 1, email: 'a@b', name: 'A', createdAt: null }; db.attach('users', user); expect(db.entry(user).isModified()).toBe(false); }); @@ -1278,7 +1354,7 @@ describe('Tracked DbContext', () => { { users: UserEntity }, { tracking: true } ); - const user = { id: 1, email: 'a@b', name: 'A' }; + const user = { id: 1, email: 'a@b', name: 'A', createdAt: null }; db.attach('users', user); user.name = 'Changed'; expect(db.entry(user).isModified()).toBe(true); @@ -1292,7 +1368,7 @@ describe('Tracked DbContext', () => { { users: UserEntity }, { tracking: true } ); - const user = { id: 1, email: 'a@b', name: 'A' }; + const user = { id: 1, email: 'a@b', name: 'A', createdAt: null }; db.attach('users', user); user.name = 'Changed'; db.entry(user).reset(); @@ -1306,7 +1382,7 @@ describe('Tracked DbContext', () => { { users: UserEntity }, { tracking: true } ); - const user = { id: 99, email: 'x', name: 'X' }; + const user = { id: 99, email: 'x', name: 'X', createdAt: null }; expect(() => db.entry(user)).toThrow(/not tracked/i); }); @@ -1316,7 +1392,7 @@ describe('Tracked DbContext', () => { { users: UserEntity }, { tracking: true } ); - const user = { id: 1, email: 'a@b', name: 'A' }; + const user = { id: 1, email: 'a@b', name: 'A', createdAt: null }; db.attach('users', user); db.detach(user); expect(() => db.entry(user)).toThrow(/not tracked/i); @@ -1357,7 +1433,7 @@ describe('Tracked DbContext', () => { { users: UserEntity }, { tracking: true } ); - const user = { id: 1, email: 'a@b', name: 'A' }; + const user = { id: 1, email: 'a@b', name: 'A', createdAt: null }; db.attach('users', user); db.remove(user); expect(db.entry(user).state).toBe('Deleted'); @@ -1369,7 +1445,9 @@ describe('Tracked DbContext', () => { { users: UserEntity }, { tracking: true } ); - expect(() => db.remove({ id: 1, email: 'x', name: 'X' })).toThrow(); + expect(() => + db.remove({ id: 1, email: 'x', name: 'X', createdAt: null }) + ).toThrow(); }); // ------------------------------------------------------------------------- @@ -1378,14 +1456,16 @@ describe('Tracked DbContext', () => { it('saveChanges() detects silently mutated Unchanged entries and emits UPDATE', async () => { stubTransaction(); - mock.responses.push([{ id: 1, email_address: 'a@b', name: 'Updated' }]); + mock.responses.push([ + { id: 1, email_address: 'a@b', name: 'Updated', created_at: null } + ]); const db = createDb( mock.knex, { users: UserEntity }, { tracking: true } ); - const user = { id: 1, email: 'a@b', name: 'A' }; + const user = { id: 1, email: 'a@b', name: 'A', createdAt: null }; db.attach('users', user); user.name = 'Updated'; // mutate without calling any set-state method @@ -1406,7 +1486,7 @@ describe('Tracked DbContext', () => { { users: UserEntity }, { tracking: true } ); - const user = { id: 1, email: 'a@b', name: 'A' }; + const user = { id: 1, email: 'a@b', name: 'A', createdAt: null }; db.attach('users', user); const result = await db.saveChanges(); @@ -1420,7 +1500,9 @@ describe('Tracked DbContext', () => { it('saveChanges() inserts Added entities', async () => { stubTransaction(); - mock.responses.push([{ id: 42, email_address: 'new@e', name: 'New' }]); + mock.responses.push([ + { id: 42, email_address: 'new@e', name: 'New', created_at: null } + ]); const db = createDb( mock.knex, @@ -1452,7 +1534,7 @@ describe('Tracked DbContext', () => { { users: UserEntity }, { tracking: true } ); - const user = { id: 3, email: 'x@y', name: 'X' }; + const user = { id: 3, email: 'x@y', name: 'X', createdAt: null }; db.attach('users', user); db.remove(user); @@ -1474,7 +1556,7 @@ describe('Tracked DbContext', () => { { users: UserEntity }, { tracking: true } ); - const user = { id: 1, email: 'a@b', name: 'A' }; + const user = { id: 1, email: 'a@b', name: 'A', createdAt: null }; db.attach('users', user); user.name = 'B'; @@ -1494,7 +1576,7 @@ describe('Tracked DbContext', () => { { users: UserEntity }, { tracking: true } ); - const user = { id: 1, email: 'a@b', name: 'A' }; + const user = { id: 1, email: 'a@b', name: 'A', createdAt: null }; db.attach('users', user); (user as any).id = 99; // mutate PK @@ -1513,7 +1595,7 @@ describe('Tracked DbContext', () => { { users: UserEntity }, { tracking: true } ); - const user = { id: 1, email: 'a@b', name: 'A' }; + const user = { id: 1, email: 'a@b', name: 'A', createdAt: null }; db.attach('users', user); user.name = 'Changed'; @@ -1528,7 +1610,7 @@ describe('Tracked DbContext', () => { { users: UserEntity }, { tracking: true } ); - const user = { id: 1, email: 'a@b', name: 'A' }; + const user = { id: 1, email: 'a@b', name: 'A', createdAt: null }; db.attach('users', user); db.remove(user); expect(db.entry(user).state).toBe('Deleted'); @@ -1550,7 +1632,7 @@ describe('Tracked DbContext', () => { { users: UserEntity }, { tracking: true } ); - const user = { id: 1, email: 'a@b', name: 'A' }; + const user = { id: 1, email: 'a@b', name: 'A', createdAt: null }; db.attach('users', user); user.name = 'B'; @@ -1569,7 +1651,7 @@ describe('Tracked DbContext', () => { it('reload() refreshes entity values from DB', async () => { mock.responses.push([ - { id: 1, email_address: 'new@b', name: 'Refreshed' } + { id: 1, email: 'new@b', name: 'Refreshed', createdAt: null } ]); const db = createDb( @@ -1577,7 +1659,7 @@ describe('Tracked DbContext', () => { { users: UserEntity }, { tracking: true } ); - const user = { id: 1, email: 'a@b', name: 'A' }; + const user = { id: 1, email: 'a@b', name: 'A', createdAt: null }; db.attach('users', user); user.name = 'Dirty'; // simulate dirty state @@ -1616,7 +1698,7 @@ describe('Tracked DbContext', () => { { users: UserEntity }, { tracking: true } ); - const user = { id: 1, email: 'a@b', name: 'A' }; + const user = { id: 1, email: 'a@b', name: 'A', createdAt: null }; db.attach('users', user); // No mutations → dispose should not throw. await expect(db[Symbol.asyncDispose]()).resolves.toBeUndefined(); @@ -1628,7 +1710,7 @@ describe('Tracked DbContext', () => { { users: UserEntity }, { tracking: true } ); - const user = { id: 1, email: 'a@b', name: 'A' }; + const user = { id: 1, email: 'a@b', name: 'A', createdAt: null }; db.attach('users', user); user.name = 'Dirty'; @@ -1643,7 +1725,7 @@ describe('Tracked DbContext', () => { { users: UserEntity }, { tracking: true } ); - const user = { id: 1, email: 'a@b', name: 'A' }; + const user = { id: 1, email: 'a@b', name: 'A', createdAt: null }; db.attach('users', user); user.name = 'Dirty'; @@ -2022,7 +2104,9 @@ describe('Tracked DbContext — first() single result onResults (line 481)', () }); it('first() in a tracked context attaches the resolved single object', async () => { - mock.responses.push([{ id: 2, email_address: 'b@b', name: 'Bob' }]); + mock.responses.push([ + { id: 2, email: 'b@b', name: 'Bob', createdAt: null } + ]); const db = createDb( mock.knex, @@ -2061,7 +2145,12 @@ describe('Tracked DbContext — reload() early returns', () => { { users: UserEntity }, { tracking: true } ); - const untracked = { id: 99, email: 'x@y.com', name: 'X' }; + const untracked = { + id: 99, + email: 'x@y.com', + name: 'X', + createdAt: null + }; // Should not throw and should not emit any SQL. await db.reload(untracked); expect(mock.captured).toHaveLength(0); @@ -2100,7 +2189,12 @@ describe('Tracked DbContext — attach() same-object snapshot refresh', () => { { users: UserEntity }, { tracking: true } ); - const user = { id: 1, email: 'a@b.com', name: 'Alice' }; + const user = { + id: 1, + email: 'a@b.com', + name: 'Alice', + createdAt: null + }; db.attach('users', user); // Dirty the entity. @@ -2231,7 +2325,12 @@ describe('Tracked DbContext — pendingSummary() branch coverage', () => { { users: UserEntity }, { tracking: true } ); - const user = { id: 1, email: 'a@b.com', name: 'Alice' }; + const user = { + id: 1, + email: 'a@b.com', + name: 'Alice', + createdAt: null + }; db.attach('users', user); // Silently mutate (no explicit state change — isDirty triggers Modified count). @@ -2248,7 +2347,7 @@ describe('Tracked DbContext — pendingSummary() branch coverage', () => { { users: UserEntity }, { tracking: true } ); - const user = { id: 2, email: 'b@c.com', name: 'Bob' }; + const user = { id: 2, email: 'b@c.com', name: 'Bob', createdAt: null }; db.attach('users', user); db.remove(user); diff --git a/libs/orm/src/query-types.test-d.ts b/libs/orm/src/query-types.test-d.ts index a52747f4..d794583e 100644 --- a/libs/orm/src/query-types.test-d.ts +++ b/libs/orm/src/query-types.test-d.ts @@ -35,6 +35,7 @@ test('include customizers know their relation schema', () => { owners.where(t => t.name, 'Alice'); // @ts-expect-error field belongs to tasks, not users owners.where(t => t.ownerId, 1); + return owners.where(t => t.name, 'Alice'); } ); }); diff --git a/libs/orm/src/result-types.ts b/libs/orm/src/result-types.ts index 7029d7a3..94e67a4e 100644 --- a/libs/orm/src/result-types.ts +++ b/libs/orm/src/result-types.ts @@ -8,7 +8,8 @@ import type { Entity, EntityRelations, EntitySchema, - RelationInfo + RelationInfo, + SchemaAwareQuery } from '@cleverbrush/knex-schema'; import type { InferType, ObjectSchemaBuilder } from '@cleverbrush/schema'; @@ -28,12 +29,9 @@ import type { InferType, ObjectSchemaBuilder } from '@cleverbrush/schema'; * * @public */ -export type EntityResult> = - TEntity extends Entity - ? [U] extends [never] - ? InferType> - : U - : InferType>; +export type EntityResult> = InferType< + SchemaAwareQuery>['rowSchema'] +>; /** * Discriminated-union variant rows for a polymorphic entity. Resolves to @@ -45,7 +43,7 @@ export type EntityResultByVariant> = TEntity extends Entity ? [U] extends [never] ? never - : U + : EntityResult : never; /** diff --git a/libs/orm/src/save-graph.ts b/libs/orm/src/save-graph.ts index 7509a54d..146c2a5c 100644 --- a/libs/orm/src/save-graph.ts +++ b/libs/orm/src/save-graph.ts @@ -152,7 +152,7 @@ async function saveNode( // 2. Save self (insert vs update from PK presence). // --------------------------------------------------------------------- const isUpdate = hasFullPk(schema, ownFields); - const sqb = schemaQuery(trx, schema) as unknown as { + let sqb = schemaQuery(trx, schema) as unknown as { andWhere: (col: string, op: string, val: unknown) => unknown; update: (data: unknown) => Promise; insert: (data: unknown) => Promise; @@ -165,7 +165,7 @@ async function saveNode( const updateData: Record = { ...ownFields }; for (const k of pk.propertyKeys) delete updateData[k]; for (const k of pk.propertyKeys) { - sqb.andWhere(k, '=', ownFields[k]); + sqb = sqb.andWhere(k, '=', ownFields[k]) as typeof sqb; } // SchemaQueryBuilder.update returns the updated row(s). const result = (await sqb.update(updateData)) as unknown; diff --git a/libs/orm/src/variant-write.ts b/libs/orm/src/variant-write.ts index bf9dfd39..0f4eb8e6 100644 --- a/libs/orm/src/variant-write.ts +++ b/libs/orm/src/variant-write.ts @@ -9,14 +9,32 @@ import { buildColumnMap, getPrimaryKeyColumns, getVariants, + object, query as schemaQuery } from '@cleverbrush/knex-schema'; +import type { ObjectSchemaBuilder } from '@cleverbrush/schema'; import type { Knex } from 'knex'; // --------------------------------------------------------------------------- // Internal helpers // --------------------------------------------------------------------------- +/** Keep table metadata and hooks without constructing a polymorphic reader. */ +function storageSchema( + schema: any, + properties = schema.introspect().properties +): ObjectSchemaBuilder { + let stored: ObjectSchemaBuilder = + object(properties); + for (const [key, value] of Object.entries( + schema.introspect().extensions ?? {} + )) { + if (key !== 'variants' && key !== 'polymorphicVariants') + stored = stored.withExtension(key, value) as typeof stored; + } + return stored; +} + /** * Resolve the variant config for a schema, throwing if the schema is not * polymorphic or if the requested variant key is unknown. @@ -90,10 +108,13 @@ export async function insertVariant( if (spec.storage === 'sti') { // Single-table: insert into base table with discriminator column const row = { ...payload, [discKey]: variantKey }; - const sqb = schemaQuery(t, schema) as unknown as { - insert: (data: unknown) => Promise>; + const props = { + ...schema.introspect().properties, + ...spec.schema.introspect().properties }; - return (await sqb.insert(row)) ?? row; + const stored = storageSchema(schema, props); + const result = await schemaQuery(t, stored).insert(row as any); + return result as Record; } // CTI: two-table insert @@ -129,20 +150,11 @@ export async function insertVariant( // Keys that match neither schema are silently dropped } - // 1. Insert base row using raw knex (not SchemaQueryBuilder) to avoid - // polymorphic result-resolution running before the variant row exists. - const { propToCol: basePropToCol } = buildColumnMap(schema); - const baseRowForInsert: Record = {}; - for (const [propKey, val] of Object.entries(basePayload)) { - baseRowForInsert[basePropToCol.get(propKey) ?? propKey] = val; - } - const baseInsertResult = await (t as unknown as Knex)(baseTableName) - .insert(baseRowForInsert) - .returning('*'); - const baseRow: Record = - Array.isArray(baseInsertResult) && baseInsertResult.length > 0 - ? (baseInsertResult[0] as Record) - : baseRowForInsert; + // 1. Decode base RETURNING values before using the PK. In particular, + // bigint IDs must be text-cast in SQL before driver parsers can round them. + const baseRow = (await schemaQuery(t, storageSchema(schema)).insert( + basePayload as any + )) as Record; // Resolve the base PK value from the returned row const pkInfo = getPrimaryKeyColumns(schema); @@ -179,17 +191,17 @@ export async function insertVariant( } await (t as unknown as Knex)(variantTableName).insert(variantRow); - // 3. Merge and return - const { colToProp: baseColToProp } = buildColumnMap(schema); - const result: Record = {}; - for (const [col, val] of Object.entries(baseRow)) { - result[baseColToProp.get(col) ?? col] = val; - } - // Ensure discriminator and variant payload are in the result - result[discKey] = variantKey; - for (const [propKey, val] of Object.entries(variantPayload)) { - result[propKey] = val; - } + // Read the completed branch inside the same transaction for one consistent storage representation. + const result = await (schemaQuery(t, schema) as any) + .selectVariants([variantKey]) + .unscoped() + .withDeleted() + .where(pkPropKey, pkValue) + .first(); + if (!result) + throw new Error( + 'insertVariant: inserted row could not be read back' + ); return result; }; diff --git a/websites/docs/app/knex-schema/page.tsx b/websites/docs/app/knex-schema/page.tsx index 1be30d69..de0e6219 100644 --- a/websites/docs/app/knex-schema/page.tsx +++ b/websites/docs/app/knex-schema/page.tsx @@ -228,9 +228,10 @@ returning *`

.joinOne() and .joinMany(){' '} load related rows in a{' '} - single PostgreSQL query using CTEs and{' '} - jsonb_agg. The inferred TypeScript type is - updated automatically for each join you add. + single PostgreSQL query using + correlated subqueries and jsonb_agg. The + inferred TypeScript type is updated automatically for + each join you add.

                          t.id,
         foreignColumn: t => t.authorId,
         as:            'posts',
-        limit:         5,
-        orderBy:       { column: t => t.id, direction: 'desc' },
-    });
+    }, posts => posts.orderBy(t => t.id, 'desc').limit(5));
 // users[0].posts → Array<{ id: number; title: string; authorId: number }>
 
 // Many-to-one — attach the author to each post
@@ -298,22 +297,24 @@ const posts = await query(db, PostSchema)
                 

Escape Hatch

- Use .apply(fn) to call any Knex method not - exposed by this API — the raw{' '} - Knex.QueryBuilder is passed to your + Use .apply(fn, {'{ output }'}) with a + complete output schema for raw SQL. The independently + mutable Knex.QueryBuilder is passed to your callback:

                          t.id, id)
-    .apply(qb => qb.forUpdate().noWait());
+    .apply(qb => qb.clearSelect().select({ name: 'first_name' }), {
+        output: UserName,
+    });
 
-// Pre-scoped base query (e.g. soft-delete filter)
-const base = db('users').where('deleted_at', null);
-const activeUsers = await query(db, UserSchema, base)
-    .where(t => t.age, '>', 18);`)
+// Share an immutable Framework base query; retain each configured result.
+const base = query(db, UserSchema).whereNull(t => t.deletedAt);
+const activeUsers = await base.where(t => t.age, '>', 18);`)
                             }}
                         />
                     
@@ -373,7 +374,7 @@ const average = await query(knex, TaskSchema).avgValue(t => t.estimate, { }} />

- Existing APIs remain unchanged. Read the{' '} + Queries are immutable in this major release. Read the{' '} complete query guide {' '} @@ -382,18 +383,19 @@ const average = await query(knex, TaskSchema).avgValue(t => t.estimate, {

-

Opt-in projection-aware reads

+

Automatic projection-aware schemas

- Start with withRowSchema() before selecting or including - fields. The immutable PostgreSQL reader exposes the - actual decoded result schema: SQL null stays null, dates - are Date objects at every depth, and decimal/bigint - values are exact strings before JSON parsing. + Every query exposes rowSchema automatically; no extra + mode switch is needed. Immutable PostgreSQL queries + describe the actual decoded result schema: SQL null + stays null, dates are Date objects at every depth, and + decimal/bigint values are exact strings before JSON + parsing.

                          ({ title: t.title, amount: t.amount }));
 const Source = read.rowSchema;
 const rows = await read.where(t => t.id, taskId);`)
@@ -404,8 +406,8 @@ const rows = await read.where(t => t.id, taskId);`)
                         Typed aliases, aggregates, named projections and nested
                         graphs retain their selected shape. Polymorphic readers
                         expose a union rowSchema and per-variant object schemas.
-                        Raw shapes are rejected; existing query behavior is
-                        unchanged.
+                        Raw shapes require an explicit Framework output schema.
+                        Retain every returned builder when configuring a query.
                     

Read representation, pagination and migration details @@ -423,7 +425,7 @@ const rows = await read.where(t => t.id, taskId);`)
                          ({ id: t.id, title: t.title }));
 const read = base.where(t => t.projectId, projectId)
     .andWhere(group => group
@@ -441,8 +443,9 @@ const read = base.where(t => t.projectId, projectId)
                         predicates. Subquery SQL and bindings are captured on
                         attachment, with no database execution. Use ref() for
                         quoted columns and generated aliases; keep values in
-                        bindings and raw SQL fragments application-authored.
-                        Unrestricted apply() remains unavailable.
+                        bindings and raw SQL fragments application-authored. Raw
+                        apply() requires an explicit output schema. Return the
+                        configured group from every predicate callback.
                     

Multi-file examples and compatibility boundaries @@ -571,7 +574,7 @@ const read = base.where(t => t.projectId, projectId) Escape hatch - .apply(fn),{' '} + .apply(fn, {'{ output }'}),{' '} .toQuery(),{' '} .toString() diff --git a/websites/docs/app/mapper/page.tsx b/websites/docs/app/mapper/page.tsx index fdf8aed4..9df84e73 100644 --- a/websites/docs/app/mapper/page.tsx +++ b/websites/docs/app/mapper/page.tsx @@ -229,7 +229,7 @@ const registry = mapper()
                          ({ id: u.id, name: u.name }));
 const Source = read.rowSchema; // no SQL
 const toDto = mapper().configure(Source, UserDto, m => m)
diff --git a/websites/docs/app/orm/page.tsx b/websites/docs/app/orm/page.tsx
index 610c276b..58be4c79 100644
--- a/websites/docs/app/orm/page.tsx
+++ b/websites/docs/app/orm/page.tsx
@@ -244,16 +244,16 @@ try {
                 

Detached reads with projection schemas

- db.users.withRowSchema() returns an immutable read-only - query with runtime metadata matching its projection and - relation graph. Results stay detached even in a tracking - context, so partial selections cannot silently become - incomplete tracked entities. + Every DbSet query is immutable, with metadata matching + its decoded projection and relations. Full entities + retain identity tracking when enabled. Projected, + grouped, distinct and raw results remain detached, so + incomplete selections cannot replace tracked entities.

                          ({ id: p.id, name: p.name }))
     .include(r => r.tasks, tasks => tasks
         .select(t => ({ title: t.title })));
@@ -265,7 +265,9 @@ const projects = await read; // one SQL statement`)
                     

Return child queries from customizers. STI/CTI reads expose variantRowSchemas for explicit mapper dispatch. - Ordinary entity reads and writes remain unchanged. + Entity objects remain mutable. Reads, reloads and + returning writes share exact numeric, date and null + representations.

Projection-aware consumer guide diff --git a/websites/docs/public/llms.txt b/websites/docs/public/llms.txt index d1bc3fa1..209db7c0 100644 --- a/websites/docs/public/llms.txt +++ b/websites/docs/public/llms.txt @@ -177,10 +177,10 @@ belong to these packages. Knex is a peer dependency; PostgreSQL also needs `pg`. exposes typed DbSets. Ordinary entity queries support identity tracking, change persistence, optimistic concurrency and STI/CTI variants. Use `@cleverbrush/orm-cli` for migrations. [ORM guide](/orm). -- **Projection-aware reads:** enter `.withRowSchema()` before selecting or - including. It returns an immutable, read-only query. `rowSchema` describes the - decoded selection, not the full table, and accessing it never executes SQL. - Results are detached even in tracking contexts. SQL nulls stay `null`, dates +- **Immutable queries:** every Framework query exposes `rowSchema` automatically; + `.withRowSchema()` is removed. Retain returned builders. `rowSchema` describes + the decoded selection, and accessing it never executes SQL. Full ORM entities + remain tracked when enabled; projected, grouped, distinct and raw rows are detached. SQL nulls stay `null`, dates decode to `Date` throughout nested graphs, and declared decimal/bigint columns retain exact strings. Input defaults and preprocessors are not replayed. [Read schemas](/knex-schema#row-schemas), [detached ORM reads](/orm#detached-read-schemas). @@ -190,10 +190,11 @@ belong to these packages. Knex is a peer dependency; PostgreSQL also needs `pg`. `orWhereRaw` / `orderByRaw` with positional bindings and `ref(selector)` for quoted mapped columns, including generated child aliases. Group callbacks run synchronously once and receive only predicate methods; retained groups are - closed after the callback. Subquery SQL/bindings are captured without executing + immutable too. Return the configured group, never void or a Promise. Subquery SQL/bindings are captured without executing SQL. These operations retain rowSchema identity; assign their returned reader. Raw fragments are trusted application SQL, not a sandbox. Keep authorization - outside grouped search ORs. No unrestricted apply() exists in read mode. + outside grouped search ORs. Opaque SELECTs use `apply(configure, { output })` or + `selectRaw(sql, bindings, { output })` with a synchronous Framework object schema. Numbered pagination retains ordering; composite cursors replace it with their explicit orderBy specification. [Read predicates](/knex-schema#read-predicates). - **Nested and polymorphic reads:** child customizers return their configured From ff6a30ecc889da486f38c9f2bbea13900bfbb0ad Mon Sep 17 00:00:00 2001 From: Andrew Zolotukhin Date: Thu, 1 Oct 2026 05:27:58 +0000 Subject: [PATCH 3/3] docs: keep Framework guidance application-agnostic and current --- .changeset/immutable-query-major.md | 8 ++- .changeset/schema-boundaries.md | 2 +- AGENTS.md | 11 +++ demos/e2e/README.md | 2 +- docs/assets/immutable-query-docs.png | Bin 157494 -> 156320 bytes docs/cache-form-migration.md | 12 ++-- libs/client/CHANGELOG.md | 2 +- libs/deep/README.md | 9 +-- libs/env/CHANGELOG.md | 2 +- libs/knex-schema/CHANGELOG.md | 2 +- libs/knex-schema/MIGRATION-v5.md | 2 +- libs/knex-schema/README.md | 8 +-- libs/knex-schema/src/QuerySource.ts | 22 +++--- libs/knex-schema/src/expressions.ts | 2 +- libs/knex-schema/src/extension.ts | 68 +----------------- libs/knex-schema/src/types.ts | 2 +- libs/orm-cli/CHANGELOG.md | 2 +- libs/orm/CHANGELOG.md | 2 +- libs/orm/README.md | 8 +-- libs/schema/README.md | 25 +++---- .../src/builders/ObjectSchemaBuilder.ts | 4 +- libs/server/CHANGELOG.md | 2 +- .../docs/app/client/sections/cacheTags.tsx | 11 ++- websites/docs/app/knex-schema/page.tsx | 15 ++-- websites/docs/app/react-form/page.tsx | 4 +- websites/docs/public/llms.txt | 4 +- .../app/docs/sections/api-reference.tsx | 8 +-- .../app/docs/sections/schema-modifiers.tsx | 4 +- .../schema/app/docs/sections/validation.tsx | 7 +- .../app/playground/schemaDeclarations.ts | 4 +- 30 files changed, 95 insertions(+), 159 deletions(-) diff --git a/.changeset/immutable-query-major.md b/.changeset/immutable-query-major.md index d792313b..631bb9fe 100644 --- a/.changeset/immutable-query-major.md +++ b/.changeset/immutable-query-major.md @@ -22,6 +22,10 @@ Make Framework query builders immutable and infer row schemas automatically. -This breaking release requires consumers to retain returned query builders, return synchronous builders from scopes and grouped predicates, and supply an explicit Framework object output schema for opaque raw SELECTs. Remove withRowSchema() calls: ordinary, aliased, polymorphic and ORM queries now expose their row schemas directly. Projections replace scalar selections, and projected/aggregate/raw queries cannot perform entity writes. Reads and write-returning rows consistently preserve exact decimal/bigint strings, Date objects and SQL nulls. +Retain returned query builders, return synchronous builders from scopes and grouped predicates, and supply an explicit Framework object output schema for opaque raw SELECTs. Ordinary, aliased, polymorphic and ORM queries expose their row schemas directly. Projections replace scalar selections, and projected/aggregate/raw queries cannot perform entity writes. Reads and write-returning rows consistently preserve exact decimal/bigint strings, Date objects and SQL nulls. -All published Framework packages advance together to the next major version. See the knex-schema and ORM migration guides before upgrading; tracked entity objects remain mutable. +All published Framework packages advance together to the next major version. Tracked entity objects remain mutable. + +### Migrating from v4.x to v5 + +Remove `withRowSchema()` calls and retain each configured query instead of relying on mutation. Replace raw base-query overloads with explicit output contracts. See `libs/knex-schema/MIGRATION-v5.md` for the complete migration guide. diff --git a/.changeset/schema-boundaries.md b/.changeset/schema-boundaries.md index c4c0d255..199781d1 100644 --- a/.changeset/schema-boundaries.md +++ b/.changeset/schema-boundaries.md @@ -10,4 +10,4 @@ Shape, validation-rule, default, fallback and extension changes clear inherited names; apply schemaName after those edits to establish a new named definition. Preserve one canonical definition in JSON Schema, OpenAPI and AsyncAPI with strict name collision checks. Keep existing -type inference and legacy optional null acceptance unchanged. +type inference and optional null acceptance unchanged. diff --git a/AGENTS.md b/AGENTS.md index 4b8d890f..d93978be 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -135,6 +135,17 @@ The `demos/` directory is linted separately (see `demos/todo-backend/biome.json` - Target `ES2022`; use modern syntax freely - Type assertions with `as` are acceptable (the linter won't block them) +## Documentation and Project Boundaries + +- Framework is an independent, application-agnostic project. Use generic domain + examples in source, documentation, changesets and PR descriptions. Consumer + application references belong only in website showcase links. +- Describe the current supported API in READMEs, guides and JSDoc. Keep historical + API comparisons and upgrade instructions in explicitly labeled v4.x-to-v5 + migration documentation, and link to it from current guides where useful. +- Preserve accurate API contracts and deprecation annotations; do not change + runtime behavior just to simplify documentation. + --- ## Testing Conventions diff --git a/demos/e2e/README.md b/demos/e2e/README.md index 8fad4eb8..aab01e1f 100644 --- a/demos/e2e/README.md +++ b/demos/e2e/README.md @@ -116,7 +116,7 @@ src/ - **Todos** — full CRUD, list pagination, `getWithAuthor`, polymorphic events (`assigned` / `commented` / `completed`), optimistic concurrency on `complete` (200 / 409 with `If-Match`), cross-user 403, attachment - download, `legacyReplace` redirect. + download, PUT redirect. - **Import / Export** — 207 small batch, 202 large batch, idempotency header (contract-level), CSV export with quoting + content headers. - **Users (admin)** — list (admin only), delete user, self-delete blocked, diff --git a/docs/assets/immutable-query-docs.png b/docs/assets/immutable-query-docs.png index 37c23b6f722c86e6102d8d7c53bd0096763f11ea..fb6c4d841623789824974d0343e5dd11ba0a24e0 100644 GIT binary patch literal 156320 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 v<+y_U`%_0;SVu|3`E};@+g8j!@t;~ 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 Y?*(}nBERbX4yE9v#TCTL-x~z{ABSSYS^xk5 literal 157494 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%0Dutj9raD3%WEuLGGrn(c@Lb+y)0S3cOjP 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

9gSsbr(mw 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 false (opaque objects compare by identity) ``` -**Breaking comparison corrections:** previous versions could throw for object/null -pairs, consider Dates equal to unrelated objects, compare distinct opaque objects -by enumerable shape, and reject equivalent cycles or repeated references. Signed -zero, invalid Dates, symbol keys and sparse arrays now follow the rules above. -Audit consumers relying on those outcomes. To compare Maps/Sets/custom instances -by content, explicitly project their relevant state into plain data first. +To compare Maps/Sets/custom instances by content, explicitly project their +relevant state into plain data first. For upgrade considerations, see the +[v4.x-to-v5 migration guide](../../docs/cache-form-migration.md#shared-data-snapshots-and-equality-breaking). ### `deepExtend(...objects)` diff --git a/libs/env/CHANGELOG.md b/libs/env/CHANGELOG.md index 0fb62f33..2b34e186 100644 --- a/libs/env/CHANGELOG.md +++ b/libs/env/CHANGELOG.md @@ -24,7 +24,7 @@ ### Minor Changes -- c75bff4: Add framework affordances discovered while reviewing Xpenser: +- c75bff4: Add reusable environment, HTTP, cache, database and migration capabilities: - `@cleverbrush/env`: add `envBoolean()` for environment-style boolean values such as `1`, `0`, `yes`, `no`, `on`, and `off`. diff --git a/libs/knex-schema/CHANGELOG.md b/libs/knex-schema/CHANGELOG.md index c7f59edc..68cc4bf1 100644 --- a/libs/knex-schema/CHANGELOG.md +++ b/libs/knex-schema/CHANGELOG.md @@ -22,7 +22,7 @@ ### Minor Changes -- c75bff4: Add framework affordances discovered while reviewing Xpenser: +- c75bff4: Add reusable environment, HTTP, cache, database and migration capabilities: - `@cleverbrush/env`: add `envBoolean()` for environment-style boolean values such as `1`, `0`, `yes`, `no`, `on`, and `off`. diff --git a/libs/knex-schema/MIGRATION-v5.md b/libs/knex-schema/MIGRATION-v5.md index b5184eb9..1f188b49 100644 --- a/libs/knex-schema/MIGRATION-v5.md +++ b/libs/knex-schema/MIGRATION-v5.md @@ -1,4 +1,4 @@ -# Migrating to immutable queries (Framework v5) +# Migrating from Framework v4.x to v5: immutable queries This is a coordinated **major release of all 19 published Framework packages**. Upgrade them together. Prerelease snapshots still use beta versions; the stable diff --git a/libs/knex-schema/README.md b/libs/knex-schema/README.md index d820d92c..26fa1779 100644 --- a/libs/knex-schema/README.md +++ b/libs/knex-schema/README.md @@ -2,8 +2,8 @@ Type-safe, schema-driven query builder for [Knex](https://knexjs.org/). Use `@cleverbrush/schema` object builders to describe your PostgreSQL tables — column name mapping, eager loading, and full CRUD are handled automatically with complete TypeScript inference. -This major release makes every Framework query immutable and exposes `.rowSchema` automatically. -See the [migration guide](./MIGRATION-v5.md) before upgrading from v4 or an opt-in read beta. +Every Framework query is immutable and exposes `.rowSchema` automatically. +Upgrading? See [Migrating from v4.x to v5](./MIGRATION-v5.md). ## Installation @@ -538,7 +538,7 @@ nested related objects instead. For reusable connection/transaction handling, ordinary and aliased schemas retain the same inference through `createQuery(knex)`, `withTransaction(trx)`, and -`transaction(callback)`. Custom Knex base-query overloads are removed; use `rawQuery(knex, Output, sql)` +`transaction(callback)`. Use `rawQuery(knex, Output, sql)` or `.apply(configure, { output })` for an explicit raw output contract. These APIs and their JSDoc are also available through `@cleverbrush/orm`. ### Aggregates with optional output schemas @@ -665,7 +665,7 @@ URL is missing. CI runs PostgreSQL 16 integration tests alongside unit/type test ## Projection-aware reads Every Framework query is immutable and exposes its decoded `.rowSchema` -automatically. There is no `.withRowSchema()` call or mutable compatibility mode. +automatically. Capture returned queries when adding filters, projections or includes. Inspecting metadata never runs SQL. Ordinary table queries also support writes; projected results are detached, while full ORM entities can still use identity tracking. diff --git a/libs/knex-schema/src/QuerySource.ts b/libs/knex-schema/src/QuerySource.ts index 2d14ae23..ff5788aa 100644 --- a/libs/knex-schema/src/QuerySource.ts +++ b/libs/knex-schema/src/QuerySource.ts @@ -237,7 +237,7 @@ export class QuerySource< } /** - * Append a legacy SQL COUNT selection without executing the query. + * Append a internal SQL COUNT selection without executing the query. * The driver controls the result shape/value type. Prefer countValue() for a * checked scalar number, or aggregate.count() in a typed object projection. */ @@ -246,16 +246,16 @@ export class QuerySource< } /** - * Append a legacy COUNT(DISTINCT column) selection. + * Append a internal COUNT(DISTINCT column) selection. * Prefer countDistinctValue() for a checked scalar or aggregate.countDistinct() - * for an inferred grouped result; this legacy method retains the builder type. + * for an inferred grouped result; this planner method retains the builder type. */ countDistinct(column?: ColumnRef | Knex.Raw): this { return (countDistinctImpl as any)(this, column); } /** - * Append a legacy MIN selection without changing the result type. + * Append a internal MIN selection without changing the result type. * Use minValue() for a scalar with explicit decoding, or aggregate.min() in a * typed projection. SQL returns null for an empty/all-null input. */ @@ -264,7 +264,7 @@ export class QuerySource< } /** - * Append a legacy MAX selection without changing the result type. + * Append a internal MAX selection without changing the result type. * Use maxValue() for a scalar with explicit decoding, or aggregate.max() in a * typed projection. SQL returns null for an empty/all-null input. */ @@ -273,7 +273,7 @@ export class QuerySource< } /** - * Append a legacy SUM selection, leaving numeric conversion to the driver. + * Append a internal SUM selection, leaving numeric conversion to the driver. * Prefer sumValue() or aggregate.sum() to preserve exact numeric text by default. */ sum(column: ColumnRef | Knex.Raw): this { @@ -281,7 +281,7 @@ export class QuerySource< } /** - * Append a legacy AVG selection, leaving numeric conversion to the driver. + * Append a internal AVG selection, leaving numeric conversion to the driver. * Prefer avgValue() or aggregate.avg() for a typed, precision-preserving result. */ avg(column: ColumnRef | Knex.Raw): this { @@ -874,7 +874,7 @@ export class QuerySource< /** * Read a cursor page without running a total-count query. * The orderBy form clones the source and preserves exact composite sort values; - * its non-null sort must contain a declared unique key. The legacy column form + * its non-null sort must contain a declared unique key. The single-column form * mutates this builder and defaults to id descending. Reapply access filters on * every request: cursors are positions, not authorization or snapshots. * @returns Mapped data, hasMore, and a nextCursor that is null on the last page. @@ -886,7 +886,7 @@ export class QuerySource< /** * Read a cursor page without running a total-count query. * The orderBy form clones the source and preserves exact composite sort values; - * its non-null sort must contain a declared unique key. The legacy column form + * its non-null sort must contain a declared unique key. The single-column form * mutates this builder and defaults to id descending. Reapply access filters on * every request: cursors are positions, not authorization or snapshots. * @returns Mapped data, hasMore, and a nextCursor that is null on the last page. @@ -897,7 +897,7 @@ export class QuerySource< cursor?: any; /** Maximum parent rows to return; one extra row determines hasMore. */ limit: number; - /** Unique sort property; defaults to id in the legacy API. */ + /** Unique sort property; defaults to id for single-column paging. */ column?: ColumnRef; /** Sort/continuation direction; defaults to descending. */ direction?: 'asc' | 'desc'; @@ -905,7 +905,7 @@ export class QuerySource< /** * Read a cursor page without running a total-count query. * The orderBy form clones the source and preserves exact composite sort values; - * its non-null sort must contain a declared unique key. The legacy column form + * its non-null sort must contain a declared unique key. The single-column form * mutates this builder and defaults to id descending. Reapply access filters on * every request: cursors are positions, not authorization or snapshots. * @returns Mapped data, hasMore, and a nextCursor that is null on the last page. diff --git a/libs/knex-schema/src/expressions.ts b/libs/knex-schema/src/expressions.ts index 1b198372..7b19cf45 100644 --- a/libs/knex-schema/src/expressions.ts +++ b/libs/knex-schema/src/expressions.ts @@ -46,7 +46,7 @@ export interface AliasedColumn { * Type-only marker carrying column nullability and value type; not a runtime row value. */ readonly __value?: T; - /** Type-only original schema used by opt-in projection metadata. */ + /** Type-only source schema used by projection metadata. */ readonly __readSource?: S; /** Type-only join nullability, independent of the stored column schema. */ readonly __leftJoined?: Nullable; diff --git a/libs/knex-schema/src/extension.ts b/libs/knex-schema/src/extension.ts index f24b14d5..5f75d00d 100644 --- a/libs/knex-schema/src/extension.ts +++ b/libs/knex-schema/src/extension.ts @@ -943,72 +943,8 @@ export const ddlExtension = defineExtension({ return this.withExtension('beforeDelete', [...existing, fn]); } - /** - * Declare polymorphic variants for this schema. - * - * Turns a base schema into a **polymorphic schema** where a discriminator - * column determines which variant each row belongs to. Variants can store - * their extra fields either in a separate table (CTI — Class Table - * Inheritance) or as nullable columns on the base table (STI — Single - * Table Inheritance). - * - * The return type carries a phantom brand - * (`[POLYMORPHIC_TYPE_BRAND]`) so that `query(db, schema)` automatically - * infers the full discriminated-union result type. - * - * @param config.discriminator - Property key (or accessor) of the - * discriminator column on the base table (e.g. `'type'` or `t => t.type`). - * @param config.variants - Map from discriminator value to - * `{ schema, storage, foreignKey?, allowOrphan?, enforceCheck? }`. - * - `storage: 'cti'` — variant fields are in a separate table; - * `foreignKey` (the FK column on the variant table) is required. - * - `storage: 'sti'` — variant fields are nullable columns on the base table. - * - * @example - * ```ts - * const FileBase = object({ id: number().primaryKey(), name: string(), type: string() }) - * .hasTableName('files'); - * - * const ImageExtras = object({ width: number(), height: number(), format: string() }) - * .hasTableName('image_file'); - * - * const DocumentExtras = object({ size: number(), issueDate: date() }) - * .hasTableName('document_file'); - * - * const ImageExtras = object({ - * fileId: number().hasColumnName('file_id'), - * type: string('image'), - * width: number(), height: number(), format: string() - * }).hasTableName('image_file'); - * - * const DocumentExtras = object({ - * fileId: number().hasColumnName('file_id'), - * type: string('document'), - * size: number(), issueDate: date() - * }).hasTableName('document_file'); - * - * const FileSchema = FileBase.withVariants({ - * discriminator: t => t.type, - * variants: { - * image: { schema: ImageExtras, storage: 'cti', foreignKey: t => t.fileId }, - * document: { schema: DocumentExtras, storage: 'cti', foreignKey: t => t.fileId }, - * }, - * }); - * - * // query(db, FileSchema) returns: - * // Array< - * // | { id: number; name: string; type: 'image'; width: number; height: number; format: string } - * // | { id: number; name: string; type: 'document'; size: number; issueDate: Date } - * // > - * ``` - */ - // NOTE: the public `.withVariants()` schema-level method has been - // removed. Variants are now declared on the {@link Entity} chain via - // `defineEntity(...).discriminator(...).ctiVariant(...).stiVariant(...)`. - // The internal worker {@link applyVariantsToSchema} (below this - // `defineExtension` block) is invoked by the Entity layer and stores - // the same `'variants'` / `'polymorphicVariants'` extensions that - // `QuerySource` reads at runtime. + // Entity declarations use applyVariantsToSchema to store the variant + // metadata consumed by polymorphic queries. } }); diff --git a/libs/knex-schema/src/types.ts b/libs/knex-schema/src/types.ts index 2b74183d..8eef5663 100644 --- a/libs/knex-schema/src/types.ts +++ b/libs/knex-schema/src/types.ts @@ -542,7 +542,7 @@ export interface RelationSpec { name: string; schema: any; foreignKey?: any; - /** Explicit schema property names retained for opt-in read correlation. */ + /** Explicit schema property names used for read correlation. */ localKey?: string; remoteKey?: string; /** Nullable belongs-to relations do not filter out their parent rows. */ diff --git a/libs/orm-cli/CHANGELOG.md b/libs/orm-cli/CHANGELOG.md index 7a77319d..d39807ef 100644 --- a/libs/orm-cli/CHANGELOG.md +++ b/libs/orm-cli/CHANGELOG.md @@ -22,7 +22,7 @@ ### Minor Changes -- c75bff4: Add framework affordances discovered while reviewing Xpenser: +- c75bff4: Add reusable environment, HTTP, cache, database and migration capabilities: - `@cleverbrush/env`: add `envBoolean()` for environment-style boolean values such as `1`, `0`, `yes`, `no`, `on`, and `off`. diff --git a/libs/orm/CHANGELOG.md b/libs/orm/CHANGELOG.md index b5a7f006..a06ebe63 100644 --- a/libs/orm/CHANGELOG.md +++ b/libs/orm/CHANGELOG.md @@ -25,7 +25,7 @@ ### Minor Changes -- c75bff4: Add framework affordances discovered while reviewing Xpenser: +- c75bff4: Add reusable environment, HTTP, cache, database and migration capabilities: - `@cleverbrush/env`: add `envBoolean()` for environment-style boolean values such as `1`, `0`, `yes`, `no`, `on`, and `off`. diff --git a/libs/orm/README.md b/libs/orm/README.md index 3037edee..aebbc37c 100644 --- a/libs/orm/README.md +++ b/libs/orm/README.md @@ -13,7 +13,7 @@ EF-Core-like typed ORM layer on top of [`@cleverbrush/knex-schema`](../knex-sche --- -For the breaking immutable-query release, read the [v5 migration guide](../knex-schema/MIGRATION-v5.md). All published Framework packages advance together. +Upgrading? See [Migrating from v4.x to v5](../knex-schema/MIGRATION-v5.md). ## Installation @@ -395,7 +395,7 @@ const tasks = await db.tasks .orderBy(t => t.createdAt, 'desc') .orderBy(t => t.id, 'desc') .include(t => t.owner, owners => { - owners.where(t => t.name, 'Alice'); // foreign schema, not any + return owners.where(t => t.name, 'Alice'); // foreign schema, not any }) .limit(20); ``` @@ -405,9 +405,9 @@ does not necessarily filter parents. Callback types infer the declared foreign schema, including variant queries when the relation schema is known. `paginateAfter({ limit, cursor, orderBy: [...] })` supports non-null scalar sorts -with a declared unique tie-breaker; single-column cursor calls are unchanged. +with a declared unique tie-breaker. Use `column` for a single-column cursor. See the [complete query guide](../knex-schema/README.md#composable-read-queries) for defaults, -precision policy, grouped aggregates, cursor restrictions, and migration examples. +precision policy, grouped aggregates, cursor restrictions, and examples. ## Related packages diff --git a/libs/schema/README.md b/libs/schema/README.md index 36bf1af8..8ae25fa6 100644 --- a/libs/schema/README.md +++ b/libs/schema/README.md @@ -122,10 +122,6 @@ if (result.valid) { const nameErrors = result.getErrorsFor((p) => p.name); console.log(nameErrors.isValid); // false console.log(nameErrors.errors); // ['Name must be at least 2 characters'] - - // result.errors on object schemas is deprecated — use getErrorsFor() instead - console.log('Errors:', result.errors); - // Array of { message: string } } ``` @@ -865,8 +861,7 @@ const result = UserSchema.validate(someObject); if (result.valid) { console.log(result.object); // typed as InferType } else { - // For object schemas, prefer getErrorsFor() for per-property error inspection (see below) - console.log(result.errors); // deprecated for object schemas — Array of { message: string } + console.log(result.getErrorsFor(t => t.name).errors); // field error strings } // Async validation (use when validators/preprocessors are async) @@ -883,12 +878,12 @@ const result = UserSchema.validate( { doNotStopOnFirstError: true } ); -console.log(result.errors); -// [ -// { message: 'Name must be at least 2 characters' }, -// { message: 'Please enter a valid email' }, -// { message: 'Age cannot be negative' } -// ] +console.log(result.getErrorsFor(t => t.name).errors); +// ['Name must be at least 2 characters'] +console.log(result.getErrorsFor(t => t.email).errors); +// ['Please enter a valid email'] +console.log(result.getErrorsFor(t => t.age).errors); +// ['Age cannot be negative'] ``` ### Custom Error Messages @@ -979,11 +974,11 @@ result.getErrorsFor((t) => t.password).errors; // → [] ``` -You can target multiple properties from a single validator by returning multiple errors with different `property` selectors. Errors without a `property` selector are attached to the root object as before. +You can target multiple properties from a single validator by returning multiple errors with different `property` selectors. Errors without a `property` selector are attached to the root object. ### Per-Property Errors with `getErrorsFor()` (Recommended) -`ObjectSchemaBuilder.validate()` returns an extended result with a `getErrorsFor()` method for inspecting errors on individual properties — perfect for showing inline form errors. **This is the recommended way to inspect validation errors on object schemas** and replaces the deprecated `errors` array on `ObjectSchemaValidationResult`: +`ObjectSchemaBuilder.validate()` returns a result with a `getErrorsFor()` method for inspecting errors on individual properties — useful for showing inline form errors: ```typescript const PersonSchema = object({ @@ -1167,7 +1162,7 @@ array(optionalText).parse(['ok', 42]); // ['ok', undefined] — no entries dropp Fallbacks are opt-in: a fallback on a property does not make a malformed required root object valid. A fallback factory runs only when validation fails. -**Null compatibility:** legacy optional schemas accept `null` at runtime even +**Null handling:** optional schemas accept `null` at runtime even though their inferred type does not include it. `.optional().catch(undefined)` therefore leaves `null` unchanged. Normalize it explicitly when needed: diff --git a/libs/schema/src/builders/ObjectSchemaBuilder.ts b/libs/schema/src/builders/ObjectSchemaBuilder.ts index 3be56a3c..5473c8d2 100644 --- a/libs/schema/src/builders/ObjectSchemaBuilder.ts +++ b/libs/schema/src/builders/ObjectSchemaBuilder.ts @@ -224,7 +224,7 @@ export type ObjectSchemaValidationResult< * This is the **recommended** way to inspect validation errors — it provides type-safe, * per-property error details including `isValid`, `errors`, and `seenValue`. * - * Prefer this over the deprecated `errors` array. + * Inspect root or property-specific validation errors with a selector. * * @param selector a callback function to select property from the schema. */ @@ -324,7 +324,7 @@ export type ObjectSchemaValidationResult< * }); * * // result.valid === false - * // result.errors is deprecated — use result.getErrorsFor() instead + * // Inspect property errors using result.getErrorsFor(). * // result.getErrorsFor((p) => p.age).errors // ["is expected to have property 'age'"] * ``` * diff --git a/libs/server/CHANGELOG.md b/libs/server/CHANGELOG.md index 387aa733..347477f2 100644 --- a/libs/server/CHANGELOG.md +++ b/libs/server/CHANGELOG.md @@ -30,7 +30,7 @@ ### Minor Changes -- c75bff4: Add framework affordances discovered while reviewing Xpenser: +- c75bff4: Add reusable environment, HTTP, cache, database and migration capabilities: - `@cleverbrush/env`: add `envBoolean()` for environment-style boolean values such as `1`, `0`, `yes`, `no`, `on`, and `off`. diff --git a/websites/docs/app/client/sections/cacheTags.tsx b/websites/docs/app/client/sections/cacheTags.tsx index 73993976..3fdb1b1f 100644 --- a/websites/docs/app/client/sections/cacheTags.tsx +++ b/websites/docs/app/client/sections/cacheTags.tsx @@ -130,7 +130,7 @@ externalCacheTags({ invalidateTag: revalidateTag });

-

Versioned keys: breaking migration

+

Deterministic cache keys

Server and client helpers, response caches and external invalidation share the deterministic ct2:{' '} @@ -141,11 +141,10 @@ externalCacheTags({ invalidateTag: revalidateTag }); values throw TypeError.

- Upgrade external cache writers and invalidators together and - flush or expire old entries. There is no legacy fallback. - Base invalidation labels remain literal names; TTLs are - unchanged. External invalidators send both base labels and - computed keys by default, including property-free tags. + External cache writers and invalidators must use the same + key format. Base invalidation labels are literal names. + External invalidators send both base labels and computed + keys by default, including property-free tags.

Response shape and auth/tenant isolation remain diff --git a/websites/docs/app/knex-schema/page.tsx b/websites/docs/app/knex-schema/page.tsx index de0e6219..715456f0 100644 --- a/websites/docs/app/knex-schema/page.tsx +++ b/websites/docs/app/knex-schema/page.tsx @@ -374,7 +374,7 @@ const average = await query(knex, TaskSchema).avgValue(t => t.estimate, { }} />

- Queries are immutable in this major release. Read the{' '} + Queries are immutable. Read the{' '} complete query guide {' '} @@ -385,12 +385,11 @@ const average = await query(knex, TaskSchema).avgValue(t => t.estimate, {

Automatic projection-aware schemas

- Every query exposes rowSchema automatically; no extra - mode switch is needed. Immutable PostgreSQL queries - describe the actual decoded result schema: SQL null - stays null, dates are Date objects at every depth, and - decimal/bigint values are exact strings before JSON - parsing. + Every query exposes rowSchema automatically. Immutable + PostgreSQL queries describe the actual decoded result + schema: SQL null stays null, dates are Date objects at + every depth, and decimal/bigint values are exact strings + before JSON parsing.

                          t.id, taskId);`)
                         Retain every returned builder when configuring a query.
                     

- Read representation, pagination and migration details + Read representation and pagination details
diff --git a/websites/docs/app/react-form/page.tsx b/websites/docs/app/react-form/page.tsx index 00f57c52..469b7519 100644 --- a/websites/docs/app/react-form/page.tsx +++ b/websites/docs/app/react-form/page.tsx @@ -307,8 +307,8 @@ function App() { createFormSystem. Its Field{' '} checks the selected value, variant and custom props. Extend it by spreading system.renderers. - Its optional Provider also configures - legacy fields. + Its optional Provider supplies renderers to + descendant fields.

                         
                                     Result of .validate(). Contains{' '}
                                     valid, errors, and{' '}
-                                    object. For object schemas,
-                                    also includes getErrorsFor() (
-                                    errors is{' '}
-                                    deprecated on object schema
-                                    results).
+                                    object. For object schemas, use{' '}
+                                    getErrorsFor() for per-property
+                                    errors.
                                 
                             
                             
diff --git a/websites/schema/app/docs/sections/schema-modifiers.tsx b/websites/schema/app/docs/sections/schema-modifiers.tsx
index 71c123dd..511d1668 100644
--- a/websites/schema/app/docs/sections/schema-modifiers.tsx
+++ b/websites/schema/app/docs/sections/schema-modifiers.tsx
@@ -140,8 +140,8 @@ array(text).parse(['ok', 42]); // ['ok', undefined] — no entries dropped`}
                 

- Legacy optional schemas accept null at runtime - even when their inferred type omits it. A fallback does not + Optional schemas accept null at runtime even + when their inferred type omits it. A fallback does not replace a value that passed validation, so normalize null explicitly when your application requires undefined:

diff --git a/websites/schema/app/docs/sections/validation.tsx b/websites/schema/app/docs/sections/validation.tsx index 7be966a3..bdc98d3f 100644 --- a/websites/schema/app/docs/sections/validation.tsx +++ b/websites/schema/app/docs/sections/validation.tsx @@ -19,9 +19,7 @@ export default function ValidationSection() { only when your schema includes async validators or preprocessors. For object schemas, the result also includes a{' '} getErrorsFor() method for per-property error - inspection — the flat errors array is{' '} - deprecated on object schema results and will be - removed in a future major version. + inspection.

                 {' '}
-                and replaces the deprecated errors array on object
-                schema validation results. It returns an object with{' '}
+                using property selectors. It returns an object with{' '}
                 isValid (boolean), errors (array of
                 error strings), and seenValue (the value that was
                 validated).
diff --git a/websites/schema/app/playground/schemaDeclarations.ts b/websites/schema/app/playground/schemaDeclarations.ts
index 11108581..aaff6960 100644
--- a/websites/schema/app/playground/schemaDeclarations.ts
+++ b/websites/schema/app/playground/schemaDeclarations.ts
@@ -2495,7 +2495,7 @@ export type ObjectSchemaValidationResult p.age).errors // ["is expected to have property 'age'"]
  * \`\`\`
  *