@outfitter/contracts 0.4.1 → 0.5.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 +18 -15
- package/dist/actions.d.ts +8 -3
- package/dist/actions.js +25 -6
- package/dist/assert/index.d.ts +7 -3
- package/dist/assert/index.js +59 -6
- package/dist/capabilities.js +57 -8
- package/dist/context.d.ts +8 -3
- package/dist/context.js +1 -1
- package/dist/envelope.d.ts +6 -2
- package/dist/envelope.js +49 -7
- package/dist/errors.d.ts +6 -2
- package/dist/errors.js +7 -1
- package/dist/from-fetch.d.ts +7 -0
- package/dist/from-fetch.js +110 -0
- package/dist/handler.d.ts +7 -2
- package/dist/hints.d.ts +2 -0
- package/dist/index.d.ts +25 -14
- package/dist/index.js +17 -153
- package/dist/internal/error-base.d.ts +2 -0
- package/dist/internal/error-base.js +31 -0
- package/dist/internal/error-operational.d.ts +3 -0
- package/dist/internal/error-operational.js +125 -0
- package/dist/internal/error-serialization.d.ts +7 -0
- package/dist/{shared/@outfitter/contracts-3wj7xghe.js → internal/error-serialization.js} +28 -67
- package/dist/internal/error-taxonomy.d.ts +2 -0
- package/dist/internal/error-taxonomy.js +21 -0
- package/dist/internal/error-validation.d.ts +3 -0
- package/dist/internal/error-validation.js +121 -0
- package/dist/internal/safe-json.d.ts +7 -0
- package/dist/internal/safe-json.js +66 -0
- package/dist/internal/schema-converters.d.ts +26 -0
- package/dist/internal/schema-converters.js +12 -0
- package/dist/internal/schema-primitives.d.ts +10 -0
- package/dist/internal/schema-primitives.js +9 -0
- package/dist/internal/schema-types.d.ts +2 -0
- package/dist/internal/schema-types.js +9 -0
- package/dist/logging.js +11 -3
- package/dist/recovery.d.ts +6 -2
- package/dist/recovery.js +49 -6
- package/dist/resilience.d.ts +6 -2
- package/dist/resilience.js +80 -4
- package/dist/result/index.js +1 -16
- package/dist/result/utilities.js +29 -7
- package/dist/schema.d.ts +2 -1
- package/dist/schema.js +165 -2
- package/dist/serialization.d.ts +8 -2
- package/dist/serialization.js +1 -3
- package/dist/shared/@outfitter/{contracts-k71jqd1m.d.ts → contracts-10p5q75w.d.ts} +1 -1
- package/dist/shared/@outfitter/contracts-1zzcpfyg.d.ts +40 -0
- package/dist/shared/@outfitter/contracts-3f5k5tg5.d.ts +28 -0
- package/dist/shared/@outfitter/contracts-3qmyq81n.d.ts +78 -0
- package/dist/shared/@outfitter/contracts-3re9d4bp.js +114 -0
- package/dist/shared/@outfitter/contracts-735ecmbq.d.ts +107 -0
- package/dist/shared/@outfitter/contracts-7a0xmwbg.d.ts +11 -0
- package/dist/shared/@outfitter/contracts-8cmkh2db.d.ts +31 -0
- package/dist/shared/@outfitter/{contracts-agmt8915.js → contracts-c3qfce25.js} +3 -0
- package/dist/shared/@outfitter/{contracts-1waabxbk.d.ts → contracts-drwd9ywk.d.ts} +4 -1
- package/dist/shared/@outfitter/{contracts-0snpmkdt.js → contracts-hgh47193.js} +10 -4
- package/dist/shared/@outfitter/contracts-hrepwwne.js +62 -0
- package/dist/shared/@outfitter/contracts-jtn6b927.js +18 -0
- package/dist/shared/@outfitter/contracts-jtt6dnmg.js +2 -0
- package/dist/shared/@outfitter/contracts-jyhqr766.js +25 -0
- package/dist/shared/@outfitter/contracts-mehpmvwp.d.ts +164 -0
- package/dist/shared/@outfitter/contracts-msxdg52h.d.ts +125 -0
- package/dist/shared/@outfitter/{contracts-95cc3y06.d.ts → contracts-mt027fqj.d.ts} +2 -1
- package/dist/shared/@outfitter/contracts-njb2art4.d.ts +174 -0
- package/dist/shared/@outfitter/contracts-p77yjs4g.d.ts +46 -0
- package/dist/shared/@outfitter/contracts-qpbv29bg.d.ts +59 -0
- package/dist/shared/@outfitter/contracts-sawwfgb5.js +111 -0
- package/dist/shared/@outfitter/{contracts-e4m948m7.d.ts → contracts-t4txv24h.d.ts} +2 -1
- package/dist/shared/@outfitter/contracts-vbgt9rfn.d.ts +74 -0
- package/dist/shared/@outfitter/{contracts-56pcsavx.d.ts → contracts-vhajx4gg.d.ts} +8 -2
- package/dist/shared/@outfitter/contracts-vhr2ep6b.js +3 -0
- package/dist/shared/@outfitter/contracts-w7nvcwrp.d.ts +44 -0
- package/dist/shared/@outfitter/contracts-x0ppyt7e.d.ts +76 -0
- package/dist/shared/@outfitter/{contracts-0akf2sm6.d.ts → contracts-zma4mscd.d.ts} +16 -1
- package/dist/shared/@outfitter/contracts-zsgxsa91.d.ts +84 -0
- package/dist/stream.d.ts +2 -0
- package/dist/stream.js +1 -0
- package/dist/validation.d.ts +7 -3
- package/dist/validation.js +6 -2
- package/dist/wrap-error.d.ts +7 -0
- package/dist/wrap-error.js +71 -0
- package/package.json +44 -20
- package/dist/shared/@outfitter/contracts-31penhwa.d.ts +0 -81
- package/dist/shared/@outfitter/contracts-3gswmhb1.d.ts +0 -446
- package/dist/shared/@outfitter/contracts-4zaj7ejb.js +0 -52
- package/dist/shared/@outfitter/contracts-85nd53s9.js +0 -53
- package/dist/shared/@outfitter/contracts-9wtm5nsw.d.ts +0 -42
- package/dist/shared/@outfitter/contracts-cp5c6dws.js +0 -32
- package/dist/shared/@outfitter/contracts-d0tq2adf.js +0 -60
- package/dist/shared/@outfitter/contracts-mmg0npfk.d.ts +0 -30
- package/dist/shared/@outfitter/contracts-phjhz5q3.js +0 -293
- package/dist/shared/@outfitter/contracts-q0v44kef.js +0 -28
- package/dist/shared/@outfitter/contracts-r21yet6j.js +0 -80
- package/dist/shared/@outfitter/contracts-sm6vak1a.js +0 -14
- package/dist/shared/@outfitter/contracts-t79engf9.d.ts +0 -60
- package/dist/shared/@outfitter/contracts-wfht4q2b.js +0 -341
- package/dist/shared/@outfitter/contracts-zx72gyh1.js +0 -32
- /package/dist/{shared/@outfitter/contracts-37gpc56f.js → hints.js} +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { AssertionError } from "./contracts-mehpmvwp.js";
|
|
2
|
+
import { Result } from "better-result";
|
|
3
|
+
/**
|
|
4
|
+
* Array type guaranteed to have at least one element.
|
|
5
|
+
*/
|
|
6
|
+
type NonEmptyArray<T> = [T, ...T[]];
|
|
7
|
+
/**
|
|
8
|
+
* Type guard for NonEmptyArray.
|
|
9
|
+
*/
|
|
10
|
+
declare const isNonEmptyArray: <T>(arr: readonly T[]) => arr is NonEmptyArray<T>;
|
|
11
|
+
/**
|
|
12
|
+
* Assert a value is defined (not null or undefined).
|
|
13
|
+
* Returns Result instead of throwing.
|
|
14
|
+
*/
|
|
15
|
+
declare const assertDefined: <T>(value: T | null | undefined, message?: string) => Result<T, InstanceType<typeof AssertionError>>;
|
|
16
|
+
/**
|
|
17
|
+
* Assert array has at least one element.
|
|
18
|
+
* Returns NonEmptyArray on success.
|
|
19
|
+
*/
|
|
20
|
+
declare const assertNonEmpty: <T>(arr: readonly T[], message?: string) => Result<NonEmptyArray<T>, InstanceType<typeof AssertionError>>;
|
|
21
|
+
/**
|
|
22
|
+
* Assert value matches a predicate.
|
|
23
|
+
* Supports type guard predicates for narrowing.
|
|
24
|
+
*/
|
|
25
|
+
declare function assertMatches<
|
|
26
|
+
T,
|
|
27
|
+
U extends T
|
|
28
|
+
>(value: T, predicate: (v: T) => v is U, message?: string): Result<U, InstanceType<typeof AssertionError>>;
|
|
29
|
+
declare function assertMatches<T>(value: T, predicate: (v: T) => boolean, message?: string): Result<T, InstanceType<typeof AssertionError>>;
|
|
30
|
+
/**
|
|
31
|
+
* Assert a Result is Ok and return the narrowed value.
|
|
32
|
+
*
|
|
33
|
+
* Throws a descriptive error if the Result is Err, making it ideal for
|
|
34
|
+
* test assertions with Bun's test runner.
|
|
35
|
+
*
|
|
36
|
+
* @param result - The Result to assert
|
|
37
|
+
* @param message - Optional context message prepended to the error
|
|
38
|
+
* @returns The unwrapped Ok value with type narrowing to `T`
|
|
39
|
+
* @throws Error with descriptive message if Result is Err
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* import { expectOk } from "@outfitter/contracts";
|
|
44
|
+
*
|
|
45
|
+
* const result = await fetchUser(id);
|
|
46
|
+
* const user = expectOk(result); // throws if Err, returns User if Ok
|
|
47
|
+
* expect(user.name).toBe("Alice");
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
declare const expectOk: <
|
|
51
|
+
T,
|
|
52
|
+
E
|
|
53
|
+
>(result: Result<T, E>, message?: string) => T;
|
|
54
|
+
/**
|
|
55
|
+
* Assert a Result is Err and return the narrowed error.
|
|
56
|
+
*
|
|
57
|
+
* Throws a descriptive error if the Result is Ok, making it ideal for
|
|
58
|
+
* test assertions with Bun's test runner.
|
|
59
|
+
*
|
|
60
|
+
* @param result - The Result to assert
|
|
61
|
+
* @param message - Optional context message prepended to the error
|
|
62
|
+
* @returns The unwrapped Err value with type narrowing to `E`
|
|
63
|
+
* @throws Error with descriptive message if Result is Ok
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* import { expectErr } from "@outfitter/contracts";
|
|
68
|
+
*
|
|
69
|
+
* const result = validateInput(invalidData);
|
|
70
|
+
* const error = expectErr(result); // throws if Ok, returns error if Err
|
|
71
|
+
* expect(error.category).toBe("validation");
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
declare const expectErr: <
|
|
75
|
+
T,
|
|
76
|
+
E
|
|
77
|
+
>(result: Result<T, E>, message?: string) => E;
|
|
78
|
+
export { NonEmptyArray, isNonEmptyArray, assertDefined, assertNonEmpty, assertMatches, expectOk, expectErr };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/contracts/src/internal/schema-primitives.ts
|
|
3
|
+
function convertString(def) {
|
|
4
|
+
const schema = { type: "string" };
|
|
5
|
+
if (def.checks) {
|
|
6
|
+
for (const check of def.checks) {
|
|
7
|
+
const normalizedCheck = check?._zod?.def ?? check?.def ?? check;
|
|
8
|
+
if (normalizedCheck?.kind) {
|
|
9
|
+
switch (normalizedCheck.kind) {
|
|
10
|
+
case "min":
|
|
11
|
+
schema.minLength = normalizedCheck.value;
|
|
12
|
+
break;
|
|
13
|
+
case "max":
|
|
14
|
+
schema.maxLength = normalizedCheck.value;
|
|
15
|
+
break;
|
|
16
|
+
case "length":
|
|
17
|
+
schema.minLength = normalizedCheck.value;
|
|
18
|
+
schema.maxLength = normalizedCheck.value;
|
|
19
|
+
break;
|
|
20
|
+
case "email":
|
|
21
|
+
schema.pattern = "^[^@]+@[^@]+\\.[^@]+$";
|
|
22
|
+
break;
|
|
23
|
+
case "url":
|
|
24
|
+
schema.pattern = "^https?://";
|
|
25
|
+
break;
|
|
26
|
+
case "uuid":
|
|
27
|
+
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}$";
|
|
28
|
+
break;
|
|
29
|
+
case "regex":
|
|
30
|
+
schema.pattern = normalizedCheck.regex?.source ?? normalizedCheck.pattern?.source ?? (typeof normalizedCheck.pattern === "string" ? normalizedCheck.pattern : undefined);
|
|
31
|
+
break;
|
|
32
|
+
default:
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (!normalizedCheck?.check) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
switch (normalizedCheck.check) {
|
|
41
|
+
case "min_length":
|
|
42
|
+
schema.minLength = normalizedCheck.minimum;
|
|
43
|
+
break;
|
|
44
|
+
case "max_length":
|
|
45
|
+
schema.maxLength = normalizedCheck.maximum;
|
|
46
|
+
break;
|
|
47
|
+
case "string_format":
|
|
48
|
+
if (normalizedCheck.pattern) {
|
|
49
|
+
schema.pattern = typeof normalizedCheck.pattern === "string" ? normalizedCheck.pattern : normalizedCheck.pattern.source;
|
|
50
|
+
}
|
|
51
|
+
if (normalizedCheck.format && normalizedCheck.format !== "regex") {
|
|
52
|
+
schema.format = normalizedCheck.format;
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
default:
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return schema;
|
|
61
|
+
}
|
|
62
|
+
function convertNumber(def) {
|
|
63
|
+
const schema = { type: "number" };
|
|
64
|
+
if (def.checks) {
|
|
65
|
+
for (const check of def.checks) {
|
|
66
|
+
const normalizedCheck = check?._zod?.def ?? check?.def ?? check;
|
|
67
|
+
if (normalizedCheck?.kind) {
|
|
68
|
+
switch (normalizedCheck.kind) {
|
|
69
|
+
case "min":
|
|
70
|
+
schema.minimum = normalizedCheck.value;
|
|
71
|
+
break;
|
|
72
|
+
case "max":
|
|
73
|
+
schema.maximum = normalizedCheck.value;
|
|
74
|
+
break;
|
|
75
|
+
case "int":
|
|
76
|
+
schema.type = "integer";
|
|
77
|
+
break;
|
|
78
|
+
default:
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (!normalizedCheck?.check) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
switch (normalizedCheck.check) {
|
|
87
|
+
case "greater_than":
|
|
88
|
+
if (normalizedCheck.inclusive) {
|
|
89
|
+
schema.minimum = normalizedCheck.value;
|
|
90
|
+
} else {
|
|
91
|
+
schema.exclusiveMinimum = normalizedCheck.value;
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
case "less_than":
|
|
95
|
+
if (normalizedCheck.inclusive) {
|
|
96
|
+
schema.maximum = normalizedCheck.value;
|
|
97
|
+
} else {
|
|
98
|
+
schema.exclusiveMaximum = normalizedCheck.value;
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
case "number_format":
|
|
102
|
+
if (normalizedCheck.format === "int" || normalizedCheck.format === "safeint") {
|
|
103
|
+
schema.type = "integer";
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
default:
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return schema;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export { convertString, convertNumber };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { AuthErrorBase, CancelledErrorBase, InternalErrorBase, NetworkErrorBase, PermissionErrorBase, RateLimitErrorBase, TimeoutErrorBase } from "./contracts-qpbv29bg.js";
|
|
2
|
+
/**
|
|
3
|
+
* Authorization denied.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* new PermissionError({ message: "Cannot delete read-only resource" });
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
declare class PermissionError extends PermissionErrorBase {
|
|
11
|
+
readonly category: "permission";
|
|
12
|
+
/** Create a PermissionError with optional context. */
|
|
13
|
+
static create(message: string, context?: Record<string, unknown>): PermissionError;
|
|
14
|
+
exitCode(): number;
|
|
15
|
+
statusCode(): number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Operation timed out.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* new TimeoutError({ message: "Database query timed out after 5000ms", operation: "Database query", timeoutMs: 5000 });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
declare class TimeoutError extends TimeoutErrorBase {
|
|
26
|
+
readonly category: "timeout";
|
|
27
|
+
/** Create a TimeoutError with auto-generated message. */
|
|
28
|
+
static create(operation: string, timeoutMs: number): TimeoutError;
|
|
29
|
+
exitCode(): number;
|
|
30
|
+
statusCode(): number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Rate limit exceeded.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* new RateLimitError({ message: "Rate limit exceeded, retry after 60s", retryAfterSeconds: 60 });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
declare class RateLimitError extends RateLimitErrorBase {
|
|
41
|
+
readonly category: "rate_limit";
|
|
42
|
+
/** Create a RateLimitError with optional retry hint. */
|
|
43
|
+
static create(message: string, retryAfterSeconds?: number): RateLimitError;
|
|
44
|
+
exitCode(): number;
|
|
45
|
+
statusCode(): number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Network/transport failure.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* new NetworkError({ message: "Connection refused to api.example.com" });
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
declare class NetworkError extends NetworkErrorBase {
|
|
56
|
+
readonly category: "network";
|
|
57
|
+
/** Create a NetworkError with optional context. */
|
|
58
|
+
static create(message: string, context?: Record<string, unknown>): NetworkError;
|
|
59
|
+
exitCode(): number;
|
|
60
|
+
statusCode(): number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Unexpected internal error.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* new InternalError({ message: "Unexpected state in processor" });
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
declare class InternalError extends InternalErrorBase {
|
|
71
|
+
readonly category: "internal";
|
|
72
|
+
/** Create an InternalError with optional context. */
|
|
73
|
+
static create(message: string, context?: Record<string, unknown>): InternalError;
|
|
74
|
+
exitCode(): number;
|
|
75
|
+
statusCode(): number;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Authentication failed (missing or invalid credentials).
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* new AuthError({ message: "Invalid API key", reason: "invalid" });
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
declare class AuthError extends AuthErrorBase {
|
|
86
|
+
readonly category: "auth";
|
|
87
|
+
/** Create an AuthError with optional reason. */
|
|
88
|
+
static create(message: string, reason?: "missing" | "invalid" | "expired"): AuthError;
|
|
89
|
+
exitCode(): number;
|
|
90
|
+
statusCode(): number;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Operation cancelled by user or signal.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```typescript
|
|
97
|
+
* new CancelledError({ message: "Operation cancelled by user" });
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
declare class CancelledError extends CancelledErrorBase {
|
|
101
|
+
readonly category: "cancelled";
|
|
102
|
+
/** Create a CancelledError. */
|
|
103
|
+
static create(message: string): CancelledError;
|
|
104
|
+
exitCode(): number;
|
|
105
|
+
statusCode(): number;
|
|
106
|
+
}
|
|
107
|
+
export { PermissionError, TimeoutError, RateLimitError, NetworkError, InternalError, AuthError, CancelledError };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { AuthError, CancelledError, InternalError, NetworkError, PermissionError, RateLimitError, TimeoutError } from "./contracts-735ecmbq.js";
|
|
2
|
+
import { AlreadyExistsError, AmbiguousError, AssertionError, ConflictError, NotFoundError, ValidationError } from "./contracts-mehpmvwp.js";
|
|
3
|
+
/**
|
|
4
|
+
* Canonical union type of all concrete error class instances.
|
|
5
|
+
*/
|
|
6
|
+
type OutfitterError = 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>;
|
|
7
|
+
/**
|
|
8
|
+
* @deprecated Use `OutfitterError` instead. This alias will be removed in v1.0.
|
|
9
|
+
*/
|
|
10
|
+
type AnyKitError = OutfitterError;
|
|
11
|
+
export { OutfitterError, AnyKitError };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { JsonSchema } from "./contracts-w7nvcwrp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
/**
|
|
4
|
+
* Convert a Zod schema to JSON Schema format.
|
|
5
|
+
*
|
|
6
|
+
* This is a simplified converter that handles common Zod types.
|
|
7
|
+
* For complex schemas, consider using a full zod-to-json-schema library.
|
|
8
|
+
*
|
|
9
|
+
* @param schema - Zod schema to convert
|
|
10
|
+
* @returns JSON Schema representation
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const zodSchema = z.object({
|
|
15
|
+
* name: z.string(),
|
|
16
|
+
* age: z.number().optional(),
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* const jsonSchema = zodToJsonSchema(zodSchema);
|
|
20
|
+
* // {
|
|
21
|
+
* // type: "object",
|
|
22
|
+
* // properties: {
|
|
23
|
+
* // name: { type: "string" },
|
|
24
|
+
* // age: { type: "number" },
|
|
25
|
+
* // },
|
|
26
|
+
* // required: ["name"],
|
|
27
|
+
* // }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
declare function zodToJsonSchema(schema: z.ZodType<unknown>): JsonSchema;
|
|
31
|
+
export { zodToJsonSchema };
|
|
@@ -22,6 +22,9 @@ function createContext(options) {
|
|
|
22
22
|
if (options.signal !== undefined) {
|
|
23
23
|
ctx.signal = options.signal;
|
|
24
24
|
}
|
|
25
|
+
if (options.progress !== undefined) {
|
|
26
|
+
ctx.progress = options.progress;
|
|
27
|
+
}
|
|
25
28
|
if (options.workspaceRoot !== undefined) {
|
|
26
29
|
ctx.workspaceRoot = options.workspaceRoot;
|
|
27
30
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { HandlerContext, ResolvedConfig } from "./contracts-
|
|
1
|
+
import { HandlerContext, ResolvedConfig } from "./contracts-zma4mscd.js";
|
|
2
|
+
import { ProgressCallback } from "./contracts-msxdg52h.js";
|
|
2
3
|
import { Logger } from "./contracts-rwzqy9rn.js";
|
|
3
4
|
/**
|
|
4
5
|
* Options for creating a handler context.
|
|
@@ -12,6 +13,8 @@ interface CreateContextOptions {
|
|
|
12
13
|
env?: Record<string, string | undefined>;
|
|
13
14
|
/** Logger instance (uses no-op logger if not provided) */
|
|
14
15
|
logger?: Logger;
|
|
16
|
+
/** Streaming progress callback (undefined means no streaming) */
|
|
17
|
+
progress?: ProgressCallback;
|
|
15
18
|
/** Explicit request ID (generates UUIDv7 if not provided) */
|
|
16
19
|
requestId?: string;
|
|
17
20
|
/** Abort signal for cancellation */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
ValidationError
|
|
4
|
-
} from "./contracts-
|
|
4
|
+
} from "./contracts-vhr2ep6b.js";
|
|
5
5
|
|
|
6
6
|
// packages/contracts/src/validation.ts
|
|
7
7
|
import { Result } from "better-result";
|
|
@@ -23,8 +23,8 @@ function createValidator(schema) {
|
|
|
23
23
|
return validateInput(schema, input);
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
-
function
|
|
27
|
-
const parseResult = schema.safeParse(
|
|
26
|
+
function parseWithSchema(schema, data) {
|
|
27
|
+
const parseResult = schema.safeParse(data);
|
|
28
28
|
if (parseResult.success) {
|
|
29
29
|
return Result.ok(parseResult.data);
|
|
30
30
|
}
|
|
@@ -36,5 +36,11 @@ function validateInput(schema, input) {
|
|
|
36
36
|
}
|
|
37
37
|
return Result.err(new ValidationError(errorProps));
|
|
38
38
|
}
|
|
39
|
+
function validateInput(schema, input) {
|
|
40
|
+
return parseWithSchema(schema, input);
|
|
41
|
+
}
|
|
42
|
+
function parseInput(schema, data) {
|
|
43
|
+
return parseWithSchema(schema, data);
|
|
44
|
+
}
|
|
39
45
|
|
|
40
|
-
export { createValidator, validateInput };
|
|
46
|
+
export { formatZodIssues, createValidator, validateInput, parseInput };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
getDef
|
|
4
|
+
} from "./contracts-jyhqr766.js";
|
|
5
|
+
|
|
6
|
+
// packages/contracts/src/internal/schema-converters.ts
|
|
7
|
+
function convertArray(def, convertZodType) {
|
|
8
|
+
const element = def.element ?? def.type;
|
|
9
|
+
const schema = {
|
|
10
|
+
type: "array",
|
|
11
|
+
items: element ? convertZodType(element) : {}
|
|
12
|
+
};
|
|
13
|
+
return schema;
|
|
14
|
+
}
|
|
15
|
+
function isFieldOptional(fieldDef) {
|
|
16
|
+
if (!(fieldDef?.typeName || fieldDef?.type)) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
const typeName = fieldDef.typeName ?? fieldDef.type;
|
|
20
|
+
if (typeName === "ZodOptional" || typeName === "ZodDefault" || typeName === "optional" || typeName === "default") {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
if (typeName === "ZodEffects") {
|
|
24
|
+
return isFieldOptional(getDef(fieldDef.schema));
|
|
25
|
+
}
|
|
26
|
+
if (typeName === "ZodPipeline" || typeName === "pipe") {
|
|
27
|
+
const inputOptional = isFieldOptional(getDef(fieldDef.in));
|
|
28
|
+
const outputDef = getDef(fieldDef.out);
|
|
29
|
+
const outputType = outputDef?.typeName ?? outputDef?.type;
|
|
30
|
+
if (outputType === "transform") {
|
|
31
|
+
return inputOptional;
|
|
32
|
+
}
|
|
33
|
+
const outputOptional = isFieldOptional(outputDef);
|
|
34
|
+
return inputOptional && outputOptional;
|
|
35
|
+
}
|
|
36
|
+
if (typeName === "ZodNullable" || typeName === "nullable") {
|
|
37
|
+
return isFieldOptional(getDef(fieldDef.innerType));
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
function convertObject(def, convertZodType) {
|
|
42
|
+
const properties = {};
|
|
43
|
+
const required = [];
|
|
44
|
+
const shape = typeof def.shape === "function" ? def.shape() : def.shape;
|
|
45
|
+
for (const [key, value] of Object.entries(shape ?? {})) {
|
|
46
|
+
properties[key] = convertZodType(value);
|
|
47
|
+
const fieldDef = getDef(value);
|
|
48
|
+
if (!isFieldOptional(fieldDef)) {
|
|
49
|
+
required.push(key);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const schema = {
|
|
53
|
+
type: "object",
|
|
54
|
+
properties
|
|
55
|
+
};
|
|
56
|
+
if (required.length > 0) {
|
|
57
|
+
schema.required = required;
|
|
58
|
+
}
|
|
59
|
+
return schema;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { convertArray, isFieldOptional, convertObject };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/contracts/src/internal/error-base.ts
|
|
3
|
+
import { TaggedError } from "better-result";
|
|
4
|
+
var ValidationErrorBase = TaggedError("ValidationError")();
|
|
5
|
+
var AmbiguousErrorBase = TaggedError("AmbiguousError")();
|
|
6
|
+
var AssertionErrorBase = TaggedError("AssertionError")();
|
|
7
|
+
var NotFoundErrorBase = TaggedError("NotFoundError")();
|
|
8
|
+
var AlreadyExistsErrorBase = TaggedError("AlreadyExistsError")();
|
|
9
|
+
var ConflictErrorBase = TaggedError("ConflictError")();
|
|
10
|
+
var PermissionErrorBase = TaggedError("PermissionError")();
|
|
11
|
+
var TimeoutErrorBase = TaggedError("TimeoutError")();
|
|
12
|
+
var RateLimitErrorBase = TaggedError("RateLimitError")();
|
|
13
|
+
var NetworkErrorBase = TaggedError("NetworkError")();
|
|
14
|
+
var InternalErrorBase = TaggedError("InternalError")();
|
|
15
|
+
var AuthErrorBase = TaggedError("AuthError")();
|
|
16
|
+
var CancelledErrorBase = TaggedError("CancelledError")();
|
|
17
|
+
|
|
18
|
+
export { ValidationErrorBase, AmbiguousErrorBase, AssertionErrorBase, NotFoundErrorBase, AlreadyExistsErrorBase, ConflictErrorBase, PermissionErrorBase, TimeoutErrorBase, RateLimitErrorBase, NetworkErrorBase, InternalErrorBase, AuthErrorBase, CancelledErrorBase };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/contracts/src/internal/schema-types.ts
|
|
3
|
+
function getDef(schemaOrDef) {
|
|
4
|
+
if (!schemaOrDef) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
if (schemaOrDef._def) {
|
|
8
|
+
return schemaOrDef._def;
|
|
9
|
+
}
|
|
10
|
+
if (schemaOrDef.def) {
|
|
11
|
+
return schemaOrDef.def;
|
|
12
|
+
}
|
|
13
|
+
return schemaOrDef;
|
|
14
|
+
}
|
|
15
|
+
function getDescription(schema, def) {
|
|
16
|
+
if (typeof schema?.description === "string") {
|
|
17
|
+
return schema.description;
|
|
18
|
+
}
|
|
19
|
+
if (typeof def?.description === "string") {
|
|
20
|
+
return def.description;
|
|
21
|
+
}
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export { getDef, getDescription };
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { AlreadyExistsErrorBase, AmbiguousErrorBase, AssertionErrorBase, ConflictErrorBase, NotFoundErrorBase, ValidationErrorBase } from "./contracts-qpbv29bg.js";
|
|
2
|
+
/**
|
|
3
|
+
* Input validation failed.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* new ValidationError({ message: "Email format invalid", field: "email" });
|
|
8
|
+
* new ValidationError({
|
|
9
|
+
* message: "Value out of range",
|
|
10
|
+
* field: "age",
|
|
11
|
+
* context: { min: 0, max: 150, received: -1 },
|
|
12
|
+
* });
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
declare class ValidationError extends ValidationErrorBase {
|
|
16
|
+
readonly category: "validation";
|
|
17
|
+
/** Create a ValidationError with auto-generated message from field name. */
|
|
18
|
+
static create(field: string, reason: string, context?: Record<string, unknown>): ValidationError;
|
|
19
|
+
/**
|
|
20
|
+
* Create a freeform ValidationError without a specific field.
|
|
21
|
+
*
|
|
22
|
+
* Use when the validation failure applies to the input as a whole
|
|
23
|
+
* rather than a single field (e.g., "Invalid pipeline configuration").
|
|
24
|
+
*
|
|
25
|
+
* @param message - Human-readable validation error message
|
|
26
|
+
* @param context - Optional structured context for debugging
|
|
27
|
+
*/
|
|
28
|
+
static fromMessage(message: string, context?: Record<string, unknown>): ValidationError;
|
|
29
|
+
exitCode(): number;
|
|
30
|
+
statusCode(): number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Multiple matches found — user must disambiguate.
|
|
34
|
+
*
|
|
35
|
+
* Used in search/resolution systems where partial input matches
|
|
36
|
+
* multiple candidates. Carries the candidate list so transport
|
|
37
|
+
* layers can prompt disambiguation.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* new AmbiguousError({
|
|
42
|
+
* message: "Multiple headings match 'Intro'",
|
|
43
|
+
* candidates: ["Introduction", "Intro to APIs"],
|
|
44
|
+
* });
|
|
45
|
+
* AmbiguousError.create("heading", ["Introduction", "Intro to APIs"]);
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
declare class AmbiguousError extends AmbiguousErrorBase {
|
|
49
|
+
readonly category: "validation";
|
|
50
|
+
/** Create an AmbiguousError with auto-generated message. */
|
|
51
|
+
static create(what: string, candidates: string[], context?: Record<string, unknown>): AmbiguousError;
|
|
52
|
+
exitCode(): number;
|
|
53
|
+
statusCode(): number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Assertion failed (invariant violation).
|
|
57
|
+
*
|
|
58
|
+
* Used by assertion utilities that return Result types instead of throwing.
|
|
59
|
+
* AssertionError indicates a programming bug — an invariant that should
|
|
60
|
+
* never be violated was broken. These are internal errors, not user input
|
|
61
|
+
* validation failures.
|
|
62
|
+
*
|
|
63
|
+
* **Category rationale**: Uses `internal` (not `validation`) because:
|
|
64
|
+
* - Assertions check **invariants** (programmer assumptions), not user input
|
|
65
|
+
* - A failed assertion means "this should be impossible if the code is correct"
|
|
66
|
+
* - User-facing validation uses {@link ValidationError} with helpful field info
|
|
67
|
+
* - HTTP 500 is correct: this is a server bug, not a client mistake
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* // In domain logic after validation has passed
|
|
72
|
+
* const result = assertDefined(cachedValue, "Cache should always have value after init");
|
|
73
|
+
* if (result.isErr()) {
|
|
74
|
+
* return result; // Propagate as internal error
|
|
75
|
+
* }
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* @see ValidationError - For user input validation failures (HTTP 400)
|
|
79
|
+
*/
|
|
80
|
+
declare class AssertionError extends AssertionErrorBase {
|
|
81
|
+
readonly category: "internal";
|
|
82
|
+
exitCode(): number;
|
|
83
|
+
statusCode(): number;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Requested resource not found.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* new NotFoundError({ message: "note not found: abc123", resourceType: "note", resourceId: "abc123" });
|
|
91
|
+
* new NotFoundError({
|
|
92
|
+
* message: "Heading not found",
|
|
93
|
+
* resourceType: "heading",
|
|
94
|
+
* resourceId: "h:Intro",
|
|
95
|
+
* context: { availableHeadings: ["Introduction", "Getting Started"] },
|
|
96
|
+
* });
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
declare class NotFoundError extends NotFoundErrorBase {
|
|
100
|
+
readonly category: "not_found";
|
|
101
|
+
/** Create a NotFoundError with auto-generated message. */
|
|
102
|
+
static create(resourceType: string, resourceId: string, context?: Record<string, unknown>): NotFoundError;
|
|
103
|
+
exitCode(): number;
|
|
104
|
+
statusCode(): number;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Resource already exists — the inverse of {@link NotFoundError}.
|
|
108
|
+
*
|
|
109
|
+
* Use when a create/write operation fails because the target resource
|
|
110
|
+
* is already present. Carries `resourceType` and `resourceId` to identify
|
|
111
|
+
* what already exists, mirroring {@link NotFoundError}'s structure.
|
|
112
|
+
*
|
|
113
|
+
* Maps to HTTP 409 (Conflict) and exit code 3.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```typescript
|
|
117
|
+
* new AlreadyExistsError({
|
|
118
|
+
* message: "File already exists: notes/meeting.md",
|
|
119
|
+
* resourceType: "file",
|
|
120
|
+
* resourceId: "notes/meeting.md",
|
|
121
|
+
* });
|
|
122
|
+
* AlreadyExistsError.create("file", "notes/meeting.md");
|
|
123
|
+
* ```
|
|
124
|
+
*
|
|
125
|
+
* @see ConflictError - For general state conflicts (version mismatch, concurrent modification)
|
|
126
|
+
* @see NotFoundError - The inverse: resource does not exist
|
|
127
|
+
*/
|
|
128
|
+
declare class AlreadyExistsError extends AlreadyExistsErrorBase {
|
|
129
|
+
readonly category: "conflict";
|
|
130
|
+
/** Create an AlreadyExistsError with auto-generated message. */
|
|
131
|
+
static create(resourceType: string, resourceId: string, context?: Record<string, unknown>): AlreadyExistsError;
|
|
132
|
+
exitCode(): number;
|
|
133
|
+
statusCode(): number;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* State conflict (version mismatch, concurrent modification).
|
|
137
|
+
*
|
|
138
|
+
* Use for general conflicts that don't fit {@link AlreadyExistsError}:
|
|
139
|
+
* optimistic locking failures, concurrent writes, ETag mismatches,
|
|
140
|
+
* or any case where the operation can't proceed due to state divergence.
|
|
141
|
+
*
|
|
142
|
+
* Maps to HTTP 409 (Conflict) and exit code 3.
|
|
143
|
+
*
|
|
144
|
+
* **Choosing the right conflict error:**
|
|
145
|
+
* - Resource already exists? Use {@link AlreadyExistsError}
|
|
146
|
+
* - Version/ETag mismatch? Use {@link ConflictError}
|
|
147
|
+
* - Concurrent modification detected? Use {@link ConflictError}
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```typescript
|
|
151
|
+
* new ConflictError({ message: "Resource was modified by another process" });
|
|
152
|
+
* ConflictError.create("ETag mismatch: expected abc, got def");
|
|
153
|
+
* ```
|
|
154
|
+
*
|
|
155
|
+
* @see AlreadyExistsError - For "resource already exists" specifically
|
|
156
|
+
*/
|
|
157
|
+
declare class ConflictError extends ConflictErrorBase {
|
|
158
|
+
readonly category: "conflict";
|
|
159
|
+
/** Create a ConflictError with optional context. */
|
|
160
|
+
static create(message: string, context?: Record<string, unknown>): ConflictError;
|
|
161
|
+
exitCode(): number;
|
|
162
|
+
statusCode(): number;
|
|
163
|
+
}
|
|
164
|
+
export { ValidationError, AmbiguousError, AssertionError, NotFoundError, AlreadyExistsError, ConflictError };
|