@ontrails/trails 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 +208 -0
- package/README.md +27 -0
- package/package.json +19 -8
- package/src/app.ts +17 -7
- package/src/clack.ts +1 -1
- package/src/cli.ts +304 -10
- package/src/completions.ts +240 -0
- package/src/load-app-mirror.ts +160 -0
- package/src/local-state-io.ts +153 -0
- package/src/project-writes.ts +320 -0
- package/src/run-collision.ts +125 -0
- package/src/run-completions-install.ts +179 -0
- package/src/run-example.ts +149 -0
- package/src/run-examples.ts +148 -0
- package/src/run-quiet.ts +75 -0
- package/src/run-trace.ts +273 -0
- package/src/run-warden.ts +39 -0
- package/src/run-watch.ts +432 -0
- package/src/scaffold-versions.generated.ts +12 -0
- package/src/trails/add-surface.ts +172 -0
- package/src/trails/add-trail.ts +73 -27
- package/src/trails/add-verify.ts +68 -23
- package/src/trails/completions-complete.ts +165 -0
- package/src/trails/completions.ts +47 -0
- package/src/trails/create-scaffold.ts +101 -35
- package/src/trails/create.ts +87 -74
- package/src/trails/dev-clean.ts +31 -22
- package/src/trails/dev-reset.ts +9 -3
- package/src/trails/dev-stats.ts +28 -20
- package/src/trails/dev-support.ts +109 -95
- package/src/trails/draft-promote.ts +351 -107
- package/src/trails/guide.ts +55 -38
- package/src/trails/load-app.ts +712 -38
- package/src/trails/root-dir.ts +21 -0
- package/src/trails/run-example.ts +482 -0
- package/src/trails/run-examples.ts +141 -0
- package/src/trails/run.ts +403 -0
- package/src/trails/survey.ts +517 -186
- package/src/trails/topo-activation.ts +385 -0
- package/src/trails/topo-compile.ts +55 -0
- package/src/trails/topo-history.ts +14 -11
- package/src/trails/topo-output-schemas.ts +175 -0
- package/src/trails/topo-pin.ts +25 -16
- package/src/trails/topo-read-support.ts +178 -238
- package/src/trails/topo-reports.ts +445 -63
- package/src/trails/topo-store-support.ts +67 -35
- package/src/trails/topo-support.ts +93 -147
- package/src/trails/topo-unpin.ts +17 -7
- package/src/trails/topo-verify.ts +19 -10
- package/src/trails/topo.ts +64 -31
- package/src/trails/warden-guide.ts +121 -0
- package/src/trails/warden.ts +137 -47
- package/src/versions.ts +28 -0
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/__tests__/examples.test.ts +0 -20
- package/dist/bin/trails.d.ts +0 -3
- package/dist/bin/trails.d.ts.map +0 -1
- package/dist/bin/trails.js +0 -4
- package/dist/bin/trails.js.map +0 -1
- package/dist/src/app.d.ts +0 -2
- package/dist/src/app.d.ts.map +0 -1
- package/dist/src/app.js +0 -22
- package/dist/src/app.js.map +0 -1
- package/dist/src/clack.d.ts +0 -9
- package/dist/src/clack.d.ts.map +0 -1
- package/dist/src/clack.js +0 -84
- package/dist/src/clack.js.map +0 -1
- package/dist/src/cli.d.ts +0 -2
- package/dist/src/cli.d.ts.map +0 -1
- package/dist/src/cli.js +0 -13
- package/dist/src/cli.js.map +0 -1
- package/dist/src/trails/add-surface.d.ts +0 -13
- package/dist/src/trails/add-surface.d.ts.map +0 -1
- package/dist/src/trails/add-surface.js +0 -88
- package/dist/src/trails/add-surface.js.map +0 -1
- package/dist/src/trails/add-trail.d.ts +0 -10
- package/dist/src/trails/add-trail.d.ts.map +0 -1
- package/dist/src/trails/add-trail.js +0 -77
- package/dist/src/trails/add-trail.js.map +0 -1
- package/dist/src/trails/add-trailhead.d.ts +0 -13
- package/dist/src/trails/add-trailhead.d.ts.map +0 -1
- package/dist/src/trails/add-trailhead.js +0 -88
- package/dist/src/trails/add-trailhead.js.map +0 -1
- package/dist/src/trails/add-verify.d.ts +0 -10
- package/dist/src/trails/add-verify.d.ts.map +0 -1
- package/dist/src/trails/add-verify.js +0 -67
- package/dist/src/trails/add-verify.js.map +0 -1
- package/dist/src/trails/create-scaffold.d.ts +0 -15
- package/dist/src/trails/create-scaffold.d.ts.map +0 -1
- package/dist/src/trails/create-scaffold.js +0 -288
- package/dist/src/trails/create-scaffold.js.map +0 -1
- package/dist/src/trails/create.d.ts +0 -22
- package/dist/src/trails/create.d.ts.map +0 -1
- package/dist/src/trails/create.js +0 -121
- package/dist/src/trails/create.js.map +0 -1
- package/dist/src/trails/dev-clean.d.ts +0 -9
- package/dist/src/trails/dev-clean.d.ts.map +0 -1
- package/dist/src/trails/dev-clean.js +0 -65
- package/dist/src/trails/dev-clean.js.map +0 -1
- package/dist/src/trails/dev-reset.d.ts +0 -6
- package/dist/src/trails/dev-reset.d.ts.map +0 -1
- package/dist/src/trails/dev-reset.js +0 -38
- package/dist/src/trails/dev-reset.js.map +0 -1
- package/dist/src/trails/dev-stats.d.ts +0 -7
- package/dist/src/trails/dev-stats.d.ts.map +0 -1
- package/dist/src/trails/dev-stats.js +0 -61
- package/dist/src/trails/dev-stats.js.map +0 -1
- package/dist/src/trails/dev-support.d.ts +0 -64
- package/dist/src/trails/dev-support.d.ts.map +0 -1
- package/dist/src/trails/dev-support.js +0 -178
- package/dist/src/trails/dev-support.js.map +0 -1
- package/dist/src/trails/draft-promote.d.ts +0 -18
- package/dist/src/trails/draft-promote.d.ts.map +0 -1
- package/dist/src/trails/draft-promote.js +0 -386
- package/dist/src/trails/draft-promote.js.map +0 -1
- package/dist/src/trails/guide.d.ts +0 -21
- package/dist/src/trails/guide.d.ts.map +0 -1
- package/dist/src/trails/guide.js +0 -64
- package/dist/src/trails/guide.js.map +0 -1
- package/dist/src/trails/load-app.d.ts +0 -6
- package/dist/src/trails/load-app.d.ts.map +0 -1
- package/dist/src/trails/load-app.js +0 -67
- package/dist/src/trails/load-app.js.map +0 -1
- package/dist/src/trails/project.d.ts +0 -8
- package/dist/src/trails/project.d.ts.map +0 -1
- package/dist/src/trails/project.js +0 -54
- package/dist/src/trails/project.js.map +0 -1
- package/dist/src/trails/survey.d.ts +0 -18
- package/dist/src/trails/survey.d.ts.map +0 -1
- package/dist/src/trails/survey.js +0 -212
- package/dist/src/trails/survey.js.map +0 -1
- package/dist/src/trails/topo-constants.d.ts +0 -3
- package/dist/src/trails/topo-constants.d.ts.map +0 -1
- package/dist/src/trails/topo-constants.js +0 -3
- package/dist/src/trails/topo-constants.js.map +0 -1
- package/dist/src/trails/topo-export.d.ts +0 -18
- package/dist/src/trails/topo-export.d.ts.map +0 -1
- package/dist/src/trails/topo-export.js +0 -34
- package/dist/src/trails/topo-export.js.map +0 -1
- package/dist/src/trails/topo-history.d.ts +0 -24
- package/dist/src/trails/topo-history.d.ts.map +0 -1
- package/dist/src/trails/topo-history.js +0 -33
- package/dist/src/trails/topo-history.js.map +0 -1
- package/dist/src/trails/topo-pin.d.ts +0 -21
- package/dist/src/trails/topo-pin.d.ts.map +0 -1
- package/dist/src/trails/topo-pin.js +0 -35
- package/dist/src/trails/topo-pin.js.map +0 -1
- package/dist/src/trails/topo-read-support.d.ts +0 -54
- package/dist/src/trails/topo-read-support.d.ts.map +0 -1
- package/dist/src/trails/topo-read-support.js +0 -178
- package/dist/src/trails/topo-read-support.js.map +0 -1
- package/dist/src/trails/topo-reports.d.ts +0 -50
- package/dist/src/trails/topo-reports.d.ts.map +0 -1
- package/dist/src/trails/topo-reports.js +0 -122
- package/dist/src/trails/topo-reports.js.map +0 -1
- package/dist/src/trails/topo-show.d.ts +0 -23
- package/dist/src/trails/topo-show.d.ts.map +0 -1
- package/dist/src/trails/topo-show.js +0 -53
- package/dist/src/trails/topo-show.js.map +0 -1
- package/dist/src/trails/topo-store-support.d.ts +0 -13
- package/dist/src/trails/topo-store-support.d.ts.map +0 -1
- package/dist/src/trails/topo-store-support.js +0 -55
- package/dist/src/trails/topo-store-support.js.map +0 -1
- package/dist/src/trails/topo-support.d.ts +0 -87
- package/dist/src/trails/topo-support.d.ts.map +0 -1
- package/dist/src/trails/topo-support.js +0 -165
- package/dist/src/trails/topo-support.js.map +0 -1
- package/dist/src/trails/topo-unpin.d.ts +0 -15
- package/dist/src/trails/topo-unpin.d.ts.map +0 -1
- package/dist/src/trails/topo-unpin.js +0 -39
- package/dist/src/trails/topo-unpin.js.map +0 -1
- package/dist/src/trails/topo-verify.d.ts +0 -5
- package/dist/src/trails/topo-verify.d.ts.map +0 -1
- package/dist/src/trails/topo-verify.js +0 -28
- package/dist/src/trails/topo-verify.js.map +0 -1
- package/dist/src/trails/topo.d.ts +0 -5
- package/dist/src/trails/topo.d.ts.map +0 -1
- package/dist/src/trails/topo.js +0 -67
- package/dist/src/trails/topo.js.map +0 -1
- package/dist/src/trails/warden.d.ts +0 -19
- package/dist/src/trails/warden.d.ts.map +0 -1
- package/dist/src/trails/warden.js +0 -89
- package/dist/src/trails/warden.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/__tests__/create.test.ts +0 -351
- package/src/__tests__/draft-promote.test.ts +0 -144
- package/src/__tests__/guide.test.ts +0 -91
- package/src/__tests__/load-app.test.ts +0 -58
- package/src/__tests__/survey.test.ts +0 -301
- package/src/__tests__/topo-dev.test.ts +0 -424
- package/src/__tests__/warden.test.ts +0 -74
- package/src/trails/add-trailhead.ts +0 -121
- package/src/trails/topo-export.ts +0 -39
- package/src/trails/topo-show.ts +0 -58
- package/tsconfig.json +0 -9
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ActivationEntry,
|
|
3
|
+
ActivationSource,
|
|
4
|
+
ActivationSourceProjection,
|
|
5
|
+
AnyTrail,
|
|
6
|
+
Topo,
|
|
7
|
+
} from '@ontrails/core';
|
|
8
|
+
import {
|
|
9
|
+
activationSourceKey,
|
|
10
|
+
projectActivationSourceDeclaration,
|
|
11
|
+
} from '@ontrails/core';
|
|
12
|
+
|
|
13
|
+
export interface ActivationChainReport {
|
|
14
|
+
readonly consumer: string;
|
|
15
|
+
readonly producer: string;
|
|
16
|
+
readonly signal: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type JsonSchemaReport = Readonly<Record<string, unknown>>;
|
|
20
|
+
|
|
21
|
+
export interface ActivationSourceReport extends ActivationSourceProjection {
|
|
22
|
+
readonly cron?: string | undefined;
|
|
23
|
+
readonly hasParse?: true | undefined;
|
|
24
|
+
readonly hasPayloadSchema?: true | undefined;
|
|
25
|
+
readonly hasVerify?: true | undefined;
|
|
26
|
+
readonly id: string;
|
|
27
|
+
readonly input?: unknown;
|
|
28
|
+
readonly inputSchema?: JsonSchemaReport | undefined;
|
|
29
|
+
readonly kind: string;
|
|
30
|
+
readonly key: string;
|
|
31
|
+
readonly meta?: Readonly<Record<string, unknown>> | undefined;
|
|
32
|
+
readonly method?: string | undefined;
|
|
33
|
+
readonly parseOutputSchema?: JsonSchemaReport | undefined;
|
|
34
|
+
readonly path?: string | undefined;
|
|
35
|
+
readonly payloadSchema?: JsonSchemaReport | undefined;
|
|
36
|
+
readonly timezone?: string | undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ActivationEdgeReport extends Readonly<
|
|
40
|
+
Record<string, unknown>
|
|
41
|
+
> {
|
|
42
|
+
readonly hasWhere: boolean;
|
|
43
|
+
readonly sourceId: string;
|
|
44
|
+
readonly sourceKey: string;
|
|
45
|
+
readonly sourceKind: string;
|
|
46
|
+
readonly trailId: string;
|
|
47
|
+
readonly where?: { readonly predicate: true } | undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface SignalActivationRelations {
|
|
51
|
+
readonly consumers: readonly string[];
|
|
52
|
+
readonly producers: readonly string[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface TrailActivationReport {
|
|
56
|
+
readonly activatedBy: readonly string[];
|
|
57
|
+
readonly activates: readonly string[];
|
|
58
|
+
readonly chains: readonly ActivationChainReport[];
|
|
59
|
+
readonly edges: readonly ActivationEdgeReport[];
|
|
60
|
+
readonly fires: readonly string[];
|
|
61
|
+
readonly on: readonly string[];
|
|
62
|
+
readonly sources: readonly ActivationSourceReport[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface ActivationOverviewReport {
|
|
66
|
+
readonly chainCount: number;
|
|
67
|
+
readonly chains: readonly ActivationChainReport[];
|
|
68
|
+
readonly edgeCount: number;
|
|
69
|
+
readonly edges: readonly ActivationEdgeReport[];
|
|
70
|
+
readonly signalIds: readonly string[];
|
|
71
|
+
readonly sourceCount: number;
|
|
72
|
+
readonly sourceKeys: readonly string[];
|
|
73
|
+
readonly trailIds: readonly string[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface ActivationGraphReport {
|
|
77
|
+
readonly overview: ActivationOverviewReport;
|
|
78
|
+
readonly signals: ReadonlyMap<string, SignalActivationRelations>;
|
|
79
|
+
readonly sources: ReadonlyMap<string, ActivationSourceReport>;
|
|
80
|
+
readonly trails: ReadonlyMap<string, TrailActivationReport>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface MutableSignalRelations {
|
|
84
|
+
readonly consumers: Set<string>;
|
|
85
|
+
readonly producers: Set<string>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface MutableTrailActivation {
|
|
89
|
+
readonly activatedBy: Set<string>;
|
|
90
|
+
readonly activates: Set<string>;
|
|
91
|
+
readonly chains: ActivationChainReport[];
|
|
92
|
+
readonly fires: readonly string[];
|
|
93
|
+
readonly on: readonly string[];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const canonicalLeaf = (value: unknown): unknown => {
|
|
97
|
+
switch (typeof value) {
|
|
98
|
+
case 'bigint': {
|
|
99
|
+
return value.toString();
|
|
100
|
+
}
|
|
101
|
+
case 'function': {
|
|
102
|
+
return `[Function:${value.name || 'anonymous'}]`;
|
|
103
|
+
}
|
|
104
|
+
case 'symbol': {
|
|
105
|
+
return `[Symbol:${value.description ?? ''}]`;
|
|
106
|
+
}
|
|
107
|
+
case 'undefined': {
|
|
108
|
+
return '[Undefined]';
|
|
109
|
+
}
|
|
110
|
+
default: {
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const canonicalize = (value: unknown): unknown => {
|
|
117
|
+
if (Array.isArray(value)) {
|
|
118
|
+
return value.map(canonicalize);
|
|
119
|
+
}
|
|
120
|
+
if (value instanceof Date) {
|
|
121
|
+
return value.toISOString();
|
|
122
|
+
}
|
|
123
|
+
if (value instanceof RegExp) {
|
|
124
|
+
return value.toString();
|
|
125
|
+
}
|
|
126
|
+
if (value !== null && typeof value === 'object') {
|
|
127
|
+
const sorted: Record<string, unknown> = {};
|
|
128
|
+
for (const key of Object.keys(value).toSorted()) {
|
|
129
|
+
const next = (value as Record<string, unknown>)[key];
|
|
130
|
+
sorted[key] = next === undefined ? '[Undefined]' : canonicalize(next);
|
|
131
|
+
}
|
|
132
|
+
return sorted;
|
|
133
|
+
}
|
|
134
|
+
return canonicalLeaf(value);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const sortKeys = <T extends Record<string, unknown>>(value: T): T => {
|
|
138
|
+
const sorted: Record<string, unknown> = {};
|
|
139
|
+
for (const key of Object.keys(value).toSorted()) {
|
|
140
|
+
sorted[key] = value[key];
|
|
141
|
+
}
|
|
142
|
+
return sorted as T;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const compareChains = (
|
|
146
|
+
a: ActivationChainReport,
|
|
147
|
+
b: ActivationChainReport
|
|
148
|
+
): number =>
|
|
149
|
+
a.producer.localeCompare(b.producer) ||
|
|
150
|
+
a.signal.localeCompare(b.signal) ||
|
|
151
|
+
a.consumer.localeCompare(b.consumer);
|
|
152
|
+
|
|
153
|
+
const sortedUnique = (values: Iterable<string>): readonly string[] =>
|
|
154
|
+
[...new Set(values)].toSorted();
|
|
155
|
+
|
|
156
|
+
const projectActivationSource = (
|
|
157
|
+
source: ActivationSource
|
|
158
|
+
): ActivationSourceReport =>
|
|
159
|
+
projectActivationSourceDeclaration(source) as ActivationSourceReport;
|
|
160
|
+
|
|
161
|
+
const projectActivationEdge = (
|
|
162
|
+
trailId: string,
|
|
163
|
+
activation: ActivationEntry
|
|
164
|
+
): ActivationEdgeReport => {
|
|
165
|
+
const sourceKey = activationSourceKey(activation.source);
|
|
166
|
+
const edge: Record<string, unknown> = {
|
|
167
|
+
hasWhere: activation.where !== undefined,
|
|
168
|
+
sourceId: activation.source.id,
|
|
169
|
+
sourceKey,
|
|
170
|
+
sourceKind: activation.source.kind,
|
|
171
|
+
trailId,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
if (activation.meta !== undefined) {
|
|
175
|
+
edge['meta'] = canonicalize(activation.meta);
|
|
176
|
+
}
|
|
177
|
+
if (activation.where !== undefined) {
|
|
178
|
+
edge['where'] = { predicate: true };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return sortKeys(edge) as ActivationEdgeReport;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const collectActivationSourceCatalog = (
|
|
185
|
+
trails: readonly AnyTrail[]
|
|
186
|
+
): readonly ActivationSourceReport[] => {
|
|
187
|
+
const sources = new Map<string, ActivationSourceReport>();
|
|
188
|
+
for (const trail of trails) {
|
|
189
|
+
for (const activation of trail.activationSources) {
|
|
190
|
+
const projected = projectActivationSource(activation.source);
|
|
191
|
+
sources.set(projected.key, projected);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return [...sources.values()].toSorted((a, b) => a.key.localeCompare(b.key));
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const collectActivationEdges = (
|
|
198
|
+
trails: readonly AnyTrail[]
|
|
199
|
+
): readonly ActivationEdgeReport[] => {
|
|
200
|
+
const edges = new Map<string, ActivationEdgeReport>();
|
|
201
|
+
for (const trail of trails) {
|
|
202
|
+
for (const activation of trail.activationSources) {
|
|
203
|
+
const edge = projectActivationEdge(trail.id, activation);
|
|
204
|
+
const key = `${edge.sourceKey}\0${edge.trailId}`;
|
|
205
|
+
const previous = edges.get(key);
|
|
206
|
+
edges.set(
|
|
207
|
+
key,
|
|
208
|
+
previous === undefined || (!previous.hasWhere && edge.hasWhere)
|
|
209
|
+
? edge
|
|
210
|
+
: previous
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return [...edges.values()].toSorted(
|
|
216
|
+
(a, b) =>
|
|
217
|
+
a.sourceKey.localeCompare(b.sourceKey) ||
|
|
218
|
+
a.trailId.localeCompare(b.trailId)
|
|
219
|
+
);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const getSignalRelations = (
|
|
223
|
+
relations: Map<string, MutableSignalRelations>,
|
|
224
|
+
signalId: string
|
|
225
|
+
): MutableSignalRelations => {
|
|
226
|
+
const existing = relations.get(signalId);
|
|
227
|
+
if (existing !== undefined) {
|
|
228
|
+
return existing;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const created = {
|
|
232
|
+
consumers: new Set<string>(),
|
|
233
|
+
producers: new Set<string>(),
|
|
234
|
+
};
|
|
235
|
+
relations.set(signalId, created);
|
|
236
|
+
return created;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const getTrailActivation = (
|
|
240
|
+
trails: Map<string, MutableTrailActivation>,
|
|
241
|
+
trail: AnyTrail
|
|
242
|
+
): MutableTrailActivation => {
|
|
243
|
+
const existing = trails.get(trail.id);
|
|
244
|
+
if (existing !== undefined) {
|
|
245
|
+
return existing;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const created = {
|
|
249
|
+
activatedBy: new Set<string>(),
|
|
250
|
+
activates: new Set<string>(),
|
|
251
|
+
chains: [],
|
|
252
|
+
fires: sortedUnique(trail.fires),
|
|
253
|
+
on: sortedUnique(trail.on),
|
|
254
|
+
};
|
|
255
|
+
trails.set(trail.id, created);
|
|
256
|
+
return created;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
export const deriveDeclaredTrailActivation = (
|
|
260
|
+
trail: AnyTrail
|
|
261
|
+
): TrailActivationReport => {
|
|
262
|
+
const sources = collectActivationSourceCatalog([trail]);
|
|
263
|
+
return {
|
|
264
|
+
activatedBy: [],
|
|
265
|
+
activates: [],
|
|
266
|
+
chains: [],
|
|
267
|
+
edges: collectActivationEdges([trail]),
|
|
268
|
+
fires: sortedUnique(trail.fires),
|
|
269
|
+
on: sortedUnique(trail.on),
|
|
270
|
+
sources,
|
|
271
|
+
};
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
export const deriveSignalActivationRelations = (
|
|
275
|
+
app: Topo,
|
|
276
|
+
signalId: string
|
|
277
|
+
): SignalActivationRelations => {
|
|
278
|
+
const consumers: string[] = [];
|
|
279
|
+
const producers: string[] = [];
|
|
280
|
+
|
|
281
|
+
for (const trail of app.list()) {
|
|
282
|
+
if (trail.fires.includes(signalId)) {
|
|
283
|
+
producers.push(trail.id);
|
|
284
|
+
}
|
|
285
|
+
if (trail.on.includes(signalId)) {
|
|
286
|
+
consumers.push(trail.id);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
consumers: sortedUnique(consumers),
|
|
292
|
+
producers: sortedUnique(producers),
|
|
293
|
+
};
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
export const deriveActivationGraph = (app: Topo): ActivationGraphReport => {
|
|
297
|
+
const signalRelations = new Map<string, MutableSignalRelations>();
|
|
298
|
+
const trailActivations = new Map<string, MutableTrailActivation>();
|
|
299
|
+
const trails = app.list();
|
|
300
|
+
const sources = collectActivationSourceCatalog(trails);
|
|
301
|
+
const edges = collectActivationEdges(trails);
|
|
302
|
+
|
|
303
|
+
for (const signal of app.listSignals()) {
|
|
304
|
+
getSignalRelations(signalRelations, signal.id);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
for (const trail of trails) {
|
|
308
|
+
const trailActivation = getTrailActivation(trailActivations, trail);
|
|
309
|
+
for (const signalId of trailActivation.fires) {
|
|
310
|
+
getSignalRelations(signalRelations, signalId).producers.add(trail.id);
|
|
311
|
+
}
|
|
312
|
+
for (const signalId of trailActivation.on) {
|
|
313
|
+
getSignalRelations(signalRelations, signalId).consumers.add(trail.id);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const chains: ActivationChainReport[] = [];
|
|
318
|
+
for (const [signal, related] of signalRelations) {
|
|
319
|
+
for (const producer of related.producers) {
|
|
320
|
+
const producerActivation = trailActivations.get(producer);
|
|
321
|
+
for (const consumer of related.consumers) {
|
|
322
|
+
const chain = { consumer, producer, signal };
|
|
323
|
+
chains.push(chain);
|
|
324
|
+
producerActivation?.activates.add(consumer);
|
|
325
|
+
producerActivation?.chains.push(chain);
|
|
326
|
+
const consumerActivation = trailActivations.get(consumer);
|
|
327
|
+
consumerActivation?.activatedBy.add(producer);
|
|
328
|
+
if (consumerActivation !== producerActivation) {
|
|
329
|
+
consumerActivation?.chains.push(chain);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
chains.sort(compareChains);
|
|
336
|
+
const activeTrailIds = sortedUnique([
|
|
337
|
+
...[...trailActivations.entries()].flatMap(([trailId, trail]) =>
|
|
338
|
+
trail.fires.length > 0 || trail.on.length > 0
|
|
339
|
+
? [trailId, ...trail.activatedBy, ...trail.activates]
|
|
340
|
+
: []
|
|
341
|
+
),
|
|
342
|
+
...edges.map((edge) => edge.trailId),
|
|
343
|
+
]);
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
overview: {
|
|
347
|
+
chainCount: chains.length,
|
|
348
|
+
chains,
|
|
349
|
+
edgeCount: edges.length,
|
|
350
|
+
edges,
|
|
351
|
+
signalIds: [...signalRelations.keys()].toSorted(),
|
|
352
|
+
sourceCount: sources.length,
|
|
353
|
+
sourceKeys: sources.map((source) => source.key),
|
|
354
|
+
trailIds: activeTrailIds,
|
|
355
|
+
},
|
|
356
|
+
signals: new Map(
|
|
357
|
+
[...signalRelations.entries()].map(([id, relations]) => [
|
|
358
|
+
id,
|
|
359
|
+
{
|
|
360
|
+
consumers: sortedUnique(relations.consumers),
|
|
361
|
+
producers: sortedUnique(relations.producers),
|
|
362
|
+
},
|
|
363
|
+
])
|
|
364
|
+
),
|
|
365
|
+
sources: new Map(sources.map((source) => [source.key, source])),
|
|
366
|
+
trails: new Map(
|
|
367
|
+
[...trailActivations.entries()].map(([id, activation]) => [
|
|
368
|
+
id,
|
|
369
|
+
{
|
|
370
|
+
activatedBy: sortedUnique(activation.activatedBy),
|
|
371
|
+
activates: sortedUnique(activation.activates),
|
|
372
|
+
chains: activation.chains.toSorted(compareChains),
|
|
373
|
+
edges: edges.filter((edge) => edge.trailId === id),
|
|
374
|
+
fires: activation.fires,
|
|
375
|
+
on: activation.on,
|
|
376
|
+
sources: sources.filter((source) =>
|
|
377
|
+
edges.some(
|
|
378
|
+
(edge) => edge.trailId === id && edge.sourceKey === source.key
|
|
379
|
+
)
|
|
380
|
+
),
|
|
381
|
+
},
|
|
382
|
+
])
|
|
383
|
+
),
|
|
384
|
+
};
|
|
385
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Result, trail } from '@ontrails/core';
|
|
2
|
+
import type { Topo } from '@ontrails/core';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
import { tryLoadFreshAppLease } from './load-app.js';
|
|
6
|
+
import { resolveTrailRootDir } from './root-dir.js';
|
|
7
|
+
import { exportCurrentTopo } from './topo-store-support.js';
|
|
8
|
+
import type { TopoExportReport } from './topo-support.js';
|
|
9
|
+
import {
|
|
10
|
+
createIsolatedExampleInput,
|
|
11
|
+
topoSnapshotOutput,
|
|
12
|
+
} from './topo-support.js';
|
|
13
|
+
|
|
14
|
+
export const compileCurrentTopo = async (
|
|
15
|
+
app: Topo,
|
|
16
|
+
options?: { readonly rootDir?: string }
|
|
17
|
+
): Promise<Result<TopoExportReport, Error>> => exportCurrentTopo(app, options);
|
|
18
|
+
|
|
19
|
+
export const topoCompileTrail = trail('topo.compile', {
|
|
20
|
+
blaze: async (input, ctx) => {
|
|
21
|
+
const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
|
|
22
|
+
if (rootDirResult.isErr()) {
|
|
23
|
+
return Result.err(rootDirResult.error);
|
|
24
|
+
}
|
|
25
|
+
const rootDir = rootDirResult.value;
|
|
26
|
+
const leaseResult = await tryLoadFreshAppLease(input.module, rootDir);
|
|
27
|
+
if (leaseResult.isErr()) {
|
|
28
|
+
return Result.err(leaseResult.error);
|
|
29
|
+
}
|
|
30
|
+
const lease = leaseResult.value;
|
|
31
|
+
try {
|
|
32
|
+
return await compileCurrentTopo(lease.app, { rootDir });
|
|
33
|
+
} finally {
|
|
34
|
+
lease.release();
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
description: 'Compile the current topo to .trails artifacts',
|
|
38
|
+
examples: [
|
|
39
|
+
{
|
|
40
|
+
input: createIsolatedExampleInput('topo-compile'),
|
|
41
|
+
name: 'Compile the current topo artifacts',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
input: z.object({
|
|
45
|
+
module: z.string().optional().describe('Path to the app module'),
|
|
46
|
+
rootDir: z.string().optional().describe('Workspace root directory'),
|
|
47
|
+
}),
|
|
48
|
+
intent: 'write',
|
|
49
|
+
output: z.object({
|
|
50
|
+
hash: z.string(),
|
|
51
|
+
lockPath: z.string(),
|
|
52
|
+
snapshot: topoSnapshotOutput,
|
|
53
|
+
topoPath: z.string(),
|
|
54
|
+
}),
|
|
55
|
+
});
|
|
@@ -3,21 +3,25 @@ import { z } from 'zod';
|
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
DEFAULT_TOPO_HISTORY_LIMIT,
|
|
6
|
-
|
|
6
|
+
createIsolatedExampleInput,
|
|
7
7
|
listTopoHistory,
|
|
8
|
-
|
|
9
|
-
topoSaveOutput,
|
|
8
|
+
topoSnapshotOutput,
|
|
10
9
|
} from './topo-support.js';
|
|
10
|
+
import { resolveTrailRootDir } from './root-dir.js';
|
|
11
11
|
|
|
12
12
|
export const topoHistoryTrail = trail('topo.history', {
|
|
13
13
|
blaze: (input, ctx) => {
|
|
14
|
-
const
|
|
14
|
+
const rootDirResult = resolveTrailRootDir(input.rootDir, ctx.cwd);
|
|
15
|
+
if (rootDirResult.isErr()) {
|
|
16
|
+
return Result.err(rootDirResult.error);
|
|
17
|
+
}
|
|
18
|
+
const rootDir = rootDirResult.value;
|
|
15
19
|
return Result.ok(listTopoHistory({ limit: input.limit, rootDir }));
|
|
16
20
|
},
|
|
17
|
-
description: 'List saved topo
|
|
21
|
+
description: 'List saved topo snapshots, including pinned references',
|
|
18
22
|
examples: [
|
|
19
23
|
{
|
|
20
|
-
input:
|
|
24
|
+
input: createIsolatedExampleInput('topo-history'),
|
|
21
25
|
name: 'Show topo history',
|
|
22
26
|
},
|
|
23
27
|
],
|
|
@@ -25,16 +29,15 @@ export const topoHistoryTrail = trail('topo.history', {
|
|
|
25
29
|
limit: z
|
|
26
30
|
.number()
|
|
27
31
|
.default(DEFAULT_TOPO_HISTORY_LIMIT)
|
|
28
|
-
.describe('Maximum number of
|
|
32
|
+
.describe('Maximum number of snapshots to return'),
|
|
29
33
|
rootDir: z.string().optional().describe('Workspace root directory'),
|
|
30
34
|
}),
|
|
31
35
|
intent: 'read',
|
|
32
36
|
output: z.object({
|
|
33
37
|
dbPath: z.string(),
|
|
34
38
|
limit: z.number(),
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
saves: z.array(topoSaveOutput),
|
|
39
|
+
pinnedCount: z.number(),
|
|
40
|
+
snapshotCount: z.number(),
|
|
41
|
+
snapshots: z.array(topoSnapshotOutput),
|
|
39
42
|
}),
|
|
40
43
|
});
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
const jsonSchemaOutput = z.record(z.string(), z.unknown());
|
|
4
|
+
|
|
5
|
+
export const activationChainOutput = z.object({
|
|
6
|
+
consumer: z.string(),
|
|
7
|
+
producer: z.string(),
|
|
8
|
+
signal: z.string(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export const activationSourceOutput = z
|
|
12
|
+
.object({
|
|
13
|
+
cron: z.string().optional(),
|
|
14
|
+
hasParse: z.literal(true).optional(),
|
|
15
|
+
hasPayloadSchema: z.literal(true).optional(),
|
|
16
|
+
hasVerify: z.literal(true).optional(),
|
|
17
|
+
id: z.string(),
|
|
18
|
+
input: z.unknown().optional(),
|
|
19
|
+
inputSchema: jsonSchemaOutput.optional(),
|
|
20
|
+
key: z.string(),
|
|
21
|
+
kind: z.string(),
|
|
22
|
+
meta: z.record(z.string(), z.unknown()).optional(),
|
|
23
|
+
method: z.string().optional(),
|
|
24
|
+
parseOutputSchema: jsonSchemaOutput.optional(),
|
|
25
|
+
path: z.string().optional(),
|
|
26
|
+
payloadSchema: jsonSchemaOutput.optional(),
|
|
27
|
+
timezone: z.string().optional(),
|
|
28
|
+
})
|
|
29
|
+
.catchall(z.unknown());
|
|
30
|
+
|
|
31
|
+
export const activationEdgeOutput = z
|
|
32
|
+
.object({
|
|
33
|
+
hasWhere: z.boolean(),
|
|
34
|
+
sourceId: z.string(),
|
|
35
|
+
sourceKey: z.string(),
|
|
36
|
+
sourceKind: z.string(),
|
|
37
|
+
trailId: z.string(),
|
|
38
|
+
where: z.object({ predicate: z.literal(true) }).optional(),
|
|
39
|
+
})
|
|
40
|
+
.catchall(z.unknown());
|
|
41
|
+
|
|
42
|
+
export const activationOverviewOutput = z.object({
|
|
43
|
+
chainCount: z.number(),
|
|
44
|
+
chains: z.array(activationChainOutput).readonly(),
|
|
45
|
+
edgeCount: z.number(),
|
|
46
|
+
edges: z.array(activationEdgeOutput).readonly(),
|
|
47
|
+
signalIds: z.array(z.string()).readonly(),
|
|
48
|
+
sourceCount: z.number(),
|
|
49
|
+
sourceKeys: z.array(z.string()).readonly(),
|
|
50
|
+
trailIds: z.array(z.string()).readonly(),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const topoGraphLayerOutput = z.object({
|
|
54
|
+
input: jsonSchemaOutput.optional(),
|
|
55
|
+
name: z.string(),
|
|
56
|
+
scope: z.enum(['topo', 'trail']),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const fieldOverrideOutput = z.object({
|
|
60
|
+
field: z.string(),
|
|
61
|
+
overrides: z
|
|
62
|
+
.array(z.enum(['hint', 'label', 'message', 'options']))
|
|
63
|
+
.readonly(),
|
|
64
|
+
provenance: z.object({
|
|
65
|
+
source: z.literal('trail.fields'),
|
|
66
|
+
}),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const contourDetailOutput = z.object({
|
|
70
|
+
description: z.string().optional(),
|
|
71
|
+
exampleCount: z.number(),
|
|
72
|
+
id: z.string(),
|
|
73
|
+
identity: z.string().optional(),
|
|
74
|
+
kind: z.literal('contour'),
|
|
75
|
+
references: z
|
|
76
|
+
.array(
|
|
77
|
+
z.object({
|
|
78
|
+
contour: z.string(),
|
|
79
|
+
field: z.string(),
|
|
80
|
+
identity: z.string(),
|
|
81
|
+
})
|
|
82
|
+
)
|
|
83
|
+
.readonly()
|
|
84
|
+
.optional(),
|
|
85
|
+
schema: jsonSchemaOutput.optional(),
|
|
86
|
+
surfaces: z.array(z.string()).readonly(),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const surfaceProjectionOutput = z.object({
|
|
90
|
+
derivedName: z.string(),
|
|
91
|
+
method: z.string().nullable(),
|
|
92
|
+
surface: z.string(),
|
|
93
|
+
trailId: z.string(),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
export const trailDetailOutput = z.object({
|
|
97
|
+
activatedBy: z.array(z.string()).readonly(),
|
|
98
|
+
activates: z.array(z.string()).readonly(),
|
|
99
|
+
activationChains: z.array(activationChainOutput).readonly(),
|
|
100
|
+
activationContext: z.object({
|
|
101
|
+
edgeCount: z.number(),
|
|
102
|
+
sourceCount: z.number(),
|
|
103
|
+
sourceKeys: z.array(z.string()).readonly(),
|
|
104
|
+
trailIds: z.array(z.string()).readonly(),
|
|
105
|
+
}),
|
|
106
|
+
activationEdges: z.array(activationEdgeOutput).readonly(),
|
|
107
|
+
activationSources: z.array(activationSourceOutput).readonly(),
|
|
108
|
+
cli: z
|
|
109
|
+
.object({
|
|
110
|
+
path: z.array(z.string()).readonly(),
|
|
111
|
+
})
|
|
112
|
+
.nullable(),
|
|
113
|
+
composedLayers: z.object({
|
|
114
|
+
surface: z.object({
|
|
115
|
+
cli: z.array(z.string()).readonly(),
|
|
116
|
+
http: z.array(z.string()).readonly(),
|
|
117
|
+
mcp: z.array(z.string()).readonly(),
|
|
118
|
+
}),
|
|
119
|
+
topo: z.array(z.string()).readonly(),
|
|
120
|
+
trail: z.array(z.string()).readonly(),
|
|
121
|
+
}),
|
|
122
|
+
contourDetails: z.array(contourDetailOutput).readonly(),
|
|
123
|
+
contours: z.array(z.string()).readonly(),
|
|
124
|
+
crosses: z.array(z.string()).readonly(),
|
|
125
|
+
description: z.string().nullable(),
|
|
126
|
+
detours: z
|
|
127
|
+
.array(
|
|
128
|
+
z.object({
|
|
129
|
+
maxAttempts: z.number(),
|
|
130
|
+
on: z.string(),
|
|
131
|
+
})
|
|
132
|
+
)
|
|
133
|
+
.readonly()
|
|
134
|
+
.nullable(),
|
|
135
|
+
examples: z.array(z.unknown()).readonly(),
|
|
136
|
+
fieldOverrides: z.array(fieldOverrideOutput).readonly(),
|
|
137
|
+
fires: z.array(z.string()).readonly(),
|
|
138
|
+
governance: z.record(z.string(), z.unknown()).nullable(),
|
|
139
|
+
id: z.string(),
|
|
140
|
+
input: jsonSchemaOutput.nullable(),
|
|
141
|
+
intent: z.enum(['read', 'write', 'destroy']),
|
|
142
|
+
kind: z.literal('trail'),
|
|
143
|
+
layers: z.array(topoGraphLayerOutput).readonly(),
|
|
144
|
+
on: z.array(z.string()).readonly(),
|
|
145
|
+
output: jsonSchemaOutput.nullable(),
|
|
146
|
+
pattern: z.string().nullable(),
|
|
147
|
+
resources: z.array(z.string()).readonly(),
|
|
148
|
+
safety: z.string(),
|
|
149
|
+
surfaceProjections: z.array(surfaceProjectionOutput).readonly(),
|
|
150
|
+
surfaces: z.array(z.string()).readonly(),
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
export const resourceDetailOutput = z.object({
|
|
154
|
+
description: z.string().nullable(),
|
|
155
|
+
health: z.enum(['available', 'none']),
|
|
156
|
+
id: z.string(),
|
|
157
|
+
kind: z.literal('resource'),
|
|
158
|
+
lifetime: z.literal('singleton'),
|
|
159
|
+
usedBy: z.array(z.string()).readonly(),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
export const signalDetailOutput = z.object({
|
|
163
|
+
consumers: z.array(z.string()).readonly(),
|
|
164
|
+
description: z.string().nullable(),
|
|
165
|
+
examples: z.array(z.unknown()).readonly(),
|
|
166
|
+
from: z.array(z.string()).readonly(),
|
|
167
|
+
id: z.string(),
|
|
168
|
+
kind: z.literal('signal'),
|
|
169
|
+
// null when the surface-map entry is missing for this signal (e.g. partial
|
|
170
|
+
// import or schema migration). Coherent with the list view's
|
|
171
|
+
// `payloadSchema: false` flag — distinguishes "schema not found" from
|
|
172
|
+
// "schema accepts any value" (the latter would be `{}`).
|
|
173
|
+
payload: z.record(z.string(), z.unknown()).nullable(),
|
|
174
|
+
producers: z.array(z.string()).readonly(),
|
|
175
|
+
});
|