Skip to content

Commit 2568643

Browse files
committed
docs: update zod and svelte patterns
1 parent 60c92e1 commit 2568643

4 files changed

Lines changed: 67 additions & 23 deletions

File tree

skills/fuz-stack/references/svelte-patterns.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ non-zero). Use `$state.raw()` unless you have a specific reason not to.
4646
```typescript
4747
// $state.raw() - the default for all types
4848
let name = $state.raw(''); // primitive — no difference in behavior
49-
let ollama_response = $state.raw<OllamaShowResponse | null>(null); // object replaced wholesale
49+
let api_response = $state.raw<ApiResponse | null>(null); // object replaced wholesale
5050
let selections: ReadonlyArray<Item> = $state.raw([]); // array replaced wholesale
5151

5252
// $state() - opt-in for arrays/objects mutated in place

skills/fuz-stack/references/zod-schemas.md

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,24 @@ defaults, metadata, CLI help text, and serialization.
2323
| Foundation | `@fuzdev/fuz_util/zod.ts` | Schema introspection — extract descriptions, defaults, aliases, types, properties; unwrap wrappers; check optional/nullable/default; format values for display |
2424
| Cell helpers | `@fuzdev/zzz/zod_helpers.ts` | `Uuid`, `Datetime` branded types and factories; schema unwrapping; field extraction; validation error formatting |
2525
| CLI | `@fuzdev/fuz_app/cli/args.ts`, `help.ts` | Schema-validated CLI arg parsing; schema-driven help text generation |
26-
| HTTP | `@fuzdev/fuz_app/http/schema_helpers.ts` | `schema_to_surface()` exports JSON Schema via `z.toJSONSchema()` for snapshot-testable API surfaces |
26+
| HTTP | `@fuzdev/fuz_app/http/schema_helpers.ts` | `schema_to_surface()` exports JSON Schema via `z.toJSONSchema()` for snapshot-testable API surfaces; `instanceof` checks for schema type detection |
2727
| Testing | `@fuzdev/fuz_app/testing/schema_generators.ts` | Schema-driven test data generation — valid bodies, adversarial inputs |
2828

2929
## Core Conventions
3030

3131
1. **`z.strictObject()`** — default for all object schemas, including inside
3232
`z.discriminatedUnion()` and `z.union()`. Rejects unknown keys.
33-
**Exception**: external data (`z.looseObject()` or `z.object()` with
34-
comment explaining why).
33+
**Exceptions**: external data (`z.looseObject()` or `z.object()` with
34+
comment explaining why); response/error schemas consumed by clients
35+
(`z.looseObject()` — allows adding fields without breaking consumers).
3536
2. **PascalCase naming** — schema and inferred type share the same name.
3637
3. **`.meta({description: '...'})`** — not `.describe()`. `.meta()` supports
3738
additional keys (`aliases`, `sensitivity`).
3839
4. **`safeParse` at boundaries** — graceful errors for external input (HTTP
39-
requests, API responses). `parse` for internal assertions, config loading,
40-
CLI args, and factory functions where failure is fatal.
40+
requests, API responses). `parse` for internal assertions, CLI args, and
41+
factory functions where failure is fatal. `safeParse` + custom throw when
42+
you need better error context than `parse` provides (e.g., env loading).
43+
`safeParse` + return null for optional config files that may be absent.
4144

4245
### The Canonical Pattern
4346

@@ -73,6 +76,11 @@ const PackageJson = z.looseObject({name: z.string(), version: z.string()});
7376
// z.object: parses external GitHub API responses
7477
const GithubPullRequest = z.object({number: z.number(), title: z.string()});
7578

79+
// OK: z.looseObject for response/error schemas — clients tolerate additions
80+
// z.looseObject: error responses may carry extra context fields
81+
const ApiError = z.looseObject({error: z.string()});
82+
const TableListOutput = z.looseObject({tables: z.array(z.strictObject({name: z.string()}))});
83+
7684
// WRONG: .describe() — works but not the convention
7785
const Bar = z.string().describe('a bar');
7886

@@ -97,6 +105,10 @@ Schemas with `.default()` or `.transform()` have different input and output
97105
types. `z.infer<>` gives the output (post-parse) type. `z.input<>` gives the
98106
pre-parse type — what callers provide before defaults are applied.
99107

108+
Export `z.input<>` when callers construct partial instances via `.parse()`
109+
Cell instantiation, resource builders, config files. Skip it when the schema
110+
is only consumed internally (env loading, action spec `satisfies`).
111+
100112
This is a **systematic pattern** in zzz and tx:
101113

102114
```typescript
@@ -212,6 +224,18 @@ email: Email.nullish(), // fuz_app invite creation
212224
before: PackageCurrent.nullable().catch(null), // tx change schemas
213225
```
214226

227+
## Field-Level Validation
228+
229+
Use `.shape` to validate individual fields without parsing the whole object:
230+
231+
```typescript
232+
// zzz — validate a single field value
233+
ProviderJson.shape.name.parse(value);
234+
235+
// zzz/socket.svelte.ts — Cell field mutations via shape access
236+
SocketJson.shape.url.parse(new_url);
237+
```
238+
215239
## Transform Pipelines
216240

217241
```typescript
@@ -403,8 +427,8 @@ if (!result.success) {
403427
}
404428
c.set('validated_input', result.data);
405429

