UNPKG

kitcn

Version:

kitcn - React Query integration and CLI tools for Convex

1,420 lines (1,403 loc) 636 kB
#!/usr/bin/env node import { createRequire } from "node:module"; import fs from "node:fs"; import path, { basename, dirname, isAbsolute, join, posix, relative, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { createHash, randomBytes } from "node:crypto"; import { confirm, isCancel, multiselect, select, spinner } from "@clack/prompts"; import { parse } from "dotenv"; import { execa } from "execa"; import { v } from "convex/values"; import "convex/server"; import { createInterface } from "node:readline/promises"; import { build } from "esbuild"; import { createColors } from "picocolors"; import { createJiti } from "jiti"; import os from "node:os"; import { parse as parse$1 } from "@babel/parser"; //#region src/orm/builders/column-builder.ts /** * entityKind symbol for runtime type checking * Following Drizzle's pattern for type guards */ const entityKind = Symbol.for("kitcn:entityKind"); /** * Base ColumnBuilder abstract class * * All column builders inherit from this class. * Implements chaining methods and stores runtime config. */ var ColumnBuilder = class { static [entityKind] = "ColumnBuilder"; [entityKind] = "ColumnBuilder"; /** * Runtime configuration - actual mutable state */ config; constructor(name, dataType, columnType) { this.config = { name, notNull: false, default: void 0, hasDefault: false, primaryKey: false, isUnique: false, uniqueName: void 0, uniqueNulls: void 0, foreignKeyConfigs: [], dataType, columnType }; } /** * Mark column as NOT NULL * Returns type-branded instance with notNull: true */ notNull() { this.config.notNull = true; return this; } /** * Override the TypeScript type for this column. * Mirrors Drizzle's $type() (type-only, no runtime validation changes). */ $type() { return this; } /** * Set default value for column * Makes field optional on insert */ default(value) { this.config.default = value; this.config.hasDefault = true; return this; } /** * Set default function for column (runtime evaluated on insert). * Mirrors Drizzle's $defaultFn() / $default(). */ $defaultFn(fn) { this.config.defaultFn = fn; return this; } /** * Alias of $defaultFn for Drizzle parity. */ $default(fn) { return this.$defaultFn(fn); } /** * Set on-update function for column (runtime evaluated on update). * Mirrors Drizzle's $onUpdateFn() / $onUpdate(). */ $onUpdateFn(fn) { this.config.onUpdateFn = fn; return this; } /** * Alias of $onUpdateFn for Drizzle parity. */ $onUpdate(fn) { return this.$onUpdateFn(fn); } /** * Mark column as primary key * Implies NOT NULL */ primaryKey() { this.config.primaryKey = true; this.config.notNull = true; return this; } /** * Mark column as UNIQUE * Mirrors Drizzle column unique API */ unique(name, config) { this.config.isUnique = true; this.config.uniqueName = name; this.config.uniqueNulls = config?.nulls; return this; } /** * Define a foreign key reference * Mirrors Drizzle column references() API */ references(ref, config = {}) { this.config.foreignKeyConfigs.push({ ref, config }); return this; } }; //#endregion //#region src/orm/builders/system-fields.ts /** * System Fields - Convex-provided fields available on all documents * * id: Document ID (string, backed by internal Convex _id) * createdAt: Creation timestamp alias (backed by internal Convex _creationTime) * * These are automatically added to every Convex table. */ var ConvexSystemIdBuilder = class extends ColumnBuilder { static [entityKind] = "ConvexSystemIdBuilder"; [entityKind] = "ConvexSystemIdBuilder"; constructor() { super("_id", "string", "ConvexSystemId"); this.config.notNull = true; } build() { return v.string(); } /** * Convex validator - runtime access * System fields use v.string() for _id */ get convexValidator() { return this.build(); } }; var ConvexSystemCreationTimeBuilder = class extends ColumnBuilder { static [entityKind] = "ConvexSystemCreationTimeBuilder"; [entityKind] = "ConvexSystemCreationTimeBuilder"; constructor() { super("_creationTime", "number", "ConvexSystemCreationTime"); this.config.notNull = true; } build() { return v.number(); } /** * Convex validator - runtime access * System fields use v.number() for _creationTime */ get convexValidator() { return this.build(); } }; var ConvexSystemCreatedAtBuilder = class extends ColumnBuilder { static [entityKind] = "ConvexSystemCreatedAtBuilder"; [entityKind] = "ConvexSystemCreatedAtBuilder"; constructor() { super("_creationTime", "number", "ConvexSystemCreatedAt"); this.config.notNull = true; } build() { return v.number(); } get convexValidator() { return this.build(); } }; function createSystemFields(tableName) { const id = new ConvexSystemIdBuilder(); const creationTime = new ConvexSystemCreationTimeBuilder(); const createdAt = new ConvexSystemCreatedAtBuilder(); id.config.tableName = tableName; creationTime.config.tableName = tableName; createdAt.config.tableName = tableName; return { id, _creationTime: creationTime, createdAt }; } //#endregion //#region src/orm/index-utils.ts function getIndexes(table) { const indexes = table.getIndexes?.(); return Array.isArray(indexes) ? indexes : []; } function getAggregateIndexes(table) { const indexes = table.getAggregateIndexes?.(); return Array.isArray(indexes) ? indexes : []; } function getRankIndexes(table) { const indexes = table.getRankIndexes?.(); return Array.isArray(indexes) ? indexes : []; } //#endregion //#region src/orm/symbols.ts const TableName = Symbol.for("kitcn:TableName"); const Columns = Symbol.for("kitcn:Columns"); const Brand = Symbol.for("kitcn:Brand"); const Relations = Symbol.for("kitcn:Relations"); const OrmContext = Symbol.for("kitcn:OrmContext"); const RlsPolicies = Symbol.for("kitcn:RlsPolicies"); const EnableRLS = Symbol.for("kitcn:EnableRLS"); const TableDeleteConfig = Symbol.for("kitcn:TableDeleteConfig"); const TablePolymorphic = Symbol.for("kitcn:TablePolymorphic"); const OrmSchemaOptions = Symbol.for("kitcn:OrmSchemaOptions"); const OrmSchemaDefinition = Symbol.for("kitcn:OrmSchemaDefinition"); const OrmSchemaExtensionTables = Symbol.for("kitcn:OrmSchemaExtensionTables"); const OrmSchemaExtensions = Symbol.for("kitcn:OrmSchemaExtensions"); const OrmSchemaExtensionRelations = Symbol.for("kitcn:OrmSchemaExtensionRelations"); const OrmSchemaExtensionTriggers = Symbol.for("kitcn:OrmSchemaExtensionTriggers"); const OrmSchemaRelations = Symbol.for("kitcn:OrmSchemaRelations"); const OrmSchemaTriggers = Symbol.for("kitcn:OrmSchemaTriggers"); //#endregion //#region src/orm/mutation-utils.ts const UTF8_ENCODER = new TextEncoder(); function getTableName(table) { const name = table.tableName ?? table[TableName] ?? table?._?.name; if (!name) throw new Error("Table is missing a name"); return name; } function getUniqueIndexes(table) { const fromMethod = table.getUniqueIndexes?.(); if (Array.isArray(fromMethod)) return fromMethod; const fromField = table.uniqueIndexes; return Array.isArray(fromField) ? fromField : []; } function getChecks(table) { const fromMethod = table.getChecks?.(); if (Array.isArray(fromMethod)) return fromMethod; const fromField = table.checks; return Array.isArray(fromField) ? fromField : []; } function getForeignKeys(table) { const fromMethod = table.getForeignKeys?.(); if (Array.isArray(fromMethod)) return fromMethod; const fromField = table.foreignKeys; return Array.isArray(fromField) ? fromField : []; } //#endregion //#region src/orm/introspection.ts function getSystemFields(table) { if (table.id && table._creationTime) return { id: table.id, createdAt: table._creationTime ?? table.createdAt }; const system = createSystemFields(getTableName(table)); for (const builder of Object.values(system)) builder.config.table = table; return { id: system.id, createdAt: system.createdAt }; } function getTableColumns$1(table) { return { ...table[Columns] ?? {}, ...getSystemFields(table) }; } function getTableConfig(table) { const policies = table.getRlsPolicies?.() ?? table[RlsPolicies] ?? []; const enabled = table.isRlsEnabled?.() ?? table[EnableRLS] ?? false; return { name: getTableName(table), columns: getTableColumns$1(table), indexes: getIndexes(table), aggregateIndexes: getAggregateIndexes(table), rankIndexes: getRankIndexes(table), uniqueIndexes: getUniqueIndexes(table), foreignKeys: getForeignKeys(table), checks: getChecks(table), rls: { enabled, policies } }; } //#endregion //#region src/orm/builders/convex-column-builder.ts /** * Convex-specific column builder base class * * All Convex column builders (ConvexTextBuilder, ConvexIntegerBuilder, etc.) * inherit from this class. */ var ConvexColumnBuilder = class extends ColumnBuilder { static [entityKind] = "ConvexColumnBuilder"; }; //#endregion //#region src/orm/builders/boolean.ts /** * Boolean column builder class * Compiles to v.boolean() or v.optional(v.boolean()) */ var ConvexBooleanBuilder = class extends ConvexColumnBuilder { static [entityKind] = "ConvexBooleanBuilder"; constructor(name) { super(name, "boolean", "ConvexBoolean"); } /** * Expose Convex validator for schema integration */ get convexValidator() { if (this.config.notNull) return v.boolean(); return v.optional(v.union(v.null(), v.boolean())); } /** * Compile to Convex validator * .notNull() → v.boolean() * nullable → v.optional(v.boolean()) */ build() { return this.convexValidator; } }; function boolean$1(name) { return new ConvexBooleanBuilder(name ?? ""); } //#endregion //#region src/internal/upstream/validators.ts /** @deprecated Use `v.string()` instead. Any string value. */ const string = v.string(); /** @deprecated Use `v.float64()` instead. JavaScript number, represented as a float64 in the database. */ const number = v.float64(); /** @deprecated Use `v.float64()` instead. JavaScript number, represented as a float64 in the database. */ const float64 = v.float64(); /** @deprecated Use `v.boolean()` instead. boolean value. For typing it only as true, use `l(true)` */ const boolean = v.boolean(); /** @deprecated Use `v.int64()` instead. bigint, though stored as an int64 in the database. */ const biging = v.int64(); /** @deprecated Use `v.int64()` instead. bigint, though stored as an int64 in the database. */ const int64 = v.int64(); /** @deprecated Use `v.any()` instead. Any Convex value */ const any = v.any(); /** @deprecated Use `v.null()` instead. Null value. Underscore is so it doesn't shadow the null builtin */ const null_ = v.null(); /** @deprecated Use `v.*()` instead. */ const { id: id$1, object, array, bytes, literal, optional, union } = v; /** @deprecated Use `v.bytes()` instead. ArrayBuffer validator. */ const arrayBuffer = v.bytes(); /** Mark fields as deprecated with this permissive validator typed as null */ const deprecated = v.optional(v.any()); /** * Converts an optional validator to a required validator. * * This is the inverse of `v.optional()`. It takes a validator that may be optional * and returns the equivalent required validator. * * ```ts * const optionalString = v.optional(v.string()); * const requiredString = vRequired(optionalString); // v.string() * * // Already required validators are returned as-is * const alreadyRequired = v.string(); * const stillRequired = vRequired(alreadyRequired); // v.string() * ``` * * @param validator The validator to make required. * @returns A required version of the validator. */ function vRequired(validator) { const { kind, isOptional } = validator; if (isOptional === "required") return validator; switch (kind) { case "id": return v.id(validator.tableName); case "string": return v.string(); case "float64": return v.float64(); case "int64": return v.int64(); case "boolean": return v.boolean(); case "null": return v.null(); case "any": return v.any(); case "literal": return v.literal(validator.value); case "bytes": return v.bytes(); case "object": return v.object(validator.fields); case "array": return v.array(validator.element); case "record": return v.record(validator.key, validator.value); case "union": return v.union(...validator.members); default: throw new Error("Unknown Convex validator type: " + kind); } } //#endregion //#region src/orm/builders/custom.ts function isRecord$2(value) { return typeof value === "object" && value !== null && !Array.isArray(value); } function isValidator(value) { return isRecord$2(value) && typeof value.kind === "string" && typeof value.isOptional === "string"; } function isColumnBuilder$1(value) { return isRecord$2(value) && value[entityKind] === "ColumnBuilder"; } function toRequiredValidator(validator) { return validator.isOptional === "optional" ? vRequired(validator) : validator; } function toRequiredBuilderValidator(validator) { const requiredValidator = toRequiredValidator(validator); if (requiredValidator.kind !== "union") return requiredValidator; const nonNullMembers = requiredValidator.members.filter((member) => member.kind !== "null"); if (nonNullMembers.length !== 1) return requiredValidator; const [member] = nonNullMembers; if (member.kind === "object" || member.kind === "array") return member; return requiredValidator; } function formatInvalidInput(path, value) { return `${path} expected a column builder, Convex validator, or nested object shape. Got ${Array.isArray(value) ? "array" : value === null ? "null" : typeof value}.`; } function objectShapeToValidator(shape, path) { const fields = {}; for (const [key, value] of Object.entries(shape)) fields[key] = nestedInputToValidator(value, `${path}.${key}`); return v.object(fields); } function nestedInputToValidator(input, path) { if (isColumnBuilder$1(input)) return toRequiredBuilderValidator(input.convexValidator); if (isValidator(input)) return toRequiredValidator(input); if (isRecord$2(input)) return objectShapeToValidator(input, path); throw new Error(formatInvalidInput(path, input)); } var ConvexCustomBuilder = class extends ConvexColumnBuilder { static [entityKind] = "ConvexCustomBuilder"; constructor(name, validator) { super(name, "any", "ConvexCustom"); this.config.validator = validator; } get convexValidator() { const validator = this.config.validator; if (this.config.notNull) return validator; return v.optional(v.union(v.null(), validator)); } build() { return this.convexValidator; } }; function custom(a, b) { if (b !== void 0) return new ConvexCustomBuilder(a, b); return new ConvexCustomBuilder("", a); } /** * Creates an array column from a nested validator or builder. * * Values in nested arrays are always compiled as required validators. */ function arrayOf(element) { return custom(v.array(nestedInputToValidator(element, "arrayOf(element)"))).$type(); } /** * Creates an object column from either: * - a nested shape of validators/builders, or * - a validator/builder describing homogeneous record values * * Fields in nested objects are always compiled as required validators. */ function objectOf(input) { if (isColumnBuilder$1(input) || isValidator(input)) return custom(v.record(v.string(), nestedInputToValidator(input, "objectOf(value)"))).$type(); if (!isRecord$2(input)) throw new Error(formatInvalidInput("objectOf(shape)", input)); return custom(objectShapeToValidator(input, "objectOf(shape)")).$type(); } /** * Convenience wrapper for Convex "JSON" values. * * Note: This is Convex JSON (runtime `v.any()`), not SQL JSON/JSONB. */ function json() { return custom(v.any()).$type(); } //#endregion //#region src/orm/builders/id.ts /** * ID column builder class * Compiles to v.id(tableName) or v.optional(v.id(tableName)) */ var ConvexIdBuilder = class extends ConvexColumnBuilder { static [entityKind] = "ConvexIdBuilder"; constructor(name, tableName) { super(name, "string", "ConvexId"); this.tableName = tableName; this.config.referenceTable = tableName; } /** * Expose Convex validator for schema integration */ get convexValidator() { if (this.config.notNull) return v.id(this.tableName); return v.optional(v.union(v.null(), v.id(this.tableName))); } /** * Compile to Convex validator * .notNull() → v.id(tableName) * nullable → v.optional(v.id(tableName)) */ build() { return this.convexValidator; } }; function id(tableName) { return new ConvexIdBuilder("", tableName); } //#endregion //#region src/orm/builders/number.ts /** * Number column builder class * Compiles to v.number() or v.optional(v.number()) */ var ConvexNumberBuilder = class extends ConvexColumnBuilder { static [entityKind] = "ConvexNumberBuilder"; constructor(name) { super(name, "number", "ConvexNumber"); } /** * Expose Convex validator for schema integration */ get convexValidator() { if (this.config.notNull) return v.number(); return v.optional(v.union(v.null(), v.number())); } /** * Compile to Convex validator * .notNull() → v.number() * nullable → v.optional(v.number()) */ build() { return this.convexValidator; } }; function integer(name) { return new ConvexNumberBuilder(name ?? ""); } //#endregion //#region src/orm/builders/text.ts /** * Text column builder class * Compiles to v.string() or v.optional(v.string()) */ var ConvexTextBuilder = class extends ConvexColumnBuilder { static [entityKind] = "ConvexTextBuilder"; constructor(name) { super(name, "string", "ConvexText"); } /** * Expose Convex validator for schema integration */ get convexValidator() { if (this.config.notNull) return v.string(); return v.optional(v.union(v.null(), v.string())); } /** * Compile to Convex validator * .notNull() → v.string() * nullable → v.optional(v.string()) */ build() { return this.convexValidator; } }; function text(name) { return new ConvexTextBuilder(name ?? ""); } //#endregion //#region src/orm/extensions.ts function defineChainMethod(target, key, value) { Object.defineProperty(target, key, { value, enumerable: false, configurable: true }); } function createSchemaExtensionChain(state, capabilities) { const extension = { key: state.key, tables: state.tables }; Object.defineProperty(extension, OrmSchemaExtensionRelations, { value: state.relations, enumerable: false, configurable: true }); Object.defineProperty(extension, OrmSchemaExtensionTriggers, { value: state.triggers, enumerable: false, configurable: true }); if (capabilities.canRelations) defineChainMethod(extension, "relations", (relations) => createSchemaExtensionChain({ ...state, relations }, { canRelations: false, canTriggers: true })); if (capabilities.canTriggers) defineChainMethod(extension, "triggers", (triggers) => createSchemaExtensionChain({ ...state, triggers }, { canRelations: false, canTriggers: false })); return extension; } function defineSchemaExtension(key, tables) { return createSchemaExtensionChain({ key, tables, relations: void 0, triggers: void 0 }, { canRelations: true, canTriggers: true }); } //#endregion //#region src/orm/indexes.ts var ConvexIndexBuilderOn = class { static [entityKind] = "ConvexIndexBuilderOn"; [entityKind] = "ConvexIndexBuilderOn"; constructor(name, unique) { this.name = name; this.unique = unique; } on(...columns) { return new ConvexIndexBuilder(this.name, columns, this.unique); } }; var ConvexIndexBuilder = class { static [entityKind] = "ConvexIndexBuilder"; [entityKind] = "ConvexIndexBuilder"; config; constructor(name, columns, unique) { this.config = { name, columns, unique, where: void 0 }; } /** * Partial index conditions are not supported in Convex. * This method is kept for Drizzle API parity. */ where(condition) { this.config.where = condition; return this; } }; var ConvexSearchIndexBuilderOn = class { static [entityKind] = "ConvexSearchIndexBuilderOn"; [entityKind] = "ConvexSearchIndexBuilderOn"; constructor(name) { this.name = name; } on(searchField) { return new ConvexSearchIndexBuilder(this.name, searchField); } }; var ConvexSearchIndexBuilder = class { static [entityKind] = "ConvexSearchIndexBuilder"; [entityKind] = "ConvexSearchIndexBuilder"; config; constructor(name, searchField) { this.config = { name, searchField, filterFields: [], staged: false }; } filter(...fields) { this.config.filterFields = fields; return this; } staged() { this.config.staged = true; return this; } }; var ConvexVectorIndexBuilderOn = class { static [entityKind] = "ConvexVectorIndexBuilderOn"; [entityKind] = "ConvexVectorIndexBuilderOn"; constructor(name) { this.name = name; } on(vectorField) { return new ConvexVectorIndexBuilder(this.name, vectorField); } }; var ConvexAggregateIndexBuilderOn = class { static [entityKind] = "ConvexAggregateIndexBuilderOn"; [entityKind] = "ConvexAggregateIndexBuilderOn"; constructor(name) { this.name = name; } on(...columns) { return new ConvexAggregateIndexBuilder(this.name, columns); } all() { return new ConvexAggregateIndexBuilder(this.name, []); } }; var ConvexAggregateIndexBuilder = class { static [entityKind] = "ConvexAggregateIndexBuilder"; [entityKind] = "ConvexAggregateIndexBuilder"; config; constructor(name, columns) { this.config = { name, columns, countFields: [], sumFields: [], avgFields: [], minFields: [], maxFields: [] }; } count(...fields) { this.config.countFields = [...this.config.countFields, ...fields]; return this; } sum(...fields) { this.config.sumFields = [...this.config.sumFields, ...fields]; return this; } avg(...fields) { this.config.avgFields = [...this.config.avgFields, ...fields]; return this; } min(...fields) { this.config.minFields = [...this.config.minFields, ...fields]; return this; } max(...fields) { this.config.maxFields = [...this.config.maxFields, ...fields]; return this; } }; var ConvexRankIndexBuilderOn = class { static [entityKind] = "ConvexRankIndexBuilderOn"; [entityKind] = "ConvexRankIndexBuilderOn"; constructor(name) { this.name = name; } partitionBy(...columns) { return new ConvexRankIndexBuilder(this.name, columns, []); } all() { return new ConvexRankIndexBuilder(this.name, [], []); } }; var ConvexRankIndexBuilder = class { static [entityKind] = "ConvexRankIndexBuilder"; [entityKind] = "ConvexRankIndexBuilder"; config; constructor(name, partitionColumns, orderColumns) { this.config = { name, partitionColumns, orderColumns, sumField: void 0 }; } orderBy(...columns) { this.config.orderColumns = columns.map((entry) => { if (entry && typeof entry === "object" && "column" in entry && "direction" in entry) { const builder = entry.column?.builder; if (!builder) throw new Error("rankIndex orderBy() expected a column builder."); return { column: builder, direction: entry.direction }; } return { column: entry, direction: "asc" }; }); return this; } sum(field) { this.config.sumField = field; return this; } }; var ConvexVectorIndexBuilder = class { static [entityKind] = "ConvexVectorIndexBuilder"; [entityKind] = "ConvexVectorIndexBuilder"; config; constructor(name, vectorField) { this.config = { name, vectorField, dimensions: void 0, filterFields: [], staged: false }; } dimensions(dimensions) { if (!Number.isInteger(dimensions)) throw new Error(`Vector index '${this.config.name}' dimensions must be an integer, got ${dimensions}`); if (dimensions <= 0) throw new Error(`Vector index '${this.config.name}' dimensions must be positive, got ${dimensions}`); if (dimensions > 1e4) console.warn(`Vector index '${this.config.name}' has unusually large dimensions (${dimensions}). Common values: 768, 1536, 3072`); this.config.dimensions = dimensions; return this; } filter(...fields) { this.config.filterFields = fields; return this; } staged() { this.config.staged = true; return this; } }; function index(name) { return new ConvexIndexBuilderOn(name, false); } //#endregion //#region src/orm/rls/policies.ts var RlsPolicy = class { static [entityKind] = "RlsPolicy"; [entityKind] = "RlsPolicy"; as; for; to; using; withCheck; /** @internal */ _linkedTable; constructor(name, config) { this.name = name; if (config) { this.as = config.as; this.for = config.for; this.to = config.to; this.using = config.using; this.withCheck = config.withCheck; } } link(table) { this._linkedTable = table; return this; } }; function isRlsPolicy(value) { return !!value && typeof value === "object" && value[entityKind] === "RlsPolicy"; } //#endregion //#region src/orm/table.ts /** * Reserved Convex system table names that cannot be used */ const RESERVED_TABLES = new Set(["_storage", "_scheduled_functions"]); const RESERVED_COLUMN_NAMES = new Set([ "id", "_id", "_creationTime" ]); const DEFAULT_POLYMORPHIC_ALIAS = "details"; const CONVEX_TABLE_FIELD_LIMIT = 1024; /** * Valid table name pattern: starts with letter/underscore, contains only alphanumeric and underscore */ const TABLE_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/; /** * Validate table name against Convex constraints */ function validateTableName(name) { if (RESERVED_TABLES.has(name)) throw new Error(`Table name '${name}' is reserved. System tables cannot be redefined.`); if (!TABLE_NAME_REGEX.test(name)) throw new Error(`Invalid table name '${name}'. Must start with letter, contain only alphanumeric and underscore.`); } /** * Create a Convex object validator from column builders * * Extracts .convexValidator from each column and creates v.object({...}) * This is the core factory that bridges ORM columns to Convex validators. * * @param columns - Record of column name to column builder * @returns Convex object validator */ function createValidatorFromColumns(columns) { const validatorFields = Object.fromEntries(Object.entries(columns).map(([key, builder]) => [key, builder.convexValidator])); return v.object(validatorFields); } var ConvexDeletionBuilder = class { static [entityKind] = "ConvexDeletionBuilder"; [entityKind] = "ConvexDeletionBuilder"; constructor(config) { this.config = config; } }; function isConvexIndexBuilder(value) { return typeof value === "object" && value !== null && value[entityKind] === "ConvexIndexBuilder"; } function isConvexIndexBuilderOn(value) { return typeof value === "object" && value !== null && value[entityKind] === "ConvexIndexBuilderOn"; } function isConvexAggregateIndexBuilder(value) { return typeof value === "object" && value !== null && value[entityKind] === "ConvexAggregateIndexBuilder"; } function isConvexAggregateIndexBuilderOn(value) { return typeof value === "object" && value !== null && value[entityKind] === "ConvexAggregateIndexBuilderOn"; } function isConvexRankIndexBuilderOn(value) { return typeof value === "object" && value !== null && value[entityKind] === "ConvexRankIndexBuilderOn"; } function isConvexRankIndexBuilder(value) { return typeof value === "object" && value !== null && value[entityKind] === "ConvexRankIndexBuilder"; } function isConvexUniqueConstraintBuilderOn(value) { return typeof value === "object" && value !== null && value[entityKind] === "ConvexUniqueConstraintBuilderOn"; } function isConvexForeignKeyBuilder(value) { return typeof value === "object" && value !== null && value[entityKind] === "ConvexForeignKeyBuilder"; } function isConvexCheckBuilder(value) { return typeof value === "object" && value !== null && value[entityKind] === "ConvexCheckBuilder"; } function isConvexSearchIndexBuilderOn(value) { return typeof value === "object" && value !== null && value[entityKind] === "ConvexSearchIndexBuilderOn"; } function isConvexUniqueConstraintBuilder(value) { return typeof value === "object" && value !== null && value[entityKind] === "ConvexUniqueConstraintBuilder"; } function isConvexSearchIndexBuilder(value) { return typeof value === "object" && value !== null && value[entityKind] === "ConvexSearchIndexBuilder"; } function isConvexVectorIndexBuilderOn(value) { return typeof value === "object" && value !== null && value[entityKind] === "ConvexVectorIndexBuilderOn"; } function isConvexVectorIndexBuilder(value) { return typeof value === "object" && value !== null && value[entityKind] === "ConvexVectorIndexBuilder"; } function isConvexDeletionBuilder(value) { return typeof value === "object" && value !== null && value[entityKind] === "ConvexDeletionBuilder"; } function isConvexLifecycleBuilder(value) { return typeof value === "object" && value !== null && value[entityKind] === "ConvexLifecycleBuilder"; } function getColumnName(column) { const config = column.config; if (!config?.name) throw new Error("Invalid index column: expected a convexTable column builder."); return config.name; } function getColumnType(column) { return column.config?.columnType; } function getColumnDimensions(column) { return column.config?.dimensions; } function getColumnTableName(column) { const config = column.config; return config?.tableName ?? config?.referenceTable; } function getColumnTable(column) { return column.config?.table; } function getUniqueIndexName(tableName, fields, explicitName) { if (explicitName) return explicitName; return `${tableName}_${fields.join("_")}_unique`; } function assertColumnInTable(column, expectedTable, context) { const tableName = getColumnTableName(column); if (tableName && tableName !== expectedTable) throw new Error(`${context} references column from '${tableName}', but belongs to '${expectedTable}'.`); return getColumnName(column); } function assertNoReservedCreatedAtIndexFields(fields, context) { if (fields.includes("createdAt")) throw new Error(`${context} cannot use 'createdAt'. 'createdAt' is reserved and maps to internal '_creationTime'.`); } function assertSearchFieldType(column, indexName) { const columnType = getColumnType(column) ?? "unknown"; if (columnType !== "ConvexText") throw new Error(`Search index '${indexName}' only supports text() columns. Field '${getColumnName(column)}' is type '${columnType}'.`); } function assertVectorFieldType(column, indexName) { const columnType = getColumnType(column) ?? "unknown"; if (columnType !== "ConvexVector") throw new Error(`Vector index '${indexName}' requires a vector() column. Field '${getColumnName(column)}' is type '${columnType}'.`); } function assertAggregateSumFieldType(column, indexName) { const columnType = getColumnType(column) ?? "unknown"; if (!["ConvexNumber", "ConvexTimestamp"].includes(columnType)) throw new Error(`aggregateIndex '${indexName}' sum() supports integer()/timestamp() columns only. Field '${getColumnName(column)}' is type '${columnType}'.`); } function assertAggregateAvgFieldType(column, indexName) { const columnType = getColumnType(column) ?? "unknown"; if (!["ConvexNumber", "ConvexTimestamp"].includes(columnType)) throw new Error(`aggregateIndex '${indexName}' avg() supports integer()/timestamp() columns only. Field '${getColumnName(column)}' is type '${columnType}'.`); } function assertAggregateComparableFieldType(column, indexName, method) { const columnType = getColumnType(column) ?? "unknown"; if (![ "ConvexNumber", "ConvexTimestamp", "ConvexDate", "ConvexText", "ConvexBoolean", "ConvexId" ].includes(columnType)) throw new Error(`aggregateIndex '${indexName}' ${method}() does not support column type '${columnType}' on '${getColumnName(column)}'.`); } function assertRankOrderFieldType(column, indexName) { const columnType = getColumnType(column) ?? "unknown"; if (![ "ConvexNumber", "ConvexTimestamp", "ConvexDate" ].includes(columnType)) throw new Error(`rankIndex '${indexName}' orderBy() supports integer()/timestamp()/date() columns only. Field '${getColumnName(column)}' is type '${columnType}'.`); } const dedupeFieldNames = (fields) => [...new Set(fields)]; const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value); const isColumnBuilder = (value) => isRecord$1(value) && typeof value.build === "function"; const getDiscriminatorConfig = (value) => { if (!isColumnBuilder(value)) return; const discriminator = value.config?.discriminator; if (!discriminator) return; return discriminator; }; const getPolymorphicFieldSignature = (column) => { const validator = column.convexValidator ?? column.build(); return JSON.stringify({ columnType: column.config?.columnType, validator: validator?.json }); }; function resolveTableColumns(tableName, columns) { const resolvedColumns = {}; const pendingPolymorphic = []; for (const [columnName, rawBuilder] of Object.entries(columns)) { if (!isColumnBuilder(rawBuilder)) throw new Error(`Column '${columnName}' on '${tableName}' must be a column builder.`); resolvedColumns[columnName] = rawBuilder; const discriminatorConfig = getDiscriminatorConfig(rawBuilder); if (!discriminatorConfig) continue; if (!isRecord$1(discriminatorConfig.variants) || Object.keys(discriminatorConfig.variants).length === 0) throw new Error(`discriminator('${tableName}.${columnName}') requires at least one variant.`); const alias = discriminatorConfig.as === void 0 ? DEFAULT_POLYMORPHIC_ALIAS : discriminatorConfig.as; if (typeof alias !== "string" || alias.length === 0) throw new Error(`discriminator('${tableName}.${columnName}').as must be a non-empty string.`); pendingPolymorphic.push({ discriminator: columnName, alias, variants: discriminatorConfig.variants }); } if (pendingPolymorphic.length > 1) throw new Error(`Only one discriminator(...) column is currently supported on '${tableName}'.`); const polymorphicConfigs = []; for (const pending of pendingPolymorphic) { if (pending.alias in resolvedColumns) throw new Error(`discriminator('${tableName}.${pending.discriminator}') alias '${pending.alias}' collides with an existing column.`); const generatedFieldMap = /* @__PURE__ */ new Map(); const variantRuntime = {}; for (const [variantKey, rawVariantColumns] of Object.entries(pending.variants)) { if (!isRecord$1(rawVariantColumns)) throw new Error(`discriminator('${tableName}.${pending.discriminator}') variant '${variantKey}' must be an object.`); const fieldNames = []; const requiredFieldNames = []; for (const [fieldName, rawFieldBuilder] of Object.entries(rawVariantColumns)) { if (!isColumnBuilder(rawFieldBuilder)) throw new Error(`discriminator('${tableName}.${pending.discriminator}').variants.${variantKey}.${fieldName} must be a column builder.`); if (fieldName in resolvedColumns) throw new Error(`discriminator('${tableName}.${pending.discriminator}').variants.${variantKey}.${fieldName} collides with an existing table column.`); const fieldBuilder = rawFieldBuilder; const fieldConfig = fieldBuilder.config; const isRequiredForVariant = fieldConfig?.notNull === true && fieldConfig.hasDefault !== true && typeof fieldConfig.defaultFn !== "function"; const signature = getPolymorphicFieldSignature(fieldBuilder); const existing = generatedFieldMap.get(fieldName); if (existing && existing.signature !== signature) throw new Error(`discriminator('${tableName}.${pending.discriminator}') field '${fieldName}' has conflicting builder signatures across variants.`); if (!existing) { if (fieldConfig) fieldConfig.notNull = false; generatedFieldMap.set(fieldName, { builder: fieldBuilder, signature }); } fieldNames.push(fieldName); if (isRequiredForVariant) requiredFieldNames.push(fieldName); } variantRuntime[variantKey] = { fieldNames, requiredFieldNames }; } for (const [fieldName, { builder }] of generatedFieldMap.entries()) resolvedColumns[fieldName] = builder; polymorphicConfigs.push({ discriminator: pending.discriminator, alias: pending.alias, generatedFieldNames: Object.freeze([...generatedFieldMap.keys()]), variants: Object.freeze(variantRuntime) }); } if (Object.keys(resolvedColumns).length > CONVEX_TABLE_FIELD_LIMIT) throw new Error(`Table '${tableName}' exceeds Convex field count limit (${CONVEX_TABLE_FIELD_LIMIT}) after discriminator expansion.`); return { columns: resolvedColumns, polymorphicConfigs }; } function applyExtraConfig(table, config) { if (!config) return; const entries = Array.isArray(config) ? config : Object.values(config); for (const entry of entries) { if (isConvexIndexBuilderOn(entry)) throw new Error(`Invalid index definition on '${table.tableName}'. Did you forget to call .on(...)?`); if (isConvexUniqueConstraintBuilderOn(entry)) throw new Error(`Invalid unique constraint definition on '${table.tableName}'. Did you forget to call .on(...)?`); if (isConvexAggregateIndexBuilderOn(entry)) throw new Error(`Invalid aggregate index definition on '${table.tableName}'. Did you forget to call .on(...) or .all()?`); if (isConvexRankIndexBuilderOn(entry)) throw new Error(`Invalid rank index definition on '${table.tableName}'. Did you forget to call .partitionBy(...) or .all()?`); if (isConvexSearchIndexBuilderOn(entry)) throw new Error(`Invalid search index definition on '${table.tableName}'. Did you forget to call .on(...)?`); if (isConvexVectorIndexBuilderOn(entry)) throw new Error(`Invalid vector index definition on '${table.tableName}'. Did you forget to call .on(...)?`); if (isRlsPolicy(entry)) { const target = entry._linkedTable ?? table; if (typeof target.addRlsPolicy === "function") target.addRlsPolicy(entry); else { const policies = target[RlsPolicies] ?? []; policies.push(entry); target[RlsPolicies] = policies; target[EnableRLS] = true; } continue; } if (isConvexDeletionBuilder(entry)) { if (table[TableDeleteConfig]) throw new Error(`Only one deletion(...) config can be defined for '${table.tableName}'.`); table[TableDeleteConfig] = { mode: entry.config.mode, delayMs: entry.config.delayMs }; continue; } if (isConvexLifecycleBuilder(entry)) throw new Error(`Lifecycle hooks are no longer supported inside convexTable('${table.tableName}', ..., extraConfig). Export schema triggers with defineTriggers(relations, { ... }) from schema.ts.`); if (isConvexIndexBuilder(entry)) { const { name, columns, unique, where } = entry.config; if (where) throw new Error(`Convex does not support partial indexes. Remove .where(...) from index '${name}'.`); if (unique) {} const fields = columns.map((column) => assertColumnInTable(column, table.tableName, `Index '${name}'`)); assertNoReservedCreatedAtIndexFields(fields, `Index '${name}'`); table.addIndex(name, fields); if (unique) table.addUniqueIndex(name, fields, false); continue; } if (isConvexAggregateIndexBuilder(entry)) { const { name, columns, countFields, sumFields, avgFields, minFields, maxFields } = entry.config; const fields = columns.map((column) => assertColumnInTable(column, table.tableName, `Aggregate index '${name}'`)); assertNoReservedCreatedAtIndexFields(fields, `Aggregate index '${name}'`); const resolvedCountFields = dedupeFieldNames(countFields.map((column) => assertColumnInTable(column, table.tableName, `Aggregate index '${name}' count`))); assertNoReservedCreatedAtIndexFields(resolvedCountFields, `Aggregate index '${name}' count`); const resolvedSumFields = dedupeFieldNames(sumFields.map((column) => { const field = assertColumnInTable(column, table.tableName, `Aggregate index '${name}' sum`); assertAggregateSumFieldType(column, name); return field; })); assertNoReservedCreatedAtIndexFields(resolvedSumFields, `Aggregate index '${name}' sum`); const resolvedAvgFields = dedupeFieldNames(avgFields.map((column) => { const field = assertColumnInTable(column, table.tableName, `Aggregate index '${name}' avg`); assertAggregateAvgFieldType(column, name); return field; })); assertNoReservedCreatedAtIndexFields(resolvedAvgFields, `Aggregate index '${name}' avg`); const resolvedMinFields = dedupeFieldNames(minFields.map((column) => { const field = assertColumnInTable(column, table.tableName, `Aggregate index '${name}' min`); assertAggregateComparableFieldType(column, name, "min"); return field; })); assertNoReservedCreatedAtIndexFields(resolvedMinFields, `Aggregate index '${name}' min`); const resolvedMaxFields = dedupeFieldNames(maxFields.map((column) => { const field = assertColumnInTable(column, table.tableName, `Aggregate index '${name}' max`); assertAggregateComparableFieldType(column, name, "max"); return field; })); assertNoReservedCreatedAtIndexFields(resolvedMaxFields, `Aggregate index '${name}' max`); table.addAggregateIndex(name, { fields, countFields: resolvedCountFields, sumFields: resolvedSumFields, avgFields: resolvedAvgFields, minFields: resolvedMinFields, maxFields: resolvedMaxFields }); continue; } if (isConvexRankIndexBuilder(entry)) { const { name, partitionColumns, orderColumns, sumField } = entry.config; if (!orderColumns.length) throw new Error(`rankIndex '${name}' on '${table.tableName}' must declare at least one orderBy(...) column.`); const resolvedPartitionFields = dedupeFieldNames(partitionColumns.map((column) => assertColumnInTable(column, table.tableName, `rankIndex '${name}'`))); assertNoReservedCreatedAtIndexFields(resolvedPartitionFields, `rankIndex '${name}' partitionBy`); const resolvedOrderFields = orderColumns.map((entry) => { const field = assertColumnInTable(entry.column, table.tableName, `rankIndex '${name}' orderBy`); assertNoReservedCreatedAtIndexFields([field], `rankIndex '${name}'`); assertRankOrderFieldType(entry.column, name); return { field, direction: entry.direction }; }); const resolvedSumField = sumField ? (() => { const field = assertColumnInTable(sumField, table.tableName, `rankIndex '${name}' sum`); assertNoReservedCreatedAtIndexFields([field], `rankIndex '${name}'`); assertAggregateSumFieldType(sumField, name); return field; })() : void 0; table.addRankIndex(name, { partitionFields: resolvedPartitionFields, orderFields: resolvedOrderFields, sumField: resolvedSumField }); continue; } if (isConvexUniqueConstraintBuilder(entry)) { const { name, columns, nullsNotDistinct } = entry.config; const fields = columns.map((column) => assertColumnInTable(column, table.tableName, "Unique constraint")); assertNoReservedCreatedAtIndexFields(fields, "Unique constraint"); const indexName = getUniqueIndexName(table.tableName, fields, name); table.addIndex(indexName, fields); table.addUniqueIndex(indexName, fields, nullsNotDistinct); continue; } if (isConvexForeignKeyBuilder(entry)) { const { name, columns, foreignColumns, onDelete, onUpdate } = entry.config; if (columns.length === 0 || foreignColumns.length === 0) throw new Error(`Foreign key on '${table.tableName}' requires at least one column.`); if (columns.length !== foreignColumns.length) throw new Error(`Foreign key on '${table.tableName}' must specify matching columns and foreignColumns.`); const localFields = columns.map((column) => assertColumnInTable(column, table.tableName, "Foreign key")); const foreignTableName = getColumnTableName(foreignColumns[0]); if (!foreignTableName) throw new Error(`Foreign key on '${table.tableName}' references a column without a table.`); const foreignTable = getColumnTable(foreignColumns[0]); const foreignFields = foreignColumns.map((column) => { const tableName = getColumnTableName(column); if (tableName && tableName !== foreignTableName) throw new Error(`Foreign key on '${table.tableName}' mixes foreign columns from '${foreignTableName}' and '${tableName}'.`); return getColumnName(column); }); table.addForeignKey({ name, columns: localFields, foreignTableName, foreignTable, foreignColumns: foreignFields, onDelete, onUpdate }); continue; } if (isConvexCheckBuilder(entry)) { const { name, expression } = entry.config; table.addCheck(name, expression); continue; } if (isConvexSearchIndexBuilder(entry)) { const { name, searchField, filterFields, staged } = entry.config; const searchFieldName = assertColumnInTable(searchField, table.tableName, `Search index '${name}'`); assertNoReservedCreatedAtIndexFields([searchFieldName], `Search index '${name}'`); assertSearchFieldType(searchField, name); const filterFieldNames = filterFields.map((field) => assertColumnInTable(field, table.tableName, `Search index '${name}'`)); assertNoReservedCreatedAtIndexFields(filterFieldNames, `Search index '${name}'`); table.addSearchIndex(name, { searchField: searchFieldName, filterFields: filterFieldNames, staged }); continue; } if (isConvexVectorIndexBuilder(entry)) { const { name, vectorField, dimensions, filterFields, staged } = entry.config; if (dimensions === void 0) throw new Error(`Vector index '${name}' is missing dimensions. Call .dimensions(n) before using.`); const vectorFieldName = assertColumnInTable(vectorField, table.tableName, `Vector index '${name}'`); assertNoReservedCreatedAtIndexFields([vectorFieldName], `Vector index '${name}'`); assertVectorFieldType(vectorField, name); const columnDimensions = getColumnDimensions(vectorField); if (columnDimensions !== void 0 && columnDimensions !== dimensions) throw new Error(`Vector index '${name}' dimensions (${dimensions}) do not match vector column '${vectorFieldName}' dimensions (${columnDimensions}).`); const filterFieldNames = filterFields.map((field) => assertColumnInTable(field, table.tableName, `Vector index '${name}'`)); assertNoReservedCreatedAtIndexFields(filterFieldNames, `Vector index '${name}'`); table.addVectorIndex(name, { vectorField: vectorFieldName, dimensions, filterFields: filterFieldNames, staged }); continue; } throw new Error(`Unsupported extra config value in convexTable('${table.tableName}').`); } } /** * ConvexTable implementation class * Provides all properties required by Convex's TableDefinition * * Following convex-ents pattern: * - Private fields for indexes (matches TableDefinition structure) * - Duck typing (defineSchema only checks object shape) * - Direct validator storage (no re-wrapping) */ var ConvexTableImpl = class { /** * Required by TableDefinition * Public validator property containing v.object({...}) with all column validators */ validator; /** * TableDefinition private fields * These satisfy structural typing requirements for defineSchema() */ indexes = []; uniqueIndexes = []; aggregateIndexes = []; rankIndexes = []; foreignKeys = []; deferredForeignKeys = []; deferredForeignKeysResolved = false; stagedDbIndexes = []; searchIndexes = []; stagedSearchIndexes = []; vectorIndexes = []; stagedVectorIndexes = []; checks = []; /** * Symbol-based metadata storage */ [TableName]; [Columns]; [Brand] = "ConvexTable"; [EnableRLS] = false; [RlsPolicies] = []; [TableDeleteConfig]; [TablePolymorphic]; /** * Public tableName for convenience */ tableName; constructor(name, columns, polymorphicConfigs) { validateTableName(name); for (const columnName of Object.keys(columns)) if (RESERVED_COLUMN_NAMES.has(columnName)) throw new Error(`Column name '${columnName}' is reserved. System fields are managed by Convex ORM.`); this[TableName] = name; const namedColumns = Object.fromEntries(Object.entries(columns).map(([columnName, builder]) => { builder.config.name = columnName; builder.config.tableName = name; builder.config.table = this; return [columnName, builder]; })); this[Columns] = namedColumns; this.tableName = name; if (polymorphicConfigs && polymorphicConfigs.length > 0) this[TablePolymorphic] = polymorphicConfigs; this.validator = createValidatorFromColumns(namedColumns); for (const [columnName, builder] of Object.entries(namedColumns)) { const config = builder.config; if (config?.isUnique) { const indexName = getUniqueIndexName(name, [columnName], config.uniqueName); const nullsNotDistinct = config.uniqueNulls === "not distinct"; this.addIndex(indexName, [columnName]); this.addUniqueIndex(indexName, [columnName], nullsNotDistinct); } if (config?.referenceTable && (!config.foreignKeyConfigs || config.foreignKeyConfigs.length === 0)) this.addForeignKey({ name: void 0, columns: [columnName], foreignTableName: config.referenceTable, foreignColumns: ["_id"] }); if (config?.foreignKeyConfigs?.length) for (const foreignConfig of config.foreignKeyConfigs) this.deferredForeignKeys.push({ localColumnName: columnName, ref: foreignConfig.ref, config: foreignConfig.config }); } } getPolymorphicConfigs() { return this[TablePolymorphic]; } /** * Internal: add index to table from builder extraConfig * */ addIndex(name, fields) { this.indexes.push({ indexDescriptor: name, fields }); } /** * Internal: add unique index metadata for runtime enforcement */ addUniqueIndex(name, fields, nullsNotDistinct) { this.uniqueIndexes.push({ name, fields, nullsNotDistinct }); } addAggregateIndex(name, config) { if (this.aggregateIndexes.some((index) => index.name === name) || this.rankIndexes.some((index) => index.name === name)) throw new Error(`Duplicate aggregate index '${name}' on '${this.tableName}'.`); this.aggregateIndexes.push({ name, fields: config.fields, countFields: config.countFields, sumFields: config.sumFields, avgFields: config.avgFields, minFields: config.minFields, maxFields: config.maxFields }); } addRankIndex(name, config) { if (this.rankIndexes.some((index) => index.name === name) || this.aggregateIndexes.some((index) => inde