@nwire/scan 0.9.2 → 0.10.1

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/scan.d.ts CHANGED
@@ -1,403 +1,162 @@
1
1
  /**
2
- * `@nwire/scan` — walks `AppDefinition[]` and produces the `.nwire/` cache.
2
+ * `@nwire/scan` — produces the `.nwire/` cache by inspecting booted apps.
3
3
  *
4
- * The framework's static-reflection layer: every action/event/actor/
5
- * projection/query/route plus the cross-module event graph emitted as
6
- * JSON. Studio reads it; the CLI reads it; codegen reads it. Rebuilt
7
- * from scratch on each invocation (a few ms for hundreds of definitions).
4
+ * Each input must be a `ForgeApp` (or any object exposing `appName`,
5
+ * `dispatcher()`, `container`, `runtime`). The scanner walks the
6
+ * dispatcher metadata maps to collect actions, actors, projections,
7
+ * queries, workflows, and external calls, and reads `container.list()`
8
+ * for DI bindings + `runtime.listHooks()` for hooks.
8
9
  *
9
- * See: architecture-sketch.html §05 (Tooling).
10
- */
11
- import { zodToJsonSchema } from "./zod-to-json.js";
12
- import type * as forge from "@nwire/forge";
13
- /**
14
- * Source location captured by `captureSourceLocation()` at `defineX` call
15
- * time. Studio uses it to render `file:line` chips with "open in IDE" links.
10
+ * Callers are responsible for booting their apps before scanning — the
11
+ * forge plugin populates the dispatcher during `app.start()`.
16
12
  */
17
13
  export interface SourceLocationEntry {
18
14
  readonly file: string;
19
- readonly line: number;
15
+ readonly line?: number;
20
16
  readonly column?: number;
21
17
  }
22
18
  export interface ActionEntry {
23
19
  readonly name: string;
24
- readonly description?: string;
25
- readonly module: string;
26
20
  readonly app: string;
27
- readonly schema: object;
28
- readonly retry?: object;
29
- readonly policy?: string | readonly string[];
30
- readonly hasInlineHandler: boolean;
31
- readonly emits: readonly string[];
32
- /** True when the module's manifest marked this action `.public()`. */
21
+ readonly module?: string;
33
22
  readonly public: boolean;
34
- /** Studio-aware metadata (all optional). */
35
- readonly persona?: string;
36
- readonly journeyStep?: string;
37
- readonly capability?: string;
38
- readonly slo?: {
39
- readonly p95LatencyMs?: number;
40
- readonly successRate?: number;
41
- };
42
- readonly tags?: readonly string[];
23
+ readonly inputSchema?: unknown;
43
24
  readonly source?: SourceLocationEntry;
44
25
  }
45
26
  export interface ExternalCallEntry {
46
27
  readonly name: string;
47
- readonly description?: string;
48
- readonly module: string;
49
28
  readonly app: string;
50
- readonly target: {
51
- provider: string;
52
- endpoint: string;
53
- region?: string;
54
- };
55
- readonly request: object;
56
- readonly response?: object;
57
- readonly hasIdempotencyKey: boolean;
58
- readonly slo?: {
59
- p95LatencyMs?: number;
60
- successRate?: number;
61
- };
62
- readonly retry?: {
63
- max: number;
64
- backoff?: string;
65
- };
66
- readonly tags?: readonly string[];
29
+ readonly source?: SourceLocationEntry;
67
30
  }
68
31
  export interface InboundWebhookEntry {
69
32
  readonly name: string;
70
- readonly description?: string;
71
- readonly module: string;
72
33
  readonly app: string;
73
- readonly source: string;
74
- readonly path: string;
75
- readonly hasSignatureVerifier: boolean;
76
- readonly dedupe?: {
77
- window: string;
78
- };
79
- readonly discriminator: string;
80
- /** Map of discriminator value → action name. */
81
- readonly routes: Readonly<Record<string, string>>;
82
- readonly tags?: readonly string[];
34
+ readonly source?: SourceLocationEntry;
83
35
  }