406-
// zzz/ollama.svelte.ts — external API responses
407-
const parsed = OllamaListResponse.safeParse(response);
430+
// zzz — external API responses
431+
const parsed = ApiResponse.safeParse(response);
408432
```
409433
410434
Route specs declare input/output schemas for auto-generated validation
@@ -416,12 +440,31 @@ Use `parse` when invalid data means a bug or fatal misconfiguration:
416440
417441
```typescript
418442
RoleName.parse(name); // internal assertion
419-
return TxOptions.parse(raw); // config loading
420443
const args = RunApplyArgs.parse(raw_args); // CLI args
421444
return PackageResource.parse({type: 'package', ...config}); // factory function
422445
const parsed = this.schema.parse(v); // Cell field update
423446
```
424447
448+
### safeParse with Custom Error Handling
449+
450+
`safeParse` + custom throw gives better error context than bare `parse`.
451+
`safeParse` + return null handles optional data that may be absent or invalid:
452+
453+
```typescript
454+
// fuz_app/env/load.ts — env loading: safeParse + custom error with raw values
455+
const result = schema.safeParse(raw);
456+
if (!result.success) {
457+
throw new EnvValidationError(raw, result.error);
458+
}
459+
460+
// fuz_app/cli/config.ts — optional config file: safeParse + return null
461+
const result = schema.safeParse(parsed);
462+
if (!result.success) {
463+
runtime.warn(`Invalid config.json: ${result.error.message}`);
464+
return null;
465+
}
466+
```
467+
425468
### Formatting Errors
426469
427470
```typescript
@@ -444,6 +487,7 @@ export const format_zod_validation_error = (error: z.ZodError): string =>
444487
|-----------|---------|-------|
445488
| Object schemas (internal) | `z.strictObject({...})` | `z.object({...})` |
446489
| Object schemas (external data) | `z.looseObject({...})` or `z.object({...})` with comment | `z.strictObject({...})` |
490+
| Response/error schemas | `z.looseObject({...})` — tolerates added fields | `z.strictObject({...})` |
447491
| Discriminated union members | `z.strictObject({type: z.literal('a'), ...})` | `z.object({type: z.literal('a'), ...})` |
448492
| Descriptions | `.meta({description: '...'})` | `.describe('...')` |
449493
| Schema naming | `const MyThing = z.strictObject(...)` | `const my_thing`, `const MyThingSchema` |

