@crustjs/validate 0.0.1 → 0.0.2

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,5 +1,4 @@
1
- import { AnyCommand as AnyCommand2, ValidateFlagAliases, ValidateVariadicArgs } from "@crustjs/core";
2
- import { CommandContext, CommandDef } from "@crustjs/core";
1
+ import { ArgDef, ArgsDef, FlagDef, FlagsDef } from "@crustjs/core";
3
2
  import * as z from "zod/v4/core";
4
3
  import { AnyCommand } from "@crustjs/core";
5
4
  /**
@@ -29,162 +28,162 @@ interface ValidatedContext<
29
28
  flags: Record<string, unknown>;
30
29
  };
31
30
  }
32
- /** A Zod schema used by the Zod entrypoint. */
31
+ /**
32
+ * Unique symbol used to attach a Zod schema to a core `ArgDef` or `FlagDef`.
33
+ *
34
+ * Survives `{ ...def }` spread in `defineCommand` and `Object.freeze`,
35
+ * making the schema available at runtime via `def[ZOD_SCHEMA]`.
36
+ */
37
+ declare const ZOD_SCHEMA: unique symbol;
38
+ type ZOD_SCHEMA = typeof ZOD_SCHEMA;
39
+ /** A Zod schema used by the validate/zod entrypoint. */
33
40
  type ZodSchemaLike<
34
41
  Input = unknown,
35
42
  Output = Input
36
43
  > = z.$ZodType<Output, Input>;
37
- /** Infer output type from a Zod schema. */
38
- type InferSchemaOutput<S> = S extends z.$ZodType ? z.output<S> : never;
39
- /** Optional metadata for a positional argument declared with `arg()`. */
40
- interface ArgOptions {
41
- /** Collect remaining positionals into this arg as an array. */
42
- readonly variadic?: true;
43
- }
44
+ /** CLI value type literals. */
45
+ type ValueType = "string" | "number" | "boolean";
44
46
  /**
45
- * A single positional argument spec produced by `arg()`.
47
+ * Resolve the CLI `ValueType` from a Zod schema at the type level.
46
48
  *
47
- * The `Variadic` generic parameter preserves the literal `true` from
48
- * `arg()` calls so `ValidateVariadicArgs` can distinguish variadic args
49
- * from non-variadic ones at compile time.
49
+ * Unwraps common wrappers (optional, default, nullable, pipe, catch,
50
+ * prefault, nonoptional, readonly) and maps leaf schemas to their
51
+ * primitive type string.
50
52
  *
51
- * - `ArgSpec<N, S, true>` variadic arg (only valid in last position)
52
- * - `ArgSpec<N, S, undefined>` normal positional arg
53
- * - `ArgSpec<N, S>` (default) — type-erased form used in constraints
53
+ * Falls back to `ValueType` (the union) for unrecognized schemas the
54
+ * runtime introspection always produces the correct narrow value.
54
55
  */
55
- interface ArgSpec<
56
+ type ResolveZodValueType<S> = S extends z.$ZodString ? "string" : S extends z.$ZodNumber ? "number" : S extends z.$ZodBoolean ? "boolean" : S extends z.$ZodEnum ? "string" : S extends z.$ZodLiteral<infer L> ? L extends string ? "string" : L extends number ? "number" : L extends boolean ? "boolean" : ValueType : S extends z.$ZodOptional<infer Inner> ? ResolveZodValueType<Inner> : S extends z.$ZodDefault<infer Inner> ? ResolveZodValueType<Inner> : S extends z.$ZodNullable<infer Inner> ? ResolveZodValueType<Inner> : S extends z.$ZodCatch<infer Inner> ? ResolveZodValueType<Inner> : S extends z.$ZodPrefault<infer Inner> ? ResolveZodValueType<Inner> : S extends z.$ZodNonOptional<infer Inner> ? ResolveZodValueType<Inner> : S extends z.$ZodReadonly<infer Inner> ? ResolveZodValueType<Inner> : S extends z.$ZodPipe<infer In, infer _Out> ? ResolveZodValueType<In> : ValueType;
57
+ /**
58
+ * An `ArgDef` enriched with a hidden Zod schema.
59
+ *
60
+ * The `Type` parameter is resolved from the schema at the type level via
61
+ * `ResolveZodValueType`, producing a narrow literal (e.g. `"number"`) that
62
+ * matches exactly one variant of core's discriminated `ArgDef` union.
63
+ *
64
+ * The `ZOD_SCHEMA` symbol key carries the schema for runtime validation.
65
+ */
66
+ interface ZodArgDef<
56
67
  Name extends string = string,
