@outfitter/contracts 0.2.0 → 0.4.0

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.
Files changed (44) hide show
  1. package/README.md +41 -2
  2. package/dist/actions.d.ts +4 -3
  3. package/dist/assert/index.d.ts +2 -2
  4. package/dist/assert/index.js +2 -2
  5. package/dist/context.d.ts +4 -3
  6. package/dist/envelope.d.ts +2 -2
  7. package/dist/envelope.js +4 -4
  8. package/dist/errors.d.ts +2 -2
  9. package/dist/errors.js +4 -2
  10. package/dist/handler.d.ts +3 -2
  11. package/dist/index.d.ts +15 -13
  12. package/dist/index.js +43 -33
  13. package/dist/logging.d.ts +2 -0
  14. package/dist/logging.js +7 -0
  15. package/dist/recovery.d.ts +2 -2
  16. package/dist/resilience.d.ts +2 -2
  17. package/dist/resilience.js +2 -2
  18. package/dist/schema.d.ts +2 -0
  19. package/dist/schema.js +7 -0
  20. package/dist/serialization.d.ts +2 -2
  21. package/dist/serialization.js +2 -2
  22. package/dist/shared/@outfitter/{contracts-k2jdpbb6.js → contracts-0snpmkdt.js} +1 -1
  23. package/dist/shared/@outfitter/{contracts-jbhbyhtt.d.ts → contracts-18vcxecr.d.ts} +1 -1
  24. package/dist/shared/@outfitter/{contracts-t68dwjbk.d.ts → contracts-25bkj17f.d.ts} +2 -1
  25. package/dist/shared/@outfitter/contracts-2g8r01zf.d.ts +73 -0
  26. package/dist/shared/@outfitter/{contracts-agvdgx9j.js → contracts-5k6q4n48.js} +41 -9
  27. package/dist/shared/@outfitter/{contracts-n9hb6hqt.d.ts → contracts-6j6z9dsd.d.ts} +1 -1
  28. package/dist/shared/@outfitter/{contracts-r64j5rsm.d.ts → contracts-bdwg55c5.d.ts} +1 -1
  29. package/dist/shared/@outfitter/{contracts-rtsa2s0f.js → contracts-btg89x4h.js} +2 -2
  30. package/dist/shared/@outfitter/{contracts-2f3khqcc.js → contracts-cp5c6dws.js} +1 -1
  31. package/dist/shared/@outfitter/{contracts-md2mvvyt.d.ts → contracts-evxky148.d.ts} +1 -1
  32. package/dist/shared/@outfitter/contracts-hbbxbwkt.d.ts +60 -0
  33. package/dist/shared/@outfitter/{contracts-6atxbfag.d.ts → contracts-j08e95jw.d.ts} +1 -1
  34. package/dist/shared/@outfitter/{contracts-k0rhaye3.d.ts → contracts-jggbn5tn.d.ts} +1 -1
  35. package/dist/shared/@outfitter/{contracts-0cj49e1a.js → contracts-phjhz5q3.js} +26 -1
  36. package/dist/shared/@outfitter/{contracts-pdk7s4t8.js → contracts-r21yet6j.js} +1 -1
  37. package/dist/shared/@outfitter/{contracts-5hr9pdcq.d.ts → contracts-r35bn9p6.d.ts} +62 -3
  38. package/dist/shared/@outfitter/{contracts-zyanj0gf.d.ts → contracts-sf1z80yc.d.ts} +2 -2
  39. package/dist/shared/@outfitter/contracts-sm6vak1a.js +14 -0
  40. package/dist/shared/@outfitter/{contracts-amd2ykn8.d.ts → contracts-ss9vjjft.d.ts} +3 -43
  41. package/dist/shared/@outfitter/contracts-wfht4q2b.js +341 -0
  42. package/dist/validation.d.ts +2 -2
  43. package/dist/validation.js +2 -2
  44. package/package.json +56 -44
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  AssertionError
4
- } from "./contracts-0cj49e1a.js";
4
+ } from "./contracts-phjhz5q3.js";
5
5
 
