@outfitter/contracts 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +41 -2
  2. package/dist/actions.d.ts +4 -3
  3. package/dist/assert/index.d.ts +2 -2
  4. package/dist/assert/index.js +2 -2
  5. package/dist/context.d.ts +4 -3
  6. package/dist/context.js +1 -1
  7. package/dist/envelope.d.ts +2 -2
  8. package/dist/envelope.js +4 -4
  9. package/dist/errors.d.ts +2 -2
  10. package/dist/errors.js +6 -2
  11. package/dist/handler.d.ts +3 -2
  12. package/dist/index.d.ts +14 -13
  13. package/dist/index.js +42 -32
  14. package/dist/logging.d.ts +2 -0
  15. package/dist/logging.js +7 -0
  16. package/dist/recovery.d.ts +2 -2
  17. package/dist/resilience.d.ts +2 -2
  18. package/dist/resilience.js +2 -2
  19. package/dist/result/index.d.ts +2 -2
  20. package/dist/result/index.js +3 -1
  21. package/dist/result/utilities.d.ts +2 -2
  22. package/dist/result/utilities.js +3 -1
  23. package/dist/serialization.d.ts +2 -2
  24. package/dist/serialization.js +2 -2
  25. package/dist/shared/@outfitter/{contracts-sf5p84cc.js → contracts-0snpmkdt.js} +1 -1
  26. package/dist/shared/@outfitter/{contracts-t3j7fba3.d.ts → contracts-18vcxecr.d.ts} +1 -1
  27. package/dist/shared/@outfitter/{contracts-wwkk3j4m.d.ts → contracts-25bkj17f.d.ts} +2 -1
  28. package/dist/shared/@outfitter/contracts-2g8r01zf.d.ts +73 -0
  29. package/dist/shared/@outfitter/{contracts-dbpj0b4k.js → contracts-5k6q4n48.js} +61 -9
  30. package/dist/shared/@outfitter/{contracts-g82822x1.d.ts → contracts-6j6z9dsd.d.ts} +1 -1
  31. package/dist/shared/@outfitter/{contracts-y8qg2nvz.js → contracts-agmt8915.js} +6 -6
  32. package/dist/shared/@outfitter/{contracts-109drsem.d.ts → contracts-ar0etwtx.d.ts} +23 -1
  33. package/dist/shared/@outfitter/{contracts-37891j7e.d.ts → contracts-bdwg55c5.d.ts} +1 -1
  34. package/dist/shared/@outfitter/{contracts-chnm7kvm.js → contracts-btg89x4h.js} +3 -3
  35. package/dist/shared/@outfitter/{contracts-d1thk8cv.js → contracts-cp5c6dws.js} +1 -1
  36. package/dist/shared/@outfitter/{contracts-47aemgd3.d.ts → contracts-evxky148.d.ts} +1 -1
  37. package/dist/shared/@outfitter/{contracts-0taqjz0a.d.ts → contracts-j08e95jw.d.ts} +1 -1
  38. package/dist/shared/@outfitter/{contracts-q9tjk2zp.d.ts → contracts-jggbn5tn.d.ts} +1 -1
  39. package/dist/shared/@outfitter/{contracts-jhsgc85s.js → contracts-phjhz5q3.js} +95 -2
  40. package/dist/shared/@outfitter/{contracts-aq745b7n.js → contracts-r21yet6j.js} +1 -1
  41. package/dist/shared/@outfitter/{contracts-ev4a68sg.d.ts → contracts-r35bn9p6.d.ts} +124 -3
  42. package/dist/shared/@outfitter/{contracts-vtrf6rbx.d.ts → contracts-sf1z80yc.d.ts} +2 -2
  43. package/dist/shared/@outfitter/contracts-sm6vak1a.js +14 -0
  44. package/dist/shared/@outfitter/{contracts-yp3derbe.d.ts → contracts-ss9vjjft.d.ts} +3 -37
  45. package/dist/shared/@outfitter/{contracts-r06bafs8.js → contracts-zx72gyh1.js} +6 -1
  46. package/dist/validation.d.ts +2 -2
  47. package/dist/validation.js +2 -2
  48. package/package.json +50 -44