84
36
  export interface OutboxEntry {
85
37
  readonly name: string;
86
- readonly description?: string;
87
- readonly module: string;
88
38
  readonly app: string;
89
- readonly publishes: readonly string[];
90
- readonly flushIntervalMs: number;
91
- readonly maxBatch: number;
92
- readonly tags?: readonly string[];
39
+ readonly source?: SourceLocationEntry;
93
40
  }
94
41
  export interface InboxEntry {
95
42
  readonly name: string;
96
- readonly description?: string;
97
- readonly module: string;
98
43
  readonly app: string;
99
- readonly window: string;
100
- readonly on: readonly string[];
101
- readonly tags?: readonly string[];
44
+ readonly source?: SourceLocationEntry;
102
45
  }
103
46
  export interface CronEntry {
104
47
  readonly name: string;
105
- readonly description?: string;
106
- readonly module: string;
107
48
  readonly app: string;
108
49
  readonly schedule: string;
109
- readonly dispatches: string;
110
- readonly timezone?: string;
111
- readonly tags?: readonly string[];
50
+ readonly source?: SourceLocationEntry;
112
51
  }
113
- /**
114
- * Operator-script command (`defineCommand` from `@nwire/please`). Commands
115
- * live at the app composition layer (not inside modules) — they're imperative
116
- * scripts the team triggers by hand, not domain intent the system processes.
117
- * Studio's CLI page consumes this entry to surface "what can ops run?".
118
- */
119
52
  export interface CommandEntry {
120
53
  readonly name: string;
121
- readonly describe?: string;
122
54
  readonly app: string;
123
- readonly hasArgsSchema: boolean;
124
55
  readonly source?: SourceLocationEntry;
125
56
  }
126
57
  export interface WorkflowEntry {
127
- /** The workflow name (`defineWorkflow("on-wash-complete", …)`). */
128
58
  readonly name: string;
129
- /** The bounded context (module) that owns this workflow. */
130
- readonly module: string;
131
59
  readonly app: string;
132
- /** Free-form description from `options.description`. */
133
- readonly description?: string;
134
- /** Every event this workflow declares an `on(...)` for. */
135
- readonly subscribesTo: readonly string[];
136
- /** Every action this workflow declares it dispatches (via `opts.dispatches`). */
137
- readonly dispatches: readonly string[];
138
- /** True when the module's manifest marked this workflow `.public()`. */
139
60
  readonly public: boolean;
140
61
  readonly source?: SourceLocationEntry;
141
62
  }
142
63
  export interface EventEntry {
143
64
  readonly name: string;
144
- readonly description?: string;
145
- readonly module: string;
146
65
  readonly app: string;
147
- readonly visibility: "public" | "internal";
148
- /**
149
- * True when the module's manifest marked this event `.public()`. Distinct
150
- * from `visibility` (which the event definition itself may declare for
151
- * back-compat) — `public` reflects the canonical module-level decision.
152
- */
153
66
  readonly public: boolean;
154
- readonly schema: object;
155
- /** Studio-aware metadata. */
156
- readonly outcome?: "success" | "failure" | "milestone" | "warning";
157
- readonly businessWeight?: number;
158
- readonly audience?: readonly string[];
159
67
  readonly source?: SourceLocationEntry;
160
68
  }
161
69
  export interface ActorEntry {
162
70
  readonly name: string;
163
- readonly module: string;
164
71
  readonly app: string;
165
- readonly key: string;
166
- readonly initial: string;
167
- readonly states: ReadonlyArray<{
168
- readonly name: string;
169
- readonly final?: boolean;
170
- readonly on: ReadonlyArray<{
171
- readonly eventName: string;
172
- readonly target?: string;
173
- }>;
174
- readonly after: ReadonlyArray<{
175
- readonly timerName: string;
176
- readonly action: string;
177
- readonly delay: string;
178
- }>;
179
- }>;
180
- readonly methods: readonly string[];
181
- readonly schema: object;
182
- /** Studio-aware metadata. */
183
- readonly stuckThresholds?: Readonly<Record<string, number>>;
184
- readonly slas?: Readonly<Record<string, {
185
- maxDurationMs: number;
186
- escalateTo?: string;
187
- }>>;
188
72
  readonly source?: SourceLocationEntry;
189
73
  }
