@distilled.cloud/core 0.0.0 → 0.2.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/lib/category.d.ts +260 -0
- package/lib/category.d.ts.map +1 -0
- package/lib/category.js +264 -0
- package/lib/category.js.map +1 -0
- package/lib/client.d.ts +123 -0
- package/lib/client.d.ts.map +1 -0
- package/lib/client.js +268 -0
- package/lib/client.js.map +1 -0
- package/lib/errors.d.ts +181 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/errors.js +122 -0
- package/lib/errors.js.map +1 -0
- package/lib/json-patch.d.ts +44 -0
- package/lib/json-patch.d.ts.map +1 -0
- package/lib/json-patch.js +208 -0
- package/lib/json-patch.js.map +1 -0
- package/lib/pagination.d.ts +74 -0
- package/lib/pagination.d.ts.map +1 -0
- package/lib/pagination.js +130 -0
- package/lib/pagination.js.map +1 -0
- package/lib/retry.d.ts +99 -0
- package/lib/retry.d.ts.map +1 -0
- package/lib/retry.js +106 -0
- package/lib/retry.js.map +1 -0
- package/lib/sensitive.d.ts +50 -0
- package/lib/sensitive.d.ts.map +1 -0
- package/lib/sensitive.js +64 -0
- package/lib/sensitive.js.map +1 -0
- package/lib/traits.d.ts +265 -0
- package/lib/traits.d.ts.map +1 -0
- package/lib/traits.js +470 -0
- package/lib/traits.js.map +1 -0
- package/package.json +66 -5
- package/src/category.ts +406 -0
- package/src/client.ts +511 -0
- package/src/errors.ts +156 -0
- package/src/json-patch.ts +261 -0
- package/src/pagination.ts +222 -0
- package/src/retry.ts +177 -0
- package/src/sensitive.ts +74 -0
- package/src/traits.ts +627 -0
- package/README.md +0 -15
- package/bun.lock +0 -26
- package/index.ts +0 -1
- package/tsconfig.json +0 -29
package/src/category.ts
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Category System
|
|
3
|
+
*
|
|
4
|
+
* Provides a unified error classification system across all SDKs.
|
|
5
|
+
* Error classes are decorated with categories using `.pipe()` on Schema.TaggedError classes,
|
|
6
|
+
* enabling semantic error handling (e.g., catch all auth errors regardless of provider).
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import * as Category from "@distilled.cloud/sdk-core/category";
|
|
11
|
+
*
|
|
12
|
+
* export class Unauthorized extends Schema.TaggedErrorClass<Unauthorized>()(
|
|
13
|
+
* "Unauthorized",
|
|
14
|
+
* { message: Schema.String },
|
|
15
|
+
* ).pipe(Category.withAuthError) {}
|
|
16
|
+
*
|
|
17
|
+
* // Catch by category
|
|
18
|
+
* effect.pipe(Category.catchAuthError((err) => Effect.succeed("handled")))
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
import * as Effect from "effect/Effect";
|
|
22
|
+
import * as Predicate from "effect/Predicate";
|
|
23
|
+
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Error Category Constants
|
|
26
|
+
// ============================================================================
|
|
27
|
+
|
|
28
|
+
export const AuthError = "AuthError";
|
|
29
|
+
export const BadRequestError = "BadRequestError";
|
|
30
|
+
export const ConflictError = "ConflictError";
|
|
31
|
+
export const NotFoundError = "NotFoundError";
|
|
32
|
+
export const QuotaError = "QuotaError";
|
|
33
|
+
export const ServerError = "ServerError";
|
|
34
|
+
export const ThrottlingError = "ThrottlingError";
|
|
35
|
+
export const NetworkError = "NetworkError";
|
|
36
|
+
export const ParseError = "ParseError";
|
|
37
|
+
export const ConfigurationError = "ConfigurationError";
|
|
38
|
+
export const TimeoutError = "TimeoutError";
|
|
39
|
+
export const RetryableError = "RetryableError";
|
|
40
|
+
export const LockedError = "LockedError";
|
|
41
|
+
export const AbortedError = "AbortedError";
|
|
42
|
+
export const AlreadyExistsError = "AlreadyExistsError";
|
|
43
|
+
export const DependencyViolationError = "DependencyViolationError";
|
|
44
|
+
|
|
45
|
+
export type Category =
|
|
46
|
+
| typeof AuthError
|
|
47
|
+
| typeof BadRequestError
|
|
48
|
+
| typeof ConflictError
|
|
49
|
+
| typeof NotFoundError
|
|
50
|
+
| typeof QuotaError
|
|
51
|
+
| typeof ServerError
|
|
52
|
+
| typeof ThrottlingError
|
|
53
|
+
| typeof NetworkError
|
|
54
|
+
| typeof ParseError
|
|
55
|
+
| typeof ConfigurationError
|
|
56
|
+
| typeof TimeoutError
|
|
57
|
+
| typeof RetryableError
|
|
58
|
+
| typeof LockedError
|
|
59
|
+
| typeof AbortedError
|
|
60
|
+
| typeof AlreadyExistsError
|
|
61
|
+
| typeof DependencyViolationError;
|
|
62
|
+
|
|
63
|
+
// ============================================================================
|
|
64
|
+
// Category Storage Key
|
|
65
|
+
// ============================================================================
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Key for storing categories on error prototypes.
|
|
69
|
+
* Shared across all SDKs so category checking works uniformly.
|
|
70
|
+
*/
|
|
71
|
+
export const categoriesKey = "@distilled.cloud/error/categories";
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Key for storing retryable trait on error prototypes.
|
|
75
|
+
* Separate from categories - indicates this error should be retried.
|
|
76
|
+
*/
|
|
77
|
+
export const retryableKey = "@distilled.cloud/error/retryable";
|
|
78
|
+
|
|
79
|
+
export interface RetryableInfo {
|
|
80
|
+
/** If true, this is a throttling error (use longer backoff) */
|
|
81
|
+
throttling?: boolean;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ============================================================================
|
|
85
|
+
// Category Decorator
|
|
86
|
+
// ============================================================================
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Add one or more categories to an error class.
|
|
90
|
+
* Use with .pipe() on Schema.TaggedError classes.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* export class MyError extends Schema.TaggedErrorClass<MyError>()(
|
|
95
|
+
* "MyError",
|
|
96
|
+
* { message: Schema.String },
|
|
97
|
+
* ).pipe(Category.withCategory(Category.AuthError)) {}
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export const withCategory =
|
|
101
|
+
<Categories extends Array<Category>>(...categories: Categories) =>
|
|
102
|
+
<Args extends Array<any>, Ret, C extends { new (...args: Args): Ret }>(
|
|
103
|
+
C: C,
|
|
104
|
+
): C & {
|
|
105
|
+
new (
|
|
106
|
+
...args: Args
|
|
107
|
+
): Ret & { [categoriesKey]: { [Cat in Categories[number]]: true } };
|
|
108
|
+
} => {
|
|
109
|
+
for (const category of categories) {
|
|
110
|
+
if (!(categoriesKey in C.prototype)) {
|
|
111
|
+
C.prototype[categoriesKey] = {};
|
|
112
|
+
}
|
|
113
|
+
C.prototype[categoriesKey][category] = true;
|
|
114
|
+
}
|
|
115
|
+
return C as any;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Mark an error class as retryable.
|
|
120
|
+
* Use with .pipe() on Schema.TaggedError classes.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```ts
|
|
124
|
+
* // Standard retryable error
|
|
125
|
+
* export class TransientError extends Schema.TaggedErrorClass<TransientError>()(
|
|
126
|
+
* "TransientError",
|
|
127
|
+
* { message: Schema.String },
|
|
128
|
+
* ).pipe(Category.withRetryable()) {}
|
|
129
|
+
*
|
|
130
|
+
* // Throttling error (uses longer backoff)
|
|
131
|
+
* export class RateLimitError extends Schema.TaggedErrorClass<RateLimitError>()(
|
|
132
|
+
* "RateLimitError",
|
|
133
|
+
* { message: Schema.String },
|
|
134
|
+
* ).pipe(Category.withRetryable({ throttling: true })) {}
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
export const withRetryable =
|
|
138
|
+
(info: RetryableInfo = {}) =>
|
|
139
|
+
<Args extends Array<any>, Ret, C extends { new (...args: Args): Ret }>(
|
|
140
|
+
C: C,
|
|
141
|
+
): C & {
|
|
142
|
+
new (...args: Args): Ret & { [retryableKey]: RetryableInfo };
|
|
143
|
+
} => {
|
|
144
|
+
C.prototype[retryableKey] = info;
|
|
145
|
+
return C as any;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// ============================================================================
|
|
149
|
+
// Category Decorators (convenience functions for common categories)
|
|
150
|
+
// ============================================================================
|
|
151
|
+
|
|
152
|
+
export const withAuthError = withCategory(AuthError);
|
|
153
|
+
export const withBadRequestError = withCategory(BadRequestError);
|
|
154
|
+
export const withConflictError = withCategory(ConflictError);
|
|
155
|
+
export const withNotFoundError = withCategory(NotFoundError);
|
|
156
|
+
export const withQuotaError = withCategory(QuotaError);
|
|
157
|
+
export const withServerError = withCategory(ServerError);
|
|
158
|
+
export const withThrottlingError = withCategory(ThrottlingError);
|
|
159
|
+
export const withNetworkError = withCategory(NetworkError);
|
|
160
|
+
export const withParseError = withCategory(ParseError);
|
|
161
|
+
export const withConfigurationError = withCategory(ConfigurationError);
|
|
162
|
+
export const withTimeoutError = withCategory(TimeoutError);
|
|
163
|
+
export const withRetryableError = withCategory(RetryableError);
|
|
164
|
+
export const withLockedError = withCategory(LockedError);
|
|
165
|
+
export const withAbortedError = withCategory(AbortedError);
|
|
166
|
+
export const withAlreadyExistsError = withCategory(AlreadyExistsError);
|
|
167
|
+
export const withDependencyViolationError = withCategory(
|
|
168
|
+
DependencyViolationError,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// ============================================================================
|
|
172
|
+
// Category Predicates
|
|
173
|
+
// ============================================================================
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Check if an error has a specific category.
|
|
177
|
+
*/
|
|
178
|
+
export const hasCategory = (error: unknown, category: Category): boolean => {
|
|
179
|
+
if (
|
|
180
|
+
Predicate.isObject(error) &&
|
|
181
|
+
Predicate.hasProperty(categoriesKey)(error)
|
|
182
|
+
) {
|
|
183
|
+
// @ts-expect-error - dynamic property access
|
|
184
|
+
return category in error[categoriesKey];
|
|
185
|
+
}
|
|
186
|
+
return false;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
export const isAuthError = (error: unknown): boolean =>
|
|
190
|
+
hasCategory(error, AuthError);
|
|
191
|
+
|
|
192
|
+
export const isBadRequestError = (error: unknown): boolean =>
|
|
193
|
+
hasCategory(error, BadRequestError);
|
|
194
|
+
|
|
195
|
+
export const isConflictError = (error: unknown): boolean =>
|
|
196
|
+
hasCategory(error, ConflictError);
|
|
197
|
+
|
|
198
|
+
export const isNotFoundError = (error: unknown): boolean =>
|
|
199
|
+
hasCategory(error, NotFoundError);
|
|
200
|
+
|
|
201
|
+
export const isQuotaError = (error: unknown): boolean =>
|
|
202
|
+
hasCategory(error, QuotaError);
|
|
203
|
+
|
|
204
|
+
export const isServerError = (error: unknown): boolean =>
|
|
205
|
+
hasCategory(error, ServerError);
|
|
206
|
+
|
|
207
|
+
export const isThrottlingError = (error: unknown): boolean =>
|
|
208
|
+
hasCategory(error, ThrottlingError);
|
|
209
|
+
|
|
210
|
+
export const isNetworkError = (error: unknown): boolean =>
|
|
211
|
+
hasCategory(error, NetworkError);
|
|
212
|
+
|
|
213
|
+
export const isParseError = (error: unknown): boolean =>
|
|
214
|
+
hasCategory(error, ParseError);
|
|
215
|
+
|
|
216
|
+
export const isConfigurationError = (error: unknown): boolean =>
|
|
217
|
+
hasCategory(error, ConfigurationError);
|
|
218
|
+
|
|
219
|
+
export const isTimeoutError = (error: unknown): boolean =>
|
|
220
|
+
hasCategory(error, TimeoutError);
|
|
221
|
+
|
|
222
|
+
export const isRetryableError = (error: unknown): boolean =>
|
|
223
|
+
hasCategory(error, RetryableError);
|
|
224
|
+
|
|
225
|
+
export const isLockedError = (error: unknown): boolean =>
|
|
226
|
+
hasCategory(error, LockedError);
|
|
227
|
+
|
|
228
|
+
export const isAbortedError = (error: unknown): boolean =>
|
|
229
|
+
hasCategory(error, AbortedError);
|
|
230
|
+
|
|
231
|
+
export const isAlreadyExistsError = (error: unknown): boolean =>
|
|
232
|
+
hasCategory(error, AlreadyExistsError);
|
|
233
|
+
|
|
234
|
+
export const isDependencyViolationError = (error: unknown): boolean =>
|
|
235
|
+
hasCategory(error, DependencyViolationError);
|
|
236
|
+
|
|
237
|
+
// ============================================================================
|
|
238
|
+
// Transient Error Detection (for retry logic)
|
|
239
|
+
// ============================================================================
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Check if an error has the retryable trait (set via withRetryable).
|
|
243
|
+
*/
|
|
244
|
+
export const isRetryable = (error: unknown): boolean => {
|
|
245
|
+
if (Predicate.isObject(error) && Predicate.hasProperty(retryableKey)(error)) {
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
return false;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Check if an error is a throttling error.
|
|
253
|
+
* Either has ThrottlingError category or retryable trait with throttling: true.
|
|
254
|
+
*/
|
|
255
|
+
export const isThrottling = (error: unknown): boolean => {
|
|
256
|
+
if (Predicate.isObject(error) && Predicate.hasProperty(retryableKey)(error)) {
|
|
257
|
+
// @ts-expect-error - dynamic property access
|
|
258
|
+
return error[retryableKey]?.throttling === true;
|
|
259
|
+
}
|
|
260
|
+
return hasCategory(error, ThrottlingError);
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Check if an error is a transient error that should be automatically retried.
|
|
265
|
+
* Transient errors include:
|
|
266
|
+
* - Errors marked with withRetryable()
|
|
267
|
+
* - RetryableError category
|
|
268
|
+
* - ThrottlingError (rate limiting)
|
|
269
|
+
* - ServerError (5xx responses)
|
|
270
|
+
* - NetworkError (connection issues)
|
|
271
|
+
* - TimeoutError (request timed out)
|
|
272
|
+
* - LockedError (423 - resource temporarily locked)
|
|
273
|
+
*/
|
|
274
|
+
export const isTransientError = (error: unknown): boolean => {
|
|
275
|
+
// Check for retryable trait first
|
|
276
|
+
if (isRetryable(error)) {
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
// Fall back to category-based checking
|
|
280
|
+
return (
|
|
281
|
+
hasCategory(error, RetryableError) ||
|
|
282
|
+
hasCategory(error, ThrottlingError) ||
|
|
283
|
+
hasCategory(error, ServerError) ||
|
|
284
|
+
hasCategory(error, NetworkError) ||
|
|
285
|
+
hasCategory(error, TimeoutError) ||
|
|
286
|
+
hasCategory(error, LockedError)
|
|
287
|
+
);
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
// ============================================================================
|
|
291
|
+
// Category Type Utilities
|
|
292
|
+
// ============================================================================
|
|
293
|
+
|
|
294
|
+
export type AllKeys<E> = E extends { [categoriesKey]: infer Q }
|
|
295
|
+
? keyof Q
|
|
296
|
+
: never;
|
|
297
|
+
|
|
298
|
+
export type ExtractAll<E, Cats extends PropertyKey> = Cats extends any
|
|
299
|
+
? Extract<E, { [categoriesKey]: { [K in Cats]: any } }>
|
|
300
|
+
: never;
|
|
301
|
+
|
|
302
|
+
// ============================================================================
|
|
303
|
+
// Category Catchers
|
|
304
|
+
// ============================================================================
|
|
305
|
+
|
|
306
|
+
const makeCatcher =
|
|
307
|
+
(category: Category) =>
|
|
308
|
+
<A2, E2, R2>(f: (err: any) => Effect.Effect<A2, E2, R2>) =>
|
|
309
|
+
<A, E, R>(effect: Effect.Effect<A, E, R>) =>
|
|
310
|
+
Effect.catchIf(effect, (e) => hasCategory(e, category), f) as Effect.Effect<
|
|
311
|
+
A | A2,
|
|
312
|
+
E | E2,
|
|
313
|
+
R | R2
|
|
314
|
+
>;
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Catch errors matching any of the specified categories.
|
|
318
|
+
*
|
|
319
|
+
* @example
|
|
320
|
+
* ```ts
|
|
321
|
+
* effect.pipe(
|
|
322
|
+
* Category.catchErrors(Category.AuthError, Category.NotFoundError, (err) =>
|
|
323
|
+
* Effect.succeed("handled")
|
|
324
|
+
* )
|
|
325
|
+
* )
|
|
326
|
+
* ```
|
|
327
|
+
*/
|
|
328
|
+
export const catchErrors =
|
|
329
|
+
<Categories extends Category[], A2, E2, R2>(
|
|
330
|
+
...args: [...Categories, (err: any) => Effect.Effect<A2, E2, R2>]
|
|
331
|
+
) =>
|
|
332
|
+
<A, E, R>(effect: Effect.Effect<A, E, R>) => {
|
|
333
|
+
const handler = args.pop() as (err: any) => Effect.Effect<A2, E2, R2>;
|
|
334
|
+
const categories = args as unknown as Categories;
|
|
335
|
+
return Effect.catchIf(
|
|
336
|
+
effect,
|
|
337
|
+
(e) => categories.some((cat) => hasCategory(e, cat)),
|
|
338
|
+
handler,
|
|
339
|
+
) as Effect.Effect<A | A2, E | E2, R | R2>;
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
// Alias for convenience
|
|
343
|
+
export { catchErrors as catch };
|
|
344
|
+
|
|
345
|
+
export const catchAuthError = makeCatcher(AuthError);
|
|
346
|
+
export const catchBadRequestError = makeCatcher(BadRequestError);
|
|
347
|
+
export const catchConflictError = makeCatcher(ConflictError);
|
|
348
|
+
export const catchNotFoundError = makeCatcher(NotFoundError);
|
|
349
|
+
export const catchQuotaError = makeCatcher(QuotaError);
|
|
350
|
+
export const catchServerError = makeCatcher(ServerError);
|
|
351
|
+
export const catchThrottlingError = makeCatcher(ThrottlingError);
|
|
352
|
+
export const catchNetworkError = makeCatcher(NetworkError);
|
|
353
|
+
export const catchParseError = makeCatcher(ParseError);
|
|
354
|
+
export const catchConfigurationError = makeCatcher(ConfigurationError);
|
|
355
|
+
export const catchTimeoutError = makeCatcher(TimeoutError);
|
|
356
|
+
export const catchRetryableError = makeCatcher(RetryableError);
|
|
357
|
+
export const catchLockedError = makeCatcher(LockedError);
|
|
358
|
+
export const catchAbortedError = makeCatcher(AbortedError);
|
|
359
|
+
export const catchAlreadyExistsError = makeCatcher(AlreadyExistsError);
|
|
360
|
+
export const catchDependencyViolationError = makeCatcher(
|
|
361
|
+
DependencyViolationError,
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Catch errors with specified categories with full type narrowing.
|
|
366
|
+
*
|
|
367
|
+
* @example
|
|
368
|
+
* ```ts
|
|
369
|
+
* effect.pipe(
|
|
370
|
+
* Category.catchCategory(Category.AuthError, (err) => Effect.succeed("handled"))
|
|
371
|
+
* )
|
|
372
|
+
* ```
|
|
373
|
+
*/
|
|
374
|
+
export const catchCategory =
|
|
375
|
+
<E, const Categories extends Array<AllKeys<E>>, A2, E2, R2>(
|
|
376
|
+
...args: [
|
|
377
|
+
...Categories,
|
|
378
|
+
f: (err: ExtractAll<E, Categories[number]>) => Effect.Effect<A2, E2, R2>,
|
|
379
|
+
]
|
|
380
|
+
) =>
|
|
381
|
+
<A, R>(
|
|
382
|
+
effect: Effect.Effect<A, E, R>,
|
|
383
|
+
): Effect.Effect<
|
|
384
|
+
A | A2,
|
|
385
|
+
E2 | Exclude<E, ExtractAll<E, Categories[number]>>,
|
|
386
|
+
R | R2
|
|
387
|
+
> => {
|
|
388
|
+
const f = args.pop()!;
|
|
389
|
+
const categories = args;
|
|
390
|
+
return Effect.catchIf(
|
|
391
|
+
effect,
|
|
392
|
+
(e) => {
|
|
393
|
+
if (Predicate.isObject(e) && Predicate.hasProperty(categoriesKey)(e)) {
|
|
394
|
+
for (const cat of categories) {
|
|
395
|
+
// @ts-expect-error - dynamic property access
|
|
396
|
+
if (cat in e[categoriesKey]) {
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return false;
|
|
402
|
+
},
|
|
403
|
+
// @ts-expect-error - type narrowing limitation
|
|
404
|
+
(e) => f(e),
|
|
405
|
+
) as any;
|
|
406
|
+
};
|