@adhd/apigen-core-client 0.2.2 → 0.3.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/lib/batch.d.ts CHANGED
@@ -45,21 +45,6 @@ export interface BatchKindSchema {
45
45
  /** Present only when ≥2 operations share this kind (a 1-branch `oneOf` is not a union). */
46
46
  discriminator?: InlineDiscriminator;
47
47
  }
48
- /**
49
- * Build the input/output schema pair for one `_batch/<kind>` mount (F1).
50
- *
51
- * - **≥2 ops of that kind:** a real `oneOf` + `InlineDiscriminator` union,
52
- * using the same-document JSON-Pointer mechanism (`morph-walk.ts`), never
53
- * `union.ts`'s $ref/nominal mechanism (§1.1).
54
- * - **Exactly 1 op of that kind:** the single branch's shape directly, no
55
- * `oneOf` wrapper — `detectDiscriminator` itself refuses below 2 variants
56
- * (§1.1's "edge case the 0.0.1 draft missed"), and a one-variant `oneOf`
57
- * is not a union.
58
- *
59
- * Throws if `ops` is empty — callers (mount-building) must never invoke this
60
- * for a kind with zero operations; `groupBatchableOperationsByKind` never
61
- * produces an empty group.
62
- */
63
48
  export declare function buildBatchKindSchema(ops: readonly Operation[]): BatchKindSchema;
64
49
  /**
65
50
  * One synthetic `_batch/<kind>` operation shape, minus `handler` — mounting
@@ -0,0 +1,23 @@
1
+ /**
2
+ * A `require` bound to this module's own location — the ESM-safe replacement
3
+ * for a bare `require(...)` in code bundled to BOTH `dist/index.js` (CJS) and
4
+ * `dist/index.mjs` (ESM). Resolves bare specifiers from this package's
5
+ * `node_modules` exactly as the CJS build's native `require` does, and (like
6
+ * the native `require`) exposes `.resolve`, so it is a drop-in for both
7
+ * `require(x)` and `require.resolve(x)` call sites.
8
+ *
9
+ * The `@ts-ignore` below suppresses TS1343 in DOWNSTREAM packages only.
10
+ * `apigen-core-client` is the base of the apigen family, so every consumer's
11
+ * `vite-plugin-dts` type-check follows this package's SOURCE through the
12
+ * tsconfig path mapping — and each consumer pins `module: commonjs` in its own
13
+ * `tsconfig.json`, where `import.meta` is rejected (TS1343). This file is only
14
+ * ever TYPE-CHECKED downstream, never emitted by it (the consumer emits its
15
+ * own declarations; the JS here is bundled by Rollup, which shims
16
+ * `import.meta.url` per output format) — so suppressing the diagnostic is
17
+ * scoped and safe. `@ts-ignore` (not `@ts-expect-error`) is deliberate: this
18
+ * package's own build runs `module: esnext`, where no diagnostic exists, so an
19
+ * expect-error directive would itself fail as unused. The rule-disable above
20
+ * the `@ts-ignore` is the established repo pattern for this exact suppression
21
+ * (see packages/data/data-query-engine/src/lib/expressions.ts).
22
+ */
23
+ export declare const lazyRequire: NodeRequire;
@@ -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
  }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Detects whether `typeText` (a return type, already `Promise<T> → T`
3
+ * unwrapped by the caller) is one of the recognized streaming wrappers, and
4
+ * if so, returns the unwrapped per-chunk element type's text.
5
+ *
6
+ * Returns `null` when `typeText` is not a full, single streaming-wrapper
7
+ * type — including when it's a union with a stream member (e.g.
8
+ * `AsyncGenerator<T> | null`), since only the outermost `<...>` closing at
9
+ * the very end of the string counts as "fully wrapped" (a union closes its
10
+ * generic early, mid-string).
11
+ *
12
+ * @param typeText - Textual return type, e.g. `'AsyncGenerator<Chunk, void, unknown>'`.
13
+ * @returns The element type text (e.g. `'Chunk'`), or `null` if not streaming.
14
+ */
15
+ export declare function detectStreamElementType(typeText: string): string | null;
package/lib/types.d.ts CHANGED
@@ -35,6 +35,18 @@ export interface PluginInput {
35
35
  importPath: string;
36
36
  fns?: Record<string, (...args: unknown[]) => unknown>;
37
37
  createClient?: (envelope: Record<string, unknown>) => Promise<unknown>;
38
+ /**
39
+ * The real, published version of THIS package (e.g. `package.json`'s own
40
+ * `version`, read fresh — never a compiled-in constant). Optional: most
41
+ * `PluginInput.packages` callers never populate it and every existing
42
+ * construction site across the monorepo remains valid without it.
43
+ * `@adhd/apigen-plugin-mcp`'s `createMcpServer` reads it (falling back to
44
+ * this package's own `apigen-mcp`/`1.0.0` identity when absent) so an MCP
45
+ * client's `initialize` handshake reports which REAL build it is talking
46
+ * to, instead of a hardcoded placeholder identical across every apigen
47
+ * host (MCP handshake identity finding, P5-cli-serve-transport).
48
+ */
49
+ version?: string;
38
50
  }>;
39
51
  outputDir: string;
40
52
  options: Record<string, unknown>;
package/package.json CHANGED
@@ -1,17 +1,40 @@
1
1
  {
2
2
  "name": "@adhd/apigen-core-client",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
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.0"
9
+ "@adhd/apigen-base-logical": "^0.1.2"
10
10
  },
11
11
  "main": "./index.js",
12
12
  "module": "./index.mjs",
13
13
  "typings": "./index.d.ts",
14
14
  "publishConfig": {
15
15
  "access": "public"
16
+ },
17
+ "description": "Code-first API generation - derive JSON Schemas from TypeScript types and live-mount to HTTP, MCP, CLI, OpenAPI, Python, and Java with zero codegen",
18
+ "keywords": [
19
+ "openapi",
20
+ "typescript",
21
+ "codegen",
22
+ "json-schema",
23
+ "swagger",
24
+ "api",
25
+ "code-first",
26
+ "mcp",
27
+ "grpc",
28
+ "schema"
29
+ ],
30
+ "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/PseudoSky/adhd.git",
34
+ "directory": "packages/apigen/apigen-core-client"
35
+ },
36
+ "homepage": "https://github.com/PseudoSky/adhd/tree/main/packages/apigen/apigen-core-client#readme",
37
+ "bugs": {
38
+ "url": "https://github.com/PseudoSky/adhd/issues"
16
39
  }
17
40
  }