@adhd/apigen-core 0.1.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/README.md +38 -0
- package/index.d.ts +10 -0
- package/index.js +31412 -0
- package/index.mjs +380115 -0
- package/lib/apigen-core.d.ts +1 -0
- package/lib/compose-schemas.d.ts +16 -0
- package/lib/descriptor.d.ts +202 -0
- package/lib/extract-classes.d.ts +30 -0
- package/lib/extract.d.ts +34 -0
- package/lib/extractors/default-export.d.ts +8 -0
- package/lib/extractors/named-object.d.ts +8 -0
- package/lib/extractors/named.d.ts +17 -0
- package/lib/generate-schemas.d.ts +9 -0
- package/lib/plugin.d.ts +433 -0
- package/lib/schema-builders/morph-fallback.d.ts +2 -0
- package/lib/schema-builders/ts-json-schema.d.ts +4 -0
- package/lib/types.d.ts +68 -0
- package/package.json +15 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function apigenCore(): string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { GeneratedSchemas, ComposedSchemas } from './types';
|
|
2
|
+
|
|
3
|
+
interface SlimMiddleware {
|
|
4
|
+
id: string;
|
|
5
|
+
envelope?: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Merges domain schemas with middleware envelope fields.
|
|
9
|
+
*
|
|
10
|
+
* The `data: {}` wrapper is **always present** even for zero-param functions
|
|
11
|
+
* [inv:data-wrapper-always-present]. Override a middleware with `false` to
|
|
12
|
+
* suppress its envelope contribution for a specific function
|
|
13
|
+
* [inv:false-suppresses-middleware].
|
|
14
|
+
*/
|
|
15
|
+
export declare function composeSchemas(domainSchemas: GeneratedSchemas, middlewares: ReadonlyArray<SlimMiddleware>, overrides?: Record<string, Record<string, boolean>>): ComposedSchemas;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A JSON-Schema-2020-12 document fragment.
|
|
3
|
+
*
|
|
4
|
+
* The apigen type IR **is** JSON Schema 2020-12 with `$defs`/`$ref` — there is
|
|
5
|
+
* no separate or abstract type model and no new IR (SPEC §4, §16). Named types,
|
|
6
|
+
* discriminated unions / enum-with-data / `Result`/`Option` (`oneOf` + a `const`
|
|
7
|
+
* tag + `$ref`), nominal/branded types (a named `$def`), and recursion (`$ref`)
|
|
8
|
+
* are all represented here faithfully — exactly what `ts-json-schema-generator`
|
|
9
|
+
* and `schemars` already emit.
|
|
10
|
+
*
|
|
11
|
+
* Conventions baked into this IR:
|
|
12
|
+
* - **Big-int / decimal wire convention:** 64-bit integers and decimals exceed
|
|
13
|
+
* JSON's `f64`, so they are **string-encoded** as `{ type: 'string', format:
|
|
14
|
+
* 'int64' }` (a serialization convention, not a schema-expressiveness gap).
|
|
15
|
+
* - **Optional extractor-derived hints** live under `x-apigen-*` keys (see
|
|
16
|
+
* {@link ApigenSchemaHints}) and exist only so codegen can emit idiomatic
|
|
17
|
+
* (vs accurate-but-verbose) clients; they are never required for correctness
|
|
18
|
+
* and are never sourced from a source annotation (Tenet 1).
|
|
19
|
+
*
|
|
20
|
+
* Modeled as an open record because a JSON Schema is, structurally, an arbitrary
|
|
21
|
+
* keyword object. This mirrors how `input`/`output` are already typed in
|
|
22
|
+
* {@link GeneratedSchemas} / {@link ComposedSchemas} (`Record<string, unknown>`).
|
|
23
|
+
*/
|
|
24
|
+
export type JSONSchema = Record<string, unknown> & {
|
|
25
|
+
/** Reusable type definitions referenced via `$ref` (`#/$defs/Name`). */
|
|
26
|
+
$defs?: Record<string, JSONSchema>;
|
|
27
|
+
/** Reference to a definition, e.g. `#/$defs/User` (enables recursion). */
|
|
28
|
+
$ref?: string;
|
|
29
|
+
} & ApigenSchemaHints;
|
|
30
|
+
/**
|
|
31
|
+
* Optional, **extractor-derived** codegen hints carried inline on a
|
|
32
|
+
* {@link JSONSchema} (SPEC §4). These are advisory: a plugin MAY use them to
|
|
33
|
+
* emit idiomatic clients (e.g. a real enum, a branded type) and a codegen MAY
|
|
34
|
+
* warn on an unresolved generic — but correctness never depends on them, and
|
|
35
|
+
* they are never required. They are computed by the extractor, never written by
|
|
36
|
+
* a human in source (Tenet 1).
|
|
37
|
+
*/
|
|
38
|
+
export interface ApigenSchemaHints {
|
|
39
|
+
/**
|
|
40
|
+
* Marks a `$def` that originated from a nominal / branded type. Validation
|
|
41
|
+
* deliberately does NOT enforce nominality — on the wire a branded type *is*
|
|
42
|
+
* its base type — but codegen can re-introduce the brand for ergonomics.
|
|
43
|
+
*/
|
|
44
|
+
'x-apigen-nominal'?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* How an enum-like type should be represented in idiomatic codegen, e.g. a
|
|
47
|
+
* native enum vs a string-literal union. Advisory only.
|
|
48
|
+
*/
|
|
49
|
+
'x-apigen-enum-repr'?: 'enum' | 'union' | string;
|
|
50
|
+
/**
|
|
51
|
+
* Fidelity of this schema fragment relative to the source type:
|
|
52
|
+
* - `'full'` — the schema captures the source type without loss.
|
|
53
|
+
* - `'lossy'` — the source type could not be fully represented (the only
|
|
54
|
+
* true residual is generic *factoring*: an unconstrained generic operation
|
|
55
|
+
* isn't serializable, so it is out of scope by physics). Codegen MAY warn.
|
|
56
|
+
*
|
|
57
|
+
* Optional; absence means `'full'`.
|
|
58
|
+
*/
|
|
59
|
+
fidelity?: 'full' | 'lossy';
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A casing-neutral name segment (SPEC §4/§5).
|
|
63
|
+
*
|
|
64
|
+
* Identity is carried by the tokenized `words`; the original `raw` spelling is
|
|
65
|
+
* preserved so a same-host plugin can reproduce it, but every transport derives
|
|
66
|
+
* its own casing from `words` via `@adhd/apigen-naming` (kebab for HTTP/CLI,
|
|
67
|
+
* `_`-joined for MCP, Pascal for gRPC). Casing is therefore per-plugin, never
|
|
68
|
+
* baked into the descriptor.
|
|
69
|
+
*/
|
|
70
|
+
export interface Segment {
|
|
71
|
+
/** Original spelling as it appeared in source, e.g. `'humanizeBytes'`. */
|
|
72
|
+
raw: string;
|
|
73
|
+
/** Tokenized, lower-cased words, e.g. `['humanize', 'bytes']`. */
|
|
74
|
+
words: string[];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Language-tagged textual rendering of a type — optional same-host *sugar*
|
|
78
|
+
* (SPEC §4). For a TypeScript host this is the literal TS source of the type;
|
|
79
|
+
* non-host targets ignore it and rely on {@link JSONSchema} (`input`/`output`)
|
|
80
|
+
* instead. `null` when no textual form is available/relevant.
|
|
81
|
+
*/
|
|
82
|
+
export interface TypeText {
|
|
83
|
+
/** Language tag for `input`/`output` text, e.g. `'ts'`. */
|
|
84
|
+
lang: string;
|
|
85
|
+
/** Textual rendering of the input/params type. */
|
|
86
|
+
input: string;
|
|
87
|
+
/** Textual rendering of the output/return type (unwrapped). */
|
|
88
|
+
output: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Classification of an exported binding (SPEC §4).
|
|
92
|
+
*
|
|
93
|
+
* - `'action'` — a callable export (function declaration, or an
|
|
94
|
+
* arrow/const function). Served by invoking it.
|
|
95
|
+
* - `'query'` — a **serializable-data** const (primitive or plain
|
|
96
|
+
* serializable object/array — no functions / non-serializable values). Served
|
|
97
|
+
* **live**: the descriptor carries the const's *type* (schema), not its value,
|
|
98
|
+
* so env-/compute-dependent consts are never stale-at-extract.
|
|
99
|
+
* - `'constructor'` — a class constructor (class export; see SPEC §10).
|
|
100
|
+
* - `'instance-method'` — a method on an exported class instance (SPEC §10).
|
|
101
|
+
*
|
|
102
|
+
* A non-serializable, non-callable export is **skipped + warned**, never
|
|
103
|
+
* emitted as an operation.
|
|
104
|
+
*/
|
|
105
|
+
export type OperationKind = 'action' | 'query' | 'constructor' | 'instance-method';
|
|
106
|
+
/**
|
|
107
|
+
* The canonical operation descriptor — the neutral contract every extractor
|
|
108
|
+
* emits and every plugin consumes (SPEC §4).
|
|
109
|
+
*
|
|
110
|
+
* One `Operation` corresponds to one selected export (an `action`, a live
|
|
111
|
+
* `query`, or a class member per §10). The descriptor is host-agnostic: a
|
|
112
|
+
* TypeScript extractor, a Python extractor, and a Rust extractor all emit the
|
|
113
|
+
* same shape, and a single plugin can project a merged set of `Operation`s into
|
|
114
|
+
* any transport.
|
|
115
|
+
*
|
|
116
|
+
* @remarks
|
|
117
|
+
* The type IR for `input`/`output`/`envelope` is JSON Schema 2020-12 with
|
|
118
|
+
* `$defs`/`$ref` (see {@link JSONSchema}) — there is no separate IR.
|
|
119
|
+
*/
|
|
120
|
+
export interface Operation {
|
|
121
|
+
/**
|
|
122
|
+
* The canonical fully-qualified slug and cross-plugin reference key, derived
|
|
123
|
+
* purely from `namespace`/`path`, e.g. `'transform/humanize/humanize-bytes'`.
|
|
124
|
+
* Globally unique within a merged descriptor and never re-cased.
|
|
125
|
+
*
|
|
126
|
+
* **Deterministic, NOT refactor-stable.** Because `id` is a pure function of
|
|
127
|
+
* `namespace`/`path`, the same source always yields the same `id` — but moving
|
|
128
|
+
* or renaming a file/export re-mints it (and thus breaks any pinned
|
|
129
|
+
* `--exclude` id or generated client that referenced the old slug). This is
|
|
130
|
+
* accepted and documented; refactor-stability is an explicit **non-goal**. It
|
|
131
|
+
* is deliberately **not** papered over with a source `@id` annotation, which
|
|
132
|
+
* Tenet 1 forbids.
|
|
133
|
+
*/
|
|
134
|
+
id: string;
|
|
135
|
+
/**
|
|
136
|
+
* The owning language runtime / host for this operation, e.g. `'ts'`,
|
|
137
|
+
* `'py'`, `'rust'`. Identifies which host's extractor produced it and which
|
|
138
|
+
* host can serve it same-process. Polyglot descriptors mix hosts.
|
|
139
|
+
*/
|
|
140
|
+
host: string;
|
|
141
|
+
/**
|
|
142
|
+
* The package segment — sourced from `--namespace` or the tsconfig folder
|
|
143
|
+
* (SPEC §4/§5). Casing-neutral; transports derive their own casing from
|
|
144
|
+
* {@link Segment.words}.
|
|
145
|
+
*/
|
|
146
|
+
namespace: Segment;
|
|
147
|
+
/**
|
|
148
|
+
* The hierarchical identity path: file → export… (SPEC §4/§5). For example a
|
|
149
|
+
* named export is `[file, name]`; a single default function is `[file]`; a
|
|
150
|
+
* default *object* is `[file, 'default', ...keys]` recursing into nested
|
|
151
|
+
* props. `index.*` drops its file segment. Each element is casing-neutral.
|
|
152
|
+
*/
|
|
153
|
+
path: Segment[];
|
|
154
|
+
/** Classification of the underlying export — see {@link OperationKind}. */
|
|
155
|
+
kind: OperationKind;
|
|
156
|
+
/** True if the export is async (returns a `Promise`). */
|
|
157
|
+
async: boolean;
|
|
158
|
+
/**
|
|
159
|
+
* True if the export returns an `AsyncIterable` / `Generator` / `Stream`
|
|
160
|
+
* (streaming is implemented in v2, SPEC §11). The `output` schema describes
|
|
161
|
+
* the per-chunk element type with streaming unwrapped.
|
|
162
|
+
*/
|
|
163
|
+
streaming: boolean;
|
|
164
|
+
/**
|
|
165
|
+
* Idempotent / no-side-effects hint (SPEC §4/§5).
|
|
166
|
+
*
|
|
167
|
+
* **Defaults from `kind`** (`query` → `true`, `action` → `false`) and is
|
|
168
|
+
* **overridable at projection time via config** (`--opt http.verb.<id>=GET`
|
|
169
|
+
* or `apigen.config`), never via a source annotation (Tenet 1). Drives the
|
|
170
|
+
* HTTP verb + cacheability (safe → GET, unsafe → POST) and gRPC
|
|
171
|
+
* idempotency-level (SPEC §5), decoupling the wire method from `kind`.
|
|
172
|
+
*/
|
|
173
|
+
safe: boolean;
|
|
174
|
+
/**
|
|
175
|
+
* The params object as JSON Schema 2020-12 (see {@link JSONSchema}).
|
|
176
|
+
*
|
|
177
|
+
* This is the operation's input type directly: the `ctx` first param is
|
|
178
|
+
* excluded (by name match only, per the `ctx-name-only` invariant) and the
|
|
179
|
+
* middleware `data`-wrapper is **dissolved** — `input` is the bare domain
|
|
180
|
+
* params object, not the composed envelope. The request-side envelope lives
|
|
181
|
+
* separately in `envelope`.
|
|
182
|
+
*/
|
|
183
|
+
input: JSONSchema;
|
|
184
|
+
/**
|
|
185
|
+
* The return type as JSON Schema 2020-12 (see {@link JSONSchema}), with
|
|
186
|
+
* `Promise<T>` and stream wrappers **unwrapped to `T`**. For a streaming
|
|
187
|
+
* operation this is the per-chunk element type.
|
|
188
|
+
*/
|
|
189
|
+
output: JSONSchema;
|
|
190
|
+
/**
|
|
191
|
+
* The effective request-side envelope as JSON Schema 2020-12 — the
|
|
192
|
+
* middleware-contributed side-channel for this operation (e.g. a `session`
|
|
193
|
+
* field), merged across the active middleware with overrides applied. Empty
|
|
194
|
+
* (no envelope fields) when no middleware contributes to this operation.
|
|
195
|
+
*/
|
|
196
|
+
envelope: JSONSchema;
|
|
197
|
+
/**
|
|
198
|
+
* Optional language-tagged textual type rendering (same-host sugar). `null`
|
|
199
|
+
* when unavailable; non-host targets ignore it. See {@link TypeText}.
|
|
200
|
+
*/
|
|
201
|
+
typeText: TypeText | null;
|
|
202
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Operation } from './descriptor';
|
|
2
|
+
|
|
3
|
+
export interface ExtractClassesOptions {
|
|
4
|
+
/** Absolute path to the TypeScript (or JavaScript) source file. */
|
|
5
|
+
sourceFile: string;
|
|
6
|
+
/**
|
|
7
|
+
* Namespace segment (from `--namespace` or tsconfig folder). Casing-neutral
|
|
8
|
+
* words are derived from the raw string. Defaults to `''`.
|
|
9
|
+
*/
|
|
10
|
+
namespace?: string;
|
|
11
|
+
/** Absolute path to a tsconfig.json for type resolution. Optional. */
|
|
12
|
+
tsconfig?: string;
|
|
13
|
+
/**
|
|
14
|
+
* When true, extract constructor + instance-method ops in addition to static
|
|
15
|
+
* method ops. Off by default (opt-in per SPEC §10).
|
|
16
|
+
*/
|
|
17
|
+
includeInstances?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Walks `opts.sourceFile` and emits canonical `Operation[]` descriptors for
|
|
21
|
+
* all exported class members per SPEC §10.
|
|
22
|
+
*
|
|
23
|
+
* Static methods are always extracted (they are stateless function-shaped ops).
|
|
24
|
+
* Constructor + instance methods require `opts.includeInstances = true`.
|
|
25
|
+
*
|
|
26
|
+
* @param opts - Extraction options.
|
|
27
|
+
* @returns Resolved array of canonical operations (static ops + optionally
|
|
28
|
+
* constructor + instance-method ops).
|
|
29
|
+
*/
|
|
30
|
+
export declare function extractClasses(opts: ExtractClassesOptions): Promise<Operation[]>;
|
package/lib/extract.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Operation } from './descriptor';
|
|
2
|
+
|
|
3
|
+
export interface ExtractOptions {
|
|
4
|
+
/** Absolute path to the TypeScript (or JavaScript) source file. */
|
|
5
|
+
sourceFile: string;
|
|
6
|
+
/**
|
|
7
|
+
* Namespace segment (from `--namespace` or tsconfig folder). Casing-neutral
|
|
8
|
+
* words are derived from the raw string. Defaults to `''`.
|
|
9
|
+
*/
|
|
10
|
+
namespace?: string;
|
|
11
|
+
/** Absolute path to a tsconfig.json for type resolution. Optional. */
|
|
12
|
+
tsconfig?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Walks `opts.sourceFile` and emits canonical `Operation[]` descriptors.
|
|
16
|
+
*
|
|
17
|
+
* Handles all six shapes in the export-shape matrix. Each operation is named
|
|
18
|
+
* by the **exported symbol** in source — never by position or internal name.
|
|
19
|
+
*
|
|
20
|
+
* @param opts - Extraction options.
|
|
21
|
+
* @returns Resolved array of canonical operations.
|
|
22
|
+
*/
|
|
23
|
+
export declare function extract(opts: ExtractOptions): Promise<Operation[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Tokenises a camelCase / PascalCase / kebab-case / snake_case identifier into
|
|
26
|
+
* lower-cased words. Used to build casing-neutral {@link Segment} records.
|
|
27
|
+
*
|
|
28
|
+
* Examples:
|
|
29
|
+
* 'humanizeBytes' → ['humanize', 'bytes']
|
|
30
|
+
* 'HTMLParser' → ['html', 'parser']
|
|
31
|
+
* 'my-util' → ['my', 'util']
|
|
32
|
+
* 'SOME_CONST' → ['some', 'const']
|
|
33
|
+
*/
|
|
34
|
+
export declare function tokenize(raw: string): string[];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { FnMeta } from './named';
|
|
2
|
+
import { SourceFile } from 'ts-morph';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Mode 2: read `export default { ... }` object literal; each property whose
|
|
6
|
+
* value is a function becomes an endpoint. Skips non-function properties.
|
|
7
|
+
*/
|
|
8
|
+
export declare function extractDefault(sf: SourceFile): FnMeta[];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { FnMeta } from './named';
|
|
2
|
+
import { SourceFile } from 'ts-morph';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Mode 3: find `export const <name> = { ... }`; each property whose value
|
|
6
|
+
* is a function becomes an endpoint. Skips non-function properties.
|
|
7
|
+
*/
|
|
8
|
+
export declare function extractNamedObject(sf: SourceFile, exportName: string): FnMeta[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { SourceFile } from 'ts-morph';
|
|
2
|
+
|
|
3
|
+
/** Metadata for a single extracted function. */
|
|
4
|
+
export type FnMeta = {
|
|
5
|
+
name: string;
|
|
6
|
+
params: {
|
|
7
|
+
name: string;
|
|
8
|
+
type: string;
|
|
9
|
+
optional: boolean;
|
|
10
|
+
}[];
|
|
11
|
+
returnType: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Mode 1: enumerate exported function declarations + exported function-valued consts.
|
|
15
|
+
* Silently skips non-function exports (e.g. `export const VERSION = '1.0.0'`).
|
|
16
|
+
*/
|
|
17
|
+
export declare function extractNamed(sf: SourceFile): FnMeta[];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { GenerateSchemasOptions, GeneratedSchemas } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reads a TypeScript source file and returns `GeneratedSchemas` — domain schemas only,
|
|
5
|
+
* no middleware envelope. Supports three extraction modes: named, default, named-object.
|
|
6
|
+
*
|
|
7
|
+
* @param opts - Extraction options including source file path, export mode, namespace, phase
|
|
8
|
+
*/
|
|
9
|
+
export declare function generateSchemas(opts: GenerateSchemasOptions): Promise<GeneratedSchemas>;
|