src/routes/libraries.json

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48405,14 +48405,14 @@
4840548405
"@fuzdev/fuz_code": "^0.45.1",
4840648406
"@fuzdev/fuz_css": "^0.58.0",
4840748407
"@fuzdev/fuz_mastodon": "^0.39.0",
48408-
"@fuzdev/fuz_ui": "^0.191.3",
48408+
"@fuzdev/fuz_ui": "^0.191.4",
4840948409
"@fuzdev/fuz_util": "^0.55.0",
48410-
"@fuzdev/gro": "^0.197.2",
48410+
"@fuzdev/gro": "^0.197.3",
4841148411
"@jridgewell/trace-mapping": "^0.3.31",
4841248412
"@ryanatkn/eslint-config": "^0.10.1",
4841348413
"@sveltejs/acorn-typescript": "^1.0.9",
4841448414
"@sveltejs/adapter-static": "^3.0.10",
48415-
"@sveltejs/kit": "^2.55.0",
48415+
"@sveltejs/kit": "^2.57.0",
4841648416
"@sveltejs/package": "^2.5.7",
4841748417
"@sveltejs/vite-plugin-svelte": "^6.2.4",
4841848418
"@types/estree": "^1.0.8",
@@ -48425,7 +48425,7 @@
4842548425
"magic-string": "^0.30.21",
4842648426
"prettier": "^3.7.4",
4842748427
"prettier-plugin-svelte": "^3.5.1",
48428-
"svelte": "^5.55.0",
48428+
"svelte": "^5.55.2",
4842948429
"svelte-check": "^4.4.5",
4843048430
"svelte2tsx": "^0.7.52",
4843148431
"tslib": "^2.8.1",
@@ -50198,14 +50198,14 @@
5019850198
"@changesets/changelog-git": "^0.2.1",
5019950199
"@fuzdev/fuz_code": "^0.45.1",
5020050200
"@fuzdev/fuz_css": "^0.58.0",
50201-
"@fuzdev/fuz_ui": "^0.191.3",
50201+
"@fuzdev/fuz_ui": "^0.191.4",
5020250202
"@fuzdev/fuz_util": "^0.55.0",
50203-
"@fuzdev/gro": "^0.197.2",
50203+
"@fuzdev/gro": "^0.197.3",
5020450204
"@jridgewell/trace-mapping": "^0.3.31",
5020550205
"@ryanatkn/eslint-config": "^0.10.1",
5020650206
"@sveltejs/acorn-typescript": "^1.0.9",
5020750207
"@sveltejs/adapter-static": "^3.0.10",
50208-
"@sveltejs/kit": "^2.55.0",
50208+
"@sveltejs/kit": "^2.57.0",
5020950209
"@sveltejs/package": "^2.5.7",
5021050210
"@sveltejs/vite-plugin-svelte": "^6.2.4",
5021150211
"@types/estree": "^1.0.8",
@@ -50217,7 +50217,7 @@
5021750217
"magic-string": "^0.30.21",
5021850218
"prettier": "^3.7.4",
5021950219
"prettier-plugin-svelte": "^3.5.1",
50220-
"svelte": "^5.55.0",
50220+
"svelte": "^5.55.2",
5022150221
"svelte-check": "^4.4.5",
5022250222
"svelte2tsx": "^0.7.52",
5022350223
"tslib": "^2.8.1",
@@ -54977,16 +54977,16 @@
5497754977
},
5497854978
"devDependencies": {
5497954979
"@changesets/changelog-git": "^0.2.1",
54980-
"@fuzdev/fuz_app": "^0.3.3",
54980+
"@fuzdev/fuz_app": "^0.4.0",
5498154981
"@fuzdev/fuz_code": "^0.45.1",
5498254982
"@fuzdev/fuz_css": "^0.58.0",
54983-
"@fuzdev/fuz_ui": "^0.191.3",
54983+
"@fuzdev/fuz_ui": "^0.191.4",
5498454984
"@fuzdev/fuz_util": "^0.55.0",
5498554985
"@jridgewell/trace-mapping": "^0.3.31",
5498654986
"@ryanatkn/eslint-config": "^0.10.1",
5498754987
"@sveltejs/acorn-typescript": "^1.0.9",
5498854988
"@sveltejs/adapter-static": "^3.0.10",
54989-
"@sveltejs/kit": "^2.55.0",
54989+
"@sveltejs/kit": "^2.57.0",
5499054990
"@sveltejs/vite-plugin-svelte": "^6.2.4",
5499154991
"@types/deno": "^2.5.0",
5499254992
"@types/estree": "^1.0.8",
@@ -54999,7 +54999,7 @@
5499954999
"ollama": "^0.6.3",
5500055000
"prettier": "^3.7.4",
5500155001
"prettier-plugin-svelte": "^3.4.1",
55002-
"svelte": "^5.55.0",
55002+
"svelte": "^5.55.2",
5500355003
"svelte-check": "^4.4.5",
5500455004
"svelte2tsx": "^0.7.52",
5500555005
"tslib": "^2.8.1",

src/routes/skills/fuz-stack/skill_data.ts

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)