57
68
  Schema extends ZodSchemaLike = ZodSchemaLike,
58
- Variadic extends true | undefined = true | undefined
69
+ Variadic extends true | undefined = true | undefined,
70
+ Type extends ValueType = ResolveZodValueType<Schema>
59
71
  > {
60
- readonly kind: "arg";
61
72
  readonly name: Name;
62
- readonly schema: Schema;
73
+ readonly type: Type;
74
+ readonly description?: string;
75
+ readonly required?: true;
76
+ /**
77
+ * Non-optional so `ValidateVariadicArgs` can match `{ variadic: true }`.
78
+ * When `Variadic` is `undefined`, the runtime value is `undefined`.
79
+ */
63
80
  readonly variadic: Variadic;
64
- }
65
- /** Ordered positional argument specs. */
66
- type ArgSpecs = readonly ArgSpec[];
67
- /** Output type for one ArgSpec in the validated handler context. */
68
- type InferArgValue<S extends ArgSpec> = S["variadic"] extends true ? InferSchemaOutput<S["schema"]>[] : InferSchemaOutput<S["schema"]>;
69
- /** Flattens an intersection of objects for readable inferred types. */
70
- type Simplify<T> = { [K in keyof T] : T[K] };
71
- /** Recursively maps ordered ArgSpec entries to a named output object type. */
72
- type InferArgsFromTuple<A extends readonly ArgSpec[]> = A extends readonly [infer Head extends ArgSpec, ...infer Tail extends readonly ArgSpec[]] ? { [K in Head["name"]] : InferArgValue<Head> } & InferArgsFromTuple<Tail> : {};
73
- /** Infer validated args object type from ordered ArgSpec entries. */
74
- type InferArgsFromSpecs<A extends ArgSpecs> = Simplify<InferArgsFromTuple<A>>;
75
- /** Optional metadata for a flag declared with `flag()`. */
76
- interface FlagOptions {
77
- /** Short alias or array of aliases (e.g. `"v"` or `["v", "V"]`). */
78
- readonly alias?: string | readonly string[];
81
+ readonly [ZOD_SCHEMA]: Schema;
79
82
  }
80
83
  /**
81
- * A named flag schema wrapper produced by `flag()`.
84
+ * A `FlagDef` enriched with a hidden Zod schema.
82
85
  *
83
- * The `Alias` generic parameter preserves alias literals (e.g. `"v"` or
84
- * `readonly ["v", "V"]`) from `flag()` calls so `ValidateFlagAliases` can
85
- * detect collisions at compile time.
86
+ * The `Type` parameter is resolved from the schema at the type level.
86
87
  *
87
- * - `FlagSpec<S, "v">` flag with alias `"v"` (collision-detectable)
88
- * - `FlagSpec<S, undefined>` — flag without an alias
89
- * - `FlagSpec<S>` (default) — type-erased form used in constraints
88
+ * The `ZOD_SCHEMA` symbol key carries the schema for runtime validation.
90
89
  */
91
- interface FlagSpec<
90
+ interface ZodFlagDef<
92
91
  Schema extends ZodSchemaLike = ZodSchemaLike,
93
- Alias extends string | readonly string[] | undefined = string | readonly string[] | undefined
92
+ Alias extends string | readonly string[] | undefined = string | readonly string[] | undefined,
93
+ Type extends ValueType = ResolveZodValueType<Schema>
94
94
  > {
95
- readonly kind: "flag";
96
- readonly schema: Schema;
97
- readonly alias: Alias;
95
+ readonly type: Type;
96
+ readonly description?: string;
97
+ readonly required?: true;
98
+ /**
99
+ * Non-optional so `ValidateFlagAliases` can extract narrow alias literals.
100
+ * When `Alias` is `undefined`, the runtime value is `undefined` (no alias).
101
+ */
102
+ readonly alias: Alias extends string | readonly string[] ? Alias : undefined;
103
+ readonly [ZOD_SCHEMA]: Schema;
98
104
  }
