cogsbox-shape 0.5.218 → 0.5.220

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.
@@ -68,7 +68,12 @@ type ShapeKeyValidationParams = {
68
68
  path: string[];
69
69
  keys?: readonly string[];
70
70
  getState?: () => unknown;
71
+ /** When true (default), writes filtered issues to shadow validation metadata. */
72
+ persist?: boolean;
73
+ /** When true, clears existing errors on sibling fields outside `keys`. */
74
+ clearOutsideKeys?: boolean;
71
75
  };
76
+ export type ValidateGroupOptions = Pick<ShapeKeyValidationParams, "persist" | "clearOutsideKeys">;
72
77
  export declare function wireShapeValidationOptions(box: ShapeSchemaBox, params: TransformStateParams): void;
73
78
  /** Cross-field refine errors only — field rules are handled by state via setOptions. */
74
79
  export declare function validateShapeRefines(box: ShapeSchemaBox, params: FormUpdateParams): void;
@@ -117,6 +117,102 @@ function addValidationIssues(params, issues) {
117
117
  params.addZodErrors(issues);
118
118
  notifyValidationPaths(params.stateKey, issues.map((issue) => issue.path));
119
119
  }
120
+ function createShadowValidationBridge(stateKey) {
121
+ const store = getGlobalStore.getState();
122
+ return {
123
+ stateKey,
124
+ getState: () => store.getShadowValue(stateKey, []),
125
+ addZodErrors: (issues) => {
126
+ for (const error of issues) {
127
+ const errorPath = error.path;
128
+ const currentMeta = store.getShadowMetadata(stateKey, errorPath) || {};
129
+ store.setShadowMetadata(stateKey, errorPath, {
130
+ ...currentMeta,
131
+ validation: {
132
+ status: "INVALID",
133
+ errors: [
134
+ {
135
+ source: "client",
136
+ message: error.message,
137
+ severity: "error",
138
+ code: error.code,
139
+ },
140
+ ],
141
+ lastValidated: Date.now(),
142
+ validatedValue: undefined,
143
+ },
144
+ });
145
+ }
146
+ },
147
+ clearZodErrors: (paths) => {
148
+ for (const path of paths) {
149
+ const currentMeta = store.getShadowMetadata(stateKey, path) || {};
150
+ store.setShadowMetadata(stateKey, path, {
151
+ ...currentMeta,
152
+ validation: {
153
+ status: "NOT_VALIDATED",
154
+ errors: [],
155
+ lastValidated: Date.now(),
156
+ validatedValue: undefined,
157
+ },
158
+ });
159
+ }
160
+ },
161
+ };
162
+ }
163
+ function notifyStateComponents(stateKey) {
164
+ const store = getGlobalStore.getState();
165
+ const stateEntry = store.getShadowMetadata(stateKey, []);
166
+ if (!stateEntry?.components)
167
+ return;
168
+ const updates = new Set();
169
+ stateEntry.components.forEach((component) => {
170
+ const reactiveTypes = component
171
+ ? Array.isArray(component.reactiveType)
172
+ ? component.reactiveType
173
+ : [component.reactiveType || "component"]
174
+ : null;
175
+ if (!reactiveTypes?.includes("none")) {
176
+ updates.add(() => component.forceUpdate());
177
+ }
178
+ });
179
+ queueMicrotask(() => {
180
+ updates.forEach((update) => update());
181
+ });
182
+ }
183
+ function persistValidateGroupResults(params, keys, mapped) {
184
+ const validationParams = createShadowValidationBridge(params.stateKey);
185
+ if (params.clearOutsideKeys === true) {
186
+ clearOutsideGroupValidation(params, keys, validationParams);
187
+ }
188
+ const keyPaths = keys.map((key) => [...params.path, key]);
189
+ const activeKeys = new Set(mapped.map((issue) => pathKey(issue.path)));
190
+ const stalePaths = keyPaths.filter((targetPath) => !activeKeys.has(pathKey(targetPath)));
191
+ clearValidationPaths(validationParams, stalePaths);
192
+ if (mapped.length > 0) {
193
+ addValidationIssues(validationParams, mapped);
194
+ }
195
+ notifyStateComponents(params.stateKey);
196
+ }
197
+ function clearOutsideGroupValidation(params, keys, validationParams) {
198
+ const store = getGlobalStore.getState();
199
+ const parentNode = store.getShadowNode(params.stateKey, params.path);
200
+ if (!parentNode)
201
+ return;
202
+ const keySet = new Set(keys);
203
+ const outsidePaths = [];
204
+ for (const childKey of Object.keys(parentNode)) {
205
+ if (childKey === "_meta" || keySet.has(childKey))
206
+ continue;
207
+ const fieldPath = [...params.path, childKey];
208
+ const status = store.getShadowMetadata(params.stateKey, fieldPath)
209
+ ?.validation?.status;
210
+ if (status === "INVALID") {
211
+ outsidePaths.push(fieldPath);
212
+ }
213
+ }
214
+ clearValidationPaths(validationParams, outsidePaths);
215
+ }
120
216
  function issueMatchesSelectedKeys(issue, parentPath, selectedKeys) {
121
217
  const issuePath = issue.path.map(String);
122
218
  if (issuePath.length <= parentPath.length)
@@ -196,6 +292,22 @@ function resolveUpdateRefineTarget(entry, updatePath, oldValue, newValue) {
196
292
  relatedPaths: resolveRelatedPaths(updatePath, relatedFields),
197
293
  };
198
294
  }