6
6
  // packages/contracts/src/assert/index.ts
7
7
  import { Result } from "better-result";
@@ -1,4 +1,4 @@
1
- import { ErrorCategory } from "./contracts-5hr9pdcq";
1
+ import { ErrorCategory } from "./contracts-r35bn9p6";
2
2
  /**
3
3
  * Backoff strategy configuration options
4
4
  */
@@ -0,0 +1,60 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * JSON Schema representation.
4
+ */
5
+ interface JsonSchema {
6
+ type?: string;
7
+ properties?: Record<string, JsonSchema>;
8
+ required?: string[];
9
+ items?: JsonSchema | JsonSchema[];
10
+ description?: string;
11
+ default?: unknown;
12
+ minimum?: number;
13
+ maximum?: number;
14
+ exclusiveMinimum?: number;
15
+ exclusiveMaximum?: number;
16
+ minLength?: number;
17
+ maxLength?: number;
18
+ pattern?: string;
19
+ format?: string;
20
+ enum?: unknown[];
21
+ const?: unknown;
22
+ anyOf?: JsonSchema[];
23
+ oneOf?: JsonSchema[];
24
+ allOf?: JsonSchema[];
25
+ not?: JsonSchema | Record<string, never>;
26
+ $ref?: string;
27
+ $schema?: string;
28
+ $defs?: Record<string, JsonSchema>;
29
+ definitions?: Record<string, JsonSchema>;
30
+ additionalProperties?: boolean | JsonSchema;
31
+ }
32
+ /**
33
+ * Convert a Zod schema to JSON Schema format.
34
+ *
35
+ * This is a simplified converter that handles common Zod types.
36
+ * For complex schemas, consider using a full zod-to-json-schema library.
37
+ *
38
+ * @param schema - Zod schema to convert
39
+ * @returns JSON Schema representation
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * const zodSchema = z.object({
44
+ * name: z.string(),
45
+ * age: z.number().optional(),
46
+ * });
47
+ *
48
+ * const jsonSchema = zodToJsonSchema(zodSchema);
49
+ * // {
50
+ * // type: "object",
51
+ * // properties: {
52
+ * // name: { type: "string" },
53
+ * // age: { type: "number" },
54
+ * // },
55
+ * // required: ["name"],
56
+ * // }
57
+ * ```
58
+ */
59
+ declare function zodToJsonSchema(schema: z.ZodType<unknown>): JsonSchema;
60
+ export { JsonSchema, zodToJsonSchema };
@@ -1,4 +1,4 @@
1
- import { OutfitterError, SerializedError, ValidationError } from "./contracts-5hr9pdcq";
1
+ import { OutfitterError, SerializedError, ValidationError } from "./contracts-r35bn9p6";
2
2
  import { Result } from "better-result";
3
3
  import { z } from "zod";
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { OutfitterError, SerializedError } from "./contracts-5hr9pdcq";
1
+ import { OutfitterError, SerializedError } from "./contracts-r35bn9p6";
2
2
  import { Result } from "better-result";