@@ -1,10 +1,8 @@
1
1
  // @bun
2
2
  import {
3
- DEFAULT_PATTERNS,
4
- DEFAULT_SENSITIVE_KEYS,
5
- createRedactor
6
- } from "./contracts-s15x2rs4.js";
7
- import {
3
+ AlreadyExistsError,
4
+ AmbiguousError,
5
+ AssertionError,
8
6
  AuthError,
9
7
  CancelledError,
10
8
  ConflictError,
@@ -15,13 +13,21 @@ import {
15
13
  RateLimitError,
16
14
  TimeoutError,
17
15
  ValidationError
18
- } from "./contracts-jhsgc85s.js";
16
+ } from "./contracts-phjhz5q3.js";
17
+ import {
18
+ DEFAULT_PATTERNS,
19
+ DEFAULT_SENSITIVE_KEYS,
20
+ createRedactor
21
+ } from "./contracts-s15x2rs4.js";
19
22
 
20
23
  // packages/contracts/src/serialization.ts
21
24
  import { Result } from "better-result";
22
25
  var errorRegistry = {
23
26
  ValidationError,
27
+ AmbiguousError,
28
+ AssertionError,
24
29
  NotFoundError,
30
+ AlreadyExistsError,
25
31
  ConflictError,
26
32
  PermissionError,
27
33
  TimeoutError,
@@ -46,10 +52,22 @@ function extractContext(error) {
46
52
  }
47
53
  case "NotFoundError": {
48
54
  const nfe = error;
55
+ if (nfe.context !== undefined) {
56
+ Object.assign(context, nfe.context);
57
+ }
49
58
  context["resourceType"] = nfe.resourceType;
50
59
  context["resourceId"] = nfe.resourceId;
51
60
  break;
52
61
  }
62
+ case "AlreadyExistsError": {
63
+ const aee = error;
64
+ if (aee.context !== undefined) {
65
+ Object.assign(context, aee.context);
66
+ }
67
+ context["resourceType"] = aee.resourceType;
68
+ context["resourceId"] = aee.resourceId;
69
+ break;
70
+ }
53
71
  case "TimeoutError": {
54
72
  const te = error;
55
73
  context["operation"] = te.operation;
@@ -70,6 +88,13 @@ function extractContext(error) {
70
88
  }
71
89
  break;
72
90
  }
91
+ case "AmbiguousError": {
92
+ const amb = error;
93
+ context["candidates"] = amb.candidates;
94
+ break;
95
+ }
96
+ case "AssertionError":
97
+ break;
73
98
  case "ConflictError":
74
99
  case "PermissionError":
75
100
  case "NetworkError":
@@ -128,12 +153,30 @@ function deserializeError(data) {
128
153
  }
129
154
  return new ValidationError(props);
130
155
  }
