@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,10 @@ import { describe, expect, test } from 'bun:test';
|
|
|
2
2
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { deriveDraftReport, isDraftId } from '../draft.js';
|
|
6
|
+
import { contour } from '../contour.js';
|
|
6
7
|
import { validateEstablishedTopo } from '../validate-established-topo.js';
|
|
7
|
-
import {
|
|
8
|
+
import { resource } from '../resource.js';
|
|
8
9
|
import { Result } from '../result.js';
|
|
9
10
|
import { topo } from '../topo.js';
|
|
10
11
|
import type { TopoIssue } from '../validate-topo.js';
|
|
@@ -21,6 +22,7 @@ const mockTrail = (
|
|
|
21
22
|
id: string,
|
|
22
23
|
overrides?: {
|
|
23
24
|
crosses?: readonly string[];
|
|
25
|
+
contours?: readonly ReturnType<typeof contour>[];
|
|
24
26
|
examples?: readonly {
|
|
25
27
|
name: string;
|
|
26
28
|
input: unknown;
|
|
@@ -28,20 +30,21 @@ const mockTrail = (
|
|
|
28
30
|
error?: string;
|
|
29
31
|
}[];
|
|
30
32
|
output?: z.ZodType;
|
|
31
|
-
|
|
33
|
+
resources?: readonly ReturnType<typeof resource>[];
|
|
32
34
|
}
|
|
33
35
|
) => ({
|
|
34
36
|
blaze: noop,
|
|
37
|
+
contours: Object.freeze([...(overrides?.contours ?? [])]),
|
|
35
38
|
crosses: Object.freeze([...(overrides?.crosses ?? [])]),
|
|
36
39
|
id,
|
|
37
40
|
input: z.object({ name: z.string() }),
|
|
38
41
|
kind: 'trail' as const,
|
|
39
|
-
|
|
42
|
+
resources: Object.freeze([...(overrides?.resources ?? [])]),
|
|
40
43
|
...overrides,
|
|
41
44
|
});
|
|
42
45
|
|
|
43
|
-
const
|
|
44
|
-
|
|
46
|
+
const mockResource = (id: string) =>
|
|
47
|
+
resource(id, {
|
|
45
48
|
create: () => Result.ok({ id }),
|
|
46
49
|
});
|
|
47
50
|
|
|
@@ -162,12 +165,12 @@ describe('validateTopo', () => {
|
|
|
162
165
|
});
|
|
163
166
|
});
|
|
164
167
|
|
|
165
|
-
describe('
|
|
166
|
-
test('draft
|
|
167
|
-
const db =
|
|
168
|
+
describe('resource declarations', () => {
|
|
169
|
+
test('draft resource references are allowed in the authored graph', () => {
|
|
170
|
+
const db = mockResource('_draft.db.main');
|
|
168
171
|
const app = topo('app', {
|
|
169
172
|
show: mockTrail('entity.show', {
|
|
170
|
-
|
|
173
|
+
resources: [db],
|
|
171
174
|
}),
|
|
172
175
|
});
|
|
173
176
|
|
|
@@ -175,12 +178,12 @@ describe('validateTopo', () => {
|
|
|
175
178
|
expect(result.isOk()).toBe(true);
|
|
176
179
|
});
|
|
177
180
|
|
|
178
|
-
test('trail declaring a registered
|
|
179
|
-
const db =
|
|
181
|
+
test('trail declaring a registered resource passes', () => {
|
|
182
|
+
const db = mockResource('db.main');
|
|
180
183
|
const app = topo('app', {
|
|
181
184
|
db,
|
|
182
185
|
show: mockTrail('entity.show', {
|
|
183
|
-
|
|
186
|
+
resources: [db],
|
|
184
187
|
}),
|
|
185
188
|
});
|
|
186
189
|
|
|
@@ -188,11 +191,11 @@ describe('validateTopo', () => {
|
|
|
188
191
|
expect(result.isOk()).toBe(true);
|
|
189
192
|
});
|
|
190
193
|
|
|
191
|
-
test('trail declaring a missing
|
|
192
|
-
const db =
|
|
194
|
+
test('trail declaring a missing resource fails', () => {
|
|
195
|
+
const db = mockResource('db.main');
|
|
193
196
|
const app = topo('app', {
|
|
194
197
|
show: mockTrail('entity.show', {
|
|
195
|
-
|
|
198
|
+
resources: [db],
|
|
196
199
|
}),
|
|
197
200
|
});
|
|
198
201
|
|
|
@@ -201,7 +204,7 @@ describe('validateTopo', () => {
|
|
|
201
204
|
|
|
202
205
|
const issues = extractIssues(result);
|
|
203
206
|
expect(issues).toHaveLength(1);
|
|
204
|
-
expect(issues[0]?.rule).toBe('
|
|
207
|
+
expect(issues[0]?.rule).toBe('resource-exists');
|
|
205
208
|
expect(issues[0]?.message).toContain('db.main');
|
|
206
209
|
});
|
|
207
210
|
});
|
|
@@ -300,6 +303,68 @@ describe('validateTopo', () => {
|
|
|
300
303
|
});
|
|
301
304
|
});
|
|
302
305
|
|
|
306
|
+
describe('contour references', () => {
|
|
307
|
+
test('contour referencing a registered contour passes', () => {
|
|
308
|
+
const user = contour(
|
|
309
|
+
'user',
|
|
310
|
+
{ id: z.string().uuid(), name: z.string() },
|
|
311
|
+
{ identity: 'id' }
|
|
312
|
+
);
|
|
313
|
+
const post = contour(
|
|
314
|
+
'post',
|
|
315
|
+
{ authorId: user.id(), id: z.string().uuid() },
|
|
316
|
+
{ identity: 'id' }
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
const app = topo('app', { post, user });
|
|
320
|
+
|
|
321
|
+
const result = validateTopo(app);
|
|
322
|
+
expect(result.isOk()).toBe(true);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test('contour referencing a missing contour fails', () => {
|
|
326
|
+
const user = contour(
|
|
327
|
+
'user',
|
|
328
|
+
{ id: z.string().uuid(), name: z.string() },
|
|
329
|
+
{ identity: 'id' }
|
|
330
|
+
);
|
|
331
|
+
const post = contour(
|
|
332
|
+
'post',
|
|
333
|
+
{ authorId: user.id(), id: z.string().uuid() },
|
|
334
|
+
{ identity: 'id' }
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
// Only register post, not user — the reference is dangling
|
|
338
|
+
const app = topo('app', { post });
|
|
339
|
+
|
|
340
|
+
const result = validateTopo(app);
|
|
341
|
+
expect(result.isErr()).toBe(true);
|
|
342
|
+
|
|
343
|
+
const issues = extractIssues(result);
|
|
344
|
+
expect(issues).toHaveLength(1);
|
|
345
|
+
expect(issues[0]?.rule).toBe('contour-reference-exists');
|
|
346
|
+
expect(issues[0]?.message).toContain('user');
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test('draft contour references are allowed', () => {
|
|
350
|
+
const draftUser = contour(
|
|
351
|
+
'_draft.user',
|
|
352
|
+
{ id: z.string().uuid() },
|
|
353
|
+
{ identity: 'id' }
|
|
354
|
+
);
|
|
355
|
+
const post = contour(
|
|
356
|
+
'post',
|
|
357
|
+
{ authorId: draftUser.id(), id: z.string().uuid() },
|
|
358
|
+
{ identity: 'id' }
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
const app = topo('app', { post });
|
|
362
|
+
|
|
363
|
+
const result = validateTopo(app);
|
|
364
|
+
expect(result.isOk()).toBe(true);
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
|
|
303
368
|
describe('event origins', () => {
|
|
304
369
|
test('event with non-existent origin fails', () => {
|
|
305
370
|
const app = topo('app', {
|
|
@@ -326,11 +391,11 @@ describe('validateTopo', () => {
|
|
|
326
391
|
});
|
|
327
392
|
|
|
328
393
|
test('collects multiple issues', () => {
|
|
329
|
-
const db =
|
|
394
|
+
const db = mockResource('db.main');
|
|
330
395
|
const app = topo('app', {
|
|
331
396
|
broken: mockTrail('entity.broken', { crosses: ['entity.missing'] }),
|
|
332
|
-
|
|
333
|
-
|
|
397
|
+
missingResource: mockTrail('entity.missing-resource', {
|
|
398
|
+
resources: [db],
|
|
334
399
|
}),
|
|
335
400
|
show: mockTrail('entity.show', {
|
|
336
401
|
examples: [{ input: { name: 123 }, name: 'Bad' }],
|
|
@@ -363,7 +428,7 @@ describe('draft state analysis', () => {
|
|
|
363
428
|
draftTrail: mockTrail('_draft.entity.prepare'),
|
|
364
429
|
});
|
|
365
430
|
|
|
366
|
-
const analysis =
|
|
431
|
+
const analysis = deriveDraftReport(app);
|
|
367
432
|
|
|
368
433
|
expect(analysis.declaredDraftIds.has('_draft.entity.prepare')).toBe(true);
|
|
369
434
|
expect(analysis.contaminatedIds.has('_draft.entity.prepare')).toBe(true);
|
|
@@ -382,7 +447,7 @@ describe('draft state analysis', () => {
|
|
|
382
447
|
}),
|
|
383
448
|
});
|
|
384
449
|
|
|
385
|
-
const analysis =
|
|
450
|
+
const analysis = deriveDraftReport(app);
|
|
386
451
|
const exportFinding = analysis.findings.find(
|
|
387
452
|
(finding) => finding.id === 'entity.export'
|
|
388
453
|
);
|
|
@@ -392,6 +457,63 @@ describe('draft state analysis', () => {
|
|
|
392
457
|
expect(exportFinding).toBeDefined();
|
|
393
458
|
expect(exportFinding?.rule).toBe('draft-contamination');
|
|
394
459
|
});
|
|
460
|
+
|
|
461
|
+
test('detects draft contour declarations and schema-reference contamination', () => {
|
|
462
|
+
const draftUser = contour(
|
|
463
|
+
'_draft.user',
|
|
464
|
+
{
|
|
465
|
+
id: z.string().uuid(),
|
|
466
|
+
},
|
|
467
|
+
{ identity: 'id' }
|
|
468
|
+
);
|
|
469
|
+
const gist = contour(
|
|
470
|
+
'gist',
|
|
471
|
+
{
|
|
472
|
+
id: z.string().uuid(),
|
|
473
|
+
ownerId: draftUser.id(),
|
|
474
|
+
},
|
|
475
|
+
{ identity: 'id' }
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
const app = topo('app', {
|
|
479
|
+
draftUser,
|
|
480
|
+
gist,
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
const analysis = deriveDraftReport(app);
|
|
484
|
+
expect(analysis.declaredDraftIds.has('_draft.user')).toBe(true);
|
|
485
|
+
expect(analysis.contaminatedIds.has('gist')).toBe(true);
|
|
486
|
+
expect(analysis.dependencies).toContainEqual({
|
|
487
|
+
fromId: 'gist',
|
|
488
|
+
kind: 'schema-reference',
|
|
489
|
+
toId: '_draft.user',
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
test('propagates contamination through trail contour dependencies', () => {
|
|
494
|
+
const draftUser = contour(
|
|
495
|
+
'_draft.user',
|
|
496
|
+
{
|
|
497
|
+
id: z.string().uuid(),
|
|
498
|
+
},
|
|
499
|
+
{ identity: 'id' }
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
const app = topo('app', {
|
|
503
|
+
createGist: mockTrail('gist.create', {
|
|
504
|
+
contours: [draftUser],
|
|
505
|
+
}),
|
|
506
|
+
draftUser,
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
const analysis = deriveDraftReport(app);
|
|
510
|
+
const finding = analysis.findings.find(
|
|
511
|
+
(entry) => entry.id === 'gist.create'
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
expect(analysis.contaminatedIds.has('gist.create')).toBe(true);
|
|
515
|
+
expect(finding?.via).toBe('contour');
|
|
516
|
+
});
|
|
395
517
|
});
|
|
396
518
|
|
|
397
519
|
describe('validateEstablishedTopo', () => {
|
|
@@ -448,10 +570,10 @@ describe('validateEstablishedTopo', () => {
|
|
|
448
570
|
expect(issues[0]?.rule).toBe('cross-exists');
|
|
449
571
|
});
|
|
450
572
|
|
|
451
|
-
test('fails when
|
|
573
|
+
test('fails when resource declarations are not established in the topo', () => {
|
|
452
574
|
const app = topo('app', {
|
|
453
575
|
show: mockTrail('entity.show', {
|
|
454
|
-
|
|
576
|
+
resources: [mockResource('db.main')],
|
|
455
577
|
}),
|
|
456
578
|
});
|
|
457
579
|
|
|
@@ -460,7 +582,7 @@ describe('validateEstablishedTopo', () => {
|
|
|
460
582
|
|
|
461
583
|
const issues = extractIssues(result);
|
|
462
584
|
expect(issues).toHaveLength(1);
|
|
463
|
-
expect(issues[0]?.rule).toBe('
|
|
585
|
+
expect(issues[0]?.rule).toBe('resource-exists');
|
|
464
586
|
});
|
|
465
587
|
|
|
466
588
|
test('allows authoring-only example issues outside projection checks', () => {
|
|
@@ -7,7 +7,7 @@ import { NotFoundError } from '../errors.js';
|
|
|
7
7
|
import {
|
|
8
8
|
findWorkspaceRoot,
|
|
9
9
|
isInsideWorkspace,
|
|
10
|
-
|
|
10
|
+
deriveRelativePath,
|
|
11
11
|
} from '../workspace.js';
|
|
12
12
|
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
@@ -162,22 +162,22 @@ describe('isInsideWorkspace', () => {
|
|
|
162
162
|
});
|
|
163
163
|
|
|
164
164
|
// ---------------------------------------------------------------------------
|
|
165
|
-
//
|
|
165
|
+
// deriveRelativePath
|
|
166
166
|
// ---------------------------------------------------------------------------
|
|
167
167
|
|
|
168
|
-
describe('
|
|
168
|
+
describe('deriveRelativePath', () => {
|
|
169
169
|
test('returns relative path from workspace root', () => {
|
|
170
170
|
expect(
|
|
171
|
-
|
|
171
|
+
deriveRelativePath('/project/packages/core/src/index.ts', '/project')
|
|
172
172
|
).toBe('packages/core/src/index.ts');
|
|
173
173
|
});
|
|
174
174
|
|
|
175
175
|
test('returns .. segments for paths outside workspace', () => {
|
|
176
|
-
const rel =
|
|
176
|
+
const rel = deriveRelativePath('/other/file.ts', '/project');
|
|
177
177
|
expect(rel.startsWith('..')).toBe(true);
|
|
178
178
|
});
|
|
179
179
|
|
|
180
180
|
test('returns empty string for the root itself', () => {
|
|
181
|
-
expect(
|
|
181
|
+
expect(deriveRelativePath('/project', '/project')).toBe('');
|
|
182
182
|
});
|
|
183
183
|
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
stripDefaultsFromShape,
|
|
6
|
+
stripDefaultWrappers,
|
|
7
|
+
} from '../internal/zod-wrappers.js';
|
|
8
|
+
|
|
9
|
+
describe('stripDefaultWrappers', () => {
|
|
10
|
+
test('drops plain default wrappers', () => {
|
|
11
|
+
const withDefault = z.string().default('x');
|
|
12
|
+
const stripped = stripDefaultWrappers(withDefault);
|
|
13
|
+
|
|
14
|
+
expect(stripped.def.type).toBe('string');
|
|
15
|
+
// Feeding an undefined through the stripped schema no longer
|
|
16
|
+
// materializes the default.
|
|
17
|
+
expect(stripped.safeParse().success).toBe(false);
|
|
18
|
+
expect(stripped.safeParse('value').success).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('drops optional + default layers together', () => {
|
|
22
|
+
const schema = z.string().optional().default('x');
|
|
23
|
+
const stripped = stripDefaultWrappers(schema);
|
|
24
|
+
|
|
25
|
+
// Every wrapper around the underlying string is gone — optional is
|
|
26
|
+
// dropped on purpose because downstream `.partial()` calls re-add it.
|
|
27
|
+
expect(stripped.def.type).toBe('string');
|
|
28
|
+
expect(stripped.safeParse().success).toBe(false);
|
|
29
|
+
expect(stripped.safeParse('value').success).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('preserves nullable constraints through the strip', () => {
|
|
33
|
+
const schema = z.string().nullable().default('x');
|
|
34
|
+
const stripped = stripDefaultWrappers(schema);
|
|
35
|
+
|
|
36
|
+
// Default is gone; null is still a legal input.
|
|
37
|
+
expect(stripped.def.type).toBe('nullable');
|
|
38
|
+
expect(stripped.safeParse(null).success).toBe(true);
|
|
39
|
+
expect(stripped.safeParse('value').success).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('is load-bearing for .partial() on both call sites', () => {
|
|
43
|
+
const shape = {
|
|
44
|
+
author: z.string().optional().default('anon'),
|
|
45
|
+
title: z.string().default('untitled'),
|
|
46
|
+
};
|
|
47
|
+
const schema = z.object(shape);
|
|
48
|
+
|
|
49
|
+
// Both the store `deriveUpdateSchema` helper and the core derive-trail
|
|
50
|
+
// `toPartialSchema` helper compose the same pattern: extend the schema
|
|
51
|
+
// with the stripped shape, then call `.partial()`. Both call sites
|
|
52
|
+
// must produce a schema that accepts an empty object without
|
|
53
|
+
// re-materializing defaults.
|
|
54
|
+
const stripped = stripDefaultsFromShape(schema);
|
|
55
|
+
const partial = schema.extend(stripped).partial();
|
|
56
|
+
|
|
57
|
+
const parsed = partial.parse({});
|
|
58
|
+
expect(parsed).toEqual({});
|
|
59
|
+
expect('author' in parsed).toBe(false);
|
|
60
|
+
expect('title' in parsed).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
});
|
package/src/branded.ts
CHANGED
|
@@ -120,7 +120,7 @@ export const shortId = (length = 8): string => {
|
|
|
120
120
|
* Produce a deterministic hex hash from an input string.
|
|
121
121
|
* Uses a simple FNV-1a 32-bit hash — good enough for non-cryptographic IDs.
|
|
122
122
|
*/
|
|
123
|
-
export const
|
|
123
|
+
export const deriveIdHash = (input: string): string => {
|
|
124
124
|
// FNV offset basis
|
|
125
125
|
let hash = 2_166_136_261;
|
|
126
126
|
for (let i = 0; i < input.length; i += 1) {
|
package/src/context.ts
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { TrailContext, TrailContextInit } from './types.js';
|
|
1
|
+
import { createResourceLookup } from './resource.js';
|
|
2
|
+
import type { TrailContext, TrailContextInit, TraceFn } from './types.js';
|
|
3
3
|
|
|
4
4
|
type MutableTrailContext = {
|
|
5
5
|
-readonly [K in keyof TrailContext]: TrailContext[K];
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Default passthrough `trace` used when a context is built outside
|
|
10
|
+
* `executeTrail`. `executeTrail` replaces this with a real sink-writing
|
|
11
|
+
* implementation. The passthrough runs `fn` without recording anything so
|
|
12
|
+
* direct `createTrailContext()` callers (tests, ad-hoc compositions) don't
|
|
13
|
+
* crash when invoking `ctx.trace(...)`.
|
|
14
|
+
*
|
|
15
|
+
* Declared `async` so both synchronous throws and async rejections from `fn`
|
|
16
|
+
* propagate as a rejected promise to the caller — matching the real
|
|
17
|
+
* sink-writing implementation's error semantics.
|
|
18
|
+
*/
|
|
19
|
+
export const passthroughTrace: TraceFn = async <T>(
|
|
20
|
+
_label: string,
|
|
21
|
+
fn: () => T | Promise<T>
|
|
22
|
+
): Promise<T> => await fn();
|
|
23
|
+
|
|
8
24
|
/**
|
|
9
25
|
* Create a TrailContext with sensible defaults.
|
|
10
26
|
*
|
|
@@ -20,9 +36,13 @@ export const createTrailContext = (
|
|
|
20
36
|
cwd: process.cwd(),
|
|
21
37
|
env: process.env as Record<string, string | undefined>,
|
|
22
38
|
requestId: Bun.randomUUIDv7(),
|
|
39
|
+
trace: passthroughTrace,
|
|
23
40
|
...overrides,
|
|
24
41
|
} as MutableTrailContext;
|
|
25
|
-
const lookup = overrides?.
|
|
26
|
-
ctx.
|
|
42
|
+
const lookup = overrides?.resource ?? createResourceLookup(() => ctx);
|
|
43
|
+
ctx.resource = lookup;
|
|
44
|
+
if (ctx.trace === undefined) {
|
|
45
|
+
ctx.trace = passthroughTrace;
|
|
46
|
+
}
|
|
27
47
|
return ctx;
|
|
28
48
|
};
|