3
3
  /**
4
4
  * Metadata attached to every response envelope.
@@ -80,6 +80,7 @@ var ValidationErrorBase = TaggedError("ValidationError")();
80
80
  var AmbiguousErrorBase = TaggedError("AmbiguousError")();
81
81
  var AssertionErrorBase = TaggedError("AssertionError")();
82
82
  var NotFoundErrorBase = TaggedError("NotFoundError")();
83
+ var AlreadyExistsErrorBase = TaggedError("AlreadyExistsError")();
83
84
  var ConflictErrorBase = TaggedError("ConflictError")();
84
85
  var PermissionErrorBase = TaggedError("PermissionError")();
85
86
  var TimeoutErrorBase = TaggedError("TimeoutError")();
@@ -98,6 +99,12 @@ class ValidationError extends ValidationErrorBase {
98
99
  ...context != null && { context }
99
100
  });
100
101
  }
102
+ static fromMessage(message, context) {
103
+ return new ValidationError({
104
+ message,
105
+ ...context != null && { context }
106
+ });
107
+ }
101
108
  exitCode() {
102
109
  return getExitCode(this.category);
103
110
  }
@@ -151,6 +158,24 @@ class NotFoundError extends NotFoundErrorBase {
151
158
  }
152
159
  }
153
160
 
161
+ class AlreadyExistsError extends AlreadyExistsErrorBase {
162
+ category = "conflict";
163
+ static create(resourceType, resourceId, context) {
164
+ return new AlreadyExistsError({
165
+ message: `${resourceType} already exists: ${resourceId}`,
166
+ resourceType,
167
+ resourceId,
168
+ ...context != null && { context }
169
+ });
170
+ }
171
+ exitCode() {
172
+ return getExitCode(this.category);
173
+ }
174
+ statusCode() {
175
+ return getStatusCode(this.category);
176
+ }
177
+ }
178
+
154
179
  class ConflictError extends ConflictErrorBase {
155
180
  category = "conflict";
156
181
  static create(message, context) {
@@ -265,4 +290,4 @@ class CancelledError extends CancelledErrorBase {
265
290
  }
266
291
  }
267
292
 
268
- export { exitCodeMap, statusCodeMap, ERROR_CODES, getExitCode, getStatusCode, ValidationError, AmbiguousError, AssertionError, NotFoundError, ConflictError, PermissionError, TimeoutError, RateLimitError, NetworkError, InternalError, AuthError, CancelledError };
293
+ export { exitCodeMap, statusCodeMap, ERROR_CODES, getExitCode, getStatusCode, ValidationError, AmbiguousError, AssertionError, NotFoundError, AlreadyExistsError, ConflictError, PermissionError, TimeoutError, RateLimitError, NetworkError, InternalError, AuthError, CancelledError };
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  TimeoutError
4
- } from "./contracts-0cj49e1a.js";
4
+ } from "./contracts-phjhz5q3.js";
5
5
 
6
6
  // packages/contracts/src/resilience.ts
7
7
  import { Result } from "better-result";
@@ -129,6 +129,12 @@ declare const NotFoundErrorBase: TaggedErrorClass<"NotFoundError", {
129
129
  resourceId: string;
130
130
  context?: Record<string, unknown>;
131
131
  }>;
132
+ declare const AlreadyExistsErrorBase: TaggedErrorClass<"AlreadyExistsError", {
133
+ message: string;
134
+ resourceType: string;
135
+ resourceId: string;
136
+ context?: Record<string, unknown>;
137
+ }>;
132
138
  declare const ConflictErrorBase: TaggedErrorClass<"ConflictError", {
133
139
  message: string;
134
140
  context?: Record<string, unknown>;
@@ -178,6 +184,16 @@ declare class ValidationError extends ValidationErrorBase {
178
184
  readonly category: "validation";
179
185
  /** Create a ValidationError with auto-generated message from field name. */
180
186
  static create(field: string, reason: string, context?: Record<string, unknown>): ValidationError;
187
+ /**
188
+ * Create a freeform ValidationError without a specific field.
189
+ *
190
+ * Use when the validation failure applies to the input as a whole
191
+ * rather than a single field (e.g., "Invalid pipeline configuration").
192
+ *
193
+ * @param message - Human-readable validation error message
194
+ * @param context - Optional structured context for debugging
195
+ */
196
+ static fromMessage(message: string, context?: Record<string, unknown>): ValidationError;
181
197
  exitCode(): number;
182
198
  statusCode(): number;
183
199
  }
@@ -256,12 +272,55 @@ declare class NotFoundError extends NotFoundErrorBase {
256
272
  statusCode(): number;
257
273
  }
