@substrat-run/kernel 0.126.0 → 0.128.0
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/dist/entity-edges.d.ts +8 -0
- package/dist/entity-edges.d.ts.map +1 -1
- package/dist/entity-edges.js +26 -12
- package/dist/entity-edges.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/invocation-log.d.ts +115 -18
- package/dist/invocation-log.d.ts.map +1 -1
- package/dist/invocation-log.js +183 -66
- package/dist/invocation-log.js.map +1 -1
- package/dist/lifecycle-flow.d.ts +41 -0
- package/dist/lifecycle-flow.d.ts.map +1 -0
- package/dist/lifecycle-flow.js +358 -0
- package/dist/lifecycle-flow.js.map +1 -0
- package/dist/platform-sweep.d.ts +12 -1
- package/dist/platform-sweep.d.ts.map +1 -1
- package/dist/platform-sweep.js +13 -3
- package/dist/platform-sweep.js.map +1 -1
- package/dist/routed-node.d.ts +12 -0
- package/dist/routed-node.d.ts.map +1 -1
- package/dist/routed-node.js +15 -6
- package/dist/routed-node.js.map +1 -1
- package/dist/scope-host.d.ts +17 -1
- package/dist/scope-host.d.ts.map +1 -1
- package/dist/scope-host.js.map +1 -1
- package/dist/scope-repoint.d.ts +33 -3
- package/dist/scope-repoint.d.ts.map +1 -1
- package/dist/scope-repoint.js +79 -6
- package/dist/scope-repoint.js.map +1 -1
- package/dist/scope-tuple-seat.d.ts +6 -2
- package/dist/scope-tuple-seat.d.ts.map +1 -1
- package/dist/scope-tuple-seat.js +10 -4
- package/dist/scope-tuple-seat.js.map +1 -1
- package/dist/spine-guard.d.ts +6 -0
- package/dist/spine-guard.d.ts.map +1 -1
- package/dist/spine-guard.js +6 -2
- package/dist/spine-guard.js.map +1 -1
- package/dist/spine-restore.d.ts +82 -0
- package/dist/spine-restore.d.ts.map +1 -0
- package/dist/spine-restore.js +138 -0
- package/dist/spine-restore.js.map +1 -0
- package/package.json +2 -2
package/dist/invocation-log.js
CHANGED
|
@@ -54,6 +54,9 @@
|
|
|
54
54
|
* verification the vertical does for its own routing happens here, with the same secret
|
|
55
55
|
* and the same dev opt-out, and a failed one writes nothing at all.
|
|
56
56
|
*/
|
|
57
|
+
// The subpath, not the root: this module is bundled into every vertical's upload (the
|
|
58
|
+
// platform entry, #1893), and the root carries every schema in the vocabulary.
|
|
59
|
+
import { encodeInvocationRecord, INVOCATION_RECORD_HEADER, invocationLevelOf, } from '@substrat-run/contracts/invocation-record';
|
|
57
60
|
import { ulid } from './ulid.js';
|
|
58
61
|
import { readRoutedNode, RouterAssertionError } from './routed-node.js';
|
|
59
62
|
/**
|
|
@@ -62,27 +65,9 @@ import { readRoutedNode, RouterAssertionError } from './routed-node.js';
|
|
|
62
65
|
* package, and a vertical's own routes may fill it in too.
|
|
63
66
|
*/
|
|
64
67
|
export const INVOCATION_RECORD_KEY = 'substratInvocationRecord';
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
* A stamped line is pure JSON, so the log platform sets no level on it (the reader's
|
|
69
|
-
* comments in `cf-observability.ts` found this out the hard way). The level histogram
|
|
70
|
-
* still needs one per request, so the line carries its own: a 5xx or an escaped throw is
|
|
71
|
-
* an error, a 4xx is a warning (the request was refused, which the caller may need to
|
|
72
|
-
* hear about, and nothing broke), a success carrying a problem code is a warning too, and
|
|
73
|
-
* anything else is info.
|
|
74
|
-
*/
|
|
75
|
-
export function invocationLevelOf(status, threw, problemCode) {
|
|
76
|
-
if (threw || status === null || status >= 500)
|
|
77
|
-
return 'error';
|
|
78
|
-
if (status >= 400)
|
|
79
|
-
return 'warn';
|
|
80
|
-
// A failure answered IN-BAND — an MCP tool error is a 200 carrying `isError` — is still
|
|
81
|
-
// a refused call, and filing it as info would hide it from the one filter that looks.
|
|
82
|
-
if (problemCode)
|
|
83
|
-
return 'warn';
|
|
84
|
-
return 'info';
|
|
85
|
-
}
|
|
68
|
+
// The level and its type moved to contracts in #1904 — the router files its datapoint under
|
|
69
|
+
// the same level — and stay exported from here, where every caller already reads them.
|
|
70
|
+
export { invocationLevelOf } from '@substrat-run/contracts/invocation-record';
|
|
86
71
|
/** Distinct values, first occurrence wins — the order a reader expects to see them in. */
|
|
87
72
|
function distinct(values) {
|
|
88
73
|
return [...new Set(values)];
|
|
@@ -119,6 +104,86 @@ function pathOf(url) {
|
|
|
119
104
|
return '/';
|
|
120
105
|
}
|
|
121
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* The registry of stamps, keyed by the incoming `Request`, on `globalThis` under a GLOBAL
|
|
109
|
+
* symbol. Global rather than module state, because two copies of this code meet in one
|
|
110
|
+
* request: the platform's entry (`withInvocationLog`, prebuilt into the upload) and the
|
|
111
|
+
* kernel the vertical bundled — different module instances, one `Symbol.for`. Weak, so a
|
|
112
|
+
* finished request's stamp goes with its `Request`.
|
|
113
|
+
*/
|
|
114
|
+
const STAMPS = Symbol.for('substrat.invocation-stamp');
|
|
115
|
+
function stamps() {
|
|
116
|
+
const g = globalThis;
|
|
117
|
+
return (g[STAMPS] ??= new WeakMap());
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* The stamp a request already carries, if a layer outside this one started it (#1893).
|
|
121
|
+
* `vertical-host` reads the record through this when the Hono context has none, which is
|
|
122
|
+
* the case when the platform's entry stamped the request and the vertical mounts nothing.
|
|
123
|
+
*/
|
|
124
|
+
export function invocationStampOf(request) {
|
|
125
|
+
return stamps().get(request);
|
|
126
|
+
}
|
|
127
|
+
function beginStamp(request) {
|
|
128
|
+
const stamp = { invocationId: ulid(), record: {} };
|
|
129
|
+
stamps().set(request, stamp);
|
|
130
|
+
return stamp;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Write the line for a finished request — the one place it is written, whichever layer
|
|
134
|
+
* started the stamp.
|
|
135
|
+
*
|
|
136
|
+
* No VERIFIED tenant ⇒ no line, and the three ways that happens are all silence here: no
|
|
137
|
+
* assertion at all (an un-routed call), an assertion this worker cannot verify or that is
|
|
138
|
+
* not the router's (`RouterAssertionError`), and — defensively — anything else the read
|
|
139
|
+
* throws. Writing a line for any of them would be filing one caller's request under a
|
|
140
|
+
* tenant of their choosing.
|
|
141
|
+
*/
|
|
142
|
+
function writeInvocationLine(stamp, done, options) {
|
|
143
|
+
// Called from a `finally`: a throw here would replace the vertical's answer, or its own
|
|
144
|
+
// error, with a logging failure. The platform wraps every uploaded script in this (#1893),
|
|
145
|
+
// so nothing in it may escape — a line that cannot be written is one missing line.
|
|
146
|
+
try {
|
|
147
|
+
writeLineOrThrow(stamp, done, options);
|
|
148
|
+
}
|
|
149
|
+
catch (e) {
|
|
150
|
+
try {
|
|
151
|
+
console.log(JSON.stringify({ substrat: 'invocation-log-fault', detail: String(e) }));
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
/* the console itself is gone; there is nowhere left to say so */
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function writeLineOrThrow(stamp, done, options) {
|
|
159
|
+
const node = routedNodeOrNull(done.request.headers, done.env, options);
|
|
160
|
+
if (!node)
|
|
161
|
+
return;
|
|
162
|
+
const { record, invocationId } = stamp;
|
|
163
|
+
const emitted = record.emitted;
|
|
164
|
+
const line = {
|
|
165
|
+
substrat: 'invocation',
|
|
166
|
+
tenantId: node.tenantId,
|
|
167
|
+
scopeId: node.scopeId,
|
|
168
|
+
vertical: node.verticalSlug,
|
|
169
|
+
surface: node.surface,
|
|
170
|
+
method: done.request.method,
|
|
171
|
+
path: pathOf(done.request.url),
|
|
172
|
+
status: done.status,
|
|
173
|
+
threw: done.threw,
|
|
174
|
+
durationMs: Date.now() - done.started,
|
|
175
|
+
invocationId,
|
|
176
|
+
level: invocationLevelOf(done.status, done.threw, record.problemCode),
|
|
177
|
+
operation: record.operation ?? null,
|
|
178
|
+
problemCode: record.problemCode ?? null,
|
|
179
|
+
principalKind: record.principalKind ?? null,
|
|
180
|
+
eventCount: emitted ? emitted.total : null,
|
|
181
|
+
eventTypes: emitted ? distinct(emitted.events.map((e) => e.type)) : [],
|
|
182
|
+
entities: emitted ? distinct(emitted.events.map((e) => e.entity)) : [],
|
|
183
|
+
versionId: versionIdOf(done.env),
|
|
184
|
+
};
|
|
185
|
+
console.log(JSON.stringify(line));
|
|
186
|
+
}
|
|
122
187
|
/**
|
|
123
188
|
* Mount as the FIRST middleware on a vertical's app, with the same two answers the
|
|
124
189
|
* vertical gives `readRoutedNode` in its own `nodeFor`:
|
|
@@ -135,26 +200,36 @@ function pathOf(url) {
|
|
|
135
200
|
* ```
|
|
136
201
|
*
|
|
137
202
|
* First, because Hono composes handlers in registration order and stops at the one that
|
|
138
|
-
* returns a response — middleware registered after a route does not wrap that route.
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
203
|
+
* returns a response — middleware registered after a route does not wrap that route.
|
|
204
|
+
*
|
|
205
|
+
* **Since #1893 the platform stamps every deployed request itself**, by wrapping the
|
|
206
|
+
* uploaded entry with `withInvocationLog`. A vertical that still mounts this middleware
|
|
207
|
+
* then finds the request already stamped and steps aside — it hands the platform's stamp
|
|
208
|
+
* to the handler chain through the context, as it always did, and writes no second line.
|
|
209
|
+
* Mounted where no platform entry runs (a dev server behind a dev router), it stamps as
|
|
210
|
+
* before.
|
|
143
211
|
*
|
|
144
212
|
* Nothing here can fail a request: the line is written in a `finally`, and a throw from
|
|
145
213
|
* the handler is re-thrown untouched for `onError` to map as it always did.
|
|
146
214
|
*/
|
|
147
215
|
export function invocationLog(options = {}) {
|
|
148
216
|
return async (c, next) => {
|
|
217
|
+
// Stamped already by the platform's entry: pass the stamp down, write nothing.
|
|
218
|
+
const existing = invocationStampOf(c.req.raw);
|
|
219
|
+
if (existing) {
|
|
220
|
+
c.set?.('substratInvocationId', existing.invocationId);
|
|
221
|
+
c.set?.(INVOCATION_RECORD_KEY, existing.record);
|
|
222
|
+
await next();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
149
225
|
// Host code, so a real clock is correct here — `ctx.now()` is the module-code rule,
|
|
150
226
|
// and this middleware runs outside any operation's transaction.
|
|
151
227
|
const started = Date.now();
|
|
152
228
|
// Minted per request, before anything can emit. A ULID so it sorts by time like
|
|
153
229
|
// every other id on the spine.
|
|
154
|
-
const
|
|
155
|
-
c.set?.('substratInvocationId', invocationId);
|
|
156
|
-
|
|
157
|
-
c.set?.(INVOCATION_RECORD_KEY, record);
|
|
230
|
+
const stamp = beginStamp(c.req.raw);
|
|
231
|
+
c.set?.('substratInvocationId', stamp.invocationId);
|
|
232
|
+
c.set?.(INVOCATION_RECORD_KEY, stamp.record);
|
|
158
233
|
let threw = false;
|
|
159
234
|
try {
|
|
160
235
|
await next();
|
|
@@ -166,41 +241,83 @@ export function invocationLog(options = {}) {
|
|
|
166
241
|
throw e;
|
|
167
242
|
}
|
|
168
243
|
finally {
|
|
169
|
-
|
|
170
|
-
// here: no assertion at all (an un-routed call), an assertion this worker cannot
|
|
171
|
-
// verify or that is not the router's (`RouterAssertionError`), and — defensively —
|
|
172
|
-
// anything else the read throws. Writing a line for any of them would be filing
|
|
173
|
-
// one caller's request under a tenant of their choosing.
|
|
174
|
-
const node = routedNodeOrNull(c, options);
|
|
175
|
-
if (node) {
|
|
176
|
-
const status = threw ? null : (c.res?.status ?? null);
|
|
177
|
-
const emitted = record.emitted;
|
|
178
|
-
const line = {
|
|
179
|
-
substrat: 'invocation',
|
|
180
|
-
tenantId: node.tenantId,
|
|
181
|
-
scopeId: node.scopeId,
|
|
182
|
-
vertical: node.verticalSlug,
|
|
183
|
-
surface: node.surface,
|
|
184
|
-
method: c.req.method,
|
|
185
|
-
path: pathOf(c.req.raw.url),
|
|
186
|
-
status,
|
|
187
|
-
threw,
|
|
188
|
-
durationMs: Date.now() - started,
|
|
189
|
-
invocationId,
|
|
190
|
-
level: invocationLevelOf(status, threw, record.problemCode),
|
|
191
|
-
operation: record.operation ?? null,
|
|
192
|
-
problemCode: record.problemCode ?? null,
|
|
193
|
-
principalKind: record.principalKind ?? null,
|
|
194
|
-
eventCount: emitted ? emitted.total : null,
|
|
195
|
-
eventTypes: emitted ? distinct(emitted.events.map((e) => e.type)) : [],
|
|
196
|
-
entities: emitted ? distinct(emitted.events.map((e) => e.entity)) : [],
|
|
197
|
-
versionId: versionIdOf(c.env),
|
|
198
|
-
};
|
|
199
|
-
console.log(JSON.stringify(line));
|
|
200
|
-
}
|
|
244
|
+
writeInvocationLine(stamp, { request: { method: c.req.method, url: c.req.raw.url, headers: c.req.raw.headers }, env: c.env, status: threw ? null : (c.res?.status ?? null), threw, started }, options);
|
|
201
245
|
}
|
|
202
246
|
};
|
|
203
247
|
}
|
|
248
|
+
/** The handlers a module worker may export besides `fetch`, passed through untouched. */
|
|
249
|
+
const OTHER_HANDLERS = ['scheduled', 'queue', 'email', 'tail', 'trace', 'alarm', 'test'];
|
|
250
|
+
/**
|
|
251
|
+
* The platform's half of the stamp (#1893): wrap a module worker's `fetch` so every request
|
|
252
|
+
* it serves is stamped, whatever framework the worker is written in and whether or not it
|
|
253
|
+
* mounts the middleware.
|
|
254
|
+
*
|
|
255
|
+
* The control plane builds this into the entry it uploads in front of every vertical's
|
|
256
|
+
* bundle (`platform-entry.generated.ts`), which is what makes the stamp the platform's
|
|
257
|
+
* rather than a line each vertical has to remember. A request the vertical's own
|
|
258
|
+
* middleware already stamped — possible only if something wrapped this one — is passed
|
|
259
|
+
* straight through. The other handlers (`scheduled`, `queue`, …) are passed through
|
|
260
|
+
* untouched, bound to the worker, since a stamp is about a routed request.
|
|
261
|
+
*
|
|
262
|
+
* Nothing here can fail a request: the line is written in a `finally`, and a throw is
|
|
263
|
+
* re-thrown untouched.
|
|
264
|
+
*/
|
|
265
|
+
export function withInvocationLog(worker, options = {}) {
|
|
266
|
+
// The platform wraps whatever a bundle's default export is (#1893): a class entrypoint or
|
|
267
|
+
// no default at all has no `fetch` to stamp, and is handed back untouched.
|
|
268
|
+
const inner = worker?.fetch;
|
|
269
|
+
if (typeof inner !== 'function')
|
|
270
|
+
return worker;
|
|
271
|
+
const wrapped = {
|
|
272
|
+
async fetch(request, env, ctx) {
|
|
273
|
+
if (invocationStampOf(request))
|
|
274
|
+
return inner.call(worker, request, env, ctx);
|
|
275
|
+
const started = Date.now();
|
|
276
|
+
const stamp = beginStamp(request);
|
|
277
|
+
let status = null;
|
|
278
|
+
let threw = false;
|
|
279
|
+
try {
|
|
280
|
+
const response = await inner.call(worker, request, env, ctx);
|
|
281
|
+
status = response.status;
|
|
282
|
+
handRecordToRouter(response, stamp.record, request, env, options);
|
|
283
|
+
return response;
|
|
284
|
+
}
|
|
285
|
+
catch (e) {
|
|
286
|
+
threw = true;
|
|
287
|
+
throw e;
|
|
288
|
+
}
|
|
289
|
+
finally {
|
|
290
|
+
writeInvocationLine(stamp, { request, env, status, threw, started }, options);
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
for (const name of OTHER_HANDLERS) {
|
|
295
|
+
const h = worker[name];
|
|
296
|
+
if (typeof h === 'function')
|
|
297
|
+
wrapped[name] = h.bind(worker);
|
|
298
|
+
}
|
|
299
|
+
return wrapped;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* #1904: hand the record back to the router on the response, which meters the request with
|
|
303
|
+
* it and strips the header before the response leaves (`INVOCATION_RECORD_HEADER`).
|
|
304
|
+
*
|
|
305
|
+
* Only on a request the router vouched for: a direct caller gets no header, and the router
|
|
306
|
+
* is the only reader. Never a throw — a response whose headers are immutable (one passed
|
|
307
|
+
* straight through from a `fetch`) reached no operation and has nothing to hand back, and
|
|
308
|
+
* metering must not fail a request either way.
|
|
309
|
+
*/
|
|
310
|
+
function handRecordToRouter(response, record, request, env, options) {
|
|
311
|
+
try {
|
|
312
|
+
const value = encodeInvocationRecord(record);
|
|
313
|
+
if (value === null || !routedNodeOrNull(request.headers, env, options))
|
|
314
|
+
return;
|
|
315
|
+
response.headers?.set?.(INVOCATION_RECORD_HEADER, value);
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
/* immutable headers: see above */
|
|
319
|
+
}
|
|
320
|
+
}
|
|
204
321
|
/**
|
|
205
322
|
* The verified node, or `null` — never a throw.
|
|
206
323
|
*
|
|
@@ -210,11 +327,11 @@ export function invocationLog(options = {}) {
|
|
|
210
327
|
* would replace the vertical's real answer with a logging failure. So the loudness is
|
|
211
328
|
* swallowed HERE and only here, and the request is unaffected either way.
|
|
212
329
|
*/
|
|
213
|
-
function routedNodeOrNull(
|
|
330
|
+
function routedNodeOrNull(headers, env, options) {
|
|
214
331
|
try {
|
|
215
|
-
return readRoutedNode(
|
|
216
|
-
expectedSecret: options.routerSecret?.(
|
|
217
|
-
allowUnsigned: options.allowUnsigned?.(
|
|
332
|
+
return readRoutedNode(headers, {
|
|
333
|
+
expectedSecret: options.routerSecret?.(env),
|
|
334
|
+
allowUnsigned: options.allowUnsigned?.(env) ?? false,
|
|
218
335
|
});
|
|
219
336
|
}
|
|
220
337
|
catch (e) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invocation-log.js","sourceRoot":"","sources":["../src/invocation-log.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAIxE;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"invocation-log.js","sourceRoot":"","sources":["../src/invocation-log.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replay one entity's declared lifecycle over a scope's outbox (#1744) — the process map's
|
|
3
|
+
* data, with no new write path.
|
|
4
|
+
*
|
|
5
|
+
* A transition is a status write inside an operation body, and the kernel never recorded
|
|
6
|
+
* "entity X moved from A to B". It did record everything a replay needs: every event names
|
|
7
|
+
* its entity, and (#1243) the operation that emitted it — which is exactly what a declared
|
|
8
|
+
* edge is keyed by. So one entity's events, in order, give its state sequence, and the gaps
|
|
9
|
+
* between moves give time-in-state.
|
|
10
|
+
*
|
|
11
|
+
* ## Where a state comes from
|
|
12
|
+
*
|
|
13
|
+
* **The payload first.** A fat event usually carries the row's lifecycle field, and when it
|
|
14
|
+
* does that value is what the row actually held — no inference about what the operation
|
|
15
|
+
* meant. It is read only from events classed `pii_class = 'none'`: the field is an enum, but
|
|
16
|
+
* #1762's rule is per event, and a read that extracts from a classified payload is the kind
|
|
17
|
+
* of exception that later grows.
|
|
18
|
+
*
|
|
19
|
+
* **The declaration otherwise.** An erased, classified or field-less event still names its
|
|
20
|
+
* operation, and the operation column survives a shred. From the state the replay has the
|
|
21
|
+
* entity in, `on[operation]` is where the declaration says it went. Counted apart
|
|
22
|
+
* (`inferred`), because this half is faithful only while the code agrees with the model.
|
|
23
|
+
*
|
|
24
|
+
* The previous state decides what an event means, which is why this is a walk in order and
|
|
25
|
+
* not a GROUP BY: `ticket0/ingest-message` is merely *allowed* in `open` and an *edge* out of
|
|
26
|
+
* `resolved`, so the same operation is a move or not depending on where the entity was.
|
|
27
|
+
*
|
|
28
|
+
* ## Bounded
|
|
29
|
+
*
|
|
30
|
+
* One entity type's events, walked in `(entity_id, id)` order — the `_substrat_outbox_entity`
|
|
31
|
+
* index — in keyset pages, up to `LIFECYCLE_FLOW_EVENT_BUDGET`. Past it the answer says
|
|
32
|
+
* `complete: false`, and every count is a lower bound; it never quietly becomes a smaller
|
|
33
|
+
* answer. History BEFORE the window is replayed too, since the state an entity was in when
|
|
34
|
+
* the window opened is only known from it; events after `until` are not read at all.
|
|
35
|
+
*
|
|
36
|
+
* Same permission posture as every read in `timeline.ts`: the caller checks, this does not.
|
|
37
|
+
*/
|
|
38
|
+
import { type LifecycleFlowInput, type LifecycleFlowResult } from '@substrat-run/contracts';
|
|
39
|
+
import type { TimelineReader } from './timeline.js';
|
|
40
|
+
export declare function readLifecycleFlow(ctx: TimelineReader, input: LifecycleFlowInput): LifecycleFlowResult;
|
|
41
|
+
//# sourceMappingURL=lifecycle-flow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle-flow.d.ts","sourceRoot":"","sources":["../src/lifecycle-flow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACzB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AA+EpD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,kBAAkB,GAAG,mBAAmB,CAgRrG"}
|