@ontrails/core 1.0.0-beta.3 → 1.0.0-beta.32
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/CHANGELOG.md +323 -8
- package/README.md +114 -33
- package/package.json +11 -1
- package/src/activation-provenance.ts +111 -0
- package/src/activation-source-compatibility.ts +430 -0
- package/src/activation-source-projection.ts +221 -0
- package/src/activation-source.ts +91 -0
- package/src/blob-ref.ts +51 -0
- package/src/branded.ts +1 -1
- package/src/compose-batch.ts +69 -0
- package/src/compose-schema.ts +36 -0
- package/src/context.ts +44 -10
- package/src/contour.ts +346 -0
- package/src/derive.ts +250 -19
- package/src/detours.ts +8 -0
- package/src/diagnostics.ts +21 -0
- package/src/draft.ts +350 -0
- package/src/error-projection.ts +51 -0
- package/src/errors.ts +335 -48
- package/src/execute.ts +1577 -0
- package/src/fire.ts +1169 -0
- package/src/glob.ts +81 -0
- package/src/index.ts +526 -35
- package/src/internal/fork-ctx.ts +69 -0
- package/src/layer-projection.ts +193 -0
- package/src/layer.ts +43 -6
- package/src/observe.ts +361 -0
- package/src/path-scope.ts +66 -0
- package/src/path-security.ts +1 -1
- package/src/permits.ts +12 -0
- package/src/redaction/patterns.ts +6 -3
- package/src/resilience.ts +11 -11
- package/src/resource-config.ts +792 -0
- package/src/resource.ts +194 -0
- package/src/result.ts +18 -0
- package/src/run.ts +76 -0
- package/src/schedule-runtime.ts +689 -0
- package/src/schedule.ts +326 -0
- package/src/serialization.ts +92 -55
- package/src/signal-diagnostics.ts +633 -0
- package/src/signal-ref.ts +87 -0
- package/src/signal.ts +104 -0
- package/src/store/accessor-protocol.ts +56 -0
- package/src/store/index.ts +4 -0
- package/src/structured-examples.ts +248 -0
- package/src/surface-derivation.ts +91 -0
- package/src/surface-filter.ts +101 -0
- package/src/surface-versioning.ts +42 -0
- package/src/topo.ts +769 -67
- package/src/tracing.ts +345 -0
- package/src/trail-id-glob.ts +15 -0
- package/src/trail.ts +1168 -30
- package/src/trails/derive-trail.ts +842 -0
- package/src/trails/index.ts +9 -0
- package/src/trails/ingest.ts +139 -0
- package/src/trails-db.ts +206 -0
- package/src/transport-error-map.ts +174 -0
- package/src/type-utils.ts +87 -0
- package/src/types.ts +254 -12
- package/src/validate-established-topo.ts +69 -0
- package/src/validate-topo.ts +414 -56
- package/src/validation.ts +152 -4
- package/src/version-marker.ts +716 -0
- package/src/version-resolution.ts +308 -0
- package/src/version-runtime.ts +120 -0
- package/src/webhook.ts +278 -0
- package/src/workspace.ts +162 -1
- package/src/zod-wrappers.ts +72 -0
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/dist/adapters.d.ts +0 -39
- package/dist/adapters.d.ts.map +0 -1
- package/dist/adapters.js +0 -2
- package/dist/adapters.js.map +0 -1
- package/dist/blob-ref.d.ts +0 -20
- package/dist/blob-ref.d.ts.map +0 -1
- package/dist/blob-ref.js +0 -22
- package/dist/blob-ref.js.map +0 -1
- package/dist/branded.d.ts +0 -36
- package/dist/branded.d.ts.map +0 -1
- package/dist/branded.js +0 -89
- package/dist/branded.js.map +0 -1
- package/dist/collections.d.ts +0 -31
- package/dist/collections.d.ts.map +0 -1
- package/dist/collections.js +0 -60
- package/dist/collections.js.map +0 -1
- package/dist/context.d.ts +0 -10
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -15
- package/dist/context.js.map +0 -1
- package/dist/derive.d.ts +0 -33
- package/dist/derive.d.ts.map +0 -1
- package/dist/derive.js +0 -125
- package/dist/derive.js.map +0 -1
- package/dist/errors.d.ts +0 -83
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -142
- package/dist/errors.js.map +0 -1
- package/dist/event.d.ts +0 -45
- package/dist/event.d.ts.map +0 -1
- package/dist/event.js +0 -17
- package/dist/event.js.map +0 -1
- package/dist/fetch.d.ts +0 -15
- package/dist/fetch.d.ts.map +0 -1
- package/dist/fetch.js +0 -102
- package/dist/fetch.js.map +0 -1
- package/dist/guards.d.ts +0 -17
- package/dist/guards.d.ts.map +0 -1
- package/dist/guards.js +0 -25
- package/dist/guards.js.map +0 -1
- package/dist/health.d.ts +0 -18
- package/dist/health.d.ts.map +0 -1
- package/dist/health.js +0 -5
- package/dist/health.js.map +0 -1
- package/dist/hike.d.ts +0 -36
- package/dist/hike.d.ts.map +0 -1
- package/dist/hike.js +0 -20
- package/dist/hike.js.map +0 -1
- package/dist/index.d.ts +0 -36
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -40
- package/dist/index.js.map +0 -1
- package/dist/job.d.ts +0 -24
- package/dist/job.d.ts.map +0 -1
- package/dist/job.js +0 -17
- package/dist/job.js.map +0 -1
- package/dist/layer.d.ts +0 -17
- package/dist/layer.d.ts.map +0 -1
- package/dist/layer.js +0 -21
- package/dist/layer.js.map +0 -1
- package/dist/path-security.d.ts +0 -28
- package/dist/path-security.d.ts.map +0 -1
- package/dist/path-security.js +0 -63
- package/dist/path-security.js.map +0 -1
- package/dist/patterns/bulk.d.ts +0 -15
- package/dist/patterns/bulk.d.ts.map +0 -1
- package/dist/patterns/bulk.js +0 -14
- package/dist/patterns/bulk.js.map +0 -1
- package/dist/patterns/change.d.ts +0 -10
- package/dist/patterns/change.d.ts.map +0 -1
- package/dist/patterns/change.js +0 -10
- package/dist/patterns/change.js.map +0 -1
- package/dist/patterns/date-range.d.ts +0 -10
- package/dist/patterns/date-range.d.ts.map +0 -1
- package/dist/patterns/date-range.js +0 -10
- package/dist/patterns/date-range.js.map +0 -1
- package/dist/patterns/index.d.ts +0 -9
- package/dist/patterns/index.d.ts.map +0 -1
- package/dist/patterns/index.js +0 -9
- package/dist/patterns/index.js.map +0 -1
- package/dist/patterns/pagination.d.ts +0 -18
- package/dist/patterns/pagination.d.ts.map +0 -1
- package/dist/patterns/pagination.js +0 -18
- package/dist/patterns/pagination.js.map +0 -1
- package/dist/patterns/progress.d.ts +0 -11
- package/dist/patterns/progress.d.ts.map +0 -1
- package/dist/patterns/progress.js +0 -11
- package/dist/patterns/progress.js.map +0 -1
- package/dist/patterns/sorting.d.ts +0 -13
- package/dist/patterns/sorting.d.ts.map +0 -1
- package/dist/patterns/sorting.js +0 -10
- package/dist/patterns/sorting.js.map +0 -1
- package/dist/patterns/status.d.ts +0 -15
- package/dist/patterns/status.d.ts.map +0 -1
- package/dist/patterns/status.js +0 -9
- package/dist/patterns/status.js.map +0 -1
- package/dist/patterns/timestamps.d.ts +0 -10
- package/dist/patterns/timestamps.d.ts.map +0 -1
- package/dist/patterns/timestamps.js +0 -10
- package/dist/patterns/timestamps.js.map +0 -1
- package/dist/redaction/index.d.ts +0 -4
- package/dist/redaction/index.d.ts.map +0 -1
- package/dist/redaction/index.js +0 -3
- package/dist/redaction/index.js.map +0 -1
- package/dist/redaction/patterns.d.ts +0 -9
- package/dist/redaction/patterns.d.ts.map +0 -1
- package/dist/redaction/patterns.js +0 -39
- package/dist/redaction/patterns.js.map +0 -1
- package/dist/redaction/redactor.d.ts +0 -27
- package/dist/redaction/redactor.d.ts.map +0 -1
- package/dist/redaction/redactor.js +0 -89
- package/dist/redaction/redactor.js.map +0 -1
- package/dist/resilience.d.ts +0 -34
- package/dist/resilience.d.ts.map +0 -1
- package/dist/resilience.js +0 -164
- package/dist/resilience.js.map +0 -1
- package/dist/result.d.ts +0 -57
- package/dist/result.d.ts.map +0 -1
- package/dist/result.js +0 -156
- package/dist/result.js.map +0 -1
- package/dist/serialization.d.ts +0 -27
- package/dist/serialization.d.ts.map +0 -1
- package/dist/serialization.js +0 -153
- package/dist/serialization.js.map +0 -1
- package/dist/topo.d.ts +0 -18
- package/dist/topo.d.ts.map +0 -1
- package/dist/topo.js +0 -80
- package/dist/topo.js.map +0 -1
- package/dist/trail.d.ts +0 -83
- package/dist/trail.d.ts.map +0 -1
- package/dist/trail.js +0 -16
- package/dist/trail.js.map +0 -1
- package/dist/types.d.ts +0 -46
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/validate-topo.d.ts +0 -24
- package/dist/validate-topo.d.ts.map +0 -1
- package/dist/validate-topo.js +0 -156
- package/dist/validate-topo.js.map +0 -1
- package/dist/validation.d.ts +0 -27
- package/dist/validation.d.ts.map +0 -1
- package/dist/validation.js +0 -134
- package/dist/validation.js.map +0 -1
- package/dist/workspace.d.ts +0 -25
- package/dist/workspace.d.ts.map +0 -1
- package/dist/workspace.js +0 -57
- package/dist/workspace.js.map +0 -1
- package/src/__tests__/blob-ref.test.ts +0 -103
- package/src/__tests__/branded.test.ts +0 -148
- package/src/__tests__/collections.test.ts +0 -126
- package/src/__tests__/context.test.ts +0 -66
- package/src/__tests__/derive.test.ts +0 -203
- package/src/__tests__/errors.test.ts +0 -309
- package/src/__tests__/event.test.ts +0 -82
- package/src/__tests__/fetch.test.ts +0 -217
- package/src/__tests__/guards.test.ts +0 -102
- package/src/__tests__/hike.test.ts +0 -117
- package/src/__tests__/job.test.ts +0 -98
- package/src/__tests__/layer.test.ts +0 -224
- package/src/__tests__/path-security.test.ts +0 -114
- package/src/__tests__/patterns.test.ts +0 -273
- package/src/__tests__/redaction.test.ts +0 -244
- package/src/__tests__/resilience.test.ts +0 -246
- package/src/__tests__/result.test.ts +0 -155
- package/src/__tests__/serialization.test.ts +0 -401
- package/src/__tests__/topo.test.ts +0 -220
- package/src/__tests__/trail.test.ts +0 -179
- package/src/__tests__/validate-topo.test.ts +0 -280
- package/src/__tests__/validation.test.ts +0 -283
- package/src/__tests__/workspace.test.ts +0 -183
- package/src/adapters.ts +0 -68
- package/src/event.ts +0 -77
- package/src/health.ts +0 -23
- package/src/hike.ts +0 -77
- package/src/job.ts +0 -20
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
TrailsError,
|
|
5
|
-
ValidationError,
|
|
6
|
-
AmbiguousError,
|
|
7
|
-
AssertionError,
|
|
8
|
-
NotFoundError,
|
|
9
|
-
AlreadyExistsError,
|
|
10
|
-
ConflictError,
|
|
11
|
-
PermissionError,
|
|
12
|
-
TimeoutError,
|
|
13
|
-
RateLimitError,
|
|
14
|
-
NetworkError,
|
|
15
|
-
InternalError,
|
|
16
|
-
AuthError,
|
|
17
|
-
CancelledError,
|
|
18
|
-
exitCodeMap,
|
|
19
|
-
statusCodeMap,
|
|
20
|
-
jsonRpcCodeMap,
|
|
21
|
-
retryableMap,
|
|
22
|
-
isRetryable,
|
|
23
|
-
isTrailsError,
|
|
24
|
-
} from '../errors.js';
|
|
25
|
-
import type { ErrorCategory } from '../errors.js';
|
|
26
|
-
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
// Error class matrix
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
|
|
31
|
-
const errorMatrix: readonly {
|
|
32
|
-
Class: new (
|
|
33
|
-
message: string,
|
|
34
|
-
options?: { cause?: Error; context?: Record<string, unknown> }
|
|
35
|
-
) => TrailsError;
|
|
36
|
-
category: ErrorCategory;
|
|
37
|
-
retryable: boolean;
|
|
38
|
-
name: string;
|
|
39
|
-
}[] = [
|
|
40
|
-
{
|
|
41
|
-
Class: ValidationError,
|
|
42
|
-
category: 'validation',
|
|
43
|
-
name: 'ValidationError',
|
|
44
|
-
retryable: false,
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
Class: AmbiguousError,
|
|
48
|
-
category: 'validation',
|
|
49
|
-
name: 'AmbiguousError',
|
|
50
|
-
retryable: false,
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
Class: AssertionError,
|
|
54
|
-
category: 'internal',
|
|
55
|
-
name: 'AssertionError',
|
|
56
|
-
retryable: false,
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
Class: NotFoundError,
|
|
60
|
-
category: 'not_found',
|
|
61
|
-
name: 'NotFoundError',
|
|
62
|
-
retryable: false,
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
Class: AlreadyExistsError,
|
|
66
|
-
category: 'conflict',
|
|
67
|
-
name: 'AlreadyExistsError',
|
|
68
|
-
retryable: false,
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
Class: ConflictError,
|
|
72
|
-
category: 'conflict',
|
|
73
|
-
name: 'ConflictError',
|
|
74
|
-
retryable: false,
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
Class: PermissionError,
|
|
78
|
-
category: 'permission',
|
|
79
|
-
name: 'PermissionError',
|
|
80
|
-
retryable: false,
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
Class: TimeoutError,
|
|
84
|
-
category: 'timeout',
|
|
85
|
-
name: 'TimeoutError',
|
|
86
|
-
retryable: true,
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
Class: RateLimitError,
|
|
90
|
-
category: 'rate_limit',
|
|
91
|
-
name: 'RateLimitError',
|
|
92
|
-
retryable: true,
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
Class: NetworkError,
|
|
96
|
-
category: 'network',
|
|
97
|
-
name: 'NetworkError',
|
|
98
|
-
retryable: true,
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
Class: InternalError,
|
|
102
|
-
category: 'internal',
|
|
103
|
-
name: 'InternalError',
|
|
104
|
-
retryable: false,
|
|
105
|
-
},
|
|
106
|
-
{ Class: AuthError, category: 'auth', name: 'AuthError', retryable: false },
|
|
107
|
-
{
|
|
108
|
-
Class: CancelledError,
|
|
109
|
-
category: 'cancelled',
|
|
110
|
-
name: 'CancelledError',
|
|
111
|
-
retryable: false,
|
|
112
|
-
},
|
|
113
|
-
];
|
|
114
|
-
|
|
115
|
-
describe('error classes', () => {
|
|
116
|
-
// oxlint-disable-next-line prefer-each -- describe.each loses type safety on heterogeneous class matrix
|
|
117
|
-
for (const { Class, category, retryable, name } of errorMatrix) {
|
|
118
|
-
describe(name, () => {
|
|
119
|
-
test('sets correct category', () => {
|
|
120
|
-
const err = new Class('test');
|
|
121
|
-
expect(err.category).toBe(category);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test('sets correct retryable', () => {
|
|
125
|
-
const err = new Class('test');
|
|
126
|
-
expect(err.retryable).toBe(retryable);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test('sets message', () => {
|
|
130
|
-
const err = new Class('something went wrong');
|
|
131
|
-
expect(err.message).toBe('something went wrong');
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test('sets name to constructor name', () => {
|
|
135
|
-
const err = new Class('test');
|
|
136
|
-
expect(err.name).toBe(name);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
test('sets cause when provided', () => {
|
|
140
|
-
const cause = new Error('root cause');
|
|
141
|
-
const err = new Class('test', { cause });
|
|
142
|
-
expect(err.cause).toBe(cause);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
test('sets context when provided', () => {
|
|
146
|
-
const ctx = { action: 'delete', userId: 'u_123' };
|
|
147
|
-
const err = new Class('test', { context: ctx });
|
|
148
|
-
expect(err.context).toEqual(ctx);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
test('context is undefined when not provided', () => {
|
|
152
|
-
const err = new Class('test');
|
|
153
|
-
expect(err.context).toBeUndefined();
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
test('is instanceof TrailsError', () => {
|
|
157
|
-
const err = new Class('test');
|
|
158
|
-
expect(err).toBeInstanceOf(TrailsError);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
test('is instanceof Error', () => {
|
|
162
|
-
const err = new Class('test');
|
|
163
|
-
expect(err).toBeInstanceOf(Error);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
test('is instanceof its own class', () => {
|
|
167
|
-
const err = new Class('test');
|
|
168
|
-
expect(err).toBeInstanceOf(Class);
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
// ---------------------------------------------------------------------------
|
|
175
|
-
// RateLimitError.retryAfter
|
|
176
|
-
// ---------------------------------------------------------------------------
|
|
177
|
-
|
|
178
|
-
describe('RateLimitError.retryAfter', () => {
|
|
179
|
-
test('stores retryAfter when provided', () => {
|
|
180
|
-
const err = new RateLimitError('slow down', { retryAfter: 30 });
|
|
181
|
-
expect(err.retryAfter).toBe(30);
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
test('retryAfter is undefined when not provided', () => {
|
|
185
|
-
const err = new RateLimitError('slow down');
|
|
186
|
-
expect(err.retryAfter).toBeUndefined();
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
test('retryAfter coexists with cause and context', () => {
|
|
190
|
-
const cause = new Error('upstream');
|
|
191
|
-
const err = new RateLimitError('slow down', {
|
|
192
|
-
cause,
|
|
193
|
-
context: { endpoint: '/api' },
|
|
194
|
-
retryAfter: 60,
|
|
195
|
-
});
|
|
196
|
-
expect(err.retryAfter).toBe(60);
|
|
197
|
-
expect(err.cause).toBe(cause);
|
|
198
|
-
expect(err.context).toEqual({ endpoint: '/api' });
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// ---------------------------------------------------------------------------
|
|
203
|
-
// Taxonomy maps
|
|
204
|
-
// ---------------------------------------------------------------------------
|
|
205
|
-
|
|
206
|
-
describe('exitCodeMap', () => {
|
|
207
|
-
test('maps all categories to expected exit codes', () => {
|
|
208
|
-
expect(exitCodeMap.validation).toBe(1);
|
|
209
|
-
expect(exitCodeMap.not_found).toBe(2);
|
|
210
|
-
expect(exitCodeMap.conflict).toBe(3);
|
|
211
|
-
expect(exitCodeMap.permission).toBe(4);
|
|
212
|
-
expect(exitCodeMap.timeout).toBe(5);
|
|
213
|
-
expect(exitCodeMap.rate_limit).toBe(6);
|
|
214
|
-
expect(exitCodeMap.network).toBe(7);
|
|
215
|
-
expect(exitCodeMap.internal).toBe(8);
|
|
216
|
-
expect(exitCodeMap.auth).toBe(9);
|
|
217
|
-
expect(exitCodeMap.cancelled).toBe(130);
|
|
218
|
-
});
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
describe('statusCodeMap', () => {
|
|
222
|
-
test('maps all categories to expected HTTP status codes', () => {
|
|
223
|
-
expect(statusCodeMap.validation).toBe(400);
|
|
224
|
-
expect(statusCodeMap.not_found).toBe(404);
|
|
225
|
-
expect(statusCodeMap.conflict).toBe(409);
|
|
226
|
-
expect(statusCodeMap.permission).toBe(403);
|
|
227
|
-
expect(statusCodeMap.timeout).toBe(504);
|
|
228
|
-
expect(statusCodeMap.rate_limit).toBe(429);
|
|
229
|
-
expect(statusCodeMap.network).toBe(502);
|
|
230
|
-
expect(statusCodeMap.internal).toBe(500);
|
|
231
|
-
expect(statusCodeMap.auth).toBe(401);
|
|
232
|
-
expect(statusCodeMap.cancelled).toBe(499);
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
describe('jsonRpcCodeMap', () => {
|
|
237
|
-
test('maps all categories to expected JSON-RPC codes', () => {
|
|
238
|
-
expect(jsonRpcCodeMap.validation).toBe(-32_602);
|
|
239
|
-
expect(jsonRpcCodeMap.not_found).toBe(-32_601);
|
|
240
|
-
expect(jsonRpcCodeMap.conflict).toBe(-32_603);
|
|
241
|
-
expect(jsonRpcCodeMap.permission).toBe(-32_600);
|
|
242
|
-
expect(jsonRpcCodeMap.timeout).toBe(-32_603);
|
|
243
|
-
expect(jsonRpcCodeMap.rate_limit).toBe(-32_603);
|
|
244
|
-
expect(jsonRpcCodeMap.network).toBe(-32_603);
|
|
245
|
-
expect(jsonRpcCodeMap.internal).toBe(-32_603);
|
|
246
|
-
expect(jsonRpcCodeMap.auth).toBe(-32_600);
|
|
247
|
-
expect(jsonRpcCodeMap.cancelled).toBe(-32_603);
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
describe('retryableMap', () => {
|
|
252
|
-
test('only timeout, rate_limit, and network are retryable', () => {
|
|
253
|
-
expect(retryableMap.timeout).toBe(true);
|
|
254
|
-
expect(retryableMap.rate_limit).toBe(true);
|
|
255
|
-
expect(retryableMap.network).toBe(true);
|
|
256
|
-
|
|
257
|
-
expect(retryableMap.validation).toBe(false);
|
|
258
|
-
expect(retryableMap.not_found).toBe(false);
|
|
259
|
-
expect(retryableMap.conflict).toBe(false);
|
|
260
|
-
expect(retryableMap.permission).toBe(false);
|
|
261
|
-
expect(retryableMap.internal).toBe(false);
|
|
262
|
-
expect(retryableMap.auth).toBe(false);
|
|
263
|
-
expect(retryableMap.cancelled).toBe(false);
|
|
264
|
-
});
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
// ---------------------------------------------------------------------------
|
|
268
|
-
// Helper functions
|
|
269
|
-
// ---------------------------------------------------------------------------
|
|
270
|
-
|
|
271
|
-
describe('isRetryable', () => {
|
|
272
|
-
test('returns true for retryable errors', () => {
|
|
273
|
-
expect(isRetryable(new TimeoutError('timed out'))).toBe(true);
|
|
274
|
-
expect(isRetryable(new RateLimitError('too fast'))).toBe(true);
|
|
275
|
-
expect(isRetryable(new NetworkError('disconnected'))).toBe(true);
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
test('returns false for non-retryable errors', () => {
|
|
279
|
-
expect(isRetryable(new ValidationError('bad input'))).toBe(false);
|
|
280
|
-
expect(isRetryable(new NotFoundError('missing'))).toBe(false);
|
|
281
|
-
expect(isRetryable(new AuthError('unauthorized'))).toBe(false);
|
|
282
|
-
expect(isRetryable(new InternalError('oops'))).toBe(false);
|
|
283
|
-
expect(isRetryable(new CancelledError('aborted'))).toBe(false);
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
test('returns false for plain Error', () => {
|
|
287
|
-
expect(isRetryable(new Error('generic'))).toBe(false);
|
|
288
|
-
});
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
describe('isTrailsError', () => {
|
|
292
|
-
test('returns true for TrailsError subclasses', () => {
|
|
293
|
-
expect(isTrailsError(new ValidationError('bad'))).toBe(true);
|
|
294
|
-
expect(isTrailsError(new NetworkError('down'))).toBe(true);
|
|
295
|
-
expect(isTrailsError(new CancelledError('stop'))).toBe(true);
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
test('returns false for plain Error', () => {
|
|
299
|
-
expect(isTrailsError(new Error('nope'))).toBe(false);
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
test('returns false for non-error values', () => {
|
|
303
|
-
expect(isTrailsError(null)).toBe(false);
|
|
304
|
-
expect(isTrailsError()).toBe(false);
|
|
305
|
-
expect(isTrailsError('string')).toBe(false);
|
|
306
|
-
expect(isTrailsError(42)).toBe(false);
|
|
307
|
-
expect(isTrailsError({})).toBe(false);
|
|
308
|
-
});
|
|
309
|
-
});
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
|
|
5
|
-
import { event } from '../event';
|
|
6
|
-
|
|
7
|
-
const payloadSchema = z.object({
|
|
8
|
-
action: z.string(),
|
|
9
|
-
userId: z.string(),
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
const userAction = event('user.action', {
|
|
13
|
-
description: 'A user performed an action',
|
|
14
|
-
from: ['auth.login', 'auth.signup'],
|
|
15
|
-
markers: { domain: 'auth', priority: 1 },
|
|
16
|
-
payload: payloadSchema,
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
describe('event() basics', () => {
|
|
20
|
-
test("returns kind 'event'", () => {
|
|
21
|
-
expect(userAction.kind).toBe('event');
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test('returns correct id', () => {
|
|
25
|
-
expect(userAction.id).toBe('user.action');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test('preserves payload schema', () => {
|
|
29
|
-
const parsed = userAction.payload.safeParse({
|
|
30
|
-
action: 'click',
|
|
31
|
-
userId: 'u-1',
|
|
32
|
-
});
|
|
33
|
-
expect(parsed.success).toBe(true);
|
|
34
|
-
|
|
35
|
-
const bad = userAction.payload.safeParse({ userId: 42 });
|
|
36
|
-
expect(bad.success).toBe(false);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test('preserves description', () => {
|
|
40
|
-
expect(userAction.description).toBe('A user performed an action');
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('result object is frozen', () => {
|
|
44
|
-
expect(Object.isFrozen(userAction)).toBe(true);
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
describe('event() from and markers', () => {
|
|
49
|
-
test('preserves from', () => {
|
|
50
|
-
expect(userAction.from).toEqual(['auth.login', 'auth.signup']);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test('from array is frozen', () => {
|
|
54
|
-
expect(Object.isFrozen(userAction.from)).toBe(true);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('preserves markers', () => {
|
|
58
|
-
expect(userAction.markers).toEqual({ domain: 'auth', priority: 1 });
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('optional fields default to undefined', () => {
|
|
62
|
-
const minimal = event('minimal', {
|
|
63
|
-
payload: z.string(),
|
|
64
|
-
});
|
|
65
|
-
expect(minimal.description).toBeUndefined();
|
|
66
|
-
expect(minimal.from).toBeUndefined();
|
|
67
|
-
expect(minimal.markers).toBeUndefined();
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
describe('event() single-object overload', () => {
|
|
72
|
-
test('accepts spec with id property', () => {
|
|
73
|
-
const e = event({
|
|
74
|
-
from: ['entity.add', 'entity.delete'],
|
|
75
|
-
id: 'entity.updated',
|
|
76
|
-
payload: z.object({ entityId: z.string() }),
|
|
77
|
-
});
|
|
78
|
-
expect(e.id).toBe('entity.updated');
|
|
79
|
-
expect(e.kind).toBe('event');
|
|
80
|
-
expect(e.from).toEqual(['entity.add', 'entity.delete']);
|
|
81
|
-
});
|
|
82
|
-
});
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
/* oxlint-disable require-await -- test mocks satisfy async interface without awaiting */
|
|
2
|
-
import { describe, test, expect, afterEach } from 'bun:test';
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
AuthError,
|
|
6
|
-
CancelledError,
|
|
7
|
-
InternalError,
|
|
8
|
-
NetworkError,
|
|
9
|
-
NotFoundError,
|
|
10
|
-
PermissionError,
|
|
11
|
-
ValidationError,
|
|
12
|
-
RateLimitError,
|
|
13
|
-
TimeoutError,
|
|
14
|
-
} from '../errors.js';
|
|
15
|
-
import { Result } from '../result.js';
|
|
16
|
-
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
// Mock fetch
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
const originalFetch = globalThis.fetch;
|
|
22
|
-
|
|
23
|
-
const mockFetch = (impl: () => Promise<Response>) => {
|
|
24
|
-
globalThis.fetch = impl as unknown as typeof globalThis.fetch;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
afterEach(() => {
|
|
28
|
-
globalThis.fetch = originalFetch;
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
const fakeResponse = (
|
|
32
|
-
status: number,
|
|
33
|
-
options?: { headers?: Record<string, string> }
|
|
34
|
-
): Response => {
|
|
35
|
-
const init: ResponseInit = { status };
|
|
36
|
-
if (options?.headers) {
|
|
37
|
-
init.headers = options.headers;
|
|
38
|
-
}
|
|
39
|
-
return new Response(null, init);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// ---------------------------------------------------------------------------
|
|
43
|
-
// Successful responses
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
|
-
|
|
46
|
-
describe('Result.fromFetch — success', () => {
|
|
47
|
-
test('returns Ok with Response for 200', async () => {
|
|
48
|
-
mockFetch(async () => fakeResponse(200));
|
|
49
|
-
const result = await Result.fromFetch('https://example.com/api');
|
|
50
|
-
expect(result.isOk()).toBe(true);
|
|
51
|
-
expect(result.unwrap().status).toBe(200);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('returns Ok for 201', async () => {
|
|
55
|
-
mockFetch(async () => fakeResponse(201));
|
|
56
|
-
const result = await Result.fromFetch('https://example.com/api');
|
|
57
|
-
expect(result.isOk()).toBe(true);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test('returns Ok for 204', async () => {
|
|
61
|
-
mockFetch(async () => fakeResponse(204));
|
|
62
|
-
const result = await Result.fromFetch('https://example.com/api');
|
|
63
|
-
expect(result.isOk()).toBe(true);
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// ---------------------------------------------------------------------------
|
|
68
|
-
// HTTP error status mapping
|
|
69
|
-
// ---------------------------------------------------------------------------
|
|
70
|
-
|
|
71
|
-
describe('Result.fromFetch — status code mapping', () => {
|
|
72
|
-
test('401 → AuthError', async () => {
|
|
73
|
-
mockFetch(async () => fakeResponse(401));
|
|
74
|
-
const result = await Result.fromFetch('https://example.com/api');
|
|
75
|
-
expect(result.isErr()).toBe(true);
|
|
76
|
-
expect((result as unknown as { error: Error }).error).toBeInstanceOf(
|
|
77
|
-
AuthError
|
|
78
|
-
);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test('403 → PermissionError', async () => {
|
|
82
|
-
mockFetch(async () => fakeResponse(403));
|
|
83
|
-
const result = await Result.fromFetch('https://example.com/api');
|
|
84
|
-
expect(result.isErr()).toBe(true);
|
|
85
|
-
expect((result as unknown as { error: Error }).error).toBeInstanceOf(
|
|
86
|
-
PermissionError
|
|
87
|
-
);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('404 → NotFoundError', async () => {
|
|
91
|
-
mockFetch(async () => fakeResponse(404));
|
|
92
|
-
const result = await Result.fromFetch('https://example.com/api');
|
|
93
|
-
expect(result.isErr()).toBe(true);
|
|
94
|
-
expect((result as unknown as { error: Error }).error).toBeInstanceOf(
|
|
95
|
-
NotFoundError
|
|
96
|
-
);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test('429 → RateLimitError', async () => {
|
|
100
|
-
mockFetch(async () => fakeResponse(429));
|
|
101
|
-
const result = await Result.fromFetch('https://example.com/api');
|
|
102
|
-
expect(result.isErr()).toBe(true);
|
|
103
|
-
expect((result as unknown as { error: Error }).error).toBeInstanceOf(
|
|
104
|
-
RateLimitError
|
|
105
|
-
);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test('429 with retry-after header', async () => {
|
|
109
|
-
mockFetch(async () =>
|
|
110
|
-
fakeResponse(429, { headers: { 'retry-after': '30' } })
|
|
111
|
-
);
|
|
112
|
-
const result = await Result.fromFetch('https://example.com/api');
|
|
113
|
-
expect(result.isErr()).toBe(true);
|
|
114
|
-
const err = (result as unknown as { error: RateLimitError }).error;
|
|
115
|
-
expect(err).toBeInstanceOf(RateLimitError);
|
|
116
|
-
expect(err.retryAfter).toBe(30);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test('500 → InternalError', async () => {
|
|
120
|
-
mockFetch(async () => fakeResponse(500));
|
|
121
|
-
const result = await Result.fromFetch('https://example.com/api');
|
|
122
|
-
expect(result.isErr()).toBe(true);
|
|
123
|
-
expect((result as unknown as { error: Error }).error).toBeInstanceOf(
|
|
124
|
-
InternalError
|
|
125
|
-
);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test('502 → NetworkError', async () => {
|
|
129
|
-
mockFetch(async () => fakeResponse(502));
|
|
130
|
-
const result = await Result.fromFetch('https://example.com/api');
|
|
131
|
-
expect(result.isErr()).toBe(true);
|
|
132
|
-
expect((result as unknown as { error: Error }).error).toBeInstanceOf(
|
|
133
|
-
NetworkError
|
|
134
|
-
);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test('504 → TimeoutError', async () => {
|
|
138
|
-
mockFetch(async () => fakeResponse(504));
|
|
139
|
-
const result = await Result.fromFetch('https://example.com/api');
|
|
140
|
-
expect(result.isErr()).toBe(true);
|
|
141
|
-
expect((result as unknown as { error: Error }).error).toBeInstanceOf(
|
|
142
|
-
TimeoutError
|
|
143
|
-
);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test('503 → InternalError (generic 5xx)', async () => {
|
|
147
|
-
mockFetch(async () => fakeResponse(503));
|
|
148
|
-
const result = await Result.fromFetch('https://example.com/api');
|
|
149
|
-
expect(result.isErr()).toBe(true);
|
|
150
|
-
expect((result as unknown as { error: Error }).error).toBeInstanceOf(
|
|
151
|
-
InternalError
|
|
152
|
-
);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
test('400 → ValidationError', async () => {
|
|
156
|
-
mockFetch(async () => fakeResponse(400));
|
|
157
|
-
const result = await Result.fromFetch('https://example.com/api');
|
|
158
|
-
expect(result.isErr()).toBe(true);
|
|
159
|
-
expect((result as unknown as { error: Error }).error).toBeInstanceOf(
|
|
160
|
-
ValidationError
|
|
161
|
-
);
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
// ---------------------------------------------------------------------------
|
|
166
|
-
// Network / abort errors
|
|
167
|
-
// ---------------------------------------------------------------------------
|
|
168
|
-
|
|
169
|
-
describe('Result.fromFetch — network errors', () => {
|
|
170
|
-
test('TypeError → NetworkError', async () => {
|
|
171
|
-
mockFetch(async () => {
|
|
172
|
-
throw new TypeError('Failed to fetch');
|
|
173
|
-
});
|
|
174
|
-
const result = await Result.fromFetch('https://example.com/api');
|
|
175
|
-
expect(result.isErr()).toBe(true);
|
|
176
|
-
expect((result as unknown as { error: Error }).error).toBeInstanceOf(
|
|
177
|
-
NetworkError
|
|
178
|
-
);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
test('AbortError → CancelledError', async () => {
|
|
182
|
-
mockFetch(async () => {
|
|
183
|
-
throw new DOMException('The operation was aborted', 'AbortError');
|
|
184
|
-
});
|
|
185
|
-
const result = await Result.fromFetch('https://example.com/api');
|
|
186
|
-
expect(result.isErr()).toBe(true);
|
|
187
|
-
expect((result as unknown as { error: Error }).error).toBeInstanceOf(
|
|
188
|
-
CancelledError
|
|
189
|
-
);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
test('unknown thrown value → NetworkError', async () => {
|
|
193
|
-
mockFetch(async () => {
|
|
194
|
-
// oxlint-disable-next-line no-throw-literal -- intentionally testing non-Error rejection handling
|
|
195
|
-
throw 'string error';
|
|
196
|
-
});
|
|
197
|
-
const result = await Result.fromFetch('https://example.com/api');
|
|
198
|
-
expect(result.isErr()).toBe(true);
|
|
199
|
-
expect((result as unknown as { error: Error }).error).toBeInstanceOf(
|
|
200
|
-
NetworkError
|
|
201
|
-
);
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
// ---------------------------------------------------------------------------
|
|
206
|
-
// Error context
|
|
207
|
-
// ---------------------------------------------------------------------------
|
|
208
|
-
|
|
209
|
-
describe('Result.fromFetch — error context', () => {
|
|
210
|
-
test('includes status in error context', async () => {
|
|
211
|
-
mockFetch(async () => fakeResponse(404));
|
|
212
|
-
const result = await Result.fromFetch('https://example.com/api');
|
|
213
|
-
expect(result.isErr()).toBe(true);
|
|
214
|
-
const err = (result as unknown as { error: NotFoundError }).error;
|
|
215
|
-
expect(err.context?.['status']).toBe(404);
|
|
216
|
-
});
|
|
217
|
-
});
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
isDefined,
|
|
5
|
-
isNonEmptyString,
|
|
6
|
-
isPlainObject,
|
|
7
|
-
hasProperty,
|
|
8
|
-
assertNever,
|
|
9
|
-
} from '../guards';
|
|
10
|
-
|
|
11
|
-
describe('guards', () => {
|
|
12
|
-
describe('isDefined()', () => {
|
|
13
|
-
test('returns true for non-nullish values', () => {
|
|
14
|
-
expect(isDefined(0)).toBe(true);
|
|
15
|
-
expect(isDefined('')).toBe(true);
|
|
16
|
-
expect(isDefined(false)).toBe(true);
|
|
17
|
-
expect(isDefined([])).toBe(true);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test('returns false for null', () => {
|
|
21
|
-
expect(isDefined(null)).toBe(false);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test('returns false for undefined', () => {
|
|
25
|
-
expect(isDefined()).toBe(false);
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
describe('isNonEmptyString()', () => {
|
|
30
|
-
test('returns true for non-empty strings', () => {
|
|
31
|
-
expect(isNonEmptyString('hello')).toBe(true);
|
|
32
|
-
expect(isNonEmptyString(' ')).toBe(true);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test('returns false for empty string', () => {
|
|
36
|
-
expect(isNonEmptyString('')).toBe(false);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test('returns false for non-string types', () => {
|
|
40
|
-
expect(isNonEmptyString(42)).toBe(false);
|
|
41
|
-
expect(isNonEmptyString(null)).toBe(false);
|
|
42
|
-
expect(isNonEmptyString()).toBe(false);
|
|
43
|
-
expect(isNonEmptyString([])).toBe(false);
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
describe('isPlainObject()', () => {
|
|
48
|
-
test('returns true for plain objects', () => {
|
|
49
|
-
expect(isPlainObject({})).toBe(true);
|
|
50
|
-
expect(isPlainObject({ a: 1 })).toBe(true);
|
|
51
|
-
expect(isPlainObject(Object.create(null))).toBe(true);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('returns false for arrays', () => {
|
|
55
|
-
expect(isPlainObject([])).toBe(false);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('returns false for class instances', () => {
|
|
59
|
-
expect(isPlainObject(new Date())).toBe(false);
|
|
60
|
-
expect(isPlainObject(new Map())).toBe(false);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('returns false for null', () => {
|
|
64
|
-
expect(isPlainObject(null)).toBe(false);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test('returns false for primitives', () => {
|
|
68
|
-
expect(isPlainObject('string')).toBe(false);
|
|
69
|
-
expect(isPlainObject(42)).toBe(false);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe('hasProperty()', () => {
|
|
74
|
-
test('returns true when key exists', () => {
|
|
75
|
-
expect(hasProperty({ name: 'test' }, 'name')).toBe(true);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('returns true for keys with undefined values', () => {
|
|
79
|
-
expect(hasProperty({ x: undefined }, 'x')).toBe(true);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test('returns false when key is missing', () => {
|
|
83
|
-
expect(hasProperty({}, 'missing')).toBe(false);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test('returns false for null', () => {
|
|
87
|
-
expect(hasProperty(null, 'key')).toBe(false);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('returns false for primitives', () => {
|
|
91
|
-
expect(hasProperty(42, 'key')).toBe(false);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe('assertNever()', () => {
|
|
96
|
-
test('throws with a descriptive message', () => {
|
|
97
|
-
expect(() => assertNever('unexpected' as never)).toThrow(
|
|
98
|
-
'Unexpected value: unexpected'
|
|
99
|
-
);
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
});
|