190
74
  export interface ProjectionEntry {
191
75
  readonly name: string;
192
- readonly module: string;
193
76
  readonly app: string;
194
- readonly listens: readonly string[];
195
- /** Studio-aware metadata. */
196
- readonly description?: string;
197
- readonly freshness?: {
198
- p95MsBehindStream?: number;
199
- };
200
77
  readonly source?: SourceLocationEntry;
201
78
  }
202
79
  export interface QueryEntry {
203
80
  readonly name: string;
204
- readonly description?: string;
205
- readonly module: string;
206
81
  readonly app: string;
207
- /** Backing projection name. Undefined for handler-form queries. */
208
- readonly projection?: string;
209
- readonly schema: object;
210
- /** True when the module's manifest marked this query `.public()`. */
211
82
  readonly public: boolean;
212
- /** Studio-aware metadata. */
213
- readonly slo?: {
214
- p95LatencyMs?: number;
215
- };
216
- readonly cacheable?: boolean;
217
83
  readonly source?: SourceLocationEntry;
218
84
  }
219
85
  export interface ModuleEntry {
220
86
  readonly name: string;
221
87
  readonly app: string;
222
- readonly provides: {
223
- readonly events: readonly string[];
224
- readonly actions: readonly string[];
225
- };
226
- readonly needs: {
227
- readonly events: readonly string[];
228
- readonly externalEvents: readonly string[];
229
- readonly actions: readonly string[];
230
- };
231
- readonly counts: {
232
- readonly actions: number;
233
- readonly actors: number;
234
- readonly projections: number;
235
- readonly queries: number;
236
- readonly workflows: number;
237
- readonly events: number;
238
- readonly routes: number;
239
- };
240
- /** Studio-aware metadata. */
241
- readonly description?: string;
242
- readonly owners?: readonly string[];
243
- readonly journey?: readonly {
244
- id: string;
245
- label: string;
246
- description?: string;
247
- }[];
248
88
  readonly source?: SourceLocationEntry;
249
89
  }
250
90
  export interface AppEntry {
251
91
  readonly name: string;
252
92
  readonly description?: string;
253
93
  readonly modules: readonly string[];
254
- /** Studio-aware metadata. */
255
- readonly tenantModel?: "single" | "per-org" | "per-account" | "per-workspace";
94
+ readonly tenantModel?: string;
256
95
  readonly tenantKey?: string;
257
96
  }
258
97
  export interface RouteEntry {
259
98
  readonly method: string;
260
99
  readonly path: string;
261
- readonly target?: string;
262
- readonly targetKind?: "action" | "query" | "resolver";
263
- readonly module?: string;
264
- readonly app?: string;
265
- /**
266
- * Free-form trigger source declared via `RouteBinding.from(source)`.
267
- * Studio + observability group routes by it. Undefined when the route
268
- * did not declare a source.
269
- */
270
- readonly source?: string;
100
+ readonly action?: string;
101
+ readonly source?: SourceLocationEntry;
271
102
  }
272
- /**
273
- * Minimal duck-typed shape the scanner reads from `@nwire/http`'s
274
- * `httpInterface().listRoutes()` (and any compatible transport that
275
- * exposes the same surface). Kept structural so the scanner doesn't
276
- * take a hard dep on the http package.
277
- */
278
103
  export interface RouteSource {
279
- listRoutes(): ReadonlyArray<{
280
- readonly verb: string;
281
- readonly path: string;
282
- readonly source?: string;
283
- }>;
104
+ listRoutes?(): readonly {
105
+ method: string;
106
+ path: string;
107
+ action?: string;
108
+ }[];
284
109
  }
