cogsbox-shape 0.5.213 → 0.5.215
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
export { createShapePlugin, validateShapeRefines, wireShapeValidationOptions, type InferShapeBoxState, type ShapeRefineInfo, type ShapeSchemaBox, type ShapeSchemaBoxEntry, } from "./plugin.js";
|
|
1
|
+
export { createShapePlugin, validateShapeKeys, validateShapeRefines, wireShapeValidationOptions, type InferShapeBoxState, type ShapeRefineInfo, type ShapeSchemaBox, type ShapeSchemaBoxEntry, } from "./plugin.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { createShapePlugin, validateShapeRefines, wireShapeValidationOptions, } from "./plugin.js";
|
|
1
|
+
export { createShapePlugin, validateShapeKeys, validateShapeRefines, wireShapeValidationOptions, } from "./plugin.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ChainMethodContext } from "cogsbox-state";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
/** Minimal shape of a createSchemaBox entry — matches journalSchemaBox.journalTechnical etc. */
|
|
3
4
|
export type ShapeRefineInfo = {
|
|
@@ -47,10 +48,52 @@ type FormUpdateParams = {
|
|
|
47
48
|
}>) => void;
|
|
48
49
|
clearZodErrors?: (paths: string[][]) => void;
|
|
49
50
|
};
|
|
51
|
+
type UpdateParams = {
|
|
52
|
+
stateKey: string;
|
|
53
|
+
update: {
|
|
54
|
+
path: string[];
|
|
55
|
+
updateType: string;
|
|
56
|
+
oldValue: unknown;
|
|
57
|
+
newValue: unknown;
|
|
58
|
+
};
|
|
59
|
+
getState: () => unknown;
|
|
60
|
+
addZodErrors: (errors: Array<{
|
|
61
|
+
path: string[];
|
|
62
|
+
message: string;
|
|
63
|
+
code?: string;
|
|
64
|
+
}>) => void;
|
|
65
|
+
clearZodErrors?: (paths: string[][]) => void;
|
|
66
|
+
};
|
|
67
|
+
type ShapeKeyValidationParams = {
|
|
68
|
+
stateKey: string;
|
|
69
|
+
path: string[];
|
|
70
|
+
keys?: readonly string[];
|
|
71
|
+
getState?: () => unknown;
|
|
72
|
+
};
|
|
50
73
|
export declare function wireShapeValidationOptions(box: ShapeSchemaBox, params: TransformStateParams): void;
|
|
51
74
|
/** Cross-field refine errors only — field rules are handled by state via setOptions. */
|
|
52
75
|
export declare function validateShapeRefines(box: ShapeSchemaBox, params: FormUpdateParams): void;
|
|
76
|
+
export declare function validateShapeRefinesOnUpdate(box: ShapeSchemaBox, params: UpdateParams): void;
|
|
77
|
+
export declare function validateShapeKeys(box: ShapeSchemaBox, params: ShapeKeyValidationParams): {
|
|
78
|
+
success: boolean;
|
|
79
|
+
results: {
|
|
80
|
+
key: string;
|
|
81
|
+
path: string[];
|
|
82
|
+
success: boolean;
|
|
83
|
+
data: any;
|
|
84
|
+
}[];
|
|
85
|
+
};
|
|
53
86
|
export declare function createShapePlugin<const TBox extends ShapeSchemaBox>(box: TBox): import("cogsbox-state").CogsPluginBuilder<"shape", {
|
|
54
87
|
logs: boolean | undefined;
|
|
55
|
-
}, unknown, unknown, never, {
|
|
88
|
+
}, unknown, unknown, never, {
|
|
89
|
+
validateShape: import("cogsbox-state").ChainMethodDefinition<(ctx: ChainMethodContext, keys?: readonly string[]) => {
|
|
90
|
+
success: boolean;
|
|
91
|
+
results: {
|
|
92
|
+
key: string;
|
|
93
|
+
path: string[];
|
|
94
|
+
success: boolean;
|
|
95
|
+
data: any;
|
|
96
|
+
}[];
|
|
97
|
+
}>;
|
|
98
|
+
}, true, true, true, true, false, true, InferShapeBoxState<TBox>>;
|
|
56
99
|
export {};
|
|
@@ -108,6 +108,41 @@ function addValidationIssues(params, issues) {
|
|
|
108
108
|
params.addZodErrors(issues);
|
|
109
109
|
notifyValidationPaths(params.stateKey, issues.map((issue) => issue.path));
|
|
110
110
|
}
|
|
111
|
+
function setValidationIssues(stateKey, issues) {
|
|
112
|
+
if (issues.length === 0)
|
|
113
|
+
return;
|
|
114
|
+
const store = getGlobalStore.getState();
|
|
115
|
+
for (const issue of issues) {
|
|
116
|
+
const currentMeta = store.getShadowMetadata(stateKey, issue.path) || {};
|
|
117
|
+
store.setShadowMetadata(stateKey, issue.path, {
|
|
118
|
+
...currentMeta,
|
|
119
|
+
validation: {
|
|
120
|
+
status: "INVALID",
|
|
121
|
+
errors: [
|
|
122
|
+
{
|
|
123
|
+
source: "client",
|
|
124
|
+
message: issue.message,
|
|
125
|
+
severity: "error",
|
|
126
|
+
code: issue.code,
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
lastValidated: Date.now(),
|
|
130
|
+
validatedValue: store.getShadowValue(stateKey, issue.path),
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
notifyValidationPaths(stateKey, issues.map((issue) => issue.path));
|
|
135
|
+
}
|
|
136
|
+
function issueMatchesSelectedKeys(issue, parentPath, selectedKeys) {
|
|
137
|
+
const issuePath = issue.path.map(String);
|
|
138
|
+
if (issuePath.length <= parentPath.length)
|
|
139
|
+
return false;
|
|
140
|
+
for (let index = 0; index < parentPath.length; index++) {
|
|
141
|
+
if (issuePath[index] !== parentPath[index])
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
return selectedKeys.has(issuePath[parentPath.length]);
|
|
145
|
+
}
|
|
111
146
|
function getRelatedFields(entry, field) {
|
|
112
147
|
const groupIndexes = entry.refineInfo?.fieldToGroup[field];
|
|
113
148
|
if (!groupIndexes?.length)
|
|
@@ -126,6 +161,74 @@ function issueMatchesRelatedFields(issue, relatedFields) {
|
|
|
126
161
|
const leaf = String(issue.path.at(-1) ?? "");
|
|
127
162
|
return relatedFields.has(leaf);
|
|
128
163
|
}
|
|
164
|
+
function getChangedObjectFields(oldValue, newValue) {
|
|
165
|
+
if (oldValue === null ||
|
|
166
|
+
newValue === null ||
|
|
167
|
+
typeof oldValue !== "object" ||
|
|
168
|
+
typeof newValue !== "object" ||
|
|
169
|
+
Array.isArray(oldValue) ||
|
|
170
|
+
Array.isArray(newValue)) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
const fields = new Set();
|
|
174
|
+
const oldRecord = oldValue;
|
|
175
|
+
const newRecord = newValue;
|
|
176
|
+
for (const key of new Set([
|
|
177
|
+
...Object.keys(oldRecord),
|
|
178
|
+
...Object.keys(newRecord),
|
|
179
|
+
])) {
|
|
180
|
+
if (!Object.is(oldRecord[key], newRecord[key]))
|
|
181
|
+
fields.add(key);
|
|
182
|
+
}
|
|
183
|
+
return fields;
|
|
184
|
+
}
|
|
185
|
+
function resolveUpdateRefineTarget(entry, updatePath, oldValue, newValue) {
|
|
186
|
+
const changedObjectFields = getChangedObjectFields(oldValue, newValue);
|
|
187
|
+
if (changedObjectFields) {
|
|
188
|
+
const parentPath = updatePath;
|
|
189
|
+
const relatedFields = new Set();
|
|
190
|
+
for (const field of changedObjectFields) {
|
|
191
|
+
const groupFields = getRelatedFields(entry, field);
|
|
192
|
+
if (!groupFields)
|
|
193
|
+
continue;
|
|
194
|
+
for (const related of groupFields)
|
|
195
|
+
relatedFields.add(related);
|
|
196
|
+
}
|
|
197
|
+
if (relatedFields.size === 0)
|
|
198
|
+
return null;
|
|
199
|
+
return {
|
|
200
|
+
relatedFields,
|
|
201
|
+
relatedPaths: [...relatedFields].map((field) => [...parentPath, field]),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
const field = updatePath.at(-1);
|
|
205
|
+
if (!field)
|
|
206
|
+
return null;
|
|
207
|
+
const relatedFields = getRelatedFields(entry, field);
|
|
208
|
+
if (!relatedFields)
|
|
209
|
+
return null;
|
|
210
|
+
return {
|
|
211
|
+
relatedFields,
|
|
212
|
+
relatedPaths: resolveRelatedPaths(updatePath, relatedFields),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function applyRefineValidation(box, params, target, state) {
|
|
216
|
+
const entry = box[params.stateKey];
|
|
217
|
+
const clientSchema = entry?.validators?.client ?? entry?.schemas.client;
|
|
218
|
+
if (!entry || !clientSchema)
|
|
219
|
+
return;
|
|
220
|
+
const result = clientSchema.safeParse(state);
|
|
221
|
+
if (result.success) {
|
|
222
|
+
clearValidationPaths(params, target.relatedPaths);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const issues = result.error.issues.filter((issue) => issueMatchesRelatedFields(issue, target.relatedFields));
|
|
226
|
+
const mapped = mapZodIssues(issues);
|
|
227
|
+
const activeKeys = new Set(mapped.map((entry) => pathKey(entry.path)));
|
|
228
|
+
const stalePaths = target.relatedPaths.filter((targetPath) => !activeKeys.has(pathKey(targetPath)));
|
|
229
|
+
clearValidationPaths(params, stalePaths);
|
|
230
|
+
addValidationIssues(params, mapped);
|
|
231
|
+
}
|
|
129
232
|
export function wireShapeValidationOptions(box, params) {
|
|
130
233
|
const entry = box[params.stateKey];
|
|
131
234
|
if (!entry)
|
|
@@ -144,8 +247,7 @@ export function validateShapeRefines(box, params) {
|
|
|
144
247
|
return;
|
|
145
248
|
}
|
|
146
249
|
const entry = box[params.stateKey];
|
|
147
|
-
|
|
148
|
-
if (!entry || !clientSchema)
|
|
250
|
+
if (!entry)
|
|
149
251
|
return;
|
|
150
252
|
const field = params.path.at(-1);
|
|
151
253
|
if (!field)
|
|
@@ -153,18 +255,79 @@ export function validateShapeRefines(box, params) {
|
|
|
153
255
|
const relatedFields = getRelatedFields(entry, field);
|
|
154
256
|
if (!relatedFields)
|
|
155
257
|
return;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
258
|
+
applyRefineValidation(box, params, {
|
|
259
|
+
relatedFields,
|
|
260
|
+
relatedPaths: resolveRelatedPaths(params.path, relatedFields),
|
|
261
|
+
}, getStateForValidation(params));
|
|
262
|
+
}
|
|
263
|
+
export function validateShapeRefinesOnUpdate(box, params) {
|
|
264
|
+
if (params.update.updateType !== "update")
|
|
265
|
+
return;
|
|
266
|
+
const entry = box[params.stateKey];
|
|
267
|
+
if (!entry)
|
|
160
268
|
return;
|
|
269
|
+
const target = resolveUpdateRefineTarget(entry, params.update.path, params.update.oldValue, params.update.newValue);
|
|
270
|
+
if (!target)
|
|
271
|
+
return;
|
|
272
|
+
applyRefineValidation(box, params, target, params.getState());
|
|
273
|
+
}
|
|
274
|
+
export function validateShapeKeys(box, params) {
|
|
275
|
+
const entry = box[params.stateKey];
|
|
276
|
+
const clientSchema = entry?.validators?.client ?? entry?.schemas.client;
|
|
277
|
+
if (!entry || !clientSchema)
|
|
278
|
+
return { success: true, results: [] };
|
|
279
|
+
const store = getGlobalStore.getState();
|
|
280
|
+
const rootState = params.getState?.() ?? store.getShadowValue(params.stateKey, []);
|
|
281
|
+
const result = clientSchema.safeParse(rootState);
|
|
282
|
+
const selectedKeys = params.keys ? new Set(params.keys) : null;
|
|
283
|
+
const targetPaths = params.keys?.map((key) => [...params.path, key]) ??
|
|
284
|
+
(result.success
|
|
285
|
+
? []
|
|
286
|
+
: mapZodIssues(result.error.issues).map((issue) => issue.path));
|
|
287
|
+
if (result.success) {
|
|
288
|
+
clearValidationPaths({
|
|
289
|
+
stateKey: params.stateKey,
|
|
290
|
+
getState: () => rootState,
|
|
291
|
+
addZodErrors: () => { },
|
|
292
|
+
}, targetPaths);
|
|
293
|
+
return {
|
|
294
|
+
success: true,
|
|
295
|
+
results: params.keys?.map((key) => ({
|
|
296
|
+
key,
|
|
297
|
+
path: [...params.path, key],
|
|
298
|
+
success: true,
|
|
299
|
+
data: store.getShadowValue(params.stateKey, [...params.path, key]),
|
|
300
|
+
})) ?? [],
|
|
301
|
+
};
|
|
161
302
|
}
|
|
162
|
-
const issues =
|
|
303
|
+
const issues = selectedKeys
|
|
304
|
+
? result.error.issues.filter((issue) => issueMatchesSelectedKeys(issue, params.path, selectedKeys))
|
|
305
|
+
: result.error.issues;
|
|
163
306
|
const mapped = mapZodIssues(issues);
|
|
164
|
-
const activeKeys = new Set(mapped.map((
|
|
165
|
-
const stalePaths =
|
|
166
|
-
clearValidationPaths(
|
|
167
|
-
|
|
307
|
+
const activeKeys = new Set(mapped.map((issue) => pathKey(issue.path)));
|
|
308
|
+
const stalePaths = targetPaths.filter((targetPath) => !activeKeys.has(pathKey(targetPath)));
|
|
309
|
+
clearValidationPaths({
|
|
310
|
+
stateKey: params.stateKey,
|
|
311
|
+
getState: () => rootState,
|
|
312
|
+
addZodErrors: () => { },
|
|
313
|
+
}, stalePaths);
|
|
314
|
+
setValidationIssues(params.stateKey, mapped);
|
|
315
|
+
return {
|
|
316
|
+
success: mapped.length === 0,
|
|
317
|
+
results: params.keys?.map((key) => {
|
|
318
|
+
const keyPath = [...params.path, key];
|
|
319
|
+
const keyIssues = mapped.filter((issue) => issue.path[params.path.length] === key);
|
|
320
|
+
return {
|
|
321
|
+
key,
|
|
322
|
+
path: keyPath,
|
|
323
|
+
success: keyIssues.length === 0,
|
|
324
|
+
data: keyIssues.length === 0
|
|
325
|
+
? store.getShadowValue(params.stateKey, keyPath)
|
|
326
|
+
: undefined,
|
|
327
|
+
error: keyIssues.length === 0 ? undefined : { issues: keyIssues },
|
|
328
|
+
};
|
|
329
|
+
}) ?? [],
|
|
330
|
+
};
|
|
168
331
|
}
|
|
169
332
|
function buildInitialState(box) {
|
|
170
333
|
const state = {};
|
|
@@ -193,5 +356,16 @@ export function createShapePlugin(box) {
|
|
|
193
356
|
console.log("[shape]", params.stateKey, params.path, params.event.activityType);
|
|
194
357
|
}
|
|
195
358
|
validateShapeRefines(box, params);
|
|
196
|
-
})
|
|
359
|
+
})
|
|
360
|
+
.onUpdate((params) => {
|
|
361
|
+
validateShapeRefinesOnUpdate(box, params);
|
|
362
|
+
})
|
|
363
|
+
.methods((m) => ({
|
|
364
|
+
validateShape: m.object((ctx, keys) => validateShapeKeys(box, {
|
|
365
|
+
stateKey: ctx.stateKey,
|
|
366
|
+
path: ctx.path,
|
|
367
|
+
keys,
|
|
368
|
+
getState: ctx.$get,
|
|
369
|
+
})),
|
|
370
|
+
}));
|
|
197
371
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cogsbox-shape",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.215",
|
|
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",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"author": "",
|
|
66
66
|
"license": "MIT",
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"cogsbox-state": "^0.5.
|
|
68
|
+
"cogsbox-state": "^0.5.488",
|
|
69
69
|
"commander": "^13.1.0",
|
|
70
70
|
"kysely": "^0.29.2",
|
|
71
71
|
"tsx": "^4.19.3",
|