cogsbox-shape
Version:
A TypeScript library for creating type-safe database schemas with Zod validation, SQL type definitions, and automatic client/server transformations. Unifies client, server, and database types through a single schema definition, with built-in support for r
1,191 lines • 57.3 kB
JavaScript
import { z } from "zod";
import { v4 as uuid } from "uuid";
export const isFunction = (fn) => typeof fn === "function";
export function currentTimeStamp() {
return {
default: "CURRENT_TIMESTAMP",
defaultValue: new Date(),
};
}
function createSqlBuilder(dialect, sqlConfig) {
const sqlZodType = (() => {
let baseType;
if (sqlConfig.pk) {
baseType = z.number();
}
else {
switch (sqlConfig.type) {
case "int":
baseType = z.number();
break;
case "real":
baseType = z.number();
break;
case "boolean":
baseType = z.number();
break;
case "date":
case "datetime":
case "timestamp":
baseType = z.date();
break;
case "enum":
baseType = z.enum(sqlConfig.values);
break;
default:
baseType = z.string();
break;
}
}
if (sqlConfig.nullable) {
baseType = baseType.nullable();
}
return baseType;
})();
const dialectConfig = { ...sqlConfig, dialect };
return createBuilder({
stage: "sql",
sqlConfig: dialectConfig,
sqlZod: sqlZodType,
initialValue: inferDefaultFromZod(sqlZodType, dialectConfig),
clientZod: sqlZodType,
validationZod: sqlZodType,
});
}
function isClientOptions(value) {
return (value !== undefined &&
typeof value === "object" &&
value !== null &&
!isFunction(value) &&
!("_def" in value) &&
!("parse" in value) &&
("value" in value || "schema" in value || "clientPk" in value));
}
export const s = {
client: (...args) => {
const first = args[0];
if (isClientOptions(first)) {
return createBuilder({
stage: "sql",
sqlConfig: null,
sqlZod: z.undefined(),
initialValue: undefined,
clientZod: z.undefined(),
validationZod: z.undefined(),
}).client(first);
}
const value = first;
const sample = isFunction(value) ? value({ uuid }) : value;
let inferredZodType;
if (typeof sample === "string") {
inferredZodType = z.string();
}
else if (typeof sample === "number") {
inferredZodType = z.number();
}
else if (typeof sample === "boolean") {
inferredZodType = z.boolean();
}
else if (sample instanceof Date) {
inferredZodType = z.date();
}
else if (sample === null) {
inferredZodType = z.null();
}
else {
inferredZodType = z.any();
}
return createBuilder({
stage: "client",
sqlConfig: null,
sqlZod: z.undefined(),
initialValue: value,
clientZod: inferredZodType,
validationZod: inferredZodType,
});
},
reference: (getter) => ({
__type: "reference",
getter: getter,
}),
hasMany: (config) => ({
__type: "placeholder-relation",
relationType: "hasMany",
defaultCount: config && typeof config === "object" && "count" in config
? config.count
: 0,
defaultConfig: config,
}),
hasOne: (config) => ({
__type: "placeholder-relation",
relationType: "hasOne",
defaultConfig: config,
}),
manyToMany: (config) => ({
__type: "placeholder-relation",
relationType: "manyToMany",
defaultCount: config?.defaultCount ?? 0,
}),
sqlite: (sqlConfig) => createSqlBuilder("sqlite", sqlConfig),
postgres: (sqlConfig) => createSqlBuilder("postgres", sqlConfig),
mysql: (sqlConfig) => createSqlBuilder("mysql", sqlConfig),
};
function createBuilder(config) {
const completedStages = config.completedStages || new Set([config.stage]);
const builderObject = {
config: {
sql: config.sqlConfig,
zodSqlSchema: config.sqlZod,
initialValue: config.initialValue ??
inferDefaultFromZod(config.clientZod, config.sqlConfig),
zodClientSchema: config.clientInputZod || config.clientZod,
zodClientCheckedSchema: config.clientZod,
zodValidationSchema: config.validationZod,
clientTransform: config.clientTransform,
validationTransform: config.validationTransform,
},
reference: (fieldGetter) => {
return createBuilder({
...config,
sqlConfig: {
...config.sqlConfig,
reference: fieldGetter,
},
});
},
client: (...args) => {
if (completedStages.has("client")) {
throw new Error("client() can only be called once in the chain");
}
if (completedStages.has("server")) {
throw new Error("client() must be called before server()");
}
const newCompletedStages = new Set(completedStages);
newCompletedStages.add("client");
let optionsOrSchema = args[0];
if (config.stage === "relation") {
const assert = typeof optionsOrSchema === "function" ||
optionsOrSchema instanceof z.ZodType
? optionsOrSchema
: optionsOrSchema?.schema;
return createBuilder({
...config,
stage: "client",
completedStages: newCompletedStages,
clientZod: assert,
clientInputZod: assert,
clientTransform: (baseSchema) => {
if (isFunction(assert)) {
return assert({ sql: baseSchema });
}
return assert;
},
});
}
let isDirectShortcut = false;
let isValueAndSchemaShortcut = false;
let options = {};
if (optionsOrSchema !== undefined &&
typeof optionsOrSchema === "function") {
if (args.length === 2 && isFunction(args[1])) {
isValueAndSchemaShortcut = true;
options = {
schema: optionsOrSchema,
value: args[1],
};
}
else {
options = { schema: optionsOrSchema };
isDirectShortcut = true;
}
}
else if (optionsOrSchema !== undefined &&
typeof optionsOrSchema === "object" &&
!("_def" in optionsOrSchema) &&
!("parse" in optionsOrSchema) &&
(optionsOrSchema.value !== undefined ||
optionsOrSchema.schema !== undefined ||
optionsOrSchema.clientPk !== undefined)) {
options = optionsOrSchema;
}
else if (optionsOrSchema !== undefined) {
options = { schema: optionsOrSchema };
isDirectShortcut = true;
}
const { value, schema: schemaOrModifier, clientPk } = options;
let actualValue = config.initialValue;
let finalSchema;
if (value !== undefined) {
actualValue = value;
}
let baseSchema;
if (schemaOrModifier &&
typeof schemaOrModifier === "object" &&
"_def" in schemaOrModifier) {
finalSchema = schemaOrModifier;
}
else {
if (value !== undefined) {
const sample = isFunction(value) ? value({ uuid }) : value;
if (typeof sample === "string")
baseSchema = z.string();
else if (typeof sample === "number")
baseSchema = z.number();
else if (typeof sample === "boolean")
baseSchema = z.boolean();
else if (sample instanceof Date)
baseSchema = z.date();
else if (sample === null)
baseSchema = z.null();
else
baseSchema = z.any();
}
else {
baseSchema = config.clientZod;
}
if (isFunction(schemaOrModifier)) {
if (isDirectShortcut) {
finalSchema = schemaOrModifier({ sql: config.sqlZod });
}
else {
finalSchema = schemaOrModifier(baseSchema);
}
}
else {
finalSchema = baseSchema;
}
}
if (value === undefined) {
const inferredClientDefault = inferDefaultFromZod(finalSchema);
if (inferredClientDefault !== undefined) {
actualValue = inferredClientDefault;
}
}
const newConfig = config.sqlConfig ? { ...config.sqlConfig } : null;
if (newConfig && clientPk !== undefined) {
newConfig.isClientPk = clientPk;
}
let clientAndServerSchema;
if (clientPk) {
clientAndServerSchema = z.union([config.sqlZod, finalSchema]);
}
else if (schemaOrModifier !== undefined) {
if (isDirectShortcut) {
clientAndServerSchema = finalSchema;
}
else {
clientAndServerSchema = finalSchema;
}
}
else {
if (config.sqlZod instanceof z.ZodUndefined) {
clientAndServerSchema = finalSchema;
}
else {
clientAndServerSchema = z.union([config.sqlZod, finalSchema]);
}
}
return createBuilder({
...config,
stage: "client",
sqlConfig: newConfig,
initialValue: actualValue,
clientZod: clientAndServerSchema,
clientInputZod: finalSchema,
validationZod: clientAndServerSchema,
completedStages: newCompletedStages,
});
},
clientCheck: (assert) => {
if (completedStages.has("server")) {
throw new Error("client() must be called before server()");
}
const clientSchema = isFunction(assert)
? assert({
sql: config.sqlZod,
client: config.clientInputZod || config.clientZod,
clientCheck: config.clientZod,
})
: assert;
const newCompletedStages = new Set(completedStages);
newCompletedStages.add("client");
const newConfig = {
...config,
stage: "client",
clientZod: clientSchema,
validationZod: clientSchema,
completedStages: newCompletedStages,
};
if (config.clientInputZod !== undefined) {
newConfig.clientInputZod = config.clientInputZod;
}
return createBuilder(newConfig);
},
server: (assert) => {
if (completedStages.has("server")) {
throw new Error("server() can only be called once in the chain");
}
const serverSchema = isFunction(assert)
? assert({
sql: config.sqlZod,
client: config.clientInputZod || config.clientZod,
clientCheck: config.clientZod,
})
: assert;
const newCompletedStages = new Set(completedStages);
newCompletedStages.add("server");
return createBuilder({
...config,
stage: "server",
validationZod: serverSchema,
completedStages: newCompletedStages,
});
},
transform: (transforms) => {
if (!completedStages.has("server") &&
!completedStages.has("client")) {
throw new Error("transform() requires at least client() or server() to be called first");
}
return {
config: {
...builderObject.config,
transforms: {
toClient: transforms.toClient,
toDb: transforms.toDb,
},
},
};
},
};
return builderObject;
}
export const SchemaWrapperBrand = Symbol("SchemaWrapper");
export function schema(schema) {
const enrichedSchema = {};
for (const key in schema) {
if (Object.prototype.hasOwnProperty.call(schema, key)) {
if (key === "_tableName") {
enrichedSchema[key] = schema[key];
}
else {
enrichedSchema[key] = {
...schema[key],
__meta: { _key: key, _fieldType: schema[key] },
__parentTableType: schema,
};
}
}
}
enrichedSchema[SchemaWrapperBrand] = true;
enrichedSchema.__primaryKeySQL = undefined;
enrichedSchema.__derives = undefined;
enrichedSchema.__refines = undefined;
enrichedSchema.primaryKeySQL = function (definer) {
const pkFieldsOnly = {};
for (const key in schema) {
const field = schema[key];
if (field &&
typeof field === "object" &&
field.config?.sql?.pk === true) {
pkFieldsOnly[key] = schema[key];
}
}
enrichedSchema.__primaryKeySQL = definer(pkFieldsOnly);
return enrichedSchema;
};
enrichedSchema.derive = function (derivers) {
enrichedSchema.__derives = derivers;
return enrichedSchema;
};
enrichedSchema.refine = function (fn) {
const r = (layers, check, deps) => {
const layerArr = Array.isArray(layers) ? layers : [layers];
return {
layers: layerArr,
deps: deps ? (Array.isArray(deps) ? deps : [deps]) : null,
check,
};
};
enrichedSchema.__refines = fn(r);
return enrichedSchema;
};
return enrichedSchema;
}
function inferDefaultFromZod(zodType, sqlConfig) {
if (sqlConfig &&
"default" in sqlConfig &&
sqlConfig.default === "CURRENT_TIMESTAMP") {
return undefined;
}
if (sqlConfig && typeof sqlConfig === "object" && "type" in sqlConfig) {
if ("default" in sqlConfig && sqlConfig.default !== undefined) {
return sqlConfig.default;
}
const sqlTypeConfig = sqlConfig;
if (sqlTypeConfig.nullable) {
return null;
}
}
if (zodType instanceof z.ZodDefault) {
const def = zodType._def;
const val = def.defaultValue;
if (val !== undefined) {
return typeof val === "function" ? val() : val;
}
}
if (zodType instanceof z.ZodNullable) {
return null;
}
if (zodType instanceof z.ZodOptional) {
return undefined;
}
if (zodType instanceof z.ZodString) {
return "";
}
if (zodType instanceof z.ZodNumber) {
return 0;
}
if (zodType instanceof z.ZodBoolean) {
return false;
}
if (zodType instanceof z.ZodDate) {
return new Date();
}
if (zodType instanceof z.ZodEnum) {
return zodType.options[0];
}
if (zodType instanceof z.ZodLiteral) {
return zodType.value;
}
if (sqlConfig && typeof sqlConfig === "object" && "type" in sqlConfig) {
const sqlTypeConfig = sqlConfig;
if (sqlTypeConfig.type && !sqlTypeConfig.nullable) {
switch (sqlTypeConfig.type) {
case "varchar":
case "text":
case "char":
case "longtext":
return "";
case "enum":
return sqlTypeConfig.default ?? sqlTypeConfig.values[0];
case "int":
return 0;
case "real":
return 0;
case "boolean":
return false;
case "date":
case "datetime":
case "timestamp":
return new Date();
}
}
if (sqlTypeConfig.nullable) {
return null;
}
}
return undefined;
}
function isReference(value) {
return value && typeof value === "object" && value.__type === "reference";
}
export function createSchema(schema, relations) {
const sqlFields = {};
const clientFields = {};
const clientCheckedFields = {};
const serverFields = {};
const defaultValues = {};
const defaultGenerators = {};
const fieldTransforms = {};
const clientToDbKeys = {};
const dbToClientKeys = {};
const sqlOnlyDbKeys = new Set();
const fullSchema = { ...schema, ...(relations || {}) };
let pkKeys = [];
let clientPkKeys = [];
const derives = schema.__derives;
const refineGroups = schema.__refines;
for (const key in fullSchema) {
const value = fullSchema[key];
if (key === "_tableName" ||
key.startsWith("__") ||
key === "derive" ||
key === String(SchemaWrapperBrand) ||
key === "primaryKeySQL" ||
typeof value === "function")
continue;
const definition = fullSchema[key];
if (isReference(definition)) {
const targetField = definition.getter();
if (targetField && targetField.config) {
const config = targetField.config;
const dbFieldName = config.sql?.field || key;
sqlFields[dbFieldName] = config.zodSqlSchema;
if (config.sql?.sqlOnly) {
sqlOnlyDbKeys.add(dbFieldName);
}
else {
clientToDbKeys[key] = dbFieldName;
dbToClientKeys[dbFieldName] = key;
clientFields[key] = config.zodClientSchema;
clientCheckedFields[key] = config.zodClientCheckedSchema;
serverFields[key] = config.zodValidationSchema;
const initialValueOrFn = config.initialValue;
defaultGenerators[key] = initialValueOrFn;
defaultValues[key] = isFunction(initialValueOrFn)
? initialValueOrFn({ uuid })
: initialValueOrFn;
if (config.transforms) {
fieldTransforms[key] = config.transforms;
}
}
}
continue;
}
if (definition && definition.config) {
const config = definition.config;
if (config.sql?.pk && !config.sql?.isForeignKey)
pkKeys.push(key);
if (config.sql?.isClientPk)
clientPkKeys.push(key);
const sqlConfig = config.sql;
if (sqlConfig &&
typeof sqlConfig === "object" &&
["hasMany", "hasOne", "belongsTo", "manyToMany"].includes(sqlConfig.type)) {
continue;
}
else if (sqlConfig?.type) {
const dbFieldName = sqlConfig.field || key;
sqlFields[dbFieldName] = config.zodSqlSchema;
if (sqlConfig.sqlOnly) {
sqlOnlyDbKeys.add(dbFieldName);
}
else {
clientToDbKeys[key] = dbFieldName;
dbToClientKeys[dbFieldName] = key;
clientFields[key] = config.zodClientSchema;
clientCheckedFields[key] = config.zodClientCheckedSchema;
serverFields[key] = config.zodValidationSchema;
if (config.transforms) {
fieldTransforms[key] = config.transforms;
}
const initialValueOrFn = config.initialValue;
defaultGenerators[key] = initialValueOrFn;
defaultValues[key] = isFunction(initialValueOrFn)
? initialValueOrFn({ uuid })
: initialValueOrFn;
}
}
else {
clientFields[key] = config.zodClientSchema;
clientCheckedFields[key] = config.zodClientCheckedSchema;
serverFields[key] = config.zodValidationSchema;
if (config.transforms) {
fieldTransforms[key] = config.transforms;
}
const initialValueOrFn = config.initialValue;
defaultGenerators[key] = initialValueOrFn;
defaultValues[key] = isFunction(initialValueOrFn)
? initialValueOrFn({ uuid })
: initialValueOrFn;
}
}
}
let isClientRecord = () => false;
if (clientPkKeys.length > 0) {
const checkers = [];
for (const key of clientPkKeys) {
const field = fullSchema[key];
const sqlConfig = field?.config?.sql;
const dbKey = sqlConfig?.field || key;
const isClientPkVal = sqlConfig?.isClientPk;
if (typeof isClientPkVal === "function") {
checkers.push({ clientKey: key, dbKey, check: isClientPkVal });
}
else {
const initialValueOrFn = field?.config?.initialValue;
let sampleValue = initialValueOrFn;
if (isFunction(initialValueOrFn)) {
try {
sampleValue = initialValueOrFn({ uuid });
}
catch (e) { }
}
if (sqlConfig?.type === "int" && typeof sampleValue === "string") {
checkers.push({
clientKey: key,
dbKey,
check: (val) => typeof val === "string",
});
}
}
}
if (checkers.length > 0) {
isClientRecord = (record) => {
if (!record || typeof record !== "object")
return false;
return checkers.some(({ clientKey, dbKey, check }) => {
const val = record[clientKey] !== undefined ? record[clientKey] : record[dbKey];
return check(val);
});
};
}
}
const generateDefaults = () => {
const freshDefaults = {};
for (const key in defaultGenerators) {
const generatorOrValue = defaultGenerators[key];
freshDefaults[key] = isFunction(generatorOrValue)
? generatorOrValue({ uuid })
: generatorOrValue;
}
// Only apply client derivations
if (derives?.forClient) {
for (const key in derives.forClient) {
freshDefaults[key] = derives.forClient[key]?.(freshDefaults);
}
}
return freshDefaults;
};
const toClient = (dbObject) => {
const clientObject = {};
for (const dbKey in dbObject) {
if (dbObject[dbKey] === undefined)
continue;
if (sqlOnlyDbKeys.has(dbKey))
continue;
const clientKey = dbToClientKeys[dbKey] || dbKey;
const transform = fieldTransforms[clientKey]?.toClient;
clientObject[clientKey] = transform
? transform(dbObject[dbKey])
: dbObject[dbKey];
}
// Only apply Client derives AFTER mapping standard fields
if (derives?.forClient) {
for (const key in derives.forClient) {
clientObject[key] = derives.forClient[key]?.(clientObject);
}
}
return clientObject;
};
const toDb = (clientObject) => {
const dbObject = {};
// 1. Map standard client fields to DB fields
for (const clientKey in clientObject) {
if (clientObject[clientKey] === undefined)
continue;
const dbKey = clientToDbKeys[clientKey];
if (!dbKey)
continue;
const transform = fieldTransforms[clientKey]?.toDb;
dbObject[dbKey] = transform
? transform(clientObject[clientKey])
: clientObject[clientKey];
}
// 2. Map Database ONLY derives directly to the dbObject
if (derives?.forDb) {
for (const schemaKey in derives.forDb) {
// Resolve custom DB column name if they used s.sqlite({ field: "custom_name" })
const sqlConfig = fullSchema[schemaKey]?.config?.sql;
const dbKey = sqlConfig?.field || schemaKey;
dbObject[dbKey] = derives.forDb[schemaKey]?.(clientObject);
}
}
return dbObject;
};
const finalSqlSchema = z.object(sqlFields);
const finalClientSchema = z.object(clientFields);
const finalClientCheckedSchema = z.object(clientCheckedFields);
const finalValidationSchema = z.object(serverFields);
const deriveDependencies = {};
const trackDeriveDependencies = (deriveGroup) => {
if (!deriveGroup)
return;
const trackingSeed = { ...defaultValues };
for (const key in deriveGroup) {
const accessed = new Set();
const trackingRow = new Proxy(trackingSeed, {
get(target, prop, receiver) {
if (typeof prop === "string" && prop !== key) {
accessed.add(prop);
}
return Reflect.get(target, prop, receiver);
},
});
try {
deriveGroup[key]?.(trackingRow);
}
catch (e) { }
deriveDependencies[key] = Array.from(new Set([...(deriveDependencies[key] ?? []), ...accessed]));
}
};
trackDeriveDependencies(derives?.forClient);
trackDeriveDependencies(derives?.forDb);
let refinedSqlSchema = finalSqlSchema;
let refinedClientSchema = finalClientSchema;
let refinedClientCheckedSchema = finalClientCheckedSchema;
let refinedValidationSchema = finalValidationSchema;
const fieldToGroup = {};
if (refineGroups) {
for (let i = 0; i < refineGroups.length; i++) {
const entry = refineGroups[i];
let { layers, deps, check } = entry;
const applyTo = layers.includes("all")
? ["sql", "client", "clientCheck", "server"]
: layers;
// Track deps from proxy if not provided explicitly
if (!deps) {
const accessed = new Set();
const trackingRow = new Proxy(defaultValues, {
get(target, prop, receiver) {
if (typeof prop === "string")
accessed.add(prop);
return Reflect.get(target, prop, receiver);
},
});
try {
check(trackingRow);
}
catch (e) { }
deps = Array.from(accessed);
entry.deps = deps;
}
for (const dep of deps) {
if (!fieldToGroup[dep])
fieldToGroup[dep] = [];
fieldToGroup[dep].push(i);
}
const refineFn = (data, ctx) => {
const result = check(data);
if (!result)
return;
const errors = Array.isArray(result) ? result : [result];
for (const err of errors) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: err.message,
path: err.path,
});
}
};
for (const layer of applyTo) {
switch (layer) {
case "sql":
refinedSqlSchema = refinedSqlSchema.superRefine(refineFn);
break;
case "client":
refinedClientSchema = refinedClientSchema.superRefine(refineFn);
break;
case "clientCheck":
refinedClientCheckedSchema = refinedClientCheckedSchema.superRefine(refineFn);
break;
case "server":
refinedValidationSchema = refinedValidationSchema.superRefine(refineFn);
break;
}
}
}
}
return {
pk: pkKeys.length ? pkKeys : null,
clientPk: clientPkKeys.length ? clientPkKeys : null,
deriveDependencies,
refineInfo: { groups: refineGroups ?? [], fieldToGroup },
isClientRecord,
sqlSchema: finalSqlSchema,
clientSchema: finalClientSchema,
clientCheckedSchema: finalClientCheckedSchema,
serverSchema: finalValidationSchema,
validators: {
sql: refinedSqlSchema,
client: refinedClientSchema,
clientChecked: refinedClientCheckedSchema,
server: refinedValidationSchema,
},
defaultValues: defaultValues,
stateType: {},
generateDefaults,
toClient,
toDb,
parseForDb: (appData) => {
const validData = refinedValidationSchema.parse(appData);
return toDb(validData);
},
parsePatchForDb: (patchData) => {
const validPatch = finalValidationSchema.partial().parse(patchData);
return toDb(validPatch);
},
parseFromDb: (dbData) => {
const parsed = refinedSqlSchema.parse(dbData);
const clientObj = toClient(parsed);
for (const key in defaultValues) {
if (!(key in clientObj)) {
clientObj[key] = defaultValues[key];
}
}
return clientObj;
},
};
}
function createViewObject(initialRegistryKey, selection, registry, tableNameToRegistryKeyMap) {
let allTablesSupportsReconciliation = true;
const checkedTables = {};
function buildView(currentRegistryKey, subSelection, schemaType) {
const registryEntry = registry[currentRegistryKey];
if (!registryEntry) {
throw new Error(`Schema with key "${currentRegistryKey}" not found in the registry.`);
}
if (!(currentRegistryKey in checkedTables)) {
const hasPks = !!(registryEntry.zodSchemas?.pk &&
registryEntry.zodSchemas.pk.length > 0 &&
registryEntry.zodSchemas?.clientPk &&
registryEntry.zodSchemas.clientPk.length > 0);
checkedTables[currentRegistryKey] = hasPks;
if (!hasPks) {
allTablesSupportsReconciliation = false;
}
}
const baseSchema = schemaType === "clientChecked"
? registryEntry.zodSchemas.clientCheckedSchema
: schemaType === "server"
? registryEntry.zodSchemas.serverSchema
: schemaType === "sql"
? registryEntry.zodSchemas.sqlSchema
: registryEntry.zodSchemas.clientSchema;
const primitiveShape = baseSchema.shape;
if (subSelection === true) {
return baseSchema;
}
const selectedRelationShapes = {};
if (typeof subSelection === "object") {
for (const relationKey in subSelection) {
const relationBuilder = registryEntry.rawSchema[relationKey];
const isRelation = relationBuilder?.config?.sql?.schema;
if (subSelection[relationKey] && isRelation) {
const relationConfig = relationBuilder.config.sql;
const targetTableName = relationConfig.schema()._tableName;
const nextRegistryKey = tableNameToRegistryKeyMap[targetTableName];
if (!nextRegistryKey) {
throw new Error(`Could not resolve registry key for table "${targetTableName}"`);
}
const relationSchema = buildView(nextRegistryKey, subSelection[relationKey], schemaType);
if (["hasMany", "manyToMany"].includes(relationConfig.type)) {
selectedRelationShapes[relationKey] = z.array(relationSchema);
}
else {
selectedRelationShapes[relationKey] = relationSchema.nullable();
}
}
}
}
const finalShape = { ...primitiveShape, ...selectedRelationShapes };
return z.object(finalShape);
}
return {
sql: buildView(initialRegistryKey, selection, "sql"),
client: buildView(initialRegistryKey, selection, "client"),
clientChecked: buildView(initialRegistryKey, selection, "clientChecked"),
server: buildView(initialRegistryKey, selection, "server"),
supportsReconciliation: allTablesSupportsReconciliation,
};
}
export function createSchemaBox(schemas, resolutions) {
const resolutionConfig = resolutions;
const resolvedSchemas = schemas;
for (const tableName in schemas) {
for (const fieldName in schemas[tableName]) {
const field = schemas[tableName][fieldName];
if (isReference(field)) {
const targetField = field.getter();
if (targetField && targetField.config) {
const newConfig = {
...targetField.config,
sql: {
...targetField.config.sql,
field: fieldName,
isForeignKey: true,
},
};
resolvedSchemas[tableName][fieldName] = {
...targetField,
config: newConfig,
};
}
else {
throw new Error(`Could not resolve reference for ${tableName}.${fieldName}`);
}
}
}
}
for (const tableName in schemas) {
const tableConfig = resolutionConfig[tableName];
if (!tableConfig)
continue;
for (const fieldName in tableConfig) {
const field = schemas[tableName][fieldName];
const resolution = tableConfig[fieldName];
if (field && field.__type === "placeholder-relation") {
const targetKey = resolution.toKey;
if (!targetKey || !targetKey.__parentTableType) {
throw new Error(`Could not resolve relation for ${tableName}.${fieldName}`);
}
const targetSchema = targetKey.__parentTableType;
const relationType = field.relationType;
const defaultCount = field.defaultCount || resolution.defaultCount;
const zodSchema = relationType === "hasMany" || relationType === "manyToMany"
? z.array(z.any())
: z.any();
const initialValue = relationType === "hasMany" || relationType === "manyToMany"
? Array.from({ length: defaultCount || 0 }, () => ({}))
: {};
const resolvedBuilder = createBuilder({
stage: "relation",
sqlConfig: {
type: relationType,
fromKey: resolution.fromKey,
toKey: () => targetKey,
schema: () => targetSchema,
defaultCount: defaultCount,
defaultConfig: field.defaultConfig,
},
sqlZod: zodSchema,
initialValue,
clientZod: zodSchema,
validationZod: zodSchema,
});
resolvedSchemas[tableName][fieldName] = resolvedBuilder;
}
}
}
const finalRegistry = {};
for (const tableName in resolvedSchemas) {
const zodSchemas = createSchema(resolvedSchemas[tableName]);
finalRegistry[tableName] = {
rawSchema: resolvedSchemas[tableName],
zodSchemas: zodSchemas,
transforms: {
toClient: zodSchemas.toClient,
toDb: zodSchemas.toDb,
parseForDb: zodSchemas.parseForDb,
parsePatchForDb: zodSchemas.parsePatchForDb,
parseFromDb: zodSchemas.parseFromDb,
},
pk: zodSchemas.pk,
clientPk: zodSchemas.clientPk,
deriveDependencies: zodSchemas.deriveDependencies,
refineInfo: zodSchemas.refineInfo,
isClientRecord: zodSchemas.isClientRecord,
generateDefaults: zodSchemas.generateDefaults,
};
}
const cleanerRegistry = {};
const tableNameToRegistryKeyMap = {};
for (const key in finalRegistry) {
const tableName = finalRegistry[key].rawSchema._tableName;
tableNameToRegistryKeyMap[tableName] = key;
}
for (const tableName in finalRegistry) {
const entry = finalRegistry[tableName];
const rawSchema = entry.rawSchema;
const tableDef = { ...entry.generateDefaults() };
for (const key in rawSchema) {
if (key === "_tableName" || key.startsWith("__"))
continue;
const field = rawSchema[key];
if (!field?.config?.sql)
continue;
const sqlConfig = field.config.sql;
if (sqlConfig.schema) {
const targetTableName = sqlConfig.schema()._tableName;
const targetRegKey = tableNameToRegistryKeyMap[targetTableName];
if (targetRegKey && finalRegistry[targetRegKey]) {
const targetEntry = finalRegistry[targetRegKey];
const targetDefaults = targetEntry.generateDefaults();
if (sqlConfig.type === "hasMany" || sqlConfig.type === "manyToMany") {
const count = sqlConfig?.defaultCount || 1;
tableDef[key] = Array.from({ length: count }, () => targetDefaults);
tableDef[`__def__${key}`] = targetDefaults;
}
else {
tableDef[key] =
sqlConfig.defaultConfig === null ? null : targetDefaults;
tableDef[`__def__${key}`] = targetDefaults;
}
}
}
}
entry.zodSchemas.defaultsDefinition = tableDef;
}
const createNavProxy = (currentTable, registry) => {
return new Proxy({}, {
get(target, relationName) {
const schema = registry[currentTable]?.rawSchema;
if (!schema)
return undefined;
const field = schema[relationName];
if (!field?.config?.sql?.schema)
return undefined;
const targetSchema = field.config.sql.schema();
const targetTable = targetSchema?._tableName;
if (targetTable && registry[targetTable]) {
return createNavProxy(targetTable, registry);
}
return undefined;
},
});
};
for (const tableName in finalRegistry) {
const entry = finalRegistry[tableName];
cleanerRegistry[tableName] = {
definition: entry.rawSchema,
schemaKey: tableName,
schemas: {
sql: entry.zodSchemas.sqlSchema,
client: entry.zodSchemas.clientSchema,
clientChecked: entry.zodSchemas.clientCheckedSchema,
server: entry.zodSchemas.serverSchema,
},
validators: {
sql: entry.zodSchemas.validators.sql,
client: entry.zodSchemas.validators.client,
clientChecked: entry.zodSchemas.validators.clientChecked,
server: entry.zodSchemas.validators.server,
},
transforms: {
toClient: entry.transforms.toClient,
toDb: entry.transforms.toDb,
parseForDb: entry.transforms.parseForDb,
parsePatchForDb: entry.transforms.parsePatchForDb,
parseFromDb: entry.transforms.parseFromDb,
},
defaults: entry.generateDefaults(),
defaultsDefinition: entry.zodSchemas.defaultsDefinition,
stateType: entry.zodSchemas.stateType,
generateDefaults: entry.generateDefaults,
pk: entry.pk,
clientPk: entry.clientPk,
deriveDependencies: entry.deriveDependencies,
refineInfo: entry.refineInfo,
isClientRecord: entry.isClientRecord,
nav: createNavProxy(tableName, finalRegistry),
createView: (selection) => {
const view = createViewObject(tableName, selection, finalRegistry, tableNameToRegistryKeyMap);
const deepToClient = (dbData, currentSelection, currentKey) => {
if (!dbData)
return dbData;
if (Array.isArray(dbData))
return dbData.map((item) => deepToClient(item, currentSelection, currentKey));
const regEntry = finalRegistry[currentKey];
const baseMapped = { ...regEntry.transforms.toClient(dbData) };
if (typeof currentSelection === "object") {
for (const relKey in currentSelection) {
if (currentSelection[relKey] &&
dbData[relKey] !== undefined &&
dbData[relKey] !== null) {
const relField = regEntry.rawSchema[relKey];
if (relField?.config?.sql?.schema) {
const targetTableName = relField.config.sql.schema()._tableName;
const nextRegKey = tableNameToRegistryKeyMap[targetTableName];
if (nextRegKey) {
baseMapped[relKey] = deepToClient(dbData[relKey], currentSelection[relKey], nextRegKey);
}
}
}
}
}
if (regEntry.rawSchema.__derives?.forClient) {
for (const key in regEntry.rawSchema.__derives.forClient) {
baseMapped[key] =
regEntry.rawSchema.__derives.forClient[key](baseMapped);
}
}
return baseMapped;
};
const deepToDb = (clientData, currentSelection, currentKey) => {
if (!clientData)
return clientData;
if (Array.isArray(clientData))
return clientData.map((item) => deepToDb(item, currentSelection, currentKey));
const regEntry = finalRegistry[currentKey];
const baseMapped = regEntry.transforms.toDb(clientData);
if (typeof currentSelection === "object") {
for (const relKey in currentSelection) {
if (currentSelection[relKey] &&
clientData[relKey] !== undefined &&
clientData[relKey] !== null) {
const relField = regEntry.rawSchema[relKey];
if (relField?.config?.sql?.schema) {
const targetTableName = relField.config.sql.schema()._tableName;
const nextRegKey = tableNameToRegistryKeyMap[targetTableName];
if (nextRegKey) {
baseMapped[relKey] = deepToDb(clientData[relKey], currentSelection[relKey], nextRegKey);
}
}
}
}
}
return baseMapped;
};
const viewToClient = (dbData) => deepToClient(dbData, selection, tableName);
const viewToDb = (clientData) => deepToDb(clientData, selection, tableName);
const reconcile = (clientData) => {
return {
withServer: (serverData) => {
const parsedServerData = viewToClient(serverData);
const mergeTrees = (cNode, sNode, tableKey, sel) => {
if (sNode === undefined || sNode === null)
return cNode;
if (cNode === undefined || cNode === null)
return sNode;
const regEntry = finalRegistry[tableKey];
const clientPkField = regEntry.clientPk?.[0] || regEntry.pk?.[0];
const dbPkField = regEntry.pk?.[0] || clientPkField;
if (Array.isArray(cNode)) {
if (!Array.isArray(sNode))
return cNode;
return cNode.map((cItem, index) => {
let sItem = undefined;
if (clientPkField && cItem[clientPkField] !== undefined) {
sItem = sNode.find((s) => s[clientPkField] === cItem[clientPkField]);
}
if (!sItem && dbPkField && cItem[dbPkField] !== undefined) {
sItem = sNode.find((s) => s[dbPkField] === cItem[dbPkField]);
}
if (!sItem && sNode[index]) {
sItem = sNode[index];
}
return mergeTrees(cItem, sItem, tableKey, sel);
});
}
if (typeof cNode === "object" && typeof sNode === "object") {
const merged = { ...cNode };
for (const key in sNode) {
const selValue = typeof sel === "object" ? sel[key] : undefined;
const relField = regEntry.rawSchema[key];
const isRelation = !!(selValue && relField?.config?.sql?.schema);
if (isRelation) {
const nextTableKey = tableNameToRegistryKeyMap[relField.config.sql.schema()._tableName];
merged[key] = mergeTrees(cNode[key], sNode[key], nextTableKey, selValue);
}
else {
merged[key] = sNode[key];
}
}
if (clientPkField &&
dbPkField &&
clientPkField !== dbPkField &&
merged[dbPkField] !== undefined &&
merged[dbPkField] !== null) {
delete merged[clientPkField];
}
return merged;
}
return sNode !== undefined ? sNode : cNode;
};
return mergeTrees(clientData, parsedServerData, tableName, selection);
},
};
};
const viewDefaults = entry.generateDefaults();
return {
definition