@ontrails/core 1.0.0-beta.14 → 1.0.0-beta.15
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/.turbo/turbo-lint.log +1 -1
- package/CHANGELOG.md +11 -0
- package/README.md +17 -13
- package/dist/branded.d.ts +1 -1
- package/dist/branded.d.ts.map +1 -1
- package/dist/branded.js +1 -1
- package/dist/branded.js.map +1 -1
- package/dist/context.d.ts +13 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +19 -3
- package/dist/context.js.map +1 -1
- package/dist/contour.d.ts +81 -0
- package/dist/contour.d.ts.map +1 -0
- package/dist/contour.js +176 -0
- package/dist/contour.js.map +1 -0
- package/dist/cross-schema.d.ts +18 -0
- package/dist/cross-schema.d.ts.map +1 -0
- package/dist/cross-schema.js +29 -0
- package/dist/cross-schema.js.map +1 -0
- package/dist/draft.d.ts +5 -5
- package/dist/draft.d.ts.map +1 -1
- package/dist/draft.js +28 -27
- package/dist/draft.js.map +1 -1
- package/dist/errors.d.ts +62 -4
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +44 -1
- package/dist/errors.js.map +1 -1
- package/dist/execute.d.ts +26 -9
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +441 -20
- package/dist/execute.js.map +1 -1
- package/dist/fire.d.ts +43 -0
- package/dist/fire.d.ts.map +1 -0
- package/dist/fire.js +185 -0
- package/dist/fire.js.map +1 -0
- package/dist/index.d.ts +31 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -13
- package/dist/index.js.map +1 -1
- package/dist/internal/cross-batch.d.ts +44 -0
- package/dist/internal/cross-batch.d.ts.map +1 -0
- package/dist/internal/cross-batch.js +58 -0
- package/dist/internal/cross-batch.js.map +1 -0
- package/dist/internal/fork-ctx.d.ts +41 -0
- package/dist/internal/fork-ctx.d.ts.map +1 -0
- package/dist/internal/fork-ctx.js +47 -0
- package/dist/internal/fork-ctx.js.map +1 -0
- package/dist/internal/signal-ref.d.ts +15 -0
- package/dist/internal/signal-ref.d.ts.map +1 -0
- package/dist/internal/signal-ref.js +41 -0
- package/dist/internal/signal-ref.js.map +1 -0
- package/dist/internal/topo-saves.d.ts +8 -2
- package/dist/internal/topo-saves.d.ts.map +1 -1
- package/dist/internal/topo-saves.js +162 -23
- package/dist/internal/topo-saves.js.map +1 -1
- package/dist/internal/topo-snapshots.d.ts +57 -0
- package/dist/internal/topo-snapshots.d.ts.map +1 -0
- package/dist/internal/topo-snapshots.js +372 -0
- package/dist/internal/topo-snapshots.js.map +1 -0
- package/dist/internal/topo-store-read.d.ts +23 -20
- package/dist/internal/topo-store-read.d.ts.map +1 -1
- package/dist/internal/topo-store-read.js +82 -83
- package/dist/internal/topo-store-read.js.map +1 -1
- package/dist/internal/topo-store.d.ts +19 -5
- package/dist/internal/topo-store.d.ts.map +1 -1
- package/dist/internal/topo-store.js +174 -124
- package/dist/internal/topo-store.js.map +1 -1
- package/dist/internal/tracing.d.ts +98 -0
- package/dist/internal/tracing.d.ts.map +1 -0
- package/dist/internal/tracing.js +102 -0
- package/dist/internal/tracing.js.map +1 -0
- package/dist/internal/trails-db.d.ts +2 -2
- package/dist/internal/trails-db.d.ts.map +1 -1
- package/dist/internal/trails-db.js +8 -8
- package/dist/internal/trails-db.js.map +1 -1
- package/dist/internal/zod-wrappers.d.ts +40 -0
- package/dist/internal/zod-wrappers.d.ts.map +1 -0
- package/dist/internal/zod-wrappers.js +59 -0
- package/dist/internal/zod-wrappers.js.map +1 -0
- package/dist/layer.d.ts +22 -5
- package/dist/layer.d.ts.map +1 -1
- package/dist/layer.js +20 -3
- package/dist/layer.js.map +1 -1
- package/dist/path-security.d.ts +1 -1
- package/dist/path-security.d.ts.map +1 -1
- package/dist/path-security.js +1 -1
- package/dist/path-security.js.map +1 -1
- package/dist/resilience.d.ts +1 -1
- package/dist/resilience.d.ts.map +1 -1
- package/dist/resilience.js +3 -3
- package/dist/resilience.js.map +1 -1
- package/dist/resource-config.d.ts +22 -0
- package/dist/resource-config.d.ts.map +1 -0
- package/dist/resource-config.js +210 -0
- package/dist/resource-config.js.map +1 -0
- package/dist/resource.d.ts +74 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/resource.js +61 -0
- package/dist/resource.js.map +1 -0
- package/dist/run.d.ts +4 -3
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +5 -4
- package/dist/run.js.map +1 -1
- package/dist/serialization.d.ts.map +1 -1
- package/dist/serialization.js +2 -1
- package/dist/serialization.js.map +1 -1
- package/dist/store/accessor-protocol.d.ts +51 -0
- package/dist/store/accessor-protocol.d.ts.map +1 -0
- package/dist/store/accessor-protocol.js +2 -0
- package/dist/store/accessor-protocol.js.map +1 -0
- package/dist/store/index.d.ts +2 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +2 -0
- package/dist/store/index.js.map +1 -0
- package/dist/surface-filter.d.ts +13 -0
- package/dist/surface-filter.d.ts.map +1 -0
- package/dist/surface-filter.js +76 -0
- package/dist/surface-filter.js.map +1 -0
- package/dist/topo-store.d.ts +36 -22
- package/dist/topo-store.d.ts.map +1 -1
- package/dist/topo-store.js +215 -71
- package/dist/topo-store.js.map +1 -1
- package/dist/topo.d.ts +23 -9
- package/dist/topo.d.ts.map +1 -1
- package/dist/topo.js +175 -46
- package/dist/topo.js.map +1 -1
- package/dist/trail.d.ts +86 -19
- package/dist/trail.d.ts.map +1 -1
- package/dist/trail.js +59 -4
- package/dist/trail.js.map +1 -1
- package/dist/trails/derive-trail.d.ts +66 -0
- package/dist/trails/derive-trail.d.ts.map +1 -0
- package/dist/trails/derive-trail.js +378 -0
- package/dist/trails/derive-trail.js.map +1 -0
- package/dist/trails/index.d.ts +5 -0
- package/dist/trails/index.d.ts.map +1 -0
- package/dist/trails/index.js +3 -0
- package/dist/trails/index.js.map +1 -0
- package/dist/trails/ingest.d.ts +24 -0
- package/dist/trails/ingest.d.ts.map +1 -0
- package/dist/trails/ingest.js +63 -0
- package/dist/trails/ingest.js.map +1 -0
- package/dist/transport-error-map.d.ts +106 -0
- package/dist/transport-error-map.d.ts.map +1 -0
- package/dist/transport-error-map.js +24 -0
- package/dist/transport-error-map.js.map +1 -0
- package/dist/type-checks.test-d.d.ts +71 -0
- package/dist/type-checks.test-d.d.ts.map +1 -0
- package/dist/type-checks.test-d.js +12 -0
- package/dist/type-checks.test-d.js.map +1 -0
- package/dist/type-utils.d.ts +11 -2
- package/dist/type-utils.d.ts.map +1 -1
- package/dist/type-utils.js.map +1 -1
- package/dist/types.d.ts +113 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/validate-established-topo.js +2 -2
- package/dist/validate-established-topo.js.map +1 -1
- package/dist/validate-topo.d.ts.map +1 -1
- package/dist/validate-topo.js +24 -7
- package/dist/validate-topo.js.map +1 -1
- package/dist/workspace.d.ts +1 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +1 -1
- package/dist/workspace.js.map +1 -1
- package/package.json +8 -2
- package/src/__tests__/branded.test.ts +5 -5
- package/src/__tests__/context.test.ts +3 -3
- package/src/__tests__/contour.test.ts +263 -0
- package/src/__tests__/derive-trail.test.ts +668 -0
- package/src/__tests__/errors.test.ts +118 -0
- package/src/__tests__/execute.test.ts +1179 -141
- package/src/__tests__/fire.test.ts +1056 -0
- package/src/__tests__/fork-ctx.test.ts +83 -0
- package/src/__tests__/ingest.test.ts +191 -0
- package/src/__tests__/{gate.test.ts → layer.test.ts} +21 -21
- package/src/__tests__/path-security.test.ts +7 -7
- package/src/__tests__/resilience.test.ts +8 -8
- package/src/__tests__/resource.test.ts +204 -0
- package/src/__tests__/run.test.ts +13 -13
- package/src/__tests__/service-config.test.ts +28 -32
- package/src/__tests__/surface-filter.test.ts +196 -0
- package/src/__tests__/topo-store-read.test.ts +41 -36
- package/src/__tests__/topo-store.test.ts +493 -93
- package/src/__tests__/topo.test.ts +350 -54
- package/src/__tests__/trail.test.ts +189 -24
- package/src/__tests__/trails-db.test.ts +32 -25
- package/src/__tests__/transport-error-map.test.ts +82 -0
- package/src/__tests__/type-utils.test.ts +29 -1
- package/src/__tests__/validate-topo.test.ts +147 -25
- package/src/__tests__/workspace.test.ts +6 -6
- package/src/__tests__/zod-wrappers.test.ts +62 -0
- package/src/branded.ts +1 -1
- package/src/context.ts +24 -4
- package/src/contour.ts +344 -0
- package/src/cross-schema.ts +36 -0
- package/src/draft.ts +56 -52
- package/src/errors.ts +62 -18
- package/src/execute.ts +840 -29
- package/src/fire.ts +295 -0
- package/src/index.ts +116 -29
- package/src/internal/cross-batch.ts +77 -0
- package/src/internal/fork-ctx.ts +69 -0
- package/src/internal/signal-ref.ts +87 -0
- package/src/internal/topo-snapshots.ts +517 -0
- package/src/internal/topo-store-read.ts +140 -132
- package/src/internal/topo-store.ts +291 -186
- package/src/internal/tracing.ts +187 -0
- package/src/internal/trails-db.ts +9 -11
- package/src/internal/zod-wrappers.ts +78 -0
- package/src/layer.ts +50 -0
- package/src/path-security.ts +1 -1
- package/src/resilience.ts +3 -3
- package/src/{provision-config.ts → resource-config.ts} +109 -123
- package/src/resource.ts +155 -0
- package/src/run.ts +5 -4
- package/src/serialization.ts +2 -0
- package/src/store/accessor-protocol.ts +56 -0
- package/src/store/index.ts +4 -0
- package/src/surface-filter.ts +182 -0
- package/src/topo-store.ts +313 -104
- package/src/topo.ts +328 -57
- package/src/trail.ts +199 -31
- package/src/trails/derive-trail.ts +842 -0
- package/src/trails/index.ts +9 -0
- package/src/trails/ingest.ts +146 -0
- package/src/transport-error-map.ts +56 -0
- package/src/type-checks.test-d.ts +106 -0
- package/src/type-utils.ts +17 -2
- package/src/types.ts +148 -11
- package/src/validate-established-topo.ts +2 -2
- package/src/validate-topo.ts +32 -8
- package/src/workspace.ts +1 -1
- package/tsconfig.tests.json +10 -0
- package/tsconfig.tsbuildinfo +1 -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/dispatch.d.ts +0 -27
- package/dist/dispatch.d.ts.map +0 -1
- package/dist/dispatch.js +0 -34
- package/dist/dispatch.js.map +0 -1
- package/dist/event.d.ts +0 -8
- package/dist/event.d.ts.map +0 -1
- package/dist/event.js +0 -7
- package/dist/event.js.map +0 -1
- package/dist/gate.d.ts +0 -17
- package/dist/gate.d.ts.map +0 -1
- package/dist/gate.js +0 -21
- package/dist/gate.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/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/provision-config.d.ts +0 -22
- package/dist/provision-config.d.ts.map +0 -1
- package/dist/provision-config.js +0 -210
- package/dist/provision-config.js.map +0 -1
- package/dist/provision.d.ts +0 -71
- package/dist/provision.d.ts.map +0 -1
- package/dist/provision.js +0 -56
- package/dist/provision.js.map +0 -1
- package/dist/service-config.d.ts +0 -22
- package/dist/service-config.d.ts.map +0 -1
- package/dist/service-config.js +0 -210
- package/dist/service-config.js.map +0 -1
- package/dist/service.d.ts +0 -71
- package/dist/service.d.ts.map +0 -1
- package/dist/service.js +0 -56
- package/dist/service.js.map +0 -1
- package/src/__tests__/service.test.ts +0 -197
- package/src/event.ts +0 -15
- package/src/gate.ts +0 -44
- package/src/internal/topo-saves.ts +0 -429
- package/src/provision.ts +0 -148
|
@@ -2,9 +2,12 @@ import { describe, test, expect } from 'bun:test';
|
|
|
2
2
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
|
|
5
|
+
import { contour } from '../contour';
|
|
5
6
|
import { createTrailContext } from '../context';
|
|
7
|
+
import { ConflictError } from '../errors';
|
|
6
8
|
import { Result } from '../result';
|
|
7
|
-
import {
|
|
9
|
+
import { resource } from '../resource';
|
|
10
|
+
import { signal } from '../signal';
|
|
8
11
|
import { trail } from '../trail';
|
|
9
12
|
import type { TrailContext } from '../types';
|
|
10
13
|
|
|
@@ -13,16 +16,25 @@ const stubCtx: TrailContext = createTrailContext({
|
|
|
13
16
|
requestId: 'test-123',
|
|
14
17
|
});
|
|
15
18
|
|
|
16
|
-
const
|
|
19
|
+
const dbResource = resource('db.main', {
|
|
17
20
|
create: () =>
|
|
18
21
|
Result.ok({
|
|
19
22
|
query(sql: string) {
|
|
20
23
|
return sql.length;
|
|
21
24
|
},
|
|
22
25
|
}),
|
|
23
|
-
description: 'Primary database
|
|
26
|
+
description: 'Primary database resource',
|
|
24
27
|
});
|
|
25
28
|
|
|
29
|
+
const userContour = contour(
|
|
30
|
+
'user',
|
|
31
|
+
{
|
|
32
|
+
id: z.string().uuid(),
|
|
33
|
+
name: z.string(),
|
|
34
|
+
},
|
|
35
|
+
{ identity: 'id' }
|
|
36
|
+
);
|
|
37
|
+
|
|
26
38
|
describe('trail()', () => {
|
|
27
39
|
const inputSchema = z.object({ name: z.string() });
|
|
28
40
|
const outputSchema = z.object({ greeting: z.string() });
|
|
@@ -92,19 +104,34 @@ describe('trail()', () => {
|
|
|
92
104
|
expect(withMeta.meta).toEqual({ domain: 'billing', tier: 1 });
|
|
93
105
|
});
|
|
94
106
|
|
|
107
|
+
test('pattern is stored when declared', () => {
|
|
108
|
+
const withPattern = trail('feature.enable', {
|
|
109
|
+
blaze: () => Result.ok({ enabled: true }),
|
|
110
|
+
input: z.object({ id: z.string() }),
|
|
111
|
+
pattern: 'toggle',
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(withPattern.pattern).toBe('toggle');
|
|
115
|
+
});
|
|
116
|
+
|
|
95
117
|
test('detours are stored', () => {
|
|
96
118
|
const withDetours = trail('orchestrator', {
|
|
97
119
|
blaze: () => Result.ok(),
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
onSuccess: ['notify', 'audit'],
|
|
101
|
-
},
|
|
120
|
+
/* oxlint-disable-next-line require-await -- test stub */
|
|
121
|
+
detours: [{ on: ConflictError, recover: async () => Result.ok() }],
|
|
102
122
|
input: z.object({}),
|
|
103
123
|
});
|
|
104
|
-
expect(withDetours.detours).
|
|
105
|
-
|
|
106
|
-
|
|
124
|
+
expect(withDetours.detours).toHaveLength(1);
|
|
125
|
+
expect(withDetours.detours[0]?.on).toBe(ConflictError);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('detours default to empty frozen array when omitted', () => {
|
|
129
|
+
const noDetours = trail('bare', {
|
|
130
|
+
blaze: () => Result.ok(),
|
|
131
|
+
input: z.object({}),
|
|
107
132
|
});
|
|
133
|
+
expect(noDetours.detours).toEqual([]);
|
|
134
|
+
expect(Object.isFrozen(noDetours.detours)).toBe(true);
|
|
108
135
|
});
|
|
109
136
|
});
|
|
110
137
|
|
|
@@ -135,35 +162,109 @@ describe('trail()', () => {
|
|
|
135
162
|
});
|
|
136
163
|
expect(Object.isFrozen(withCrosses.crosses)).toBe(true);
|
|
137
164
|
});
|
|
165
|
+
|
|
166
|
+
test('trail object in crosses is normalized to its id', () => {
|
|
167
|
+
const target = trail('target.trail', {
|
|
168
|
+
blaze: () => Result.ok(),
|
|
169
|
+
input: z.object({}),
|
|
170
|
+
});
|
|
171
|
+
const composed = trail('composed', {
|
|
172
|
+
blaze: () => Result.ok(),
|
|
173
|
+
crosses: [target],
|
|
174
|
+
input: z.object({}),
|
|
175
|
+
});
|
|
176
|
+
expect(composed.crosses).toEqual(['target.trail']);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('mixed string and trail object in crosses normalizes correctly', () => {
|
|
180
|
+
const target = trail('target.trail', {
|
|
181
|
+
blaze: () => Result.ok(),
|
|
182
|
+
input: z.object({}),
|
|
183
|
+
});
|
|
184
|
+
const composed = trail('composed', {
|
|
185
|
+
blaze: () => Result.ok(),
|
|
186
|
+
crosses: ['string-id', target],
|
|
187
|
+
input: z.object({}),
|
|
188
|
+
});
|
|
189
|
+
expect(composed.crosses).toEqual(['string-id', 'target.trail']);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('crossInput is stored on the trail', () => {
|
|
193
|
+
const crossInputSchema = z.object({ forkedFrom: z.string().optional() });
|
|
194
|
+
const t = trail('gist.create', {
|
|
195
|
+
blaze: () => Result.ok(),
|
|
196
|
+
crossInput: crossInputSchema,
|
|
197
|
+
input: z.object({ content: z.string() }),
|
|
198
|
+
});
|
|
199
|
+
expect(t.crossInput).toBe(crossInputSchema);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('crossInput is undefined when omitted', () => {
|
|
203
|
+
const t = trail('bare', {
|
|
204
|
+
blaze: () => Result.ok(),
|
|
205
|
+
input: z.object({}),
|
|
206
|
+
});
|
|
207
|
+
expect(t.crossInput).toBeUndefined();
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('contours', () => {
|
|
212
|
+
test('defaults to empty frozen array when omitted', () => {
|
|
213
|
+
const minimal = trail('bare', {
|
|
214
|
+
blaze: () => Result.ok(),
|
|
215
|
+
input: z.object({}),
|
|
216
|
+
});
|
|
217
|
+
expect(minimal.contours).toEqual([]);
|
|
218
|
+
expect(Object.isFrozen(minimal.contours)).toBe(true);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test('preserves declared contour objects', () => {
|
|
222
|
+
const withContours = trail('user.create', {
|
|
223
|
+
blaze: () => Result.ok(),
|
|
224
|
+
contours: [userContour],
|
|
225
|
+
input: z.object({}),
|
|
226
|
+
});
|
|
227
|
+
expect(withContours.contours).toEqual([userContour]);
|
|
228
|
+
expect(withContours.contours[0]).toBe(userContour);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('contours array is frozen', () => {
|
|
232
|
+
const withContours = trail('user.create', {
|
|
233
|
+
blaze: () => Result.ok(),
|
|
234
|
+
contours: [userContour],
|
|
235
|
+
input: z.object({}),
|
|
236
|
+
});
|
|
237
|
+
expect(Object.isFrozen(withContours.contours)).toBe(true);
|
|
238
|
+
});
|
|
138
239
|
});
|
|
139
240
|
|
|
140
|
-
describe('
|
|
241
|
+
describe('resources', () => {
|
|
141
242
|
test('defaults to empty frozen array when omitted', () => {
|
|
142
243
|
const minimal = trail('bare', {
|
|
143
244
|
blaze: () => Result.ok(),
|
|
144
245
|
input: z.object({}),
|
|
145
246
|
});
|
|
146
|
-
expect(minimal.
|
|
147
|
-
expect(Object.isFrozen(minimal.
|
|
247
|
+
expect(minimal.resources).toEqual([]);
|
|
248
|
+
expect(Object.isFrozen(minimal.resources)).toBe(true);
|
|
148
249
|
});
|
|
149
250
|
|
|
150
|
-
test('preserves declared
|
|
151
|
-
const
|
|
251
|
+
test('preserves declared resource objects', () => {
|
|
252
|
+
const withResources = trail('search', {
|
|
152
253
|
blaze: () => Result.ok(),
|
|
153
254
|
input: z.object({}),
|
|
154
|
-
|
|
255
|
+
resources: [dbResource],
|
|
155
256
|
});
|
|
156
|
-
expect(
|
|
157
|
-
expect(
|
|
257
|
+
expect(withResources.resources).toEqual([dbResource]);
|
|
258
|
+
expect(withResources.resources[0]).toBe(dbResource);
|
|
158
259
|
});
|
|
159
260
|
|
|
160
|
-
test('
|
|
161
|
-
const
|
|
261
|
+
test('resources array is frozen', () => {
|
|
262
|
+
const withResources = trail('search', {
|
|
162
263
|
blaze: () => Result.ok(),
|
|
163
264
|
input: z.object({}),
|
|
164
|
-
|
|
265
|
+
resources: [dbResource],
|
|
165
266
|
});
|
|
166
|
-
expect(Object.isFrozen(
|
|
267
|
+
expect(Object.isFrozen(withResources.resources)).toBe(true);
|
|
167
268
|
});
|
|
168
269
|
});
|
|
169
270
|
|
|
@@ -201,6 +302,23 @@ describe('trail()', () => {
|
|
|
201
302
|
});
|
|
202
303
|
expect(t.idempotent).toBe(true);
|
|
203
304
|
});
|
|
305
|
+
|
|
306
|
+
test('visibility defaults to public', () => {
|
|
307
|
+
const minimal = trail('visible', {
|
|
308
|
+
blaze: () => Result.ok(),
|
|
309
|
+
input: z.object({}),
|
|
310
|
+
});
|
|
311
|
+
expect(minimal.visibility).toBe('public');
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test('visibility is preserved when set', () => {
|
|
315
|
+
const t = trail('internal.helper', {
|
|
316
|
+
blaze: () => Result.ok(),
|
|
317
|
+
input: z.object({}),
|
|
318
|
+
visibility: 'internal',
|
|
319
|
+
});
|
|
320
|
+
expect(t.visibility).toBe('internal');
|
|
321
|
+
});
|
|
204
322
|
});
|
|
205
323
|
|
|
206
324
|
describe('single-object overload', () => {
|
|
@@ -225,12 +343,12 @@ describe('trail()', () => {
|
|
|
225
343
|
input: inputSchema,
|
|
226
344
|
intent: 'read',
|
|
227
345
|
output: outputSchema,
|
|
228
|
-
|
|
346
|
+
resources: [dbResource],
|
|
229
347
|
});
|
|
230
348
|
expect(t.description).toBe('A full trail');
|
|
231
349
|
expect(t.intent).toBe('read');
|
|
232
350
|
expect(t.examples).toHaveLength(1);
|
|
233
|
-
expect(t.
|
|
351
|
+
expect(t.resources).toEqual([dbResource]);
|
|
234
352
|
});
|
|
235
353
|
|
|
236
354
|
test('implementation is callable', async () => {
|
|
@@ -259,3 +377,50 @@ describe('trail()', () => {
|
|
|
259
377
|
});
|
|
260
378
|
});
|
|
261
379
|
});
|
|
380
|
+
|
|
381
|
+
describe('trail() fires/on normalization', () => {
|
|
382
|
+
const orderPlaced = signal('order.placed', {
|
|
383
|
+
payload: z.object({ id: z.string() }),
|
|
384
|
+
});
|
|
385
|
+
const auditLogged = signal('audit.logged', {
|
|
386
|
+
payload: z.object({ actor: z.string() }),
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
test('Signal value in fires: is normalized to its id', () => {
|
|
390
|
+
const t = trail('checkout', {
|
|
391
|
+
blaze: () => Result.ok({}),
|
|
392
|
+
fires: [orderPlaced],
|
|
393
|
+
input: z.object({}),
|
|
394
|
+
});
|
|
395
|
+
expect(t.fires).toEqual(['order.placed']);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test('Signal value in on: is normalized to its id', () => {
|
|
399
|
+
const t = trail('notify', {
|
|
400
|
+
blaze: () => Result.ok({}),
|
|
401
|
+
input: z.object({}),
|
|
402
|
+
on: [orderPlaced],
|
|
403
|
+
});
|
|
404
|
+
expect(t.on).toEqual(['order.placed']);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
test('mixed string + Signal value in fires: is normalized', () => {
|
|
408
|
+
const t = trail('checkout', {
|
|
409
|
+
blaze: () => Result.ok({}),
|
|
410
|
+
fires: ['metric.emitted', orderPlaced, auditLogged],
|
|
411
|
+
input: z.object({}),
|
|
412
|
+
});
|
|
413
|
+
expect(t.fires).toEqual(['metric.emitted', 'order.placed', 'audit.logged']);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
test('defaults to empty frozen arrays when omitted', () => {
|
|
417
|
+
const minimal = trail('bare', {
|
|
418
|
+
blaze: () => Result.ok(),
|
|
419
|
+
input: z.object({}),
|
|
420
|
+
});
|
|
421
|
+
expect(minimal.fires).toEqual([]);
|
|
422
|
+
expect(Object.isFrozen(minimal.fires)).toBe(true);
|
|
423
|
+
expect(minimal.on).toEqual([]);
|
|
424
|
+
expect(Object.isFrozen(minimal.on)).toBe(true);
|
|
425
|
+
});
|
|
426
|
+
});
|
|
@@ -7,16 +7,15 @@ import {
|
|
|
7
7
|
ensureSubsystemSchema,
|
|
8
8
|
openReadTrailsDb,
|
|
9
9
|
openWriteTrailsDb,
|
|
10
|
-
|
|
10
|
+
deriveTrailsDbPath,
|
|
11
11
|
} from '../internal/trails-db.js';
|
|
12
12
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
} from '../internal/topo-saves.js';
|
|
13
|
+
createTopoSnapshot,
|
|
14
|
+
ensureTopoSnapshotSchema,
|
|
15
|
+
listTopoSnapshots,
|
|
16
|
+
pinTopoSnapshot,
|
|
17
|
+
pruneUnpinnedSnapshots,
|
|
18
|
+
} from '../internal/topo-snapshots.js';
|
|
20
19
|
|
|
21
20
|
describe('trails db foundation', () => {
|
|
22
21
|
let tmpRoot: string | undefined;
|
|
@@ -40,9 +39,9 @@ describe('trails db foundation', () => {
|
|
|
40
39
|
expect(existsSync(join(rootDir, '.trails', 'generated'))).toBe(true);
|
|
41
40
|
};
|
|
42
41
|
|
|
43
|
-
test('
|
|
42
|
+
test('deriveTrailsDbPath places the database in .trails/trails.db', () => {
|
|
44
43
|
const rootDir = '/tmp/example-app';
|
|
45
|
-
expect(
|
|
44
|
+
expect(deriveTrailsDbPath({ rootDir })).toBe(
|
|
46
45
|
'/tmp/example-app/.trails/trails.db'
|
|
47
46
|
);
|
|
48
47
|
});
|
|
@@ -52,7 +51,7 @@ describe('trails db foundation', () => {
|
|
|
52
51
|
const db = openWriteTrailsDb({ rootDir });
|
|
53
52
|
|
|
54
53
|
try {
|
|
55
|
-
expect(existsSync(
|
|
54
|
+
expect(existsSync(deriveTrailsDbPath({ rootDir }))).toBe(true);
|
|
56
55
|
expectWorkspaceLayout(rootDir);
|
|
57
56
|
|
|
58
57
|
const journal = db
|
|
@@ -127,7 +126,7 @@ describe('trails db foundation', () => {
|
|
|
127
126
|
});
|
|
128
127
|
});
|
|
129
128
|
|
|
130
|
-
describe('topo
|
|
129
|
+
describe('topo snapshot primitives', () => {
|
|
131
130
|
let tmpRoot: string | undefined;
|
|
132
131
|
|
|
133
132
|
afterEach(() => {
|
|
@@ -138,52 +137,60 @@ describe('topo save primitives', () => {
|
|
|
138
137
|
});
|
|
139
138
|
|
|
140
139
|
const makeRoot = (): string => {
|
|
141
|
-
tmpRoot = mkdtempSync(join(tmpdir(), 'topo-
|
|
140
|
+
tmpRoot = mkdtempSync(join(tmpdir(), 'topo-snapshots-'));
|
|
142
141
|
return tmpRoot;
|
|
143
142
|
};
|
|
144
143
|
|
|
145
144
|
const seedHistory = (db: ReturnType<typeof openWriteTrailsDb>) => {
|
|
146
|
-
|
|
145
|
+
ensureTopoSnapshotSchema(db);
|
|
147
146
|
|
|
148
|
-
const pinned =
|
|
147
|
+
const pinned = createTopoSnapshot(db, {
|
|
149
148
|
createdAt: '2026-04-01T00:00:00.000Z',
|
|
150
149
|
gitDirty: false,
|
|
151
150
|
gitSha: 'abc123',
|
|
152
|
-
|
|
151
|
+
resourceCount: 2,
|
|
153
152
|
signalCount: 1,
|
|
154
153
|
trailCount: 3,
|
|
155
154
|
});
|
|
156
|
-
const disposable =
|
|
155
|
+
const disposable = createTopoSnapshot(db, {
|
|
157
156
|
createdAt: '2026-04-02T00:00:00.000Z',
|
|
158
157
|
gitDirty: true,
|
|
159
158
|
gitSha: 'def456',
|
|
160
|
-
|
|
159
|
+
resourceCount: 3,
|
|
161
160
|
signalCount: 2,
|
|
162
161
|
trailCount: 4,
|
|
163
162
|
});
|
|
164
163
|
|
|
165
164
|
return {
|
|
166
165
|
disposable,
|
|
167
|
-
pin: pinTopoSave(db, { name: 'before-auth', saveId: pinned.id }),
|
|
168
166
|
pinned,
|
|
167
|
+
pinnedSnapshot: pinTopoSnapshot(db, {
|
|
168
|
+
id: pinned.id,
|
|
169
|
+
name: 'before-auth',
|
|
170
|
+
}),
|
|
169
171
|
};
|
|
170
172
|
};
|
|
171
173
|
|
|
172
|
-
test('creates
|
|
174
|
+
test('creates snapshots, pins them, and prunes only unpinned history', () => {
|
|
173
175
|
const rootDir = makeRoot();
|
|
174
176
|
const db = openWriteTrailsDb({ rootDir });
|
|
175
177
|
|
|
176
178
|
try {
|
|
177
|
-
const { disposable,
|
|
179
|
+
const { disposable, pinned, pinnedSnapshot } = seedHistory(db);
|
|
178
180
|
|
|
179
|
-
expect(
|
|
180
|
-
expect(
|
|
181
|
+
expect(pinnedSnapshot?.pinnedAs).toBe('before-auth');
|
|
182
|
+
expect(
|
|
183
|
+
listTopoSnapshots(db, { pinned: true }).map((snapshot) => snapshot.id)
|
|
184
|
+
).toEqual([pinned.id]);
|
|
185
|
+
expect(listTopoSnapshots(db).map((snapshot) => snapshot.id)).toEqual([
|
|
181
186
|
disposable.id,
|
|
182
187
|
pinned.id,
|
|
183
188
|
]);
|
|
184
189
|
|
|
185
|
-
expect(
|
|
186
|
-
expect(
|
|
190
|
+
expect(pruneUnpinnedSnapshots(db, { keep: 0 })).toBe(1);
|
|
191
|
+
expect(listTopoSnapshots(db).map((snapshot) => snapshot.id)).toEqual([
|
|
192
|
+
pinned.id,
|
|
193
|
+
]);
|
|
187
194
|
} finally {
|
|
188
195
|
db.close();
|
|
189
196
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
CancelledError,
|
|
5
|
+
NetworkError,
|
|
6
|
+
NotFoundError,
|
|
7
|
+
ValidationError,
|
|
8
|
+
errorCategories,
|
|
9
|
+
exitCodeMap,
|
|
10
|
+
jsonRpcCodeMap,
|
|
11
|
+
statusCodeMap,
|
|
12
|
+
} from '../errors.js';
|
|
13
|
+
import {
|
|
14
|
+
createTransportErrorMapper,
|
|
15
|
+
mapTransportError,
|
|
16
|
+
transportErrorMap,
|
|
17
|
+
transportErrorRegistry,
|
|
18
|
+
transportNames,
|
|
19
|
+
} from '../transport-error-map.js';
|
|
20
|
+
|
|
21
|
+
describe('transportErrorMap', () => {
|
|
22
|
+
test('covers every error category for every transport', () => {
|
|
23
|
+
for (const transport of transportNames) {
|
|
24
|
+
const mappings = transportErrorMap[transport];
|
|
25
|
+
for (const category of errorCategories) {
|
|
26
|
+
expect(category in mappings).toBe(true);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('reuses the existing public transport maps', () => {
|
|
32
|
+
expect(transportErrorMap.cli).toBe(exitCodeMap);
|
|
33
|
+
expect(transportErrorMap.http).toBe(statusCodeMap);
|
|
34
|
+
expect(transportErrorMap.mcp).toBe(jsonRpcCodeMap);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('transportErrorRegistry', () => {
|
|
39
|
+
test('exposes a callable mapper for each transport', () => {
|
|
40
|
+
const notFound = new NotFoundError('missing');
|
|
41
|
+
|
|
42
|
+
expect(transportErrorRegistry.cli.map(notFound)).toBe(2);
|
|
43
|
+
expect(transportErrorRegistry.http.map(notFound)).toBe(404);
|
|
44
|
+
expect(transportErrorRegistry.mcp.map(notFound)).toBe(-32_601);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('mapTransportError', () => {
|
|
49
|
+
test('maps known error instances through each registered transport', () => {
|
|
50
|
+
const validation = new ValidationError('bad input');
|
|
51
|
+
const notFound = new NotFoundError('missing');
|
|
52
|
+
const network = new NetworkError('offline');
|
|
53
|
+
const cancelled = new CancelledError('cancelled');
|
|
54
|
+
|
|
55
|
+
expect(mapTransportError('cli', validation)).toBe(1);
|
|
56
|
+
expect(mapTransportError('http', notFound)).toBe(404);
|
|
57
|
+
expect(mapTransportError('mcp', notFound)).toBe(-32_601);
|
|
58
|
+
expect(mapTransportError('http', network)).toBe(502);
|
|
59
|
+
expect(mapTransportError('cli', cancelled)).toBe(130);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('createTransportErrorMapper', () => {
|
|
64
|
+
test('uses the error category to project into transport-specific values', () => {
|
|
65
|
+
const mapper = createTransportErrorMapper({
|
|
66
|
+
auth: 'auth',
|
|
67
|
+
cancelled: 'cancelled',
|
|
68
|
+
conflict: 'conflict',
|
|
69
|
+
internal: 'internal',
|
|
70
|
+
network: 'network',
|
|
71
|
+
not_found: 'not_found',
|
|
72
|
+
permission: 'permission',
|
|
73
|
+
rate_limit: 'rate_limit',
|
|
74
|
+
timeout: 'timeout',
|
|
75
|
+
validation: 'validation',
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(mapper(new ValidationError('bad input'))).toBe('validation');
|
|
79
|
+
expect(mapper(new NetworkError('offline'))).toBe('network');
|
|
80
|
+
expect(mapper(new CancelledError('cancelled'))).toBe('cancelled');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -4,7 +4,12 @@ import { z } from 'zod';
|
|
|
4
4
|
|
|
5
5
|
import { Result } from '../result';
|
|
6
6
|
import { trail } from '../trail';
|
|
7
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
CrossInput,
|
|
9
|
+
TrailInput,
|
|
10
|
+
TrailOutput,
|
|
11
|
+
TrailResult,
|
|
12
|
+
} from '../type-utils';
|
|
8
13
|
import { inputOf, outputOf } from '../type-utils';
|
|
9
14
|
|
|
10
15
|
const greetTrail = trail('greet', {
|
|
@@ -68,6 +73,29 @@ describe('type-utils', () => {
|
|
|
68
73
|
});
|
|
69
74
|
});
|
|
70
75
|
|
|
76
|
+
describe('CrossInput', () => {
|
|
77
|
+
test('returns base input when no crossInput defined', () => {
|
|
78
|
+
type Input = CrossInput<typeof greetTrail>;
|
|
79
|
+
const _input: Input = { name: 'test' };
|
|
80
|
+
expect(_input.name).toBe('test');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('merges crossInput with base input when crossInput defined', () => {
|
|
84
|
+
const crossTrail = trail('cross.test', {
|
|
85
|
+
blaze: (input) => Result.ok({ name: input.name }),
|
|
86
|
+
crossInput: z.object({ forkedFrom: z.string() }),
|
|
87
|
+
input: z.object({ name: z.string() }),
|
|
88
|
+
output: z.object({ name: z.string() }),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
type Input = CrossInput<typeof crossTrail>;
|
|
92
|
+
// If this compiles, the type correctly merges both schemas
|
|
93
|
+
const _input: Input = { forkedFrom: 'origin', name: 'test' };
|
|
94
|
+
expect(_input.name).toBe('test');
|
|
95
|
+
expect(_input.forkedFrom).toBe('origin');
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
71
99
|
describe('TrailResult', () => {
|
|
72
100
|
test('extracts Result<Output, Error> from a trail', () => {
|
|
73
101
|
const t = trail('test.result', {
|