295
+ function clearStaleRefineValidation(box, params, target, state) {
296
+ const entry = box[params.stateKey];
297
+ const clientSchema = entry?.validators?.client ?? entry?.schemas.client;
298
+ if (!entry || !clientSchema)
299
+ return;
300
+ const result = clientSchema.safeParse(state);
301
+ if (result.success) {
302
+ clearValidationPaths(params, target.relatedPaths);
303
+ return;
304
+ }
305
+ const issues = result.error.issues.filter((issue) => issueMatchesRelatedFields(issue, target.relatedFields));
306
+ const mapped = mapZodIssues(issues);
307
+ const activeKeys = new Set(mapped.map((entry) => pathKey(entry.path)));
308
+ const stalePaths = target.relatedPaths.filter((targetPath) => !activeKeys.has(pathKey(targetPath)));
309
+ clearValidationPaths(params, stalePaths);
310
+ }
199
311
  function applyRefineValidation(box, params, target, state) {
200
312
  const entry = box[params.stateKey];
201
313
  const clientSchema = entry?.validators?.client ?? entry?.schemas.client;
@@ -253,7 +365,7 @@ export function validateShapeRefinesOnUpdate(box, params) {
253
365
  const target = resolveUpdateRefineTarget(entry, params.update.path, params.update.oldValue, params.update.newValue);
254
366
  if (!target)
255
367
  return;
256
- applyRefineValidation(box, params, target, params.getState());
368
+ clearStaleRefineValidation(box, params, target, params.getState());
257
369
  }
258
370
  export function validateShapeKeys(box, params) {
259
371
  const entry = box[params.stateKey];
@@ -263,7 +375,11 @@ export function validateShapeKeys(box, params) {
263
375
  const rootState = params.getState?.() ?? getGlobalStore.getState().getShadowValue(params.stateKey, []);
264
376
  const result = clientSchema.safeParse(rootState);
265
377
  const selectedKeys = params.keys ? new Set(params.keys) : null;
378
+ const shouldPersist = params.persist !== false && !!params.keys?.length;
266
379
  if (result.success) {
380
+ if (shouldPersist) {
381
+ persistValidateGroupResults(params, params.keys, []);
382
+ }
267
383
  return {
268
384
  success: true,
269
385
  results: params.keys?.map((key) => ({
@@ -278,6 +394,9 @@ export function validateShapeKeys(box, params) {
278
394
  ? result.error.issues.filter((issue) => issueMatchesSelectedKeys(issue, params.path, selectedKeys))
279
395
  : result.error.issues;
280
396
  const mapped = mapZodIssues(issues);
397
+ if (shouldPersist) {
398
+ persistValidateGroupResults(params, params.keys, mapped);
399
+ }
281
400
  return {
282
401
  success: mapped.length === 0,
283
402
  results: params.keys?.map((key) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cogsbox-shape",
3
- "version": "0.5.218",
3
+ "version": "0.5.220",
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",