kitcn
Version:
kitcn - React Query integration and CLI tools for Convex
1,143 lines (1,132 loc) • 460 kB
JavaScript
import { A as vectorIndex, B as ConvexColumnBuilder, C as RlsPolicy, D as rankIndex, E as index, F as arrayOf, I as custom, L as json, M as createSystemFields, N as integer, O as searchIndex, P as id, R as objectOf, S as TablePolymorphic, T as aggregateIndex, V as entityKind, _ as OrmSchemaRelations, a as deletion, b as TableDeleteConfig, c as Columns, d as OrmSchemaDefinition, f as OrmSchemaExtensionRelations, g as OrmSchemaOptions, h as OrmSchemaExtensions, i as convexTable, j as text, k as uniqueIndex, l as EnableRLS, m as OrmSchemaExtensionTriggers, o as discriminator, p as OrmSchemaExtensionTables, s as Brand, t as DirectAggregate, u as OrmContext, v as OrmSchemaTriggers, w as rlsPolicy, x as TableName, y as RlsPolicies, z as unionOf } from "../runtime-i6t-HoZn.js";
import { a as pretendRequired, i as pretend, n as deprecated } from "../validators-C7LelqTN.js";
import { A as ne, C as inArray, D as like, E as isNull, F as notLike, I as or, L as startsWith, M as notBetween, N as notIlike, O as lt, P as notInArray, S as ilike, T as isNotNull, _ as endsWith, a as mergedStream, b as gt, c as isUnsetToken, d as arrayContained, f as arrayContains, g as contains, h as column, i as getIndexFields, j as not, k as lte, l as unsetToken, m as between, n as EmptyStream, o as stream, p as arrayOverlaps, r as QueryStream, s as streamIndexRange, t as getByIdWithOrmQueryFallback, u as and, v as eq, w as isFieldReference, x as gte, y as fieldRef } from "../query-context-ydn9kb6P.js";
import { v } from "convex/values";
import { defineSchema as defineSchema$1, internalActionGeneric, internalMutationGeneric } from "convex/server";
//#region src/orm/builders/bigint.ts
/**
* BigInt column builder class
* Compiles to v.int64() or v.optional(v.int64())
*/
var ConvexBigIntBuilder = class extends ConvexColumnBuilder {
static [entityKind] = "ConvexBigIntBuilder";
constructor(name) {
super(name, "bigint", "ConvexBigInt");
}
/**
* Expose Convex validator for schema integration
*/
get convexValidator() {
if (this.config.notNull) return v.int64();
return v.optional(v.union(v.null(), v.int64()));
}
/**
* Compile to Convex validator
* .notNull() → v.int64()
* nullable → v.optional(v.int64())
*/
build() {
return this.convexValidator;
}
};
function bigint(name) {
return new ConvexBigIntBuilder(name ?? "");
}
//#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(name) {
return new ConvexBooleanBuilder(name ?? "");
}
//#endregion
//#region src/orm/builders/bytes.ts
var ConvexBytesBuilder = class extends ConvexColumnBuilder {
static [entityKind] = "ConvexBytesBuilder";
constructor(name) {
super(name, "bytes", "ConvexBytes");
}
get convexValidator() {
if (this.config.notNull) return v.bytes();
return v.optional(v.union(v.null(), v.bytes()));
}
build() {
return this.convexValidator;
}
};
function bytes(name) {
return new ConvexBytesBuilder(name ?? "");
}
//#endregion
//#region src/orm/builders/date.ts
const toDateOnlyIsoString = (value) => value.toISOString().slice(0, 10);
var ConvexDateBuilder = class extends ConvexColumnBuilder {
static [entityKind] = "ConvexDateBuilder";
constructor(name, mode) {
super(name, "string", "ConvexDate");
this.config.mode = mode;
}
get convexValidator() {
if (this.config.notNull) return v.string();
return v.optional(v.union(v.null(), v.string()));
}
defaultNow() {
if (this.config.mode === "date") return this.$defaultFn(() => /* @__PURE__ */ new Date());
return this.$defaultFn(() => toDateOnlyIsoString(/* @__PURE__ */ new Date()));
}
build() {
return this.convexValidator;
}
};
const normalizeDateFactoryArgs = (nameOrConfig, maybeConfig) => {
return {
name: typeof nameOrConfig === "string" ? nameOrConfig : "",
mode: (typeof nameOrConfig === "string" ? maybeConfig : nameOrConfig)?.mode ?? "string"
};
};
function date(nameOrConfig, maybeConfig) {
const { name, mode } = normalizeDateFactoryArgs(nameOrConfig, maybeConfig);
return new ConvexDateBuilder(name, mode);
}
//#endregion
//#region src/orm/builders/text-enum.ts
var ConvexTextEnumBuilder = class extends ConvexColumnBuilder {
static [entityKind] = "ConvexTextEnumBuilder";
constructor(name, values) {
super(name, "string", "ConvexText");
this.config.values = [...values];
}
_enumValidator() {
const literals = this.config.values.map((value) => v.literal(value));
if (literals.length === 1) return literals[0];
return v.union(...literals);
}
get convexValidator() {
const base = this._enumValidator();
if (this.config.notNull) return base;
return v.optional(v.union(v.null(), base));
}
build() {
return this.convexValidator;
}
};
function textEnum(values) {
return new ConvexTextEnumBuilder("", values);
}
//#endregion
//#region src/orm/builders/timestamp.ts
var ConvexTimestampBuilder = class extends ConvexColumnBuilder {
static [entityKind] = "ConvexTimestampBuilder";
constructor(name, mode) {
super(name, "number", "ConvexTimestamp");
this.config.mode = mode;
}
get convexValidator() {
if (this.config.notNull && this.config.name === "createdAt" && typeof this.config.defaultFn === "function") return v.optional(v.number());
if (this.config.notNull) return v.number();
return v.optional(v.union(v.null(), v.number()));
}
defaultNow() {
if (this.config.mode === "string") return this.$defaultFn(() => (/* @__PURE__ */ new Date()).toISOString());
return this.$defaultFn(() => /* @__PURE__ */ new Date());
}
build() {
return this.convexValidator;
}
};
const normalizeTimestampFactoryArgs = (nameOrConfig, maybeConfig) => {
return {
name: typeof nameOrConfig === "string" ? nameOrConfig : "",
mode: (typeof nameOrConfig === "string" ? maybeConfig : nameOrConfig)?.mode ?? "date"
};
};
function timestamp(nameOrConfig, maybeConfig) {
const { name, mode } = normalizeTimestampFactoryArgs(nameOrConfig, maybeConfig);
return new ConvexTimestampBuilder(name, mode);
}
//#endregion
//#region src/orm/builders/vector.ts
const MAX_VECTOR_DIMENSIONS = 1e4;
function validateVectorDimensions(dimensions, columnName) {
if (!Number.isInteger(dimensions)) throw new Error(`Vector column '${columnName}' dimensions must be an integer, got ${dimensions}`);
if (dimensions <= 0) throw new Error(`Vector column '${columnName}' dimensions must be positive, got ${dimensions}`);
if (dimensions > MAX_VECTOR_DIMENSIONS) console.warn(`Vector column '${columnName}' has unusually large dimensions (${dimensions}). Common values: 768, 1536, 3072`);
}
/**
* Vector column builder class
* Compiles to v.array(v.float64()) or v.optional(v.array(v.float64()))
*/
var ConvexVectorBuilder = class extends ConvexColumnBuilder {
static [entityKind] = "ConvexVectorBuilder";
constructor(name, dimensions) {
super(name, "vector", "ConvexVector");
validateVectorDimensions(dimensions, name || "vector");
this.config.dimensions = dimensions;
}
get dimensions() {
return this.config.dimensions;
}
/**
* Expose Convex validator for schema integration
*/
get convexValidator() {
const validator = v.array(v.float64());
if (this.config.notNull) return validator;
return v.optional(v.union(v.null(), validator));
}
/**
* Compile to Convex validator
* .notNull() → v.array(v.float64())
* nullable → v.optional(v.array(v.float64()))
*/
build() {
return this.convexValidator;
}
};
function vector(a, b) {
if (typeof a === "string") {
if (b === void 0) throw new Error("vector(name, dimensions) requires a dimensions number as the second argument");
return new ConvexVectorBuilder(a, b);
}
return new ConvexVectorBuilder("", a);
}
//#endregion
//#region src/orm/constraints.ts
var ConvexUniqueConstraintBuilderOn = class {
static [entityKind] = "ConvexUniqueConstraintBuilderOn";
[entityKind] = "ConvexUniqueConstraintBuilderOn";
constructor(name) {
this.name = name;
}
on(...columns) {
return new ConvexUniqueConstraintBuilder(this.name, columns);
}
};
var ConvexUniqueConstraintBuilder = class {
static [entityKind] = "ConvexUniqueConstraintBuilder";
[entityKind] = "ConvexUniqueConstraintBuilder";
config;
constructor(name, columns) {
this.config = {
name,
columns,
nullsNotDistinct: false
};
}
nullsNotDistinct() {
this.config.nullsNotDistinct = true;
return this;
}
};
var ConvexForeignKeyBuilder = class {
static [entityKind] = "ConvexForeignKeyBuilder";
[entityKind] = "ConvexForeignKeyBuilder";
config;
constructor(config) {
this.config = {
...config,
onDelete: config.onDelete,
onUpdate: config.onUpdate
};
}
onUpdate(action) {
this.config.onUpdate = action;
return this;
}
onDelete(action) {
this.config.onDelete = action;
return this;
}
};
function unique(name) {
return new ConvexUniqueConstraintBuilderOn(name);
}
function foreignKey(config) {
return new ConvexForeignKeyBuilder(config);
}
var ConvexCheckBuilder = class {
static [entityKind] = "ConvexCheckBuilder";
[entityKind] = "ConvexCheckBuilder";
config;
constructor(name, expression) {
this.config = {
name,
expression
};
}
};
function check(name, expression) {
return new ConvexCheckBuilder(name, expression);
}
//#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 : [];
}
function getSearchIndexes(table) {
const indexes = table.getSearchIndexes?.();
return Array.isArray(indexes) ? indexes : [];
}
function findSearchIndexByName(table, indexName) {
return getSearchIndexes(table).find((index) => index.name === indexName) ?? null;
}
function getVectorIndexes(table) {
const indexes = table.getVectorIndexes?.();
return Array.isArray(indexes) ? indexes : [];
}
function findVectorIndexByName(table, indexName) {
return getVectorIndexes(table).find((index) => index.name === indexName) ?? null;
}
function findIndexForColumns(indexes, columns) {
for (const index of indexes) {
if (index.fields.length < columns.length) continue;
let matches = true;
for (let i = 0; i < columns.length; i++) if (index.fields[i] !== columns[i]) {
matches = false;
break;
}
if (matches) return index.name;
}
return null;
}
function findRelationIndex(table, columns, relationName, targetTableName, strict = true, allowFullScan = false) {
const index = findIndexForColumns(getIndexes(table), columns);
if (!index && !allowFullScan) throw new Error(`Relation ${relationName} requires index on '${targetTableName}(${columns.join(", ")})'. Set allowFullScan: true to override.`);
if (!index && strict) console.warn(`Relation ${relationName} running without index (allowFullScan: true).`);
return index;
}
//#endregion
//#region src/orm/timestamp-mode.ts
const PUBLIC_CREATED_AT_FIELD = "createdAt";
const INTERNAL_CREATION_TIME_FIELD = "_creationTime";
const CREATED_AT_MIGRATION_MESSAGE = "`_creationTime` is no longer public. Use `createdAt` instead.";
const hasUserCreatedAtColumn = (table) => {
if (!table || typeof table !== "object") return false;
const columns = table[Columns];
if (!columns || typeof columns !== "object") return false;
return Object.hasOwn(columns, PUBLIC_CREATED_AT_FIELD);
};
const usesSystemCreatedAtAlias = (_table) => true;
//#endregion
//#region src/orm/mutation-utils.ts
const UNDEFINED_SENTINEL_KEY = "__kitcnUndefined";
const INTERNAL_ID_FIELD$2 = "_id";
const PUBLIC_ID_FIELD$2 = "id";
const DATE_COLUMN_TYPE = "ConvexDate";
const TIMESTAMP_COLUMN_TYPE = "ConvexTimestamp";
const isPlainObject$1 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
const temporalColumnDescriptorCache = /* @__PURE__ */ new WeakMap();
const DATE_ONLY_REGEX = /^\d{4}-\d{2}-\d{2}$/;
const toDateOnlyString = (value) => value.toISOString().slice(0, 10);
const toDateOnlyDate = (value) => DATE_ONLY_REGEX.test(value) ? /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`) : value;
const toTimestampMillis = (value) => {
if (value instanceof Date) return value.getTime();
if (typeof value === "string") {
const parsed = Date.parse(value);
if (!Number.isNaN(parsed)) return parsed;
}
return value;
};
const readTimestampValue = (value, mode) => {
if (mode === "date" && typeof value === "number") return new Date(value);
if (mode === "string" && typeof value === "number") return new Date(value).toISOString();
return value;
};
const getTemporalDescriptorFromColumn = (name, column) => {
const config = column?.config;
const columnType = config?.columnType;
if (columnType !== DATE_COLUMN_TYPE && columnType !== TIMESTAMP_COLUMN_TYPE) return;
if (columnType === DATE_COLUMN_TYPE) return {
name,
columnType,
mode: config?.mode === "date" ? "date" : "string"
};
return {
name,
columnType,
mode: config?.mode === "string" ? "string" : "date"
};
};
const getTemporalColumnDescriptors = (table) => {
const cacheKey = table;
const cached = temporalColumnDescriptorCache.get(cacheKey);
if (cached) return cached;
const descriptors = /* @__PURE__ */ new Map();
for (const [name, column] of Object.entries(getTableColumns$2(table))) {
const descriptor = getTemporalDescriptorFromColumn(name, column);
if (!descriptor) continue;
descriptors.set(name, descriptor);
}
temporalColumnDescriptorCache.set(cacheKey, descriptors);
return descriptors;
};
const getTemporalColumnDescriptor = (table, columnName) => {
const temporalColumns = getTemporalColumnDescriptors(table);
const direct = temporalColumns.get(columnName);
if (direct) return direct;
const columns = getTableColumns$2(table);
for (const descriptor of temporalColumns.values()) if (columns[descriptor.name]?.config?.name === columnName) return descriptor;
};
const normalizeTemporalWriteValue = (descriptor, value) => {
if (descriptor.columnType === DATE_COLUMN_TYPE) {
if (value instanceof Date) return toDateOnlyString(value);
return value;
}
return toTimestampMillis(value);
};
const hydrateTemporalReadValue = (descriptor, value) => {
if (descriptor.columnType === DATE_COLUMN_TYPE) {
if (descriptor.mode === "date" && typeof value === "string") return toDateOnlyDate(value);
return value;
}
return readTimestampValue(value, descriptor.mode);
};
const normalizeTemporalComparableValue = (table, fieldName, value) => {
const descriptor = getTemporalColumnDescriptor(table, fieldName);
if (!descriptor) return value;
if (Array.isArray(value)) return value.map((entry) => normalizeTemporalWriteValue(descriptor, entry));
return normalizeTemporalWriteValue(descriptor, value);
};
const normalizePublicSystemFields = (value, options) => {
if (!isPlainObject$1(value)) return value;
const hasId = Object.hasOwn(value, INTERNAL_ID_FIELD$2);
const hasCreationTime = Object.hasOwn(value, INTERNAL_CREATION_TIME_FIELD);
if (!hasId && !hasCreationTime) return value;
const obj = value;
const { [INTERNAL_ID_FIELD$2]: internalId, ...rest } = obj;
const publicRow = { ...rest };
if (hasId) publicRow[PUBLIC_ID_FIELD$2] = internalId;
if (hasCreationTime) {
const raw = obj[INTERNAL_CREATION_TIME_FIELD];
if (options?.useSystemCreatedAtAlias && raw !== void 0) publicRow[PUBLIC_CREATED_AT_FIELD] = raw;
delete publicRow[INTERNAL_CREATION_TIME_FIELD];
}
delete publicRow[INTERNAL_ID_FIELD$2];
return publicRow;
};
const normalizeDateFieldsForWrite = (table, value) => {
const useSystemCreatedAt = usesSystemCreatedAtAlias(table);
const hasUserCreatedAt = hasUserCreatedAtColumn(table);
const temporalColumns = getTemporalColumnDescriptors(table);
const result = { ...value };
if (Object.hasOwn(result, INTERNAL_CREATION_TIME_FIELD)) throw new Error(CREATED_AT_MIGRATION_MESSAGE);
if (useSystemCreatedAt && !hasUserCreatedAt && Object.hasOwn(result, PUBLIC_CREATED_AT_FIELD)) delete result[PUBLIC_CREATED_AT_FIELD];
for (const [name, descriptor] of temporalColumns.entries()) {
if (!Object.hasOwn(result, name)) continue;
result[name] = normalizeTemporalWriteValue(descriptor, result[name]);
}
return result;
};
const hydrateDateFieldsForRead = (table, value) => {
const rawCreationTime = isPlainObject$1(value) && typeof value[INTERNAL_CREATION_TIME_FIELD] === "number" ? value[INTERNAL_CREATION_TIME_FIELD] : void 0;
const base = normalizePublicSystemFields(value, { useSystemCreatedAtAlias: usesSystemCreatedAtAlias(table) });
if (!isPlainObject$1(base)) return base;
const result = { ...base };
const temporalColumns = getTemporalColumnDescriptors(table);
for (const [name, descriptor] of temporalColumns.entries()) {
if (name === PUBLIC_CREATED_AT_FIELD && result[name] === void 0 && rawCreationTime !== void 0) {
result[name] = hydrateTemporalReadValue(descriptor, rawCreationTime);
continue;
}
if (!Object.hasOwn(result, name)) continue;
result[name] = hydrateTemporalReadValue(descriptor, result[name]);
}
return result;
};
const selectReturningRowWithHydration = (table, row, fields) => {
const useSystemCreatedAt = usesSystemCreatedAtAlias(table);
const temporalColumns = getTemporalColumnDescriptors(table);
const selected = {};
for (const [selectedKey, column] of Object.entries(fields)) {
const columnName = getSelectionColumnName(column);
let value = row[columnName];
if (!(columnName === INTERNAL_CREATION_TIME_FIELD && useSystemCreatedAt)) {
const descriptor = temporalColumns.get(columnName);
if (descriptor) value = hydrateTemporalReadValue(descriptor, value);
}
selected[selectedKey] = value;
}
return { ...selected };
};
const encodeUndefinedDeep = (value) => {
if (value === void 0) return { [UNDEFINED_SENTINEL_KEY]: true };
if (Array.isArray(value)) return value.map((item) => encodeUndefinedDeep(item));
if (isPlainObject$1(value)) {
const result = {};
for (const [key, nested] of Object.entries(value)) result[key] = encodeUndefinedDeep(nested);
return result;
}
return value;
};
const decodeUndefinedDeep = (value) => {
if (Array.isArray(value)) return value.map((item) => decodeUndefinedDeep(item));
if (isPlainObject$1(value)) {
if (Object.keys(value).length === 1 && value[UNDEFINED_SENTINEL_KEY] === true) return;
const result = {};
for (const [key, nested] of Object.entries(value)) result[key] = decodeUndefinedDeep(nested);
return result;
}
return value;
};
const isSerializedFieldReference = (value) => isPlainObject$1(value) && typeof value.fieldName === "string";
const createBinaryExpression = (operator, fieldName, value) => {
return {
type: "binary",
operator,
operands: [fieldRef(fieldName), value],
accept(visitor) {
return visitor.visitBinary(this);
}
};
};
const createLogicalExpression = (operator, operands) => ({
type: "logical",
operator,
operands,
accept(visitor) {
return visitor.visitLogical(this);
}
});
const createUnaryExpression = (operator, operand) => ({
type: "unary",
operator,
operands: [operand],
accept(visitor) {
return visitor.visitUnary(this);
}
});
const serializeFilterExpression = (expression) => {
if (!expression) return;
if (expression.type === "binary") {
const binary = expression;
const [field, value] = binary.operands;
if (!isFieldReference(field)) throw new Error("Binary expression must have FieldReference as first operand");
return {
type: "binary",
operator: binary.operator,
field: { fieldName: field.fieldName },
value: encodeUndefinedDeep(value)
};
}
if (expression.type === "logical") {
const logical = expression;
return {
type: "logical",
operator: logical.operator,
operands: logical.operands.map((operand) => serializeFilterExpression(operand))
};
}
const unary = expression;
const [operand] = unary.operands;
return {
type: "unary",
operator: unary.operator,
operand: isFieldReference(operand) ? { fieldName: operand.fieldName } : serializeFilterExpression(operand)
};
};
const deserializeFilterExpression = (expression) => {
if (!expression) return;
if (expression.type === "binary") {
const binary = expression;
return createBinaryExpression(binary.operator, binary.field.fieldName, decodeUndefinedDeep(binary.value));
}
if (expression.type === "logical") {
const logical = expression;
return createLogicalExpression(logical.operator, logical.operands.map((operand) => deserializeFilterExpression(operand)).filter((operand) => !!operand));
}
const unary = expression;
const operand = unary.operand;
if (isSerializedFieldReference(operand)) return createUnaryExpression(unary.operator, fieldRef(operand.fieldName));
const nested = deserializeFilterExpression(operand);
if (!nested) throw new Error("Serialized unary operand is missing.");
return createUnaryExpression(unary.operator, nested);
};
const DEFAULT_MUTATION_BATCH_SIZE = 400;
const DEFAULT_MUTATION_LEAF_BATCH_SIZE = 1600;
const DEFAULT_MUTATION_MAX_ROWS = 1e4;
const DEFAULT_MUTATION_MAX_BYTES_PER_BATCH = 2097152;
const DEFAULT_MUTATION_SCHEDULE_CALL_CAP = 800;
const DEFAULT_MUTATION_ASYNC_DELAY_MS = 0;
const DEFAULT_COUNT_BACKFILL_BATCH_SIZE = 1e3;
const DEFAULT_RELATION_FAN_OUT_MAX_KEYS$1 = 1e3;
const DEFAULT_AGGREGATE_CARTESIAN_MAX_KEYS$2 = 4096;
const DEFAULT_AGGREGATE_WORK_BUDGET$2 = 16384;
const MEASURED_BYTE_SAFETY_MULTIPLIER = 2;
const UTF8_LENGTH_THRESHOLD = 500;
const UTF8_ENCODER = new TextEncoder();
const getUtf8ByteLength = (value) => {
if (value.length > UTF8_LENGTH_THRESHOLD) return UTF8_ENCODER.encode(value).length;
let bytes = 0;
for (let i = 0; i < value.length; i++) {
const code = value.charCodeAt(i);
if (code < 128) bytes += 1;
else if (code < 2048) bytes += 2;
else if (code >= 55296 && code <= 56319) {
bytes += 4;
i += 1;
} else bytes += 3;
}
return bytes;
};
const resolveOrmRuntimeDefaults = (defaults, runtime = {}) => {
const inferredMode = runtime.scheduler && runtime.scheduledMutationBatch ? "async" : "sync";
return {
defaultLimit: defaults?.defaultLimit,
countBackfillBatchSize: defaults?.countBackfillBatchSize ?? DEFAULT_COUNT_BACKFILL_BATCH_SIZE,
relationFanOutMaxKeys: defaults?.relationFanOutMaxKeys ?? DEFAULT_RELATION_FAN_OUT_MAX_KEYS$1,
aggregateCartesianMaxKeys: defaults?.aggregateCartesianMaxKeys ?? DEFAULT_AGGREGATE_CARTESIAN_MAX_KEYS$2,
aggregateWorkBudget: defaults?.aggregateWorkBudget ?? DEFAULT_AGGREGATE_WORK_BUDGET$2,
mutationBatchSize: defaults?.mutationBatchSize ?? DEFAULT_MUTATION_BATCH_SIZE,
mutationLeafBatchSize: defaults?.mutationLeafBatchSize ?? DEFAULT_MUTATION_LEAF_BATCH_SIZE,
mutationMaxRows: defaults?.mutationMaxRows ?? DEFAULT_MUTATION_MAX_ROWS,
mutationMaxBytesPerBatch: defaults?.mutationMaxBytesPerBatch ?? DEFAULT_MUTATION_MAX_BYTES_PER_BATCH,
mutationScheduleCallCap: defaults?.mutationScheduleCallCap ?? DEFAULT_MUTATION_SCHEDULE_CALL_CAP,
mutationExecutionMode: defaults?.mutationExecutionMode ?? inferredMode,
mutationAsyncDelayMs: defaults?.mutationAsyncDelayMs ?? DEFAULT_MUTATION_ASYNC_DELAY_MS
};
};
const estimateMeasuredMutationRowBytes = (row) => {
return getUtf8ByteLength(JSON.stringify(row)) * MEASURED_BYTE_SAFETY_MULTIPLIER;
};
const takeRowsWithinByteBudget = (rows, maxBytesPerBatch) => {
if (!Number.isInteger(maxBytesPerBatch) || maxBytesPerBatch < 1) throw new Error("mutationMaxBytesPerBatch must be a positive integer.");
if (rows.length === 0) return {
rows,
hitLimit: false
};
let bytes = 0;
const selected = [];
for (const row of rows) {
const rowBytes = estimateMeasuredMutationRowBytes(row);
if (selected.length > 0 && bytes + rowBytes > maxBytesPerBatch) return {
rows: selected,
hitLimit: true
};
selected.push(row);
bytes += rowBytes;
}
return {
rows: selected,
hitLimit: false
};
};
const getMutationCollectionLimits = (context) => {
const defaults = context?.resolvedDefaults ?? context?.defaults;
const batchSize = defaults?.mutationBatchSize ?? DEFAULT_MUTATION_BATCH_SIZE;
const leafBatchSize = defaults?.mutationLeafBatchSize ?? DEFAULT_MUTATION_LEAF_BATCH_SIZE;
const maxRows = defaults?.mutationMaxRows ?? DEFAULT_MUTATION_MAX_ROWS;
const maxBytesPerBatch = defaults?.mutationMaxBytesPerBatch ?? DEFAULT_MUTATION_MAX_BYTES_PER_BATCH;
const scheduleCallCap = defaults?.mutationScheduleCallCap ?? DEFAULT_MUTATION_SCHEDULE_CALL_CAP;
if (!Number.isInteger(batchSize) || batchSize < 1) throw new Error("mutationBatchSize must be a positive integer.");
if (!Number.isInteger(leafBatchSize) || leafBatchSize < 1) throw new Error("mutationLeafBatchSize must be a positive integer.");
if (!Number.isInteger(maxRows) || maxRows < 1) throw new Error("mutationMaxRows must be a positive integer.");
if (!Number.isInteger(maxBytesPerBatch) || maxBytesPerBatch < 1) throw new Error("mutationMaxBytesPerBatch must be a positive integer.");
if (!Number.isInteger(scheduleCallCap) || scheduleCallCap < 1) throw new Error("mutationScheduleCallCap must be a positive integer.");
return {
batchSize,
leafBatchSize,
maxRows,
maxBytesPerBatch,
scheduleCallCap
};
};
const consumeScheduleCall = (state) => {
if (!state) return;
if (state.remainingCalls < 1) throw new Error(`Async cascade scheduling exceeded mutationScheduleCallCap (${state.callCap}). Increase defineSchema(..., { defaults: { mutationScheduleCallCap } }) or reduce fan-out per mutation.`);
state.remainingCalls -= 1;
};
const getMutationExecutionMode = (context, override) => {
const requestedMode = override ?? context?.resolvedDefaults?.mutationExecutionMode ?? context?.defaults?.mutationExecutionMode;
if (requestedMode === "sync") return "sync";
if (requestedMode === "async") {
if (override === "async") return "async";
if (context?.scheduler && context?.scheduledMutationBatch) return "async";
return "sync";
}
if (context?.scheduler && context?.scheduledMutationBatch) return "async";
return "sync";
};
const getMutationAsyncDelayMs = (context, override) => override ?? context?.resolvedDefaults?.mutationAsyncDelayMs ?? context?.defaults?.mutationAsyncDelayMs ?? DEFAULT_MUTATION_ASYNC_DELAY_MS;
const collectMutationRowsBounded = async (buildQuery, options) => {
const rows = await buildQuery().take(options.maxRows + 1);
if (rows.length > options.maxRows) throw new Error(`${options.operation} matched more than ${options.maxRows} rows on "${options.tableName}". Narrow the filter or increase defineSchema(..., { defaults: { mutationMaxRows } }).`);
return rows;
};
function getTableName(table) {
const name = table.tableName ?? table[TableName] ?? table?._?.name;
if (!name) throw new Error("Table is missing a name");
return name;
}
function getTableDeleteConfig(table) {
return table[TableDeleteConfig];
}
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 buildForeignKeyGraph(schema) {
const tableByName = /* @__PURE__ */ new Map();
for (const tableConfig of Object.values(schema)) if (tableConfig?.name && tableConfig.table) tableByName.set(tableConfig.name, tableConfig.table);
const incomingByTable = /* @__PURE__ */ new Map();
for (const tableConfig of Object.values(schema)) {
const sourceTable = tableConfig.table;
const sourceTableName = tableConfig.name;
const foreignKeys = getForeignKeys(sourceTable);
for (const foreignKey of foreignKeys) {
const targetTableName = foreignKey.foreignTableName;
if (!tableByName.get(targetTableName)) throw new Error(`Foreign key from '${sourceTableName}' references missing table '${targetTableName}'.`);
const entry = {
sourceTable,
sourceTableName,
sourceColumns: foreignKey.columns,
targetTableName,
targetColumns: foreignKey.foreignColumns,
onDelete: foreignKey.onDelete,
onUpdate: foreignKey.onUpdate
};
const list = incomingByTable.get(targetTableName) ?? [];
list.push(entry);
incomingByTable.set(targetTableName, list);
}
}
return { incomingByTable };
}
function getOrmContext(db) {
return db[OrmContext];
}
function getForeignKeys(table) {
const fromMethod = table.getForeignKeys?.();
if (Array.isArray(fromMethod)) return fromMethod;
const fromField = table.foreignKeys;
return Array.isArray(fromField) ? fromField : [];
}
function getColumnName$1(column) {
const name = column.config?.name ?? column?._?.name;
if (!name) throw new Error("Column builder is missing a column name");
return name;
}
function getTableColumns$2(table) {
return table[Columns] ?? {};
}
function getTablePolymorphicConfigs(table) {
const fromMethod = table.getPolymorphicConfigs?.();
if (Array.isArray(fromMethod)) return fromMethod;
const fromSymbol = table[TablePolymorphic];
return Array.isArray(fromSymbol) ? fromSymbol : [];
}
function enforcePolymorphicWrite(table, candidate, options) {
const configs = getTablePolymorphicConfigs(table);
if (configs.length === 0) return;
for (const config of configs) {
const changedFields = options?.changedFields;
const discriminatorChanged = !changedFields || changedFields.has(config.discriminator);
const generatedFieldChanged = !changedFields || config.generatedFieldNames.some((fieldName) => changedFields.has(fieldName));
if (!discriminatorChanged && !generatedFieldChanged) continue;
const discriminatorValue = candidate[config.discriminator];
if (typeof discriminatorValue !== "string" || !Object.hasOwn(config.variants, discriminatorValue)) throw new Error(`Invalid discriminator '${config.discriminator}' on '${getTableName(table)}'. Expected one of: ${Object.keys(config.variants).join(", ")}.`);
const activeVariant = config.variants[discriminatorValue];
if (!activeVariant) throw new Error(`Invalid discriminator '${config.discriminator}' value '${discriminatorValue}' on '${getTableName(table)}'.`);
for (const requiredFieldName of activeVariant.requiredFieldNames) {
const value = candidate[requiredFieldName];
if (value === null || value === void 0) throw new Error(`discriminator branch '${discriminatorValue}' requires '${requiredFieldName}' on '${getTableName(table)}'.`);
}
const activeFieldSet = new Set(activeVariant.fieldNames);
for (const generatedFieldName of config.generatedFieldNames) {
if (activeFieldSet.has(generatedFieldName)) continue;
const value = candidate[generatedFieldName];
if (value !== null && value !== void 0) throw new Error(`discriminator branch '${discriminatorValue}' cannot set '${generatedFieldName}' on '${getTableName(table)}' because it belongs to another variant.`);
}
}
}
function getColumnConfig(table, columnName) {
const builder = getTableColumns$2(table)[columnName];
if (!builder) return null;
return builder.config ?? null;
}
function applyDefaults(table, value) {
const columns = table[Columns];
if (!columns) return value;
const result = { ...value };
const polymorphicConfigs = getTablePolymorphicConfigs(table);
const activeGeneratedFields = /* @__PURE__ */ new Set();
const generatedFields = /* @__PURE__ */ new Set();
for (const config of polymorphicConfigs) {
for (const fieldName of config.generatedFieldNames) generatedFields.add(fieldName);
const discriminatorValue = result[config.discriminator];
if (typeof discriminatorValue !== "string") continue;
const activeVariant = config.variants[discriminatorValue];
if (!activeVariant) continue;
for (const fieldName of activeVariant.fieldNames) activeGeneratedFields.add(fieldName);
}
for (const [columnName, builder] of Object.entries(columns)) {
if (result[columnName] !== void 0) continue;
if (generatedFields.has(columnName) && !activeGeneratedFields.has(columnName)) continue;
const config = builder.config;
if (!config) continue;
if (typeof config.defaultFn === "function") {
result[columnName] = config.defaultFn();
continue;
}
if (config.hasDefault) {
result[columnName] = config.default;
continue;
}
if (typeof config.onUpdateFn === "function") result[columnName] = config.onUpdateFn();
}
return result;
}
async function enforceUniqueIndexes(db, table, candidate, options) {
const uniqueIndexes = getUniqueIndexes(table);
if (uniqueIndexes.length === 0) return;
const tableName = getTableName(table);
const changedFields = options?.changedFields;
for (const index of uniqueIndexes) {
if (changedFields && !index.fields.some((field) => changedFields.has(field))) continue;
const entries = index.fields.map((field) => [field, candidate[field]]);
if (entries.some(([, value]) => value === void 0 || value === null) && !index.nullsNotDistinct) continue;
const existing = await db.query(tableName).withIndex(index.name, (q) => {
let builder = q.eq(entries[0][0], entries[0][1]);
for (let i = 1; i < entries.length; i++) builder = builder.eq(entries[i][0], entries[i][1]);
return builder;
}).unique();
if (existing !== null && (options?.currentId === void 0 || existing._id !== options.currentId)) throw new Error(`Unique index '${index.name}' violation on '${tableName}'.`);
}
}
async function enforceForeignKeys(db, table, candidate, options) {
const foreignKeys = getForeignKeys(table);
if (foreignKeys.length === 0) return;
const tableName = getTableName(table);
const changedFields = options?.changedFields;
for (const foreignKey of foreignKeys) {
if (changedFields && !foreignKey.columns.some((field) => changedFields.has(field))) continue;
const entries = foreignKey.columns.map((field) => [field, candidate[field]]);
if (entries.some(([, value]) => value === void 0 || value === null)) continue;
if (foreignKey.foreignColumns.length === 1 && foreignKey.foreignColumns[0] === "_id") {
const foreignId = entries[0]?.[1];
if (!await db.get(foreignId)) throw new Error(`Foreign key violation on '${tableName}': missing document in '${foreignKey.foreignTableName}'.`);
continue;
}
if (!foreignKey.foreignTable) throw new Error(`Foreign key on '${tableName}' requires indexed foreign columns on '${foreignKey.foreignTableName}'.`);
const indexName = findIndexForColumns(getIndexes(foreignKey.foreignTable), foreignKey.foreignColumns);
if (!indexName) throw new Error(`Foreign key on '${tableName}' requires index on '${foreignKey.foreignTableName}(${foreignKey.foreignColumns.join(", ")})'.`);
if (!await db.query(foreignKey.foreignTableName).withIndex(indexName, (q) => {
let builder = q.eq(foreignKey.foreignColumns[0], entries[0][1]);
for (let i = 1; i < entries.length; i++) builder = builder.eq(foreignKey.foreignColumns[i], entries[i][1]);
return builder;
}).first()) throw new Error(`Foreign key violation on '${tableName}': missing document in '${foreignKey.foreignTableName}'.`);
}
}
function getIndexForForeignKey(foreignKey) {
return findIndexForColumns(getIndexes(foreignKey.sourceTable), foreignKey.sourceColumns);
}
function foreignKeyIndexError(foreignKey) {
return /* @__PURE__ */ new Error(`Foreign key on '${foreignKey.sourceTableName}' requires index on '${foreignKey.sourceTableName}(${foreignKey.sourceColumns.join(", ")})' for cascading actions.`);
}
function buildIndexPredicate(q, columns, values) {
let builder = q.eq(columns[0], values[0]);
for (let i = 1; i < columns.length; i++) builder = builder.eq(columns[i], values[i]);
return builder;
}
function buildFilterPredicate(q, columns, values) {
let expr = q.eq(q.field(columns[0]), values[0]);
for (let i = 1; i < columns.length; i++) expr = q.and(expr, q.eq(q.field(columns[i]), values[i]));
return expr;
}
function ensureNullableColumns(table, columns, context) {
for (const columnName of columns) {
const config = getColumnConfig(table, columnName);
if (!config) throw new Error(`${context}: missing column '${columnName}' in table '${getTableName(table)}'.`);
if (config.notNull) throw new Error(`${context}: column '${columnName}' is not nullable in '${getTableName(table)}'.`);
}
}
function ensureDefaultColumns(table, columns, context) {
const defaults = {};
for (const columnName of columns) {
const config = getColumnConfig(table, columnName);
if (!config) throw new Error(`${context}: missing column '${columnName}' in table '${getTableName(table)}'.`);
if (!config.hasDefault) throw new Error(`${context}: column '${columnName}' has no default in '${getTableName(table)}'.`);
defaults[columnName] = config.default;
}
return defaults;
}
function ensureNonNullValues(table, values, context) {
for (const [columnName, value] of Object.entries(values)) if (getColumnConfig(table, columnName)?.notNull && (value === null || value === void 0)) throw new Error(`${context}: column '${columnName}' cannot be null in '${getTableName(table)}'.`);
}
async function collectReferencingRows(db, foreignKey, targetValues, indexName, options) {
return collectMutationRowsBounded(() => db.query(foreignKey.sourceTableName).withIndex(indexName, (q) => buildIndexPredicate(q, foreignKey.sourceColumns, targetValues)), {
operation: options.operation,
tableName: foreignKey.sourceTableName,
batchSize: options.batchSize,
maxRows: options.maxRows
});
}
async function collectAsyncCascadeRowsBounded(buildQuery, batchSize, maxBytesPerBatch) {
const rows = await buildQuery().take(batchSize + 1);
const hasMoreRows = rows.length > batchSize;
const bounded = takeRowsWithinByteBudget(hasMoreRows ? rows.slice(0, batchSize) : rows, maxBytesPerBatch);
return {
rows: bounded.rows,
needsContinuation: hasMoreRows || bounded.hitLimit
};
}
async function hasReferencingRow(db, foreignKey, targetValues, indexName) {
const query = db.query(foreignKey.sourceTableName);
return (indexName ? await query.withIndex(indexName, (q) => buildIndexPredicate(q, foreignKey.sourceColumns, targetValues)).first() : await query.filter((q) => buildFilterPredicate(q, foreignKey.sourceColumns, targetValues)).first()) !== null;
}
async function softDeleteRow(db, table, row) {
const tableName = getTableName(table);
if (!("deletionTime" in getTableColumns$2(table))) throw new Error(`Soft delete requires 'deletionTime' field on '${tableName}'.`);
const deletionTime = Date.now();
await db.patch(tableName, row._id, { deletionTime });
return deletionTime;
}
async function hardDeleteRow(db, _tableName, row) {
await db.delete(row._id);
}
async function applyIncomingForeignKeyActionsOnDelete(db, table, row, options) {
const tableName = getTableName(table);
const incoming = options.graph.incomingByTable.get(tableName) ?? [];
if (incoming.length === 0) return;
for (const foreignKey of incoming) {
const action = foreignKey.onDelete ?? "no action";
const targetValues = foreignKey.targetColumns.map((column) => row[column]);
if (targetValues.some((value) => value === void 0 || value === null)) continue;
const indexName = getIndexForForeignKey(foreignKey);
if (action === "restrict" || action === "no action") {
if (!indexName && !options.allowFullScan) throw foreignKeyIndexError(foreignKey);
if (!indexName && options.strict) console.warn(`Foreign key check running without index (allowFullScan: true) on '${foreignKey.sourceTableName}'.`);
if (await hasReferencingRow(db, foreignKey, targetValues, indexName)) throw new Error(`Foreign key restrict violation on '${tableName}' from '${foreignKey.sourceTableName}'.`);
continue;
}
if (!indexName) {
if (!options.allowFullScan) throw foreignKeyIndexError(foreignKey);
if (options.strict) console.warn(`Foreign key cascade check running without index (allowFullScan: true) on '${foreignKey.sourceTableName}'.`);
if (await hasReferencingRow(db, foreignKey, targetValues, null)) throw foreignKeyIndexError(foreignKey);
continue;
}
let referencingRows;
if (options.executionMode === "async") {
const asyncBatchSize = action === "cascade" ? options.batchSize : options.leafBatchSize;
const { rows, needsContinuation } = await collectAsyncCascadeRowsBounded(() => db.query(foreignKey.sourceTableName).withIndex(indexName, (q) => buildIndexPredicate(q, foreignKey.sourceColumns, targetValues)), asyncBatchSize, options.maxBytesPerBatch);
referencingRows = rows;
if (needsContinuation) {
if (!options.scheduler || !options.scheduledMutationBatch) throw new Error("Async mutation execution requires orm.db(ctx) configured with scheduling (ormFunctions.scheduledMutationBatch).");
consumeScheduleCall(options.scheduleState);
await options.scheduler.runAfter(options.delayMs ?? 0, options.scheduledMutationBatch, {
workType: "cascade-delete",
mode: "async",
operation: "delete",
table: foreignKey.sourceTableName,
foreignIndexName: indexName,
foreignSourceColumns: foreignKey.sourceColumns,
targetValues: encodeUndefinedDeep(targetValues),
foreignAction: action,
deleteMode: options.deleteMode,
cascadeMode: options.cascadeMode,
cursor: null,
batchSize: asyncBatchSize,
maxBytesPerBatch: options.maxBytesPerBatch,
delayMs: options.delayMs ?? 0
});
}
} else referencingRows = await collectReferencingRows(db, foreignKey, targetValues, indexName, {
operation: "delete",
batchSize: options.batchSize,
maxRows: options.maxRows
});
if (referencingRows.length === 0) continue;
if (action === "set null") {
ensureNullableColumns(foreignKey.sourceTable, foreignKey.sourceColumns, `Foreign key set null on '${foreignKey.sourceTableName}'`);
for (const referencingRow of referencingRows) {
const patch = {};
for (const columnName of foreignKey.sourceColumns) patch[columnName] = null;
await db.patch(foreignKey.sourceTableName, referencingRow._id, patch);
}
continue;
}
if (action === "set default") {
const defaults = ensureDefaultColumns(foreignKey.sourceTable, foreignKey.sourceColumns, `Foreign key set default on '${foreignKey.sourceTableName}'`);
for (const referencingRow of referencingRows) await db.patch(foreignKey.sourceTableName, referencingRow._id, defaults);
continue;
}
if (action === "cascade") for (const referencingRow of referencingRows) {
const key = `${foreignKey.sourceTableName}:${referencingRow._id}`;
if (options.visited.has(key)) continue;
options.visited.add(key);
await applyIncomingForeignKeyActionsOnDelete(db, foreignKey.sourceTable, referencingRow, options);
if (options.cascadeMode === "soft") await softDeleteRow(db, foreignKey.sourceTable, referencingRow);
else await hardDeleteRow(db, foreignKey.sourceTableName, referencingRow);
}
}
}
async function applyIncomingForeignKeyActionsOnUpdate(db, table, oldRow, newRow, options) {
const tableName = getTableName(table);
const incoming = options.graph.incomingByTable.get(tableName) ?? [];
if (incoming.length === 0) return;
for (const foreignKey of incoming) {
const action = foreignKey.onUpdate ?? "no action";
const oldValues = foreignKey.targetColumns.map((column) => oldRow[column]);
const newValues = foreignKey.targetColumns.map((column) => newRow[column]);
if (!oldValues.some((value, index) => !Object.is(value, newValues[index]))) continue;
if (oldValues.some((value) => value === void 0 || value === null)) continue;
const indexName = getIndexForForeignKey(foreignKey);
if (action === "restrict" || action === "no action") {
if (!indexName && !options.allowFullScan) throw foreignKeyIndexError(foreignKey);
if (!indexName && options.strict) console.warn(`Foreign key check running without index (allowFullScan: true) on '${foreignKey.sourceTableName}'.`);
if (await hasReferencingRow(db, foreignKey, oldValues, indexName)) throw new Error(`Foreign key restrict violation on '${tableName}' from '${foreignKey.sourceTableName}'.`);
continue;
}
if (!indexName) {
if (!options.allowFullScan) throw foreignKeyIndexError(foreignKey);
if (options.strict) console.warn(`Foreign key cascade check running without index (allowFullScan: true) on '${foreignKey.sourceTableName}'.`);
if (await hasReferencingRow(db, foreignKey, oldValues, null)) throw foreignKeyIndexError(foreignKey);
continue;
}
let referencingRows;
if (options.executionMode === "async") {
const asyncBatchSize = options.leafBatchSize;
const { rows, needsContinuation } = await collectAsyncCascadeRowsBounded(() => db.query(foreignKey.sourceTableName).withIndex(indexName, (q) => buildIndexPredicate(q, foreignKey.sourceColumns, oldValues)), asyncBatchSize, options.maxBytesPerBatch);
referencingRows = rows;
if (needsContinuation) {
if (!options.scheduler || !options.scheduledMutationBatch) throw new Error("Async mutation execution requires orm.db(ctx) configured with scheduling (ormFunctions.scheduledMutationBatch).");
consumeScheduleCall(options.scheduleState);
await options.scheduler.runAfter(options.delayMs ?? 0, options.scheduledMutationBatch, {
workType: "cascade-update",
mode: "async",
operation: "update",
table: foreignKey.sourceTableName,
foreignIndexName: indexName,
foreignSourceColumns: foreignKey.sourceColumns,
targetValues: encodeUndefinedDeep(oldValues),
newValues: encodeUndefinedDeep(newValues),
foreignAction: action,
cursor: null,
batchSize: asyncBatchSize,
maxBytesPerBatch: options.maxBytesPerBatch,
delayMs: options.delayMs ?? 0
});
}
} else referencingRows = await collectReferencingRows(db, foreignKey, oldValues, indexName, {
operation: "update",
batchSize: options.batchSize,
maxRows: options.maxRows
});
if (referencingRows.length === 0) continue;
if (action === "set null") {
ensureNullableColumns(foreignKey.sourceTable, foreignKey.sourceColumns, `Foreign key set null on '${foreignKey.sourceTableName}'`);
for (const referencingRow of referencingRows) {
const patch = {};
for (const columnName of foreignKey.sourceColumns) patch[columnName] = null;
await db.patch(foreignKey.sourceTableName, referencingRow._id, patch);
}
continue;
}
if (action === "set default") {
const defaults = ensureDefaultColumns(foreignKey.sourceTable, foreignKey.sourceColumns, `Foreign key set default on '${foreignKey.sourceTableName}'`);
for (const referencingRow of referencingRows) await db.patch(foreignKey.sourceTableName, referencingRow._id, defaults);
continue;
}
if (action === "cascade") {
const patchValues = {};
for (let i = 0; i < foreignKey.sourceColumns.length; i++) patchValues[foreignKey.sourceColumns[i]] = newValues[i];
ensureNonNullValues(foreignKey.sourceTable, patchValues, `Foreign key cascade update on '${foreignKey.sourceTableName}'`);
for (const referencingRow of referencingRows) await db.patch(foreignKey.sourceTableName, referencingRow._id, patchValues);
}
}
}
function getSelectionColumnName(value) {
if (value && typeof value === "object") {
if ("columnName" in value) return value.columnName;
if ("config" in value && value.config?.name) return value.config.name;
}
throw new Error("Returning selection must reference a column");
}
function splitReturningSelection(fields) {
const columnSelection = {};
let countSelection;
for (const [key, value] of Object.entries(fields)) {
if (key !== "_count") {
getSelectionColumnName(value);
columnSelection[key] = value;
continue;
}
if (value === void 0) continue;
if (!isPlainObject$1(value)) throw new Error("returning({ _count }) requires an object.");
const nextCountSelection = {};
for (const [relationName, relationSelection] of Object.entries(value)) {
if (relationSelection === void 0 || relationSelection === false) continue;
if (relationSelection === true) {
nextCountSelection[relationName] = true;
continue;
}
if (!isPlainObject$1(relationSelection)) throw new Error(`returning({ _count.${relationName} }) must be true or { where }`);
if ("select" in relationSelection) throw new Error(`returning({ _count.${relationName}.select }) is removed. Use returning({ _count: { ${relationName}: true } })`);
for (const [optionKey, optionValue] of Object.entries(relationSelection)) if (optionKey !== "where" && optionValue !== void 0) throw new Error(`returning({ _count.${relationName} }) does not support '${optionKey}'`);
if (typeof relationSelection.where === "function") throw new Error(`returning({ _count.${relationName}.where }) callback is unsupported in v1`);
nextCountSelection[relationName] = { where: relationSelection.where };
}
countSelection = nextCountSelection;
}
return {
columnSelection: Object.keys(columnSelection).length > 0 ? columnSelection : void 0,
countSelection
};
}
function matchLike(value, pattern, caseInsensitive) {
const targetValue = caseInsensitive ? value.toLowerCase() : value;
const targetPattern = caseInsensitive ? pattern.toLowerCase() : pattern;
if (targetPattern.startsWith("%") && targetPattern.endsWith("%")) {
const substring = targetPattern.slice(1, -1);
return targetValue.includes(substring);
}
if (targetPattern.startsWith("%")) {
const suffix = targetPattern.slice(1);
return targetValue.endsWith(suffix);
}
if (targetPattern.endsWith("%")) {
const prefix = targetPattern.slice(0, -1);
return targetValue.startsWith(prefix);
}
return targetValue === targetPattern;
}
function evaluat