99
- /** Allowed value shape for `flags` in `defineZodCommand()`. */
100
- type FlagShape = Record<string, ZodSchemaLike | FlagSpec>;
101
- /** Extract the schema from a flag shape value (plain schema or `flag()` wrapper). */
102
- type ExtractFlagSchema<V> = V extends FlagSpec<infer S> ? S : V extends ZodSchemaLike ? V : never;
103
- /** Infer validated flags object type from the flags shape. */
104
- type InferFlagsFromShape<F extends FlagShape> = { [K in keyof F] : InferSchemaOutput<ExtractFlagSchema<F[K]>> };
105
- /** Handler type for `defineZodCommand()` with validated/transformed context. */
106
- type ZodCommandRunHandler<
107
- ArgsOut,
108
- FlagsOut
109
- > = (context: ValidatedContext<ArgsOut, FlagsOut>) => void | Promise<void>;
110
- /** Infer args output type from command config args. */
111
- type InferArgsFromConfig<A> = A extends ArgSpecs ? InferArgsFromSpecs<A> : Record<string, never>;
112
- /** Infer flags output type from command config flags. */
113
- type InferFlagsFromConfig<F> = F extends FlagShape ? InferFlagsFromShape<F> : Record<string, never>;
105
+ /** Options for `arg()`. */
106
+ interface ArgOptions {
107
+ /** Collect remaining positionals into this arg as an array. */
108
+ readonly variadic?: true;
109
+ }
110
+ /** Options for `flag()`. */
111
+ interface FlagOptions {
112
+ /** Short alias or array of aliases (e.g. `"v"` or `["v", "V"]`). */
113
+ readonly alias?: string | readonly string[];
114
+ }
115
+ /** Infer Zod output type from a schema. */
116
+ type InferSchemaOutput<S> = S extends z.$ZodType ? z.output<S> : never;
117
+ /** Output type for a single arg: variadic `output[]`, scalar `output`. */
118
+ type InferValidatedArgValue<D> = D extends {
119
+ readonly [ZOD_SCHEMA]: infer S;
120
+ readonly variadic: true;
121
+ } ? InferSchemaOutput<S>[] : D extends {
122
+ readonly [ZOD_SCHEMA]: infer S;
123
+ } ? InferSchemaOutput<S> : never;
124
+ /** Flattens an intersection of objects for readable inferred types. */
125
+ type Simplify<T> = { [K in keyof T] : T[K] };
126
+ /** Recursively maps args tuple to a named output object. */
127
+ type InferValidatedArgsTuple<A extends readonly ArgDef[]> = A extends readonly [infer Head extends ArgDef, ...infer Tail extends readonly ArgDef[]] ? Head extends {
128
+ readonly name: infer N extends string;
129
+ } ? { [K in N] : InferValidatedArgValue<Head> } & InferValidatedArgsTuple<Tail> : InferValidatedArgsTuple<Tail> : {};
114
130
  /**
115
- * Keys from `CommandDef` that `ZodCommandDef` redefines with different types.
116
- *
117
- * - `args` / `flags`: Zod schema-based definitions replace core's `ArgsDef`/`FlagsDef`
118
- * - `run`: receives `ValidatedContext` instead of raw `CommandContext`
119
- * - `preRun` / `postRun`: use raw `CommandContext` (no `NoInfer` wrapper)
120
- *
121
- * All remaining `CommandDef` keys (e.g. `meta`, `subCommands`) are inherited
122
- * automatically via `Omit`. If a new passthrough field is added to `CommandDef`,
123
- * it propagates here without changes. The compile-time key exhaustiveness
124
- * assertion in `command.test.ts` will fail, forcing a review.
131
+ * Infer the validated args output type from an `ArgsDef` tuple
132
+ * where each element carries a `[ZOD_SCHEMA]` brand.
125
133
  */
126
- type ZodOverriddenKeys = "args" | "flags" | "run" | "preRun" | "postRun";
134
+ type InferValidatedArgs<A> = A extends readonly ArgDef[] ? Simplify<InferValidatedArgsTuple<A>> : Record<string, never>;
127
135
  /**
128
- * Config for `defineZodCommand()` using `arg()` + `flag()` schema-first DSL.
129
- *
130
- * Extends `CommandDef` (minus overridden keys) so passthrough fields like
131
- * `meta` and `subCommands` stay in sync automatically. If a new field is
132
- * added to `CommandDef`, the key exhaustiveness assertion in
133
- * `command.test.ts` fails at compile time, forcing a review.
136
+ * Infer the validated flags output type from a `FlagsDef` record
137
+ * where each value carries a `[ZOD_SCHEMA]` brand.
134
138
  */
