@ontrails/core 1.0.0-beta.14 → 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 +74 -0
- package/README.md +73 -24
- package/package.json +11 -4
- package/src/activation-provenance.ts +111 -0
- package/src/activation-source-compatibility.ts +430 -0
- package/src/activation-source-projection.ts +221 -0
- package/src/activation-source.ts +91 -0
- package/src/blob-ref.ts +51 -0
- package/src/branded.ts +1 -1
- package/src/context.ts +28 -4
- package/src/contour.ts +344 -0
- package/src/cross-batch.ts +69 -0
- package/src/cross-schema.ts +36 -0
- package/src/detours.ts +8 -0
- package/src/draft.ts +60 -52
- package/src/error-projection.ts +51 -0
- package/src/errors.ts +245 -48
- package/src/execute.ts +1250 -40
- package/src/fire.ts +1169 -0
- package/src/index.ts +383 -49
- package/src/internal/fork-ctx.ts +69 -0
- package/src/layer-projection.ts +192 -0
- package/src/layer.ts +81 -0
- package/src/observe.ts +361 -0
- package/src/path-security.ts +1 -1
- package/src/permits.ts +12 -0
- package/src/redaction/patterns.ts +6 -3
- package/src/resilience.ts +11 -11
- package/src/resource-config.ts +792 -0
- package/src/resource.ts +175 -0
- package/src/result.ts +18 -0
- package/src/run.ts +7 -6
- package/src/schedule-runtime.ts +689 -0
- package/src/schedule.ts +324 -0
- package/src/serialization.ts +90 -55
- package/src/signal-diagnostics.ts +633 -0
- package/src/signal-ref.ts +87 -0
- package/src/signal.ts +37 -0
- package/src/store/accessor-protocol.ts +56 -0
- package/src/store/index.ts +4 -0
- package/src/structured-examples.ts +245 -0
- package/src/surface-derivation.ts +91 -0
- package/src/surface-filter.ts +176 -0
- package/src/topo.ts +710 -59
- package/src/tracing.ts +345 -0
- package/src/trail.ts +347 -33
- package/src/trails/derive-trail.ts +842 -0
- package/src/trails/index.ts +9 -0
- package/src/trails/ingest.ts +138 -0
- package/src/{internal/trails-db.ts → trails-db.ts} +50 -31
- package/src/transport-error-map.ts +174 -0
- package/src/type-utils.ts +17 -2
- package/src/types.ts +208 -17
- package/src/validate-established-topo.ts +8 -2
- package/src/validate-topo.ts +244 -13
- package/src/validation.ts +58 -1
- package/src/webhook.ts +276 -0
- package/src/workspace.ts +1 -1
- package/src/zod-wrappers.ts +72 -0
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/dist/adapters.d.ts +0 -39
- package/dist/adapters.d.ts.map +0 -1
- package/dist/adapters.js +0 -2
- package/dist/adapters.js.map +0 -1
- package/dist/blob-ref.d.ts +0 -20
- package/dist/blob-ref.d.ts.map +0 -1
- package/dist/blob-ref.js +0 -22
- package/dist/blob-ref.js.map +0 -1
- package/dist/branded.d.ts +0 -36
- package/dist/branded.d.ts.map +0 -1
- package/dist/branded.js +0 -89
- package/dist/branded.js.map +0 -1
- package/dist/collections.d.ts +0 -31
- package/dist/collections.d.ts.map +0 -1
- package/dist/collections.js +0 -60
- package/dist/collections.js.map +0 -1
- package/dist/context.d.ts +0 -10
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -21
- package/dist/context.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/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/draft.d.ts +0 -28
- package/dist/draft.d.ts.map +0 -1
- package/dist/draft.js +0 -156
- package/dist/draft.js.map +0 -1
- package/dist/errors.d.ts +0 -83
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -142
- package/dist/errors.js.map +0 -1
- package/dist/event.d.ts +0 -8
- package/dist/event.d.ts.map +0 -1
- package/dist/event.js +0 -7
- package/dist/event.js.map +0 -1
- package/dist/execute.d.ts +0 -35
- package/dist/execute.d.ts.map +0 -1
- package/dist/execute.js +0 -90
- 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/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/guards.d.ts +0 -17
- package/dist/guards.d.ts.map +0 -1
- package/dist/guards.js +0 -25
- package/dist/guards.js.map +0 -1
- package/dist/health.d.ts +0 -18
- package/dist/health.d.ts.map +0 -1
- package/dist/health.js +0 -5
- package/dist/health.js.map +0 -1
- package/dist/index.d.ts +0 -48
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -51
- package/dist/index.js.map +0 -1
- package/dist/internal/topo-saves.d.ts +0 -47
- package/dist/internal/topo-saves.d.ts.map +0 -1
- package/dist/internal/topo-saves.js +0 -310
- package/dist/internal/topo-saves.js.map +0 -1
- package/dist/internal/topo-store-read.d.ts +0 -67
- package/dist/internal/topo-store-read.d.ts.map +0 -1
- package/dist/internal/topo-store-read.js +0 -222
- package/dist/internal/topo-store-read.js.map +0 -1
- package/dist/internal/topo-store.d.ts +0 -12
- package/dist/internal/topo-store.d.ts.map +0 -1
- package/dist/internal/topo-store.js +0 -571
- package/dist/internal/topo-store.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/job.d.ts +0 -24
- package/dist/job.d.ts.map +0 -1
- package/dist/job.js +0 -17
- package/dist/job.js.map +0 -1
- package/dist/layer.d.ts +0 -6
- package/dist/layer.d.ts.map +0 -1
- package/dist/layer.js +0 -4
- 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/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/redaction/index.d.ts +0 -4
- package/dist/redaction/index.d.ts.map +0 -1
- package/dist/redaction/index.js +0 -3
- package/dist/redaction/index.js.map +0 -1
- package/dist/redaction/patterns.d.ts +0 -9
- package/dist/redaction/patterns.d.ts.map +0 -1
- package/dist/redaction/patterns.js +0 -39
- package/dist/redaction/patterns.js.map +0 -1
- package/dist/redaction/redactor.d.ts +0 -27
- package/dist/redaction/redactor.d.ts.map +0 -1
- package/dist/redaction/redactor.js +0 -89
- package/dist/redaction/redactor.js.map +0 -1
- package/dist/resilience.d.ts +0 -34
- package/dist/resilience.d.ts.map +0 -1
- package/dist/resilience.js +0 -164
- package/dist/resilience.js.map +0 -1
- package/dist/result.d.ts +0 -57
- package/dist/result.d.ts.map +0 -1
- package/dist/result.js +0 -156
- package/dist/result.js.map +0 -1
- package/dist/run.d.ts +0 -27
- package/dist/run.d.ts.map +0 -1
- package/dist/run.js +0 -34
- 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 -153
- package/dist/serialization.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/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/topo-store.d.ts +0 -48
- package/dist/topo-store.d.ts.map +0 -1
- package/dist/topo-store.js +0 -175
- package/dist/topo-store.js.map +0 -1
- package/dist/topo.d.ts +0 -25
- package/dist/topo.d.ts.map +0 -1
- package/dist/topo.js +0 -112
- package/dist/topo.js.map +0 -1
- package/dist/trail.d.ts +0 -96
- package/dist/trail.d.ts.map +0 -1
- package/dist/trail.js +0 -20
- package/dist/trail.js.map +0 -1
- package/dist/type-utils.d.ts +0 -24
- 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 -71
- 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 -176
- 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__/derive.test.ts +0 -260
- package/src/__tests__/errors.test.ts +0 -309
- package/src/__tests__/execute.test.ts +0 -525
- package/src/__tests__/fetch.test.ts +0 -217
- package/src/__tests__/gate.test.ts +0 -125
- package/src/__tests__/guards.test.ts +0 -102
- package/src/__tests__/path-security.test.ts +0 -114
- package/src/__tests__/patterns.test.ts +0 -273
- package/src/__tests__/redaction.test.ts +0 -244
- package/src/__tests__/resilience.test.ts +0 -246
- package/src/__tests__/result.test.ts +0 -155
- package/src/__tests__/run.test.ts +0 -181
- package/src/__tests__/serialization.test.ts +0 -401
- package/src/__tests__/service-config.test.ts +0 -228
- package/src/__tests__/service.test.ts +0 -197
- package/src/__tests__/signal.test.ts +0 -82
- package/src/__tests__/topo-store-read.test.ts +0 -251
- package/src/__tests__/topo-store.test.ts +0 -469
- package/src/__tests__/topo.test.ts +0 -277
- package/src/__tests__/trail-permit.test.ts +0 -60
- package/src/__tests__/trail.test.ts +0 -261
- package/src/__tests__/trails-db.test.ts +0 -191
- package/src/__tests__/type-utils.test.ts +0 -90
- package/src/__tests__/validate-topo.test.ts +0 -489
- package/src/__tests__/validation.test.ts +0 -336
- package/src/__tests__/workspace.test.ts +0 -183
- package/src/event.ts +0 -15
- package/src/gate.ts +0 -44
- package/src/internal/topo-saves.ts +0 -429
- package/src/internal/topo-store-read.ts +0 -473
- package/src/internal/topo-store.ts +0 -1087
- package/src/provision-config.ts +0 -373
- package/src/provision.ts +0 -148
- package/src/topo-store.ts +0 -301
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
Result,
|
|
6
|
-
createTrailContext,
|
|
7
|
-
findDuplicateProvisionId,
|
|
8
|
-
isProvision,
|
|
9
|
-
provision as defineProvision,
|
|
10
|
-
} from '../index.js';
|
|
11
|
-
import type {
|
|
12
|
-
Provision,
|
|
13
|
-
ProvisionContext,
|
|
14
|
-
ProvisionSpec,
|
|
15
|
-
TrailContext,
|
|
16
|
-
} from '../index.js';
|
|
17
|
-
|
|
18
|
-
const provisionCtx: ProvisionContext = {
|
|
19
|
-
cwd: '/tmp/trails',
|
|
20
|
-
env: { DATABASE_URL: 'file::memory:' },
|
|
21
|
-
workspaceRoot: '/tmp',
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
let disposedValue: number | undefined;
|
|
25
|
-
|
|
26
|
-
const counterProvisionSpec: ProvisionSpec<number> = {
|
|
27
|
-
create: () => Result.ok(3),
|
|
28
|
-
description: 'Counter provision',
|
|
29
|
-
dispose: (provision) => {
|
|
30
|
-
disposedValue = provision;
|
|
31
|
-
},
|
|
32
|
-
health: (provision) => Result.ok({ healthy: provision > 0 }),
|
|
33
|
-
meta: { domain: 'data' },
|
|
34
|
-
mock: () => 1,
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const resolvedServiceCtx = (id: string, instance: unknown): TrailContext =>
|
|
38
|
-
createTrailContext({
|
|
39
|
-
abortSignal: new AbortController().signal,
|
|
40
|
-
extensions: { [id]: instance },
|
|
41
|
-
requestId: `${id}-request`,
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe('provision types', () => {
|
|
45
|
-
test('ProvisionContext exposes the stable process-scoped fields', () => {
|
|
46
|
-
expect(provisionCtx.cwd).toBe('/tmp/trails');
|
|
47
|
-
expect(provisionCtx.env?.DATABASE_URL).toBe('file::memory:');
|
|
48
|
-
expect(provisionCtx.workspaceRoot).toBe('/tmp');
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test('ProvisionSpec stores description and meta', () => {
|
|
52
|
-
expect(counterProvisionSpec.description).toBe('Counter provision');
|
|
53
|
-
expect(counterProvisionSpec.meta).toEqual({ domain: 'data' });
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test('ProvisionSpec can reserve a config schema for future composition', () => {
|
|
57
|
-
const config = z.object({ url: z.string().url() });
|
|
58
|
-
const spec: ProvisionSpec<number> = {
|
|
59
|
-
config,
|
|
60
|
-
create: () => Result.ok(1),
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
expect(spec.config).toBe(config);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test('ProvisionSpec create and health callbacks are callable', async () => {
|
|
67
|
-
const result = await counterProvisionSpec.create(provisionCtx);
|
|
68
|
-
expect(result.isOk()).toBe(true);
|
|
69
|
-
expect(result.unwrap()).toBe(3);
|
|
70
|
-
|
|
71
|
-
const health = await counterProvisionSpec.health?.(result.unwrap());
|
|
72
|
-
expect(health?.isOk()).toBe(true);
|
|
73
|
-
expect(health?.unwrap()).toEqual({ healthy: true });
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test('ProvisionSpec mock and dispose callbacks are callable', async () => {
|
|
77
|
-
disposedValue = undefined;
|
|
78
|
-
const result = await counterProvisionSpec.create(provisionCtx);
|
|
79
|
-
expect(result.isOk()).toBe(true);
|
|
80
|
-
|
|
81
|
-
const provision = result.unwrap();
|
|
82
|
-
expect(await counterProvisionSpec.mock?.()).toBe(1);
|
|
83
|
-
|
|
84
|
-
await counterProvisionSpec.dispose?.(provision);
|
|
85
|
-
expect(disposedValue).toBe(3);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test('ProvisionSpec create can be async', async () => {
|
|
89
|
-
const spec: ProvisionSpec<number> = {
|
|
90
|
-
create: async () => {
|
|
91
|
-
await Bun.sleep(0);
|
|
92
|
-
return Result.ok(7);
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const result = await spec.create(provisionCtx);
|
|
97
|
-
expect(result.isOk()).toBe(true);
|
|
98
|
-
expect(result.unwrap()).toBe(7);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test('Provision carries identity alongside the shared spec fields', async () => {
|
|
102
|
-
const counterProvision: Provision<number> = {
|
|
103
|
-
from(ctx) {
|
|
104
|
-
return ctx.provision(this);
|
|
105
|
-
},
|
|
106
|
-
id: 'counter.main',
|
|
107
|
-
kind: 'provision',
|
|
108
|
-
...counterProvisionSpec,
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
expect(counterProvision.kind).toBe('provision');
|
|
112
|
-
expect(counterProvision.id).toBe('counter.main');
|
|
113
|
-
|
|
114
|
-
const created = await counterProvision.create(provisionCtx);
|
|
115
|
-
expect(created.isOk()).toBe(true);
|
|
116
|
-
expect(created.unwrap()).toBe(3);
|
|
117
|
-
expect(await counterProvision.mock?.()).toBe(1);
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
describe('provision()', () => {
|
|
122
|
-
test('returns a frozen provision object with kind and id', () => {
|
|
123
|
-
const counter = defineProvision('counter.main', counterProvisionSpec);
|
|
124
|
-
|
|
125
|
-
expect(counter.kind).toBe('provision');
|
|
126
|
-
expect(counter.id).toBe('counter.main');
|
|
127
|
-
expect(Object.isFrozen(counter)).toBe(true);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test('infers the provision type through from(ctx)', () => {
|
|
131
|
-
const db = defineProvision('db.main', {
|
|
132
|
-
create: () =>
|
|
133
|
-
Result.ok({
|
|
134
|
-
query(sql: string) {
|
|
135
|
-
return sql.length;
|
|
136
|
-
},
|
|
137
|
-
}),
|
|
138
|
-
description: 'Typed database service',
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
const store = {
|
|
142
|
-
query(sql: string) {
|
|
143
|
-
return sql.length;
|
|
144
|
-
},
|
|
145
|
-
};
|
|
146
|
-
const ctx = resolvedServiceCtx('db.main', store);
|
|
147
|
-
|
|
148
|
-
const resolved = db.from(ctx);
|
|
149
|
-
expect(resolved.query('select 1')).toBe(8);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test('from(ctx) throws when the provision is missing', () => {
|
|
153
|
-
const counter = defineProvision('counter.main', counterProvisionSpec);
|
|
154
|
-
const ctx = createTrailContext({
|
|
155
|
-
abortSignal: new AbortController().signal,
|
|
156
|
-
requestId: 'missing-provision',
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
expect(() => counter.from(ctx)).toThrow(
|
|
160
|
-
'Provision "counter.main" not found in trail context'
|
|
161
|
-
);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test('from(ctx) returns undefined when the provision key exists with an undefined value', () => {
|
|
165
|
-
const optional = defineProvision<undefined>('optional.main', {
|
|
166
|
-
create: () => Result.ok<undefined>(),
|
|
167
|
-
});
|
|
168
|
-
const ctx = createTrailContext({
|
|
169
|
-
abortSignal: new AbortController().signal,
|
|
170
|
-
extensions: { [optional.id]: undefined },
|
|
171
|
-
requestId: 'undefined-provision',
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
expect(optional.from(ctx)).toBeUndefined();
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
describe('provision helpers', () => {
|
|
179
|
-
test('isProvision identifies provision definitions', () => {
|
|
180
|
-
const counter = defineProvision('counter.main', counterProvisionSpec);
|
|
181
|
-
|
|
182
|
-
expect(isProvision(counter)).toBe(true);
|
|
183
|
-
expect(isProvision({ id: 'counter.main', kind: 'trail' })).toBe(false);
|
|
184
|
-
expect(isProvision(null)).toBe(false);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
test('findDuplicateProvisionId returns the first repeated ID', () => {
|
|
188
|
-
const first = defineProvision('counter.main', counterProvisionSpec);
|
|
189
|
-
const duplicate = defineProvision('counter.main', counterProvisionSpec);
|
|
190
|
-
const other = defineProvision('counter.secondary', counterProvisionSpec);
|
|
191
|
-
|
|
192
|
-
expect(findDuplicateProvisionId([first, other])).toBeUndefined();
|
|
193
|
-
expect(findDuplicateProvisionId([first, other, duplicate])).toBe(
|
|
194
|
-
'counter.main'
|
|
195
|
-
);
|
|
196
|
-
});
|
|
197
|
-
});
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
|
|
5
|
-
import { signal } from '../signal.js';
|
|
6
|
-
|
|
7
|
-
const payloadSchema = z.object({
|
|
8
|
-
action: z.string(),
|
|
9
|
-
userId: z.string(),
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
const userAction = signal('user.action', {
|
|
13
|
-
description: 'A user performed an action',
|
|
14
|
-
from: ['auth.login', 'auth.signup'],
|
|
15
|
-
meta: { domain: 'auth', priority: 1 },
|
|
16
|
-
payload: payloadSchema,
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
describe('signal() basics', () => {
|
|
20
|
-
test("returns kind 'signal'", () => {
|
|
21
|
-
expect(userAction.kind).toBe('signal');
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test('returns correct id', () => {
|
|
25
|
-
expect(userAction.id).toBe('user.action');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test('preserves payload schema', () => {
|
|
29
|
-
const parsed = userAction.payload.safeParse({
|
|
30
|
-
action: 'click',
|
|
31
|
-
userId: 'u-1',
|
|
32
|
-
});
|
|
33
|
-
expect(parsed.success).toBe(true);
|
|
34
|
-
|
|
35
|
-
const bad = userAction.payload.safeParse({ userId: 42 });
|
|
36
|
-
expect(bad.success).toBe(false);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test('preserves description', () => {
|
|
40
|
-
expect(userAction.description).toBe('A user performed an action');
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('result object is frozen', () => {
|
|
44
|
-
expect(Object.isFrozen(userAction)).toBe(true);
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
describe('signal() from and meta', () => {
|
|
49
|
-
test('preserves from', () => {
|
|
50
|
-
expect(userAction.from).toEqual(['auth.login', 'auth.signup']);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test('from array is frozen', () => {
|
|
54
|
-
expect(Object.isFrozen(userAction.from)).toBe(true);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('preserves meta', () => {
|
|
58
|
-
expect(userAction.meta).toEqual({ domain: 'auth', priority: 1 });
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('optional fields default to undefined', () => {
|
|
62
|
-
const minimal = signal('minimal', {
|
|
63
|
-
payload: z.string(),
|
|
64
|
-
});
|
|
65
|
-
expect(minimal.description).toBeUndefined();
|
|
66
|
-
expect(minimal.from).toBeUndefined();
|
|
67
|
-
expect(minimal.meta).toBeUndefined();
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
describe('signal() single-object overload', () => {
|
|
72
|
-
test('accepts spec with id property', () => {
|
|
73
|
-
const e = signal({
|
|
74
|
-
from: ['entity.add', 'entity.delete'],
|
|
75
|
-
id: 'entity.updated',
|
|
76
|
-
payload: z.object({ entityId: z.string() }),
|
|
77
|
-
});
|
|
78
|
-
expect(e.id).toBe('entity.updated');
|
|
79
|
-
expect(e.kind).toBe('signal');
|
|
80
|
-
expect(e.from).toEqual(['entity.add', 'entity.delete']);
|
|
81
|
-
});
|
|
82
|
-
});
|
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, test } from 'bun:test';
|
|
2
|
-
import { mkdtempSync, rmSync } from 'node:fs';
|
|
3
|
-
import { tmpdir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
|
|
6
|
-
import { z } from 'zod';
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
createMockTopoStore,
|
|
10
|
-
createTopoStore,
|
|
11
|
-
provision,
|
|
12
|
-
Result,
|
|
13
|
-
signal,
|
|
14
|
-
topo,
|
|
15
|
-
topoStore,
|
|
16
|
-
trail,
|
|
17
|
-
} from '../index.js';
|
|
18
|
-
import { pinTopoSave } from '../internal/topo-saves.js';
|
|
19
|
-
import type { TopoSaveRecord } from '../internal/topo-saves.js';
|
|
20
|
-
import { persistEstablishedTopoSave } from '../internal/topo-store.js';
|
|
21
|
-
import { openWriteTrailsDb } from '../internal/trails-db.js';
|
|
22
|
-
|
|
23
|
-
const noop = () => Result.ok({ ok: true });
|
|
24
|
-
|
|
25
|
-
const expectOk = async <T>(
|
|
26
|
-
result: Promise<Result<T, Error>> | Result<T, Error>
|
|
27
|
-
): Promise<T> => {
|
|
28
|
-
const resolved = await result;
|
|
29
|
-
if (resolved.isErr()) {
|
|
30
|
-
throw resolved.error;
|
|
31
|
-
}
|
|
32
|
-
return resolved.value;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const requireValue = <T>(value: T | undefined, message: string): T => {
|
|
36
|
-
if (value === undefined) {
|
|
37
|
-
throw new Error(message);
|
|
38
|
-
}
|
|
39
|
-
return value;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const exampleApp = () => {
|
|
43
|
-
const dbMain = provision('db.main', {
|
|
44
|
-
create: () => Result.ok({ source: 'factory' }),
|
|
45
|
-
description: 'Primary database',
|
|
46
|
-
health: () => Result.ok({ ok: true }),
|
|
47
|
-
mock: () => ({ source: 'mock' }),
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const entityAdded = signal('entity.added', {
|
|
51
|
-
description: 'An entity was added',
|
|
52
|
-
from: ['entity.add'],
|
|
53
|
-
payload: z.object({ id: z.string() }),
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
const entityAdd = trail('entity.add', {
|
|
57
|
-
blaze: (input: { readonly name: string }) =>
|
|
58
|
-
Result.ok({ id: input.name.toLowerCase(), ok: true }),
|
|
59
|
-
description: 'Add a new entity',
|
|
60
|
-
examples: [
|
|
61
|
-
{
|
|
62
|
-
expected: { id: 'ada', ok: true },
|
|
63
|
-
input: { name: 'Ada' },
|
|
64
|
-
name: 'Add Ada',
|
|
65
|
-
},
|
|
66
|
-
],
|
|
67
|
-
input: z.object({ name: z.string() }),
|
|
68
|
-
output: z.object({ id: z.string(), ok: z.boolean() }),
|
|
69
|
-
provisions: [dbMain],
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
const entityList = trail('entity.list', {
|
|
73
|
-
blaze: noop,
|
|
74
|
-
crosses: ['entity.add'],
|
|
75
|
-
description: 'List entities',
|
|
76
|
-
detours: {
|
|
77
|
-
ConflictError: ['entity.add'],
|
|
78
|
-
},
|
|
79
|
-
idempotent: true,
|
|
80
|
-
input: z.object({}),
|
|
81
|
-
intent: 'read',
|
|
82
|
-
output: z.object({ ok: z.boolean() }),
|
|
83
|
-
provisions: [dbMain],
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
return topo('projection-app', {
|
|
87
|
-
dbMain,
|
|
88
|
-
entityAdd,
|
|
89
|
-
entityAdded,
|
|
90
|
-
entityList,
|
|
91
|
-
});
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
describe('read-only topo store', () => {
|
|
95
|
-
let tmpRoot: string | undefined;
|
|
96
|
-
|
|
97
|
-
afterEach(() => {
|
|
98
|
-
if (tmpRoot) {
|
|
99
|
-
rmSync(tmpRoot, { force: true, recursive: true });
|
|
100
|
-
tmpRoot = undefined;
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
const makeRoot = (): string => {
|
|
105
|
-
tmpRoot = mkdtempSync(join(tmpdir(), 'topo-store-read-'));
|
|
106
|
-
return tmpRoot;
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const seedStore = (): {
|
|
110
|
-
readonly rootDir: string;
|
|
111
|
-
readonly save: TopoSaveRecord;
|
|
112
|
-
} => {
|
|
113
|
-
const rootDir = makeRoot();
|
|
114
|
-
const db = openWriteTrailsDb({ rootDir });
|
|
115
|
-
|
|
116
|
-
try {
|
|
117
|
-
const result = persistEstablishedTopoSave(db, exampleApp(), {
|
|
118
|
-
createdAt: '2026-04-03T14:00:00.000Z',
|
|
119
|
-
gitSha: 'abc123',
|
|
120
|
-
});
|
|
121
|
-
if (result.isErr()) {
|
|
122
|
-
throw result.error;
|
|
123
|
-
}
|
|
124
|
-
const save = result.value;
|
|
125
|
-
pinTopoSave(db, { name: 'baseline', saveId: save.id });
|
|
126
|
-
return { rootDir, save };
|
|
127
|
-
} finally {
|
|
128
|
-
db.close();
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
test('lists save-scoped trails and provisions through typed accessors', () => {
|
|
133
|
-
const { rootDir, save } = seedStore();
|
|
134
|
-
const store = createTopoStore({ rootDir });
|
|
135
|
-
|
|
136
|
-
expect(store.saves.latest()?.id).toBe(save.id);
|
|
137
|
-
expect(store.pins.get('baseline')?.saveId).toBe(save.id);
|
|
138
|
-
|
|
139
|
-
expect(store.trails.list({ save: { saveId: save.id } })).toEqual([
|
|
140
|
-
expect.objectContaining({
|
|
141
|
-
description: 'Add a new entity',
|
|
142
|
-
exampleCount: 1,
|
|
143
|
-
hasOutput: true,
|
|
144
|
-
id: 'entity.add',
|
|
145
|
-
intent: 'write',
|
|
146
|
-
}),
|
|
147
|
-
expect.objectContaining({
|
|
148
|
-
description: 'List entities',
|
|
149
|
-
exampleCount: 0,
|
|
150
|
-
id: 'entity.list',
|
|
151
|
-
intent: 'read',
|
|
152
|
-
safety: 'read',
|
|
153
|
-
}),
|
|
154
|
-
]);
|
|
155
|
-
|
|
156
|
-
expect(store.provisions.list({ save: { saveId: save.id } })).toEqual([
|
|
157
|
-
expect.objectContaining({
|
|
158
|
-
description: 'Primary database',
|
|
159
|
-
health: 'available',
|
|
160
|
-
id: 'db.main',
|
|
161
|
-
usedBy: ['entity.add', 'entity.list'],
|
|
162
|
-
}),
|
|
163
|
-
]);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
test('filters trails by intent', () => {
|
|
167
|
-
const { rootDir, save } = seedStore();
|
|
168
|
-
const store = createTopoStore({ rootDir });
|
|
169
|
-
|
|
170
|
-
const writeTrails = store.trails.list({
|
|
171
|
-
intent: 'write',
|
|
172
|
-
save: { saveId: save.id },
|
|
173
|
-
});
|
|
174
|
-
expect(writeTrails).toHaveLength(1);
|
|
175
|
-
expect(writeTrails[0]?.id).toBe('entity.add');
|
|
176
|
-
|
|
177
|
-
const readTrails = store.trails.list({
|
|
178
|
-
intent: 'read',
|
|
179
|
-
save: { saveId: save.id },
|
|
180
|
-
});
|
|
181
|
-
expect(readTrails).toHaveLength(1);
|
|
182
|
-
expect(readTrails[0]?.id).toBe('entity.list');
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
test('returns detailed trail and export views, plus a query escape hatch', () => {
|
|
186
|
-
const { rootDir, save } = seedStore();
|
|
187
|
-
const store = createTopoStore({ rootDir });
|
|
188
|
-
|
|
189
|
-
const detail = store.trails.get('entity.list', {
|
|
190
|
-
save: { saveId: save.id },
|
|
191
|
-
});
|
|
192
|
-
expect(detail).toEqual(
|
|
193
|
-
expect.objectContaining({
|
|
194
|
-
crosses: ['entity.add'],
|
|
195
|
-
detours: { ConflictError: ['entity.add'] },
|
|
196
|
-
id: 'entity.list',
|
|
197
|
-
provisions: ['db.main'],
|
|
198
|
-
})
|
|
199
|
-
);
|
|
200
|
-
expect(detail?.examples).toEqual([]);
|
|
201
|
-
|
|
202
|
-
const exported = store.exports.get({ pin: 'baseline' });
|
|
203
|
-
expect(exported?.save.id).toBe(save.id);
|
|
204
|
-
expect(exported?.trailheadHash).toHaveLength(64);
|
|
205
|
-
|
|
206
|
-
const rows = store.query<{ id: string }>(
|
|
207
|
-
'SELECT id FROM topo_trails WHERE save_id = ? ORDER BY id ASC',
|
|
208
|
-
[save.id]
|
|
209
|
-
);
|
|
210
|
-
expect(rows).toEqual([{ id: 'entity.add' }, { id: 'entity.list' }]);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
test('fails loudly when no saved topo state exists', () => {
|
|
214
|
-
const rootDir = makeRoot();
|
|
215
|
-
const store = createTopoStore({ rootDir });
|
|
216
|
-
|
|
217
|
-
expect(() => store.saves.latest()).toThrow('No saved topo state found');
|
|
218
|
-
expect(() => store.trails.list()).toThrow('No saved topo state found');
|
|
219
|
-
expect(() => store.exports.get()).toThrow('No saved topo state found');
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
test('exposes a provision factory and mock for topoStore', async () => {
|
|
223
|
-
const { rootDir } = seedStore();
|
|
224
|
-
|
|
225
|
-
const created = await expectOk(
|
|
226
|
-
topoStore.create({
|
|
227
|
-
config: undefined,
|
|
228
|
-
cwd: rootDir,
|
|
229
|
-
env: {},
|
|
230
|
-
workspaceRoot: rootDir,
|
|
231
|
-
})
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
expect(created.saves.latest()).toBeDefined();
|
|
235
|
-
expect(
|
|
236
|
-
created.query<{ count: number }>(
|
|
237
|
-
'SELECT COUNT(*) as count FROM topo_saves'
|
|
238
|
-
)[0]?.count
|
|
239
|
-
).toBe(1);
|
|
240
|
-
|
|
241
|
-
expect(topoStore.mock).toBeDefined();
|
|
242
|
-
const mock = await requireValue(
|
|
243
|
-
topoStore.mock,
|
|
244
|
-
'Expected topoStore to define a mock factory'
|
|
245
|
-
)();
|
|
246
|
-
expect(mock).toBeDefined();
|
|
247
|
-
expect(() =>
|
|
248
|
-
createMockTopoStore().query('SELECT id FROM topo_trails')
|
|
249
|
-
).toThrow('Mock topoStore.query() is unsupported');
|
|
250
|
-
});
|
|
251
|
-
});
|