@lostgradient/weft 0.6.0 → 0.7.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 +4 -4
- package/dist/cli/generated/operation-client.generated.d.ts +1 -0
- package/dist/cli-main.js +63 -63
- package/dist/client/event-stream-options.d.ts +58 -0
- package/dist/client/event-stream-options.js +0 -0
- package/dist/client/event-stream.d.ts +27 -44
- package/dist/client/event-stream.js +21 -56
- package/dist/client/event-tail-lifecycle.d.ts +21 -0
- package/dist/client/event-tail-lifecycle.js +71 -0
- package/dist/client/event-tail.d.ts +3 -1
- package/dist/client/http-client.d.ts +4 -5
- package/dist/client/http-client.js +4 -1
- package/dist/client/http-handle.d.ts +4 -5
- package/dist/client/http-request.d.ts +7 -0
- package/dist/client/index.d.ts +2 -1
- package/dist/client/interface.d.ts +23 -7
- package/dist/client/local-handles.d.ts +2 -0
- package/dist/client/local-handles.js +10 -1
- package/dist/client/local.d.ts +3 -3
- package/dist/client/open-event-subscription.d.ts +3 -2
- package/dist/client/open-event-subscription.js +28 -1
- package/dist/client/sse-event-stream.d.ts +27 -0
- package/dist/client/sse-event-stream.js +276 -0
- package/dist/client/start-body.d.ts +2 -2
- package/dist/client/start-body.js +1 -0
- package/dist/core/engine/construction.js +3 -1
- package/dist/core/engine/engine-internal-types.d.ts +2 -0
- package/dist/core/engine/errors.d.ts +6 -6
- package/dist/core/engine/errors.js +1 -1
- package/dist/core/engine/handles.js +1 -1
- package/dist/core/engine/index.d.ts +6 -5
- package/dist/core/engine/index.js +1 -0
- package/dist/core/engine/lifecycle/recovered-services.js +8 -1
- package/dist/core/engine/lifecycle/start-or-signal-create.d.ts +2 -2
- package/dist/core/engine/lifecycle/start-or-signal-create.js +17 -15
- package/dist/core/engine/lifecycle/start-or-signal-resolution.d.ts +1 -1
- package/dist/core/engine/lifecycle/start-or-signal-resolution.js +23 -5
- package/dist/core/engine/lifecycle/start-or-signal.d.ts +6 -4
- package/dist/core/engine/lifecycle/start-or-signal.js +21 -4
- package/dist/core/engine/schedule-run.js +12 -3
- package/dist/core/engine/storage-io.js +1 -1
- package/dist/core/engine/termination/complete.js +3 -3
- package/dist/core/scheduler/scheduler-class.js +2 -1
- package/dist/core/start-workflow-validation.d.ts +3 -4
- package/dist/core/start-workflow-validation.js +1 -1
- package/dist/core/timeouts.d.ts +13 -1
- package/dist/core/timeouts.js +4 -1
- package/dist/core/types/constants.d.ts +16 -0
- package/dist/core/types/options.d.ts +60 -12
- package/dist/core/types/services-resolution.d.ts +49 -3
- package/dist/core/weft-error.d.ts +1 -1
- package/dist/core/weft-error.js +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -0
- package/dist/json-schema.js +1 -1
- package/dist/mcp/cli.js +17 -17
- package/dist/server/asyncapi-channels.js +82 -16
- package/dist/server/handler/auth-context-principal.d.ts +19 -0
- package/dist/server/handler/auth-context-principal.js +25 -0
- package/dist/server/handler/index.d.ts +2 -1
- package/dist/server/handler/index.js +35 -18
- package/dist/server/handler/route-dispatch.d.ts +12 -22
- package/dist/server/handler/route-dispatch.js +30 -36
- package/dist/server/handler/sse-route-dispatch.d.ts +25 -0
- package/dist/server/handler/sse-route-dispatch.js +45 -0
- package/dist/server/handler.js +21 -21
- package/dist/server/index.d.ts +8 -7
- package/dist/server/index.js +15 -15
- package/dist/server/operations/fleet-events-sse.d.ts +11 -0
- package/dist/server/operations/fleet-events-sse.js +164 -0
- package/dist/server/operations/sse-stream.d.ts +43 -0
- package/dist/server/operations/sse-stream.js +152 -7
- package/dist/server/operations/start-or-signal-workflow.d.ts +2 -0
- package/dist/server/operations/start-or-signal-workflow.js +25 -14
- package/dist/server/operations/start-workflow-options.d.ts +9 -6
- package/dist/server/operations/start-workflow-options.js +13 -0
- package/dist/server/operations/workflow-events-sse.d.ts +17 -0
- package/dist/server/operations/workflow-events-sse.js +200 -0
- package/dist/server/rest-bindings.js +12 -0
- package/dist/server/runtime/authentication-bridge.js +4 -0
- package/dist/server/runtime/cors.d.ts +1 -1
- package/dist/server/runtime/cors.js +6 -1
- package/dist/server/runtime/websocket-stream.d.ts +4 -0
- package/dist/server/runtime/websocket-stream.js +20 -3
- package/dist/server/workflow-event-feed.d.ts +1 -0
- package/dist/server/workflow-event-feed.js +17 -6
- package/dist/service-worker/index.js +28 -28
- package/dist/storage/typed-storage.js +1 -1
- package/dist/testing/index.js +17 -17
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -82,10 +82,59 @@ export function buildWebSocketMessages(operation, definitionSchemaToJsonSchema)
|
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
84
|
export function buildSseMessages(operation, definitionSchemaToJsonSchema) {
|
|
85
|
-
const
|
|
85
|
+
const logicalEventSchema = eventJsonSchema(operation, definitionSchemaToJsonSchema);
|
|
86
|
+
if (!isTokenSseOperation(operation)) {
|
|
87
|
+
const eventNames = eventSseMessageNames(operation);
|
|
88
|
+
return {
|
|
89
|
+
[eventNames.eventEnvelopeEvent]: {
|
|
90
|
+
name: eventNames.eventEnvelopeEvent,
|
|
91
|
+
contentType: "text/event-stream",
|
|
92
|
+
payload: logicalEventSchema,
|
|
93
|
+
summary: `SSE event envelope for ${operation.name}.`,
|
|
94
|
+
bindings: {
|
|
95
|
+
http: {
|
|
96
|
+
event: "<envelope.kind>"
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"x-weft-sse-frame": "id: <cursor>\\nevent: <envelope.kind>\\ndata: <JSON event envelope>\\n\\n"
|
|
100
|
+
},
|
|
101
|
+
[eventNames.pingEvent]: {
|
|
102
|
+
name: eventNames.pingEvent,
|
|
103
|
+
contentType: "text/event-stream",
|
|
104
|
+
payload: {
|
|
105
|
+
additionalProperties: !1,
|
|
106
|
+
properties: {
|
|
107
|
+
emittedAtMs: { type: "number" }
|
|
108
|
+
},
|
|
109
|
+
required: ["emittedAtMs"],
|
|
110
|
+
type: "object"
|
|
111
|
+
},
|
|
112
|
+
summary: `SSE ping keepalive for ${operation.name}.`,
|
|
113
|
+
bindings: {
|
|
114
|
+
http: {
|
|
115
|
+
event: "ping"
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"x-weft-sse-frame": 'event: ping\\ndata: {"emittedAtMs":<timestamp>}\\n\\n'
|
|
119
|
+
},
|
|
120
|
+
[eventNames.errorEvent]: {
|
|
121
|
+
name: eventNames.errorEvent,
|
|
122
|
+
contentType: "text/event-stream",
|
|
123
|
+
payload: sseErrorPayloadSchema(),
|
|
124
|
+
summary: `SSE error event for ${operation.name}.`,
|
|
125
|
+
bindings: {
|
|
126
|
+
http: {
|
|
127
|
+
event: "error"
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
"x-weft-sse-frame": "event: error\\ndata: <JSON Error>\\n\\n"
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const tokenNames = tokenSseMessageNames(operation);
|
|
86
135
|
return {
|
|
87
|
-
[
|
|
88
|
-
name:
|
|
136
|
+
[tokenNames.tokenEvent]: {
|
|
137
|
+
name: tokenNames.tokenEvent,
|
|
89
138
|
contentType: "text/event-stream",
|
|
90
139
|
payload: { type: "string" },
|
|
91
140
|
summary: `SSE token event for ${operation.name}. The data: line carries the token text.`,
|
|
@@ -97,8 +146,8 @@ export function buildSseMessages(operation, definitionSchemaToJsonSchema) {
|
|
|
97
146
|
"x-weft-sse-frame": "event: token\\nid: <sequence>\\ndata: <token-text>\\n\\n",
|
|
98
147
|
"x-weft-event-schema": logicalEventSchema
|
|
99
148
|
},
|
|
100
|
-
[
|
|
101
|
-
name:
|
|
149
|
+
[tokenNames.doneEvent]: {
|
|
150
|
+
name: tokenNames.doneEvent,
|
|
102
151
|
contentType: "text/event-stream",
|
|
103
152
|
payload: {
|
|
104
153
|
additionalProperties: !1,
|
|
@@ -113,18 +162,10 @@ export function buildSseMessages(operation, definitionSchemaToJsonSchema) {
|
|
|
113
162
|
},
|
|
114
163
|
"x-weft-sse-frame": "event: done\\n\\n"
|
|
115
164
|
},
|
|
116
|
-
[
|
|
117
|
-
name:
|
|
165
|
+
[tokenNames.errorEvent]: {
|
|
166
|
+
name: tokenNames.errorEvent,
|
|
118
167
|
contentType: "text/event-stream",
|
|
119
|
-
payload:
|
|
120
|
-
properties: {
|
|
121
|
-
code: { type: "string" },
|
|
122
|
-
data: {},
|
|
123
|
-
message: { type: "string" }
|
|
124
|
-
},
|
|
125
|
-
required: ["message"],
|
|
126
|
-
type: "object"
|
|
127
|
-
},
|
|
168
|
+
payload: sseErrorPayloadSchema(),
|
|
128
169
|
summary: `SSE error event for ${operation.name}.`,
|
|
129
170
|
bindings: {
|
|
130
171
|
http: {
|
|
@@ -164,6 +205,17 @@ function webSocketMessageNames(operation) {
|
|
|
164
205
|
};
|
|
165
206
|
}
|
|
166
207
|
function sseMessageNames(operation) {
|
|
208
|
+
return isTokenSseOperation(operation) ? tokenSseMessageNames(operation) : eventSseMessageNames(operation);
|
|
209
|
+
}
|
|
210
|
+
function eventSseMessageNames(operation) {
|
|
211
|
+
const prefix = operationPrefix(operation);
|
|
212
|
+
return {
|
|
213
|
+
eventEnvelopeEvent: `${prefix}_eventEnvelopeEvent`,
|
|
214
|
+
pingEvent: `${prefix}_pingEvent`,
|
|
215
|
+
errorEvent: `${prefix}_errorEvent`
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function tokenSseMessageNames(operation) {
|
|
167
219
|
const prefix = operationPrefix(operation);
|
|
168
220
|
return {
|
|
169
221
|
tokenEvent: `${prefix}_tokenEvent`,
|
|
@@ -171,6 +223,20 @@ function sseMessageNames(operation) {
|
|
|
171
223
|
errorEvent: `${prefix}_errorEvent`
|
|
172
224
|
};
|
|
173
225
|
}
|
|
226
|
+
function isTokenSseOperation(operation) {
|
|
227
|
+
return operation.name === "weft.workflows.streams.sse";
|
|
228
|
+
}
|
|
229
|
+
function sseErrorPayloadSchema() {
|
|
230
|
+
return {
|
|
231
|
+
properties: {
|
|
232
|
+
code: { type: "string" },
|
|
233
|
+
data: {},
|
|
234
|
+
message: { type: "string" }
|
|
235
|
+
},
|
|
236
|
+
required: ["message"],
|
|
237
|
+
type: "object"
|
|
238
|
+
};
|
|
239
|
+
}
|
|
174
240
|
function messageReferenceMap(names) {
|
|
175
241
|
const references = {};
|
|
176
242
|
for (const [key, name] of Object.entries(names).toSorted(([left], [right]) => compareStrings(left, right)))
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AuthContext } from '../authentication.ts';
|
|
2
|
+
import { type Principal } from '../principal.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Convert the REST transport's `authContext` into a `Principal`. The
|
|
5
|
+
* authenticator (`serve()`) reports method + optional claims; this maps
|
|
6
|
+
* that transport authentication result into the `Principal` used by the
|
|
7
|
+
* operation pipeline.
|
|
8
|
+
* Returns `anonymousPrincipal()` when no context is provided (public
|
|
9
|
+
* request).
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { authContextToPrincipal } from '@lostgradient/weft/server/handler';
|
|
14
|
+
*
|
|
15
|
+
* const principal = authContextToPrincipal({ method: 'api-key' });
|
|
16
|
+
* console.log(principal.method); // 'api-key'
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function authContextToPrincipal(authContext: AuthContext | undefined): Principal;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
anonymousPrincipal,
|
|
3
|
+
principalFromApiKey,
|
|
4
|
+
principalFromJwtClaims,
|
|
5
|
+
principalFromMutualTls
|
|
6
|
+
} from "../principal.js";
|
|
7
|
+
export function authContextToPrincipal(authContext) {
|
|
8
|
+
if (authContext === void 0)
|
|
9
|
+
return anonymousPrincipal();
|
|
10
|
+
if (authContext.principal !== void 0)
|
|
11
|
+
return authContext.principal;
|
|
12
|
+
switch (authContext.method) {
|
|
13
|
+
case "jwt": {
|
|
14
|
+
if (authContext.claims === void 0)
|
|
15
|
+
throw Error("authContextToPrincipal: jwt authContext reached the pipeline without claims \u2014 " + "authenticator contract violation");
|
|
16
|
+
return principalFromJwtClaims(authContext.claims);
|
|
17
|
+
}
|
|
18
|
+
case "api-key":
|
|
19
|
+
return principalFromApiKey({ subject: "api-key-caller", scopes: [] });
|
|
20
|
+
case "mtls":
|
|
21
|
+
return principalFromMutualTls({ subject: "mtls-caller", scopes: [] });
|
|
22
|
+
case "public":
|
|
23
|
+
return anonymousPrincipal();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import type { Engine } from '../../core/engine.ts';
|
|
13
13
|
import { type HandlerOptions } from './route-dispatch.ts';
|
|
14
|
-
export { authContextToPrincipal
|
|
14
|
+
export { authContextToPrincipal } from './auth-context-principal.ts';
|
|
15
|
+
export { isOperationFaultLike, type HandlerOptions } from './route-dispatch.ts';
|
|
15
16
|
export { extractRouteParameters, getRequiredRouteParameter } from './route-matching.ts';
|
|
16
17
|
/**
|
|
17
18
|
* Pure HTTP request handler. Maps Request to Response.
|
|
@@ -1,31 +1,23 @@
|
|
|
1
1
|
import { MalformedRouteParameterError } from "../rest-binding.js";
|
|
2
|
+
import { authContextToPrincipal } from "./auth-context-principal.js";
|
|
2
3
|
import { matchRestBinding } from "./binding-dispatch.js";
|
|
3
4
|
import { errorResponse } from "./response-helpers.js";
|
|
4
5
|
import {
|
|
5
|
-
authContextToPrincipal,
|
|
6
6
|
defaultOperationRegistry,
|
|
7
7
|
defaultRestBindings,
|
|
8
8
|
DIRECT_ROUTE_EXECUTORS,
|
|
9
9
|
dispatchViaExecuteOperation
|
|
10
10
|
} from "./route-dispatch.js";
|
|
11
11
|
import { matchDirectRoute } from "./route-matching.js";
|
|
12
|
-
export {
|
|
13
|
-
|
|
14
|
-
isOperationFaultLike
|
|
15
|
-
} from "./route-dispatch.js";
|
|
12
|
+
export { authContextToPrincipal } from "./auth-context-principal.js";
|
|
13
|
+
export { isOperationFaultLike } from "./route-dispatch.js";
|
|
16
14
|
export { extractRouteParameters, getRequiredRouteParameter } from "./route-matching.js";
|
|
17
|
-
function routeParameterErrorResponse(error) {
|
|
18
|
-
if (error instanceof MalformedRouteParameterError)
|
|
19
|
-
return errorResponse(error.message, 400);
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
15
|
function matchRouteBoundary(matcher) {
|
|
23
16
|
try {
|
|
24
17
|
return { kind: "matched", value: matcher() };
|
|
25
18
|
} catch (error) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return { kind: "malformed", response };
|
|
19
|
+
if (error instanceof MalformedRouteParameterError)
|
|
20
|
+
return { kind: "malformed", response: errorResponse(error.message, 400) };
|
|
29
21
|
throw error;
|
|
30
22
|
}
|
|
31
23
|
}
|
|
@@ -34,10 +26,20 @@ function validateHandlerOptions(options) {
|
|
|
34
26
|
return null;
|
|
35
27
|
return errorResponse("`restBindings` and `operationRegistry` must be supplied together (or both omitted).", 500);
|
|
36
28
|
}
|
|
29
|
+
function liveEventStreamContextFromOptions(options) {
|
|
30
|
+
const context = {};
|
|
31
|
+
if (options?.workflowEventFeed !== void 0)
|
|
32
|
+
context.workflowEventFeed = options.workflowEventFeed;
|
|
33
|
+
if (options?.fleetEventFeed !== void 0)
|
|
34
|
+
context.fleetEventFeed = options.fleetEventFeed;
|
|
35
|
+
if (options?.acquireWorkflowStreamConnection !== void 0)
|
|
36
|
+
context.acquireWorkflowStreamConnection = options.acquireWorkflowStreamConnection;
|
|
37
|
+
return context;
|
|
38
|
+
}
|
|
37
39
|
async function dispatchRestBinding(request, engine, bindingMatch, operationRegistry, options, url) {
|
|
38
40
|
try {
|
|
39
41
|
const principal = authContextToPrincipal(options?.authContext);
|
|
40
|
-
return await dispatchViaExecuteOperation(request, engine, bindingMatch.binding, bindingMatch.pathParams, operationRegistry, principal, options?.pipelineTrace, options?.maxRequestBodyBytes);
|
|
42
|
+
return await dispatchViaExecuteOperation(request, engine, bindingMatch.binding, bindingMatch.pathParams, operationRegistry, principal, options?.pipelineTrace, options?.maxRequestBodyBytes, options?.supportedAuthenticationSchemes, liveEventStreamContextFromOptions(options));
|
|
41
43
|
} catch (error) {
|
|
42
44
|
console.error("Unhandled error in dispatchViaExecuteOperation", {
|
|
43
45
|
method: request.method,
|
|
@@ -60,10 +62,7 @@ async function dispatchDirectRoute(request, engine, route, options, url) {
|
|
|
60
62
|
return errorResponse("Internal server error", 500);
|
|
61
63
|
}
|
|
62
64
|
}
|
|
63
|
-
|
|
64
|
-
const url = new URL(request.url), optionError = validateHandlerOptions(options);
|
|
65
|
-
if (optionError !== null)
|
|
66
|
-
return optionError;
|
|
65
|
+
async function dispatchMatchedRoute(request, engine, options, url) {
|
|
67
66
|
const directRouteLookup = matchRouteBoundary(() => matchDirectRoute(request.method, url.pathname));
|
|
68
67
|
if (directRouteLookup.kind === "malformed")
|
|
69
68
|
return directRouteLookup.response;
|
|
@@ -76,3 +75,21 @@ export async function handleRequest(request, engine, options) {
|
|
|
76
75
|
return dispatchRestBinding(request, engine, bindingLookup.value, operationRegistry, options, url);
|
|
77
76
|
return errorResponse(`Not found: ${request.method} ${url.pathname}`, 404);
|
|
78
77
|
}
|
|
78
|
+
async function dispatchMatchedRouteBoundary(request, engine, options, url) {
|
|
79
|
+
try {
|
|
80
|
+
return await dispatchMatchedRoute(request, engine, options, url);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error("Unhandled error in handleRequest route matching", {
|
|
83
|
+
method: request.method,
|
|
84
|
+
path: url.pathname,
|
|
85
|
+
error
|
|
86
|
+
});
|
|
87
|
+
return errorResponse("Internal server error", 500);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export async function handleRequest(request, engine, options) {
|
|
91
|
+
const url = new URL(request.url), optionError = validateHandlerOptions(options);
|
|
92
|
+
if (optionError !== null)
|
|
93
|
+
return optionError;
|
|
94
|
+
return dispatchMatchedRouteBoundary(request, engine, options, url);
|
|
95
|
+
}
|
|
@@ -2,14 +2,16 @@ import type { Engine } from '../../core/engine.ts';
|
|
|
2
2
|
import { type PrometheusExporter } from '../../observability/metrics.ts';
|
|
3
3
|
import type { AuthContext } from '../authentication.ts';
|
|
4
4
|
import type { DiscoveryInfo } from '../discovery-info.ts';
|
|
5
|
+
import type { FleetEventFeed } from '../fleet-event-feed.ts';
|
|
5
6
|
import { type OpenApiSecuritySchemeName } from '../openapi.ts';
|
|
6
7
|
import { type OperationRegistry, type PipelineTrace } from '../operation-catalog.ts';
|
|
7
8
|
import type { OperationFault } from '../operation-fault.ts';
|
|
8
|
-
import {
|
|
9
|
+
import type { WorkflowStreamConnectionAcquirer } from '../operations/workflow-events-sse.ts';
|
|
10
|
+
import type { Principal } from '../principal.ts';
|
|
9
11
|
import { type UnknownRestBinding } from '../rest-bindings.ts';
|
|
12
|
+
import type { WorkflowEventFeed } from '../workflow-event-feed.ts';
|
|
10
13
|
import type { DirectRouteHandlerName } from './route-matching.ts';
|
|
11
|
-
|
|
12
|
-
type AuthenticatedRequestContext = AuthContext;
|
|
14
|
+
import { type LiveEventStreamContext } from './sse-route-dispatch.ts';
|
|
13
15
|
/**
|
|
14
16
|
* Options bag passed to `handleRequest` by the HTTP server wrapper.
|
|
15
17
|
*
|
|
@@ -83,6 +85,12 @@ export interface HandlerOptions {
|
|
|
83
85
|
trustedHosts?: ReadonlyArray<string>;
|
|
84
86
|
/** Maximum REST operation request body size in bytes. Defaults to 1 MB. */
|
|
85
87
|
maxRequestBodyBytes?: number;
|
|
88
|
+
/** Event feed used by live workflow SSE routes. */
|
|
89
|
+
workflowEventFeed?: WorkflowEventFeed;
|
|
90
|
+
/** Fleet feed used by live fleet SSE routes. */
|
|
91
|
+
fleetEventFeed?: Pick<FleetEventFeed, 'subscribe'>;
|
|
92
|
+
/** Shared per-workflow long-lived stream connection limiter. */
|
|
93
|
+
acquireWorkflowStreamConnection?: WorkflowStreamConnectionAcquirer;
|
|
86
94
|
/**
|
|
87
95
|
* Optional pipeline-trace observer. **Internal test seam** used by the
|
|
88
96
|
* dispatch-audit suite to prove every transport drives the full
|
|
@@ -100,7 +108,7 @@ export type RouteExecutionContext = {
|
|
|
100
108
|
};
|
|
101
109
|
export type RouteExecutor = (context: RouteExecutionContext) => Promise<Response>;
|
|
102
110
|
export declare const DIRECT_ROUTE_EXECUTORS: Record<DirectRouteHandlerName, RouteExecutor>;
|
|
103
|
-
export declare function dispatchViaExecuteOperation(request: Request, engine: Engine, binding: UnknownRestBinding, pathParams: Record<string, string>, registry: OperationRegistry, principal: Principal, pipelineTrace?: PipelineTrace, maxRequestBodyBytes?: number): Promise<Response>;
|
|
111
|
+
export declare function dispatchViaExecuteOperation(request: Request, engine: Engine, binding: UnknownRestBinding, pathParams: Record<string, string>, registry: OperationRegistry, principal: Principal, pipelineTrace?: PipelineTrace, maxRequestBodyBytes?: number, supportedAuthenticationSchemes?: ReadonlySet<OpenApiSecuritySchemeName>, liveEventStreamContext?: LiveEventStreamContext): Promise<Response>;
|
|
104
112
|
/**
|
|
105
113
|
* Type guard that returns true if the value structurally resembles an
|
|
106
114
|
* {@link OperationFault} (carries `code`, `message`, and `data` properties).
|
|
@@ -125,23 +133,5 @@ export declare function dispatchViaExecuteOperation(request: Request, engine: En
|
|
|
125
133
|
* ```
|
|
126
134
|
*/
|
|
127
135
|
export declare function isOperationFaultLike(value: unknown): value is OperationFault;
|
|
128
|
-
/**
|
|
129
|
-
* Convert the REST transport's `authContext` into a `Principal`. The
|
|
130
|
-
* authenticator (`serve()`) reports method + optional claims; this maps
|
|
131
|
-
* that transport authentication result into the `Principal` used by the
|
|
132
|
-
* operation pipeline.
|
|
133
|
-
* Returns `anonymousPrincipal()` when no context is provided (public
|
|
134
|
-
* request).
|
|
135
|
-
*
|
|
136
|
-
* @example
|
|
137
|
-
* ```ts
|
|
138
|
-
* import { authContextToPrincipal } from '@lostgradient/weft/server/handler';
|
|
139
|
-
*
|
|
140
|
-
* const principal = authContextToPrincipal({ method: 'api-key' });
|
|
141
|
-
* console.log(principal.method); // 'api-key'
|
|
142
|
-
* ```
|
|
143
|
-
*/
|
|
144
|
-
export declare function authContextToPrincipal(authContext: AuthenticatedRequestContext | undefined): Principal;
|
|
145
136
|
export declare function defaultOperationRegistry(): OperationRegistry;
|
|
146
137
|
export declare function defaultRestBindings(): ReadonlyArray<UnknownRestBinding>;
|
|
147
|
-
export {};
|
|
@@ -12,12 +12,6 @@ import {
|
|
|
12
12
|
executeOperation
|
|
13
13
|
} from "../operation-catalog.js";
|
|
14
14
|
import { FAULT_CODE_TO_HTTP_STATUS } from "../operation-fault.js";
|
|
15
|
-
import {
|
|
16
|
-
anonymousPrincipal,
|
|
17
|
-
principalFromApiKey,
|
|
18
|
-
principalFromJwtClaims,
|
|
19
|
-
principalFromMutualTls
|
|
20
|
-
} from "../principal.js";
|
|
21
15
|
import {
|
|
22
16
|
createLiveOperationRegistry,
|
|
23
17
|
createLiveRestBindings
|
|
@@ -28,6 +22,10 @@ import {
|
|
|
28
22
|
jsonResponse,
|
|
29
23
|
negotiatedResponse
|
|
30
24
|
} from "./response-helpers.js";
|
|
25
|
+
import {
|
|
26
|
+
dispatchServerSentEventsBinding,
|
|
27
|
+
isDirectServerSentEventsOperation
|
|
28
|
+
} from "./sse-route-dispatch.js";
|
|
31
29
|
const defaultPrometheusExporter = createMetricsCollectorExporter(void 0);
|
|
32
30
|
function resolveDiscoveryOrigin(options) {
|
|
33
31
|
const { absoluteUrlDescription, path, request, serverOptions } = options;
|
|
@@ -113,17 +111,22 @@ export const DIRECT_ROUTE_EXECUTORS = {
|
|
|
113
111
|
...options?.discoveryInfo !== void 0 ? { discoveryInfo: options.discoveryInfo } : {}
|
|
114
112
|
}))
|
|
115
113
|
};
|
|
116
|
-
export async function dispatchViaExecuteOperation(request, engine, binding, pathParams, registry, principal, pipelineTrace, maxRequestBodyBytes) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
114
|
+
export async function dispatchViaExecuteOperation(request, engine, binding, pathParams, registry, principal, pipelineTrace, maxRequestBodyBytes, supportedAuthenticationSchemes, liveEventStreamContext) {
|
|
115
|
+
const extracted = await extractRestBindingInput(request, binding, pathParams, maxRequestBodyBytes);
|
|
116
|
+
if (!extracted.ok)
|
|
117
|
+
return extracted.response;
|
|
118
|
+
if (isDirectServerSentEventsOperation(binding.operationName))
|
|
119
|
+
return dispatchServerSentEventsBinding({
|
|
120
|
+
request,
|
|
121
|
+
binding,
|
|
122
|
+
rawInput: extracted.input,
|
|
123
|
+
principal,
|
|
124
|
+
registry,
|
|
125
|
+
...pipelineTrace === void 0 ? {} : { pipelineTrace },
|
|
126
|
+
...supportedAuthenticationSchemes === void 0 ? {} : { supportedAuthenticationSchemes },
|
|
127
|
+
...liveEventStreamContext === void 0 ? {} : { liveEventStreamContext }
|
|
128
|
+
});
|
|
129
|
+
const result = await executeOperation(binding.operationName, extracted.input, {
|
|
127
130
|
principal,
|
|
128
131
|
engine,
|
|
129
132
|
transport: "http-rest",
|
|
@@ -134,6 +137,16 @@ export async function dispatchViaExecuteOperation(request, engine, binding, path
|
|
|
134
137
|
return binding.shapeSuccess ? binding.shapeSuccess(result.value, request) : defaultShapeSuccess(result.value, binding.success);
|
|
135
138
|
return binding.shapeFault ? binding.shapeFault(result.fault) : faultToHttpResponse(result.fault);
|
|
136
139
|
}
|
|
140
|
+
async function extractRestBindingInput(request, binding, pathParams, maxRequestBodyBytes) {
|
|
141
|
+
try {
|
|
142
|
+
return { ok: !0, input: await binding.extractInput(request, pathParams, maxRequestBodyBytes !== void 0 ? { maxBodyBytes: maxRequestBodyBytes } : {}) };
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (isOperationFaultLike(error))
|
|
145
|
+
return { ok: !1, response: binding.shapeFault ? binding.shapeFault(error) : faultToHttpResponse(error) };
|
|
146
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
147
|
+
return { ok: !1, response: errorResponse(message, 400) };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
137
150
|
function hasRequiredFaultProperties(candidate) {
|
|
138
151
|
return Object.hasOwn(candidate, "code") && Object.hasOwn(candidate, "message") && Object.hasOwn(candidate, "data");
|
|
139
152
|
}
|
|
@@ -159,25 +172,6 @@ export function isOperationFaultLike(value) {
|
|
|
159
172
|
return !1;
|
|
160
173
|
return typeof fields.code === "string" && Object.hasOwn(FAULT_CODE_TO_HTTP_STATUS, fields.code) && typeof fields.message === "string" && typeof fields.data === "object" && fields.data !== null && !Array.isArray(fields.data);
|
|
161
174
|
}
|
|
162
|
-
export function authContextToPrincipal(authContext) {
|
|
163
|
-
if (authContext === void 0)
|
|
164
|
-
return anonymousPrincipal();
|
|
165
|
-
if (authContext.principal !== void 0)
|
|
166
|
-
return authContext.principal;
|
|
167
|
-
switch (authContext.method) {
|
|
168
|
-
case "jwt": {
|
|
169
|
-
if (authContext.claims === void 0)
|
|
170
|
-
throw Error("authContextToPrincipal: jwt authContext reached the pipeline without claims \u2014 " + "authenticator contract violation");
|
|
171
|
-
return principalFromJwtClaims(authContext.claims);
|
|
172
|
-
}
|
|
173
|
-
case "api-key":
|
|
174
|
-
return principalFromApiKey({ subject: "api-key-caller", scopes: [] });
|
|
175
|
-
case "mtls":
|
|
176
|
-
return principalFromMutualTls({ subject: "mtls-caller", scopes: [] });
|
|
177
|
-
case "public":
|
|
178
|
-
return anonymousPrincipal();
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
175
|
let defaultOperationRegistryCache;
|
|
182
176
|
export function defaultOperationRegistry() {
|
|
183
177
|
if (defaultOperationRegistryCache === void 0)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { FleetEventFeed } from '../fleet-event-feed.ts';
|
|
2
|
+
import type { OpenApiSecuritySchemeName } from '../openapi.ts';
|
|
3
|
+
import { type OperationRegistry, type PipelineTrace } from '../operation-catalog.ts';
|
|
4
|
+
import { type WorkflowStreamConnectionAcquirer } from '../operations/workflow-events-sse.ts';
|
|
5
|
+
import { type Principal } from '../principal.ts';
|
|
6
|
+
import type { UnknownRestBinding } from '../rest-bindings.ts';
|
|
7
|
+
import type { WorkflowEventFeed } from '../workflow-event-feed.ts';
|
|
8
|
+
export type LiveEventStreamContext = {
|
|
9
|
+
workflowEventFeed?: WorkflowEventFeed;
|
|
10
|
+
fleetEventFeed?: Pick<FleetEventFeed, 'subscribe'>;
|
|
11
|
+
acquireWorkflowStreamConnection?: WorkflowStreamConnectionAcquirer;
|
|
12
|
+
};
|
|
13
|
+
type DispatchServerSentEventsBindingOptions = {
|
|
14
|
+
readonly request: Request;
|
|
15
|
+
readonly binding: UnknownRestBinding;
|
|
16
|
+
readonly rawInput: unknown;
|
|
17
|
+
readonly principal: Principal;
|
|
18
|
+
readonly registry: OperationRegistry;
|
|
19
|
+
readonly pipelineTrace?: PipelineTrace;
|
|
20
|
+
readonly supportedAuthenticationSchemes?: ReadonlySet<OpenApiSecuritySchemeName>;
|
|
21
|
+
readonly liveEventStreamContext?: LiveEventStreamContext;
|
|
22
|
+
};
|
|
23
|
+
export declare function isDirectServerSentEventsOperation(operationName: string): boolean;
|
|
24
|
+
export declare function dispatchServerSentEventsBinding(options: DispatchServerSentEventsBindingOptions): Promise<Response>;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { faultToHttpResponse } from "../fault-to-http.js";
|
|
2
|
+
import {
|
|
3
|
+
executeOperation
|
|
4
|
+
} from "../operation-catalog.js";
|
|
5
|
+
import { fleetEventsSseOperation } from "../operations/fleet-events-sse.js";
|
|
6
|
+
import {
|
|
7
|
+
workflowEventsSseOperation
|
|
8
|
+
} from "../operations/workflow-events-sse.js";
|
|
9
|
+
import { isAuthenticated, principalFromStdioLocal } from "../principal.js";
|
|
10
|
+
export function isDirectServerSentEventsOperation(operationName) {
|
|
11
|
+
return operationName === workflowEventsSseOperation.name || operationName === fleetEventsSseOperation.name;
|
|
12
|
+
}
|
|
13
|
+
export async function dispatchServerSentEventsBinding(options) {
|
|
14
|
+
const { binding, registry } = options, missingOperationResponse = shapeMissingOperation(binding, registry);
|
|
15
|
+
if (missingOperationResponse !== null)
|
|
16
|
+
return missingOperationResponse;
|
|
17
|
+
const shapeSuccess = binding.shapeSuccess;
|
|
18
|
+
if (shapeSuccess === void 0)
|
|
19
|
+
throw Error("SSE binding missing success shaper");
|
|
20
|
+
const result = await executeOperation(binding.operationName, options.rawInput, {
|
|
21
|
+
principal: principalForServerSentEvents(options),
|
|
22
|
+
engine: options.liveEventStreamContext ?? {},
|
|
23
|
+
transport: "http-rest",
|
|
24
|
+
registry,
|
|
25
|
+
...options.pipelineTrace === void 0 ? {} : { pipelineTrace: options.pipelineTrace }
|
|
26
|
+
});
|
|
27
|
+
if (result.ok)
|
|
28
|
+
return shapeSuccess(result.value, options.request);
|
|
29
|
+
return binding.shapeFault ? binding.shapeFault(result.fault) : faultToHttpResponse(result.fault);
|
|
30
|
+
}
|
|
31
|
+
function shapeMissingOperation(binding, registry) {
|
|
32
|
+
if (registry.get(binding.operationName) !== void 0)
|
|
33
|
+
return null;
|
|
34
|
+
const fault = {
|
|
35
|
+
code: "MethodNotFound",
|
|
36
|
+
message: `unknown operation: ${binding.operationName}`,
|
|
37
|
+
data: { method: binding.operationName }
|
|
38
|
+
};
|
|
39
|
+
return binding.shapeFault ? binding.shapeFault(fault) : faultToHttpResponse(fault);
|
|
40
|
+
}
|
|
41
|
+
function principalForServerSentEvents(options) {
|
|
42
|
+
if (options.supportedAuthenticationSchemes?.size === 0 && !isAuthenticated(options.principal))
|
|
43
|
+
return principalFromStdioLocal();
|
|
44
|
+
return options.principal;
|
|
45
|
+
}
|