@pulse-compute/runtime 0.0.0 → 1.0.0-beta.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/README.md +125 -2
- package/docs/API.md +539 -0
- package/docs/guides/routing.md +184 -0
- package/docs/preview-scope.md +142 -0
- package/package.json +38 -5
- package/src/host.d.ts +376 -0
- package/src/host.js +114 -0
- package/src/index.d.ts +189 -0
- package/src/index.js +11 -0
- package/src/internal/bindings.js +281 -0
- package/src/internal/body.js +254 -0
- package/src/internal/context.js +271 -0
- package/src/internal/effect-adapter.js +857 -0
- package/src/internal/errors.js +27 -0
- package/src/internal/event-emission.js +182 -0
- package/src/internal/event-execution.js +495 -0
- package/src/internal/fetch.js +221 -0
- package/src/internal/index.js +24 -0
- package/src/internal/logging.js +65 -0
- package/src/internal/package-runtime.js +597 -0
- package/src/internal/path.js +130 -0
- package/src/internal/redaction.js +231 -0
- package/src/internal/response.js +415 -0
- package/src/internal/router.js +459 -0
- package/src/internal/schema.js +340 -0
- package/src/package.d.ts +127 -0
- package/src/package.js +19 -0
package/src/host.d.ts
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Handler,
|
|
3
|
+
PulseEffect,
|
|
4
|
+
PulseFetchResponse,
|
|
5
|
+
PulseParallelEffect,
|
|
6
|
+
PulseParallelResult,
|
|
7
|
+
Router
|
|
8
|
+
} from './index';
|
|
9
|
+
|
|
10
|
+
/** Provider-owned execution metadata supplied to every JavaScript effect. */
|
|
11
|
+
export interface PulseJavascriptEffectHostExecution {
|
|
12
|
+
readonly kind: 'request' | 'event';
|
|
13
|
+
readonly request?: Request;
|
|
14
|
+
readonly event?: PulseEventFrame;
|
|
15
|
+
readonly application?: unknown;
|
|
16
|
+
readonly signal?: AbortSignal;
|
|
17
|
+
/** Adds execution-owned sensitive text to runtime redaction before provider work continues. */
|
|
18
|
+
registerRedactionValue(value: string | Uint8Array): void;
|
|
19
|
+
/** Applies one project-owned schema codec directly to an in-memory semantic value. */
|
|
20
|
+
validateSchemaValue(
|
|
21
|
+
schemaId: string,
|
|
22
|
+
value: unknown,
|
|
23
|
+
context?: Readonly<{ source?: string; operationId?: string }>
|
|
24
|
+
): unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Canonical execution-owned effect descriptor delivered to a trusted provider adapter. */
|
|
28
|
+
export interface PulseJavascriptEffectDescriptor {
|
|
29
|
+
readonly version: 'pulse.javascript-effect.v1';
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly kind: string;
|
|
32
|
+
readonly providerKind: string;
|
|
33
|
+
readonly operation: string;
|
|
34
|
+
readonly capability: string;
|
|
35
|
+
readonly [field: string]: unknown;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface PulseJavascriptEffectAdapterInput {
|
|
39
|
+
readonly id?: string;
|
|
40
|
+
dispatch(
|
|
41
|
+
effect: PulseJavascriptEffectDescriptor,
|
|
42
|
+
execution: PulseJavascriptEffectHostExecution
|
|
43
|
+
): Promise<unknown> | unknown;
|
|
44
|
+
dispose?(execution: PulseJavascriptEffectHostExecution): Promise<void> | void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface PulseJavascriptEffectAdapter {
|
|
48
|
+
readonly version: 'pulse.javascript-effect-adapter.v1';
|
|
49
|
+
readonly id: string;
|
|
50
|
+
dispatch(
|
|
51
|
+
effect: PulseJavascriptEffectDescriptor,
|
|
52
|
+
execution: PulseJavascriptEffectHostExecution
|
|
53
|
+
): Promise<unknown> | unknown;
|
|
54
|
+
dispose?(execution: PulseJavascriptEffectHostExecution): Promise<void> | void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface PulseEventAdapterSemantics {
|
|
58
|
+
readonly acceptance: 'host-accepted';
|
|
59
|
+
readonly deliveryGuarantee: 'none';
|
|
60
|
+
readonly autoLoopback: false;
|
|
61
|
+
readonly sameStackReentry: false;
|
|
62
|
+
readonly queueDiscipline: 'fifo-per-declared-adapter';
|
|
63
|
+
readonly executionIsolation: 'one-event-frame-per-execution';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface PulseEventRecordingAdapterOptions {
|
|
67
|
+
readonly id?: string;
|
|
68
|
+
readonly maxQueueDepth?: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Bounded deterministic event acceptance adapter. It never invokes a local application handler. */
|
|
72
|
+
export interface PulseEventRecordingAdapter extends PulseJavascriptEffectAdapter {
|
|
73
|
+
readonly eventAdapter: {
|
|
74
|
+
readonly version: 'pulse.event-adapter.v1';
|
|
75
|
+
readonly id: string;
|
|
76
|
+
readonly maxQueueDepth: number;
|
|
77
|
+
readonly semantics: PulseEventAdapterSemantics;
|
|
78
|
+
};
|
|
79
|
+
acceptedFrames(): readonly PulseEventFrame[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface PulseRuntimeHostKvNamespace<T = unknown> {
|
|
83
|
+
get(key: string): Promise<T | undefined> | T | undefined;
|
|
84
|
+
put(key: string, value: T): Promise<boolean> | boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Compatibility injection shape retained while providers move to one effect adapter. */
|
|
88
|
+
export interface PulseRuntimeHostCapabilities {
|
|
89
|
+
fetch?(
|
|
90
|
+
url: string,
|
|
91
|
+
init?: unknown,
|
|
92
|
+
execution?: PulseJavascriptEffectHostExecution
|
|
93
|
+
): Promise<Response | PulseFetchResponse> | Response | PulseFetchResponse;
|
|
94
|
+
config?(
|
|
95
|
+
name: string,
|
|
96
|
+
execution?: PulseJavascriptEffectHostExecution
|
|
97
|
+
): Promise<string | undefined> | string | undefined;
|
|
98
|
+
secret?(
|
|
99
|
+
name: string,
|
|
100
|
+
execution?: PulseJavascriptEffectHostExecution
|
|
101
|
+
): Promise<string | undefined> | string | undefined;
|
|
102
|
+
kv?<T = unknown>(
|
|
103
|
+
name: string,
|
|
104
|
+
execution?: PulseJavascriptEffectHostExecution
|
|
105
|
+
): PulseRuntimeHostKvNamespace<T> | undefined;
|
|
106
|
+
emit?(
|
|
107
|
+
frame: PulseEventFrame,
|
|
108
|
+
execution?: PulseJavascriptEffectHostExecution
|
|
109
|
+
): Promise<void> | void;
|
|
110
|
+
effect?(
|
|
111
|
+
effect: PulseJavascriptEffectDescriptor,
|
|
112
|
+
execution: PulseJavascriptEffectHostExecution
|
|
113
|
+
): Promise<unknown> | unknown;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface PulseJavascriptEffectObservation {
|
|
117
|
+
readonly version: 'pulse.javascript-effect-observation.v1';
|
|
118
|
+
readonly type: 'effect-dispatched' | 'effect-settled' | 'parallel-dispatched' | 'parallel-settled';
|
|
119
|
+
readonly [field: string]: unknown;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface PulseJavascriptEffectGroupSummary {
|
|
123
|
+
readonly id: string;
|
|
124
|
+
readonly keys: readonly string[];
|
|
125
|
+
readonly effectIds: readonly string[];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface PulseJavascriptEffectSummary {
|
|
129
|
+
readonly version: 'pulse.javascript-effect-observation.v1';
|
|
130
|
+
readonly adapter: {
|
|
131
|
+
readonly version: 'pulse.javascript-effect-adapter.v1';
|
|
132
|
+
readonly id: string;
|
|
133
|
+
};
|
|
134
|
+
readonly effectCount: number;
|
|
135
|
+
readonly localEffectCount: number;
|
|
136
|
+
readonly ownedEffectCount: number;
|
|
137
|
+
readonly parallelCount: number;
|
|
138
|
+
readonly resolutionOrder: readonly string[];
|
|
139
|
+
readonly groups: readonly PulseJavascriptEffectGroupSummary[];
|
|
140
|
+
readonly observations: readonly PulseJavascriptEffectObservation[];
|
|
141
|
+
readonly redactedSecretCount: number;
|
|
142
|
+
readonly aborted: boolean;
|
|
143
|
+
readonly abortCode?: string;
|
|
144
|
+
readonly closed: boolean;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Maintainer bridge used by the managed Router lifecycle and focused adapter tests. */
|
|
148
|
+
export interface PulseJavascriptEffectExecution {
|
|
149
|
+
/** Execution-owned local projection. It participates in lifecycle containment but is not parallel-eligible. */
|
|
150
|
+
local<T = unknown>(
|
|
151
|
+
kind: string,
|
|
152
|
+
producer: (execution: PulseJavascriptEffectHostExecution) => T | PromiseLike<T>
|
|
153
|
+
): PulseEffect<T>;
|
|
154
|
+
dispatch<T = unknown>(
|
|
155
|
+
effect: Omit<PulseJavascriptEffectDescriptor, 'version' | 'id'> & { readonly id?: string },
|
|
156
|
+
projector?: (value: unknown) => T
|
|
157
|
+
): PulseParallelEffect<T>;
|
|
158
|
+
project<T, U>(
|
|
159
|
+
effect: PulseParallelEffect<T>,
|
|
160
|
+
projection: string,
|
|
161
|
+
projector: (value: T) => U | PromiseLike<U>
|
|
162
|
+
): PulseParallelEffect<U>;
|
|
163
|
+
parallel<T extends Readonly<Record<string, PulseParallelEffect<unknown>>>>(
|
|
164
|
+
effects: T
|
|
165
|
+
): PulseEffect<PulseParallelResult<T>>;
|
|
166
|
+
/** Applies execution-owned secret redaction to an error before it crosses a managed boundary. */
|
|
167
|
+
redactError(error: unknown): unknown;
|
|
168
|
+
/** Applies execution-owned secret and sensitive-field redaction to structured evidence. */
|
|
169
|
+
redactValue<T = unknown>(value: T): unknown;
|
|
170
|
+
assertIdle(): Promise<void>;
|
|
171
|
+
summary(): PulseJavascriptEffectSummary;
|
|
172
|
+
close(): Promise<void>;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface PulseRuntimeExecutionOptions {
|
|
176
|
+
/** Compatibility bridge. New providers should inject one shared effectAdapter instead. */
|
|
177
|
+
readonly capabilities?: PulseRuntimeHostCapabilities;
|
|
178
|
+
readonly effectAdapter?: PulseJavascriptEffectAdapter | PulseJavascriptEffectAdapterInput;
|
|
179
|
+
readonly application?: unknown;
|
|
180
|
+
/** Managed execution plane. Direct effect executions default to request. */
|
|
181
|
+
readonly executionKind?: 'request' | 'event';
|
|
182
|
+
/** Normalized event input supplied only for event-owned adapter work. */
|
|
183
|
+
readonly event?: PulseEventFrame;
|
|
184
|
+
readonly requestHeaders?: readonly (readonly [string, string])[];
|
|
185
|
+
readonly signal?: AbortSignal;
|
|
186
|
+
readonly maxEffects?: number;
|
|
187
|
+
/** Optional stricter limits for direct event ingress normalization. */
|
|
188
|
+
readonly eventLimits?: PulseEventLimits;
|
|
189
|
+
/** Maximum bytes materialized by ctx.req.text()/json(). */
|
|
190
|
+
readonly maxRequestBodyBytes?: number;
|
|
191
|
+
/** Maximum bytes materialized by fetched response text()/json() projections. */
|
|
192
|
+
readonly maxFetchBodyBytes?: number;
|
|
193
|
+
/** Shared fallback for request and fetched structured bodies. */
|
|
194
|
+
readonly maxStructuredBodyBytes?: number;
|
|
195
|
+
/** Maximum UTF-8 bytes in config and secret names. */
|
|
196
|
+
readonly maxBindingNameBytes?: number;
|
|
197
|
+
/** Maximum UTF-8 bytes returned by config and secret providers. */
|
|
198
|
+
readonly maxBindingValueBytes?: number;
|
|
199
|
+
/** Maximum UTF-8 bytes in a KV namespace name. */
|
|
200
|
+
readonly maxKvNamespaceBytes?: number;
|
|
201
|
+
/** Maximum UTF-8 bytes in a KV key. */
|
|
202
|
+
readonly maxKvKeyBytes?: number;
|
|
203
|
+
/** Maximum serialized bytes in a detached KV value. */
|
|
204
|
+
readonly maxKvValueBytes?: number;
|
|
205
|
+
/** Maximum nesting depth in a detached KV value. */
|
|
206
|
+
readonly maxKvValueDepth?: number;
|
|
207
|
+
/** Maximum entries in a detached KV value. */
|
|
208
|
+
readonly maxKvValueEntries?: number;
|
|
209
|
+
/** Compiler-generated project schema codec table. This is a host integration surface, not an authoring API. */
|
|
210
|
+
readonly schemaCodecs?: {
|
|
211
|
+
readonly registry?: unknown;
|
|
212
|
+
has(schemaId: string): boolean;
|
|
213
|
+
decode(schemaId: string, value: unknown, source?: string): unknown;
|
|
214
|
+
decodeJsonText(schemaId: string, text: string, source?: string): unknown;
|
|
215
|
+
encodeJsonText(schemaId: string, value: unknown, source?: string): string;
|
|
216
|
+
responseCase?(responseCaseId: string): { readonly id: string; readonly status: number; readonly schemaId: string } | undefined;
|
|
217
|
+
createTraceEvent?(event: unknown, options?: unknown): unknown;
|
|
218
|
+
};
|
|
219
|
+
/** Enforces registered schema IDs at every semantic JSON boundary. */
|
|
220
|
+
readonly strict?: boolean;
|
|
221
|
+
/** Provider-known sensitive values available before the first secret read. */
|
|
222
|
+
readonly redactionValues?: readonly string[];
|
|
223
|
+
/** Resolved synchronous logging threshold. */
|
|
224
|
+
readonly reporting?: 'off' | 'error' | 'warn' | 'info' | 'debug' | 0 | 1 | 2 | 3 | 4;
|
|
225
|
+
/** Provider-owned best-effort logging sink. */
|
|
226
|
+
readonly log?: (level: 1 | 2 | 3 | 4, message: string, event: Readonly<Record<string, unknown>>) => void;
|
|
227
|
+
readonly onLogObservation?: (event: Readonly<Record<string, unknown>>) => void;
|
|
228
|
+
readonly onJsonTrace?: (event: unknown) => void;
|
|
229
|
+
readonly onEffectObservation?: (observation: PulseJavascriptEffectObservation) => void;
|
|
230
|
+
readonly onEffectSummary?: (summary: PulseJavascriptEffectSummary) => void;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export declare class PulseRuntimeContractError extends Error {
|
|
234
|
+
readonly code: string;
|
|
235
|
+
readonly detail?: unknown;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export declare class PulseUnhandledError extends Error {
|
|
239
|
+
readonly code: 'PULSE_RUNTIME_UNHANDLED_ERROR';
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export declare function createJavascriptEffectAdapter(
|
|
243
|
+
input: PulseJavascriptEffectAdapterInput
|
|
244
|
+
): PulseJavascriptEffectAdapter;
|
|
245
|
+
export declare function createCapabilityEffectAdapter(
|
|
246
|
+
capabilities?: PulseRuntimeHostCapabilities
|
|
247
|
+
): PulseJavascriptEffectAdapter;
|
|
248
|
+
export declare function createEventRecordingAdapter(
|
|
249
|
+
options?: PulseEventRecordingAdapterOptions
|
|
250
|
+
): PulseEventRecordingAdapter;
|
|
251
|
+
export declare function createJavascriptEffectExecution(
|
|
252
|
+
options?: PulseRuntimeExecutionOptions & { readonly request?: Request }
|
|
253
|
+
): PulseJavascriptEffectExecution;
|
|
254
|
+
export interface PulseBindingValueLimits {
|
|
255
|
+
readonly maxBindingNameBytes?: number;
|
|
256
|
+
readonly maxBindingValueBytes?: number;
|
|
257
|
+
readonly maxKvNamespaceBytes?: number;
|
|
258
|
+
readonly maxKvKeyBytes?: number;
|
|
259
|
+
readonly maxKvValueBytes?: number;
|
|
260
|
+
readonly maxKvValueDepth?: number;
|
|
261
|
+
readonly maxKvValueEntries?: number;
|
|
262
|
+
}
|
|
263
|
+
export declare function normalizeBindingName(kind: string, value: unknown, options?: PulseBindingValueLimits): string;
|
|
264
|
+
export declare function normalizeBindingValue(
|
|
265
|
+
capability: string,
|
|
266
|
+
name: string,
|
|
267
|
+
value: unknown,
|
|
268
|
+
options?: PulseBindingValueLimits
|
|
269
|
+
): string | undefined;
|
|
270
|
+
export declare function normalizeKvNamespace(value: unknown, options?: PulseBindingValueLimits): string;
|
|
271
|
+
export declare function normalizeKvKey(value: unknown, options?: PulseBindingValueLimits): string;
|
|
272
|
+
export declare function cloneKvValue<T>(
|
|
273
|
+
value: T,
|
|
274
|
+
options?: {
|
|
275
|
+
readonly operation?: string;
|
|
276
|
+
readonly allowUndefined?: boolean;
|
|
277
|
+
readonly maxDepth?: number;
|
|
278
|
+
readonly maxEntries?: number;
|
|
279
|
+
readonly maxBytes?: number;
|
|
280
|
+
readonly maxKvValueBytes?: number;
|
|
281
|
+
readonly maxKvValueDepth?: number;
|
|
282
|
+
readonly maxKvValueEntries?: number;
|
|
283
|
+
}
|
|
284
|
+
): T;
|
|
285
|
+
export declare function normalizeKvPutResult(value: unknown): boolean;
|
|
286
|
+
export interface PulseRedactionState {
|
|
287
|
+
add(value: unknown): unknown;
|
|
288
|
+
count(): number;
|
|
289
|
+
redactString(value: unknown): string;
|
|
290
|
+
redactValue(value: unknown): unknown;
|
|
291
|
+
redactError(error: unknown): unknown;
|
|
292
|
+
values(): readonly '<redacted>'[];
|
|
293
|
+
summary(): { readonly registeredSecretCount: number };
|
|
294
|
+
}
|
|
295
|
+
export declare function createRedactionState(initialValues?: Iterable<unknown> | string): PulseRedactionState;
|
|
296
|
+
export declare function isPulseJavascriptEffect(value: unknown): boolean;
|
|
297
|
+
|
|
298
|
+
export declare function isRouterApplication(value: unknown): value is Router;
|
|
299
|
+
export declare function assertRouterApplication(value: unknown): Router;
|
|
300
|
+
export declare function normalizeApplication(value: unknown): Router;
|
|
301
|
+
export declare function executeRouter(
|
|
302
|
+
application: Router,
|
|
303
|
+
request: Request,
|
|
304
|
+
options?: PulseRuntimeExecutionOptions
|
|
305
|
+
): Promise<Response>;
|
|
306
|
+
export declare function executeApplication(
|
|
307
|
+
application: Router | Handler,
|
|
308
|
+
request: Request,
|
|
309
|
+
options?: PulseRuntimeExecutionOptions
|
|
310
|
+
): Promise<Response>;
|
|
311
|
+
export interface PulseEventLimits {
|
|
312
|
+
readonly maxTypeBytes?: number;
|
|
313
|
+
readonly maxSchemaIdBytes?: number;
|
|
314
|
+
readonly maxPayloadBytes?: number;
|
|
315
|
+
readonly maxPayloadDepth?: number;
|
|
316
|
+
readonly maxPayloadEntries?: number;
|
|
317
|
+
readonly maxEvents?: number;
|
|
318
|
+
}
|
|
319
|
+
export type PulseEventFrame<Payload = unknown> =
|
|
320
|
+
| Readonly<{
|
|
321
|
+
version: 'pulse.event-frame.v1';
|
|
322
|
+
type: string;
|
|
323
|
+
schemaId: string;
|
|
324
|
+
payload: Payload;
|
|
325
|
+
}>
|
|
326
|
+
| Readonly<{
|
|
327
|
+
version: 'pulse.event-frame.v1';
|
|
328
|
+
type: string;
|
|
329
|
+
schemaId: null;
|
|
330
|
+
payload?: never;
|
|
331
|
+
}>;
|
|
332
|
+
export type PulseEventExecutionResult =
|
|
333
|
+
| Readonly<{
|
|
334
|
+
version: 'pulse.event-execution-result.v1';
|
|
335
|
+
status: 'completed';
|
|
336
|
+
}>
|
|
337
|
+
| Readonly<{
|
|
338
|
+
version: 'pulse.event-execution-result.v1';
|
|
339
|
+
status: 'failed';
|
|
340
|
+
error: unknown;
|
|
341
|
+
}>;
|
|
342
|
+
/** Direct, transport-free JavaScript execution of one canonical Pulse event frame. */
|
|
343
|
+
export declare function executeEvent(
|
|
344
|
+
application: Router,
|
|
345
|
+
frame: PulseEventFrame,
|
|
346
|
+
options?: PulseRuntimeExecutionOptions
|
|
347
|
+
): Promise<PulseEventExecutionResult>;
|
|
348
|
+
export declare function responseBodyClass(response: Response): 'structured' | 'opaque';
|
|
349
|
+
export declare function responseHeaderPairs(response: Response): readonly (readonly [string, string])[];
|
|
350
|
+
export declare function isPulseFetchResponse(value: unknown): value is PulseFetchResponse;
|
|
351
|
+
/** Marks a provider-owned Web Response as an opaque pass-through without copying its body. */
|
|
352
|
+
export declare function markOpaqueResponse(
|
|
353
|
+
response: Response,
|
|
354
|
+
headers?: Headers | Readonly<Record<string, string>> | readonly (readonly [string, string])[]
|
|
355
|
+
): Response;
|
|
356
|
+
export declare function wrapStructuredResponse(response: Response): PulseFetchResponse;
|
|
357
|
+
export declare function createOpaqueFetchResponse(input?: {
|
|
358
|
+
readonly status?: number;
|
|
359
|
+
readonly headers?: Headers | Readonly<Record<string, string>> | readonly (readonly [string, string])[];
|
|
360
|
+
readonly bodyHandle?: unknown;
|
|
361
|
+
readonly response?: Response;
|
|
362
|
+
}): PulseFetchResponse;
|
|
363
|
+
|
|
364
|
+
export declare const RUNTIME_HOST_API_VERSION: 'pulse.runtime-host.v3';
|
|
365
|
+
export declare const JAVASCRIPT_EFFECT_PROTOCOL_VERSION: 'pulse.javascript-effect.v1';
|
|
366
|
+
export declare const JAVASCRIPT_EFFECT_ADAPTER_VERSION: 'pulse.javascript-effect-adapter.v1';
|
|
367
|
+
export declare const JAVASCRIPT_EFFECT_OBSERVATION_VERSION: 'pulse.javascript-effect-observation.v1';
|
|
368
|
+
export declare const EVENT_ADAPTER_VERSION: 'pulse.event-adapter.v1';
|
|
369
|
+
export declare const EVENT_ADAPTER_SEMANTICS: PulseEventAdapterSemantics;
|
|
370
|
+
export declare const EVENT_EMIT_CODES: Readonly<{
|
|
371
|
+
INPUT_INVALID: 'PULSE_RUNTIME_EVENT_EMIT_INPUT_INVALID';
|
|
372
|
+
SCHEMA_INVALID: 'PULSE_RUNTIME_EVENT_EMIT_SCHEMA_INVALID';
|
|
373
|
+
ACCEPTANCE_INVALID: 'PULSE_RUNTIME_EVENT_EMIT_ACCEPTANCE_INVALID';
|
|
374
|
+
QUEUE_FULL: 'PULSE_RUNTIME_EVENT_EMIT_QUEUE_FULL';
|
|
375
|
+
ADAPTER_INVALID: 'PULSE_RUNTIME_EVENT_ADAPTER_INVALID';
|
|
376
|
+
}>;
|
package/src/host.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
PulseRuntimeContractError,
|
|
5
|
+
PulseUnhandledError,
|
|
6
|
+
createOpaqueFetchResponse,
|
|
7
|
+
isPulseFetchResponse,
|
|
8
|
+
markOpaqueResponse,
|
|
9
|
+
responseBodyClass,
|
|
10
|
+
executeRouter,
|
|
11
|
+
routerEntries,
|
|
12
|
+
wrapStructuredResponse
|
|
13
|
+
} = require('./internal/index.js');
|
|
14
|
+
const {
|
|
15
|
+
JAVASCRIPT_EFFECT_PROTOCOL_VERSION,
|
|
16
|
+
JAVASCRIPT_EFFECT_ADAPTER_VERSION,
|
|
17
|
+
JAVASCRIPT_EFFECT_OBSERVATION_VERSION,
|
|
18
|
+
createJavascriptEffectAdapter,
|
|
19
|
+
createCapabilityEffectAdapter,
|
|
20
|
+
createJavascriptEffectExecution,
|
|
21
|
+
isPulseJavascriptEffect
|
|
22
|
+
} = require('./internal/effect-adapter.js');
|
|
23
|
+
const {
|
|
24
|
+
cloneKvValue,
|
|
25
|
+
normalizeBindingName,
|
|
26
|
+
normalizeBindingValue,
|
|
27
|
+
normalizeKvKey,
|
|
28
|
+
normalizeKvNamespace,
|
|
29
|
+
normalizeKvPutResult
|
|
30
|
+
} = require('./internal/bindings.js');
|
|
31
|
+
const { createRedactionState } = require('./internal/redaction.js');
|
|
32
|
+
const { executeEvent } = require('./internal/event-execution.js');
|
|
33
|
+
const {
|
|
34
|
+
EVENT_ADAPTER_VERSION,
|
|
35
|
+
EVENT_ADAPTER_SEMANTICS,
|
|
36
|
+
EVENT_EMIT_CODES,
|
|
37
|
+
createEventRecordingAdapter
|
|
38
|
+
} = require('./internal/event-emission.js');
|
|
39
|
+
const { responseHeaderPairs } = require('./internal/response.js');
|
|
40
|
+
const { Router } = require('./index.js');
|
|
41
|
+
|
|
42
|
+
const RUNTIME_HOST_API_VERSION = 'pulse.runtime-host.v3';
|
|
43
|
+
|
|
44
|
+
function isRouterApplication(value) {
|
|
45
|
+
try {
|
|
46
|
+
routerEntries(value);
|
|
47
|
+
return true;
|
|
48
|
+
} catch (_) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizeApplication(value) {
|
|
54
|
+
if (isRouterApplication(value)) return value;
|
|
55
|
+
if (typeof value === 'function') {
|
|
56
|
+
const router = new Router();
|
|
57
|
+
router.use(async (ctx) => value(ctx));
|
|
58
|
+
return router;
|
|
59
|
+
}
|
|
60
|
+
throw new PulseRuntimeContractError(
|
|
61
|
+
'PULSE_RUNTIME_APPLICATION_EXPORT_INVALID',
|
|
62
|
+
'Pulse JavaScript application modules must default-export a Router, Pulse application, or managed handler.'
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function assertRouterApplication(value) {
|
|
67
|
+
if (!isRouterApplication(value)) {
|
|
68
|
+
throw new PulseRuntimeContractError(
|
|
69
|
+
'PULSE_RUNTIME_APPLICATION_EXPORT_INVALID',
|
|
70
|
+
'Expected a Router or Pulse application.'
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function executeApplication(application, request, options = {}) {
|
|
77
|
+
return executeRouter(normalizeApplication(application), request, options);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = Object.freeze({
|
|
81
|
+
RUNTIME_HOST_API_VERSION,
|
|
82
|
+
JAVASCRIPT_EFFECT_PROTOCOL_VERSION,
|
|
83
|
+
JAVASCRIPT_EFFECT_ADAPTER_VERSION,
|
|
84
|
+
JAVASCRIPT_EFFECT_OBSERVATION_VERSION,
|
|
85
|
+
EVENT_ADAPTER_VERSION,
|
|
86
|
+
EVENT_ADAPTER_SEMANTICS,
|
|
87
|
+
EVENT_EMIT_CODES,
|
|
88
|
+
PulseRuntimeContractError,
|
|
89
|
+
PulseUnhandledError,
|
|
90
|
+
assertRouterApplication,
|
|
91
|
+
cloneKvValue,
|
|
92
|
+
createCapabilityEffectAdapter,
|
|
93
|
+
createEventRecordingAdapter,
|
|
94
|
+
createJavascriptEffectAdapter,
|
|
95
|
+
createJavascriptEffectExecution,
|
|
96
|
+
createRedactionState,
|
|
97
|
+
createOpaqueFetchResponse,
|
|
98
|
+
executeApplication,
|
|
99
|
+
executeEvent,
|
|
100
|
+
executeRouter,
|
|
101
|
+
isPulseFetchResponse,
|
|
102
|
+
isPulseJavascriptEffect,
|
|
103
|
+
isRouterApplication,
|
|
104
|
+
markOpaqueResponse,
|
|
105
|
+
normalizeBindingName,
|
|
106
|
+
normalizeBindingValue,
|
|
107
|
+
normalizeKvKey,
|
|
108
|
+
normalizeKvNamespace,
|
|
109
|
+
normalizeKvPutResult,
|
|
110
|
+
normalizeApplication,
|
|
111
|
+
responseBodyClass,
|
|
112
|
+
responseHeaderPairs,
|
|
113
|
+
wrapStructuredResponse
|
|
114
|
+
});
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
export type HeaderPair = readonly [name: string, value: string];
|
|
2
|
+
|
|
3
|
+
declare const pulseEffectBrand: unique symbol;
|
|
4
|
+
declare const pulseParallelEffectBrand: unique symbol;
|
|
5
|
+
|
|
6
|
+
export interface PulseEffect<T> extends Promise<T> {
|
|
7
|
+
readonly [pulseEffectBrand]: T;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** An externally dispatched Pulse effect that may be a member of ctx.parallel(). */
|
|
11
|
+
export interface PulseParallelEffect<T> extends PulseEffect<T> {
|
|
12
|
+
readonly [pulseParallelEffectBrand]: true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type PulseEffectResult<T> = T extends PulseEffect<infer Result> ? Result : never;
|
|
16
|
+
export type PulseParallelResult<T extends Readonly<Record<string, PulseParallelEffect<unknown>>>> = {
|
|
17
|
+
readonly [Key in keyof T]: PulseEffectResult<T[Key]>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export interface PulseRequest {
|
|
21
|
+
readonly method: string;
|
|
22
|
+
readonly url: string;
|
|
23
|
+
readonly path: string;
|
|
24
|
+
readonly headers: readonly HeaderPair[];
|
|
25
|
+
header(name: string): string | undefined;
|
|
26
|
+
text(): PulseEffect<string>;
|
|
27
|
+
json<T = unknown>(schemaId?: string): PulseEffect<T>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface PulseFetchInitBase {
|
|
31
|
+
readonly method?: 'GET' | 'HEAD' | 'POST';
|
|
32
|
+
readonly headers?: Readonly<Record<string, string>> | readonly HeaderPair[];
|
|
33
|
+
readonly timeoutMs?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type PulseFetchInit = PulseFetchInitBase & (
|
|
37
|
+
| { readonly body?: never; readonly json?: never; readonly schema?: never }
|
|
38
|
+
| { readonly body: string; readonly json?: never; readonly schema?: never }
|
|
39
|
+
| { readonly body?: never; readonly json: unknown; readonly schema?: string }
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
export interface PulseStructuredResponse {
|
|
43
|
+
readonly status: number;
|
|
44
|
+
readonly ok: boolean;
|
|
45
|
+
readonly kind: string;
|
|
46
|
+
readonly bodyClass: 'structured';
|
|
47
|
+
readonly headers: readonly HeaderPair[];
|
|
48
|
+
header(name: string): string | undefined;
|
|
49
|
+
text(): PulseEffect<string>;
|
|
50
|
+
json<T = unknown>(schemaId?: string): PulseEffect<T>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface PulseOpaqueResponse {
|
|
54
|
+
readonly status: number;
|
|
55
|
+
readonly ok: boolean;
|
|
56
|
+
readonly kind: 'stream';
|
|
57
|
+
readonly bodyClass: 'opaque';
|
|
58
|
+
readonly headers: readonly HeaderPair[];
|
|
59
|
+
header(name: string): string | undefined;
|
|
60
|
+
/** Opaque host-owned handle. It is returnable but not inspectable or iterable. */
|
|
61
|
+
readonly bodyHandle: unknown;
|
|
62
|
+
/** Opaque pass-through bodies cannot be inspected. */
|
|
63
|
+
text(): never;
|
|
64
|
+
/** Opaque pass-through bodies cannot be inspected. */
|
|
65
|
+
json<T = unknown>(schemaId?: string): never;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type PulseFetchResponse = PulseStructuredResponse | PulseOpaqueResponse;
|
|
69
|
+
|
|
70
|
+
export interface PulseFetchOperation extends PulseParallelEffect<PulseOpaqueResponse> {
|
|
71
|
+
text(): PulseParallelEffect<string>;
|
|
72
|
+
json<T = unknown>(schemaId?: string): PulseParallelEffect<T>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface PulseResult {
|
|
76
|
+
readonly status: number;
|
|
77
|
+
readonly kind: string;
|
|
78
|
+
readonly bodyClass: 'structured' | 'opaque';
|
|
79
|
+
readonly headers: readonly HeaderPair[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface PulseResponseOptions {
|
|
83
|
+
readonly status?: number;
|
|
84
|
+
readonly headers?: Readonly<Record<string, string>> | readonly HeaderPair[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface PulseJsonResponseOptions extends PulseResponseOptions {
|
|
88
|
+
/** Exact schema ID declared by the project. */
|
|
89
|
+
readonly schema?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface PulseState {
|
|
93
|
+
get(key: string): string | undefined;
|
|
94
|
+
set(key: string, value: string): void;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface PulseLogger {
|
|
98
|
+
error(message: string): void;
|
|
99
|
+
warn(message: string): void;
|
|
100
|
+
info(message: string): void;
|
|
101
|
+
debug(message: string): void;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface PulseKvNamespace<T = unknown> {
|
|
105
|
+
get(key: string): PulseParallelEffect<T | undefined>;
|
|
106
|
+
put(key: string, value: T): PulseParallelEffect<boolean>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export type PulseEmitEvent<Payload = unknown> =
|
|
110
|
+
| Readonly<{ schema: string; payload: Payload }>
|
|
111
|
+
| Readonly<{ schema: null; payload?: never }>;
|
|
112
|
+
|
|
113
|
+
/** Plane-neutral authority shared by one isolated HTTP request or event invocation. */
|
|
114
|
+
export interface PulseExecutionContext {
|
|
115
|
+
readonly state: PulseState;
|
|
116
|
+
readonly log: PulseLogger;
|
|
117
|
+
fetch(url: string, init?: PulseFetchInit): PulseFetchOperation;
|
|
118
|
+
/** Dispatches a statically keyed group of independent Pulse effects and rejoins in property order. */
|
|
119
|
+
parallel<const T extends Readonly<Record<string, PulseParallelEffect<unknown>>>>(effects: T): PulseEffect<PulseParallelResult<T>>;
|
|
120
|
+
readonly config: { get(name: string): PulseParallelEffect<string | undefined> };
|
|
121
|
+
readonly secret: { get(name: string): PulseParallelEffect<string | undefined> };
|
|
122
|
+
kv<T = unknown>(name: string): PulseKvNamespace<T>;
|
|
123
|
+
emit<Payload = unknown>(
|
|
124
|
+
type: string,
|
|
125
|
+
event: Readonly<{ schema: string; payload: Payload }>
|
|
126
|
+
): PulseParallelEffect<void>;
|
|
127
|
+
emit(
|
|
128
|
+
type: string,
|
|
129
|
+
event: Readonly<{ schema: null; payload?: never }>
|
|
130
|
+
): PulseParallelEffect<void>;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** HTTP request execution context. */
|
|
134
|
+
export interface PulseContext extends PulseExecutionContext {
|
|
135
|
+
readonly req: PulseRequest;
|
|
136
|
+
json(value: unknown, descriptor?: PulseJsonResponseOptions | string): PulseResult;
|
|
137
|
+
text(value: string, options?: PulseResponseOptions): PulseResult;
|
|
138
|
+
response(input?: PulseResponseOptions & { readonly body?: string }): PulseResult;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface PulseRouteContext extends PulseContext {
|
|
142
|
+
/** Returns the named static route parameter for the matched Router path. */
|
|
143
|
+
param(name: string): string | undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface PulseEvent<Payload = unknown> {
|
|
147
|
+
readonly type: string;
|
|
148
|
+
readonly payload: Payload;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Non-HTTP event execution context. Event handlers cannot access request or response fields. */
|
|
152
|
+
export interface PulseEventContext<Payload = unknown> extends PulseExecutionContext {
|
|
153
|
+
readonly event: PulseEvent<Payload>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export type PulseEventHandler<Payload = unknown> = (ctx: PulseEventContext<Payload>) => Promise<void>;
|
|
157
|
+
|
|
158
|
+
export type HandlerResult = PulseResult | PulseFetchResponse;
|
|
159
|
+
export type Handler = (ctx: PulseContext) => Promise<HandlerResult>;
|
|
160
|
+
/** Terminal Router control transfer. The current handler never resumes after this value is returned. */
|
|
161
|
+
export type RouterNext = (error?: unknown) => never;
|
|
162
|
+
export type RouteHandler = (ctx: PulseRouteContext, next: RouterNext) => Promise<HandlerResult>;
|
|
163
|
+
export type RouterMiddleware = (ctx: PulseContext, next: RouterNext) => Promise<HandlerResult>;
|
|
164
|
+
export type RouterErrorHandler = (error: unknown, ctx: PulseContext, next: RouterNext) => Promise<HandlerResult>;
|
|
165
|
+
|
|
166
|
+
/** Canonical Pulse-prefixed aliases. They do not create a second handler or Router algebra. */
|
|
167
|
+
export type PulseHandlerResult = HandlerResult;
|
|
168
|
+
export type PulseHandler = Handler;
|
|
169
|
+
export type PulseRouteHandler = RouteHandler;
|
|
170
|
+
export type PulseMiddleware = RouterMiddleware;
|
|
171
|
+
export type PulseErrorHandler = RouterErrorHandler;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Static authoring marker compiled by Pulse. Router instances are not runtime dispatchers.
|
|
175
|
+
* Canonical v2 supports terminal middleware/error transfer, get/head/post routes,
|
|
176
|
+
* fallthrough, and acyclic static mounts.
|
|
177
|
+
*/
|
|
178
|
+
export declare class Router {
|
|
179
|
+
use(handler: RouterMiddleware): this;
|
|
180
|
+
use(path: string, handler: RouterMiddleware): this;
|
|
181
|
+
get(path: string, handler: RouteHandler): this;
|
|
182
|
+
head(path: string, handler: RouteHandler): this;
|
|
183
|
+
post(path: string, handler: RouteHandler): this;
|
|
184
|
+
mount(path: string, router: Router): this;
|
|
185
|
+
error(handler: RouterErrorHandler): this;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export declare const RUNTIME_API_VERSION: 'pulse.runtime-authoring.v4';
|
|
189
|
+
export declare const ROUTER_API_VERSION: 'pulse.router-authoring.v2';
|
package/src/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { Router: LiveRouter } = require('./internal/router.js');
|
|
4
|
+
|
|
5
|
+
const RUNTIME_API_VERSION = 'pulse.runtime-authoring.v4';
|
|
6
|
+
const ROUTER_API_VERSION = 'pulse.router-authoring.v2';
|
|
7
|
+
|
|
8
|
+
/** Public authoring value backed by the package-internal live Router realization. */
|
|
9
|
+
class Router extends LiveRouter {}
|
|
10
|
+
|
|
11
|
+
module.exports = Object.freeze({ RUNTIME_API_VERSION, ROUTER_API_VERSION, Router });
|