135
- interface ZodCommandDef<
136
- A extends ArgSpecs | undefined = undefined,
137
- F extends FlagShape | undefined = undefined
138
- > extends Omit<CommandDef, ZodOverriddenKeys> {
139
- /** Ordered positional args as `arg()` specs. */
140
- readonly args?: A;
141
- /** Named flags as plain schemas or `flag()` wrappers. */
142
- readonly flags?: F;
143
- /**
144
- * Optional setup hook before schema validation runs.
145
- *
146
- * Receives raw parser output (`CommandContext`), not schema-transformed values.
147
- */
148
- readonly preRun?: (context: CommandContext) => void | Promise<void>;
149
- /** Main handler with validated/transformed args and flags. */
150
- readonly run?: ZodCommandRunHandler<InferArgsFromConfig<A>, InferFlagsFromConfig<F>>;
151
- /**
152
- * Optional teardown hook after command execution.
153
- *
154
- * Receives raw parser output (`CommandContext`), not schema-transformed values.
155
- */
156
- readonly postRun?: (context: CommandContext) => void | Promise<void>;
157
- }
139
+ type InferValidatedFlags<F> = F extends Record<string, FlagDef> ? Simplify<{ [K in keyof F] : F[K] extends {
140
+ readonly [ZOD_SCHEMA]: infer S;
141
+ } ? InferSchemaOutput<S> : never }> : Record<string, never>;
142
+ /** Check that every arg in a tuple carries the `[ZOD_SCHEMA]` brand. */
143
+ type AllArgsHaveSchema<A extends ArgsDef> = A extends readonly [infer Head, ...infer Tail extends readonly ArgDef[]] ? Head extends {
144
+ readonly [ZOD_SCHEMA]: unknown;
145
+ } ? AllArgsHaveSchema<Tail> : false : true;
146
+ /** Check that every flag in a record carries the `[ZOD_SCHEMA]` brand. */
147
+ type AllFlagsHaveSchema<F extends FlagsDef> = string extends keyof F ? true : { [K in keyof F] : F[K] extends {
148
+ readonly [ZOD_SCHEMA]: unknown;
149
+ } ? true : false }[keyof F] extends true ? true : false;
158
150
  /**
159
- * Define a Crust command where schemas are the source of truth.
160
- *
161
- * Positional args are declared with `arg(name, schema)` in an ordered array,
162
- * flags are declared as plain schemas or `flag(schema, meta)` wrappers.
163
- *
164
- * The factory generates Crust parser/help definitions and runs schema
165
- * validation after parsing but before user handler execution.
151
+ * Resolves to `true` only when all args and flags carry schema metadata.
152
+ * Used by `withZod` to enforce strict mode at compile time.
153
+ */
154
+ type HasAllSchemas<
155
+ A extends ArgsDef,
156
+ F extends FlagsDef
157
+ > = AllArgsHaveSchema<A> extends true ? AllFlagsHaveSchema<F> extends true ? true : false : false;
158
+ /**
159
+ * The validated handler type for `withZod()`.
166
160
  *
167
- * Compile-time validation (via intersection branding) catches:
168
- * - Variadic args that aren't in the last position
169
- * - Flag alias collisions (alias→name or alias→alias)
161
+ * When all args/flags carry schema metadata, resolves to a typed handler
162
+ * receiving `ValidatedContext`. Otherwise resolves to `never`, causing
163
+ * a compile error at the call site.
170
164
  */
