@ontrails/library 1.0.0-beta.24
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 +5 -0
- package/README.md +87 -0
- package/package.json +33 -0
- package/src/compile.ts +506 -0
- package/src/derive.ts +207 -0
- package/src/errors.ts +196 -0
- package/src/index.ts +56 -0
- package/src/kernel.ts +63 -0
- package/src/layer-input.ts +188 -0
- package/src/surface.ts +175 -0
package/src/surface.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The in-memory library surface: `surface(graph, options)` returns a callable
|
|
3
|
+
* client. It is a peer of the CLI, MCP, and HTTP surfaces — same contract, same
|
|
4
|
+
* shared pipeline — and the first surface whose `surface()` returns a held
|
|
5
|
+
* client rather than opening a long-running endpoint.
|
|
6
|
+
*
|
|
7
|
+
* The client routes execution through the runtime kernel (`kernelRun`), never
|
|
8
|
+
* through ad hoc `@ontrails/core` deep imports, so the standalone trajectory
|
|
9
|
+
* stays a vendoring step. Root-call behavior: unwrap `Result.ok` to a return
|
|
10
|
+
* value, throw on `Result.err`. The held client also exposes a no-throw
|
|
11
|
+
* `result` lane with the same export names so downstream package emission can
|
|
12
|
+
* map that lane to the generated `/result` subpath.
|
|
13
|
+
*/
|
|
14
|
+
import { Result, ValidationError } from '@ontrails/core';
|
|
15
|
+
|
|
16
|
+
import { deriveLibraryApi } from './derive.js';
|
|
17
|
+
import type {
|
|
18
|
+
DeriveLibraryApiOptions,
|
|
19
|
+
LibraryExport,
|
|
20
|
+
LibraryProjection,
|
|
21
|
+
} from './derive.js';
|
|
22
|
+
import { toLibraryError } from './errors.js';
|
|
23
|
+
import type { LibraryError } from './errors.js';
|
|
24
|
+
import { partitionLibraryInput } from './layer-input.js';
|
|
25
|
+
import { kernelRun } from './kernel.js';
|
|
26
|
+
import type { KernelRunOptions, Topo } from './kernel.js';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Options for the in-memory library surface: projection selectors plus the
|
|
30
|
+
* runtime context the client owns (e.g. a permit for permitted trails).
|
|
31
|
+
*/
|
|
32
|
+
export interface SurfaceLibraryOptions
|
|
33
|
+
extends
|
|
34
|
+
DeriveLibraryApiOptions,
|
|
35
|
+
Omit<KernelRunOptions, 'layerInputs' | 'surfaceLayers' | 'topoLayers'> {}
|
|
36
|
+
|
|
37
|
+
/** A callable library method: validated input in, output out, throws on failure. */
|
|
38
|
+
export type LibraryMethod = (input: unknown) => Promise<unknown>;
|
|
39
|
+
|
|
40
|
+
/** A no-throw library method: validated input in, raw Result boundary out. */
|
|
41
|
+
export type LibraryResultMethod = (
|
|
42
|
+
input: unknown
|
|
43
|
+
) => Promise<Result<unknown, LibraryError>>;
|
|
44
|
+
|
|
45
|
+
/** The held in-memory client: one method per projected export, plus the projection. */
|
|
46
|
+
export interface LibraryClient {
|
|
47
|
+
/** Invoke an exported trail by its consumer-native name. */
|
|
48
|
+
readonly call: Readonly<Record<string, LibraryMethod>>;
|
|
49
|
+
/** Invoke an exported trail by name without unwrapping the Result boundary. */
|
|
50
|
+
readonly result: Readonly<Record<string, LibraryResultMethod>>;
|
|
51
|
+
/** The resolved projection this client was built from (introspection). */
|
|
52
|
+
readonly projection: LibraryProjection;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const prepareLibraryInput = (
|
|
56
|
+
entry: LibraryExport,
|
|
57
|
+
input: unknown
|
|
58
|
+
):
|
|
59
|
+
| {
|
|
60
|
+
readonly layerInputs: Record<string, unknown>;
|
|
61
|
+
readonly trailInput: unknown;
|
|
62
|
+
}
|
|
63
|
+
| ValidationError => {
|
|
64
|
+
const parsed = entry.input.safeParse(input);
|
|
65
|
+
if (!parsed.success) {
|
|
66
|
+
return new ValidationError(
|
|
67
|
+
`Invalid input for library export '${entry.exportName}': ${parsed.error.message}`,
|
|
68
|
+
{
|
|
69
|
+
cause: parsed.error,
|
|
70
|
+
context: { issues: parsed.error.issues, trailId: entry.trailId },
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
return partitionLibraryInput(parsed.data, entry.layerInputs);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const runtimeOptionsFor = (
|
|
78
|
+
graph: Topo,
|
|
79
|
+
options: SurfaceLibraryOptions
|
|
80
|
+
): KernelRunOptions => ({
|
|
81
|
+
abortSignal: options.abortSignal,
|
|
82
|
+
configValues: options.configValues,
|
|
83
|
+
createContext: options.createContext,
|
|
84
|
+
ctx: options.ctx,
|
|
85
|
+
dryRun: options.dryRun,
|
|
86
|
+
permit: options.permit,
|
|
87
|
+
resources: options.resources,
|
|
88
|
+
surfaceLayers: options.layers,
|
|
89
|
+
topoLayers: graph.layers,
|
|
90
|
+
version: options.version,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
export const runLibraryResult = async (
|
|
94
|
+
graph: Topo,
|
|
95
|
+
id: string,
|
|
96
|
+
input: unknown,
|
|
97
|
+
options: SurfaceLibraryOptions = {}
|
|
98
|
+
): Promise<Result<unknown, LibraryError>> => {
|
|
99
|
+
const projection = deriveLibraryApi(graph, options);
|
|
100
|
+
const runOptions = runtimeOptionsFor(graph, options);
|
|
101
|
+
const entry = projection.exports.find(
|
|
102
|
+
(candidate) => candidate.trailId === id
|
|
103
|
+
);
|
|
104
|
+
const prepared = entry
|
|
105
|
+
? prepareLibraryInput(entry, input)
|
|
106
|
+
: { layerInputs: {}, trailInput: input };
|
|
107
|
+
if (prepared instanceof ValidationError) {
|
|
108
|
+
return Result.err(toLibraryError(prepared));
|
|
109
|
+
}
|
|
110
|
+
const outcome = await kernelRun(graph, id, prepared.trailInput, {
|
|
111
|
+
...runOptions,
|
|
112
|
+
...(Object.keys(prepared.layerInputs).length === 0
|
|
113
|
+
? {}
|
|
114
|
+
: { layerInputs: prepared.layerInputs }),
|
|
115
|
+
});
|
|
116
|
+
return outcome.mapErr(toLibraryError);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Materialize a topo as an in-memory library client. Each projected export
|
|
121
|
+
* becomes a method that executes its trail through the shared pipeline and
|
|
122
|
+
* unwraps the Result — returning the value or throwing the error. The same
|
|
123
|
+
* export names are available under `result` for callers that want the raw
|
|
124
|
+
* `Result` boundary.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* const lib = await surface(app);
|
|
128
|
+
* const widget = await lib.call.widgetGet({ id: '1' });
|
|
129
|
+
*/
|
|
130
|
+
export const surface = async (
|
|
131
|
+
graph: Topo,
|
|
132
|
+
options: SurfaceLibraryOptions = {}
|
|
133
|
+
// oxlint-disable-next-line require-await -- async to match peer surfaces and allow future resource init
|
|
134
|
+
): Promise<LibraryClient> => {
|
|
135
|
+
const projection = deriveLibraryApi(graph, options);
|
|
136
|
+
const runOptions = runtimeOptionsFor(graph, options);
|
|
137
|
+
const call: Record<string, LibraryMethod> = {};
|
|
138
|
+
const result: Record<string, LibraryResultMethod> = {};
|
|
139
|
+
|
|
140
|
+
for (const entry of projection.exports) {
|
|
141
|
+
const runExport: LibraryResultMethod = async (input: unknown) => {
|
|
142
|
+
const prepared = prepareLibraryInput(entry, input);
|
|
143
|
+
if (prepared instanceof ValidationError) {
|
|
144
|
+
return Result.err(toLibraryError(prepared));
|
|
145
|
+
}
|
|
146
|
+
const outcome = await kernelRun(
|
|
147
|
+
graph,
|
|
148
|
+
entry.trailId,
|
|
149
|
+
prepared.trailInput,
|
|
150
|
+
{
|
|
151
|
+
...runOptions,
|
|
152
|
+
...(Object.keys(prepared.layerInputs).length === 0
|
|
153
|
+
? {}
|
|
154
|
+
: { layerInputs: prepared.layerInputs }),
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
return outcome.mapErr(toLibraryError);
|
|
158
|
+
};
|
|
159
|
+
result[entry.exportName] = runExport;
|
|
160
|
+
|
|
161
|
+
call[entry.exportName] = async (input: unknown): Promise<unknown> => {
|
|
162
|
+
const outcome = await runExport(input);
|
|
163
|
+
if (outcome.isErr()) {
|
|
164
|
+
throw outcome.error;
|
|
165
|
+
}
|
|
166
|
+
return outcome.value;
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
call: Object.freeze(call),
|
|
172
|
+
projection,
|
|
173
|
+
result: Object.freeze(result),
|
|
174
|
+
};
|
|
175
|
+
};
|