285
- /**
286
- * Interface-layer operation. Each resolver is a typed, transport-agnostic
287
- * surface — Studio's canonical "what does this app expose?" answer.
288
- */
289
110
  export interface ResolverEntry {
290
- readonly operation: string;
291
- readonly version: number;
292
- readonly status: "draft" | "active" | "deprecated" | "sunset";
293
- readonly summary?: string;
294
- readonly description?: string;
295
- readonly tags?: readonly string[];
111
+ readonly name: string;
296
112
  readonly app: string;
297
- /** Transport bindings — verb/path table from httpInterface().wire() + friends. */
298
- readonly bindings: ReadonlyArray<{
299
- readonly transport: "rest" | "graphql" | "cli";
300
- readonly method?: string;
301
- readonly path?: string;
302
- }>;
303
- /** Zod schemas converted to JSON schema. */
304
- readonly params?: object;
305
- readonly query?: object;
306
- readonly body?: object;
307
- readonly returns: ReadonlyArray<{
308
- readonly status: number;
309
- readonly kind: "single" | "list" | "empty";
310
- }>;
311
- readonly errors: ReadonlyArray<{
312
- readonly code: string;
313
- readonly status: number;
314
- }>;
315
- readonly successor?: {
316
- readonly operation: string;
317
- readonly version: number;
318
- };
319
- readonly sunsetDate?: string;
113
+ readonly source?: SourceLocationEntry;
320
114
  }
321
- /**
322
- * Optional mount table for resolvers. The scanner can't reach into
323
- * `@nwire/http`'s rest registry (transport-layer dep), so callers pass
324
- * the mount tuples in. Passing nothing is fine — resolvers will be
325
- * emitted without verb/path bindings.
326
- */
327
115
  export interface ResolverMount {
328
- readonly resolver: any;
329
- readonly transport: "rest" | "graphql" | "cli";
330
- readonly method?: string;
331
- readonly path?: string;
116
+ readonly verb: string;
117
+ readonly path: string;
118
+ readonly resolverName: string;
332
119
  }
333
- /**
334
- * Resource definition entry — emitted to `.nwire/models.json`. Captures the
335
- * external-contract data shape (the "M" in MVC; what the API client sees)
336
- * produced by `defineResource(name, { schema, public, examples, … })`. The
337
- * scanner collects these from `BuildCacheOptions.resources` because
338
- * resources are imported by handlers/resolvers and not attached to module
339
- * manifests — the caller passes them in alongside the apps.
340
- */
341
120
  export interface ResourceEntry {
342
121
  readonly name: string;
343
- readonly summary?: string;
344
- readonly description?: string;
345
- /** App attribution (when the caller provided it). */
346
122
  readonly app?: string;
347
- /** Module attribution (when the caller provided it). */
348
123
  readonly module?: string;
349
- /** Field paths exposed by the public projection (from `defineResource.public`). */
350
- readonly public: readonly string[];
351
- /** JSON-Schema of the resource's zod shape. */
352
- readonly schema: object;
353
- /** Variant names declared via `defineResource.examples`. */
354
- readonly exampleVariants: readonly string[];
355
- readonly audience?: readonly string[];
356
124
  readonly source?: SourceLocationEntry;
357
125
  }
358
- /**
359
- * Error definition entry — emitted to `.nwire/errors.json`. One row per
360
- * `defineError({ code, status, summary, … })`. Studio's Errors page lists
361
- * the full error catalogue; transport adapters cross-reference the codes
362
- * for OpenAPI / GraphQL error extensions.
363
- */
364
126
  export interface ErrorEntry {
365
127
  readonly code: string;
366
- readonly status: number;
367
- readonly summary: string;
368
- readonly description?: string;
369
128
  readonly app?: string;
370
129
  readonly module?: string;
371
- readonly tags?: readonly string[];
372
130
  readonly source?: SourceLocationEntry;
373
131
  }