171
- declare function defineZodCommand<
172
- const A extends readonly ArgSpec[] | undefined,
173
- const F extends FlagShape | undefined
174
- >(config: ZodCommandDef<A, F> & {
175
- args?: A extends readonly object[] ? ValidateVariadicArgs<A> : A;
176
- flags?: F extends Record<string, unknown> ? ValidateFlagAliases<F> : F;
177
- }): AnyCommand2;
165
+ type WithZodHandler<
166
+ A extends ArgsDef,
167
+ F extends FlagsDef
168
+ > = HasAllSchemas<A, F> extends true ? (context: ValidatedContext<InferValidatedArgs<A>, InferValidatedFlags<F>>) => void | Promise<void> : never;
178
169
  /**
179
- * Define a named positional argument schema for `defineZodCommand()`.
170
+ * Define a named positional argument from a Zod schema.
171
+ *
172
+ * Returns a core `ArgDef` (accepted by `defineCommand`) enriched with hidden
173
+ * schema metadata (via `[ZOD_SCHEMA]` symbol) for runtime validation by `withZod`.
180
174
  *
181
- * The `const Variadic` parameter preserves `{ variadic: true }` as a
182
- * literal in the return type, enabling compile-time variadic position
183
- * validation in `defineZodCommand()`.
175
+ * CLI metadata (`type`, `required`, `description`, `variadic`) is derived
176
+ * from the schema automatically single source of truth.
184
177
  *
185
178
  * @param name - Positional arg name used in parser output and help text
186
- * @param schema - Zod schema
187
- * @param options - Optional CLI metadata (description, variadic)
179
+ * @param schema - Zod schema (source of truth for type/optionality/description)
180
+ * @param options - Optional CLI metadata (`variadic`)
181
+ *
182
+ * @example
183
+ * ```ts
184
+ * arg("port", z.number().int().min(1).describe("Port to listen on"))
185
+ * arg("files", z.string(), { variadic: true })
186
+ * ```
188
187
  */
189
188
  declare function arg<
190
189
  Name extends string,
@@ -192,21 +191,64 @@ declare function arg<
192
191
  const Variadic extends true | undefined = undefined
193
192
  >(name: Name, schema: Schema, options?: ArgOptions & {
194
193
  variadic?: Variadic;
195
- }): ArgSpec<Name, Schema, Variadic>;
194
+ }): ZodArgDef<Name, Schema, Variadic>;
196
195
  /**
197
- * Define a flag schema for `defineZodCommand()` with optional alias/description.
196
+ * Define a flag from a Zod schema with optional alias.
197
+ *
198
+ * Returns a core `FlagDef` (accepted by `defineCommand`) enriched with hidden
199
+ * schema metadata (via `[ZOD_SCHEMA]` symbol) for runtime validation by `withZod`.
200
+ *
201
+ * CLI metadata (`type`, `multiple`, `required`, `description`) is derived
202
+ * from the schema automatically — single source of truth.
198
203
  *
199
- * The `const Alias` parameter preserves alias literals (e.g. `"v"` or
200
- * `readonly ["v", "V"]`) in the return type, enabling compile-time alias
201
- * collision detection in `defineZodCommand()`.
204
+ * @param schema - Zod schema (source of truth for type/optionality/description)
205
+ * @param options - Optional flag metadata (`alias`)
202
206
  *
203
- * @param schema - Zod schema
204
- * @param options - Optional flag metadata
207
+ * @example
208
+ * ```ts
209
+ * flag(z.boolean().default(false).describe("Enable verbose logging"), { alias: "v" })
210
+ * flag(z.enum(["json", "text"]).default("text"))
211
+ * ```
205
212
  */
206
213
  declare function flag<
207
214
  Schema extends ZodSchemaLike,
208
215
  const Alias extends string | readonly string[] | undefined = undefined