131
- case "NotFoundError":
132
- return new NotFoundError({
156
+ case "NotFoundError": {
157
+ const props = {
133
158
  message: data.message,
134
159
  resourceType: context["resourceType"] ?? "unknown",
135
160
  resourceId: context["resourceId"] ?? "unknown"
136
- });
161
+ };
162
+ const contextWithoutIdentity = Object.fromEntries(Object.entries(context).filter(([key]) => key !== "resourceType" && key !== "resourceId"));
163
+ if (Object.keys(contextWithoutIdentity).length > 0) {
164
+ props.context = contextWithoutIdentity;
165
+ }
166
+ return new NotFoundError(props);
167
+ }
168
+ case "AlreadyExistsError": {
169
+ const props = {
170
+ message: data.message,
171
+ resourceType: context["resourceType"] ?? "unknown",
172
+ resourceId: context["resourceId"] ?? "unknown"
173
+ };
174
+ const contextWithoutIdentity = Object.fromEntries(Object.entries(context).filter(([key]) => key !== "resourceType" && key !== "resourceId"));
175
+ if (Object.keys(contextWithoutIdentity).length > 0) {
176
+ props.context = contextWithoutIdentity;
177
+ }
178
+ return new AlreadyExistsError(props);
179
+ }
137
180
  case "ConflictError": {
138
181
  const props = {
139
182
  message: data.message
@@ -196,6 +239,15 @@ function deserializeError(data) {
196
239
  }
197
240
  return new AuthError(props);
198
241
  }
242
+ case "AmbiguousError":
243
+ return new AmbiguousError({
244
+ message: data.message,
245
+ candidates: context["candidates"] ?? []
246
+ });
247
+ case "AssertionError":
248
+ return new AssertionError({
249
+ message: data.message
250
+ });
199
251
  case "CancelledError":
200
252
  return new CancelledError({
201
253
  message: data.message
@@ -1,4 +1,4 @@
1
- import { ValidationError } from "./contracts-ev4a68sg";
1
+ import { ValidationError } from "./contracts-r35bn9p6";
2
2
  import { Result } from "better-result";
3
3
  import { z } from "zod";
4
4
  /**
@@ -1,12 +1,12 @@
1
1
  // @bun
2
2
  // packages/contracts/src/context.ts
3
3
  var noopLogger = {
4
- trace: () => {},
5
- debug: () => {},
6
- info: () => {},
7
- warn: () => {},
8
- error: () => {},
9
- fatal: () => {},
4
+ trace: (..._args) => {},
5
+ debug: (..._args) => {},
6
+ info: (..._args) => {},
7
+ warn: (..._args) => {},
8
+ error: (..._args) => {},
9
+ fatal: (..._args) => {},
10
10
  child: () => noopLogger
11
11
  };
12
12
  function createContext(options) {
@@ -100,4 +100,26 @@ declare const combine3: <
100
100
  T3,
101
101
  E
102
102
  >(r1: Result<T1, E>, r2: Result<T2, E>, r3: Result<T3, E>) => Result<[T1, T2, T3], E>;
103
- export { unwrapOrElse, orElse, combine2, combine3 };
103
+ /**
104
+ * Extract value from Ok, or throw with a contextual error message.
105
+ *
106
+ * Like `unwrap()` but with a caller-provided message for better
107
+ * debugging context. Use at system boundaries (CLI adapters, MCP
108
+ * handlers) where you need the value and want a clear error.
109
+ *
110
+ * @param result - The Result to unwrap
111
+ * @param message - Context message prepended to the error
112
+ * @returns The success value
113
+ * @throws Error with contextual message if Result is Err
114
+ *
115
+ * @example
116
+ * ```typescript
117
+ * const config = expect(loadConfig(), "Failed to load config");
118
+ * // On Err, throws: "Failed to load config: <error details>"
119
+ * ```
120
+ */
121
+ declare const expect: <
122
+ T,
123
+ E
124
+ >(result: Result<T, E>, message: string) => T;
125
+ export { unwrapOrElse, orElse, combine2, combine3, expect };
@@ -1,4 +1,4 @@
1
- import { OutfitterError, TimeoutError } from "./contracts-ev4a68sg";
1
+ import { OutfitterError, TimeoutError } from "./contracts-r35bn9p6";
2
2
  import { Result } from "better-result";
3
3
  /**
4
4
  * Options for retry behavior.
@@ -1,13 +1,13 @@
1
1
  // @bun
2
2
  import {
3
3
  serializeError
4
- } from "./contracts-dbpj0b4k.js";
4
+ } from "./contracts-5k6q4n48.js";
5
5
  import {
6
6
  generateRequestId
7
- } from "./contracts-y8qg2nvz.js";
7
+ } from "./contracts-agmt8915.js";
8
8
  import {
9
9
  statusCodeMap
10
- } from "./contracts-jhsgc85s.js";
10
+ } from "./contracts-phjhz5q3.js";
11
11
 
12
12
  // packages/contracts/src/envelope.ts
13
13
  function buildMeta(overrides) {
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  AssertionError
4
- } from "./contracts-jhsgc85s.js";
4
+ } from "./contracts-phjhz5q3.js";
5
5
 
6
6
  // packages/contracts/src/assert/index.ts
7
7
  import { Result } from "better-result";
@@ -1,4 +1,4 @@
1
- import { ErrorCategory } from "./contracts-ev4a68sg";
1
+ import { ErrorCategory } from "./contracts-r35bn9p6";
2
2
  /**
3
3
  * Backoff strategy configuration options
4
4
  */
@@ -1,4 +1,4 @@
1
- import { OutfitterError, SerializedError, ValidationError } from "./contracts-ev4a68sg";
1
+ import { OutfitterError, SerializedError, ValidationError } from "./contracts-r35bn9p6";
2
2
  import { Result } from "better-result";
3
3
  import { z } from "zod";
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { OutfitterError, SerializedError } from "./contracts-ev4a68sg";
1
+ import { OutfitterError, SerializedError } from "./contracts-r35bn9p6";
2
2
  import { Result } from "better-result";
3
3
  /**
4
4
  * Metadata attached to every response envelope.
@@ -30,7 +30,8 @@ var ERROR_CODES = {
30
30
  FIELD_REQUIRED: 1001,
31
31
  INVALID_FORMAT: 1002,
32
32
  OUT_OF_RANGE: 1003,
33
- TYPE_MISMATCH: 1004
33
+ TYPE_MISMATCH: 1004,
34
+ AMBIGUOUS_MATCH: 1005
34
35
  },
35
36
  not_found: {
36
37
  RESOURCE_NOT_FOUND: 2001,
@@ -76,8 +77,10 @@ function getStatusCode(category) {
76
77
  return statusCodeMap[category];
77
78
  }
78
79
  var ValidationErrorBase = TaggedError("ValidationError")();
80
+ var AmbiguousErrorBase = TaggedError("AmbiguousError")();
79
81
  var AssertionErrorBase = TaggedError("AssertionError")();
80
82
  var NotFoundErrorBase = TaggedError("NotFoundError")();
83
+ var AlreadyExistsErrorBase = TaggedError("AlreadyExistsError")();
81
84
  var ConflictErrorBase = TaggedError("ConflictError")();
82
85
  var PermissionErrorBase = TaggedError("PermissionError")();
83
86
  var TimeoutErrorBase = TaggedError("TimeoutError")();
@@ -89,6 +92,36 @@ var CancelledErrorBase = TaggedError("CancelledError")();
89
92
 
90
93
  class ValidationError extends ValidationErrorBase {
91
94
  category = "validation";
95
+ static create(field, reason, context) {
96
+ return new ValidationError({
97
+ message: `${field}: ${reason}`,
98
+ field,
99
+ ...context != null && { context }
100
+ });
101
+ }
102
+ static fromMessage(message, context) {
103
+ return new ValidationError({
104
+ message,
105
+ ...context != null && { context }
106
+ });
107
+ }
108
+ exitCode() {
109
+ return getExitCode(this.category);
110
+ }
111
+ statusCode() {
112
+ return getStatusCode(this.category);
113
+ }
114
+ }
115
+
116
+ class AmbiguousError extends AmbiguousErrorBase {
117
+ category = "validation";
118
+ static create(what, candidates, context) {
119
+ return new AmbiguousError({
120
+ message: `Ambiguous ${what}: ${candidates.length} matches found`,
121
+ candidates,
122
+ ...context != null && { context }
123
+ });
124
+ }
92
125
  exitCode() {
93
126
  return getExitCode(this.category);
94
127
  }
@@ -109,6 +142,32 @@ class AssertionError extends AssertionErrorBase {
109
142
 
110
143
  class NotFoundError extends NotFoundErrorBase {
111
144
  category = "not_found";
145
+ static create(resourceType, resourceId, context) {
146
+ return new NotFoundError({
147
+ message: `${resourceType} not found: ${resourceId}`,
148
+ resourceType,
149
+ resourceId,
150
+ ...context != null && { context }
151
+ });
152
+ }
153
+ exitCode() {
154
+ return getExitCode(this.category);
155
+ }
156
+ statusCode() {
157
+ return getStatusCode(this.category);
158
+ }
159
+ }
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
+ }
112
171
  exitCode() {
113
172
  return getExitCode(this.category);
114
173
  }
@@ -119,6 +178,9 @@ class NotFoundError extends NotFoundErrorBase {
119
178
 
120
179
  class ConflictError extends ConflictErrorBase {
121
180
  category = "conflict";
181
+ static create(message, context) {
182
+ return new ConflictError({ message, ...context != null && { context } });
183
+ }
122
184
  exitCode() {
123
185
  return getExitCode(this.category);
124
186
  }
@@ -129,6 +191,12 @@ class ConflictError extends ConflictErrorBase {
129
191
 
130
192
  class PermissionError extends PermissionErrorBase {
131
193
  category = "permission";
194
+ static create(message, context) {
195
+ return new PermissionError({
196
+ message,
197
+ ...context != null && { context }
198
+ });
199
+ }
132
200
  exitCode() {
133
201
  return getExitCode(this.category);
134
202
  }
@@ -139,6 +207,13 @@ class PermissionError extends PermissionErrorBase {
139
207
 
140
208
  class TimeoutError extends TimeoutErrorBase {
141
209
  category = "timeout";
210
+ static create(operation, timeoutMs) {
211
+ return new TimeoutError({
212
+ message: `${operation} timed out after ${timeoutMs}ms`,
213
+ operation,
214
+ timeoutMs
215
+ });
216
+ }
142
217
  exitCode() {
143
218
  return getExitCode(this.category);
144
219
  }
@@ -149,6 +224,12 @@ class TimeoutError extends TimeoutErrorBase {
149
224
 
150
225
  class RateLimitError extends RateLimitErrorBase {
151
226
  category = "rate_limit";
227
+ static create(message, retryAfterSeconds) {
228
+ return new RateLimitError({
229
+ message,
230
+ ...retryAfterSeconds != null && { retryAfterSeconds }
231
+ });
232
+ }
152
233
  exitCode() {
153
234
  return getExitCode(this.category);
154
235
  }
@@ -159,6 +240,9 @@ class RateLimitError extends RateLimitErrorBase {
159
240
 
160
241
  class NetworkError extends NetworkErrorBase {
161
242
  category = "network";
243
+ static create(message, context) {
244
+ return new NetworkError({ message, ...context != null && { context } });
245
+ }
162
246
  exitCode() {
163
247
  return getExitCode(this.category);
164
248
  }
@@ -169,6 +253,9 @@ class NetworkError extends NetworkErrorBase {
169
253
 
170
254
  class InternalError extends InternalErrorBase {
171
255
  category = "internal";
256
+ static create(message, context) {
257
+ return new InternalError({ message, ...context != null && { context } });
258
+ }
172
259
  exitCode() {
173
260
  return getExitCode(this.category);
174
261
  }
@@ -179,6 +266,9 @@ class InternalError extends InternalErrorBase {
179
266
 
180
267
  class AuthError extends AuthErrorBase {
181
268
  category = "auth";
269
+ static create(message, reason) {
270
+ return new AuthError({ message, ...reason != null && { reason } });
271
+ }
182
272
  exitCode() {
183
273
  return getExitCode(this.category);
184
274
  }
@@ -189,6 +279,9 @@ class AuthError extends AuthErrorBase {
189
279
 
190
280
  class CancelledError extends CancelledErrorBase {
191
281
  category = "cancelled";
282
+ static create(message) {
283
+ return new CancelledError({ message });
284
+ }
192
285
  exitCode() {
193
286
  return getExitCode(this.category);
194
287
  }
@@ -197,4 +290,4 @@ class CancelledError extends CancelledErrorBase {
197
290
  }
198
291
  }
199
292
 
200
- export { exitCodeMap, statusCodeMap, ERROR_CODES, getExitCode, getStatusCode, ValidationError, AssertionError, NotFoundError, ConflictError, PermissionError, TimeoutError, RateLimitError, NetworkError, InternalError, AuthError, CancelledError };
293
+ export { exitCodeMap, statusCodeMap, ERROR_CODES, getExitCode, getStatusCode, ValidationError, AmbiguousError, AssertionError, NotFoundError, AlreadyExistsError, ConflictError, PermissionError, TimeoutError, RateLimitError, NetworkError, InternalError, AuthError, CancelledError };
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  TimeoutError
4
- } from "./contracts-jhsgc85s.js";
4
+ } from "./contracts-phjhz5q3.js";
5
5
 
6
6
  // packages/contracts/src/resilience.ts
7
7
  import { Result } from "better-result";
@@ -40,6 +40,7 @@ declare const ERROR_CODES: {
40
40
  readonly INVALID_FORMAT: 1002;
41
41
  readonly OUT_OF_RANGE: 1003;
42
42
  readonly TYPE_MISMATCH: 1004;
43
+ readonly AMBIGUOUS_MATCH: 1005;
43
44
  };
44
45
  readonly not_found: {
45
46
  readonly RESOURCE_NOT_FOUND: 2001;
@@ -112,6 +113,12 @@ declare function getStatusCode(category: ErrorCategory): number;
112
113
  declare const ValidationErrorBase: TaggedErrorClass<"ValidationError", {
113
114
  message: string;
114
115
  field?: string;
116
+ context?: Record<string, unknown>;
117
+ }>;
118
+ declare const AmbiguousErrorBase: TaggedErrorClass<"AmbiguousError", {
119
+ message: string;
120
+ candidates: string[];
121
+ context?: Record<string, unknown>;
115
122
  }>;
116
123
  declare const AssertionErrorBase: TaggedErrorClass<"AssertionError", {
117
124
  message: string;
@@ -120,6 +127,13 @@ declare const NotFoundErrorBase: TaggedErrorClass<"NotFoundError", {
120
127
  message: string;
121
128
  resourceType: string;
122
129
  resourceId: string;
130
+ context?: Record<string, unknown>;
131
+ }>;
132
+ declare const AlreadyExistsErrorBase: TaggedErrorClass<"AlreadyExistsError", {
133
+ message: string;
134
+ resourceType: string;
135
+ resourceId: string;
136
+ context?: Record<string, unknown>;
123
137
  }>;
124
138
  declare const ConflictErrorBase: TaggedErrorClass<"ConflictError", {
125
139
  message: string;
@@ -159,10 +173,50 @@ declare const CancelledErrorBase: TaggedErrorClass<"CancelledError", {
159
173
  * @example
160
174
  * ```typescript
161
175
  * new ValidationError({ message: "Email format invalid", field: "email" });
176
+ * new ValidationError({
177
+ * message: "Value out of range",
178
+ * field: "age",
179
+ * context: { min: 0, max: 150, received: -1 },
180
+ * });
162
181
  * ```
163
182
  */
164
183
  declare class ValidationError extends ValidationErrorBase {
165
184
  readonly category: "validation";
185
+ /** Create a ValidationError with auto-generated message from field name. */
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;
197
+ exitCode(): number;
198
+ statusCode(): number;
199
+ }
200
+ /**
201
+ * Multiple matches found — user must disambiguate.
202
+ *
203
+ * Used in search/resolution systems where partial input matches
204
+ * multiple candidates. Carries the candidate list so transport
205
+ * layers can prompt disambiguation.
206
+ *
207
+ * @example
208
+ * ```typescript
209
+ * new AmbiguousError({
210
+ * message: "Multiple headings match 'Intro'",
211
+ * candidates: ["Introduction", "Intro to APIs"],
212
+ * });
213
+ * AmbiguousError.create("heading", ["Introduction", "Intro to APIs"]);
214
+ * ```
215
+ */
216
+ declare class AmbiguousError extends AmbiguousErrorBase {
217
+ readonly category: "validation";
218
+ /** Create an AmbiguousError with auto-generated message. */
219
+ static create(what: string, candidates: string[], context?: Record<string, unknown>): AmbiguousError;
166
220
  exitCode(): number;
167
221
  statusCode(): number;
168
222
  }
@@ -202,23 +256,76 @@ declare class AssertionError extends AssertionErrorBase {
202
256
  * @example
203
257
  * ```typescript
204
258
  * new NotFoundError({ message: "note not found: abc123", resourceType: "note", resourceId: "abc123" });
259
+ * new NotFoundError({
260
+ * message: "Heading not found",
261
+ * resourceType: "heading",
262
+ * resourceId: "h:Intro",
263
+ * context: { availableHeadings: ["Introduction", "Getting Started"] },
264
+ * });
205
265
  * ```
206
266
  */
207
267
  declare class NotFoundError extends NotFoundErrorBase {
208
268
  readonly category: "not_found";
269
+ /** Create a NotFoundError with auto-generated message. */
270
+ static create(resourceType: string, resourceId: string, context?: Record<string, unknown>): NotFoundError;
209
271
  exitCode(): number;
210
272
  statusCode(): number;
211
273
  }
212
274
  /**
213
- * State conflict (optimistic locking, concurrent modification).
275
+ * Resource already exists the inverse of {@link NotFoundError}.
276
+ *
277
+ * Use when a create/write operation fails because the target resource
278
+ * is already present. Carries `resourceType` and `resourceId` to identify
279
+ * what already exists, mirroring {@link NotFoundError}'s structure.
280
+ *
281
+ * Maps to HTTP 409 (Conflict) and exit code 3.
282
+ *
283
+ * @example
284
+ * ```typescript
285
+ * new AlreadyExistsError({
286
+ * message: "File already exists: notes/meeting.md",
287
+ * resourceType: "file",
288
+ * resourceId: "notes/meeting.md",
289
+ * });
290
+ * AlreadyExistsError.create("file", "notes/meeting.md");
291
+ * ```
292
+ *
293
+ * @see ConflictError - For general state conflicts (version mismatch, concurrent modification)
294
+ * @see NotFoundError - The inverse: resource does not exist
295
+ */
296
+ declare class AlreadyExistsError extends AlreadyExistsErrorBase {
297
+ readonly category: "conflict";
298
+ /** Create an AlreadyExistsError with auto-generated message. */
299
+ static create(resourceType: string, resourceId: string, context?: Record<string, unknown>): AlreadyExistsError;
300
+ exitCode(): number;
301
+ statusCode(): number;
302
+ }
303
+ /**
304
+ * State conflict (version mismatch, concurrent modification).
305
+ *
306
+ * Use for general conflicts that don't fit {@link AlreadyExistsError}:
307
+ * optimistic locking failures, concurrent writes, ETag mismatches,
308
+ * or any case where the operation can't proceed due to state divergence.
309
+ *
310
+ * Maps to HTTP 409 (Conflict) and exit code 3.
311
+ *
312
+ * **Choosing the right conflict error:**
313
+ * - Resource already exists? Use {@link AlreadyExistsError}
314
+ * - Version/ETag mismatch? Use {@link ConflictError}
315
+ * - Concurrent modification detected? Use {@link ConflictError}
214
316
  *
215
317
  * @example
216
318
  * ```typescript
217
319
  * new ConflictError({ message: "Resource was modified by another process" });
320
+ * ConflictError.create("ETag mismatch: expected abc, got def");
218
321
  * ```
322
+ *
323
+ * @see AlreadyExistsError - For "resource already exists" specifically
219
324
  */
220
325
  declare class ConflictError extends ConflictErrorBase {
221
326
  readonly category: "conflict";
327
+ /** Create a ConflictError with optional context. */
328
+ static create(message: string, context?: Record<string, unknown>): ConflictError;
222
329
  exitCode(): number;
223
330
  statusCode(): number;
224
331
  }
@@ -232,6 +339,8 @@ declare class ConflictError extends ConflictErrorBase {
232
339
  */
233
340
  declare class PermissionError extends PermissionErrorBase {
234
341
  readonly category: "permission";
342
+ /** Create a PermissionError with optional context. */
343
+ static create(message: string, context?: Record<string, unknown>): PermissionError;
235
344
  exitCode(): number;
236
345
  statusCode(): number;
237
346
  }
@@ -245,6 +354,8 @@ declare class PermissionError extends PermissionErrorBase {
245
354
  */
246
355
  declare class TimeoutError extends TimeoutErrorBase {
247
356
  readonly category: "timeout";
357
+ /** Create a TimeoutError with auto-generated message. */
358
+ static create(operation: string, timeoutMs: number): TimeoutError;
248
359
  exitCode(): number;
249
360
  statusCode(): number;
250
361
  }
@@ -258,6 +369,8 @@ declare class TimeoutError extends TimeoutErrorBase {
258
369
  */
259
370
  declare class RateLimitError extends RateLimitErrorBase {
260
371
  readonly category: "rate_limit";
372
+ /** Create a RateLimitError with optional retry hint. */
373
+ static create(message: string, retryAfterSeconds?: number): RateLimitError;
261
374
  exitCode(): number;
262
375
  statusCode(): number;
263
376
  }
@@ -271,6 +384,8 @@ declare class RateLimitError extends RateLimitErrorBase {
271
384
  */
272
385
  declare class NetworkError extends NetworkErrorBase {
273
386
  readonly category: "network";
387
+ /** Create a NetworkError with optional context. */
388
+ static create(message: string, context?: Record<string, unknown>): NetworkError;
274
389
  exitCode(): number;
275
390
  statusCode(): number;
276
391
  }
@@ -284,6 +399,8 @@ declare class NetworkError extends NetworkErrorBase {
284
399
  */
285
400
  declare class InternalError extends InternalErrorBase {
286
401
  readonly category: "internal";
402
+ /** Create an InternalError with optional context. */
403
+ static create(message: string, context?: Record<string, unknown>): InternalError;
287
404
  exitCode(): number;
288
405
  statusCode(): number;
289
406
  }
@@ -297,6 +414,8 @@ declare class InternalError extends InternalErrorBase {
297
414
  */
298
415
  declare class AuthError extends AuthErrorBase {
299
416
  readonly category: "auth";
417
+ /** Create an AuthError with optional reason. */
418
+ static create(message: string, reason?: "missing" | "invalid" | "expired"): AuthError;
300
419
  exitCode(): number;
301
420
  statusCode(): number;
302
421
  }
@@ -310,16 +429,18 @@ declare class AuthError extends AuthErrorBase {
310
429
  */
311
430
  declare class CancelledError extends CancelledErrorBase {
312
431
  readonly category: "cancelled";
432
+ /** Create a CancelledError. */
433
+ static create(message: string): CancelledError;
313
434
  exitCode(): number;
314
435
  statusCode(): number;
315
436
  }
316
437
  /**
317
438
  * Union type of all concrete error class instances.
318
439
  */
319
- type AnyKitError = InstanceType<typeof ValidationError> | 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>;
320
441
  /**
321
442
  * Type alias for backwards compatibility with handler signatures.
322
443
  * Use AnyKitError for the union type.
323
444
  */
324
445
  type OutfitterError = AnyKitError;
325
- export { ErrorCategory, exitCodeMap, statusCodeMap, ERROR_CODES, ErrorCode, SerializedError, KitErrorProps, getExitCode, getStatusCode, ValidationError, 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-yp3derbe";
2
- import { OutfitterError } from "./contracts-ev4a68sg";
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 };