@adhd/apigen-core-client 0.2.1 → 0.3.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/CHANGELOG.md +17 -0
- package/index.d.ts +4 -2
- package/index.js +1 -1
- package/index.mjs +774 -671
- package/lib/extract-invoker.d.ts +90 -0
- package/lib/extraction-session.d.ts +18 -0
- package/lib/format-alias.d.ts +35 -0
- package/lib/plugin.d.ts +63 -0
- package/package.json +2 -2
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Plugin } from './plugin';
|
|
2
|
+
import { Operation } from './descriptor';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* One source unit to extract. Deliberately host-neutral — describes WHAT is
|
|
6
|
+
* being extracted, not HOW (no ts-morph/ts-json-schema-generator names leak
|
|
7
|
+
* in; a future per-language subprocess extractor speaks the same shape).
|
|
8
|
+
*/
|
|
9
|
+
export interface ExtractCall {
|
|
10
|
+
/**
|
|
11
|
+
* Absolute path to the source artifact for this language's extractor (a
|
|
12
|
+
* `.d.ts`/`.ts` file today; a directory/module root for a future
|
|
13
|
+
* subprocess-based extractor — the contract doesn't care which).
|
|
14
|
+
*/
|
|
15
|
+
source: string;
|
|
16
|
+
/** Declared owning language runtime tag, e.g. 'ts' | 'py' | 'rust' (SPEC §4 host). */
|
|
17
|
+
host: string;
|
|
18
|
+
/** Namespace segment (SPEC §4). Casing-neutral. */
|
|
19
|
+
namespace?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Free-form, extractor-specific options (e.g. a tsconfig path for TS today;
|
|
22
|
+
* a venv path for Python tomorrow). Opaque to the onion — read only by the
|
|
23
|
+
* terminal extractor step (and by cache layers that need identity signals).
|
|
24
|
+
*/
|
|
25
|
+
extractorOptions?: Record<string, unknown>;
|
|
26
|
+
/**
|
|
27
|
+
* Opt-in caller-supplied identity tag for cache-key purposes (e.g. a
|
|
28
|
+
* monorepo's own content-addressed build hash). Absence is fine — a cache
|
|
29
|
+
* layer must compute its own key when absent, never trust an unverified
|
|
30
|
+
* hint from an untrusted caller.
|
|
31
|
+
*/
|
|
32
|
+
versionHint?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Extraction's RESULT is already the host-neutral descriptor shape SPEC §4
|
|
36
|
+
* defines — reuse it verbatim, do not invent a parallel result type.
|
|
37
|
+
*/
|
|
38
|
+
export type ExtractResult = Operation[];
|
|
39
|
+
/** A stage-agnostic middleware step: receives the call, owns the continuation. */
|
|
40
|
+
export type ExtractMiddleware = (call: ExtractCall, next: () => Promise<ExtractResult>) => Promise<ExtractResult>;
|
|
41
|
+
/**
|
|
42
|
+
* Generic right-fold onion composition around an arbitrary innermost service.
|
|
43
|
+
* Identical composition algebra to the dispatch invoker's `reduceRight`
|
|
44
|
+
* (`apigen-engine-runtime`'s `invoke.ts`): `middlewares` are composed
|
|
45
|
+
* outermost-first (index 0 runs first); a middleware that returns without
|
|
46
|
+
* calling `next` short-circuits all downstream middlewares and the core.
|
|
47
|
+
*/
|
|
48
|
+
export declare function composeOnion<TCall, TResult>(middlewares: readonly ((call: TCall, next: () => Promise<TResult>) => Promise<TResult>)[], core: (call: TCall) => Promise<TResult>): (call: TCall) => Promise<TResult>;
|
|
49
|
+
/**
|
|
50
|
+
* Compose an extract-stage invoker: `middlewares` (e.g. a cache layer)
|
|
51
|
+
* wrapping the terminal `runExtractor` step. Mirrors the dispatch stage's
|
|
52
|
+
* `createPackageInvoker` for the extract stage — the terminal step is "run
|
|
53
|
+
* the real extractor for `call.host`" instead of "run dispatch".
|
|
54
|
+
*
|
|
55
|
+
* A cache layer sits ABOVE `runExtractor`, so a cache HIT never invokes it —
|
|
56
|
+
* which is what makes this correct whether `runExtractor` is an in-process
|
|
57
|
+
* function today or a spawned subprocess tomorrow.
|
|
58
|
+
*/
|
|
59
|
+
export declare function createExtractInvoker(middlewares: readonly ExtractMiddleware[], runExtractor: (call: ExtractCall) => Promise<ExtractResult>): (call: ExtractCall) => Promise<ExtractResult>;
|
|
60
|
+
/**
|
|
61
|
+
* Mirrors `createPackageInvoker`'s (`apigen-engine-runtime`'s
|
|
62
|
+
* `package-invoker.ts:124`) role for the extract stage (design doc Revision 2,
|
|
63
|
+
* R2.6 item 2 / implementation spec R2-2): pulls every loaded plugin's
|
|
64
|
+
* `extractLayer` capability — in declaration order, outermost-first, the
|
|
65
|
+
* identical composition rule `--use` layer ordering already follows on the
|
|
66
|
+
* dispatch side — and wraps `runExtractor` with them via {@link createExtractInvoker}.
|
|
67
|
+
*
|
|
68
|
+
* A plugin with no `extractLayer` capability (the common case today — nothing
|
|
69
|
+
* ships one yet) is filtered out silently; passing an empty `plugins` array
|
|
70
|
+
* (or a list where none declare `extractLayer`) degrades to `runExtractor`
|
|
71
|
+
* itself, unwrapped — a pure pass-through with zero behavioural change from
|
|
72
|
+
* calling `runExtractor` directly (R2-3's "byte-identical on MISS" guarantee).
|
|
73
|
+
*
|
|
74
|
+
* @param plugins - The loaded `--use` plugin objects (or an explicit host-
|
|
75
|
+
* constructed list, e.g. backlog's `extractStagePlugins()`).
|
|
76
|
+
* @param runExtractor - The terminal extraction step (today: an in-process
|
|
77
|
+
* ts-morph `extract()` call; SPEC §12: potentially a
|
|
78
|
+
* spawned per-language subprocess later).
|
|
79
|
+
* @param opts - This invocation's flat `--opt key=value` bag (the
|
|
80
|
+
* SAME bag already passed to `TargetCapability.generate`
|
|
81
|
+
* — apigen-cli has one flat bag per invocation, not a
|
|
82
|
+
* per-plugin-id namespaced one). Passed to each
|
|
83
|
+
* plugin's `extractLayer.createLayer(opts)` when
|
|
84
|
+
* present (`plugin.ts`'s `ExtractLayerCapability` doc);
|
|
85
|
+
* a plugin with only a static `.layer` ignores it.
|
|
86
|
+
* Defaults to `{}` — every existing call site (and
|
|
87
|
+
* every plugin with no opts-dependent behaviour)
|
|
88
|
+
* behaves identically whether this is passed or not.
|
|
89
|
+
*/
|
|
90
|
+
export declare function createExtractInvokerFromPlugins(plugins: readonly Plugin[], runExtractor: (call: ExtractCall) => Promise<ExtractResult>, opts?: Record<string, unknown>): (call: ExtractCall) => Promise<ExtractResult>;
|
|
@@ -82,6 +82,24 @@ export declare function fileVersion(pathStr: string): string;
|
|
|
82
82
|
export declare function persistentSchemasFor(sfPath: string, tsconfig: string | undefined, version: string): Map<string, Record<string, unknown>>;
|
|
83
83
|
/** Drop the process-lifetime Project + schema caches (tests / explicit memory reclaim). */
|
|
84
84
|
export declare function clearPersistentProjectCache(): void;
|
|
85
|
+
/**
|
|
86
|
+
* FEAT-002: absolute paths of every LOCAL (non-`node_modules`) file
|
|
87
|
+
* transitively imported by `entryPath`, in deterministic (sorted) order.
|
|
88
|
+
*
|
|
89
|
+
* Host-neutral, path-in/paths-out — the same `collectReferencedFiles` walk the
|
|
90
|
+
* session uses for its own invalidation, exposed so a persistent IR cache can
|
|
91
|
+
* content-hash the entry file's ENTIRE dependency graph for a stable,
|
|
92
|
+
* cross-machine cache key (an imported file's content is part of what the
|
|
93
|
+
* extractor's output depends on; a key covering only the entry file would
|
|
94
|
+
* falsely HIT after a type in an imported file changed).
|
|
95
|
+
*
|
|
96
|
+
* Uses a process-lifetime singleton Project with syntactic-only module
|
|
97
|
+
* resolution (no lib.d.ts parse, no type checking) so repeated key
|
|
98
|
+
* computations stay cheap. If the entry file was loaded before and has since
|
|
99
|
+
* changed on disk it is refreshed first, matching the walk's own
|
|
100
|
+
* refresh-before-descend behavior for visited targets.
|
|
101
|
+
*/
|
|
102
|
+
export declare function collectLocalImportPaths(entryPath: string): string[];
|
|
85
103
|
/**
|
|
86
104
|
* Create a fresh {@link ExtractionSession}.
|
|
87
105
|
*
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { TypeNode } from 'ts-morph';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Detects a `@format` JSDoc annotation on a plain, non-generic reference to a
|
|
5
|
+
* user-defined scalar type alias (e.g. `/** @format decimal *\/ type X =
|
|
6
|
+
* string;` referenced as a param/return type `X`), and returns the format
|
|
7
|
+
* string if found.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately narrow — returns `undefined` (never throws) for every case
|
|
10
|
+
* outside this one:
|
|
11
|
+
* - `typeNode` absent, or not a `TypeReference` node at all (covers plain
|
|
12
|
+
* keyword types like `string`/`number`, array/tuple/union/etc. syntax).
|
|
13
|
+
* - A generic reference (`Array<X>`, `Foo<T>`, …) — out of scope per spec;
|
|
14
|
+
* the RESOLVED type at the call site must still flow through the
|
|
15
|
+
* existing `Type#getText()` path unchanged.
|
|
16
|
+
* - A qualified name (`Foo.Bar`) — only plain identifiers are considered.
|
|
17
|
+
* - The identifier has no resolvable symbol, or none of its declarations is
|
|
18
|
+
* a `TypeAliasDeclaration` (this alone already excludes interfaces,
|
|
19
|
+
* classes, enums, and generic type parameters — none of those are ever a
|
|
20
|
+
* `TypeAliasDeclaration`).
|
|
21
|
+
* - The alias's own underlying type is not a plain scalar (string / number
|
|
22
|
+
* / boolean) — guards against ever attaching `format` onto a structural
|
|
23
|
+
* / object alias.
|
|
24
|
+
* - No `@format` JSDoc tag is present on the alias declaration.
|
|
25
|
+
*
|
|
26
|
+
* Wrapped in a try/catch: this is an enhancement to extraction, never a
|
|
27
|
+
* required path, and must never crash extraction on an unexpected ts-morph
|
|
28
|
+
* API failure.
|
|
29
|
+
*
|
|
30
|
+
* @param typeNode - The syntactic type-node for a parameter or return type
|
|
31
|
+
* (e.g. `paramDecl?.getTypeNode()`, `sig.getDeclaration()?.getReturnTypeNode()`),
|
|
32
|
+
* as opposed to the RESOLVED `Type` that `Type#getText()` operates on.
|
|
33
|
+
* @returns The trimmed `@format` JSDoc comment text, or `undefined`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function detectFormatAnnotatedAlias(typeNode: TypeNode | undefined): string | undefined;
|
package/lib/plugin.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ExtractMiddleware } from './extract-invoker';
|
|
1
2
|
import { PluginLanguage } from './types';
|
|
2
3
|
import { Operation, OperationKind, JSONSchema, TypeText } from './descriptor';
|
|
3
4
|
|
|
@@ -248,6 +249,58 @@ export interface LayerCapability {
|
|
|
248
249
|
*/
|
|
249
250
|
layer(call: Call, next: Next): Promise<Result> | AsyncIterable<Chunk>;
|
|
250
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* **`extractLayer`** capability — wrap the EXTRACT stage (design doc
|
|
254
|
+
* `extract-stage-onion-and-ir-cache.md` Revision 2, R2.6 item 1), not the
|
|
255
|
+
* dispatch stage `LayerCapability` above wraps.
|
|
256
|
+
*
|
|
257
|
+
* Same right-fold onion algebra as `LayerCapability.layer` — composed via
|
|
258
|
+
* {@link createExtractInvokerFromPlugins} in `extract-invoker.ts`, outermost
|
|
259
|
+
* plugin first — but typed against `ExtractCall`/`ExtractResult`
|
|
260
|
+
* (`extract-invoker.ts`), never the dispatch-shaped `Call`/`Result`. This is
|
|
261
|
+
* the deliberate, accepted "two Call shapes" asymmetry: `ExtractCall` has no
|
|
262
|
+
* `domainArgs`/`envelope`/`operation.id` to view-cast into a dispatch `Call`,
|
|
263
|
+
* so an extract-stage plugin (e.g. `@adhd/apigen-plugin-ir-cache`) declares
|
|
264
|
+
* this capability rather than reusing `layer`.
|
|
265
|
+
*
|
|
266
|
+
* A plugin may declare BOTH `layer` and `extractLayer` (they compose in their
|
|
267
|
+
* own, independent stages) or `extractLayer` alongside `target` (e.g. the
|
|
268
|
+
* ir-cache plugin: `extractLayer` for RUNTIME CACHE mode via `--use`,
|
|
269
|
+
* `target` for ARTIFACT mode via `--type`).
|
|
270
|
+
*
|
|
271
|
+
* **`createLayer` vs `layer` — the `--opt` gap fix.** A `Plugin` object is a
|
|
272
|
+
* static value with no per-invocation lifecycle hook, so a plain `layer`
|
|
273
|
+
* middleware built once at module-import time can never see a caller's
|
|
274
|
+
* `--use <id> --opt cache=<path>` value (unlike `TargetCapability.generate`/
|
|
275
|
+
* `MountCapability.operations`, which already receive `opts` as an explicit
|
|
276
|
+
* call-time parameter). `createLayer`, when present, is called ONCE per
|
|
277
|
+
* plugin at invoker-construction time (`createExtractInvokerFromPlugins`)
|
|
278
|
+
* with that invocation's flat `--opt` bag (the same bag already threaded to
|
|
279
|
+
* `TargetCapability.generate`'s `opts` — apigen-cli has exactly one flat
|
|
280
|
+
* `--opt` bag per invocation, not a per-plugin-id namespaced one; see
|
|
281
|
+
* `readUseOptions`'s `UseOptions` bag in `apigen-engine-runtime` for the
|
|
282
|
+
* dispatch-side per-plugin-id shape, which nothing in apigen-cli populates
|
|
283
|
+
* today, so this deliberately does not invent that infrastructure) and takes
|
|
284
|
+
* precedence over the static `layer`, which stays as the fallback for a
|
|
285
|
+
* plugin with no opts-dependent behaviour (or as an already-configured
|
|
286
|
+
* middleware a HOST builds directly via a factory, e.g.
|
|
287
|
+
* `entrypoint/backlog/src/server.ts`'s `backlogIrCachePlugin()`).
|
|
288
|
+
*/
|
|
289
|
+
export interface ExtractLayerCapability {
|
|
290
|
+
/**
|
|
291
|
+
* A ready-to-use extract-stage middleware — same short-circuit rule as
|
|
292
|
+
* `LayerCapability.layer`. Used when `createLayer` is absent, or by a host
|
|
293
|
+
* that constructs its own already-configured `Plugin` object directly
|
|
294
|
+
* (bypassing `--opt` entirely).
|
|
295
|
+
*/
|
|
296
|
+
layer?: ExtractMiddleware;
|
|
297
|
+
/**
|
|
298
|
+
* Opts-aware factory: builds the middleware from this invocation's `--opt`
|
|
299
|
+
* bag. Preferred over `layer` by `createExtractInvokerFromPlugins` when
|
|
300
|
+
* both are present.
|
|
301
|
+
*/
|
|
302
|
+
createLayer?: (opts: Record<string, unknown>) => ExtractMiddleware;
|
|
303
|
+
}
|
|
251
304
|
/**
|
|
252
305
|
* Runtime dispatch options a {@link MountHostBridge} hands back to a mount
|
|
253
306
|
* plugin's handler — structurally equivalent to `apigen-engine-runtime`'s
|
|
@@ -536,5 +589,15 @@ export interface Plugin<Opts = Record<string, unknown>> {
|
|
|
536
589
|
* fields it needs and *read/write* them in its layer function.
|
|
537
590
|
*/
|
|
538
591
|
envelope?: EnvelopeCapability;
|
|
592
|
+
/**
|
|
593
|
+
* Extract-layer capability — wrap the EXTRACT stage (design doc Revision
|
|
594
|
+
* 2, R2.6 item 1), not the dispatch stage `layer` above wraps.
|
|
595
|
+
*
|
|
596
|
+
* Loaded by `--use <plugin>` and composed via
|
|
597
|
+
* `createExtractInvokerFromPlugins` (`extract-invoker.ts`) — the single
|
|
598
|
+
* call site is `entrypoint/apigen-cli`'s `orchestrator.ts`'s
|
|
599
|
+
* `extractSource()`.
|
|
600
|
+
*/
|
|
601
|
+
extractLayer?: ExtractLayerCapability;
|
|
539
602
|
};
|
|
540
603
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhd/apigen-core-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"ts-morph": "^23.0.0",
|
|
6
6
|
"ts-json-schema-generator": "^2.3.0",
|
|
7
7
|
"pino": "10.3.1",
|
|
8
8
|
"typescript": "^6.0.3",
|
|
9
|
-
"@adhd/apigen-base-logical": "^0.1.
|
|
9
|
+
"@adhd/apigen-base-logical": "^0.1.1"
|
|
10
10
|
},
|
|
11
11
|
"main": "./index.js",
|
|
12
12
|
"module": "./index.mjs",
|