@adhd/apigen-core-client 0.2.0 → 0.2.2
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 +33 -0
- package/README.md +28 -2
- package/index.d.ts +4 -1
- package/index.js +1 -1
- package/index.mjs +784 -630
- package/lib/batch.d.ts +89 -0
- package/lib/plugin.d.ts +99 -5
- package/package.json +1 -1
package/lib/batch.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { InlineDiscriminator } from './schema-builders/morph-walk';
|
|
2
|
+
import { Operation, OperationKind, JSONSchema } from './descriptor';
|
|
3
|
+
import { Descriptor, MountedOperation } from './plugin';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Options accepted when deriving `_batch/<kind>` mounts (§2.1).
|
|
7
|
+
*
|
|
8
|
+
* `exclude` is the plugin's own opt-out switch — `--opt batch.exclude=...`
|
|
9
|
+
* (CLI `--use` path) or a literal argument at a hand-wired direct-invocation
|
|
10
|
+
* call site (§2.1). There is deliberately no other configuration surface
|
|
11
|
+
* (no runtime flag, no `apigen.config` file — Tenet 1).
|
|
12
|
+
*/
|
|
13
|
+
export interface BatchMountOptions {
|
|
14
|
+
/** Operation ids to exclude from every `_batch/<kind>` branch set. */
|
|
15
|
+
exclude?: string[];
|
|
16
|
+
}
|
|
17
|
+
/** One discriminator branch derived from a single batchable `Operation` (§1.2). */
|
|
18
|
+
export interface BatchOperationBranch {
|
|
19
|
+
/** The literal `operation` value selecting this branch. */
|
|
20
|
+
operationConst: string;
|
|
21
|
+
/** The per-item input schema — `op.input` verbatim. */
|
|
22
|
+
itemsSchema: JSONSchema;
|
|
23
|
+
/** `BatchItemResult<op.output>` as a JSON Schema fragment (§3/§F3). */
|
|
24
|
+
resultSchema: JSONSchema;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Pure `Operation → schema fragment` transform (§1.2). Host-agnostic: takes
|
|
28
|
+
* only the already-host-neutral `Operation` IR, produces plain JSON Schema.
|
|
29
|
+
*/
|
|
30
|
+
export declare function deriveBatchOperationBranch(op: Operation): BatchOperationBranch;
|
|
31
|
+
/**
|
|
32
|
+
* Group batchable operations (excluding `opts.exclude`) by `Operation.kind`
|
|
33
|
+
* (F1). Each distinct kind present gets its own `_batch/<kind>` mount —
|
|
34
|
+
* `_batch/query`, `_batch/action`, etc. — because `Operation.kind`/`.safe`
|
|
35
|
+
* are static per-op classifications transports read for wire decisions
|
|
36
|
+
* (HTTP verb/cacheability, gRPC idempotency), and a single `_batch` mount
|
|
37
|
+
* whose *actual* kind varies per selected `operation` branch cannot carry
|
|
38
|
+
* that truthfully (architect review F1).
|
|
39
|
+
*/
|
|
40
|
+
export declare function groupBatchableOperationsByKind(operations: readonly Operation[], opts?: BatchMountOptions): Map<OperationKind, Operation[]>;
|
|
41
|
+
/** The input/output schema pair (+ discriminator, when applicable) for one `_batch/<kind>` mount. */
|
|
42
|
+
export interface BatchKindSchema {
|
|
43
|
+
input: JSONSchema;
|
|
44
|
+
output: JSONSchema;
|
|
45
|
+
/** Present only when ≥2 operations share this kind (a 1-branch `oneOf` is not a union). */
|
|
46
|
+
discriminator?: InlineDiscriminator;
|
|
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
|
+
export declare function buildBatchKindSchema(ops: readonly Operation[]): BatchKindSchema;
|
|
64
|
+
/**
|
|
65
|
+
* One synthetic `_batch/<kind>` operation shape, minus `handler` — mounting
|
|
66
|
+
* a real, request-servable handler requires wiring `invokeBatch`
|
|
67
|
+
* (`@adhd/apigen-engine-runtime`), which is TS-runtime plumbing this
|
|
68
|
+
* (host-agnostic) module deliberately does not depend on. A `MountCapability`
|
|
69
|
+
* implementation composes `{ ...op, handler: ... }` using this fragment plus
|
|
70
|
+
* its own `invokeBatch` wiring (see BATCH_0.0.1.md §3/§5).
|
|
71
|
+
*/
|
|
72
|
+
export type BatchKindOperation = Omit<MountedOperation, 'handler'> & {
|
|
73
|
+
kind: OperationKind;
|
|
74
|
+
/** Ids of the operations this `_batch/<kind>` mount can fan out over. */
|
|
75
|
+
operationIds: string[];
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Derive one `_batch/<kind>` synthetic mount per distinct kind present in the
|
|
79
|
+
* batchable operation set (F1). Refuses to mount anything for a descriptor
|
|
80
|
+
* with zero batchable operations (empty array — §1.1's "edge case the 0.0.1
|
|
81
|
+
* draft missed" restated per-kind rather than for a single global mount).
|
|
82
|
+
*
|
|
83
|
+
* `kind`/`safe` are truthful per mount (query kinds are `safe: true`;
|
|
84
|
+
* everything else defaults `safe: false` per the existing `Operation.safe`
|
|
85
|
+
* default-from-kind rule) — never the single hardcoded
|
|
86
|
+
* `kind:'action', safe:false` the 0.0.1 draft proposed for one omnibus
|
|
87
|
+
* `_batch` mount (architect review F1).
|
|
88
|
+
*/
|
|
89
|
+
export declare function buildBatchMountedOperations(descriptor: Descriptor, opts?: BatchMountOptions): BatchKindOperation[];
|
package/lib/plugin.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PluginLanguage } from './types';
|
|
2
|
-
import { Operation, JSONSchema } from './descriptor';
|
|
2
|
+
import { Operation, OperationKind, JSONSchema, TypeText } from './descriptor';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* The four canonical transport carriers apigen knows about (SPEC §5/§7/§9.1).
|
|
@@ -248,6 +248,49 @@ export interface LayerCapability {
|
|
|
248
248
|
*/
|
|
249
249
|
layer(call: Call, next: Next): Promise<Result> | AsyncIterable<Chunk>;
|
|
250
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Runtime dispatch options a {@link MountHostBridge} hands back to a mount
|
|
253
|
+
* plugin's handler — structurally equivalent to `apigen-engine-runtime`'s
|
|
254
|
+
* real `InvokeOptions` (duck-typed here, never imported: `apigen-core-client`
|
|
255
|
+
* is a lower tier than `apigen-engine-runtime` and must never depend upward
|
|
256
|
+
* on it — batch-rollout architect review, Finding 3).
|
|
257
|
+
*/
|
|
258
|
+
export interface MountHostBridgeInvokeOptions {
|
|
259
|
+
/** The live function table (op-id → implementation), spanning EVERY package the host serves. */
|
|
260
|
+
fns: Record<string, (...args: unknown[]) => unknown>;
|
|
261
|
+
/** Composed schemas (op-id → schema), spanning every package the host serves. */
|
|
262
|
+
schemas: Record<string, unknown>;
|
|
263
|
+
/** Optional client factory (session-ctx middleware), when the host has one. */
|
|
264
|
+
createClient?: (envelope: Record<string, unknown>) => Promise<unknown>;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* A host-supplied bridge letting a `MountCapability`'s handler invoke OTHER
|
|
268
|
+
* operations already registered on that host — the missing piece a fan-out
|
|
269
|
+
* mount (e.g. `_batch/<kind>`) needs that a pure mount like `/meta/health` or
|
|
270
|
+
* `/meta/openapi` never did (batch-rollout design note + architect review).
|
|
271
|
+
*
|
|
272
|
+
* `invoke`/`invokeOptions` are structurally equivalent to
|
|
273
|
+
* `apigen-engine-runtime`'s real `InvokeFn`/`InvokeOptions` pair so a plugin
|
|
274
|
+
* can hand them straight to `invokeBatch(hostBridge.invoke, fnName, calls,
|
|
275
|
+
* hostBridge.invokeOptions, batchOptions)` with zero adapter shim (Finding 1
|
|
276
|
+
* of the review) — the tier boundary is preserved by keeping this an inline
|
|
277
|
+
* structural type, never an import from `apigen-engine-runtime` (Finding 3).
|
|
278
|
+
*/
|
|
279
|
+
export interface MountHostBridge {
|
|
280
|
+
/**
|
|
281
|
+
* Invoke `fnName` (any operation id already registered on this host, from
|
|
282
|
+
* any package) with the given domain call, through the SAME composed
|
|
283
|
+
* `--use` Layer stack (auth/logging/validate) every other request goes
|
|
284
|
+
* through — never a bypass of it.
|
|
285
|
+
*/
|
|
286
|
+
invoke(fnName: string, call: {
|
|
287
|
+
domainArgs: Record<string, unknown>;
|
|
288
|
+
envelope: Record<string, unknown>;
|
|
289
|
+
signal?: AbortSignal;
|
|
290
|
+
}, opts: MountHostBridgeInvokeOptions): Promise<unknown>;
|
|
291
|
+
/** The host's merged, package-spanning `InvokeOptions`-equivalent — pass straight to `invokeBatch`. */
|
|
292
|
+
invokeOptions: MountHostBridgeInvokeOptions;
|
|
293
|
+
}
|
|
251
294
|
/**
|
|
252
295
|
* **`mount`** capability — add synthetic operations to the descriptor
|
|
253
296
|
* (SPEC §7.1 / §7.2b / §7.2c).
|
|
@@ -255,7 +298,7 @@ export interface LayerCapability {
|
|
|
255
298
|
* A `MountCapability` is loaded via `--use <plugin>` and contributes extra
|
|
256
299
|
* `Operation`-like entries (with an in-process `handler`) that flow through the
|
|
257
300
|
* harness and Layer stack exactly like extracted operations. Typical uses:
|
|
258
|
-
* `/meta/openapi`, `/meta/health`, version endpoints.
|
|
301
|
+
* `/meta/openapi`, `/meta/health`, version endpoints, `_batch/<kind>` fan-out.
|
|
259
302
|
*/
|
|
260
303
|
export interface MountCapability {
|
|
261
304
|
/**
|
|
@@ -264,11 +307,18 @@ export interface MountCapability {
|
|
|
264
307
|
* `MountedOperation` extends `Operation` with an in-process `handler` and
|
|
265
308
|
* an optional `transports` filter (default: all transports).
|
|
266
309
|
*
|
|
267
|
-
* @param descriptor
|
|
268
|
-
* @param opts
|
|
310
|
+
* @param descriptor - The current merged descriptor (read-only).
|
|
311
|
+
* @param opts - Plugin-specific options.
|
|
312
|
+
* @param hostBridge - (BATCH_0.0.1.md §2/§F1 rollout) Optional, additive —
|
|
313
|
+
* a host-supplied bridge letting the returned operations' handlers invoke
|
|
314
|
+
* OTHER operations on this host (e.g. a `_batch/<kind>` fan-out target).
|
|
315
|
+
* Omitted by plugins (like `health`/`openapi`) that never need it; a
|
|
316
|
+
* plugin whose handler DOES need cross-op invocation (batch) must throw a
|
|
317
|
+
* clear error if it's undefined rather than silently no-op, since that
|
|
318
|
+
* means the host hasn't wired hostBridge support yet.
|
|
269
319
|
* @returns Array of `MountedOperation`s; may be empty.
|
|
270
320
|
*/
|
|
271
|
-
operations(descriptor: Descriptor, opts?: Record<string, unknown
|
|
321
|
+
operations(descriptor: Descriptor, opts?: Record<string, unknown>, hostBridge?: MountHostBridge): MountedOperation[];
|
|
272
322
|
}
|
|
273
323
|
/**
|
|
274
324
|
* A synthetic operation contributed by a {@link MountCapability}.
|
|
@@ -296,6 +346,50 @@ export type MountedOperation = Operation & {
|
|
|
296
346
|
*/
|
|
297
347
|
handler(call: Call): unknown | Promise<unknown> | AsyncIterable<Chunk>;
|
|
298
348
|
};
|
|
349
|
+
/**
|
|
350
|
+
* Overridable fields accepted by {@link syntheticOp}. Every field has a safe
|
|
351
|
+
* default so a minimal mount (like health's `_meta/health`) needs to specify
|
|
352
|
+
* only what actually differs from a zero-input, zero-output `action`.
|
|
353
|
+
*/
|
|
354
|
+
export interface SyntheticOpFields {
|
|
355
|
+
/** @default 'action' */
|
|
356
|
+
kind?: OperationKind;
|
|
357
|
+
/** @default `kind === 'query'` (mirrors {@link Operation.safe}'s own default-from-kind rule) */
|
|
358
|
+
safe?: boolean;
|
|
359
|
+
/** @default false */
|
|
360
|
+
async?: boolean;
|
|
361
|
+
/** @default false */
|
|
362
|
+
streaming?: boolean;
|
|
363
|
+
/** @default {} */
|
|
364
|
+
input?: JSONSchema;
|
|
365
|
+
/** @default {} */
|
|
366
|
+
output?: JSONSchema;
|
|
367
|
+
/** @default {} */
|
|
368
|
+
envelope?: JSONSchema;
|
|
369
|
+
/** @default null */
|
|
370
|
+
typeText?: TypeText | null;
|
|
371
|
+
/** @default undefined (all transports) */
|
|
372
|
+
transports?: Transport[];
|
|
373
|
+
hasCtx?: boolean;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Build the common ~10-field {@link Operation} boilerplate for a synthetic
|
|
377
|
+
* mount-contributed operation (F2 — `apigen-core-client/src/lib/plugin.ts`
|
|
378
|
+
* JSDoc previously referenced this as though it already existed; it did not).
|
|
379
|
+
*
|
|
380
|
+
* `id` is `"<namespace>/<path...>"` (e.g. `'_meta/health'`, `'_batch/query'`).
|
|
381
|
+
* Per the existing `_meta/*` mount convention (health/openapi, pre-retrofit),
|
|
382
|
+
* a leading run of underscores on the first segment marks a synthetic
|
|
383
|
+
* namespace in the *id* but is stripped when tokenizing the namespace
|
|
384
|
+
* {@link Segment} itself — `'_meta/health'` → `namespace: {raw:'meta',…}`,
|
|
385
|
+
* `path: [{raw:'health',…}]`, matching what both plugins already produced
|
|
386
|
+
* by hand.
|
|
387
|
+
*
|
|
388
|
+
* Returns everything `MountedOperation` needs except `handler` — the caller
|
|
389
|
+
* supplies that (it's the one part that's genuinely plugin-specific) via
|
|
390
|
+
* `{ ...syntheticOp(id, descriptor, fields), handler: ... }`.
|
|
391
|
+
*/
|
|
392
|
+
export declare function syntheticOp(id: string, descriptor: Descriptor, fields?: SyntheticOpFields): Omit<MountedOperation, 'handler'>;
|
|
299
393
|
/**
|
|
300
394
|
* **`envelope`** capability — declare request/response side-channel fields
|
|
301
395
|
* (SPEC §7.1 / §9 / §9.1).
|