kitcn
Version:
kitcn - React Query integration and CLI tools for Convex
1,420 lines (1,403 loc) • 636 kB
JavaScript
#!/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