@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.
- package/README.md +41 -2
- package/dist/actions.d.ts +4 -3
- package/dist/assert/index.d.ts +2 -2
- package/dist/assert/index.js +2 -2
- package/dist/context.d.ts +4 -3
- package/dist/envelope.d.ts +2 -2
- package/dist/envelope.js +4 -4
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +4 -2
- package/dist/handler.d.ts +3 -2
- package/dist/index.d.ts +15 -13
- package/dist/index.js +43 -33
- package/dist/logging.d.ts +2 -0
- package/dist/logging.js +7 -0
- package/dist/recovery.d.ts +2 -2
- package/dist/resilience.d.ts +2 -2
- package/dist/resilience.js +2 -2
- package/dist/schema.d.ts +2 -0
- package/dist/schema.js +7 -0
- package/dist/serialization.d.ts +2 -2
- package/dist/serialization.js +2 -2
- package/dist/shared/@outfitter/{contracts-k2jdpbb6.js → contracts-0snpmkdt.js} +1 -1
- package/dist/shared/@outfitter/{contracts-jbhbyhtt.d.ts → contracts-18vcxecr.d.ts} +1 -1
- package/dist/shared/@outfitter/{contracts-t68dwjbk.d.ts → contracts-25bkj17f.d.ts} +2 -1
- package/dist/shared/@outfitter/contracts-2g8r01zf.d.ts +73 -0
- package/dist/shared/@outfitter/{contracts-agvdgx9j.js → contracts-5k6q4n48.js} +41 -9
- package/dist/shared/@outfitter/{contracts-n9hb6hqt.d.ts → contracts-6j6z9dsd.d.ts} +1 -1
- package/dist/shared/@outfitter/{contracts-r64j5rsm.d.ts → contracts-bdwg55c5.d.ts} +1 -1
- package/dist/shared/@outfitter/{contracts-rtsa2s0f.js → contracts-btg89x4h.js} +2 -2
- package/dist/shared/@outfitter/{contracts-2f3khqcc.js → contracts-cp5c6dws.js} +1 -1
- package/dist/shared/@outfitter/{contracts-md2mvvyt.d.ts → contracts-evxky148.d.ts} +1 -1
- package/dist/shared/@outfitter/contracts-hbbxbwkt.d.ts +60 -0
- package/dist/shared/@outfitter/{contracts-6atxbfag.d.ts → contracts-j08e95jw.d.ts} +1 -1
- package/dist/shared/@outfitter/{contracts-k0rhaye3.d.ts → contracts-jggbn5tn.d.ts} +1 -1
- package/dist/shared/@outfitter/{contracts-0cj49e1a.js → contracts-phjhz5q3.js} +26 -1
- package/dist/shared/@outfitter/{contracts-pdk7s4t8.js → contracts-r21yet6j.js} +1 -1
- package/dist/shared/@outfitter/{contracts-5hr9pdcq.d.ts → contracts-r35bn9p6.d.ts} +62 -3
- package/dist/shared/@outfitter/{contracts-zyanj0gf.d.ts → contracts-sf1z80yc.d.ts} +2 -2
- package/dist/shared/@outfitter/contracts-sm6vak1a.js +14 -0
- package/dist/shared/@outfitter/{contracts-amd2ykn8.d.ts → contracts-ss9vjjft.d.ts} +3 -43
- package/dist/shared/@outfitter/contracts-wfht4q2b.js +341 -0
- package/dist/validation.d.ts +2 -2
- package/dist/validation.js +2 -2
- package/package.json +56 -44
|
@@ -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 };
|
|
@@ -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 };
|
|
@@ -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
|
-
*
|
|
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-
|
|
2
|
-
import { OutfitterError } from "./contracts-
|
|
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-
|
|
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 {
|
|
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 };
|
package/dist/validation.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { createValidator, validateInput } from "./shared/@outfitter/contracts-
|
|
2
|
-
import "./shared/@outfitter/contracts-
|
|
1
|
+
import { createValidator, validateInput } from "./shared/@outfitter/contracts-6j6z9dsd";
|
|
2
|
+
import "./shared/@outfitter/contracts-r35bn9p6";
|
|
3
3
|
export { validateInput, createValidator };
|