cogsbox-shape 0.5.117 → 0.5.119
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/schema.js +141 -80
- package/package.json +1 -1
package/dist/schema.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { z
|
|
2
|
-
import { ca } from "zod/v4/locales";
|
|
1
|
+
import { z } from "zod";
|
|
3
2
|
export const isFunction = (fn) => typeof fn === "function";
|
|
4
3
|
// Function to create a properly typed current timestamp config
|
|
5
4
|
export function currentTimeStamp() {
|
|
@@ -127,53 +126,82 @@ function createBuilder(config) {
|
|
|
127
126
|
clientTransform: config.clientTransform, // <-- FIX: Make sure transform is passed through
|
|
128
127
|
validationTransform: config.validationTransform, // <-- FIX: Make sure transform is passed through
|
|
129
128
|
},
|
|
130
|
-
initialState: (value,
|
|
129
|
+
initialState: (value, schemaOrModifier) => {
|
|
131
130
|
if (completedStages.has("new")) {
|
|
132
131
|
throw new Error("initialState() can only be called once in the chain");
|
|
133
132
|
}
|
|
134
133
|
let actualValue;
|
|
135
134
|
let baseSchema;
|
|
136
|
-
|
|
135
|
+
let finalSchema;
|
|
136
|
+
// Check if value is a Zod schema (single argument case)
|
|
137
137
|
if (value && typeof value === "object" && "_def" in value) {
|
|
138
138
|
// It's a Zod schema - infer the default value
|
|
139
139
|
baseSchema = value;
|
|
140
140
|
actualValue = inferDefaultFromZod(baseSchema, config.sqlConfig);
|
|
141
|
+
finalSchema = baseSchema;
|
|
141
142
|
}
|
|
142
143
|
else {
|
|
143
144
|
// Get the actual value
|
|
144
145
|
actualValue = isFunction(value) ? value() : value;
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
baseSchema = z.literal(actualValue);
|
|
146
|
+
// If second parameter is provided and is a Zod schema, use it directly
|
|
147
|
+
if (schemaOrModifier &&
|
|
148
|
+
typeof schemaOrModifier === "object" &&
|
|
149
|
+
"_def" in schemaOrModifier) {
|
|
150
|
+
finalSchema = schemaOrModifier;
|
|
151
151
|
}
|
|
152
|
-
else if (
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
152
|
+
else if (isFunction(schemaOrModifier)) {
|
|
153
|
+
// It's a schema modifier function
|
|
154
|
+
// Create base Zod schema from the value type
|
|
155
|
+
if (typeof actualValue === "string" ||
|
|
156
|
+
typeof actualValue === "number" ||
|
|
157
|
+
typeof actualValue === "boolean") {
|
|
158
|
+
baseSchema = z.literal(actualValue);
|
|
159
|
+
}
|
|
160
|
+
else if (actualValue instanceof Date) {
|
|
161
|
+
baseSchema = z.date();
|
|
162
|
+
}
|
|
163
|
+
else if (actualValue === null) {
|
|
164
|
+
baseSchema = z.null();
|
|
165
|
+
}
|
|
166
|
+
else if (actualValue === undefined) {
|
|
167
|
+
baseSchema = z.undefined();
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
baseSchema = z.any();
|
|
171
|
+
}
|
|
172
|
+
// Apply the modifier
|
|
173
|
+
finalSchema = schemaOrModifier(baseSchema);
|
|
160
174
|
}
|
|
161
175
|
else {
|
|
162
|
-
|
|
176
|
+
// No schema provided, create from value type
|
|
177
|
+
if (typeof actualValue === "string" ||
|
|
178
|
+
typeof actualValue === "number" ||
|
|
179
|
+
typeof actualValue === "boolean") {
|
|
180
|
+
baseSchema = z.literal(actualValue);
|
|
181
|
+
}
|
|
182
|
+
else if (actualValue instanceof Date) {
|
|
183
|
+
baseSchema = z.date();
|
|
184
|
+
}
|
|
185
|
+
else if (actualValue === null) {
|
|
186
|
+
baseSchema = z.null();
|
|
187
|
+
}
|
|
188
|
+
else if (actualValue === undefined) {
|
|
189
|
+
baseSchema = z.undefined();
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
baseSchema = z.any();
|
|
193
|
+
}
|
|
194
|
+
finalSchema = baseSchema;
|
|
163
195
|
}
|
|
164
196
|
}
|
|
165
|
-
// Apply schema modifier if provided
|
|
166
|
-
const newSchema = isFunction(schemaModifier)
|
|
167
|
-
? schemaModifier(baseSchema)
|
|
168
|
-
: baseSchema;
|
|
169
197
|
const newCompletedStages = new Set(completedStages);
|
|
170
198
|
newCompletedStages.add("new");
|
|
171
199
|
// Create union for client/validation
|
|
172
|
-
const clientValidationSchema = z.union([config.sqlZod,
|
|
200
|
+
const clientValidationSchema = z.union([config.sqlZod, finalSchema]);
|
|
173
201
|
return createBuilder({
|
|
174
202
|
...config,
|
|
175
203
|
stage: "new",
|
|
176
|
-
newZod:
|
|
204
|
+
newZod: finalSchema,
|
|
177
205
|
initialValue: actualValue,
|
|
178
206
|
clientZod: clientValidationSchema,
|
|
179
207
|
validationZod: clientValidationSchema,
|
|
@@ -516,52 +544,70 @@ export function createSchema(schema, relations) {
|
|
|
516
544
|
toDb,
|
|
517
545
|
};
|
|
518
546
|
}
|
|
519
|
-
function createViewObject(
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
547
|
+
function createViewObject(initialRegistryKey, // The key for the starting schema, e.g., "users"
|
|
548
|
+
selection, registry, tableNameToRegistryKeyMap // The lookup map
|
|
549
|
+
) {
|
|
550
|
+
/**
|
|
551
|
+
* A recursive helper function that builds a Zod schema for a given schema and its selected relations.
|
|
552
|
+
* It is defined inside createViewObject to have access to the `registry` and `tableNameToRegistryKeyMap` via a closure.
|
|
553
|
+
*
|
|
554
|
+
* @param currentRegistryKey - The user-defined key for the current schema being processed (e.g., "users", then "posts").
|
|
555
|
+
* @param subSelection - The part of the selection object for the current schema (e.g., { comments: true } or just `true`).
|
|
556
|
+
* @param schemaType - Whether to build the 'client' or 'validation' schema.
|
|
557
|
+
* @returns A ZodObject representing the composed schema.
|
|
558
|
+
*/
|
|
559
|
+
function buildView(currentRegistryKey, subSelection, schemaType) {
|
|
560
|
+
// 1. Find the current schema's definition in the registry using its KEY.
|
|
561
|
+
const registryEntry = registry[currentRegistryKey];
|
|
562
|
+
if (!registryEntry) {
|
|
563
|
+
throw new Error(`Schema with key "${currentRegistryKey}" not found in the registry.`);
|
|
564
|
+
}
|
|
565
|
+
// 2. Get the base Zod schema (primitives and references only) for the current level.
|
|
524
566
|
const baseSchema = registryEntry.zodSchemas[`${schemaType}Schema`];
|
|
525
|
-
// --- START OF THE FIX ---
|
|
526
|
-
// 1. Get the shape of the base schema (e.g., { id: z.ZodNumber, name: z.ZodString })
|
|
527
|
-
// The base schema correctly contains only primitive/referenced fields.
|
|
528
567
|
const primitiveShape = baseSchema.shape;
|
|
529
|
-
//
|
|
568
|
+
// 3. If the selection is just `true`, we are done at this level. Return the base primitive schema.
|
|
530
569
|
if (subSelection === true) {
|
|
531
570
|
return z.object(primitiveShape);
|
|
532
571
|
}
|
|
533
|
-
//
|
|
572
|
+
// 4. If the selection is an object, we need to process its relations.
|
|
534
573
|
const selectedRelationShapes = {};
|
|
535
574
|
if (typeof subSelection === "object") {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
if
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const
|
|
543
|
-
//
|
|
575
|
+
// Iterate over the keys in the selection object (e.g., "posts", "profile").
|
|
576
|
+
for (const relationKey in subSelection) {
|
|
577
|
+
// Check if this key corresponds to a valid relation in the raw schema definition.
|
|
578
|
+
const relationBuilder = registryEntry.rawSchema[relationKey];
|
|
579
|
+
const isRelation = relationBuilder?.config?.sql?.schema;
|
|
580
|
+
if (subSelection[relationKey] && isRelation) {
|
|
581
|
+
const relationConfig = relationBuilder.config.sql;
|
|
582
|
+
// 5. KEY STEP: Get the internal `_tableName` of the TARGET schema (e.g., "post_table").
|
|
583
|
+
const targetTableName = relationConfig.schema()._tableName;
|
|
584
|
+
// 6. KEY STEP: Use the map to find the REGISTRY KEY for that target schema (e.g., "posts").
|
|
585
|
+
const nextRegistryKey = tableNameToRegistryKeyMap[targetTableName];
|
|
586
|
+
if (!nextRegistryKey) {
|
|
587
|
+
throw new Error(`Could not resolve registry key for table "${targetTableName}"`);
|
|
588
|
+
}
|
|
589
|
+
// 7. RECURSIVE CALL: Call `buildView` for the related schema, passing the
|
|
590
|
+
// CORRECT registry key and the sub-selection for that relation.
|
|
591
|
+
const relationSchema = buildView(nextRegistryKey, subSelection[relationKey], schemaType);
|
|
592
|
+
// 8. Wrap the resulting schema in an array or optional based on the relation type.
|
|
544
593
|
if (["hasMany", "manyToMany"].includes(relationConfig.type)) {
|
|
545
|
-
selectedRelationShapes[
|
|
594
|
+
selectedRelationShapes[relationKey] = z.array(relationSchema);
|
|
546
595
|
}
|
|
547
596
|
else {
|
|
548
|
-
selectedRelationShapes[
|
|
597
|
+
selectedRelationShapes[relationKey] = relationSchema.optional();
|
|
549
598
|
}
|
|
550
599
|
}
|
|
551
600
|
}
|
|
552
601
|
}
|
|
553
|
-
//
|
|
602
|
+
// 9. Combine the base primitive fields with the newly built relational schemas.
|
|
554
603
|
const finalShape = { ...primitiveShape, ...selectedRelationShapes };
|
|
555
|
-
// 5. Return a brand new, clean Zod object from the final shape.
|
|
556
604
|
return z.object(finalShape);
|
|
557
|
-
// --- END OF THE FIX ---
|
|
558
605
|
}
|
|
559
|
-
// The main function
|
|
560
|
-
const sourceRegistryEntry = registry[tableName];
|
|
606
|
+
// The main function's return value. It kicks off the recursive process for both client and validation schemas.
|
|
561
607
|
return {
|
|
562
|
-
sql:
|
|
563
|
-
client: buildView(
|
|
564
|
-
validation: buildView(
|
|
608
|
+
sql: registry[initialRegistryKey].zodSchemas.sqlSchema,
|
|
609
|
+
client: buildView(initialRegistryKey, selection, "client"),
|
|
610
|
+
validation: buildView(initialRegistryKey, selection, "validation"),
|
|
565
611
|
};
|
|
566
612
|
}
|
|
567
613
|
export function createSchemaBox(schemas, resolver) {
|
|
@@ -675,6 +721,11 @@ export function createSchemaBox(schemas, resolver) {
|
|
|
675
721
|
});
|
|
676
722
|
};
|
|
677
723
|
const cleanerRegistry = {};
|
|
724
|
+
const tableNameToRegistryKeyMap = {};
|
|
725
|
+
for (const key in finalRegistry) {
|
|
726
|
+
const tableName = finalRegistry[key].rawSchema._tableName;
|
|
727
|
+
tableNameToRegistryKeyMap[tableName] = key;
|
|
728
|
+
}
|
|
678
729
|
for (const tableName in finalRegistry) {
|
|
679
730
|
const entry = finalRegistry[tableName];
|
|
680
731
|
cleanerRegistry[tableName] = {
|
|
@@ -691,26 +742,35 @@ export function createSchemaBox(schemas, resolver) {
|
|
|
691
742
|
defaults: entry.zodSchemas.defaultValues,
|
|
692
743
|
nav: createNavProxy(tableName, finalRegistry),
|
|
693
744
|
// Add this
|
|
694
|
-
RelationSelection: {},
|
|
695
745
|
createView: (selection) => {
|
|
696
|
-
const view = createViewObject(tableName, selection, finalRegistry);
|
|
697
|
-
const defaults = computeViewDefaults(tableName, selection, finalRegistry);
|
|
746
|
+
const view = createViewObject(tableName, selection, finalRegistry, tableNameToRegistryKeyMap);
|
|
747
|
+
const defaults = computeViewDefaults(tableName, selection, finalRegistry, tableNameToRegistryKeyMap);
|
|
698
748
|
console.log("View defaults:", defaults); // ADD THIS
|
|
699
749
|
return {
|
|
700
750
|
...view,
|
|
701
751
|
defaults: defaults,
|
|
702
752
|
};
|
|
703
753
|
},
|
|
754
|
+
RelationSelection: {},
|
|
704
755
|
};
|
|
705
756
|
}
|
|
706
757
|
return cleanerRegistry;
|
|
707
758
|
}
|
|
708
|
-
function computeViewDefaults(
|
|
709
|
-
|
|
759
|
+
function computeViewDefaults(currentRegistryKey, // Renamed for clarity, e.g., "users"
|
|
760
|
+
selection, registry, tableNameToRegistryKeyMap, // Accept the map
|
|
761
|
+
visited = new Set()) {
|
|
762
|
+
if (visited.has(currentRegistryKey)) {
|
|
710
763
|
return undefined; // Prevent circular references
|
|
711
764
|
}
|
|
712
|
-
visited.add(
|
|
713
|
-
|
|
765
|
+
visited.add(currentRegistryKey);
|
|
766
|
+
// This lookup now uses the correct key every time.
|
|
767
|
+
const entry = registry[currentRegistryKey];
|
|
768
|
+
// This check prevents the crash.
|
|
769
|
+
if (!entry) {
|
|
770
|
+
// This case should ideally not be hit if the map is correct, but it's safe to have.
|
|
771
|
+
console.warn(`Could not find entry for key "${currentRegistryKey}" in registry while computing defaults.`);
|
|
772
|
+
return {};
|
|
773
|
+
}
|
|
714
774
|
const rawSchema = entry.rawSchema;
|
|
715
775
|
const baseDefaults = { ...entry.zodSchemas.defaultValues };
|
|
716
776
|
if (selection === true || typeof selection !== "object") {
|
|
@@ -724,13 +784,16 @@ function computeViewDefaults(tableName, selection, registry, visited = new Set()
|
|
|
724
784
|
if (!field?.config?.sql?.schema)
|
|
725
785
|
continue;
|
|
726
786
|
const relationConfig = field.config.sql;
|
|
727
|
-
|
|
728
|
-
//
|
|
729
|
-
|
|
730
|
-
|
|
787
|
+
// --- THE CORE FIX ---
|
|
788
|
+
// 1. Get the internal _tableName of the related schema (e.g., "post_table")
|
|
789
|
+
const targetTableName = relationConfig.schema()._tableName;
|
|
790
|
+
// 2. Use the map to find the correct registry key for it (e.g., "posts")
|
|
791
|
+
const nextRegistryKey = tableNameToRegistryKeyMap[targetTableName];
|
|
792
|
+
if (!nextRegistryKey)
|
|
793
|
+
continue; // Could not resolve, skip this relation
|
|
731
794
|
// Handle different default configurations
|
|
795
|
+
const defaultConfig = relationConfig.defaultConfig;
|
|
732
796
|
if (defaultConfig === undefined) {
|
|
733
|
-
// Don't include in defaults
|
|
734
797
|
delete baseDefaults[key];
|
|
735
798
|
}
|
|
736
799
|
else if (defaultConfig === null) {
|
|
@@ -739,23 +802,21 @@ function computeViewDefaults(tableName, selection, registry, visited = new Set()
|
|
|
739
802
|
else if (Array.isArray(defaultConfig)) {
|
|
740
803
|
baseDefaults[key] = [];
|
|
741
804
|
}
|
|
742
|
-
else if (
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
else {
|
|
750
|
-
baseDefaults[key] = computeViewDefaults(targetTable, selection[key], registry, new Set(visited));
|
|
751
|
-
}
|
|
805
|
+
else if (relationConfig.type === "hasMany" ||
|
|
806
|
+
relationConfig.type === "manyToMany") {
|
|
807
|
+
const count = defaultConfig?.count || relationConfig.defaultCount || 1;
|
|
808
|
+
baseDefaults[key] = Array.from({ length: count }, () =>
|
|
809
|
+
// 3. Make the recursive call with the CORRECT key
|
|
810
|
+
computeViewDefaults(nextRegistryKey, selection[key], registry, tableNameToRegistryKeyMap, // Pass the map along
|
|
811
|
+
new Set(visited)));
|
|
752
812
|
}
|
|
753
|
-
else
|
|
754
|
-
|
|
813
|
+
else {
|
|
814
|
+
// hasOne or belongsTo
|
|
815
|
+
baseDefaults[key] =
|
|
816
|
+
// 3. Make the recursive call with the CORRECT key
|
|
817
|
+
computeViewDefaults(nextRegistryKey, selection[key], registry, tableNameToRegistryKeyMap, // Pass the map along
|
|
818
|
+
new Set(visited));
|
|
755
819
|
}
|
|
756
820
|
}
|
|
757
|
-
// NOTE: This was in the original code but is not needed for the recursive logic.
|
|
758
|
-
// It's safe to remove, but also safe to keep.
|
|
759
|
-
// visited.delete(tableName);
|
|
760
821
|
return baseDefaults;
|
|
761
822
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cogsbox-shape",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.119",
|
|
4
4
|
"description": "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 relationships and serialization.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|