374
- /**
375
- * Middleware definition entry — emitted to `.nwire/middleware.json`. One
376
- * row per `defineMiddleware(...)` value the caller passes in. `where`
377
- * is a free-form label declared by the caller — typically the route
378
- * group, resolver layer, or interface the middleware applies to (for
379
- * example `"stations:guard"` or `"auth:authenticate"`). The scanner
380
- * does not infer it; it round-trips whatever the caller supplies.
381
- */
382
132
  export interface MiddlewareEntry {
383
133
  readonly name: string;
384
- readonly app?: string;
385
- readonly module?: string;
386
- /** Caller-supplied label for "where it runs" — see interface docstring. */
387
134
  readonly where?: string;
388
135
  readonly source?: SourceLocationEntry;
389
136
  }
137
+ export interface HookEntry {
138
+ readonly id: string;
139
+ readonly name: string;
140
+ readonly chain: number;
141
+ readonly listeners: number;
142
+ readonly source?: SourceLocationEntry;
143
+ }
144
+ export interface PluginEntry {
145
+ readonly name: string;
146
+ readonly kind: "plugin" | "module";
147
+ readonly app: string;
148
+ readonly source?: SourceLocationEntry;
149
+ }
150
+ export interface DIBindingEntry {
151
+ readonly name: string;
152
+ readonly kind: "singleton" | "transient";
153
+ readonly app: string;
154
+ readonly source?: SourceLocationEntry;
155
+ }
390
156
  export interface EventGraphEdge {
391
- readonly event: string;
392
- readonly producer: {
393
- app: string;
394
- module: string;
395
- };
396
- readonly consumers: ReadonlyArray<{
397
- readonly app: string;
398
- readonly module: string;
399
- readonly via: "workflow" | "projection" | "actor" | "external";
400
- }>;
157
+ readonly from: string;
158
+ readonly to: string;
159
+ readonly via: string;
401
160
  }
402
161
  export interface Cache {
403
162
  readonly generatedAt: string;
@@ -427,95 +186,6 @@ export interface Cache {
427
186
  readonly events: readonly EventGraphEdge[];
428
187
  };
429
188
  }