209
216
  >(schema: Schema, options?: FlagOptions & {
210
217
  alias?: Alias;
211
- }): FlagSpec<Schema, Alias>;
212
- export { flag, defineZodCommand, arg, ZodSchemaLike, ZodCommandRunHandler, ZodCommandDef, InferSchemaOutput, InferFlagsFromShape, InferFlagsFromConfig, InferArgsFromSpecs, InferArgsFromConfig, FlagSpec, FlagShape, FlagOptions, ArgSpecs, ArgSpec, ArgOptions };
218
+ }): ZodFlagDef<Schema, Alias>;
219
+ import { ArgsDef as ArgsDef2, CommandContext, FlagsDef as FlagsDef2 } from "@crustjs/core";
220
+ /**
221
+ * Create a validated `run` handler for `defineCommand`.
222
+ *
223
+ * Reads Zod schemas from the command's `arg()` / `flag()` definitions,
224
+ * validates parsed CLI input against them, and calls `handler` with
225
+ * the transformed, fully-typed result.
226
+ *
227
+ * **Strict mode**: all args and flags in the command must be created with
228
+ * `arg()` / `flag()` from `@crustjs/validate/zod`. Plain core defs cause
229
+ * a compile-time error (handler parameter becomes `never`).
230
+ *
231
+ * @param handler - Receives `ValidatedContext` with typed args/flags after validation
232
+ * @returns A `run` function compatible with `defineCommand`
233
+ *
234
+ * @example
235
+ * ```ts
236
+ * import { defineCommand } from "@crustjs/core";
237
+ * import { z } from "zod";
238
+ * import { arg, flag, withZod } from "@crustjs/validate/zod";
239
+ *
240
+ * const serve = defineCommand({
241
+ * meta: { name: "serve" },
242
+ * args: [arg("port", z.number().min(1))],
243
+ * flags: { verbose: flag(z.boolean().default(false), { alias: "v" }) },
244
+ * run: withZod(({ args, flags }) => {
245
+ * // args.port: number, flags.verbose: boolean
246
+ * }),
247
+ * });
248
+ * ```
249
+ */
250
+ declare function withZod<
251
+ A extends ArgsDef2 = ArgsDef2,
252
+ F extends FlagsDef2 = FlagsDef2
253
+ >(handler: WithZodHandler<A, F>): (context: CommandContext<A, F>) => Promise<void>;
254
+ export { withZod, flag, arg, ZodSchemaLike, ZodFlagDef, ZodArgDef, WithZodHandler, InferValidatedFlags, InferValidatedArgs, InferSchemaOutput, FlagOptions, ArgOptions };
package/dist/zod/index.js CHANGED
@@ -1,92 +1,19 @@
1
1
  // @bun
2
2
  import {
3
- buildArgDefinitions,
4
- buildFlagDefinitions,
5
- buildRunHandler,
3
+ buildValidatedRunner,
6
4
  normalizeIssues
7
- } from "../shared/chunk-zfhm7pmv.js";
5
+ } from "../shared/chunk-p8pzhj6c.js";
8
6
 
9
- // src/zod/command.ts
10
- import { defineCommand } from "@crustjs/core";
11
- import { safeParseAsync } from "zod/v4/core";
7
+ // src/zod/schema.ts
8
+ import { CrustError } from "@crustjs/core";
12
9
 
13
- // src/zod/definitions.ts
14
- import { CrustError as CrustError2 } from "@crustjs/core";
10
+ // src/zod/types.ts
11
+ var ZOD_SCHEMA = Symbol.for("crustjs.validate.zod");
15
12
 
16
13
  // src/zod/schema.ts
17
- import { CrustError } from "@crustjs/core";
18
14
  function isZodSchema(value) {
19
- if (typeof value !== "object" || value === null) {
20
- return false;
21
- }
22
- if (!("_zod" in value)) {
23
- return false;
24
- }
25
- return true;
26
- }
27
- function arg(name, schema, options) {
28
- if (!name.trim()) {
29
- throw new CrustError("DEFINITION", "arg(): name is required and must be a non-empty string");
30
- }
31
- if (!isZodSchema(schema)) {
32
- throw new CrustError("DEFINITION", `arg("${name}"): schema must be a Zod schema`);
33
- }
34
- return {
35
- kind: "arg",
36
- name,
37
- schema,
38
- variadic: options?.variadic
39
- };
40
- }
41
- function flag(schema, options) {
42
- if (!isZodSchema(schema)) {
43
- throw new CrustError("DEFINITION", "flag(): schema must be a Zod schema");
44
- }
45
- return {
46
- kind: "flag",
47
- schema,
48
- alias: options?.alias
49
- };
50
- }
51
- function resolveDescription(schema) {
52
- let current = schema;
53
- const seen = new Set;
54
- for (;; ) {
55
- if (current === undefined || current === null || seen.has(current)) {
56
- return;
57
- }
58
- seen.add(current);
59
- if (typeof current !== "object") {
60
- return;
61
- }
62
- if ("description" in current && typeof current.description === "string") {
63
- return current.description;
64
- }
65
- const type = "type" in current ? current.type : undefined;
66
- if (typeof type !== "string") {
67
- return;
68
- }
69
- if (type === "pipe" || type === "transform") {
70
- const input = current.in;
71
- if (input !== undefined) {
72
- current = input;
73
- continue;
74
- }
75
- return;
76
- }
77
- if (type === "optional" || type === "nullable" || type === "default" || type === "prefault" || type === "nonoptional" || type === "readonly" || type === "catch") {
78
- const unwrap = current.unwrap;
79
- if (typeof unwrap === "function") {
80
- current = unwrap();
81
- continue;
82
- }
83
- return;
84
- }
85
- return;
86
- }
15
+ return typeof value === "object" && value !== null && "_zod" in value;
87
16
  }
