@ontrails/core 1.0.0-beta.4 → 1.0.0-beta.41
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 +412 -12
- package/README.md +112 -28
- package/package.json +11 -1
- package/src/activation-provenance.ts +116 -0
- package/src/activation-source-compatibility.ts +430 -0
- package/src/activation-source-projection.ts +227 -0
- package/src/activation-source.ts +93 -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 +59 -11
- package/src/derive.ts +271 -19
- package/src/detours.ts +8 -0
- package/src/diagnostics.ts +21 -0
- package/src/draft.ts +350 -0
- package/src/entity.ts +346 -0
- package/src/error-projection.ts +51 -0
- package/src/errors.ts +335 -48
- package/src/execute.ts +1577 -0
- package/src/fire.ts +1172 -0
- package/src/glob.ts +81 -0
- package/src/index.ts +568 -32
- 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 +11 -3
- package/src/permits.ts +12 -0
- package/src/queue.ts +163 -0
- package/src/redaction/patterns.ts +6 -3
- package/src/resilience.ts +11 -11
- package/src/resource-config.ts +804 -0
- package/src/resource.ts +194 -0
- package/src/result.ts +18 -0
- package/src/run.ts +76 -0
- package/src/runtime-builtins.ts +69 -0
- package/src/schedule-runtime.ts +689 -0
- package/src/schedule.ts +326 -0
- package/src/serialization.ts +92 -55
- package/src/sha256.ts +136 -0
- package/src/signal-diagnostics.ts +633 -0
- package/src/signal-ref.ts +111 -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-overlay.ts +694 -0
- package/src/surface-versioning.ts +42 -0
- package/src/topo.ts +762 -38
- package/src/tracing.ts +346 -0
- package/src/trail-id-glob.ts +15 -0
- package/src/trail.ts +1244 -34
- package/src/trails/derive-trail.ts +835 -0
- package/src/trails/index.ts +9 -0
- package/src/trails/ingest.ts +152 -0
- package/src/trails-db.ts +212 -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 +73 -0
- package/src/validate-topo.ts +569 -55
- 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 +461 -0
- package/src/workspace.ts +183 -16
- 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/index.d.ts +0 -34
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -38
- 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 -16
- package/dist/topo.d.ts.map +0 -1
- package/dist/topo.js +0 -66
- package/dist/topo.js.map +0 -1
- package/dist/trail.d.ts +0 -89
- package/dist/trail.d.ts.map +0 -1
- package/dist/trail.js +0 -18
- 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 -158
- 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__/job.test.ts +0 -98
- package/src/__tests__/layer.test.ts +0 -212
- 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 -181
- package/src/__tests__/trail.test.ts +0 -217
- package/src/__tests__/validate-topo.test.ts +0 -278
- 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/job.ts +0 -20
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -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
|
-
});
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
|
|
5
|
-
import { jobOutputSchema } from '../job';
|
|
6
|
-
|
|
7
|
-
describe('jobOutputSchema', () => {
|
|
8
|
-
test('parses a valid completed job output', () => {
|
|
9
|
-
const input = {
|
|
10
|
-
completedAt: '2026-03-25T00:01:00Z',
|
|
11
|
-
current: 10,
|
|
12
|
-
jobId: 'job-123',
|
|
13
|
-
percentage: 100,
|
|
14
|
-
result: { rows: 42 },
|
|
15
|
-
startedAt: '2026-03-25T00:00:00Z',
|
|
16
|
-
status: 'completed',
|
|
17
|
-
total: 10,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const parsed = jobOutputSchema.parse(input);
|
|
21
|
-
|
|
22
|
-
expect(parsed.jobId).toBe('job-123');
|
|
23
|
-
expect(parsed.status).toBe('completed');
|
|
24
|
-
expect(parsed.current).toBe(10);
|
|
25
|
-
expect(parsed.total).toBe(10);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test('parses a minimal pending job output', () => {
|
|
29
|
-
const input = {
|
|
30
|
-
current: 0,
|
|
31
|
-
jobId: 'job-456',
|
|
32
|
-
status: 'pending',
|
|
33
|
-
total: 100,
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const parsed = jobOutputSchema.parse(input);
|
|
37
|
-
|
|
38
|
-
expect(parsed.jobId).toBe('job-456');
|
|
39
|
-
expect(parsed.status).toBe('pending');
|
|
40
|
-
expect(parsed.result).toBeUndefined();
|
|
41
|
-
expect(parsed.error).toBeUndefined();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test('parses a failed job with error', () => {
|
|
45
|
-
const input = {
|
|
46
|
-
current: 3,
|
|
47
|
-
error: 'connection reset',
|
|
48
|
-
jobId: 'job-789',
|
|
49
|
-
status: 'failed',
|
|
50
|
-
total: 10,
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const parsed = jobOutputSchema.parse(input);
|
|
54
|
-
|
|
55
|
-
expect(parsed.status).toBe('failed');
|
|
56
|
-
expect(parsed.error).toBe('connection reset');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test('rejects when jobId is missing', () => {
|
|
60
|
-
const input = {
|
|
61
|
-
current: 1,
|
|
62
|
-
status: 'running',
|
|
63
|
-
total: 5,
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
expect(() => jobOutputSchema.parse(input)).toThrow();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test('rejects an invalid status value', () => {
|
|
70
|
-
const input = {
|
|
71
|
-
current: 0,
|
|
72
|
-
jobId: 'job-bad',
|
|
73
|
-
status: 'unknown',
|
|
74
|
-
total: 0,
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
expect(() => jobOutputSchema.parse(input)).toThrow();
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test('composes statusFields and progressFields correctly', () => {
|
|
81
|
-
const { shape } = jobOutputSchema;
|
|
82
|
-
|
|
83
|
-
// Status field from statusFields()
|
|
84
|
-
expect(shape.status).toBeInstanceOf(z.ZodEnum);
|
|
85
|
-
|
|
86
|
-
// Progress fields from progressFields()
|
|
87
|
-
expect(shape.current).toBeInstanceOf(z.ZodNumber);
|
|
88
|
-
expect(shape.total).toBeInstanceOf(z.ZodNumber);
|
|
89
|
-
expect(shape.percentage).toBeDefined();
|
|
90
|
-
|
|
91
|
-
// Job-specific fields
|
|
92
|
-
expect(shape.jobId).toBeInstanceOf(z.ZodString);
|
|
93
|
-
expect(shape.error).toBeDefined();
|
|
94
|
-
expect(shape.result).toBeDefined();
|
|
95
|
-
expect(shape.startedAt).toBeDefined();
|
|
96
|
-
expect(shape.completedAt).toBeDefined();
|
|
97
|
-
});
|
|
98
|
-
});
|
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
/* oxlint-disable require-await -- adapter mocks and layer wrappers satisfy async interfaces without awaiting */
|
|
2
|
-
import { describe, test, expect } from 'bun:test';
|
|
3
|
-
|
|
4
|
-
import { z } from 'zod';
|
|
5
|
-
|
|
6
|
-
import type {
|
|
7
|
-
IndexAdapter,
|
|
8
|
-
StorageAdapter,
|
|
9
|
-
CacheAdapter,
|
|
10
|
-
SearchOptions,
|
|
11
|
-
SearchResult,
|
|
12
|
-
StorageOptions,
|
|
13
|
-
} from '../adapters';
|
|
14
|
-
import type { HealthStatus, HealthResult, HealthCheck } from '../health';
|
|
15
|
-
import { composeLayers } from '../layer';
|
|
16
|
-
import type { Layer } from '../layer';
|
|
17
|
-
import { Result } from '../result';
|
|
18
|
-
import { trail } from '../trail';
|
|
19
|
-
import type { TrailContext } from '../types';
|
|
20
|
-
|
|
21
|
-
const stubCtx: TrailContext = {
|
|
22
|
-
requestId: 'test-layer',
|
|
23
|
-
signal: AbortSignal.timeout(5000),
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const echoTrail = trail('echo', {
|
|
27
|
-
input: z.object({ value: z.string() }),
|
|
28
|
-
metadata: { domain: 'test' },
|
|
29
|
-
output: z.object({ value: z.string() }),
|
|
30
|
-
run: (input) => Result.ok({ value: input.value }),
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
// Layer tests
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
|
|
37
|
-
describe('Layer', () => {
|
|
38
|
-
test('single layer wraps implementation', async () => {
|
|
39
|
-
const prefixLayer: Layer = {
|
|
40
|
-
name: 'prefix',
|
|
41
|
-
wrap(_trail, impl) {
|
|
42
|
-
return async (input, ctx) => {
|
|
43
|
-
const result = await impl(input, ctx);
|
|
44
|
-
return result.map((out) => ({
|
|
45
|
-
...out,
|
|
46
|
-
value: `prefixed:${(out as { value: string }).value}`,
|
|
47
|
-
}));
|
|
48
|
-
};
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const wrapped = composeLayers([prefixLayer], echoTrail, echoTrail.run);
|
|
53
|
-
const result = await wrapped({ value: 'hello' }, stubCtx);
|
|
54
|
-
|
|
55
|
-
expect(result.isOk()).toBe(true);
|
|
56
|
-
expect(result.unwrap()).toEqual({ value: 'prefixed:hello' });
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test('multiple layers compose in outermost-first order', async () => {
|
|
60
|
-
const log: string[] = [];
|
|
61
|
-
|
|
62
|
-
const outer: Layer = {
|
|
63
|
-
name: 'outer',
|
|
64
|
-
wrap(_trail, impl) {
|
|
65
|
-
return async (input, ctx) => {
|
|
66
|
-
log.push('outer:before');
|
|
67
|
-
const r = await impl(input, ctx);
|
|
68
|
-
log.push('outer:after');
|
|
69
|
-
return r;
|
|
70
|
-
};
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const inner: Layer = {
|
|
75
|
-
name: 'inner',
|
|
76
|
-
wrap(_trail, impl) {
|
|
77
|
-
return async (input, ctx) => {
|
|
78
|
-
log.push('inner:before');
|
|
79
|
-
const r = await impl(input, ctx);
|
|
80
|
-
log.push('inner:after');
|
|
81
|
-
return r;
|
|
82
|
-
};
|
|
83
|
-
},
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const wrapped = composeLayers([outer, inner], echoTrail, echoTrail.run);
|
|
87
|
-
await wrapped({ value: 'x' }, stubCtx);
|
|
88
|
-
|
|
89
|
-
expect(log).toEqual([
|
|
90
|
-
'outer:before',
|
|
91
|
-
'inner:before',
|
|
92
|
-
'inner:after',
|
|
93
|
-
'outer:after',
|
|
94
|
-
]);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
test('layer can short-circuit without calling inner implementation', async () => {
|
|
98
|
-
const shortCircuit: Layer = {
|
|
99
|
-
name: 'short-circuit',
|
|
100
|
-
wrap() {
|
|
101
|
-
return async () => Result.err(new Error('blocked'));
|
|
102
|
-
},
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const wrapped = composeLayers([shortCircuit], echoTrail, echoTrail.run);
|
|
106
|
-
const result = await wrapped({ value: 'hello' }, stubCtx);
|
|
107
|
-
|
|
108
|
-
expect(result.isErr()).toBe(true);
|
|
109
|
-
const err = result as unknown as { error: Error };
|
|
110
|
-
expect(err.error.message).toBe('blocked');
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test('layer can inspect trail metadata', () => {
|
|
114
|
-
let capturedDomain: unknown;
|
|
115
|
-
|
|
116
|
-
const inspectLayer: Layer = {
|
|
117
|
-
name: 'inspect',
|
|
118
|
-
wrap(t, impl) {
|
|
119
|
-
capturedDomain = t.metadata?.['domain'];
|
|
120
|
-
return impl;
|
|
121
|
-
},
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
composeLayers([inspectLayer], echoTrail, echoTrail.run);
|
|
125
|
-
|
|
126
|
-
expect(capturedDomain).toBe('test');
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test('empty layers array returns implementation unchanged', () => {
|
|
130
|
-
const wrapped = composeLayers([], echoTrail, echoTrail.run);
|
|
131
|
-
expect(wrapped).toBe(echoTrail.run);
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// ---------------------------------------------------------------------------
|
|
136
|
-
// Health types (compile-time verification)
|
|
137
|
-
// ---------------------------------------------------------------------------
|
|
138
|
-
|
|
139
|
-
describe('health types', () => {
|
|
140
|
-
test('HealthResult satisfies the interface', () => {
|
|
141
|
-
const check: HealthCheck = {
|
|
142
|
-
latency: 12,
|
|
143
|
-
message: 'ok',
|
|
144
|
-
status: 'healthy',
|
|
145
|
-
};
|
|
146
|
-
const result: HealthResult = {
|
|
147
|
-
checks: { db: check },
|
|
148
|
-
status: 'healthy',
|
|
149
|
-
uptime: 3600,
|
|
150
|
-
version: '1.0.0',
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
expect(result.status).toBe('healthy');
|
|
154
|
-
expect(result.checks['db']?.status).toBe('healthy');
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test('HealthStatus union is exhaustive', () => {
|
|
158
|
-
const statuses: HealthStatus[] = ['healthy', 'degraded', 'unhealthy'];
|
|
159
|
-
expect(statuses).toHaveLength(3);
|
|
160
|
-
});
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
// ---------------------------------------------------------------------------
|
|
164
|
-
// Adapter types (compile-time verification)
|
|
165
|
-
// ---------------------------------------------------------------------------
|
|
166
|
-
|
|
167
|
-
describe('adapter types', () => {
|
|
168
|
-
test('IndexAdapter mock satisfies the interface', () => {
|
|
169
|
-
const mock: IndexAdapter = {
|
|
170
|
-
index: async () => Result.ok(),
|
|
171
|
-
remove: async () => Result.ok(),
|
|
172
|
-
search: async () => Result.ok([]),
|
|
173
|
-
};
|
|
174
|
-
expect(mock.index).toBeDefined();
|
|
175
|
-
expect(mock.search).toBeDefined();
|
|
176
|
-
expect(mock.remove).toBeDefined();
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
test('StorageAdapter mock satisfies the interface', () => {
|
|
180
|
-
const mock: StorageAdapter = {
|
|
181
|
-
delete: async () => Result.ok(),
|
|
182
|
-
get: async () => Result.ok('value'),
|
|
183
|
-
has: async () => Result.ok(true),
|
|
184
|
-
set: async () => Result.ok(),
|
|
185
|
-
};
|
|
186
|
-
expect(mock.has).toBeDefined();
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
test('CacheAdapter mock satisfies the interface', () => {
|
|
190
|
-
const mock: CacheAdapter = {
|
|
191
|
-
clear: async () => Result.ok(),
|
|
192
|
-
delete: async () => Result.ok(),
|
|
193
|
-
get: async () => Result.ok(),
|
|
194
|
-
set: async () => Result.ok(),
|
|
195
|
-
};
|
|
196
|
-
expect(mock.clear).toBeDefined();
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
test('SearchOptions and SearchResult satisfy their shapes', () => {
|
|
200
|
-
const opts: SearchOptions = { filters: { tag: 'a' }, limit: 10, offset: 0 };
|
|
201
|
-
const hit: SearchResult = {
|
|
202
|
-
document: { title: 'x' },
|
|
203
|
-
id: '1',
|
|
204
|
-
score: 0.95,
|
|
205
|
-
};
|
|
206
|
-
const sopts: StorageOptions = { ttl: 5000 };
|
|
207
|
-
|
|
208
|
-
expect(opts.limit).toBe(10);
|
|
209
|
-
expect(hit.score).toBe(0.95);
|
|
210
|
-
expect(sopts.ttl).toBe(5000);
|
|
211
|
-
});
|
|
212
|
-
});
|