@cleverbrush/knex-schema 3.0.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/extension.ts"],"sourcesContent":["// @cleverbrush/knex-schema — Schema extension: hasColumnName / hasTableName\n\nimport type {\n AnySchemaBuilder,\n ArraySchemaBuilder,\n BooleanSchemaBuilder,\n DateSchemaBuilder,\n FunctionSchemaBuilder,\n GenericSchemaBuilder,\n NumberSchemaBuilder,\n ObjectSchemaBuilder,\n PropertyDescriptor,\n PropertyDescriptorTree,\n SchemaBuilder,\n StringSchemaBuilder,\n UnionSchemaBuilder\n} from '@cleverbrush/schema';\nimport {\n arrayExtensions,\n defineExtension,\n EXTRA_TYPE_BRAND,\n METHOD_LITERAL_BRAND,\n NumberSchemaBuilder as NumberSchemaBuilderClass,\n numberExtensions,\n ObjectSchemaBuilder as ObjectSchemaBuilderClass,\n StringSchemaBuilder as StringSchemaBuilderClass,\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR,\n stringExtensions,\n withExtensions\n} from '@cleverbrush/schema';\nimport type {\n ResolvedVariantConfig,\n ResolvedVariantRelationSpec,\n ResolvedVariantSpec,\n VariantStorageType\n} from './types.js';\n\n// Re-export these symbols so consumer packages can name them when generating\n// TypeScript declarations for schemas built with @cleverbrush/knex-schema.\n// Without these exports, tsc emits TS4023 (\"cannot be named\") errors for any\n// file that exports a variable whose inferred type traverses FixedMethods.\nexport { EXTRA_TYPE_BRAND, METHOD_LITERAL_BRAND } from '@cleverbrush/schema';\n\n// ---------------------------------------------------------------------------\n// Primary-key type brands\n// ---------------------------------------------------------------------------\n\n/**\n * Phantom-type brand placed on a column schema by `.primaryKey()`.\n * Carried on the property schema's type so that {@link PrimaryKeyOf} can\n * locate primary-key columns at the type level.\n *\n * @public\n */\nexport const PRIMARY_KEY_BRAND: unique symbol = Symbol.for(\n '@cleverbrush/knex-schema:primaryKey'\n);\n\n/**\n * Phantom-type brand placed on an object schema by `.hasPrimaryKey([cols])`\n * to record the composite primary-key column tuple at the type level.\n *\n * @public\n */\nexport const COMPOSITE_PRIMARY_KEY_BRAND: unique symbol = Symbol.for(\n '@cleverbrush/knex-schema:compositePrimaryKey'\n);\n\n// ---------------------------------------------------------------------------\n// Polymorphic type brand\n// ---------------------------------------------------------------------------\n\n/**\n * Phantom-type brand placed on an `ObjectSchemaBuilder` by `.withVariants()`.\n * The brand carries the discriminated-union result type so that\n * `query(db, polymorphicSchema)` automatically infers the correct union type\n * without any extra type annotation.\n *\n * @public\n */\nexport const POLYMORPHIC_TYPE_BRAND: unique symbol = Symbol.for(\n '@cleverbrush/knex-schema:polymorphicType'\n);\n\n// ---------------------------------------------------------------------------\n// Shared implementations\n// ---------------------------------------------------------------------------\n\n/**\n * Stores the SQL column name for a schema property using the schema extension\n * system. Consumed by {@link getColumnName} and the query builder's column\n * resolution logic.\n */\nfunction hasColumnName(this: SchemaBuilder<any, any, any>, name: string) {\n return this.withExtension('columnName', name);\n}\n\n/**\n * Stores the SQL table name for an `ObjectSchemaBuilder` using the schema\n * extension system. Required for {@link query} to build queries — throws at\n * query creation time if not set.\n */\nfunction hasTableName(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n name: string\n) {\n return this.withExtension('tableName', name);\n}\n\n// ---------------------------------------------------------------------------\n// Extension definition\n// ---------------------------------------------------------------------------\n\n/**\n * Schema extension that adds database-mapping metadata to schema builders.\n *\n * Import the typed factory functions (`string`, `number`, `object`, etc.) from\n * this package instead of from `@cleverbrush/schema` to gain access to the\n * `.hasColumnName()` and `.hasTableName()` methods.\n *\n * @example\n * ```ts\n * import { object, string, number } from '@cleverbrush/knex-schema';\n *\n * const UserSchema = object({\n * id: number(),\n * firstName: string().hasColumnName('first_name'),\n * lastName: string().hasColumnName('last_name'),\n * createdAt: date().hasColumnName('created_at'),\n * }).hasTableName('users');\n * ```\n */\nexport const dbExtension = defineExtension({\n string: {\n /**\n * Override the SQL column name for this property.\n *\n * By default the property key is used as the column name. Call\n * `.hasColumnName('sql_col')` when the database column differs from the\n * schema property name (e.g. camelCase property → snake_case column).\n *\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: StringSchemaBuilder<any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n number: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: NumberSchemaBuilder<any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n boolean: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: BooleanSchemaBuilder<any, any, any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n date: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: DateSchemaBuilder<any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n any: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: AnySchemaBuilder<any, any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n func: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: FunctionSchemaBuilder<any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n array: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: ArraySchemaBuilder<any, any, any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n union: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: UnionSchemaBuilder<any, any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n generic: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: GenericSchemaBuilder<any, any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n object: {\n /**\n * Set the SQL table name for this object schema.\n *\n * Required before creating a {@link query} builder — throws at\n * query creation time when not set.\n *\n * @param name - The SQL table name (e.g. `'users'`).\n */\n hasTableName(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n name: string\n ) {\n return hasTableName.call(this, name);\n }\n }\n});\n\n// ---------------------------------------------------------------------------\n// DDL + ORM extension\n// ---------------------------------------------------------------------------\n\ntype FKAction = 'CASCADE' | 'SET NULL' | 'RESTRICT' | 'NO ACTION';\n\n/**\n * DDL and ORM extension that adds database DDL metadata, relationship\n * definitions, and ORM conveniences to schema builders.\n *\n * Column-level methods: `.primaryKey()`, `.references()`, `.unique()`,\n * `.index()`, `.defaultTo()`, `.columnType()`, `.check()`, `.defaultToRaw()`.\n *\n * Object-level methods: `.hasIndex()`, `.hasUnique()`, `.hasCheck()`,\n * `.hasPrimaryKey()`, `.hasRawColumn()`, `.hasRawIndex()`, `.hasMany()`,\n * `.hasOne()`, `.belongsTo()`, `.belongsToMany()`, `.hasTimestamps()`,\n * `.softDelete()`, `.scope()`, `.defaultScope()`, `.beforeInsert()`,\n * `.afterInsert()`, `.beforeUpdate()`, `.beforeDelete()`.\n */\nexport const ddlExtension = defineExtension({\n number: {\n /** Add a foreign key reference to another table.\n * @param table - The referenced table name.\n * @param column - The referenced column (defaults to `'id'`).\n */\n references(\n this: NumberSchemaBuilder<any, any, any, any, any>,\n table: string,\n column: string = 'id'\n ) {\n return this.withExtension('references', { table, column });\n },\n /** Set the ON DELETE action for a foreign key. */\n onDelete(\n this: NumberSchemaBuilder<any, any, any, any, any>,\n action: FKAction\n ) {\n return this.withExtension('onDelete', action);\n },\n /** Set the ON UPDATE action for a foreign key. */\n onUpdate(\n this: NumberSchemaBuilder<any, any, any, any, any>,\n action: FKAction\n ) {\n return this.withExtension('onUpdate', action);\n },\n /** Set a default value for this column. */\n defaultTo(\n this: NumberSchemaBuilder<any, any, any, any, any>,\n value: number | 'auto_increment'\n ) {\n return this.withExtension('defaultTo', value);\n },\n /** Add an index on this column.\n * @param name - Optional index name.\n */\n index(\n this: NumberSchemaBuilder<any, any, any, any, any>,\n name?: string\n ) {\n return this.withExtension('index', name ?? true);\n },\n /** Add a unique constraint on this column.\n * @param name - Optional constraint name.\n */\n unique(\n this: NumberSchemaBuilder<any, any, any, any, any>,\n name?: string\n ) {\n return this.withExtension('unique', name ?? true);\n },\n /** Override the SQL column type (e.g. `'bigint'`, `'smallint'`). */\n columnType(\n this: NumberSchemaBuilder<any, any, any, any, any>,\n type: string\n ) {\n return this.withExtension('columnType', type);\n },\n /** Shorthand for `.columnType('bigint')`. */\n bigint(this: NumberSchemaBuilder<any, any, any, any, any>) {\n return this.withExtension('columnType', 'bigint');\n },\n /** Shorthand for `.columnType('smallint')`. */\n smallint(this: NumberSchemaBuilder<any, any, any, any, any>) {\n return this.withExtension('columnType', 'smallint');\n },\n /** Shorthand for `.columnType('decimal(p,s)')` — exact numeric.\n * @param precision - Total digits.\n * @param scale - Digits after decimal point.\n */\n decimal(\n this: NumberSchemaBuilder<any, any, any, any, any>,\n precision: number,\n scale: number\n ) {\n return this.withExtension(\n 'columnType',\n `decimal(${precision},${scale})`\n );\n },\n /** Set a raw SQL default expression.\n * @param expression - Raw SQL expression (e.g. `\"nextval('my_seq')\"`).\n */\n defaultToRaw(\n this: NumberSchemaBuilder<any, any, any, any, any>,\n expression: string\n ) {\n return this.withExtension('defaultTo', { raw: expression });\n },\n /**\n * Mark this integer column as an optimistic-concurrency row-version\n * token checked by the ORM on every UPDATE / DELETE.\n *\n * A mismatch between the stored value and the snapshot taken at read\n * time throws `ConcurrencyError`.\n *\n * @param opts.strategy\n * - `'increment'` (default) — ORM adds 1 on each UPDATE.\n * - `'manual'` — caller supplies the new value.\n */\n rowVersion(\n this: NumberSchemaBuilder<any, any, any, any, any>,\n opts?: { strategy?: 'increment' | 'manual' }\n ) {\n return this.withExtension('rowVersion', {\n strategy: opts?.strategy ?? 'increment'\n });\n }\n },\n string: {\n /** Override the SQL column type (e.g. `'text'`, `'uuid'`, `'jsonb'`, `'citext'`). */\n columnType(\n this: StringSchemaBuilder<any, any, any, any, any>,\n type: string\n ) {\n return this.withExtension('columnType', type);\n },\n /** Shorthand for `.columnType('text')` — unlimited-length text. */\n text(this: StringSchemaBuilder<any, any, any, any, any>) {\n return this.withExtension('columnType', 'text');\n },\n /** Shorthand for `.columnType('uuid')` — UUID column type.\n * Note: this sets the *storage type* — for UUID format validation use\n * the schema-level `.uuid()` validator instead.\n */\n asUuid(this: StringSchemaBuilder<any, any, any, any, any>) {\n return this.withExtension('columnType', 'uuid');\n },\n /** Shorthand for `.columnType('citext')` — case-insensitive text (Postgres). */\n citext(this: StringSchemaBuilder<any, any, any, any, any>) {\n return this.withExtension('columnType', 'citext');\n },\n /** Shorthand for `.columnType('jsonb')` — binary JSON (Postgres). */\n jsonb(this: StringSchemaBuilder<any, any, any, any, any>) {\n return this.withExtension('columnType', 'jsonb');\n },\n /** Shorthand for `.columnType('tsvector')` — full-text search vector (Postgres). */\n tsvector(this: StringSchemaBuilder<any, any, any, any, any>) {\n return this.withExtension('columnType', 'tsvector');\n },\n /** Add a foreign key reference to another table. */\n references(\n this: StringSchemaBuilder<any, any, any, any, any>,\n table: string,\n column: string = 'id'\n ) {\n return this.withExtension('references', { table, column });\n },\n /** Set the ON DELETE action for a foreign key. */\n onDelete(\n this: StringSchemaBuilder<any, any, any, any, any>,\n action: FKAction\n ) {\n return this.withExtension('onDelete', action);\n },\n /** Set the ON UPDATE action for a foreign key. */\n onUpdate(\n this: StringSchemaBuilder<any, any, any, any, any>,\n action: FKAction\n ) {\n return this.withExtension('onUpdate', action);\n },\n /** Add a unique constraint on this column. */\n unique(\n this: StringSchemaBuilder<any, any, any, any, any>,\n name?: string\n ) {\n return this.withExtension('unique', name ?? true);\n },\n /** Add an index on this column. */\n index(\n this: StringSchemaBuilder<any, any, any, any, any>,\n name?: string\n ) {\n return this.withExtension('index', name ?? true);\n },\n /** Set a default value for this column. */\n defaultTo(\n this: StringSchemaBuilder<any, any, any, any, any>,\n value: string\n ) {\n return this.withExtension('defaultTo', value);\n },\n /** Add a CHECK constraint with raw SQL.\n * @param sql - SQL expression for the check (e.g. `\"role IN ('user','admin')\"`).\n */\n check(this: StringSchemaBuilder<any, any, any, any, any>, sql: string) {\n return this.withExtension('check', sql);\n },\n /** Set a raw SQL default expression. */\n defaultToRaw(\n this: StringSchemaBuilder<any, any, any, any, any>,\n expression: string\n ) {\n return this.withExtension('defaultTo', { raw: expression });\n },\n /**\n * Mark this string column as an optimistic-concurrency row-version\n * token (e.g. a UUID or opaque hash). Strategy is always `'manual'`\n * — the caller must supply a new value on each UPDATE.\n */\n rowVersion(this: StringSchemaBuilder<any, any, any, any, any>) {\n return this.withExtension('rowVersion', { strategy: 'manual' });\n }\n },\n boolean: {\n /** Set a default value for this column. */\n defaultTo(\n this: BooleanSchemaBuilder<any, any, any, any, any, any, any>,\n value: boolean\n ) {\n return this.withExtension('defaultTo', value);\n },\n /** Override the SQL column type (e.g. `'smallint'`, `'integer'`). */\n columnType(\n this: BooleanSchemaBuilder<any, any, any, any, any, any, any>,\n type: string\n ) {\n return this.withExtension('columnType', type);\n },\n /** Add an index on this column. */\n index(\n this: BooleanSchemaBuilder<any, any, any, any, any, any, any>,\n name?: string\n ) {\n return this.withExtension('index', name ?? true);\n },\n /** Add a unique constraint on this column. */\n unique(\n this: BooleanSchemaBuilder<any, any, any, any, any, any, any>,\n name?: string\n ) {\n return this.withExtension('unique', name ?? true);\n }\n },\n date: {\n /** Set a default value (`'now'` for current timestamp). */\n defaultTo(\n this: DateSchemaBuilder<any, any, any, any, any>,\n value: 'now'\n ) {\n return this.withExtension('defaultTo', value);\n },\n /** Override the SQL column type\n * (e.g. `'timestamptz'`, `'date'`, `'time'`).\n */\n columnType(\n this: DateSchemaBuilder<any, any, any, any, any>,\n type: string\n ) {\n return this.withExtension('columnType', type);\n },\n /** Add an index on this column. */\n index(this: DateSchemaBuilder<any, any, any, any, any>, name?: string) {\n return this.withExtension('index', name ?? true);\n },\n /** Set a raw SQL default expression. */\n defaultToRaw(\n this: DateSchemaBuilder<any, any, any, any, any>,\n expression: string\n ) {\n return this.withExtension('defaultTo', { raw: expression });\n },\n /** Shorthand for `.columnType('timestamptz')`. */\n timestamptz(this: DateSchemaBuilder<any, any, any, any, any>) {\n return this.withExtension('columnType', 'timestamptz');\n },\n /** Shorthand for `.columnType('date')` (date-only, no time). */\n dateOnly(this: DateSchemaBuilder<any, any, any, any, any>) {\n return this.withExtension('columnType', 'date');\n },\n /**\n * Mark this date column as an optimistic-concurrency row-version\n * token (timestamp-based). Strategy is always `'timestamp'` —\n * the ORM sets the value to `new Date()` on each UPDATE.\n */\n rowVersion(this: DateSchemaBuilder<any, any, any, any, any>) {\n return this.withExtension('rowVersion', { strategy: 'timestamp' });\n }\n },\n object: {\n /** Override the SQL column type for an object property stored inline.\n * Object-typed properties default to `jsonb` when used as columns in\n * a parent schema's table.\n */\n columnType(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n type: string\n ) {\n return this.withExtension('columnType', type);\n },\n /** Shorthand for `.columnType('jsonb')` — store this nested object as\n * a `jsonb` column (Postgres). Nested objects already default to\n * `jsonb` in DDL; calling `.jsonb()` makes the intent explicit.\n */\n jsonb(this: ObjectSchemaBuilder<any, any, any, any, any, any, any>) {\n return this.withExtension('columnType', 'jsonb');\n },\n /** Shorthand for `.columnType('json')` — store this nested object as\n * a plain `json` column (Postgres / MySQL).\n */\n json(this: ObjectSchemaBuilder<any, any, any, any, any, any, any>) {\n return this.withExtension('columnType', 'json');\n },\n /** Add a composite index on multiple columns.\n * @param columns - Column names to index.\n * @param opts - Optional index name and unique flag.\n */\n hasIndex(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n columns: string[],\n opts?: { name?: string; unique?: boolean }\n ) {\n const existing = (this.getExtension('indexes') as any[]) ?? [];\n return this.withExtension('indexes', [\n ...existing,\n { columns, ...opts }\n ]);\n },\n /** Add a composite unique constraint.\n * @param columns - Column names.\n * @param name - Optional constraint name.\n */\n hasUnique(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n columns: string[],\n name?: string\n ) {\n const existing = (this.getExtension('uniques') as any[]) ?? [];\n return this.withExtension('uniques', [\n ...existing,\n { columns, name }\n ]);\n },\n /** Add a table-level CHECK constraint.\n * @param sql - Raw SQL expression for the check.\n */\n hasCheck(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n sql: string\n ) {\n const existing = (this.getExtension('checks') as any[]) ?? [];\n return this.withExtension('checks', [...existing, sql]);\n },\n /** Add a raw SQL column definition not backed by a schema property.\n * @param name - Column name.\n * @param definition - SQL type and constraints (e.g. `\"tsvector GENERATED ALWAYS AS (...) STORED\"`).\n */\n hasRawColumn(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n name: string,\n definition: string\n ) {\n const existing = (this.getExtension('rawColumns') as any[]) ?? [];\n return this.withExtension('rawColumns', [\n ...existing,\n { name, definition }\n ]);\n },\n /** Add a raw SQL index statement executed after table creation.\n * @param sql - Full `CREATE INDEX` SQL statement.\n */\n hasRawIndex(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n sql: string\n ) {\n const existing = (this.getExtension('rawIndexes') as any[]) ?? [];\n return this.withExtension('rawIndexes', [...existing, sql]);\n },\n /** Define a one-to-many relationship.\n * @param name - Relation name used with `include()`.\n * @param opts - `{ schema, foreignKey }` — `foreignKey` is a ColumnRef on the foreign schema.\n */\n hasMany(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n name: string,\n opts: { schema: any; foreignKey: any }\n ) {\n const existing = (this.getExtension('relations') as any[]) ?? [];\n return this.withExtension('relations', [\n ...existing,\n { type: 'hasMany', name, ...opts }\n ]);\n },\n /** Define a one-to-one relationship (FK on foreign table).\n * @param name - Relation name used with `include()`.\n * @param opts - `{ schema, foreignKey }` — `foreignKey` is a ColumnRef on the foreign schema.\n */\n hasOne(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n name: string,\n opts: { schema: any; foreignKey: any }\n ) {\n const existing = (this.getExtension('relations') as any[]) ?? [];\n return this.withExtension('relations', [\n ...existing,\n { type: 'hasOne', name, ...opts }\n ]);\n },\n /** Define a belongs-to relationship (FK on local table).\n * @param name - Relation name used with `include()`.\n * @param opts - `{ schema, foreignKey }` — `foreignKey` is a ColumnRef on the local schema.\n */\n belongsTo(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n name: string,\n opts: { schema: any; foreignKey: any }\n ) {\n const existing = (this.getExtension('relations') as any[]) ?? [];\n return this.withExtension('relations', [\n ...existing,\n { type: 'belongsTo', name, ...opts }\n ]);\n },\n /** Define a many-to-many relationship through a pivot table.\n * @param name - Relation name used with `include()`.\n * @param opts - `{ schema, through: { table, localKey, foreignKey } }`.\n */\n belongsToMany(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n name: string,\n opts: {\n schema: any;\n through: {\n table: string;\n localKey: string;\n foreignKey: string;\n };\n }\n ) {\n const existing = (this.getExtension('relations') as any[]) ?? [];\n return this.withExtension('relations', [\n ...existing,\n { type: 'belongsToMany', name, ...opts }\n ]);\n },\n /** Auto-add `created_at` and `updated_at` timestamp columns.\n * @param opts - Optional custom column names.\n */\n hasTimestamps(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n opts?: { createdAt?: string; updatedAt?: string }\n ) {\n return this.withExtension('timestamps', {\n createdAt: opts?.createdAt ?? 'created_at',\n updatedAt: opts?.updatedAt ?? 'updated_at'\n });\n },\n /** Enable soft deletes (adds a `deleted_at` column, auto-filters queries).\n * @param opts - Optional custom column name.\n */\n softDelete(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n opts?: { column?: string }\n ) {\n return this.withExtension('softDelete', {\n column: opts?.column ?? 'deleted_at'\n });\n },\n /** Register a named query scope.\n * @param name - Scope name to use with `.scoped(name)`.\n * @param fn - Function that receives a `SchemaQueryBuilder` and applies filters.\n */\n scope<N extends string>(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n name: N,\n fn: Function\n ): typeof this & { readonly [METHOD_LITERAL_BRAND]?: N } {\n const existing =\n (this.getExtension('scopes') as Record<string, Function>) ?? {};\n return this.withExtension('scopes', {\n ...existing,\n [name]: fn\n }) as typeof this & { readonly [METHOD_LITERAL_BRAND]?: N };\n },\n /**\n * Register a **named projection** — a reusable column subset that can be\n * applied at query time via `.projected(name)`.\n *\n * When a projection is applied the query builder:\n * 1. Issues `SELECT <cols>` instead of `SELECT *`.\n * 2. Narrows the TypeScript result row type to `Pick<Row, Keys>`.\n *\n * Columns are passed as **rest parameters**. Each argument can be either:\n *\n * - A **string** literal of a property name (autocompleted against the\n * schema's properties):\n * ```ts\n * .projection('summary', 'id', 'title', 'completed')\n * ```\n * - An **accessor callback** (refactor-safe — renaming a property\n * updates the projection automatically):\n * ```ts\n * .projection('listView', t => t.id, t => t.title, t => t.userId)\n * ```\n *\n * The two forms can be mixed freely:\n * ```ts\n * .projection('mixed', 'id', t => t.title)\n * ```\n *\n * The literal property keys flow through the type system, so\n * `.projected('listView')` still narrows the result type to\n * `Pick<Row, 'id' | 'title' | 'userId'>`.\n *\n * @param name - Unique projection name (used with `.projected()`).\n * @param columns - One argument per column: either a property name\n * string or a `t => t.propName` accessor callback.\n *\n * @example\n * ```ts\n * const PostSchema = object({ id: number(), title: string(), body: string() })\n * .hasTableName('posts')\n * .projection('summary', 'id', 'title')\n * .projection('detail', t => t.id, t => t.title, t => t.body);\n *\n * // Later:\n * const rows = await query(db, PostSchema).projected('summary');\n * // rows: Array<Pick<Post, 'id' | 'title'>>\n * ```\n *\n * @see {@link SchemaQueryBuilder.projected}\n */\n projection<\n TProperties extends Record<\n string,\n SchemaBuilder<any, any, any, any, any>\n >,\n const N extends string,\n const TKey extends keyof TProperties & string\n >(\n this: ObjectSchemaBuilder<\n TProperties,\n any,\n any,\n any,\n any,\n any,\n any\n >,\n name: N,\n ...columns: ReadonlyArray<\n | TKey\n | ((\n t: PropertyDescriptorTree<\n ObjectSchemaBuilder<\n TProperties,\n any,\n any,\n any,\n any,\n any,\n any\n >,\n ObjectSchemaBuilder<\n TProperties,\n any,\n any,\n any,\n any,\n any,\n any\n >\n >\n ) => PropertyDescriptor<any, any, any, TKey>)\n >\n ): typeof this & {\n readonly [EXTRA_TYPE_BRAND]?: { [P in N]: readonly TKey[] };\n } {\n const tree = ObjectSchemaBuilderClass.getPropertiesFor(this as any);\n const keys: string[] = (\n columns as ReadonlyArray<\n | string\n | ((t: any) => PropertyDescriptor<any, any, any, any>)\n >\n ).map(col => {\n if (typeof col === 'string') {\n return col;\n }\n const descriptor = col(tree);\n const inner = (descriptor as any)[\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR\n ];\n if (!inner) {\n throw new Error(\n `projection('${name}'): each accessor must return a valid PropertyDescriptor. ` +\n `Use \\`t => t.propName\\`.`\n );\n }\n if (typeof inner.propertyName !== 'string') {\n throw new Error(\n `projection('${name}'): could not resolve property name from descriptor. ` +\n `Ensure the accessor returns a top-level property descriptor.`\n );\n }\n return inner.propertyName as string;\n });\n const existing =\n (this.getExtension('projections') as Record<\n string,\n { keys: readonly string[] }\n >) ?? {};\n if (Object.hasOwn(existing, name)) {\n throw new Error(\n `projection('${name}'): a projection with this name is already registered on this schema. ` +\n `Each projection name must be unique.`\n );\n }\n return this.withExtension('projections', {\n ...existing,\n [name]: { keys }\n }) as typeof this & {\n readonly [EXTRA_TYPE_BRAND]?: { [P in N]: readonly TKey[] };\n };\n },\n /** Set a default scope applied to all queries unless `.unscoped()` is called.\n * @param fn - Function that receives a `SchemaQueryBuilder` and applies filters.\n */\n defaultScope(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n fn: Function\n ) {\n return this.withExtension('defaultScope', fn);\n },\n /** Register a before-insert lifecycle hook.\n * @param fn - Async function `(data) => data` called before inserting.\n */\n beforeInsert(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n fn: Function\n ) {\n const existing =\n (this.getExtension('beforeInsert') as Function[]) ?? [];\n return this.withExtension('beforeInsert', [...existing, fn]);\n },\n /** Register an after-insert lifecycle hook.\n * @param fn - Async function `(row)` called after inserting.\n */\n afterInsert(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n fn: Function\n ) {\n const existing =\n (this.getExtension('afterInsert') as Function[]) ?? [];\n return this.withExtension('afterInsert', [...existing, fn]);\n },\n /** Register a before-update lifecycle hook.\n * @param fn - Async function `(data) => data` called before updating.\n */\n beforeUpdate(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n fn: Function\n ) {\n const existing =\n (this.getExtension('beforeUpdate') as Function[]) ?? [];\n return this.withExtension('beforeUpdate', [...existing, fn]);\n },\n /** Register a before-delete lifecycle hook.\n * @param fn - Async function `(query)` called before deleting.\n */\n beforeDelete(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n fn: Function\n ) {\n const existing =\n (this.getExtension('beforeDelete') as Function[]) ?? [];\n return this.withExtension('beforeDelete', [...existing, fn]);\n }\n\n /**\n * Declare polymorphic variants for this schema.\n *\n * Turns a base schema into a **polymorphic schema** where a discriminator\n * column determines which variant each row belongs to. Variants can store\n * their extra fields either in a separate table (CTI — Class Table\n * Inheritance) or as nullable columns on the base table (STI — Single\n * Table Inheritance).\n *\n * The return type carries a phantom brand\n * (`[POLYMORPHIC_TYPE_BRAND]`) so that `query(db, schema)` automatically\n * infers the full discriminated-union result type.\n *\n * @param config.discriminator - Property key (or accessor) of the\n * discriminator column on the base table (e.g. `'type'` or `t => t.type`).\n * @param config.variants - Map from discriminator value to\n * `{ schema, storage, foreignKey?, allowOrphan?, enforceCheck? }`.\n * - `storage: 'cti'` — variant fields are in a separate table;\n * `foreignKey` (the FK column on the variant table) is required.\n * - `storage: 'sti'` — variant fields are nullable columns on the base table.\n *\n * @example\n * ```ts\n * const FileBase = object({ id: number().primaryKey(), name: string(), type: string() })\n * .hasTableName('files');\n *\n * const ImageExtras = object({ width: number(), height: number(), format: string() })\n * .hasTableName('image_file');\n *\n * const DocumentExtras = object({ size: number(), issueDate: date() })\n * .hasTableName('document_file');\n *\n * const ImageExtras = object({\n * fileId: number().hasColumnName('file_id'),\n * type: string('image'),\n * width: number(), height: number(), format: string()\n * }).hasTableName('image_file');\n *\n * const DocumentExtras = object({\n * fileId: number().hasColumnName('file_id'),\n * type: string('document'),\n * size: number(), issueDate: date()\n * }).hasTableName('document_file');\n *\n * const FileSchema = FileBase.withVariants({\n * discriminator: t => t.type,\n * variants: {\n * image: { schema: ImageExtras, storage: 'cti', foreignKey: t => t.fileId },\n * document: { schema: DocumentExtras, storage: 'cti', foreignKey: t => t.fileId },\n * },\n * });\n *\n * // query(db, FileSchema) returns:\n * // Array<\n * // | { id: number; name: string; type: 'image'; width: number; height: number; format: string }\n * // | { id: number; name: string; type: 'document'; size: number; issueDate: Date }\n * // >\n * ```\n */\n // NOTE: the public `.withVariants()` schema-level method has been\n // removed. Variants are now declared on the {@link Entity} chain via\n // `defineEntity(...).discriminator(...).ctiVariant(...).stiVariant(...)`.\n // The internal worker {@link applyVariantsToSchema} (below this\n // `defineExtension` block) is invoked by the Entity layer and stores\n // the same `'variants'` / `'polymorphicVariants'` extensions that\n // `SchemaQueryBuilder` reads at runtime.\n }\n});\n\n// ---------------------------------------------------------------------------\n// Variant resolution worker (used by the Entity layer)\n// ---------------------------------------------------------------------------\n\n/**\n * @internal Input to {@link applyVariantsToSchema}: a single resolved variant\n * entry, as produced by `Entity.ctiVariant()` / `Entity.stiVariant()`.\n */\nexport interface VariantInputForResolver {\n /** CTI-only: FK column name on the variant table that joins back to base PK. */\n foreignKeyColumn?: string;\n /** Variant body schema (CTI table or STI extras). */\n schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>;\n storage: VariantStorageType;\n allowOrphan?: boolean;\n enforceCheck?: boolean;\n /**\n * Variant-scoped relations (already resolved to FK column names). When\n * the variant entry is sourced from another `Entity`, these are read\n * from that entity's `'relations'` extension and passed in here.\n */\n relations?: ResolvedVariantRelationSpec[];\n}\n\n/**\n * @internal Validate + apply a fully-resolved variant config to a base\n * schema. Stores the `'variants'` and `'polymorphicVariants'` extensions\n * read by {@link SchemaQueryBuilder}.\n *\n * Called by the {@link Entity} chain (`.discriminator().ctiVariant().stiVariant()`).\n * Replaces the previous schema-level `.withVariants()` method.\n */\nexport function applyVariantsToSchema(\n baseSchema: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n discriminatorKey: string,\n variants: Record<string, VariantInputForResolver>\n): ObjectSchemaBuilder<any, any, any, any, any, any, any> {\n const resolvedVariants: Record<string, ResolvedVariantSpec> = {};\n const variantSchemas: ObjectSchemaBuilder<\n any,\n any,\n any,\n any,\n any,\n any,\n any\n >[] = [];\n\n for (const [key, spec] of Object.entries(variants)) {\n const varSchema = spec.schema;\n\n // Validate: if the variant schema declares the discriminator\n // property, its equalsTo literal must match the map key.\n const varIntrospected = varSchema.introspect() as any;\n const varProps: Record<string, any> = varIntrospected.properties ?? {};\n if (discriminatorKey in varProps) {\n const discPropIntrospected = varProps[\n discriminatorKey\n ].introspect() as any;\n const equalsTo: string | undefined = discPropIntrospected.equalsTo;\n if (equalsTo === undefined) {\n throw new Error(\n `withVariants: variant \"${key}\" declares discriminator property \"${discriminatorKey}\" ` +\n `but it has no literal value — use string('${key}') instead of string()`\n );\n }\n if (equalsTo !== key) {\n throw new Error(\n `withVariants: variant \"${key}\" declares discriminator \"${discriminatorKey}\" = ` +\n `string('${equalsTo}') but the map key is '${key}' — they must match`\n );\n }\n }\n\n let tableName: string | undefined;\n if (spec.storage === 'cti') {\n tableName = varSchema.getExtension('tableName') as\n | string\n | undefined;\n if (!tableName) {\n throw new Error(\n `withVariants: CTI variant \"${key}\" schema must have .hasTableName() configured`\n );\n }\n if (!spec.foreignKeyColumn) {\n throw new Error(\n `withVariants: CTI variant \"${key}\" must specify foreignKey (the FK property accessor on the variant schema)`\n );\n }\n }\n\n resolvedVariants[key] = {\n storage: spec.storage,\n schema: varSchema,\n foreignKey: spec.foreignKeyColumn,\n tableName,\n allowOrphan: spec.allowOrphan ?? false,\n enforceCheck: spec.enforceCheck ?? false,\n relations: spec.relations ?? []\n };\n\n variantSchemas.push(varSchema);\n }\n\n const variantConfig: Omit<ResolvedVariantConfig, 'discriminatorColumn'> = {\n discriminatorKey,\n variants: resolvedVariants\n };\n\n return (baseSchema as any)\n .withExtension('variants', variantConfig)\n .withExtension('polymorphicVariants', variantSchemas);\n}\n\n// ---------------------------------------------------------------------------\n// Extended factory functions\n// ---------------------------------------------------------------------------\n\nconst extended = withExtensions(\n stringExtensions,\n numberExtensions,\n arrayExtensions,\n dbExtension,\n ddlExtension\n);\n\nexport const string = extended.string;\nexport const number = extended.number;\nexport const boolean = extended.boolean;\nexport const date = extended.date;\nexport const object = extended.object;\nexport const array = extended.array;\nexport const union = extended.union;\nexport const func = extended.func;\nexport const any = extended.any;\n\n// ---------------------------------------------------------------------------\n// Primary-key methods (declared & patched out-of-band)\n// ---------------------------------------------------------------------------\n//\n// `.primaryKey()` and `.hasPrimaryKey()` are declared here via TypeScript\n// declaration merging on the base builder classes (NumberSchemaBuilder,\n// StringSchemaBuilder, ObjectSchemaBuilder) instead of via `defineExtension`.\n//\n// Why: `defineExtension` methods are rewritten by the schema library's\n// `FixedMethods<>` mapped type, which replaces the declared return type of\n// every extension method with `TBase & FixedMethods<...>`. That rewriting\n// silently discards any phantom-brand intersections (such as the\n// `[PRIMARY_KEY_BRAND]` carried on the return type), so downstream type-level\n// helpers like `PrimaryKeyOf` / `PrimaryKeyValueOf` resolve to `never`.\n//\n// By declaring these methods directly on the underlying classes via module\n// augmentation, the brand intersection survives — `this & { [PRIMARY_KEY_BRAND]?: true }`\n// stays attached to the builder type stored in `TProperties[K]`, so\n// `find(id)` infers the correct primary-key value type.\n// ---------------------------------------------------------------------------\n\ndeclare module '@cleverbrush/schema' {\n interface NumberSchemaBuilder<\n TResult,\n TRequired extends boolean,\n TNullable extends boolean,\n THasDefault extends boolean,\n TExtensions\n > {\n /** Mark this column as a primary key.\n * @param opts - Options. `autoIncrement` defaults to `true`.\n */\n primaryKey(opts?: { autoIncrement?: boolean }): this & {\n readonly [PRIMARY_KEY_BRAND]?: true;\n };\n }\n\n interface StringSchemaBuilder<\n TResult,\n TRequired extends boolean,\n TNullable extends boolean,\n THasDefault extends boolean,\n TExtensions\n > {\n /** Mark this column as a primary key (non-auto-increment). */\n primaryKey(): this & {\n readonly [PRIMARY_KEY_BRAND]?: true;\n };\n }\n\n interface ObjectSchemaBuilder<\n TProperties extends Record<\n string,\n SchemaBuilder<any, any, any, any, any>\n >,\n TRequired extends boolean,\n TNullable extends boolean,\n TExplicitType,\n THasDefault extends boolean,\n TExtensions,\n TConstructorSchemas\n > {\n /** Set a composite primary key.\n * @param columns - Column names (or property keys) forming the primary key,\n * in declaration order. Use `as const` to preserve ordering at the type level.\n */\n hasPrimaryKey<const TCols extends readonly string[]>(\n columns: TCols\n ): this & {\n readonly [COMPOSITE_PRIMARY_KEY_BRAND]?: TCols;\n };\n }\n}\n\n// Runtime patches: install the methods on the prototypes.\n// Idempotent — safe even if the module is loaded multiple times.\n(() => {\n const numProto = NumberSchemaBuilderClass.prototype as any;\n if (typeof numProto.primaryKey !== 'function') {\n numProto.primaryKey = function (opts?: { autoIncrement?: boolean }) {\n return this.withExtension('primaryKey', {\n autoIncrement: opts?.autoIncrement ?? true\n });\n };\n }\n\n const strProto = StringSchemaBuilderClass.prototype as any;\n if (typeof strProto.primaryKey !== 'function') {\n strProto.primaryKey = function () {\n return this.withExtension('primaryKey', { autoIncrement: false });\n };\n }\n\n const objProto = ObjectSchemaBuilderClass.prototype as any;\n if (typeof objProto.hasPrimaryKey !== 'function') {\n objProto.hasPrimaryKey = function (columns: readonly string[]) {\n return this.withExtension(\n 'compositePrimaryKey',\n columns as readonly string[]\n );\n };\n }\n})();\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Get the SQL column name for a schema property.\n * Returns the `hasColumnName()` value if set, otherwise falls back to `propertyKey`.\n */\nexport function getColumnName(\n schema: SchemaBuilder<any, any, any>,\n propertyKey: string\n): string {\n const col = schema.getExtension('columnName');\n return typeof col === 'string' ? col : propertyKey;\n}\n\n/**\n * Get the SQL table name from an ObjectSchemaBuilder.\n * Throws if `hasTableName()` was never called.\n */\nexport function getTableName(\n schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>\n): string {\n const table = schema.getExtension('tableName');\n if (typeof table !== 'string') {\n throw new Error(\n 'Schema does not have a table name. Use .hasTableName(\"table_name\") to set one.'\n );\n }\n return table;\n}\n\n/**\n * Retrieve the named projections registered on a schema via\n * `.projection(name, columns)`.\n *\n * Returns a map of `{ [name]: { keys: readonly string[] } }` where each\n * entry's `keys` array contains the **property keys** (not SQL column names)\n * for that projection. Returns an empty object when no projections are\n * defined.\n *\n * @example\n * ```ts\n * const projs = getProjections(PostSchema);\n * // { summary: { keys: ['id', 'title'] } }\n * ```\n */\nexport function getProjections(\n schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>\n): Record<string, { keys: readonly string[] }> {\n return (\n (schema.getExtension('projections') as Record<\n string,\n { keys: readonly string[] }\n >) ?? {}\n );\n}\n\n/**\n * Retrieve the resolved variant configuration stored by `.withVariants()`.\n * Returns `null` when the schema is not polymorphic.\n *\n * @internal — used by {@link SchemaQueryBuilder}.\n */\nexport function getVariants(\n schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>\n): Omit<ResolvedVariantConfig, 'discriminatorColumn'> | null {\n const cfg = schema.getExtension('variants');\n return cfg != null\n ? (cfg as Omit<ResolvedVariantConfig, 'discriminatorColumn'>)\n : null;\n}\n\n/**\n * Retrieve the array of variant `ObjectSchemaBuilder` instances stored by\n * `.withVariants()`. Used by migration / DDL tools to discover variant\n * tables without walking the full schema tree.\n *\n * @internal\n */\nexport function getPolymorphicVariantSchemas(\n schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>\n): ObjectSchemaBuilder<any, any, any, any, any, any, any>[] {\n return (\n (schema.getExtension('polymorphicVariants') as ObjectSchemaBuilder<\n any,\n any,\n any,\n any,\n any,\n any,\n any\n >[]) ?? []\n );\n}\n"],"mappings":"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","names":["arrayExtensions","defineExtension","NumberSchemaBuilderClass","numberExtensions","ObjectSchemaBuilderClass","StringSchemaBuilderClass","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","stringExtensions","withExtensions","EXTRA_TYPE_BRAND","METHOD_LITERAL_BRAND","PRIMARY_KEY_BRAND","COMPOSITE_PRIMARY_KEY_BRAND","POLYMORPHIC_TYPE_BRAND","hasColumnName","name","hasTableName","dbExtension","ddlExtension","table","column","action","value","type","precision","scale","expression","opts","sql","columns","existing","definition","fn","tree","keys","col","inner","applyVariantsToSchema","baseSchema","discriminatorKey","variants","resolvedVariants","variantSchemas","key","spec","varSchema","varProps","equalsTo","tableName","variantConfig","extended","string","number","boolean","date","object","array","union","func","any","numProto","strProto","objProto","getColumnName","schema","propertyKey","getTableName","getProjections","getVariants","cfg","getPolymorphicVariantSchemas"]}
package/dist/columns.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { ObjectSchemaBuilder } from '@cleverbrush/schema';
2
+ import type { Knex } from 'knex';
2
3
  import type { ColumnRef } from './types.js';
