@outfitter/contracts 0.4.1 → 0.4.2
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.js +25 -6
- package/dist/assert/index.js +28 -6
- package/dist/capabilities.js +57 -8
- package/dist/envelope.js +49 -6
- package/dist/index.d.ts +10 -10
- package/dist/index.js +15 -153
- package/dist/logging.js +11 -3
- package/dist/recovery.js +49 -6
- package/dist/resilience.js +76 -4
- package/dist/result/index.js +1 -16
- package/dist/result/utilities.js +29 -7
- package/dist/schema.js +338 -3
- package/dist/serialization.js +2 -2
- package/dist/validation.js +36 -4
- package/package.json +24 -24
- package/dist/shared/@outfitter/contracts-0snpmkdt.js +0 -40
- package/dist/shared/@outfitter/contracts-37gpc56f.js +0 -1
- package/dist/shared/@outfitter/contracts-4zaj7ejb.js +0 -52
- package/dist/shared/@outfitter/contracts-85nd53s9.js +0 -53
- package/dist/shared/@outfitter/contracts-cp5c6dws.js +0 -32
- package/dist/shared/@outfitter/contracts-d0tq2adf.js +0 -60
- 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-wfht4q2b.js +0 -341
- package/dist/shared/@outfitter/contracts-zx72gyh1.js +0 -32
- package/dist/shared/@outfitter/{contracts-3wj7xghe.js → contracts-5k6q4n48.js} +5 -5
package/dist/result/utilities.js
CHANGED
|
@@ -1,11 +1,33 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
// packages/contracts/src/result/utilities.ts
|
|
3
|
+
import { Result } from "better-result";
|
|
4
|
+
var unwrapOrElse = (result, defaultFn) => {
|
|
5
|
+
return result.isOk() ? result.value : defaultFn(result.error);
|
|
6
|
+
};
|
|
7
|
+
var orElse = (result, fallback) => {
|
|
8
|
+
return result.isOk() ? result : fallback;
|
|
9
|
+
};
|
|
10
|
+
var combine2 = (r1, r2) => {
|
|
11
|
+
if (r1.isErr())
|
|
12
|
+
return r1;
|
|
13
|
+
if (r2.isErr())
|
|
14
|
+
return r2;
|
|
15
|
+
return Result.ok([r1.value, r2.value]);
|
|
16
|
+
};
|
|
17
|
+
var combine3 = (r1, r2, r3) => {
|
|
18
|
+
if (r1.isErr())
|
|
19
|
+
return r1;
|
|
20
|
+
if (r2.isErr())
|
|
21
|
+
return r2;
|
|
22
|
+
if (r3.isErr())
|
|
23
|
+
return r3;
|
|
24
|
+
return Result.ok([r1.value, r2.value, r3.value]);
|
|
25
|
+
};
|
|
26
|
+
var expect = (result, message) => {
|
|
27
|
+
if (result.isOk())
|
|
28
|
+
return result.value;
|
|
29
|
+
throw new Error(`${message}: ${String(result.error)}`);
|
|
30
|
+
};
|
|
9
31
|
export {
|
|
10
32
|
unwrapOrElse,
|
|
11
33
|
orElse,
|
package/dist/schema.js
CHANGED
|
@@ -1,7 +1,342 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
// packages/contracts/src/schema.ts
|
|
3
|
+
function zodToJsonSchema(schema) {
|
|
4
|
+
return convertZodType(schema);
|
|
5
|
+
}
|
|
6
|
+
function getDef(schemaOrDef) {
|
|
7
|
+
if (!schemaOrDef) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (schemaOrDef._def) {
|
|
11
|
+
return schemaOrDef._def;
|
|
12
|
+
}
|
|
13
|
+
if (schemaOrDef.def) {
|
|
14
|
+
return schemaOrDef.def;
|
|
15
|
+
}
|
|
16
|
+
return schemaOrDef;
|
|
17
|
+
}
|
|
18
|
+
function getDescription(schema, def) {
|
|
19
|
+
if (typeof schema?.description === "string") {
|
|
20
|
+
return schema.description;
|
|
21
|
+
}
|
|
22
|
+
if (typeof def?.description === "string") {
|
|
23
|
+
return def.description;
|
|
24
|
+
}
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
function convertZodType(schema) {
|
|
28
|
+
const def = getDef(schema);
|
|
29
|
+
if (!def) {
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
const typeName = def.typeName ?? def.type;
|
|
33
|
+
let jsonSchema;
|
|
34
|
+
switch (typeName) {
|
|
35
|
+
case "ZodString":
|
|
36
|
+
case "string":
|
|
37
|
+
jsonSchema = convertString(def);
|
|
38
|
+
break;
|
|
39
|
+
case "ZodNumber":
|
|
40
|
+
case "number":
|
|
41
|
+
jsonSchema = convertNumber(def);
|
|
42
|
+
break;
|
|
43
|
+
case "ZodBoolean":
|
|
44
|
+
case "boolean":
|
|
45
|
+
jsonSchema = { type: "boolean" };
|
|
46
|
+
break;
|
|
47
|
+
case "ZodNull":
|
|
48
|
+
case "null":
|
|
49
|
+
jsonSchema = { type: "null" };
|
|
50
|
+
break;
|
|
51
|
+
case "ZodUndefined":
|
|
52
|
+
case "undefined":
|
|
53
|
+
jsonSchema = {};
|
|
54
|
+
break;
|
|
55
|
+
case "ZodArray":
|
|
56
|
+
case "array":
|
|
57
|
+
jsonSchema = convertArray(def);
|
|
58
|
+
break;
|
|
59
|
+
case "ZodObject":
|
|
60
|
+
case "object":
|
|
61
|
+
jsonSchema = convertObject(def);
|
|
62
|
+
break;
|
|
63
|
+
case "ZodOptional":
|
|
64
|
+
case "optional":
|
|
65
|
+
jsonSchema = convertZodType(def.innerType);
|
|
66
|
+
break;
|
|
67
|
+
case "ZodNullable":
|
|
68
|
+
case "nullable":
|
|
69
|
+
jsonSchema = {
|
|
70
|
+
anyOf: [convertZodType(def.innerType), { type: "null" }]
|
|
71
|
+
};
|
|
72
|
+
break;
|
|
73
|
+
case "ZodDefault":
|
|
74
|
+
case "default": {
|
|
75
|
+
const defaultValue = typeof def.defaultValue === "function" ? def.defaultValue() : def.defaultValue;
|
|
76
|
+
jsonSchema = {
|
|
77
|
+
...convertZodType(def.innerType),
|
|
78
|
+
default: defaultValue
|
|
79
|
+
};
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
case "ZodEnum":
|
|
83
|
+
case "enum": {
|
|
84
|
+
const values = def.values ?? Object.values(def.entries ?? {});
|
|
85
|
+
jsonSchema = {
|
|
86
|
+
type: "string",
|
|
87
|
+
enum: values
|
|
88
|
+
};
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
case "ZodNativeEnum":
|
|
92
|
+
jsonSchema = {
|
|
93
|
+
enum: Object.values(def.values ?? def.entries ?? {})
|
|
94
|
+
};
|
|
95
|
+
break;
|
|
96
|
+
case "ZodLiteral":
|
|
97
|
+
case "literal": {
|
|
98
|
+
const literalValues = Array.isArray(def.values) ? def.values : [def.value].filter((value) => value !== undefined);
|
|
99
|
+
if (literalValues.length > 1) {
|
|
100
|
+
jsonSchema = {
|
|
101
|
+
enum: literalValues
|
|
102
|
+
};
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
jsonSchema = literalValues.length ? {
|
|
106
|
+
const: literalValues[0]
|
|
107
|
+
} : {};
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case "ZodUnion":
|
|
111
|
+
case "union":
|
|
112
|
+
jsonSchema = {
|
|
113
|
+
anyOf: def.options.map(convertZodType)
|
|
114
|
+
};
|
|
115
|
+
break;
|
|
116
|
+
case "ZodIntersection":
|
|
117
|
+
case "intersection":
|
|
118
|
+
jsonSchema = {
|
|
119
|
+
allOf: [convertZodType(def.left), convertZodType(def.right)]
|
|
120
|
+
};
|
|
121
|
+
break;
|
|
122
|
+
case "ZodRecord":
|
|
123
|
+
case "record":
|
|
124
|
+
jsonSchema = {
|
|
125
|
+
type: "object",
|
|
126
|
+
additionalProperties: def.valueType ? convertZodType(def.valueType) : {}
|
|
127
|
+
};
|
|
128
|
+
break;
|
|
129
|
+
case "ZodTuple":
|
|
130
|
+
case "tuple":
|
|
131
|
+
jsonSchema = {
|
|
132
|
+
type: "array",
|
|
133
|
+
items: def.items.map(convertZodType)
|
|
134
|
+
};
|
|
135
|
+
break;
|
|
136
|
+
case "ZodAny":
|
|
137
|
+
case "any":
|
|
138
|
+
jsonSchema = {};
|
|
139
|
+
break;
|
|
140
|
+
case "ZodUnknown":
|
|
141
|
+
case "unknown":
|
|
142
|
+
jsonSchema = {};
|
|
143
|
+
break;
|
|
144
|
+
case "ZodVoid":
|
|
145
|
+
case "void":
|
|
146
|
+
jsonSchema = {};
|
|
147
|
+
break;
|
|
148
|
+
case "ZodNever":
|
|
149
|
+
case "never":
|
|
150
|
+
jsonSchema = { not: {} };
|
|
151
|
+
break;
|
|
152
|
+
case "ZodEffects":
|
|
153
|
+
jsonSchema = convertZodType(def.schema);
|
|
154
|
+
break;
|
|
155
|
+
case "ZodPipeline":
|
|
156
|
+
case "pipe": {
|
|
157
|
+
const outputDef = getDef(def.out);
|
|
158
|
+
const outputType = outputDef?.typeName ?? outputDef?.type;
|
|
159
|
+
jsonSchema = outputType === "transform" ? convertZodType(def.in) : convertZodType(def.out);
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case "ZodLazy":
|
|
163
|
+
case "lazy":
|
|
164
|
+
jsonSchema = {};
|
|
165
|
+
break;
|
|
166
|
+
default:
|
|
167
|
+
jsonSchema = {};
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
const description = getDescription(schema, def);
|
|
171
|
+
if (description && !jsonSchema.description) {
|
|
172
|
+
jsonSchema.description = description;
|
|
173
|
+
}
|
|
174
|
+
return jsonSchema;
|
|
175
|
+
}
|
|
176
|
+
function convertString(def) {
|
|
177
|
+
const schema = { type: "string" };
|
|
178
|
+
if (def.checks) {
|
|
179
|
+
for (const check of def.checks) {
|
|
180
|
+
const normalizedCheck = check?._zod?.def ?? check?.def ?? check;
|
|
181
|
+
if (normalizedCheck?.kind) {
|
|
182
|
+
switch (normalizedCheck.kind) {
|
|
183
|
+
case "min":
|
|
184
|
+
schema.minLength = normalizedCheck.value;
|
|
185
|
+
break;
|
|
186
|
+
case "max":
|
|
187
|
+
schema.maxLength = normalizedCheck.value;
|
|
188
|
+
break;
|
|
189
|
+
case "length":
|
|
190
|
+
schema.minLength = normalizedCheck.value;
|
|
191
|
+
schema.maxLength = normalizedCheck.value;
|
|
192
|
+
break;
|
|
193
|
+
case "email":
|
|
194
|
+
schema.pattern = "^[^@]+@[^@]+\\.[^@]+$";
|
|
195
|
+
break;
|
|
196
|
+
case "url":
|
|
197
|
+
schema.pattern = "^https?://";
|
|
198
|
+
break;
|
|
199
|
+
case "uuid":
|
|
200
|
+
schema.pattern = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$";
|
|
201
|
+
break;
|
|
202
|
+
case "regex":
|
|
203
|
+
schema.pattern = normalizedCheck.regex?.source ?? normalizedCheck.pattern?.source ?? (typeof normalizedCheck.pattern === "string" ? normalizedCheck.pattern : undefined);
|
|
204
|
+
break;
|
|
205
|
+
default:
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (!normalizedCheck?.check) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
switch (normalizedCheck.check) {
|
|
214
|
+
case "min_length":
|
|
215
|
+
schema.minLength = normalizedCheck.minimum;
|
|
216
|
+
break;
|
|
217
|
+
case "max_length":
|
|
218
|
+
schema.maxLength = normalizedCheck.maximum;
|
|
219
|
+
break;
|
|
220
|
+
case "string_format":
|
|
221
|
+
if (normalizedCheck.pattern) {
|
|
222
|
+
schema.pattern = typeof normalizedCheck.pattern === "string" ? normalizedCheck.pattern : normalizedCheck.pattern.source;
|
|
223
|
+
}
|
|
224
|
+
if (normalizedCheck.format && normalizedCheck.format !== "regex") {
|
|
225
|
+
schema.format = normalizedCheck.format;
|
|
226
|
+
}
|
|
227
|
+
break;
|
|
228
|
+
default:
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return schema;
|
|
234
|
+
}
|
|
235
|
+
function convertNumber(def) {
|
|
236
|
+
const schema = { type: "number" };
|
|
237
|
+
if (def.checks) {
|
|
238
|
+
for (const check of def.checks) {
|
|
239
|
+
const normalizedCheck = check?._zod?.def ?? check?.def ?? check;
|
|
240
|
+
if (normalizedCheck?.kind) {
|
|
241
|
+
switch (normalizedCheck.kind) {
|
|
242
|
+
case "min":
|
|
243
|
+
schema.minimum = normalizedCheck.value;
|
|
244
|
+
break;
|
|
245
|
+
case "max":
|
|
246
|
+
schema.maximum = normalizedCheck.value;
|
|
247
|
+
break;
|
|
248
|
+
case "int":
|
|
249
|
+
schema.type = "integer";
|
|
250
|
+
break;
|
|
251
|
+
default:
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (!normalizedCheck?.check) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
switch (normalizedCheck.check) {
|
|
260
|
+
case "greater_than":
|
|
261
|
+
if (normalizedCheck.inclusive) {
|
|
262
|
+
schema.minimum = normalizedCheck.value;
|
|
263
|
+
} else {
|
|
264
|
+
schema.exclusiveMinimum = normalizedCheck.value;
|
|
265
|
+
}
|
|
266
|
+
break;
|
|
267
|
+
case "less_than":
|
|
268
|
+
if (normalizedCheck.inclusive) {
|
|
269
|
+
schema.maximum = normalizedCheck.value;
|
|
270
|
+
} else {
|
|
271
|
+
schema.exclusiveMaximum = normalizedCheck.value;
|
|
272
|
+
}
|
|
273
|
+
break;
|
|
274
|
+
case "number_format":
|
|
275
|
+
if (normalizedCheck.format === "int" || normalizedCheck.format === "safeint") {
|
|
276
|
+
schema.type = "integer";
|
|
277
|
+
}
|
|
278
|
+
break;
|
|
279
|
+
default:
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return schema;
|
|
285
|
+
}
|
|
286
|
+
function convertArray(def) {
|
|
287
|
+
const element = def.element ?? def.type;
|
|
288
|
+
const schema = {
|
|
289
|
+
type: "array",
|
|
290
|
+
items: element ? convertZodType(element) : {}
|
|
291
|
+
};
|
|
292
|
+
return schema;
|
|
293
|
+
}
|
|
294
|
+
function isFieldOptional(fieldDef) {
|
|
295
|
+
if (!(fieldDef?.typeName || fieldDef?.type)) {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
const typeName = fieldDef.typeName ?? fieldDef.type;
|
|
299
|
+
if (typeName === "ZodOptional" || typeName === "ZodDefault" || typeName === "optional" || typeName === "default") {
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
if (typeName === "ZodEffects") {
|
|
303
|
+
return isFieldOptional(getDef(fieldDef.schema));
|
|
304
|
+
}
|
|
305
|
+
if (typeName === "ZodPipeline" || typeName === "pipe") {
|
|
306
|
+
const inputOptional = isFieldOptional(getDef(fieldDef.in));
|
|
307
|
+
const outputDef = getDef(fieldDef.out);
|
|
308
|
+
const outputType = outputDef?.typeName ?? outputDef?.type;
|
|
309
|
+
if (outputType === "transform") {
|
|
310
|
+
return inputOptional;
|
|
311
|
+
}
|
|
312
|
+
const outputOptional = isFieldOptional(outputDef);
|
|
313
|
+
return inputOptional && outputOptional;
|
|
314
|
+
}
|
|
315
|
+
if (typeName === "ZodNullable" || typeName === "nullable") {
|
|
316
|
+
return isFieldOptional(getDef(fieldDef.innerType));
|
|
317
|
+
}
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
function convertObject(def) {
|
|
321
|
+
const properties = {};
|
|
322
|
+
const required = [];
|
|
323
|
+
const shape = typeof def.shape === "function" ? def.shape() : def.shape;
|
|
324
|
+
for (const [key, value] of Object.entries(shape ?? {})) {
|
|
325
|
+
properties[key] = convertZodType(value);
|
|
326
|
+
const fieldDef = getDef(value);
|
|
327
|
+
if (!isFieldOptional(fieldDef)) {
|
|
328
|
+
required.push(key);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const schema = {
|
|
332
|
+
type: "object",
|
|
333
|
+
properties
|
|
334
|
+
};
|
|
335
|
+
if (required.length > 0) {
|
|
336
|
+
schema.required = required;
|
|
337
|
+
}
|
|
338
|
+
return schema;
|
|
339
|
+
}
|
|
5
340
|
export {
|
|
6
341
|
zodToJsonSchema
|
|
7
342
|
};
|
package/dist/serialization.js
CHANGED
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
safeParse,
|
|
5
5
|
safeStringify,
|
|
6
6
|
serializeError
|
|
7
|
-
} from "./shared/@outfitter/contracts-
|
|
8
|
-
import"./shared/@outfitter/contracts-s15x2rs4.js";
|
|
7
|
+
} from "./shared/@outfitter/contracts-5k6q4n48.js";
|
|
9
8
|
import"./shared/@outfitter/contracts-phjhz5q3.js";
|
|
9
|
+
import"./shared/@outfitter/contracts-s15x2rs4.js";
|
|
10
10
|
export {
|
|
11
11
|
serializeError,
|
|
12
12
|
safeStringify,
|
package/dist/validation.js
CHANGED
|
@@ -1,9 +1,41 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
ValidationError
|
|
4
|
+
} from "./shared/@outfitter/contracts-phjhz5q3.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 validateInput(schema, input) {
|
|
27
|
+
const parseResult = schema.safeParse(input);
|
|
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
|
+
}
|
|
7
39
|
export {
|
|
8
40
|
validateInput,
|
|
9
41
|
createValidator
|
package/package.json
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outfitter/contracts",
|
|
3
|
+
"version": "0.4.2",
|
|
3
4
|
"description": "Result/Error patterns, error taxonomy, and handler contracts for Outfitter",
|
|
4
|
-
"
|
|
5
|
-
|
|
5
|
+
"keywords": [
|
|
6
|
+
"contracts",
|
|
7
|
+
"errors",
|
|
8
|
+
"outfitter",
|
|
9
|
+
"result",
|
|
10
|
+
"typescript"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/outfitter-dev/outfitter.git",
|
|
16
|
+
"directory": "packages/contracts"
|
|
17
|
+
},
|
|
6
18
|
"files": [
|
|
7
19
|
"dist"
|
|
8
20
|
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"sideEffects": false,
|
|
9
23
|
"module": "./dist/index.js",
|
|
10
24
|
"types": "./dist/index.d.ts",
|
|
11
25
|
"exports": {
|
|
@@ -119,38 +133,24 @@
|
|
|
119
133
|
}
|
|
120
134
|
}
|
|
121
135
|
},
|
|
122
|
-
"
|
|
136
|
+
"publishConfig": {
|
|
137
|
+
"access": "public"
|
|
138
|
+
},
|
|
123
139
|
"scripts": {
|
|
124
140
|
"build": "cd ../.. && bunup --filter @outfitter/contracts",
|
|
125
|
-
"lint": "
|
|
126
|
-
"lint:fix": "
|
|
141
|
+
"lint": "oxlint ./src",
|
|
142
|
+
"lint:fix": "oxlint --fix ./src",
|
|
127
143
|
"test": "bun test",
|
|
128
144
|
"typecheck": "tsc --noEmit",
|
|
129
145
|
"clean": "rm -rf dist",
|
|
130
146
|
"prepublishOnly": "bun ../../scripts/check-publish-manifest.ts"
|
|
131
147
|
},
|
|
132
148
|
"dependencies": {
|
|
133
|
-
"better-result": "^2.5.
|
|
149
|
+
"better-result": "^2.5.1",
|
|
134
150
|
"zod": "^4.3.5"
|
|
135
151
|
},
|
|
136
152
|
"devDependencies": {
|
|
137
|
-
"@types/bun": "
|
|
138
|
-
"typescript": "^5.
|
|
139
|
-
},
|
|
140
|
-
"keywords": [
|
|
141
|
-
"outfitter",
|
|
142
|
-
"contracts",
|
|
143
|
-
"errors",
|
|
144
|
-
"result",
|
|
145
|
-
"typescript"
|
|
146
|
-
],
|
|
147
|
-
"license": "MIT",
|
|
148
|
-
"repository": {
|
|
149
|
-
"type": "git",
|
|
150
|
-
"url": "https://github.com/outfitter-dev/outfitter.git",
|
|
151
|
-
"directory": "packages/contracts"
|
|
152
|
-
},
|
|
153
|
-
"publishConfig": {
|
|
154
|
-
"access": "public"
|
|
153
|
+
"@types/bun": "^1.3.9",
|
|
154
|
+
"typescript": "^5.9.3"
|
|
155
155
|
}
|
|
156
156
|
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
import {
|
|
3
|
-
ValidationError
|
|
4
|
-
} from "./contracts-phjhz5q3.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 validateInput(schema, input) {
|
|
27
|
-
const parseResult = schema.safeParse(input);
|
|
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
|
-
|
|
40
|
-
export { createValidator, validateInput };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// packages/contracts/src/recovery.ts
|
|
3
|
-
var RECOVERABLE_CATEGORIES = [
|
|
4
|
-
"network",
|
|
5
|
-
"timeout",
|
|
6
|
-
"rate_limit",
|
|
7
|
-
"conflict"
|
|
8
|
-
];
|
|
9
|
-
var RETRYABLE_CATEGORIES = ["network", "timeout"];
|
|
10
|
-
var isRecoverable = (error) => {
|
|
11
|
-
return RECOVERABLE_CATEGORIES.includes(error.category);
|
|
12
|
-
};
|
|
13
|
-
var isRetryable = (error) => {
|
|
14
|
-
return RETRYABLE_CATEGORIES.includes(error.category);
|
|
15
|
-
};
|
|
16
|
-
var getBackoffDelay = (attempt, options = {}) => {
|
|
17
|
-
const {
|
|
18
|
-
baseDelayMs = 100,
|
|
19
|
-
maxDelayMs = 30000,
|
|
20
|
-
strategy = "exponential",
|
|
21
|
-
useJitter = true
|
|
22
|
-
} = options;
|
|
23
|
-
let delay;
|
|
24
|
-
switch (strategy) {
|
|
25
|
-
case "constant": {
|
|
26
|
-
delay = baseDelayMs;
|
|
27
|
-
break;
|
|
28
|
-
}
|
|
29
|
-
case "linear": {
|
|
30
|
-
delay = baseDelayMs * (attempt + 1);
|
|
31
|
-
break;
|
|
32
|
-
}
|
|
33
|
-
default: {
|
|
34
|
-
delay = baseDelayMs * 2 ** attempt;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
delay = Math.min(delay, maxDelayMs);
|
|
38
|
-
if (useJitter) {
|
|
39
|
-
const jitterFactor = 0.1;
|
|
40
|
-
const jitter = delay * jitterFactor * (Math.random() * 2 - 1);
|
|
41
|
-
delay = Math.round(delay + jitter);
|
|
42
|
-
}
|
|
43
|
-
return Math.min(delay, maxDelayMs);
|
|
44
|
-
};
|
|
45
|
-
var shouldRetry = (error, attempt, maxAttempts = 3) => {
|
|
46
|
-
if (attempt >= maxAttempts) {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
return isRetryable(error);
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
export { isRecoverable, isRetryable, getBackoffDelay, shouldRetry };
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
import {
|
|
3
|
-
serializeError
|
|
4
|
-
} from "./contracts-3wj7xghe.js";
|
|
5
|
-
import {
|
|
6
|
-
statusCodeMap
|
|
7
|
-
} from "./contracts-phjhz5q3.js";
|
|
8
|
-
import {
|
|
9
|
-
generateRequestId
|
|
10
|
-
} from "./contracts-agmt8915.js";
|
|
11
|
-
|
|
12
|
-
// packages/contracts/src/envelope.ts
|
|
13
|
-
function buildMeta(overrides) {
|
|
14
|
-
const meta = {
|
|
15
|
-
requestId: overrides?.requestId ?? generateRequestId(),
|
|
16
|
-
timestamp: new Date().toISOString()
|
|
17
|
-
};
|
|
18
|
-
if (overrides?.durationMs !== undefined) {
|
|
19
|
-
meta.durationMs = overrides.durationMs;
|
|
20
|
-
}
|
|
21
|
-
return meta;
|
|
22
|
-
}
|
|
23
|
-
function toEnvelope(result, meta) {
|
|
24
|
-
const envelopeMeta = buildMeta(meta);
|
|
25
|
-
if (result.isOk()) {
|
|
26
|
-
return {
|
|
27
|
-
ok: true,
|
|
28
|
-
data: result.value,
|
|
29
|
-
meta: envelopeMeta
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
return {
|
|
33
|
-
ok: false,
|
|
34
|
-
error: serializeError(result.error),
|
|
35
|
-
meta: envelopeMeta
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
function toHttpResponse(result) {
|
|
39
|
-
const envelope = toEnvelope(result);
|
|
40
|
-
if (envelope.ok) {
|
|
41
|
-
return {
|
|
42
|
-
status: 200,
|
|
43
|
-
body: envelope
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
const status = statusCodeMap[envelope.error.category];
|
|
47
|
-
return {
|
|
48
|
-
status,
|
|
49
|
-
body: envelope
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export { toEnvelope, toHttpResponse };
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
import {
|
|
3
|
-
AssertionError
|
|
4
|
-
} from "./contracts-phjhz5q3.js";
|
|
5
|
-
|
|
6
|
-
// packages/contracts/src/assert/index.ts
|
|
7
|
-
import { Result } from "better-result";
|
|
8
|
-
var isNonEmptyArray = (arr) => {
|
|
9
|
-
return arr.length > 0;
|
|
10
|
-
};
|
|
11
|
-
var assertDefined = (value, message) => {
|
|
12
|
-
if (value === null || value === undefined) {
|
|
13
|
-
return Result.err(new AssertionError({ message: message ?? "Value is null or undefined" }));
|
|
14
|
-
}
|
|
15
|
-
return Result.ok(value);
|
|
16
|
-
};
|
|
17
|
-
var assertNonEmpty = (arr, message) => {
|
|
18
|
-
if (arr.length === 0) {
|
|
19
|
-
return Result.err(new AssertionError({ message: message ?? "Array is empty" }));
|
|
20
|
-
}
|
|
21
|
-
return Result.ok(arr);
|
|
22
|
-
};
|
|
23
|
-
function assertMatches(value, predicate, message) {
|
|
24
|
-
if (!predicate(value)) {
|
|
25
|
-
return Result.err(new AssertionError({
|
|
26
|
-
message: message ?? "Value does not match predicate"
|
|
27
|
-
}));
|
|
28
|
-
}
|
|
29
|
-
return Result.ok(value);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export { isNonEmptyArray, assertDefined, assertNonEmpty, assertMatches };
|