430
- /**
431
- * One entry per `hook()` instantiated in this process at scan time. The
432
- * scanner snapshots `listHooks()` after the consumer's apps have loaded —
433
- * runtime construction, plugin registration, and module wiring all create
434
- * hooks during import, so the snapshot covers the full extension surface
435
- * the running app exposes.
436
- *
437
- * Studio's Hooks page consumes this directly; OTel + structured-logging
438
- * adapters use it to know the name space they're observing.
439
- */
440
- export interface HookEntry {
441
- readonly id: string;
442
- readonly name: string;
443
- readonly chain: number;
444
- readonly listeners: number;
445
- readonly source?: SourceLocationEntry;
446
- }
447
- /**
448
- * One entry per registered plugin OR module. Modules participate in the
449
- * same lifecycle event surface as plugins (see `App.plugins` docs); the
450
- * `kind` field distinguishes them so Studio can render each with the
451
- * right affordances + link.
452
- */
453
- export interface PluginEntry {
454
- readonly name: string;
455
- readonly kind: "plugin" | "module";
456
- readonly app: string;
457
- readonly source?: SourceLocationEntry;
458
- }
459
- /**
460
- * One row per DI registration visible on the app's container at scan time.
461
- * Studio's future DI page will render this as a "who registered what?"
462
- * table — capability factories registered by `createApp` (`execute`,
463
- * `send`, `useProjection`), plus everything plugins contributed via
464
- * `provide()`.
465
- *
466
- * Captured by walking `app.container.list()` (defensive — older apps may
467
- * predate the introspection contract; we skip them silently rather than
468
- * crash the cache build).
469
- */
470
- export interface DIBindingEntry {
471
- readonly name: string;
472
- readonly kind: "singleton" | "transient";
473
- readonly app: string;
474
- readonly source?: SourceLocationEntry;
475
- }
476
- export interface BuildCacheOptions {
477
- /**
478
- * Resolver mount tuples — pass in from the runtime side (typically
479
- * `rest.listMounts()` from `@nwire/http`) so resolvers carry their
480
- * verb/path bindings in the cache.
481
- */
482
- readonly mounts?: readonly ResolverMount[];
483
- /**
484
- * HTTP interfaces (or anything implementing `RouteSource`) whose
485
- * wired routes should be emitted to `.nwire/routes.json`. The
486
- * scanner reads `listRoutes()` and stamps each entry with the
487
- * optional `.from(source)` label. Pass the same `httpInterface`
488
- * the wire boots with.
489
- */
490
- readonly interfaces?: readonly RouteSource[];
491
- /**
492
- * Resource definitions (`defineResource(...)`) to emit to
493
- * `.nwire/models.json`. Resources live at the interface layer
494
- * (imported by handlers + transports), not on module manifests, so
495
- * the caller passes them in. Each input is either the bare
496
- * definition or a tuple carrying `{ definition, app?, module? }`
497
- * attribution for Studio's grouping.
498
- */
499
- readonly resources?: readonly ResourceInput[];
500
- /**
501
- * Error definitions (`defineError(...)`) to emit to `.nwire/errors.json`.
502
- * Same attribution model as `resources`.
503
- */
504
- readonly errors?: readonly ErrorInput[];
505
- /**
506
- * Middleware definitions (`defineMiddleware(...)`) to emit to
507
- * `.nwire/middleware.json`. The optional `where` field is a free-form
508
- * caller-supplied label (e.g. `"stations:guard"`) describing where the
509
- * middleware runs — the scanner round-trips it, doesn't infer it.
510
- */
511
- readonly middleware?: readonly MiddlewareInput[];
512
- }
513
- /**
514
- * Resource input — accepts the bare definition OR a tagged object that
515
- * carries app/module attribution. The bare form is the common case
516
- * (caller imports the resource and drops it in the array); the tagged
517
- * form is for callers building cache for multiple apps at once.
518
- */
519
189
  export type ResourceInput = {
520
190
  readonly $kind: "resource";
521
191
  } | {
@@ -535,16 +205,41 @@ export type ErrorInput = {
535
205
  readonly module?: string;
536
206
  };
537
207
  export type MiddlewareInput = {
538
- readonly $kind: "middleware";
208
+ readonly name: string;
539
209
  } | {
540
210
  readonly definition: {
541
- readonly $kind: "middleware";
542
- } & Record<string, any>;
543
- readonly app?: string;
544
- readonly module?: string;
211
+ readonly name: string;
212
+ };
545
213
  readonly where?: string;
546
214
  };
547
- export declare function buildCache(apps: readonly forge.AppDefinition[], options?: BuildCacheOptions): Cache;
215
+ export interface BuildCacheOptions {
216
+ readonly mounts?: readonly ResolverMount[];
217
+ readonly interfaces?: readonly RouteSource[];
218
+ readonly resources?: readonly ResourceInput[];
219
+ readonly errors?: readonly ErrorInput[];
220
+ readonly middleware?: readonly MiddlewareInput[];
221
+ }
222
+ interface ScannedApp {
223
+ readonly appName: string;
224
+ dispatcher?: () => any;
225
+ container: {
226
+ resolve?: <T = any>(name: string) => T;
227
+ list?(): readonly {
228
+ name: string;
229
+ kind: string;
230
+ source?: SourceLocationEntry;
231
+ }[];
232
+ };
233
+ runtime?: {
234
+ listHooks?(): readonly {
235
+ id: string;
236
+ name: string;
237
+ chain: number;
238
+ listeners: number;
239
+ source?: SourceLocationEntry;
240
+ }[];
241
+ };
242
+ }
243
+ export declare function buildCache(apps: readonly ScannedApp[], options?: BuildCacheOptions): Cache;
548
244
  export declare function writeCache(cache: Cache, dir: string): Promise<void>;
549
- export { zodToJsonSchema };
550
- //# sourceMappingURL=scan.d.ts.map
245
+ export {};