@adhd/apigen-core-client 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/CHANGELOG.md +31 -0
- package/README.md +238 -0
- package/index.d.ts +14 -0
- package/index.js +1 -0
- package/index.mjs +1618 -0
- package/lib/apigen-core.d.ts +1 -0
- package/lib/compose-schemas.d.ts +31 -0
- package/lib/descriptor.d.ts +220 -0
- package/lib/extract-classes.d.ts +37 -0
- package/lib/extract.d.ts +43 -0
- package/lib/extraction-session.d.ts +97 -0
- package/lib/get-safety.d.ts +14 -0
- package/lib/param-defaults.d.ts +36 -0
- package/lib/plugin.d.ts +446 -0
- package/lib/schema-builders/map-set-tuple.d.ts +19 -0
- package/lib/schema-builders/morph-fallback.d.ts +13 -0
- package/lib/schema-builders/morph-walk.d.ts +59 -0
- package/lib/schema-builders/nominal.d.ts +95 -0
- package/lib/schema-builders/ts-json-schema.d.ts +65 -0
- package/lib/schema-builders/union.d.ts +74 -0
- package/lib/source-language.d.ts +81 -0
- package/lib/types.d.ts +87 -0
- package/package.json +17 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { InternalExtractionSession } from '../extraction-session';
|
|
2
|
+
import { Project, SourceFile } from 'ts-morph';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Canonical JSON-Schema fragments for TS built-in scalar types.
|
|
6
|
+
* Keyed by the exact type-text string the extractor emits.
|
|
7
|
+
*
|
|
8
|
+
* Mappings follow §3 / §12–13 of the apigen-logical-types DESIGN:
|
|
9
|
+
* Date → format:date-time (RFC 3339 UTC; Date.prototype.toJSON already emits this)
|
|
10
|
+
* bigint → format:int64 (decimal string to avoid JS f64 precision loss)
|
|
11
|
+
* Uint8Array → format:byte (base64 standard + padding)
|
|
12
|
+
* Buffer → format:byte (Node.js Buffer; same wire as Uint8Array)
|
|
13
|
+
* URL → format:uri
|
|
14
|
+
* RegExp → format:regex
|
|
15
|
+
* Decimal → format:decimal (decimal.js Decimal; arbitrary-precision decimal string)
|
|
16
|
+
*
|
|
17
|
+
* Map / Set are handled in later states (lt-extract-nominal / lt-scalars).
|
|
18
|
+
*
|
|
19
|
+
* NOTE on Decimal: ts-morph emits different type-text strings for the same
|
|
20
|
+
* Decimal class depending on how the user imports it:
|
|
21
|
+
* - `import { Decimal } from 'decimal.js'` → `"Decimal"` (keyed directly below)
|
|
22
|
+
* - `import Decimal from 'decimal.js'` → qualified import path like
|
|
23
|
+
* `import("/path/to/decimal.js/decimal").default`
|
|
24
|
+
* - `import { Decimal as D2 } from 'decimal.js'` / `import D2 from 'decimal.js'`
|
|
25
|
+
* → `"D2"` (local alias)
|
|
26
|
+
* All forms are normalised to the "Decimal" key before this map is consulted:
|
|
27
|
+
* - qualified-import form: handled by `normalizeTypeText` regex
|
|
28
|
+
* - alias form: handled by the alias map from `extractScalarAliases`
|
|
29
|
+
* See `normalizeTypeText` and `extractScalarAliases`.
|
|
30
|
+
*/
|
|
31
|
+
export declare const SCALAR_SCHEMAS: Readonly<Record<string, Record<string, unknown>>>;
|
|
32
|
+
/**
|
|
33
|
+
* Scan a source file's import declarations and return a map of
|
|
34
|
+
* `localName → canonicalScalarKey`
|
|
35
|
+
* for any import whose module specifier is in MODULE_SCALAR_MAP and whose
|
|
36
|
+
* local name differs from the canonical key.
|
|
37
|
+
*
|
|
38
|
+
* Examples:
|
|
39
|
+
* `import { Decimal as D2 } from 'decimal.js'` → { D2: 'Decimal' }
|
|
40
|
+
* `import MyDec from 'decimal.js'` → { MyDec: 'Decimal' }
|
|
41
|
+
* `import Decimal from 'decimal.js'` → {} (no alias needed)
|
|
42
|
+
* `import { Decimal } from 'decimal.js'` → {} (no alias needed)
|
|
43
|
+
*
|
|
44
|
+
* The returned map is consumed by normalizeTypeText and morphFallback so that
|
|
45
|
+
* aliased external scalar types are recognised at ANY nesting depth.
|
|
46
|
+
*/
|
|
47
|
+
export declare function extractScalarAliases(sf: SourceFile): ReadonlyMap<string, string>;
|
|
48
|
+
/**
|
|
49
|
+
* Attempts ts-json-schema-generator first; falls back to morphFallback for
|
|
50
|
+
* inline/anonymous types.
|
|
51
|
+
*
|
|
52
|
+
* When `session` is supplied, results are memoized per
|
|
53
|
+
* `(sourceFile, tsconfig, typeText)` for the session's lifetime — the
|
|
54
|
+
* orchestrator's extract + generateSchemas double pass over the same file, and
|
|
55
|
+
* repeated parameter types across functions, all become Map hits. Cached
|
|
56
|
+
* fragments are shared by reference; callers treat schemas as immutable.
|
|
57
|
+
*
|
|
58
|
+
* BUG-APIGEN-CORE-003 (cache-stampede fix): `session.schemaCache` stores the
|
|
59
|
+
* in-flight/resolved `Promise<Schema>` itself — not just the resolved value
|
|
60
|
+
* — and stores it SYNCHRONOUSLY before anything is awaited, so concurrent
|
|
61
|
+
* identical requests (e.g. `morph-walk.ts`'s `Promise.all` over union
|
|
62
|
+
* variants) join ONE computation instead of each redundantly recomputing.
|
|
63
|
+
* See the in-function comments below for the mechanism.
|
|
64
|
+
*/
|
|
65
|
+
export declare function buildSchema(_project: Project, sf: SourceFile, typeText: string, tsconfig?: string, session?: InternalExtractionSession, _ancestors?: ReadonlySet<string>): Promise<Record<string, unknown>>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { X_APIGEN_LOGICAL } from '@adhd/apigen-base-logical';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One variant of a discriminated union.
|
|
5
|
+
*
|
|
6
|
+
* - `className` the PascalCase class name matching its `$defs` key
|
|
7
|
+
* (produced by `buildNominalSchema`'s `defKey` output).
|
|
8
|
+
* - `discriminantValue` the literal string value of the discriminant property
|
|
9
|
+
* on this variant (e.g. `"dog"` for `kind:"dog"`).
|
|
10
|
+
*/
|
|
11
|
+
export interface UnionVariant {
|
|
12
|
+
/** The class name — must match the `$defs` key produced by `nominal.ts`. */
|
|
13
|
+
className: string;
|
|
14
|
+
/**
|
|
15
|
+
* The literal discriminant value for this variant (the `const` value on the
|
|
16
|
+
* discriminant property in the variant's $def, e.g. `"dog"` for `kind:"dog"`).
|
|
17
|
+
*/
|
|
18
|
+
discriminantValue: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Everything `buildUnionSchema` needs to emit the union fragment.
|
|
22
|
+
*/
|
|
23
|
+
export interface UnionInfo {
|
|
24
|
+
/**
|
|
25
|
+
* Name of the shared const-tag property that discriminates the variants,
|
|
26
|
+
* e.g. `"kind"`.
|
|
27
|
+
*/
|
|
28
|
+
discriminatorPropertyName: string;
|
|
29
|
+
/** Ordered list of union variants; must contain at least two entries. */
|
|
30
|
+
variants: UnionVariant[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The JSON Schema fragment for a discriminated union per DESIGN §4.1:
|
|
34
|
+
*
|
|
35
|
+
* ```json
|
|
36
|
+
* {
|
|
37
|
+
* "oneOf": [ {"$ref":"#/$defs/Dog"}, {"$ref":"#/$defs/Cat"} ],
|
|
38
|
+
* "discriminator": {
|
|
39
|
+
* "propertyName": "kind",
|
|
40
|
+
* "mapping": { "dog": "#/$defs/Dog", "cat": "#/$defs/Cat" }
|
|
41
|
+
* },
|
|
42
|
+
* "x-apigen-logical": "union"
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export interface UnionSchema {
|
|
47
|
+
oneOf: Array<{
|
|
48
|
+
$ref: string;
|
|
49
|
+
}>;
|
|
50
|
+
discriminator: {
|
|
51
|
+
propertyName: string;
|
|
52
|
+
mapping: Record<string, string>;
|
|
53
|
+
};
|
|
54
|
+
[X_APIGEN_LOGICAL]: 'union';
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Given a discriminated-union descriptor, emit the canonical OpenAPI-compatible
|
|
58
|
+
* `oneOf` + `discriminator` + `x-apigen-logical:"union"` schema fragment.
|
|
59
|
+
*
|
|
60
|
+
* Each variant is referenced via `$ref` (`#/$defs/<ClassName>`) — the caller is
|
|
61
|
+
* responsible for ensuring each variant's `$def` is registered in the descriptor
|
|
62
|
+
* (via `buildNominalSchema` from `nominal.ts`).
|
|
63
|
+
*
|
|
64
|
+
* Per DESIGN §4.1 `[inv:hints-advisory]`: `x-apigen-logical:"union"` is advisory.
|
|
65
|
+
* The structural contract (the `oneOf` + `discriminator`) is the authoritative
|
|
66
|
+
* wire representation and MUST be usable without the `x-apigen-*` key.
|
|
67
|
+
*
|
|
68
|
+
* @throws {Error} When `variants` is empty or contains a single entry (a
|
|
69
|
+
* one-variant "union" is not a union).
|
|
70
|
+
*
|
|
71
|
+
* @param info - Discriminated union descriptor.
|
|
72
|
+
* @returns The `oneOf` + `discriminator` schema fragment for inline use.
|
|
73
|
+
*/
|
|
74
|
+
export declare function buildUnionSchema(info: UnionInfo): UnionSchema;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { PluginLanguage } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Derive the canonical {@link PluginLanguage} tag for a source file from its
|
|
5
|
+
* extension.
|
|
6
|
+
*
|
|
7
|
+
* Returns `undefined` when the extension is not recognised — callers should
|
|
8
|
+
* treat an unknown extension as "no plugin will consume this file" rather than
|
|
9
|
+
* guessing.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* languageOfSource('src/api.ts') // → 'ts'
|
|
14
|
+
* languageOfSource('src/api.tsx') // → 'ts'
|
|
15
|
+
* languageOfSource('src/api.py') // → 'py'
|
|
16
|
+
* languageOfSource('src/api.go') // → 'go'
|
|
17
|
+
* languageOfSource('README.md') // → undefined
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function languageOfSource(file: string): PluginLanguage | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* The minimal plugin shape that the routing helpers need to inspect.
|
|
23
|
+
*
|
|
24
|
+
* Both the v1 `OutputPlugin` and the v2 `Plugin` satisfy this interface
|
|
25
|
+
* because `language` is defined on both (as an optional field).
|
|
26
|
+
*/
|
|
27
|
+
export interface LanguageAwarePlugin {
|
|
28
|
+
/** @see {@link PluginLanguage} */
|
|
29
|
+
language?: PluginLanguage;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Return the effective language for a plugin — the declared `language` if set,
|
|
33
|
+
* or `'ts'` as the documented default for back-compat.
|
|
34
|
+
*
|
|
35
|
+
* @param plugin - Any plugin that may declare `language`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function effectiveLanguage(plugin: LanguageAwarePlugin): PluginLanguage;
|
|
38
|
+
/**
|
|
39
|
+
* Returns `true` when the given plugin should consume `file`.
|
|
40
|
+
*
|
|
41
|
+
* A plugin consumes a file when:
|
|
42
|
+
* 1. The file's extension maps to a known {@link PluginLanguage}, AND
|
|
43
|
+
* 2. That language matches the plugin's effective language (declared or
|
|
44
|
+
* defaulting to `'ts'`).
|
|
45
|
+
*
|
|
46
|
+
* Files with unrecognised extensions are never routed to any plugin.
|
|
47
|
+
*
|
|
48
|
+
* @param plugin - The plugin to test.
|
|
49
|
+
* @param file - Absolute or relative path to the source file.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* pluginConsumesSource({ language: 'ts' }, 'src/api.ts') // → true
|
|
54
|
+
* pluginConsumesSource({ language: 'ts' }, 'src/api.py') // → false
|
|
55
|
+
* pluginConsumesSource({ language: 'py' }, 'src/api.py') // → true
|
|
56
|
+
* pluginConsumesSource({}, 'src/api.ts') // → true (default 'ts')
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare function pluginConsumesSource(plugin: LanguageAwarePlugin, file: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Filter `files` to the subset whose language matches the given plugin.
|
|
62
|
+
*
|
|
63
|
+
* This is the primary entry-point for the `serve` command's dispatch loop:
|
|
64
|
+
* call once per plugin to obtain the slice of changed/watched files it should
|
|
65
|
+
* re-process.
|
|
66
|
+
*
|
|
67
|
+
* @param plugin - The plugin to route for.
|
|
68
|
+
* @param files - All candidate source files.
|
|
69
|
+
* @returns The subset of `files` the plugin should consume (may be empty).
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* const all = ['src/api.ts', 'src/api.py', 'src/utils.mts', 'README.md']
|
|
74
|
+
* sourcesForPlugin({ language: 'ts' }, all)
|
|
75
|
+
* // → ['src/api.ts', 'src/utils.mts']
|
|
76
|
+
*
|
|
77
|
+
* sourcesForPlugin({ language: 'py' }, all)
|
|
78
|
+
* // → ['src/api.py']
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function sourcesForPlugin(plugin: LanguageAwarePlugin, files: readonly string[]): string[];
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Operation } from './descriptor';
|
|
2
|
+
import { Logger } from 'pino';
|
|
3
|
+
|
|
4
|
+
export interface GeneratedSchemas {
|
|
5
|
+
metadata: {
|
|
6
|
+
namespace: string;
|
|
7
|
+
phase: string;
|
|
8
|
+
};
|
|
9
|
+
schemas: Record<string, {
|
|
10
|
+
input: Record<string, unknown>;
|
|
11
|
+
output: Record<string, unknown>;
|
|
12
|
+
hasCtx?: boolean;
|
|
13
|
+
safe?: boolean;
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
16
|
+
export type ComposedSchemas = Record<string, {
|
|
17
|
+
input: Record<string, unknown>;
|
|
18
|
+
output: Record<string, unknown>;
|
|
19
|
+
hasCtx?: boolean;
|
|
20
|
+
'x-apigen-safe'?: boolean;
|
|
21
|
+
}>;
|
|
22
|
+
export type ExportMode = {
|
|
23
|
+
type: 'named';
|
|
24
|
+
} | {
|
|
25
|
+
type: 'default';
|
|
26
|
+
} | {
|
|
27
|
+
type: 'named-object';
|
|
28
|
+
name: string;
|
|
29
|
+
};
|
|
30
|
+
export interface PluginInput {
|
|
31
|
+
packages: Array<{
|
|
32
|
+
id: string;
|
|
33
|
+
schemas: ComposedSchemas;
|
|
34
|
+
importPath: string;
|
|
35
|
+
fns?: Record<string, (...args: unknown[]) => unknown>;
|
|
36
|
+
createClient?: (envelope: Record<string, unknown>) => Promise<unknown>;
|
|
37
|
+
}>;
|
|
38
|
+
outputDir: string;
|
|
39
|
+
options: Record<string, unknown>;
|
|
40
|
+
/**
|
|
41
|
+
* Shared structured logger (pino). Built once by the CLI and threaded through
|
|
42
|
+
* the pipeline + plugins. Always targets stderr or a file — never stdout —
|
|
43
|
+
* so the MCP stdio JSON-RPC channel stays clean. Plugins should fall back to
|
|
44
|
+
* a default stderr logger when this is absent.
|
|
45
|
+
*/
|
|
46
|
+
logger?: Logger;
|
|
47
|
+
}
|
|
48
|
+
export interface PluginOutput {
|
|
49
|
+
files: Array<{
|
|
50
|
+
path: string;
|
|
51
|
+
content: string;
|
|
52
|
+
}>;
|
|
53
|
+
postCommands?: string[];
|
|
54
|
+
}
|
|
55
|
+
export interface RunInput extends PluginInput {
|
|
56
|
+
signal?: AbortSignal;
|
|
57
|
+
/**
|
|
58
|
+
* BUG-APIGEN-024: the full merged `Operation[]` descriptor (the same set
|
|
59
|
+
* `buildDescriptor()` produces), threaded through so a `--use` mount plugin
|
|
60
|
+
* (e.g. `apigen-plugin-openapi`) can build its real `Descriptor` instead of
|
|
61
|
+
* the empty-`operations` stub `collectMountRoutes()` used to synthesize.
|
|
62
|
+
* Absent for non-TS-extraction run paths (e.g. py-flask), where mount
|
|
63
|
+
* plugins have nothing extracted to describe.
|
|
64
|
+
*/
|
|
65
|
+
operations?: Operation[];
|
|
66
|
+
}
|
|
67
|
+
/** Source-language tags understood by apigen's routing layer. */
|
|
68
|
+
export type PluginLanguage = 'ts' | 'py' | 'rust' | 'go' | 'java';
|
|
69
|
+
export interface OutputPlugin {
|
|
70
|
+
id: string;
|
|
71
|
+
description: string;
|
|
72
|
+
/**
|
|
73
|
+
* The source language this plugin consumes.
|
|
74
|
+
*
|
|
75
|
+
* Used by the `serve` command to route each source file to the plugin(s)
|
|
76
|
+
* whose `language` matches its extension (`.ts`/`.tsx`/`.mts`/`.cts` → `'ts'`,
|
|
77
|
+
* `.py` → `'py'`, etc.).
|
|
78
|
+
*
|
|
79
|
+
* Defaults to `'ts'` when omitted for backward-compatibility with plugins
|
|
80
|
+
* authored before this field was introduced. All first-party plugins
|
|
81
|
+
* explicitly declare `language: 'ts'`.
|
|
82
|
+
*/
|
|
83
|
+
language?: PluginLanguage;
|
|
84
|
+
optionsSchema?: Record<string, unknown>;
|
|
85
|
+
generate(input: PluginInput): PluginOutput | Promise<PluginOutput>;
|
|
86
|
+
run?(input: RunInput): Promise<void>;
|
|
87
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@adhd/apigen-core-client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"dependencies": {
|
|
5
|
+
"ts-morph": "^23.0.0",
|
|
6
|
+
"ts-json-schema-generator": "^2.3.0",
|
|
7
|
+
"pino": "10.3.1",
|
|
8
|
+
"typescript": "^6.0.3",
|
|
9
|
+
"@adhd/apigen-base-logical": "^0.0.1"
|
|
10
|
+
},
|
|
11
|
+
"main": "./index.js",
|
|
12
|
+
"module": "./index.mjs",
|
|
13
|
+
"typings": "./index.d.ts",
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
}
|
|
17
|
+
}
|