@outfitter/contracts 0.4.2 → 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/dist/actions.d.ts +8 -3
- package/dist/assert/index.d.ts +7 -3
- package/dist/assert/index.js +32 -1
- package/dist/context.d.ts +8 -3
- package/dist/context.js +1 -1
- package/dist/envelope.d.ts +6 -2
- package/dist/envelope.js +5 -6
- 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/hints.js +1 -0
- package/dist/index.d.ts +24 -13
- package/dist/index.js +5 -3
- 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-5k6q4n48.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/recovery.d.ts +6 -2
- package/dist/resilience.d.ts +6 -2
- package/dist/resilience.js +6 -2
- package/dist/schema.d.ts +2 -1
- package/dist/schema.js +15 -187
- 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-hgh47193.js +46 -0
- 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 +8 -36
- package/dist/wrap-error.d.ts +7 -0
- package/dist/wrap-error.js +71 -0
- package/package.json +41 -17
- 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-9wtm5nsw.d.ts +0 -42
- package/dist/shared/@outfitter/contracts-mmg0npfk.d.ts +0 -30
- package/dist/shared/@outfitter/contracts-phjhz5q3.js +0 -293
- package/dist/shared/@outfitter/contracts-t79engf9.d.ts +0 -60
|
@@ -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 */
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
ValidationError
|
|
4
|
+
} from "./contracts-vhr2ep6b.js";
|
|
5
|
+
|
|
6
|
+
// packages/contracts/src/validation.ts
|
|
7
|
+
import { Result } from "better-result";
|
|
8
|
+
function formatZodIssues(issues) {
|
|
9
|
+
return issues.map((issue) => {
|
|
10
|
+
const path = issue.path.length > 0 ? issue.path.join(".") : "(root)";
|
|
11
|
+
return `${path}: ${issue.message}`;
|
|
12
|
+
}).join("; ");
|
|
13
|
+
}
|
|
14
|
+
function extractField(issues) {
|
|
15
|
+
const firstIssue = issues[0];
|
|
16
|
+
if (firstIssue && firstIssue.path.length > 0) {
|
|
17
|
+
return firstIssue.path.join(".");
|
|
18
|
+
}
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
function createValidator(schema) {
|
|
22
|
+
return (input) => {
|
|
23
|
+
return validateInput(schema, input);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function parseWithSchema(schema, data) {
|
|
27
|
+
const parseResult = schema.safeParse(data);
|
|
28
|
+
if (parseResult.success) {
|
|
29
|
+
return Result.ok(parseResult.data);
|
|
30
|
+
}
|
|
31
|
+
const message = formatZodIssues(parseResult.error.issues);
|
|
32
|
+
const field = extractField(parseResult.error.issues);
|
|
33
|
+
const errorProps = { message };
|
|
34
|
+
if (field !== undefined) {
|
|
35
|
+
errorProps.field = field;
|
|
36
|
+
}
|
|
37
|
+
return Result.err(new ValidationError(errorProps));
|
|
38
|
+
}
|
|
39
|
+
function validateInput(schema, input) {
|
|
40
|
+
return parseWithSchema(schema, input);
|
|
41
|
+
}
|
|
42
|
+
function parseInput(schema, data) {
|
|
43
|
+
return parseWithSchema(schema, data);
|
|
44
|
+
}
|
|
45
|
+
|
|
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 };
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport-agnostic streaming types for handler progress reporting.
|
|
3
|
+
*
|
|
4
|
+
* {@link StreamEvent} is a discriminated union of events that handlers emit
|
|
5
|
+
* via {@link ProgressCallback} to report progress. Transport adapters (CLI NDJSON,
|
|
6
|
+
* MCP notifications) consume these events without the handler knowing the transport.
|
|
7
|
+
*
|
|
8
|
+
* - {@link StreamStartEvent} — Emitted once at the beginning of a streaming operation
|
|
9
|
+
* - {@link StreamStepEvent} — Emitted when a named phase completes
|
|
10
|
+
* - {@link StreamProgressEvent} — Emitted for incremental progress updates
|
|
11
|
+
*
|
|
12
|
+
* @packageDocumentation
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Emitted once at the beginning of a streaming operation.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* ctx.progress?.({
|
|
20
|
+
* type: "start",
|
|
21
|
+
* command: "check tsdoc",
|
|
22
|
+
* ts: new Date().toISOString(),
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
interface StreamStartEvent {
|
|
27
|
+
/** Discriminator — always `"start"`. */
|
|
28
|
+
type: "start";
|
|
29
|
+
/** The command or operation being executed. */
|
|
30
|
+
command: string;
|
|
31
|
+
/** ISO-8601 timestamp of when the operation started. */
|
|
32
|
+
ts: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Emitted when a named phase or step completes.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* ctx.progress?.({
|
|
40
|
+
* type: "step",
|
|
41
|
+
* name: "scanning files",
|
|
42
|
+
* status: "complete",
|
|
43
|
+
* duration_ms: 42,
|
|
44
|
+
* });
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
interface StreamStepEvent {
|
|
48
|
+
/** Discriminator — always `"step"`. */
|
|
49
|
+
type: "step";
|
|
50
|
+
/** Name of the phase or step. */
|
|
51
|
+
name: string;
|
|
52
|
+
/**
|
|
53
|
+
* Status of the step.
|
|
54
|
+
*
|
|
55
|
+
* Common values are `"running"`, `"complete"`, and `"failed"`.
|
|
56
|
+
* Custom values are still allowed for forward compatibility.
|
|
57
|
+
*/
|
|
58
|
+
status: "running" | "complete" | "failed" | (string & {});
|
|
59
|
+
/** Optional duration of the step in milliseconds. */
|
|
60
|
+
duration_ms?: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Emitted for incremental progress updates (e.g., processing N of M items).
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* ctx.progress?.({
|
|
68
|
+
* type: "progress",
|
|
69
|
+
* current: 5,
|
|
70
|
+
* total: 10,
|
|
71
|
+
* message: "Processing file 5 of 10",
|
|
72
|
+
* });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
interface StreamProgressEvent {
|
|
76
|
+
/** Discriminator — always `"progress"`. */
|
|
77
|
+
type: "progress";
|
|
78
|
+
/** Current progress count. */
|
|
79
|
+
current: number;
|
|
80
|
+
/** Total expected count. */
|
|
81
|
+
total: number;
|
|
82
|
+
/** Optional human-readable progress message. */
|
|
83
|
+
message?: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Discriminated union of all stream event types.
|
|
87
|
+
*
|
|
88
|
+
* Handlers emit these events via `ctx.progress?.()` to report progress
|
|
89
|
+
* without coupling to any specific transport. The `type` field serves
|
|
90
|
+
* as the discriminator for narrowing.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```typescript
|
|
94
|
+
* function handleEvent(event: StreamEvent) {
|
|
95
|
+
* switch (event.type) {
|
|
96
|
+
* case "start":
|
|
97
|
+
* console.log(`Starting: ${event.command}`);
|
|
98
|
+
* break;
|
|
99
|
+
* case "step":
|
|
100
|
+
* console.log(`Step: ${event.name} — ${event.status}`);
|
|
101
|
+
* break;
|
|
102
|
+
* case "progress":
|
|
103
|
+
* console.log(`${event.current}/${event.total}`);
|
|
104
|
+
* break;
|
|
105
|
+
* }
|
|
106
|
+
* }
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
type StreamEvent = StreamStartEvent | StreamStepEvent | StreamProgressEvent;
|
|
110
|
+
/**
|
|
111
|
+
* Callback type for reporting streaming progress events.
|
|
112
|
+
*
|
|
113
|
+
* Transport adapters provide this callback to handlers via `ctx.progress`.
|
|
114
|
+
* When `progress` is `undefined` on {@link HandlerContext}, the handler
|
|
115
|
+
* does not stream — it simply returns its final result.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```typescript
|
|
119
|
+
* const progress: ProgressCallback = (event) => {
|
|
120
|
+
* process.stdout.write(JSON.stringify(event) + "\n");
|
|
121
|
+
* };
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
type ProgressCallback = (event: StreamEvent) => void;
|
|
125
|
+
export { StreamStartEvent, StreamStepEvent, StreamProgressEvent, StreamEvent, ProgressCallback };
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error taxonomy: categories, code maps, metadata, and lookup functions.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Error categories for classification, exit codes, and HTTP status mapping.
|
|
8
|
+
*
|
|
9
|
+
* Used for:
|
|
10
|
+
* - CLI exit code determination
|
|
11
|
+
* - HTTP status code mapping
|
|
12
|
+
* - Error grouping in logs and metrics
|
|
13
|
+
* - Client retry decisions (transient vs permanent)
|
|
14
|
+
*/
|
|
15
|
+
type ErrorCategory = "validation" | "not_found" | "conflict" | "permission" | "timeout" | "rate_limit" | "network" | "internal" | "auth" | "cancelled";
|
|
16
|
+
/**
|
|
17
|
+
* Maps error category to CLI exit code.
|
|
18
|
+
* Non-zero exit indicates error; specific values for script automation.
|
|
19
|
+
*/
|
|
20
|
+
declare const exitCodeMap: Record<ErrorCategory, number>;
|
|
21
|
+
/**
|
|
22
|
+
* Maps error category to HTTP status code.
|
|
23
|
+
* Used by MCP servers and API responses.
|
|
24
|
+
*/
|
|
25
|
+
declare const statusCodeMap: Record<ErrorCategory, number>;
|
|
26
|
+
/**
|
|
27
|
+
* Maps error category to JSON-RPC 2.0 error code (for MCP protocol compliance).
|
|
28
|
+
*
|
|
29
|
+
* Standard protocol codes (-32600 series) for direct mappings:
|
|
30
|
+
* - validation -> -32602 (Invalid params)
|
|
31
|
+
* - internal -> -32603 (Internal error)
|
|
32
|
+
*
|
|
33
|
+
* Implementation-defined server error codes (-32000 to -32099) for domain categories:
|
|
34
|
+
* - auth -> -32000
|
|
35
|
+
* - timeout -> -32001
|
|
36
|
+
* - conflict -> -32002
|
|
37
|
+
* - permission -> -32003
|
|
38
|
+
* - rate_limit -> -32004
|
|
39
|
+
* - network -> -32005
|
|
40
|
+
* - cancelled -> -32006
|
|
41
|
+
* - not_found -> -32007
|
|
42
|
+
*/
|
|
43
|
+
declare const jsonRpcCodeMap: Record<ErrorCategory, number>;
|
|
44
|
+
/**
|
|
45
|
+
* Maps error category to whether the error is safe to retry (for agent safety).
|
|
46
|
+
*
|
|
47
|
+
* Transient errors (timeout, rate_limit, network) are retryable — they may
|
|
48
|
+
* succeed on a subsequent attempt. Permanent errors require human intervention
|
|
49
|
+
* or input correction before retrying would help.
|
|
50
|
+
*/
|
|
51
|
+
declare const retryableMap: Record<ErrorCategory, boolean>;
|
|
52
|
+
/**
|
|
53
|
+
* Unified metadata for an error category.
|
|
54
|
+
*
|
|
55
|
+
* Combines exit code, HTTP status, JSON-RPC code, and retryable flag
|
|
56
|
+
* into a single lookup for transport adapters and agent tooling.
|
|
57
|
+
*/
|
|
58
|
+
interface ErrorCategoryMeta {
|
|
59
|
+
exitCode: number;
|
|
60
|
+
statusCode: number;
|
|
61
|
+
jsonRpcCode: number;
|
|
62
|
+
retryable: boolean;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get unified metadata for an error category.
|
|
66
|
+
*
|
|
67
|
+
* Returns exit code, HTTP status code, JSON-RPC error code, and retryable
|
|
68
|
+
* flag in a single object. Useful for transport adapters that need all
|
|
69
|
+
* metadata at once.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* const meta = errorCategoryMeta("validation");
|
|
74
|
+
* // { exitCode: 1, statusCode: 400, jsonRpcCode: -32602, retryable: false }
|
|
75
|
+
*
|
|
76
|
+
* const meta = errorCategoryMeta("timeout");
|
|
77
|
+
* // { exitCode: 5, statusCode: 504, jsonRpcCode: -32001, retryable: true }
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
declare function errorCategoryMeta(category: ErrorCategory): ErrorCategoryMeta;
|
|
81
|
+
/**
|
|
82
|
+
* Numeric error codes for granular error identification.
|
|
83
|
+
*
|
|
84
|
+
* Ranges by category:
|
|
85
|
+
* - validation: 1000-1999
|
|
86
|
+
* - not_found: 2000-2999
|
|
87
|
+
* - conflict: 3000-3999
|
|
88
|
+
* - permission: 4000-4999
|
|
89
|
+
* - timeout: 5000-5999
|
|
90
|
+
* - rate_limit: 6000-6999
|
|
91
|
+
* - network: 7000-7999
|
|
92
|
+
* - internal: 8000-8999
|
|
93
|
+
* - auth: 9000-9999
|
|
94
|
+
* - cancelled: 10000-10999
|
|
95
|
+
*/
|
|
96
|
+
declare const ERROR_CODES: {
|
|
97
|
+
readonly validation: {
|
|
98
|
+
readonly FIELD_REQUIRED: 1001;
|
|
99
|
+
readonly INVALID_FORMAT: 1002;
|
|
100
|
+
readonly OUT_OF_RANGE: 1003;
|
|
101
|
+
readonly TYPE_MISMATCH: 1004;
|
|
102
|
+
readonly AMBIGUOUS_MATCH: 1005;
|
|
103
|
+
};
|
|
104
|
+
readonly not_found: {
|
|
105
|
+
readonly RESOURCE_NOT_FOUND: 2001;
|
|
106
|
+
readonly FILE_NOT_FOUND: 2002;
|
|
107
|
+
};
|
|
108
|
+
readonly conflict: {
|
|
109
|
+
readonly ALREADY_EXISTS: 3001;
|
|
110
|
+
readonly VERSION_MISMATCH: 3002;
|
|
111
|
+
};
|
|
112
|
+
readonly permission: {
|
|
113
|
+
readonly FORBIDDEN: 4001;
|
|
114
|
+
readonly INSUFFICIENT_RIGHTS: 4002;
|
|
115
|
+
};
|
|
116
|
+
readonly timeout: {
|
|
117
|
+
readonly OPERATION_TIMEOUT: 5001;
|
|
118
|
+
readonly CONNECTION_TIMEOUT: 5002;
|
|
119
|
+
};
|
|
120
|
+
readonly rate_limit: {
|
|
121
|
+
readonly QUOTA_EXCEEDED: 6001;
|
|
122
|
+
readonly THROTTLED: 6002;
|
|
123
|
+
};
|
|
124
|
+
readonly network: {
|
|
125
|
+
readonly CONNECTION_REFUSED: 7001;
|
|
126
|
+
readonly DNS_FAILED: 7002;
|
|
127
|
+
};
|
|
128
|
+
readonly internal: {
|
|
129
|
+
readonly UNEXPECTED_STATE: 8001;
|
|
130
|
+
readonly ASSERTION_FAILED: 8002;
|
|
131
|
+
};
|
|
132
|
+
readonly auth: {
|
|
133
|
+
readonly INVALID_TOKEN: 9001;
|
|
134
|
+
readonly EXPIRED_TOKEN: 9002;
|
|
135
|
+
};
|
|
136
|
+
readonly cancelled: {
|
|
137
|
+
readonly USER_CANCELLED: 10_001;
|
|
138
|
+
readonly SIGNAL_RECEIVED: 10_002;
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Union type of all numeric error codes.
|
|
143
|
+
* Useful for type-safe error code handling.
|
|
144
|
+
*/
|
|
145
|
+
type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES][keyof (typeof ERROR_CODES)[keyof typeof ERROR_CODES]];
|
|
146
|
+
/**
|
|
147
|
+
* Serialized error format for JSON transport.
|
|
148
|
+
*/
|
|
149
|
+
interface SerializedError {
|
|
150
|
+
_tag: string;
|
|
151
|
+
category: ErrorCategory;
|
|
152
|
+
context?: Record<string, unknown>;
|
|
153
|
+
message: string;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Base interface for OutfitterError properties.
|
|
157
|
+
* All concrete error classes must include these fields.
|
|
158
|
+
*
|
|
159
|
+
* @deprecated Use `OutfitterError` (or concrete error class constructor props) instead. This alias will be removed in v1.0.
|
|
160
|
+
*/
|
|
161
|
+
interface KitErrorProps {
|
|
162
|
+
category: ErrorCategory;
|
|
163
|
+
context?: Record<string, unknown>;
|
|
164
|
+
message: string;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Get CLI exit code for an error category.
|
|
168
|
+
*/
|
|
169
|
+
declare function getExitCode(category: ErrorCategory): number;
|
|
170
|
+
/**
|
|
171
|
+
* Get HTTP status code for an error category.
|
|
172
|
+
*/
|
|
173
|
+
declare function getStatusCode(category: ErrorCategory): number;
|
|
174
|
+
export { ErrorCategory, exitCodeMap, statusCodeMap, jsonRpcCodeMap, retryableMap, ErrorCategoryMeta, errorCategoryMeta, ERROR_CODES, ErrorCode, SerializedError, KitErrorProps, getExitCode, getStatusCode };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { OutfitterError } from "./contracts-7a0xmwbg.js";
|
|
2
|
+
import { SerializedError } from "./contracts-njb2art4.js";
|
|
3
|
+
/**
|
|
4
|
+
* Options for error serialization.
|
|
5
|
+
*/
|
|
6
|
+
interface SerializeErrorOptions {
|
|
7
|
+
/** Include stack trace (default: false in production, true otherwise). */
|
|
8
|
+
includeStack?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Serialize a OutfitterError to JSON-safe format.
|
|
12
|
+
*
|
|
13
|
+
* Strips stack traces in production, preserves in development.
|
|
14
|
+
* Automatically redacts sensitive values from context.
|
|
15
|
+
*
|
|
16
|
+
* @param error - The error to serialize
|
|
17
|
+
* @param options - Serialization options
|
|
18
|
+
* @param isProduction - Whether the environment is production. When omitted, falls back to
|
|
19
|
+
* `process.env["NODE_ENV"] === "production"` for safe-by-default stack stripping.
|
|
20
|
+
* @returns JSON-safe serialized error
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* const serialized = serializeError(new NotFoundError("note", "abc123"));
|
|
25
|
+
* // { _tag: "NotFoundError", category: "not_found", message: "note not found: abc123", context: { resourceType: "note", resourceId: "abc123" } }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
declare function serializeError(error: OutfitterError, options?: SerializeErrorOptions, isProduction?: boolean): SerializedError;
|
|
29
|
+
/**
|
|
30
|
+
* Deserialize error from JSON (e.g., from MCP response).
|
|
31
|
+
*
|
|
32
|
+
* Returns a typed OutfitterError subclass based on _tag.
|
|
33
|
+
*
|
|
34
|
+
* @param data - Serialized error data
|
|
35
|
+
* @returns Reconstructed OutfitterError instance
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* const error = deserializeError(jsonData);
|
|
40
|
+
* if (error._tag === "NotFoundError") {
|
|
41
|
+
* // TypeScript knows error.resourceType exists
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
declare function deserializeError(data: SerializedError): OutfitterError;
|
|
46
|
+
export { SerializeErrorOptions, serializeError, deserializeError };
|