258
274
  /**
259
- * State conflict (optimistic locking, concurrent modification).
275
+ * Resource already exists the inverse of {@link NotFoundError}.
276
+ *
277
+ * Use when a create/write operation fails because the target resource
278
+ * is already present. Carries `resourceType` and `resourceId` to identify
279
+ * what already exists, mirroring {@link NotFoundError}'s structure.
280
+ *
281
+ * Maps to HTTP 409 (Conflict) and exit code 3.
282
+ *
283
+ * @example
284
+ * ```typescript
285
+ * new AlreadyExistsError({
286
+ * message: "File already exists: notes/meeting.md",
287
+ * resourceType: "file",
288
+ * resourceId: "notes/meeting.md",
289
+ * });
290
+ * AlreadyExistsError.create("file", "notes/meeting.md");
291
+ * ```
292
+ *
293
+ * @see ConflictError - For general state conflicts (version mismatch, concurrent modification)
294
+ * @see NotFoundError - The inverse: resource does not exist
295
+ */
296
+ declare class AlreadyExistsError extends AlreadyExistsErrorBase {
297
+ readonly category: "conflict";
298
+ /** Create an AlreadyExistsError with auto-generated message. */
299
+ static create(resourceType: string, resourceId: string, context?: Record<string, unknown>): AlreadyExistsError;
300
+ exitCode(): number;
301
+ statusCode(): number;
302
+ }
303
+ /**
304
+ * State conflict (version mismatch, concurrent modification).
305
+ *
306
+ * Use for general conflicts that don't fit {@link AlreadyExistsError}:
307
+ * optimistic locking failures, concurrent writes, ETag mismatches,
308
+ * or any case where the operation can't proceed due to state divergence.
309
+ *
310
+ * Maps to HTTP 409 (Conflict) and exit code 3.
311
+ *
312
+ * **Choosing the right conflict error:**
313
+ * - Resource already exists? Use {@link AlreadyExistsError}
314
+ * - Version/ETag mismatch? Use {@link ConflictError}
315
+ * - Concurrent modification detected? Use {@link ConflictError}
260
316
  *
261
317
  * @example
262
318
  * ```typescript
263
319
  * new ConflictError({ message: "Resource was modified by another process" });
320
+ * ConflictError.create("ETag mismatch: expected abc, got def");
264
321
  * ```
322
+ *
323
+ * @see AlreadyExistsError - For "resource already exists" specifically
265
324
  */