3
4
  interface ColumnMapResult {
4
5
  propToCol: Map<string, string>;
@@ -11,17 +12,85 @@ interface ColumnMapResult {
11
12
  */
12
13
  export declare function buildColumnMap(schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>): ColumnMapResult;
13
14
  /**
14
- * Resolve a ColumnRef to a plain SQL column name.
15
+ * Resolve a ColumnRef to a plain SQL column name (or a Knex.Raw expression for
16
+ * nested JSON paths when `knex` is supplied).
15
17
  *
16
- * - String refs are treated as **property keys** and translated to column
17
- * names via the column map.
18
+ * - String refs are treated as **property keys** (or dot-separated nested paths
19
+ * such as `'address.city'`) and translated to column names via the column map.
18
20
  * - Function refs (property accessor) are resolved via PropertyDescriptorTree,
19
- * then translated to column names.
21
+ * then translated to column names. Nested accessors (e.g. `t => t.address.city`)
22
+ * produce a `Knex.Raw` JSON-path expression when `knex` is provided.
23
+ *
24
+ * @param ref - Column reference (property key, dotted path, or accessor fn).
25
+ * @param schema - Root ObjectSchemaBuilder.
26
+ * @param label - Human-readable label for error messages.
27
+ * @param knex - Knex instance. Required for nested JSON-path refs; when omitted
28
+ * and a nested path is detected, an error is thrown.
20
29
  */
30
+ export declare function resolveColumnRef(ref: ColumnRef<any>, schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>, label: string, knex: Knex): string | Knex.Raw;
21
31
  export declare function resolveColumnRef(ref: ColumnRef<any>, schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>, label: string): string;
22
32
  /**
23
33
  * Resolve a ColumnRef to the **property key** (not the column name).
24
34
  * Used for result mapping.
25
35
  */
26
36
  export declare function resolvePropertyKey(ref: ColumnRef<any>, schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>, label: string): string;
37
+ /**
38
+ * Result of {@link getPrimaryKeyColumns}.
39
+ *
40
+ * `propertyKeys` are the schema property names (the keys you'd use in
41
+ * `.where(t => t.id, ...)` style accessors); `columnNames` are the SQL
42
+ * column names after applying any `hasColumnName()` overrides. Order is
43
+ * preserved: composite-PK ordering matches the user's `hasPrimaryKey()`
44
+ * declaration; single-PK arrays have length 1.
45
+ *
46
+ * @public
47
+ */
48
+ export interface PrimaryKeyColumns {
49
+ readonly propertyKeys: readonly string[];
50
+ readonly columnNames: readonly string[];
51
+ }
52
+ /**
53
+ * Resolve the primary-key columns of a schema at runtime.
54
+ *
55
+ * Composite primary keys (declared via `.hasPrimaryKey([...])` on the
56
+ * object schema) take precedence over single-column primary keys. The
57
+ * `columns` argument to `.hasPrimaryKey()` may contain either property keys
58
+ * or SQL column names — both forms are accepted and normalised.
59
+ *
60
+ * Returns `{ propertyKeys: [], columnNames: [] }` when no primary key is
61
+ * declared.
62
+ *
63
+ * @public
64
+ */
65
+ export declare function getPrimaryKeyColumns(schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>): PrimaryKeyColumns;
66
+ /**
67
+ * Concurrency-token strategy stored by `.rowVersion()`.
68
+ * - `'increment'` — ORM increments an integer counter on each UPDATE.
69
+ * - `'timestamp'` — ORM sets the value to `new Date()` on each UPDATE.
70
+ * - `'manual'` — caller supplies the new value; ORM only enforces the check.
71
+ *
72
+ * @public
73
+ */
74
+ export type RowVersionStrategy = 'increment' | 'timestamp' | 'manual';
75
+ /**
76
+ * Resolved row-version column for a schema, returned by
77
+ * {@link getRowVersionColumn}.
78
+ *
79
+ * @public
80
+ */
81
+ export interface RowVersionColumn {
82
+ /** Schema property name. */
83
+ propertyKey: string;
84
+ /** SQL column name (after any `hasColumnName()` override). */
85
+ columnName: string;
86
+ /** Concurrency-token update strategy. */
87
+ strategy: RowVersionStrategy;
88
+ }
89
+ /**
90
+ * Find the first column marked `.rowVersion()` on the schema and return its
91
+ * metadata. Returns `null` when no row-version column is declared.
92
+ *
93
+ * @public
94
+ */
95
+ export declare function getRowVersionColumn(schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>): RowVersionColumn | null;
27
96
  export {};
package/dist/ddl.d.ts ADDED
@@ -0,0 +1,66 @@
1
+ import type { ObjectSchemaBuilder } from '@cleverbrush/schema';
2
+ import type { Knex } from 'knex';
3
+ /**
4
+ * Generate a Knex `schema.createTable()` call from an ObjectSchemaBuilder's
5
+ * introspected metadata and DDL extensions.
6
+ *
7
+ * Returns a function that accepts a Knex instance and returns a
8
+ * `SchemaBuilder` (thenable). Call it inside a migration or setup script:
9
+ *
10
+ * @param schema - An `ObjectSchemaBuilder` with `.hasTableName()` and
11
+ * column-level DDL extensions (`.primaryKey()`, `.references()`, etc.).
12
+ * @returns `(knex: Knex) => Knex.SchemaBuilder` — a function that creates the
13
+ * table when executed.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const createUsersTable = generateCreateTable(UserSchema);
18
+ * await createUsersTable(knex);
19
+ * ```
20
+ */
21
+ export declare function generateCreateTable(schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>): (knex: Knex) => Knex.SchemaBuilder;
22
+ /**
23
+ * Generate `up` and `down` TypeScript code *strings* (4-space-indented\n * fragments) for creating a table from a schema.
24
+ *
25
+ * Unlike {@link generateCreateTable} (which returns a Knex callback), the
26
+ * output is plain source text suitable for writing into a migration file. The
27
+ * fragments are designed to be embedded directly inside `up()` / `down()`
28
+ * functions.
29
+ *
30
+ * @param schema - An `ObjectSchemaBuilder` with `.hasTableName()` and column
31
+ * DDL extensions.
32
+ * @returns `{ up, down }` source fragments.
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * const { up, down } = generateCreateTableSource(UserSchema);
37
+ * // up: " await knex.schema.createTable('users', (table) => { … });"
38
+ * // down: " await knex.schema.dropTableIfExists('users');"
39
+ * ```
40
+ */
41
+ export declare function generateCreateTableSource(schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>): {
42
+ up: string;
43
+ down: string;
44
+ };
45
+ /**
46
+ * Generate a series of Knex `schema.createTable()` calls for a polymorphic
47
+ * schema and all its CTI variant tables.
48
+ *
49
+ * Returns an array of `(knex: Knex) => Knex.SchemaBuilder` functions — one
50
+ * for the base table and one for each CTI variant. STI variants add their
51
+ * columns to the base table itself so no extra table is needed.
52
+ *
53
+ * Execute them sequentially in a migration:
54
+ *
55
+ * @param schema - The base `ObjectSchemaBuilder` with `.withVariants()` applied.
56
+ * @returns An array of table-creation functions to execute in order.
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * const creators = generateCreatePolymorphicTables(FileSchema);
61
+ * for (const create of creators) {
62
+ * await create(knex);
63
+ * }
64
+ * ```
65
+ */
66
+ export declare function generateCreatePolymorphicTables(schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>): Array<(knex: Knex) => Knex.SchemaBuilder>;
@@ -0,0 +1,264 @@
1
+ import { type ArraySchemaBuilder, type InferType, ObjectSchemaBuilder, type PropertyDescriptorTree, type SchemaBuilder, SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR } from '@cleverbrush/schema';
2
+ /**
3
+ * Phantom info for a single declared relation, stored on `Entity`'s `TRels`
4
+ * generic. `TForeign` is captured so `.include()` can type the customize
5
+ * callback against the correct foreign schema.
6
+ *
7
+ * @public
8
+ */
9
+ export interface RelationInfo<TKind extends 'belongsTo' | 'hasOne' | 'hasMany' | 'belongsToMany' = any, TForeign extends ObjectSchemaBuilder<any, any, any, any, any, any, any> = ObjectSchemaBuilder<any, any, any, any, any, any, any>> {
10
+ readonly kind: TKind;
11
+ readonly foreign: TForeign;
12
+ }
13
+ /**
14
+ * Extract the property record `{ [name]: SchemaBuilder }` from any
15
+ * `ObjectSchemaBuilder` regardless of variance positions.
16
+ *
17
+ * @public
18
+ */
19
+ export type SchemaProps<T> = T extends ObjectSchemaBuilder<infer P, any, any, any, any, any, any> ? P : never;
20
+ /**
21
+ * Strips the `withExtensions()` overlay from a schema type, reducing it to a
22
+ * plain `ObjectSchemaBuilder<TProps, TReq>` so `PropertyDescriptorTree<...>`
23
+ * resolves without hitting TypeScript's recursion depth limit.
24
+ *
25
+ * @internal
26
+ */
27
+ type EntitySchemaBase<T extends ObjectSchemaBuilder<any, any, any, any, any, any, any>> = T extends ObjectSchemaBuilder<infer P, infer Req, any, any, any, any, any> ? ObjectSchemaBuilder<P, Req> : never;
28
+ /**
29
+ * `PropertyDescriptorTree` overlay that pins each top-level property's
30
+ * literal `propertyName` into its descriptor. Required because
31
+ * `PropertyDescriptorTree` widens `propertyName` to `string` for sub-trees
32
+ * whose property is itself an `ObjectSchemaBuilder` (since
33
+ * `PropertyDescriptorTree` lacks a `TPropertyKey` generic).
34
+ *
35
+ * @internal
36
+ */
37
+ type EntityTree<TSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>> = PropertyDescriptorTree<EntitySchemaBase<TSchema>, EntitySchemaBase<TSchema>> & {
38
+ readonly [K in keyof SchemaProps<TSchema> & string]: {
39
+ readonly [SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR]: {
40
+ readonly propertyName: K;
41
+ };
42
+ };
43
+ };
44
+ /**
45
+ * Selector callback that receives a real {@link PropertyDescriptorTree} so
46
+ * `t => t.someProp` navigates to the property definition and preserves its
47
+ * JSDoc in IDE tooltips. The return shape is matched structurally on the
48
+ * `[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR].propertyName` field, which captures
49
+ * the literal property key as `TKey`.
50
+ *
51
+ * @public
52
+ */
53
+ export type EntityPropSelector<TSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>, TKey extends string = string> = (t: EntityTree<TSchema>) => {
54
+ readonly [SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR]: {
55
+ readonly propertyName: TKey;
56
+ };
57
+ };
58
+ /**
59
+ * Peel `.optional()` / `array(...)` wrappers off a navigation property's
60
+ * schema to recover the underlying foreign `ObjectSchemaBuilder`.
61
+ *
62
+ * @public
63
+ */
64
+ export type UnwrapNavSchema<TProp> = TProp extends ArraySchemaBuilder<infer TEl, any, any, any> ? TEl extends ObjectSchemaBuilder<any, any, any, any, any, any, any> ? TEl : never : TProp extends ObjectSchemaBuilder<any, any, any, any, any, any, any> ? TProp : TProp extends SchemaBuilder<infer T, any, any, any, any> ? T extends ObjectSchemaBuilder<any, any, any, any, any, any, any> ? T : never : never;
65
+ /**
66
+ * Merge type for a single polymorphic variant branch: variant schema fields
67
+ * overlay base schema fields (narrowing the discriminator from `string` to its
68
+ * specific literal value).
69
+ *
70
+ * @public
71
+ */
72
+ export type VariantBranch<TBaseSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>, TVarSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>> = Omit<InferType<TBaseSchema>, keyof InferType<TVarSchema>> & InferType<TVarSchema>;
73
+ /**
74
+ * Return type for an Entity method that adds a relation: keeps `TSchema`,
75
+ * extends `TRels` with one more entry, and preserves `TVariantUnion`.
76
+ *
77
+ * @public
78
+ */
79
+ export type WithRelation<TSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>, TRels extends Record<string, RelationInfo>, TKey extends string, TKind extends 'belongsTo' | 'hasOne' | 'hasMany' | 'belongsToMany', TForeign extends ObjectSchemaBuilder<any, any, any, any, any, any, any>, TVariantUnion = never> = Entity<TSchema, TRels & Record<TKey, RelationInfo<TKind, TForeign>>, TVariantUnion>;
80
+ /**
81
+ * A typed wrapper around an `ObjectSchemaBuilder` that tracks declared
82
+ * relations in its `TRels` generic. Use {@link defineEntity} to create one.
83
+ *
84
+ * Relations are declared via `.hasOne()` / `.hasMany()` / `.belongsTo()` /
85
+ * `.belongsToMany()` on the entity (NOT on the underlying schema). Each
86
+ * call returns a new `Entity` whose `TRels` includes the new relation, so
87
+ * `query(db, entity).include(t => t.assignee)` is fully typed.
88
+ *
89
+ * @public
90
+ */
91
+ export declare class Entity<TSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>, TRels extends Record<string, RelationInfo> = {}, TVariantUnion = never> {
92
+ /** The underlying schema (relations registered via `withExtension('relations', ...)`). */
93
+ readonly schema: TSchema;
94
+ /** @internal Phantom slot to retain `TRels` in inferred types. */
95
+ private readonly __relations__;
96
+ /** @internal Phantom slot to retain `TVariantUnion` in inferred types. */
97
+ private readonly __variantUnion__;
98
+ constructor(schema: TSchema);
99
+ private _addRelation;
100
+ /**
101
+ * Declare a one-to-one relation where the FK lives on the FOREIGN table.
102
+ * `navSel` selects the navigation property on THIS schema (must hold the
103
+ * foreign schema, typically `.optional()`). The foreign schema is
104
+ * auto-resolved at runtime from that property; provided explicitly here
105
+ * via `opts.foreign` only when peeling fails.
106
+ *
107
+ * @param navSel Selector of nav property: `t => t.author`
108
+ * @param localSel Selector of local-side join key: `l => l.id`
109
+ * @param remoteSel Selector of remote-side FK on foreign schema: `r => r.userId`
110
+ * @param opts Optional `{ optional?: boolean }` (default false).
111
+ */
112
+ hasOne<TKey extends keyof SchemaProps<TSchema> & string, TForeign extends ObjectSchemaBuilder<any, any, any, any, any, any, any> = UnwrapNavSchema<SchemaProps<TSchema>[TKey]>>(navSel: EntityPropSelector<TSchema, TKey>, _localSel: EntityPropSelector<TSchema>, remoteSel: EntityPropSelector<TForeign>, opts?: {
113
+ optional?: boolean;
114
+ }): WithRelation<TSchema, TRels, TKey, 'hasOne', TForeign, TVariantUnion>;
115
+ /**
116
+ * Declare a one-to-many relation where the FK lives on the FOREIGN table.
117
+ *
118
+ * @param navSel Selector of nav array property: `t => t.posts`
119
+ * @param localSel Selector of local-side join key: `l => l.id`
120
+ * @param remoteSel Selector of remote-side FK on foreign schema: `r => r.userId`
121
+ */
122
+ hasMany<TKey extends keyof SchemaProps<TSchema> & string, TForeign extends ObjectSchemaBuilder<any, any, any, any, any, any, any> = UnwrapNavSchema<SchemaProps<TSchema>[TKey]>>(navSel: EntityPropSelector<TSchema, TKey>, _localSel: EntityPropSelector<TSchema>, remoteSel: EntityPropSelector<TForeign>): WithRelation<TSchema, TRels, TKey, 'hasMany', TForeign, TVariantUnion>;
123
+ /**
124
+ * Declare a many-to-one relation where the FK lives on THIS table.
125
+ *
126
+ * @param navSel Selector of nav property (foreign schema, usually `.optional()`).
127
+ * @param localSel Selector of local-side FK property: `l => l.userId`
128
+ * @param remoteSel Selector of foreign-side PK property: `r => r.id`
129
+ */
130
+ belongsTo<TKey extends keyof SchemaProps<TSchema> & string, TForeign extends ObjectSchemaBuilder<any, any, any, any, any, any, any> = UnwrapNavSchema<SchemaProps<TSchema>[TKey]>>(navSel: EntityPropSelector<TSchema, TKey>, localSel: EntityPropSelector<TSchema>, _remoteSel: EntityPropSelector<TForeign>, opts?: {
131
+ optional?: boolean;
132
+ }): WithRelation<TSchema, TRels, TKey, 'belongsTo', TForeign, TVariantUnion>;
133
+ /**
134
+ * Declare a many-to-many relation through a pivot table.
135
+ *
136
+ * @param navSel Selector of nav array property: `t => t.tags`
137
+ * @param through Pivot table config `{ table, localKey, foreignKey }`.
138
+ */
139
+ belongsToMany<TKey extends keyof SchemaProps<TSchema> & string, TForeign extends ObjectSchemaBuilder<any, any, any, any, any, any, any> = UnwrapNavSchema<SchemaProps<TSchema>[TKey]>>(navSel: EntityPropSelector<TSchema, TKey>, through: {
140
+ table: string;
141
+ localKey: string;
142
+ foreignKey: string;
143
+ }): WithRelation<TSchema, TRels, TKey, 'belongsToMany', TForeign, TVariantUnion>;
144
+ /**
145
+ * Mark this entity as polymorphic by selecting the discriminator
146
+ * property. Returns a new `Entity` whose `.ctiVariant()` /
147
+ * `.stiVariant()` methods declare each variant.
148
+ *
149
+ * Declare ordinary relations (`.belongsTo()` etc.) on this entity BEFORE
150
+ * `.discriminator()`. After `.discriminator()` you are in the
151
+ * polymorphic-builder phase: `.hasOne/.hasMany/.belongsTo/.belongsToMany`
152
+ * still work, but the per-variant relations live on the `.ctiVariant()`
153
+ * / `.stiVariant()` calls.
154
+ *
155
+ * @example
156
+ * ```ts
157
+ * const FileEntity = defineEntity(FileSchema)
158
+ * .discriminator(t => t.type)
159
+ * .ctiVariant('image', ImageEntity, t => t.fileId)
160
+ * .ctiVariant('document', DocumentEntity, t => t.fileId);
161
+ * ```
162
+ */
163
+ discriminator<TKey extends keyof SchemaProps<TSchema> & string>(sel: TKey | EntityPropSelector<TSchema, TKey>): Entity<TSchema, TRels, never>;
164
+ /**
165
+ * Declare a CTI (Class Table Inheritance) variant. Only valid after
166
+ * `.discriminator()`. Returns a new `Entity` whose schema carries the
167
+ * updated `'variants'` extension.
168
+ *
169
+ * @param key Discriminator literal (e.g. `'image'`).
170
+ * @param variant Entity wrapping the variant table schema.
171
+ * @param fkSel Accessor returning the FK property on the variant
172
+ * schema that joins back to the base PK.
173
+ * @param opts `{ allowOrphan?: boolean; relations?: ... }` — see CTI docs.
174
+ */
175
+ ctiVariant<TVarSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>>(key: string, variant: Entity<TVarSchema, any>, fkSel: (t: EntityTree<TVarSchema>) => any, opts?: {
176
+ allowOrphan?: boolean;
177
+ relations?: Record<string, VariantRelationInput<TVarSchema>>;
178
+ }): Entity<TSchema, TRels, TVariantUnion | VariantBranch<TSchema, TVarSchema>>;
179
+ /**
180
+ * Declare an STI (Single Table Inheritance) variant. Only valid after
181
+ * `.discriminator()`. Accepts either an {@link Entity} or a bare
182
+ * {@link ObjectSchemaBuilder}.
183
+ */
184
+ stiVariant<TVarSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>>(key: string, body: Entity<TVarSchema, any> | TVarSchema, opts?: {
185
+ enforceCheck?: boolean;
186
+ relations?: Record<string, VariantRelationInput<TVarSchema>>;
187
+ }): Entity<TSchema, TRels, TVariantUnion | VariantBranch<TSchema, TVarSchema>>;
188
+ /** @internal Apply the variant extension to the schema and return a new Entity. */
189
+ private _withVariantBuilder;
190
+ /**
191
+ * @internal
192
+ * Resolve a selector callback against the real `PropertyDescriptorTree`
193
+ * of the given schema, returning the top-level property name. Throws if
194
+ * the accessor does not yield a valid descriptor.
195
+ */
196
+ private _resolvePropName;
197
+ /** @internal Resolve the foreign schema from a nav property. */
198
+ private _resolveForeignSchema;
199
+ }
200
+ /**
201
+ * Inline relation spec accepted by {@link Entity.ctiVariant} /
202
+ * {@link Entity.stiVariant} via `opts.relations`. The `foreignKey`
203
+ * accessor (when given) is typed against the variant's own schema and
204
+ * resolved to a SQL column name.
205
+ *
206
+ * @public
207
+ */
208
+ export interface VariantRelationInput<TVarSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any> = ObjectSchemaBuilder<any, any, any, any, any, any, any>> {
209
+ type: 'belongsTo' | 'hasOne' | 'hasMany' | 'belongsToMany';
210
+ /**
211
+ * Foreign side — accept either a bare schema, an Entity wrapper, or a
212
+ * lazy thunk for forward references.
213
+ */
214
+ schema?: ObjectSchemaBuilder<any, any, any, any, any, any, any> | (() => ObjectSchemaBuilder<any, any, any, any, any, any, any>);
215
+ entity?: Entity<any, any>;
216
+ foreignKey?: (t: EntityTree<TVarSchema>) => any;
217
+ through?: {
218
+ table: string;
219
+ localKey: string;
220
+ foreignKey: string;
221
+ };
222
+ }
223
+ /**
224
+ * Wrap an `ObjectSchemaBuilder` in an {@link Entity}, enabling typed relation
225
+ * declaration via `.hasOne()` / `.hasMany()` / `.belongsTo()` / `.belongsToMany()`.
226
+ *
227
+ * @public
228
+ *
229
+ * @example
230
+ * ```ts
231
+ * const UserEntity = defineEntity(
232
+ * object({
233
+ * id: number().primaryKey(),
234
+ * name: string(),
235
+ * posts: array(PostEntity.schema)
236
+ * }).hasTableName('users')
237
+ * ).hasMany(t => t.posts, l => l.id, r => r.userId);
238
+ * ```
239
+ */
240
+ export declare function defineEntity<TSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>>(schema: TSchema): Entity<TSchema, {}>;
241
+ /**
242
+ * Type-level helper: extract `TRels` from any `Entity` type.
243
+ * @public
244
+ */
245
+ export type EntityRelations<E> = E extends Entity<any, infer R, any> ? R : never;
246
+ /**
247
+ * Type-level helper: extract the underlying schema type from any `Entity`.
248
+ * @public
249
+ */
250
+ export type EntitySchema<E> = E extends Entity<infer S, any, any> ? S : never;
251
+ /**
252
+ * Type-level helper: extract the accumulated variant union from any `Entity`.
253
+ * Resolves to `never` for non-polymorphic entities.
254
+ * @public
255
+ */
256
+ export type EntityVariantUnion<E> = E extends Entity<any, any, infer U> ? U : never;
257
+ /**
258
+ * Type-level helper: union of relation key names declared on an entity.
259
+ * Used by `SchemaQueryBuilder.insert()/update()/upsert()` to omit relation
260
+ * navigation properties from accepted input.
261
+ * @public
262
+ */
263
+ export type EntityRelationKeys<E> = E extends Entity<any, infer R, any> ? keyof R & string : never;
264
+ export {};