@ontrails/core 1.0.0-beta.15 → 1.0.0-beta.16
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 +63 -0
- package/README.md +61 -16
- package/package.json +9 -8
- package/src/activation-provenance.ts +111 -0
- package/src/activation-source-compatibility.ts +430 -0
- package/src/activation-source-projection.ts +221 -0
- package/src/activation-source.ts +91 -0
- package/src/blob-ref.ts +51 -0
- package/src/context.ts +4 -0
- package/src/{internal/cross-batch.ts → cross-batch.ts} +3 -11
- package/src/detours.ts +8 -0
- package/src/draft.ts +4 -0
- package/src/error-projection.ts +51 -0
- package/src/errors.ts +192 -39
- package/src/execute.ts +443 -44
- package/src/fire.ts +965 -91
- package/src/index.ts +285 -38
- package/src/layer-projection.ts +192 -0
- package/src/layer.ts +32 -1
- package/src/observe.ts +361 -0
- package/src/permits.ts +12 -0
- package/src/redaction/patterns.ts +6 -3
- package/src/resilience.ts +8 -8
- package/src/resource-config.ts +477 -44
- package/src/resource.ts +21 -1
- package/src/result.ts +18 -0
- package/src/run.ts +2 -2
- package/src/schedule-runtime.ts +689 -0
- package/src/schedule.ts +324 -0
- package/src/serialization.ts +90 -57
- package/src/signal-diagnostics.ts +633 -0
- package/src/{internal/signal-ref.ts → signal-ref.ts} +1 -1
- package/src/signal.ts +37 -0
- package/src/store/accessor-protocol.ts +5 -5
- package/src/structured-examples.ts +245 -0
- package/src/surface-derivation.ts +91 -0
- package/src/surface-filter.ts +7 -13
- package/src/topo.ts +386 -6
- package/src/tracing.ts +345 -0
- package/src/trail.ts +168 -22
- package/src/trails/derive-trail.ts +1 -1
- package/src/trails/ingest.ts +7 -15
- package/src/{internal/trails-db.ts → trails-db.ts} +42 -21
- package/src/transport-error-map.ts +146 -28
- package/src/types.ts +82 -28
- package/src/validate-established-topo.ts +6 -0
- package/src/validate-topo.ts +212 -5
- package/src/validation.ts +58 -1
- package/src/webhook.ts +276 -0
- package/src/{internal/zod-wrappers.ts → zod-wrappers.ts} +0 -6
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +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 -22
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -37
- package/dist/context.js.map +0 -1
- package/dist/contour.d.ts +0 -81
- package/dist/contour.d.ts.map +0 -1
- package/dist/contour.js +0 -176
- package/dist/contour.js.map +0 -1
- package/dist/cross-schema.d.ts +0 -18
- package/dist/cross-schema.d.ts.map +0 -1
- package/dist/cross-schema.js +0 -29
- package/dist/cross-schema.js.map +0 -1
- package/dist/derive.d.ts +0 -44
- package/dist/derive.d.ts.map +0 -1
- package/dist/derive.js +0 -149
- package/dist/derive.js.map +0 -1
- package/dist/draft.d.ts +0 -28
- package/dist/draft.d.ts.map +0 -1
- package/dist/draft.js +0 -157
- package/dist/draft.js.map +0 -1
- package/dist/errors.d.ts +0 -141
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -185
- package/dist/errors.js.map +0 -1
- package/dist/execute.d.ts +0 -52
- package/dist/execute.d.ts.map +0 -1
- package/dist/execute.js +0 -511
- package/dist/execute.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/fire.d.ts +0 -43
- package/dist/fire.d.ts.map +0 -1
- package/dist/fire.js +0 -185
- package/dist/fire.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/index.d.ts +0 -59
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -61
- package/dist/index.js.map +0 -1
- package/dist/internal/cross-batch.d.ts +0 -44
- package/dist/internal/cross-batch.d.ts.map +0 -1
- package/dist/internal/cross-batch.js +0 -58
- package/dist/internal/cross-batch.js.map +0 -1
- package/dist/internal/fork-ctx.d.ts +0 -41
- package/dist/internal/fork-ctx.d.ts.map +0 -1
- package/dist/internal/fork-ctx.js +0 -47
- package/dist/internal/fork-ctx.js.map +0 -1
- package/dist/internal/signal-ref.d.ts +0 -15
- package/dist/internal/signal-ref.d.ts.map +0 -1
- package/dist/internal/signal-ref.js +0 -41
- package/dist/internal/signal-ref.js.map +0 -1
- package/dist/internal/topo-saves.d.ts +0 -53
- package/dist/internal/topo-saves.d.ts.map +0 -1
- package/dist/internal/topo-saves.js +0 -449
- package/dist/internal/topo-saves.js.map +0 -1
- package/dist/internal/topo-snapshots.d.ts +0 -57
- package/dist/internal/topo-snapshots.d.ts.map +0 -1
- package/dist/internal/topo-snapshots.js +0 -372
- package/dist/internal/topo-snapshots.js.map +0 -1
- package/dist/internal/topo-store-read.d.ts +0 -70
- package/dist/internal/topo-store-read.d.ts.map +0 -1
- package/dist/internal/topo-store-read.js +0 -221
- package/dist/internal/topo-store-read.js.map +0 -1
- package/dist/internal/topo-store.d.ts +0 -26
- package/dist/internal/topo-store.d.ts.map +0 -1
- package/dist/internal/topo-store.js +0 -621
- package/dist/internal/topo-store.js.map +0 -1
- package/dist/internal/tracing.d.ts +0 -98
- package/dist/internal/tracing.d.ts.map +0 -1
- package/dist/internal/tracing.js +0 -102
- package/dist/internal/tracing.js.map +0 -1
- package/dist/internal/trails-db.d.ts +0 -16
- package/dist/internal/trails-db.d.ts.map +0 -1
- package/dist/internal/trails-db.js +0 -118
- package/dist/internal/trails-db.js.map +0 -1
- package/dist/internal/zod-wrappers.d.ts +0 -40
- package/dist/internal/zod-wrappers.d.ts.map +0 -1
- package/dist/internal/zod-wrappers.js +0 -59
- package/dist/internal/zod-wrappers.js.map +0 -1
- package/dist/layer.d.ts +0 -23
- 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/resource-config.d.ts +0 -22
- package/dist/resource-config.d.ts.map +0 -1
- package/dist/resource-config.js +0 -210
- package/dist/resource-config.js.map +0 -1
- package/dist/resource.d.ts +0 -74
- package/dist/resource.d.ts.map +0 -1
- package/dist/resource.js +0 -61
- package/dist/resource.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/run.d.ts +0 -28
- package/dist/run.d.ts.map +0 -1
- package/dist/run.js +0 -35
- package/dist/run.js.map +0 -1
- package/dist/serialization.d.ts +0 -27
- package/dist/serialization.d.ts.map +0 -1
- package/dist/serialization.js +0 -154
- package/dist/serialization.js.map +0 -1
- package/dist/signal.d.ts +0 -33
- package/dist/signal.d.ts.map +0 -1
- package/dist/signal.js +0 -17
- package/dist/signal.js.map +0 -1
- package/dist/store/accessor-protocol.d.ts +0 -51
- package/dist/store/accessor-protocol.d.ts.map +0 -1
- package/dist/store/accessor-protocol.js +0 -2
- package/dist/store/accessor-protocol.js.map +0 -1
- package/dist/store/index.d.ts +0 -2
- package/dist/store/index.d.ts.map +0 -1
- package/dist/store/index.js +0 -2
- package/dist/store/index.js.map +0 -1
- package/dist/surface-filter.d.ts +0 -13
- package/dist/surface-filter.d.ts.map +0 -1
- package/dist/surface-filter.js +0 -76
- package/dist/surface-filter.js.map +0 -1
- package/dist/topo-store.d.ts +0 -62
- package/dist/topo-store.d.ts.map +0 -1
- package/dist/topo-store.js +0 -319
- package/dist/topo-store.js.map +0 -1
- package/dist/topo.d.ts +0 -39
- package/dist/topo.d.ts.map +0 -1
- package/dist/topo.js +0 -241
- package/dist/topo.js.map +0 -1
- package/dist/trail.d.ts +0 -163
- package/dist/trail.d.ts.map +0 -1
- package/dist/trail.js +0 -75
- package/dist/trail.js.map +0 -1
- package/dist/trails/derive-trail.d.ts +0 -66
- package/dist/trails/derive-trail.d.ts.map +0 -1
- package/dist/trails/derive-trail.js +0 -378
- package/dist/trails/derive-trail.js.map +0 -1
- package/dist/trails/index.d.ts +0 -5
- package/dist/trails/index.d.ts.map +0 -1
- package/dist/trails/index.js +0 -3
- package/dist/trails/index.js.map +0 -1
- package/dist/trails/ingest.d.ts +0 -24
- package/dist/trails/ingest.d.ts.map +0 -1
- package/dist/trails/ingest.js +0 -63
- package/dist/trails/ingest.js.map +0 -1
- package/dist/transport-error-map.d.ts +0 -106
- package/dist/transport-error-map.d.ts.map +0 -1
- package/dist/transport-error-map.js +0 -24
- package/dist/transport-error-map.js.map +0 -1
- package/dist/type-checks.test-d.d.ts +0 -71
- package/dist/type-checks.test-d.d.ts.map +0 -1
- package/dist/type-checks.test-d.js +0 -12
- package/dist/type-checks.test-d.js.map +0 -1
- package/dist/type-utils.d.ts +0 -33
- package/dist/type-utils.d.ts.map +0 -1
- package/dist/type-utils.js +0 -12
- package/dist/type-utils.js.map +0 -1
- package/dist/types.d.ts +0 -177
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
- package/dist/validate-established-topo.d.ts +0 -76
- package/dist/validate-established-topo.d.ts.map +0 -1
- package/dist/validate-established-topo.js +0 -43
- package/dist/validate-established-topo.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 -193
- 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 -165
- 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 -77
- package/src/__tests__/contour.test.ts +0 -263
- package/src/__tests__/derive-trail.test.ts +0 -668
- package/src/__tests__/derive.test.ts +0 -260
- package/src/__tests__/errors.test.ts +0 -427
- package/src/__tests__/execute.test.ts +0 -1563
- package/src/__tests__/fetch.test.ts +0 -217
- package/src/__tests__/fire.test.ts +0 -1056
- package/src/__tests__/fork-ctx.test.ts +0 -83
- package/src/__tests__/guards.test.ts +0 -102
- package/src/__tests__/ingest.test.ts +0 -191
- package/src/__tests__/layer.test.ts +0 -125
- 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__/resource.test.ts +0 -204
- package/src/__tests__/result.test.ts +0 -155
- package/src/__tests__/run.test.ts +0 -181
- package/src/__tests__/serialization.test.ts +0 -401
- package/src/__tests__/service-config.test.ts +0 -224
- package/src/__tests__/signal.test.ts +0 -82
- package/src/__tests__/surface-filter.test.ts +0 -196
- package/src/__tests__/topo-store-read.test.ts +0 -256
- package/src/__tests__/topo-store.test.ts +0 -869
- package/src/__tests__/topo.test.ts +0 -573
- package/src/__tests__/trail-permit.test.ts +0 -60
- package/src/__tests__/trail.test.ts +0 -426
- package/src/__tests__/trails-db.test.ts +0 -198
- package/src/__tests__/transport-error-map.test.ts +0 -82
- package/src/__tests__/type-utils.test.ts +0 -118
- package/src/__tests__/validate-topo.test.ts +0 -611
- package/src/__tests__/validation.test.ts +0 -336
- package/src/__tests__/workspace.test.ts +0 -183
- package/src/__tests__/zod-wrappers.test.ts +0 -62
- package/src/internal/topo-snapshots.ts +0 -517
- package/src/internal/topo-store-read.ts +0 -481
- package/src/internal/topo-store.ts +0 -1192
- package/src/internal/tracing.ts +0 -187
- package/src/topo-store.ts +0 -510
- package/src/type-checks.test-d.ts +0 -106
- package/tsconfig.json +0 -9
- package/tsconfig.tests.json +0 -10
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,573 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
|
|
5
|
-
import { contour } from '../contour.js';
|
|
6
|
-
import { ValidationError } from '../errors.js';
|
|
7
|
-
import {
|
|
8
|
-
attachLateBoundSignalRef,
|
|
9
|
-
cloneSignalWithId,
|
|
10
|
-
} from '../internal/signal-ref.js';
|
|
11
|
-
import { resource } from '../resource.js';
|
|
12
|
-
import { Result } from '../result.js';
|
|
13
|
-
import { signal } from '../signal.js';
|
|
14
|
-
import { trail } from '../trail.js';
|
|
15
|
-
import { topo } from '../topo.js';
|
|
16
|
-
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
// Mock factories
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
const mockContour = (name: string) =>
|
|
22
|
-
contour(
|
|
23
|
-
name,
|
|
24
|
-
{
|
|
25
|
-
id: z.string().uuid(),
|
|
26
|
-
value: z.string(),
|
|
27
|
-
},
|
|
28
|
-
{ identity: 'id' }
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
const mockTrail = (
|
|
32
|
-
id: string,
|
|
33
|
-
crosses?: readonly string[],
|
|
34
|
-
contours?: readonly ReturnType<typeof mockContour>[]
|
|
35
|
-
) =>
|
|
36
|
-
trail(id, {
|
|
37
|
-
blaze: () => Result.ok({ y: 0 }),
|
|
38
|
-
contours,
|
|
39
|
-
crosses,
|
|
40
|
-
input: z.object({ x: z.number() }),
|
|
41
|
-
output: z.object({ y: z.number() }),
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const mockEvent = (id: string) =>
|
|
45
|
-
signal(id, {
|
|
46
|
-
payload: z.object({ payload: z.string() }),
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
const mockSignalConsumer = (on: readonly ReturnType<typeof mockEvent>[]) =>
|
|
50
|
-
trail('notify.users', {
|
|
51
|
-
blaze: () => Result.ok({ ok: true }),
|
|
52
|
-
input: z.object({ payload: z.string() }),
|
|
53
|
-
on,
|
|
54
|
-
output: z.object({ ok: z.boolean() }),
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
const mockResource = (
|
|
58
|
-
id: string,
|
|
59
|
-
signals?: readonly ReturnType<typeof mockEvent>[]
|
|
60
|
-
) =>
|
|
61
|
-
resource(id, {
|
|
62
|
-
create: () => Result.ok({ id }),
|
|
63
|
-
description: `${id} resource`,
|
|
64
|
-
signals,
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// ---------------------------------------------------------------------------
|
|
68
|
-
// topo()
|
|
69
|
-
// ---------------------------------------------------------------------------
|
|
70
|
-
|
|
71
|
-
describe('topo', () => {
|
|
72
|
-
describe('identity', () => {
|
|
73
|
-
test('preserves object identity metadata', () => {
|
|
74
|
-
const t = topo({
|
|
75
|
-
description: 'Demo topo',
|
|
76
|
-
name: 'my-app',
|
|
77
|
-
version: '1.2.3',
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
expect(t.name).toBe('my-app');
|
|
81
|
-
expect(t.version).toBe('1.2.3');
|
|
82
|
-
expect(t.description).toBe('Demo topo');
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
describe('trail collection', () => {
|
|
87
|
-
test('returns Topo with name', () => {
|
|
88
|
-
const t = topo('my-app');
|
|
89
|
-
expect(t.name).toBe('my-app');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test('collects trails from modules', () => {
|
|
93
|
-
const mod = { myTrail: mockTrail('create-user') };
|
|
94
|
-
const t = topo('app', mod);
|
|
95
|
-
|
|
96
|
-
expect(t.trails.size).toBe(1);
|
|
97
|
-
expect(t.trails.get('create-user')).toBe(mod.myTrail);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test('auto-scans exports by kind discriminant', () => {
|
|
101
|
-
const mod = {
|
|
102
|
-
event1: mockEvent('e1'),
|
|
103
|
-
resource1: mockResource('s1'),
|
|
104
|
-
trail1: mockTrail('t1'),
|
|
105
|
-
trail2: mockTrail('t2', ['t1']),
|
|
106
|
-
};
|
|
107
|
-
const t = topo('app', mod);
|
|
108
|
-
|
|
109
|
-
expect(t.trails.size).toBe(2);
|
|
110
|
-
expect(t.signals.size).toBe(1);
|
|
111
|
-
expect(t.resources.size).toBe(1);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test('collects from multiple modules', () => {
|
|
115
|
-
const mod1 = { a: mockTrail('t1') };
|
|
116
|
-
const mod2 = { b: mockTrail('t2'), c: mockTrail('t3', ['t1']) };
|
|
117
|
-
const t = topo('app', mod1, mod2);
|
|
118
|
-
|
|
119
|
-
expect(t.trails.size).toBe(3);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test('non-trail exports are silently ignored', () => {
|
|
123
|
-
const mod = {
|
|
124
|
-
config: { port: 3000 },
|
|
125
|
-
helper: () => 'not a trail',
|
|
126
|
-
name: 'some-string',
|
|
127
|
-
nothing: null,
|
|
128
|
-
num: 42,
|
|
129
|
-
trail1: mockTrail('t1'),
|
|
130
|
-
undef: undefined,
|
|
131
|
-
};
|
|
132
|
-
const t = topo('app', mod);
|
|
133
|
-
|
|
134
|
-
expect(t.trails.size).toBe(1);
|
|
135
|
-
expect(t.signals.size).toBe(0);
|
|
136
|
-
expect(t.resources.size).toBe(0);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
test('trail with crossings registers correctly', () => {
|
|
140
|
-
const mod = { t: mockTrail('trail-1', ['trail-2']) };
|
|
141
|
-
const t = topo('app', mod);
|
|
142
|
-
|
|
143
|
-
expect(t.trails.size).toBe(1);
|
|
144
|
-
const registered = t.trails.get('trail-1');
|
|
145
|
-
expect(registered?.crosses).toEqual(['trail-2']);
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
describe('resource and signal collection', () => {
|
|
150
|
-
test('collects resources from modules', () => {
|
|
151
|
-
const mod = { db: mockResource('db.main') };
|
|
152
|
-
const t = topo('app', mod);
|
|
153
|
-
|
|
154
|
-
expect(t.resources.size).toBe(1);
|
|
155
|
-
expect(t.resources.get('db.main')).toBe(mod.db);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
test('registers signals declared on resources into the topo graph', () => {
|
|
159
|
-
const usersCreated = mockEvent('users.created');
|
|
160
|
-
const t = topo('app', {
|
|
161
|
-
db: mockResource('db.main', [usersCreated]),
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
expect(t.signals.size).toBe(1);
|
|
165
|
-
expect(t.listSignals()).toContain(usersCreated);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test('resolves late-bound store signal refs from resource-scoped signals', () => {
|
|
169
|
-
const authored = attachLateBoundSignalRef(mockEvent('users.created'), {
|
|
170
|
-
kind: 'store-derived',
|
|
171
|
-
token: 'users-created',
|
|
172
|
-
});
|
|
173
|
-
const consumer = mockSignalConsumer([authored]);
|
|
174
|
-
const scoped = cloneSignalWithId(authored, 'identity:users.created');
|
|
175
|
-
const t = topo('app', {
|
|
176
|
-
consumer,
|
|
177
|
-
identity: mockResource('identity', [scoped]),
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
expect(t.get('notify.users')?.on).toEqual(['identity:users.created']);
|
|
181
|
-
expect(t.listSignals().map((s) => s.id)).toContain(
|
|
182
|
-
'identity:users.created'
|
|
183
|
-
);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test('preserves canonical scoped signal ids across multi-binding stores', () => {
|
|
187
|
-
// Regression test: the same store definition bound under two resources
|
|
188
|
-
// (identity + billing). A trail that registers under one resource and
|
|
189
|
-
// another that registers under the other must resolve to the distinct
|
|
190
|
-
// canonical scoped ids, not collide on a shared late-bound token.
|
|
191
|
-
const authored = attachLateBoundSignalRef(mockEvent('users.created'), {
|
|
192
|
-
kind: 'store-derived',
|
|
193
|
-
token: 'users-created-multi',
|
|
194
|
-
});
|
|
195
|
-
const identityScoped = cloneSignalWithId(
|
|
196
|
-
authored,
|
|
197
|
-
'identity:users.created'
|
|
198
|
-
);
|
|
199
|
-
const billingScoped = cloneSignalWithId(
|
|
200
|
-
authored,
|
|
201
|
-
'billing:users.created'
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
const identityConsumer = trail('notify.identity-users', {
|
|
205
|
-
blaze: () => Result.ok({ ok: true }),
|
|
206
|
-
input: z.object({ payload: z.string() }),
|
|
207
|
-
on: [identityScoped],
|
|
208
|
-
output: z.object({ ok: z.boolean() }),
|
|
209
|
-
});
|
|
210
|
-
const billingConsumer = trail('notify.billing-users', {
|
|
211
|
-
blaze: () => Result.ok({ ok: true }),
|
|
212
|
-
input: z.object({ payload: z.string() }),
|
|
213
|
-
on: [billingScoped],
|
|
214
|
-
output: z.object({ ok: z.boolean() }),
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
const t = topo('app', {
|
|
218
|
-
billing: mockResource('billing', [billingScoped]),
|
|
219
|
-
billingConsumer,
|
|
220
|
-
identity: mockResource('identity', [identityScoped]),
|
|
221
|
-
identityConsumer,
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
expect(t.get('notify.identity-users')?.on).toEqual([
|
|
225
|
-
'identity:users.created',
|
|
226
|
-
]);
|
|
227
|
-
expect(t.get('notify.billing-users')?.on).toEqual([
|
|
228
|
-
'billing:users.created',
|
|
229
|
-
]);
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
test('preserves canonical scoped signal ids when resource ids contain dots', () => {
|
|
233
|
-
// Regression test: the canonical-scope predicate previously disallowed
|
|
234
|
-
// dots inside the scope segment, which collapsed dotted resource ids
|
|
235
|
-
// like `demo.store` and `other.store` back to their shared late-bound
|
|
236
|
-
// token. With `:` prohibited inside resource ids, the scope is
|
|
237
|
-
// unambiguously everything before the first `:`, so dotted scopes must
|
|
238
|
-
// resolve to distinct canonical ids.
|
|
239
|
-
const authored = attachLateBoundSignalRef(mockEvent('gists.created'), {
|
|
240
|
-
kind: 'store-derived',
|
|
241
|
-
token: 'gists-created-dotted',
|
|
242
|
-
});
|
|
243
|
-
const demoScoped = cloneSignalWithId(
|
|
244
|
-
authored,
|
|
245
|
-
'demo.store:gists.created'
|
|
246
|
-
);
|
|
247
|
-
const otherScoped = cloneSignalWithId(
|
|
248
|
-
authored,
|
|
249
|
-
'other.store:gists.created'
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
const demoConsumer = trail('notify.demo-gists', {
|
|
253
|
-
blaze: () => Result.ok({ ok: true }),
|
|
254
|
-
input: z.object({ payload: z.string() }),
|
|
255
|
-
on: [demoScoped],
|
|
256
|
-
output: z.object({ ok: z.boolean() }),
|
|
257
|
-
});
|
|
258
|
-
const otherConsumer = trail('notify.other-gists', {
|
|
259
|
-
blaze: () => Result.ok({ ok: true }),
|
|
260
|
-
input: z.object({ payload: z.string() }),
|
|
261
|
-
on: [otherScoped],
|
|
262
|
-
output: z.object({ ok: z.boolean() }),
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
const t = topo('app', {
|
|
266
|
-
'demo.store': mockResource('demo.store', [demoScoped]),
|
|
267
|
-
demoConsumer,
|
|
268
|
-
'other.store': mockResource('other.store', [otherScoped]),
|
|
269
|
-
otherConsumer,
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
expect(t.get('notify.demo-gists')?.on).toEqual([
|
|
273
|
-
'demo.store:gists.created',
|
|
274
|
-
]);
|
|
275
|
-
expect(t.get('notify.other-gists')?.on).toEqual([
|
|
276
|
-
'other.store:gists.created',
|
|
277
|
-
]);
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
test('markerizes late-bound ids containing `:` but not in canonical scoped form', () => {
|
|
281
|
-
// Guard regression: `normalizeSignalRef` previously passed any id
|
|
282
|
-
// containing `:` through unchanged, which would let a non-canonical
|
|
283
|
-
// late-bound id like `foo:bar` slip past markerization and then fail
|
|
284
|
-
// to resolve during topo finalization. The strict predicate should
|
|
285
|
-
// still rewrite such ids onto the late-bound marker path, while
|
|
286
|
-
// leaving canonical `<scope>:<table>.<event>` ids alone.
|
|
287
|
-
const nonCanonical = attachLateBoundSignalRef(mockEvent('foo:bar'), {
|
|
288
|
-
kind: 'store-derived',
|
|
289
|
-
token: 'non-canonical',
|
|
290
|
-
});
|
|
291
|
-
const nonCanonicalConsumer = trail('notify.non-canonical', {
|
|
292
|
-
blaze: () => Result.ok({ ok: true }),
|
|
293
|
-
input: z.object({ payload: z.string() }),
|
|
294
|
-
on: [nonCanonical],
|
|
295
|
-
output: z.object({ ok: z.boolean() }),
|
|
296
|
-
});
|
|
297
|
-
expect(nonCanonicalConsumer.on).not.toEqual(['foo:bar']);
|
|
298
|
-
|
|
299
|
-
const canonical = attachLateBoundSignalRef(mockEvent('users.created'), {
|
|
300
|
-
kind: 'store-derived',
|
|
301
|
-
token: 'canonical',
|
|
302
|
-
});
|
|
303
|
-
const scoped = cloneSignalWithId(canonical, 'identity:users.created');
|
|
304
|
-
const canonicalConsumer = trail('notify.canonical', {
|
|
305
|
-
blaze: () => Result.ok({ ok: true }),
|
|
306
|
-
input: z.object({ payload: z.string() }),
|
|
307
|
-
on: [scoped],
|
|
308
|
-
output: z.object({ ok: z.boolean() }),
|
|
309
|
-
});
|
|
310
|
-
expect(canonicalConsumer.on).toEqual(['identity:users.created']);
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
test('rejects ambiguous late-bound store signal refs', () => {
|
|
314
|
-
const authored = attachLateBoundSignalRef(mockEvent('users.created'), {
|
|
315
|
-
kind: 'store-derived',
|
|
316
|
-
token: 'users-created',
|
|
317
|
-
});
|
|
318
|
-
const consumer = mockSignalConsumer([authored]);
|
|
319
|
-
|
|
320
|
-
expect(() =>
|
|
321
|
-
topo('app', {
|
|
322
|
-
billing: mockResource('billing', [
|
|
323
|
-
cloneSignalWithId(authored, 'billing:users.created'),
|
|
324
|
-
]),
|
|
325
|
-
consumer,
|
|
326
|
-
identity: mockResource('identity', [
|
|
327
|
-
cloneSignalWithId(authored, 'identity:users.created'),
|
|
328
|
-
]),
|
|
329
|
-
})
|
|
330
|
-
).toThrow(
|
|
331
|
-
'Trail "notify.users" references late-bound signal "users.created" but it resolves to multiple bound resource signals'
|
|
332
|
-
);
|
|
333
|
-
});
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
describe('contour collection', () => {
|
|
337
|
-
test('collects contours exported directly from modules', () => {
|
|
338
|
-
const user = mockContour('user');
|
|
339
|
-
const t = topo('app', { user });
|
|
340
|
-
|
|
341
|
-
expect(t.contours.size).toBe(1);
|
|
342
|
-
expect(t.getContour('user')).toBe(user);
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
test('registers contours declared on trails into the topo graph', () => {
|
|
346
|
-
const user = mockContour('user');
|
|
347
|
-
const t = topo('app', {
|
|
348
|
-
createUser: mockTrail('user.create', [], [user]),
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
expect(t.contours.size).toBe(1);
|
|
352
|
-
expect(t.getContour('user')).toBe(user);
|
|
353
|
-
});
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
describe('duplicate rejection', () => {
|
|
357
|
-
test('rejects duplicate trail IDs', () => {
|
|
358
|
-
const mod1 = { a: mockTrail('dup') };
|
|
359
|
-
const mod2 = { b: mockTrail('dup') };
|
|
360
|
-
|
|
361
|
-
expect(() => topo('app', mod1, mod2)).toThrow(ValidationError);
|
|
362
|
-
expect(() => topo('app', mod1, mod2)).toThrow(
|
|
363
|
-
'Duplicate trail ID: "dup"'
|
|
364
|
-
);
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
test('rejects duplicate event IDs', () => {
|
|
368
|
-
const mod1 = { a: mockEvent('dup') };
|
|
369
|
-
const mod2 = { b: mockEvent('dup') };
|
|
370
|
-
|
|
371
|
-
expect(() => topo('app', mod1, mod2)).toThrow(ValidationError);
|
|
372
|
-
expect(() => topo('app', mod1, mod2)).toThrow(
|
|
373
|
-
'Duplicate signal ID: "dup"'
|
|
374
|
-
);
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
test('rejects duplicate resource IDs', () => {
|
|
378
|
-
const mod1 = { a: mockResource('dup') };
|
|
379
|
-
const mod2 = { b: mockResource('dup') };
|
|
380
|
-
|
|
381
|
-
expect(() => topo('app', mod1, mod2)).toThrow(ValidationError);
|
|
382
|
-
expect(() => topo('app', mod1, mod2)).toThrow(
|
|
383
|
-
'Duplicate resource ID: "dup"'
|
|
384
|
-
);
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
test('rejects duplicate contour names', () => {
|
|
388
|
-
const mod1 = { a: mockContour('user') };
|
|
389
|
-
const mod2 = { b: mockContour('user') };
|
|
390
|
-
|
|
391
|
-
expect(() => topo('app', mod1, mod2)).toThrow(ValidationError);
|
|
392
|
-
expect(() => topo('app', mod1, mod2)).toThrow(
|
|
393
|
-
'Duplicate contour name: "user"'
|
|
394
|
-
);
|
|
395
|
-
});
|
|
396
|
-
});
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
// ---------------------------------------------------------------------------
|
|
400
|
-
// topo accessors
|
|
401
|
-
// ---------------------------------------------------------------------------
|
|
402
|
-
|
|
403
|
-
describe('topo accessors', () => {
|
|
404
|
-
test('ids() returns all trail IDs', () => {
|
|
405
|
-
const a = mockTrail('alpha');
|
|
406
|
-
const b = mockTrail('beta');
|
|
407
|
-
const app = topo('test', { a, b });
|
|
408
|
-
expect(app.ids().toSorted()).toEqual(['alpha', 'beta']);
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
test('count returns number of trails', () => {
|
|
412
|
-
const a = mockTrail('alpha');
|
|
413
|
-
const app = topo('test', { a });
|
|
414
|
-
expect(app.count).toBe(1);
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
test('resourceCount returns number of resources', () => {
|
|
418
|
-
const db = mockResource('db.main');
|
|
419
|
-
const cache = mockResource('cache.main');
|
|
420
|
-
const app = topo('test', { cache, db });
|
|
421
|
-
expect(app.resourceCount).toBe(2);
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
test('contourCount returns number of contours', () => {
|
|
425
|
-
const gist = mockContour('gist');
|
|
426
|
-
const user = mockContour('user');
|
|
427
|
-
const app = topo('test', { gist, user });
|
|
428
|
-
expect(app.contourCount).toBe(2);
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
test('empty topo has zero count and empty ids', () => {
|
|
432
|
-
const app = topo('empty');
|
|
433
|
-
expect(app.count).toBe(0);
|
|
434
|
-
expect(app.contourCount).toBe(0);
|
|
435
|
-
expect(app.contourIds()).toEqual([]);
|
|
436
|
-
expect(app.ids()).toEqual([]);
|
|
437
|
-
expect(app.resourceCount).toBe(0);
|
|
438
|
-
expect(app.resourceIds()).toEqual([]);
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
test('resourceIds() returns all resource IDs', () => {
|
|
442
|
-
const db = mockResource('db.main');
|
|
443
|
-
const cache = mockResource('cache.main');
|
|
444
|
-
const app = topo('test', { cache, db });
|
|
445
|
-
expect(app.resourceIds().toSorted()).toEqual(['cache.main', 'db.main']);
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
test('contourIds() returns all contour names', () => {
|
|
449
|
-
const gist = mockContour('gist');
|
|
450
|
-
const user = mockContour('user');
|
|
451
|
-
const app = topo('test', { gist, user });
|
|
452
|
-
expect(app.contourIds().toSorted()).toEqual(['gist', 'user']);
|
|
453
|
-
});
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
// ---------------------------------------------------------------------------
|
|
457
|
-
// Topo
|
|
458
|
-
// ---------------------------------------------------------------------------
|
|
459
|
-
|
|
460
|
-
describe('Topo', () => {
|
|
461
|
-
const mod = {
|
|
462
|
-
e1: mockEvent('event-1'),
|
|
463
|
-
p1: mockResource('resource-1'),
|
|
464
|
-
t1: mockTrail('trail-1'),
|
|
465
|
-
t2: mockTrail('trail-2'),
|
|
466
|
-
t3: mockTrail('trail-3', ['trail-1']),
|
|
467
|
-
};
|
|
468
|
-
|
|
469
|
-
// Build once for the describe block
|
|
470
|
-
const app = topo('app', mod);
|
|
471
|
-
|
|
472
|
-
describe('get()', () => {
|
|
473
|
-
test('retrieves contour by name', () => {
|
|
474
|
-
const user = mockContour('user');
|
|
475
|
-
const contourApp = topo('app', { user });
|
|
476
|
-
|
|
477
|
-
expect(contourApp.getContour('user')).toBe(user);
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
test('retrieves trail by ID', () => {
|
|
481
|
-
expect(app.get('trail-1')).toBe(mod.t1);
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
test('retrieves trail with crossings by ID', () => {
|
|
485
|
-
expect(app.get('trail-3')).toBe(mod.t3);
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
test('returns undefined for unknown ID', () => {
|
|
489
|
-
expect(app.get('nope')).toBeUndefined();
|
|
490
|
-
});
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
describe('has()', () => {
|
|
494
|
-
test('returns true for known contour', () => {
|
|
495
|
-
const user = mockContour('user');
|
|
496
|
-
const contourApp = topo('app', { user });
|
|
497
|
-
|
|
498
|
-
expect(contourApp.hasContour('user')).toBe(true);
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
test('returns true for known trail', () => {
|
|
502
|
-
expect(app.has('trail-1')).toBe(true);
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
test('returns true for trail with crossings', () => {
|
|
506
|
-
expect(app.has('trail-3')).toBe(true);
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
test('returns false for unknown ID', () => {
|
|
510
|
-
expect(app.has('nope')).toBe(false);
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
test('returns false for unknown contour', () => {
|
|
514
|
-
const contourApp = topo('app');
|
|
515
|
-
|
|
516
|
-
expect(contourApp.hasContour('missing')).toBe(false);
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
test('returns false for event ID (signals are not trails)', () => {
|
|
520
|
-
expect(app.has('event-1')).toBe(false);
|
|
521
|
-
});
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
describe('getResource()', () => {
|
|
525
|
-
test('retrieves resource by ID', () => {
|
|
526
|
-
expect(app.getResource('resource-1')).toBe(mod.p1);
|
|
527
|
-
});
|
|
528
|
-
|
|
529
|
-
test('returns undefined for unknown resource ID', () => {
|
|
530
|
-
expect(app.getResource('missing-resource')).toBeUndefined();
|
|
531
|
-
});
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
describe('hasResource()', () => {
|
|
535
|
-
test('returns true for known resource', () => {
|
|
536
|
-
expect(app.hasResource('resource-1')).toBe(true);
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
test('returns false for unknown resource', () => {
|
|
540
|
-
expect(app.hasResource('missing-resource')).toBe(false);
|
|
541
|
-
});
|
|
542
|
-
});
|
|
543
|
-
|
|
544
|
-
describe('listing', () => {
|
|
545
|
-
test('list() returns all trails (with and without crossings)', () => {
|
|
546
|
-
const items = app.list();
|
|
547
|
-
expect(items).toHaveLength(3);
|
|
548
|
-
expect(items).toContain(mod.t1);
|
|
549
|
-
expect(items).toContain(mod.t2);
|
|
550
|
-
expect(items).toContain(mod.t3);
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
test('listEvents() returns all signals', () => {
|
|
554
|
-
const items = app.listSignals();
|
|
555
|
-
expect(items).toHaveLength(1);
|
|
556
|
-
expect(items).toContain(mod.e1);
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
test('listResources() returns all resources', () => {
|
|
560
|
-
const items = app.listResources();
|
|
561
|
-
expect(items).toHaveLength(1);
|
|
562
|
-
expect(items).toContain(mod.p1);
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
test('listContours() returns all contours', () => {
|
|
566
|
-
const gist = mockContour('gist');
|
|
567
|
-
const user = mockContour('user');
|
|
568
|
-
const contourApp = topo('app', { gist, user });
|
|
569
|
-
|
|
570
|
-
expect(contourApp.listContours()).toEqual([gist, user]);
|
|
571
|
-
});
|
|
572
|
-
});
|
|
573
|
-
});
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
|
|
5
|
-
import { Result } from '../result';
|
|
6
|
-
import { trail } from '../trail';
|
|
7
|
-
import type { PermitRequirement } from '../types';
|
|
8
|
-
|
|
9
|
-
describe('PermitRequirement type', () => {
|
|
10
|
-
test('accepts a scopes object', () => {
|
|
11
|
-
const req: PermitRequirement = { scopes: ['user:write', 'user:read'] };
|
|
12
|
-
expect(req).toEqual({ scopes: ['user:write', 'user:read'] });
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
test('accepts public literal', () => {
|
|
16
|
-
const req: PermitRequirement = 'public';
|
|
17
|
-
expect(req).toBe('public');
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
describe('trail() with permit field', () => {
|
|
22
|
-
test('accepts permit with scopes', () => {
|
|
23
|
-
const t = trail('user.delete', {
|
|
24
|
-
blaze: (input) => Result.ok({ deleted: input.id }),
|
|
25
|
-
input: z.object({ id: z.string() }),
|
|
26
|
-
intent: 'destroy',
|
|
27
|
-
permit: { scopes: ['user:write'] },
|
|
28
|
-
});
|
|
29
|
-
expect(t.permit).toEqual({ scopes: ['user:write'] });
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test('accepts permit: public', () => {
|
|
33
|
-
const t = trail('health.check', {
|
|
34
|
-
blaze: () => Result.ok({ status: 'ok' }),
|
|
35
|
-
input: z.object({}),
|
|
36
|
-
intent: 'read',
|
|
37
|
-
permit: 'public',
|
|
38
|
-
});
|
|
39
|
-
expect(t.permit).toBe('public');
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('permit is undefined when omitted (backward compatible)', () => {
|
|
43
|
-
const t = trail('legacy.trail', {
|
|
44
|
-
blaze: () => Result.ok(),
|
|
45
|
-
input: z.object({}),
|
|
46
|
-
});
|
|
47
|
-
expect(t.permit).toBeUndefined();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test('preserves permit on the frozen Trail object', () => {
|
|
51
|
-
const t = trail('user.list', {
|
|
52
|
-
blaze: () => Result.ok([]),
|
|
53
|
-
input: z.object({}),
|
|
54
|
-
intent: 'read',
|
|
55
|
-
permit: { scopes: ['user:read'] },
|
|
56
|
-
});
|
|
57
|
-
expect(Object.isFrozen(t)).toBe(true);
|
|
58
|
-
expect(t.permit).toEqual({ scopes: ['user:read'] });
|
|
59
|
-
});
|
|
60
|
-
});
|