88
-
89
- // src/zod/definitions.ts
90
17
  function asRuntimeSchema(value) {
91
18
  if (typeof value !== "object" || value === null) {
92
19
  return;
@@ -102,7 +29,12 @@ function getSchemaType(schema) {
102
29
  }
103
30
  function unwrapInputSchema(schema) {
104
31
  let current = schema;
32
+ const seen = new Set;
105
33
  for (;; ) {
34
+ if (seen.has(current)) {
35
+ return current;
36
+ }
37
+ seen.add(current);
106
38
  const type = getSchemaType(current);
107
39
  const runtime = asRuntimeSchema(current);
108
40
  if (!type || !runtime) {
@@ -159,24 +91,29 @@ function resolveInputShape(schema, label) {
159
91
  if (getSchemaType(inputSchema) === "array") {
160
92
  const runtime = asRuntimeSchema(inputSchema);
161
93
  if (typeof runtime?.unwrap !== "function") {
162
- throw new CrustError2("DEFINITION", `${label}: unable to inspect array element schema`);
94
+ throw new CrustError("DEFINITION", `${label}: unable to inspect array element schema`);
163
95
  }
164
96
  const elementSchema = unwrapInputSchema(runtime.unwrap());
165
97
  const primitive2 = resolvePrimitiveInputType(elementSchema);
166
98
  if (primitive2) {
167
99
  return { type: primitive2, multiple: true };
168
100
  }
169
- throw new CrustError2("DEFINITION", `${label}: array element type must be string, number, or boolean`);
101
+ throw new CrustError("DEFINITION", `${label}: array element type must be string, number, or boolean`);
170
102
  }
171
103
  const primitive = resolvePrimitiveInputType(inputSchema);
172
104
  if (primitive) {
173
105
  return { type: primitive, multiple: false };
174
106
  }
175
- throw new CrustError2("DEFINITION", `${label}: unsupported schema type for CLI parsing. Use string, number, boolean, enum/literal, or array of these.`);
107
+ throw new CrustError("DEFINITION", `${label}: unsupported schema type for CLI parsing. Use string, number, boolean, enum/literal, or array of these.`);
176
108
  }
177
109
  function isOptionalInputSchema(schema) {
178
110
  let current = schema;
111
+ const seen = new Set;
179
112
  for (;; ) {
113
+ if (seen.has(current)) {
114
+ return false;
115
+ }
116
+ seen.add(current);
180
117
  const type = getSchemaType(current);
181
118
  const runtime = asRuntimeSchema(current);
182
119
  if (!type || !runtime) {
@@ -202,21 +139,90 @@ function isOptionalInputSchema(schema) {
202
139
  return false;
203
140
  }
204
141
  }
205
- var zodAdapter = {
206
- resolveInputShape,
207
- isOptionalInputSchema,
208
- resolveDescription,
209
- commandLabel: "defineZodCommand",
210
- arrayHint: "z.array(...)"
211
- };
212
- function argsToDefinitions(args) {
213
- return buildArgDefinitions(args, zodAdapter);
142
+ function resolveDescription(schema) {
143
+ let current = schema;
144
+ const seen = new Set;
145
+ for (;; ) {
146
+ if (current === undefined || current === null || seen.has(current)) {
147
+ return;
148
+ }
149
+ seen.add(current);
150
+ if (typeof current !== "object") {
151
+ return;
152
+ }
153
+ if ("description" in current && typeof current.description === "string") {
154
+ return current.description;
155
+ }
156
+ const type = "type" in current ? current.type : undefined;
157
+ if (typeof type !== "string") {
158
+ return;
159
+ }
160
+ if (type === "pipe" || type === "transform") {
161
+ const input = current.in;
162
+ if (input !== undefined) {
163
+ current = input;
164
+ continue;
165
+ }
166
+ return;
167
+ }
168
+ if (type === "optional" || type === "nullable" || type === "default" || type === "prefault" || type === "nonoptional" || type === "readonly" || type === "catch") {
169
+ const unwrap = current.unwrap;
170
+ if (typeof unwrap === "function") {
171
+ current = unwrap();
172
+ continue;
173
+ }
174
+ return;
175
+ }
176
+ return;
177
+ }
214
178
  }
215
- function flagsToDefinitions(flags) {
216
- return buildFlagDefinitions(flags, zodAdapter);
179
+ function arg(name, schema, options) {
180
+ if (!name.trim()) {
181
+ throw new CrustError("DEFINITION", "arg(): name is required and must be a non-empty string");
182
+ }
183
+ if (!isZodSchema(schema)) {
184
+ throw new CrustError("DEFINITION", `arg("${name}"): schema must be a Zod schema`);
185
+ }
186
+ const shape = resolveInputShape(schema, `arg "${name}"`);
187
+ const variadic = options?.variadic;
188
+ if (variadic && shape.multiple) {
189
+ throw new CrustError("DEFINITION", `arg "${name}": variadic args must use a scalar schema; do not wrap the schema in z.array(...)`);
190
+ }
191
+ if (!variadic && shape.multiple) {
192
+ throw new CrustError("DEFINITION", `arg "${name}": array schema requires { variadic: true }`);
193
+ }
194
+ const description = resolveDescription(schema);
195
+ const required = !isOptionalInputSchema(schema);
196
+ const def = {
197
+ name,
198
+ type: shape.type,
199
+ ...description !== undefined && { description },
200
+ variadic,
201
+ ...required && { required: true },
202
+ [ZOD_SCHEMA]: schema
203
+ };
204
+ return def;
217
205
  }
218
-
219
- // src/zod/command.ts
206
+ function flag(schema, options) {
207
+ if (!isZodSchema(schema)) {
208
+ throw new CrustError("DEFINITION", "flag(): schema must be a Zod schema");
209
+ }
210
+ const shape = resolveInputShape(schema, "flag");
211
+ const required = !isOptionalInputSchema(schema);
212
+ const description = resolveDescription(schema);
213
+ const alias = options?.alias === undefined ? undefined : typeof options.alias === "string" ? options.alias : [...options.alias];
214
+ const def = {
215
+ type: shape.type,
216
+ ...shape.multiple && { multiple: true },
217
+ alias,
218
+ ...description !== undefined && { description },
219
+ ...required && { required: true },
220
+ [ZOD_SCHEMA]: schema
221
+ };
222
+ return def;
223
+ }
224
+ // src/zod/withZod.ts
225
+ import { safeParseAsync } from "zod/v4/core";
220
226
  async function validateValue(schema, value, prefix) {
221
227
  const parseResult = await safeParseAsync(schema, value);
222
228
  if (parseResult.success) {
@@ -228,28 +234,11 @@ async function validateValue(schema, value, prefix) {
228
234
  }));
229
235
  return { ok: false, issues: normalizeIssues(prefixed) };
230
236
  }
231
- function defineZodCommand(config) {
232
- const {
233
- args: zodArgs,
234
- flags: zodFlags,
235
- run: userRun,
236
- ...passthrough
237
- } = config;
238
- const argSpecs = zodArgs ?? [];
239
- const generatedArgs = argsToDefinitions(argSpecs);
240
- const generatedFlags = flagsToDefinitions(zodFlags);
241
- const command = defineCommand({
242
- ...passthrough,
243
- ...generatedArgs.length > 0 && { args: generatedArgs },
244
- ...Object.keys(generatedFlags).length > 0 && { flags: generatedFlags },
245
- ...userRun && {
246
- run: buildRunHandler(argSpecs, zodFlags, userRun, validateValue)
247
- }
248
- });
249
- return command;
237
+ function withZod(handler) {
238
+ return buildValidatedRunner(handler, validateValue, ZOD_SCHEMA, "withZod");
250
239
  }
251
240
  export {
241
+ withZod,
252
242
  flag,
253
- defineZodCommand,
254
243
  arg
255
244
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crustjs/validate",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Validation helpers for the Crust CLI framework",
5
5
  "type": "module",
6
6
  "license": "MIT",