266
325
  declare class ConflictError extends ConflictErrorBase {
267
326
  readonly category: "conflict";
@@ -378,10 +437,10 @@ declare class CancelledError extends CancelledErrorBase {
378
437
  /**
379
438
  * Union type of all concrete error class instances.
380
439
  */
381
- type AnyKitError = InstanceType<typeof ValidationError> | InstanceType<typeof AmbiguousError> | InstanceType<typeof AssertionError> | InstanceType<typeof NotFoundError> | InstanceType<typeof ConflictError> | InstanceType<typeof PermissionError> | InstanceType<typeof TimeoutError> | InstanceType<typeof RateLimitError> | InstanceType<typeof NetworkError> | InstanceType<typeof InternalError> | InstanceType<typeof AuthError> | InstanceType<typeof CancelledError>;
440
+ type AnyKitError = InstanceType<typeof ValidationError> | InstanceType<typeof AmbiguousError> | InstanceType<typeof AssertionError> | InstanceType<typeof NotFoundError> | InstanceType<typeof AlreadyExistsError> | InstanceType<typeof ConflictError> | InstanceType<typeof PermissionError> | InstanceType<typeof TimeoutError> | InstanceType<typeof RateLimitError> | InstanceType<typeof NetworkError> | InstanceType<typeof InternalError> | InstanceType<typeof AuthError> | InstanceType<typeof CancelledError>;
382
441
  /**
383
442
  * Type alias for backwards compatibility with handler signatures.
384
443
  * Use AnyKitError for the union type.
385
444
  */
386
445
  type OutfitterError = AnyKitError;
387
- export { ErrorCategory, exitCodeMap, statusCodeMap, ERROR_CODES, ErrorCode, SerializedError, KitErrorProps, getExitCode, getStatusCode, ValidationError, AmbiguousError, AssertionError, NotFoundError, ConflictError, PermissionError, TimeoutError, RateLimitError, NetworkError, InternalError, AuthError, CancelledError, AnyKitError, OutfitterError };
446
+ export { ErrorCategory, exitCodeMap, statusCodeMap, ERROR_CODES, ErrorCode, SerializedError, KitErrorProps, getExitCode, getStatusCode, ValidationError, AmbiguousError, AssertionError, NotFoundError, AlreadyExistsError, ConflictError, PermissionError, TimeoutError, RateLimitError, NetworkError, InternalError, AuthError, CancelledError, AnyKitError, OutfitterError };
@@ -1,5 +1,5 @@
1
- import { Handler, SyncHandler } from "./contracts-amd2ykn8";
2
- import { OutfitterError } from "./contracts-5hr9pdcq";
1
+ import { Handler, SyncHandler } from "./contracts-ss9vjjft";
2
+ import { OutfitterError } from "./contracts-r35bn9p6";
3
3
  import { z } from "zod";
4
4
  declare const ACTION_SURFACES: readonly ["cli", "mcp", "api", "server"];
5
5
  type ActionSurface = (typeof ACTION_SURFACES)[number];
@@ -0,0 +1,14 @@
1
+ // @bun
2
+ // packages/contracts/src/logging.ts
3
+ function createLoggerFactory(adapter) {
4
+ return {
5
+ createLogger(config) {
6
+ return adapter.createLogger(config);
7
+ },
8
+ async flush() {
9
+ await adapter.flush?.();
10
+ }
11
+ };
12
+ }
13
+
14
+ export { createLoggerFactory };
@@ -1,47 +1,7 @@
1
- import { OutfitterError } from "./contracts-5hr9pdcq";
1
+ import { OutfitterError } from "./contracts-r35bn9p6";
2
+ import { Logger } from "./contracts-2g8r01zf";
2
3
  import { Result } from "better-result";
3
4
  /**
4
- * Logger interface for handler context.
5
- * Implementations provided by @outfitter/logging.
6
- *
7
- * All log methods accept an optional context object that will be merged
8
- * with any context inherited from parent loggers created via `child()`.
9
- */
10
- interface Logger {
11
- trace(message: string, metadata?: Record<string, unknown>): void;
12
- trace(metadata: Record<string, unknown>, message: string): never;
13
- debug(message: string, metadata?: Record<string, unknown>): void;
14
- debug(metadata: Record<string, unknown>, message: string): never;
15
- info(message: string, metadata?: Record<string, unknown>): void;
16
- info(metadata: Record<string, unknown>, message: string): never;
17
- warn(message: string, metadata?: Record<string, unknown>): void;
18
- warn(metadata: Record<string, unknown>, message: string): never;
19
- error(message: string, metadata?: Record<string, unknown>): void;
20
- error(metadata: Record<string, unknown>, message: string): never;
21
- fatal(message: string, metadata?: Record<string, unknown>): void;
22
- fatal(metadata: Record<string, unknown>, message: string): never;
23
- /**
24
- * Creates a child logger with additional context.
25
- *
26
- * Context from the child is merged with the parent's context,
27
- * with child context taking precedence for duplicate keys.
28
- * Child loggers are composable (can create nested children).
29
- *
30
- * @param context - Additional context to include in all log messages
31
- * @returns A new Logger instance with the merged context
32
- *
33
- * @example
34
- * ```typescript
35
- * const requestLogger = ctx.logger.child({ requestId: ctx.requestId });
36
- * requestLogger.info("Processing request"); // includes requestId
37
- *
38
- * const opLogger = requestLogger.child({ operation: "create" });
39
- * opLogger.debug("Starting"); // includes requestId + operation
40
- * ```
41
- */
42
- child(context: Record<string, unknown>): Logger;
43
- }
44
- /**
45
5
  * Resolved configuration interface.
46
6
  * Implementations provided by @outfitter/config.
47
7
  */
@@ -108,4 +68,4 @@ type SyncHandler<
108
68
  TOutput,
109
69
  TError extends OutfitterError = OutfitterError
110
70
  > = (input: TInput, ctx: HandlerContext) => Result<TOutput, TError>;
111
- export { Logger, ResolvedConfig, HandlerContext, Handler, SyncHandler };
71
+ export { ResolvedConfig, HandlerContext, Handler, SyncHandler };
@@ -0,0 +1,341 @@
1
+ // @bun
2
+ // packages/contracts/src/schema.ts
3
+ function zodToJsonSchema(schema) {
4
+ return convertZodType(schema);
5
+ }
6
+ function getDef(schemaOrDef) {
7
+ if (!schemaOrDef) {
8
+ return;
9
+ }
10
+ if (schemaOrDef._def) {
11
+ return schemaOrDef._def;
12
+ }
13
+ if (schemaOrDef.def) {
14
+ return schemaOrDef.def;
15
+ }
16
+ return schemaOrDef;
17
+ }
18
+ function getDescription(schema, def) {
19
+ if (typeof schema?.description === "string") {
20
+ return schema.description;
21
+ }
22
+ if (typeof def?.description === "string") {
23
+ return def.description;
24
+ }
25
+ return;
26
+ }
27
+ function convertZodType(schema) {
28
+ const def = getDef(schema);
29
+ if (!def) {
30
+ return {};
31
+ }
32
+ const typeName = def.typeName ?? def.type;
33
+ let jsonSchema;
34
+ switch (typeName) {
35
+ case "ZodString":
36
+ case "string":
37
+ jsonSchema = convertString(def);
38
+ break;
39
+ case "ZodNumber":
40
+ case "number":
41
+ jsonSchema = convertNumber(def);
42
+ break;
43
+ case "ZodBoolean":
44
+ case "boolean":
45
+ jsonSchema = { type: "boolean" };
46
+ break;
47
+ case "ZodNull":
48
+ case "null":
49
+ jsonSchema = { type: "null" };
50
+ break;
51
+ case "ZodUndefined":
52
+ case "undefined":
53
+ jsonSchema = {};
54
+ break;
55
+ case "ZodArray":
56
+ case "array":
57
+ jsonSchema = convertArray(def);
58
+ break;
59
+ case "ZodObject":
60
+ case "object":
61
+ jsonSchema = convertObject(def);
62
+ break;
63
+ case "ZodOptional":
64
+ case "optional":
65
+ jsonSchema = convertZodType(def.innerType);
66
+ break;
67
+ case "ZodNullable":
68
+ case "nullable":
69
+ jsonSchema = {
70
+ anyOf: [convertZodType(def.innerType), { type: "null" }]
71
+ };
72
+ break;
73
+ case "ZodDefault":
74
+ case "default": {
75
+ const defaultValue = typeof def.defaultValue === "function" ? def.defaultValue() : def.defaultValue;
76
+ jsonSchema = {
77
+ ...convertZodType(def.innerType),
78
+ default: defaultValue
79
+ };
80
+ break;
81
+ }
82
+ case "ZodEnum":
83
+ case "enum": {
84
+ const values = def.values ?? Object.values(def.entries ?? {});
85
+ jsonSchema = {
86
+ type: "string",
87
+ enum: values
88
+ };
89
+ break;
90
+ }
91
+ case "ZodNativeEnum":
92
+ jsonSchema = {
93
+ enum: Object.values(def.values ?? def.entries ?? {})
94
+ };
95
+ break;
96
+ case "ZodLiteral":
97
+ case "literal": {
98
+ const literalValues = Array.isArray(def.values) ? def.values : [def.value].filter((value) => value !== undefined);
99
+ if (literalValues.length > 1) {
100
+ jsonSchema = {
101
+ enum: literalValues
102
+ };
103
+ break;
104
+ }
105
+ jsonSchema = literalValues.length ? {
106
+ const: literalValues[0]
107
+ } : {};
108
+ break;
109
+ }
110
+ case "ZodUnion":
111
+ case "union":
112
+ jsonSchema = {
113
+ anyOf: def.options.map(convertZodType)
114
+ };
115
+ break;
116
+ case "ZodIntersection":
117
+ case "intersection":
118
+ jsonSchema = {
119
+ allOf: [convertZodType(def.left), convertZodType(def.right)]
120
+ };
121
+ break;
122
+ case "ZodRecord":
123
+ case "record":
124
+ jsonSchema = {
125
+ type: "object",
126
+ additionalProperties: def.valueType ? convertZodType(def.valueType) : {}
127
+ };
128
+ break;
129
+ case "ZodTuple":
130
+ case "tuple":
131
+ jsonSchema = {
132
+ type: "array",
133
+ items: def.items.map(convertZodType)
134
+ };
135
+ break;
136
+ case "ZodAny":
137
+ case "any":
138
+ jsonSchema = {};
139
+ break;
140
+ case "ZodUnknown":
141
+ case "unknown":
142
+ jsonSchema = {};
143
+ break;
144
+ case "ZodVoid":
145
+ case "void":
146
+ jsonSchema = {};
147
+ break;
148
+ case "ZodNever":
149
+ case "never":
150
+ jsonSchema = { not: {} };
151
+ break;
152
+ case "ZodEffects":
153
+ jsonSchema = convertZodType(def.schema);
154
+ break;
155
+ case "ZodPipeline":
156
+ case "pipe": {
157
+ const outputDef = getDef(def.out);
158
+ const outputType = outputDef?.typeName ?? outputDef?.type;
159
+ jsonSchema = outputType === "transform" ? convertZodType(def.in) : convertZodType(def.out);
160
+ break;
161
+ }
162
+ case "ZodLazy":
163
+ case "lazy":
164
+ jsonSchema = {};
165
+ break;
166
+ default:
167
+ jsonSchema = {};
168
+ break;
169
+ }
170
+ const description = getDescription(schema, def);
171
+ if (description && !jsonSchema.description) {
172
+ jsonSchema.description = description;
173
+ }
174
+ return jsonSchema;
175
+ }
176
+ function convertString(def) {
177
+ const schema = { type: "string" };
178
+ if (def.checks) {
179
+ for (const check of def.checks) {
180
+ const normalizedCheck = check?._zod?.def ?? check?.def ?? check;
181
+ if (normalizedCheck?.kind) {
182
+ switch (normalizedCheck.kind) {
183
+ case "min":
184
+ schema.minLength = normalizedCheck.value;
185
+ break;
186
+ case "max":
187
+ schema.maxLength = normalizedCheck.value;
188
+ break;
189
+ case "length":
190
+ schema.minLength = normalizedCheck.value;
191
+ schema.maxLength = normalizedCheck.value;
192
+ break;
193
+ case "email":
194
+ schema.pattern = "^[^@]+@[^@]+\\.[^@]+$";
195
+ break;
196
+ case "url":
197
+ schema.pattern = "^https?://";
198
+ break;
199
+ case "uuid":
200
+ schema.pattern = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$";
201
+ break;
202
+ case "regex":
203
+ schema.pattern = normalizedCheck.regex?.source ?? normalizedCheck.pattern?.source ?? (typeof normalizedCheck.pattern === "string" ? normalizedCheck.pattern : undefined);
204
+ break;
205
+ default:
206
+ break;
207
+ }
208
+ continue;
209
+ }
210
+ if (!normalizedCheck?.check) {
211
+ continue;
212
+ }
213
+ switch (normalizedCheck.check) {
214
+ case "min_length":
215
+ schema.minLength = normalizedCheck.minimum;
216
+ break;
217
+ case "max_length":
218
+ schema.maxLength = normalizedCheck.maximum;
219
+ break;
220
+ case "string_format":
221
+ if (normalizedCheck.pattern) {
222
+ schema.pattern = typeof normalizedCheck.pattern === "string" ? normalizedCheck.pattern : normalizedCheck.pattern.source;
223
+ }
224
+ if (normalizedCheck.format && normalizedCheck.format !== "regex") {
225
+ schema.format = normalizedCheck.format;
226
+ }
227
+ break;
228
+ default:
229
+ break;
230
+ }
231
+ }
232
+ }
233
+ return schema;
234
+ }
235
+ function convertNumber(def) {
236
+ const schema = { type: "number" };
237
+ if (def.checks) {
238
+ for (const check of def.checks) {
239
+ const normalizedCheck = check?._zod?.def ?? check?.def ?? check;
240
+ if (normalizedCheck?.kind) {
241
+ switch (normalizedCheck.kind) {
242
+ case "min":
243
+ schema.minimum = normalizedCheck.value;
244
+ break;
245
+ case "max":
246
+ schema.maximum = normalizedCheck.value;
247
+ break;
248
+ case "int":
249
+ schema.type = "integer";
250
+ break;
251
+ default:
252
+ break;
253
+ }
254
+ continue;
255
+ }
256
+ if (!normalizedCheck?.check) {
257
+ continue;
258
+ }
259
+ switch (normalizedCheck.check) {
260
+ case "greater_than":
261
+ if (normalizedCheck.inclusive) {
262
+ schema.minimum = normalizedCheck.value;
263
+ } else {
264
+ schema.exclusiveMinimum = normalizedCheck.value;
265
+ }
266
+ break;
267
+ case "less_than":
268
+ if (normalizedCheck.inclusive) {
269
+ schema.maximum = normalizedCheck.value;
270
+ } else {
271
+ schema.exclusiveMaximum = normalizedCheck.value;
272
+ }
273
+ break;
274
+ case "number_format":
275
+ if (normalizedCheck.format === "int" || normalizedCheck.format === "safeint") {
276
+ schema.type = "integer";
277
+ }
278
+ break;
279
+ default:
280
+ break;
281
+ }
282
+ }
283
+ }
284
+ return schema;
285
+ }
286
+ function convertArray(def) {
287
+ const element = def.element ?? def.type;
288
+ const schema = {
289
+ type: "array",
290
+ items: element ? convertZodType(element) : {}
291
+ };
292
+ return schema;
293
+ }
294
+ function isFieldOptional(fieldDef) {
295
+ if (!(fieldDef?.typeName || fieldDef?.type)) {
296
+ return false;
297
+ }
298
+ const typeName = fieldDef.typeName ?? fieldDef.type;
299
+ if (typeName === "ZodOptional" || typeName === "ZodDefault" || typeName === "optional" || typeName === "default") {
300
+ return true;
301
+ }
302
+ if (typeName === "ZodEffects") {
303
+ return isFieldOptional(getDef(fieldDef.schema));
304
+ }
305
+ if (typeName === "ZodPipeline" || typeName === "pipe") {
306
+ const inputOptional = isFieldOptional(getDef(fieldDef.in));
307
+ const outputDef = getDef(fieldDef.out);
308
+ const outputType = outputDef?.typeName ?? outputDef?.type;
309
+ if (outputType === "transform") {
310
+ return inputOptional;
311
+ }
312
+ const outputOptional = isFieldOptional(outputDef);
313
+ return inputOptional && outputOptional;
314
+ }
315
+ if (typeName === "ZodNullable" || typeName === "nullable") {
316
+ return isFieldOptional(getDef(fieldDef.innerType));
317
+ }
318
+ return false;
319
+ }
320
+ function convertObject(def) {
321
+ const properties = {};
322
+ const required = [];
323
+ const shape = typeof def.shape === "function" ? def.shape() : def.shape;
324
+ for (const [key, value] of Object.entries(shape ?? {})) {
325
+ properties[key] = convertZodType(value);
326
+ const fieldDef = getDef(value);
327
+ if (!isFieldOptional(fieldDef)) {
328
+ required.push(key);
329
+ }
330
+ }
331
+ const schema = {
332
+ type: "object",
333
+ properties
334
+ };
335
+ if (required.length > 0) {
336
+ schema.required = required;
337
+ }
338
+ return schema;
339
+ }
340
+
341
+ export { zodToJsonSchema };
@@ -1,3 +1,3 @@
1
- import { createValidator, validateInput } from "./shared/@outfitter/contracts-n9hb6hqt";
2
- import "./shared/@outfitter/contracts-5hr9pdcq";
1
+ import { createValidator, validateInput } from "./shared/@outfitter/contracts-6j6z9dsd";
2
+ import "./shared/@outfitter/contracts-r35bn9p6";
3
3
  export { validateInput, createValidator };