@jskit-ai/kernel 0.1.159 → 0.1.160
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/_testable/index.js +1 -3
- package/client/componentRegistry.js +44 -0
- package/client/componentRegistry.test.js +16 -0
- package/client/moduleBootstrap.js +54 -97
- package/client/moduleBootstrap.test.js +47 -32
- package/client/vite/clientBootstrapPlugin.test.js +2 -2
- package/internal/node/installedPackages.js +2 -7
- package/internal/node/installedPackages.test.js +26 -5
- package/package.json +3 -5
- package/server/actions/actionCatalogue.js +162 -0
- package/server/actions/actionEvents.js +66 -0
- package/server/actions/actionProvider.js +23 -0
- package/server/actions/index.js +3 -6
- package/server/features/defineFeature.js +131 -0
- package/server/features/defineFeature.test.js +238 -0
- package/server/features/index.js +1 -0
- package/server/http/HttpProvider.js +23 -0
- package/server/http/_testable/index.js +0 -1
- package/server/http/capabilityHttpRuntime.js +123 -0
- package/server/http/capabilityHttpRuntime.test.js +161 -0
- package/server/http/directRequestActionExecutor.js +78 -0
- package/server/http/index.js +2 -1
- package/server/http/lib/index.js +1 -13
- package/server/http/lib/routeRegistration.js +9 -25
- package/server/platform/index.js +2 -2
- package/server/platform/installedRuntime.js +122 -0
- package/server/platform/installedRuntime.test.js +103 -0
- package/server/platform/providerRuntime/capabilityProviderLoader.js +100 -0
- package/server/platform/providerRuntime/capabilityProviderLoader.test.js +80 -0
- package/server/platform/providerRuntime/packageCatalog.js +4 -1
- package/server/platform/surfaceRuntime.js +1 -15
- package/server/runtime/BootstrapProvider.js +69 -0
- package/server/runtime/EventProvider.js +74 -0
- package/server/runtime/EventProvider.test.js +32 -0
- package/server/runtime/entityChangeEvents.js +7 -7
- package/server/runtime/entityChangeEvents.test.js +2 -2
- package/server/runtime/fastifyBootstrap.js +0 -40
- package/server/runtime/index.js +2 -3
- package/server/surface/index.js +0 -1
- package/shared/actions/actionDefinitions.js +20 -0
- package/shared/actions/pipeline.js +22 -0
- package/shared/actions/policies.js +1 -1
- package/shared/actions/registry.js +3 -0
- package/shared/capabilities/defineProvider.js +99 -0
- package/shared/capabilities/index.js +2 -0
- package/shared/capabilities/runtime.js +243 -0
- package/shared/capabilities/runtime.test.js +170 -0
- package/test/barrelExposure.test.js +36 -13
- package/test/exportsContract.test.js +8 -3
- package/server/actions/ActionRuntimeServiceProvider.js +0 -304
- package/server/actions/ActionRuntimeServiceProvider.test.js +0 -558
- package/server/container/ContainerCoreServiceProvider.js +0 -27
- package/server/container/index.js +0 -10
- package/server/http/HttpFastifyServiceProvider.js +0 -25
- package/server/http/lib/httpRuntime.js +0 -83
- package/server/http/lib/kernel.js +0 -15
- package/server/http/lib/kernel.test.js +0 -1565
- package/server/http/lib/requestActionExecutor.js +0 -265
- package/server/http/lib/requestScope.js +0 -57
- package/server/kernel/KernelCoreServiceProvider.js +0 -27
- package/server/kernel/index.js +0 -10
- package/server/platform/PlatformServerRuntimeServiceProvider.js +0 -30
- package/server/platform/providerRuntime/providerLoader.js +0 -283
- package/server/platform/providerRuntime.js +0 -146
- package/server/platform/providerRuntime.test.js +0 -214
- package/server/platform/runtime.js +0 -40
- package/server/registries/actionSurfaceSourceRegistry.js +0 -148
- package/server/registries/bootstrapPayloadContributorRegistry.js +0 -51
- package/server/registries/domainEventListenerRegistry.js +0 -58
- package/server/registries/index.js +0 -32
- package/server/registries/primitives.js +0 -63
- package/server/registries/routeVisibilityResolverRegistry.js +0 -84
- package/server/registries/serviceRegistrationRegistry.js +0 -488
- package/server/runtime/ServerRuntimeCoreServiceProvider.js +0 -64
- package/server/runtime/ServerRuntimeCoreServiceProvider.test.js +0 -53
- package/server/runtime/apiRoutePolicyParity.test.js +0 -109
- package/server/runtime/apiRouteRegistration.js +0 -65
- package/server/runtime/bootBootstrapRoutes.js +0 -29
- package/server/runtime/bootBootstrapRoutes.test.js +0 -69
- package/server/runtime/bootstrapContributors.test.js +0 -166
- package/server/runtime/composition.js +0 -142
- package/server/runtime/domainEvents.test.js +0 -114
- package/server/runtime/domainRules.js +0 -50
- package/server/runtime/domainRules.test.js +0 -87
- package/server/runtime/runtimeAssembly.js +0 -113
- package/server/runtime/runtimeKernel.js +0 -55
- package/server/runtime/serviceRegistration.test.js +0 -251
- package/server/support/SupportCoreServiceProvider.js +0 -27
- package/server/surface/SurfaceRoutingServiceProvider.js +0 -27
- package/shared/actions/index.js +0 -2
- package/shared/runtime/application.js +0 -324
- package/shared/runtime/container.js +0 -261
- package/shared/runtime/containerErrors.js +0 -22
- package/shared/runtime/index.js +0 -18
- package/shared/runtime/kernelErrors.js +0 -25
- package/shared/runtime/kernelErrors.test.js +0 -120
- package/shared/runtime/serviceProvider.js +0 -13
- package/shared/support/containerToken.js +0 -8
- package/shared/support/generatedUiContract.js +0 -542
- package/shared/support/generatedUiContract.test.js +0 -218
- package/shared/support/tokens.js +0 -1
- package/shared/support/tokens.test.js +0 -17
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeRouteVisibility,
|
|
3
|
+
normalizeRouteVisibilityToken,
|
|
4
|
+
normalizeVisibilityContext
|
|
5
|
+
} from "../../shared/support/visibility.js";
|
|
6
|
+
import { normalizeText } from "../../shared/support/normalize.js";
|
|
7
|
+
import { createRouter } from "./lib/router.js";
|
|
8
|
+
import { registerRoutes } from "./lib/routeRegistration.js";
|
|
9
|
+
import {
|
|
10
|
+
registerApiErrorHandler,
|
|
11
|
+
registerBodylessContentTypeNormalizer
|
|
12
|
+
} from "../runtime/fastifyBootstrap.js";
|
|
13
|
+
import { isAppError } from "../runtime/errors.js";
|
|
14
|
+
|
|
15
|
+
function normalizeResolver({ id, resolve } = {}) {
|
|
16
|
+
const resolverId = normalizeText(id);
|
|
17
|
+
if (!resolverId) {
|
|
18
|
+
throw new TypeError("HTTP visibility resolver id is required.");
|
|
19
|
+
}
|
|
20
|
+
if (typeof resolve !== "function") {
|
|
21
|
+
throw new TypeError(`HTTP visibility resolver "${resolverId}" requires resolve().`);
|
|
22
|
+
}
|
|
23
|
+
return Object.freeze({ id: resolverId, resolve });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function createCapabilityHttpRuntime({ fastify, actions }) {
|
|
27
|
+
if (!fastify || typeof fastify.route !== "function") {
|
|
28
|
+
throw new TypeError("HTTP runtime requires a Fastify instance.");
|
|
29
|
+
}
|
|
30
|
+
if (!actions || typeof actions.registerContextContributor !== "function") {
|
|
31
|
+
throw new TypeError("HTTP runtime requires the runtime.actions capability.");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const router = createRouter();
|
|
35
|
+
const visibilityResolvers = [];
|
|
36
|
+
const visibilityResolverIds = new Set();
|
|
37
|
+
let started = false;
|
|
38
|
+
let registration = null;
|
|
39
|
+
|
|
40
|
+
function assertOpen() {
|
|
41
|
+
if (started) {
|
|
42
|
+
throw new Error("HTTP runtime registration is closed after startup.");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function registerVisibilityResolver(value) {
|
|
47
|
+
assertOpen();
|
|
48
|
+
const resolver = normalizeResolver(value);
|
|
49
|
+
if (visibilityResolverIds.has(resolver.id)) {
|
|
50
|
+
throw new Error(`HTTP visibility resolver "${resolver.id}" is duplicated.`);
|
|
51
|
+
}
|
|
52
|
+
visibilityResolverIds.add(resolver.id);
|
|
53
|
+
visibilityResolvers.push(resolver);
|
|
54
|
+
return api;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
actions.registerContextContributor({
|
|
58
|
+
id: "runtime.http.visibility",
|
|
59
|
+
async contribute({ actionId, version, definition, input, context }) {
|
|
60
|
+
const visibility = normalizeRouteVisibilityToken(context.routeVisibility);
|
|
61
|
+
const patch = {};
|
|
62
|
+
for (const resolver of visibilityResolvers) {
|
|
63
|
+
const contribution = await resolver.resolve(Object.freeze({
|
|
64
|
+
request: context.requestMeta?.request || null,
|
|
65
|
+
visibility,
|
|
66
|
+
context,
|
|
67
|
+
input,
|
|
68
|
+
actionId,
|
|
69
|
+
version,
|
|
70
|
+
definition,
|
|
71
|
+
channel: normalizeText(context.channel).toLowerCase() || "api"
|
|
72
|
+
}));
|
|
73
|
+
if (contribution && typeof contribution === "object" && !Array.isArray(contribution)) {
|
|
74
|
+
Object.assign(patch, contribution);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const coreVisibility = normalizeRouteVisibility(visibility);
|
|
78
|
+
const visibilityContext = normalizeVisibilityContext({
|
|
79
|
+
...patch,
|
|
80
|
+
visibility,
|
|
81
|
+
requiresActorScope: patch.requiresActorScope === true || coreVisibility === "user"
|
|
82
|
+
});
|
|
83
|
+
return {
|
|
84
|
+
visibilityContext,
|
|
85
|
+
requestMeta: {
|
|
86
|
+
visibilityContext,
|
|
87
|
+
routeVisibility: visibilityContext.visibility
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
function start(options = {}) {
|
|
94
|
+
assertOpen();
|
|
95
|
+
started = true;
|
|
96
|
+
registerBodylessContentTypeNormalizer(fastify);
|
|
97
|
+
registerApiErrorHandler(fastify, { isAppError });
|
|
98
|
+
registration = Object.freeze(registerRoutes(fastify, {
|
|
99
|
+
...options,
|
|
100
|
+
actions,
|
|
101
|
+
routes: router.list()
|
|
102
|
+
}));
|
|
103
|
+
return registration;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function diagnostics() {
|
|
107
|
+
return Object.freeze({
|
|
108
|
+
started,
|
|
109
|
+
routeCount: registration?.routeCount || router.list().filter((route) => route.internal !== true).length,
|
|
110
|
+
visibilityResolverIds: Object.freeze([...visibilityResolverIds].sort())
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const api = Object.freeze({
|
|
115
|
+
router,
|
|
116
|
+
registerVisibilityResolver,
|
|
117
|
+
start,
|
|
118
|
+
diagnostics
|
|
119
|
+
});
|
|
120
|
+
return api;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export { createCapabilityHttpRuntime };
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { createSchema } from "json-rest-schema";
|
|
4
|
+
|
|
5
|
+
import { createActionProvider } from "../actions/actionProvider.js";
|
|
6
|
+
import { createCapabilityRuntime } from "../../shared/capabilities/runtime.js";
|
|
7
|
+
import { defineFeature } from "../features/defineFeature.js";
|
|
8
|
+
import { HttpProvider } from "./HttpProvider.js";
|
|
9
|
+
|
|
10
|
+
function createFastifyStub() {
|
|
11
|
+
return {
|
|
12
|
+
routes: [],
|
|
13
|
+
hooks: {},
|
|
14
|
+
contentTypeParsers: new Map(),
|
|
15
|
+
log: { error() {} },
|
|
16
|
+
route(value) {
|
|
17
|
+
this.routes.push(value);
|
|
18
|
+
},
|
|
19
|
+
addHook(name, handler) {
|
|
20
|
+
this.hooks[name] = handler;
|
|
21
|
+
},
|
|
22
|
+
setErrorHandler(handler) {
|
|
23
|
+
this.errorHandler = handler;
|
|
24
|
+
},
|
|
25
|
+
hasContentTypeParser(type) {
|
|
26
|
+
return this.contentTypeParsers.has(type);
|
|
27
|
+
},
|
|
28
|
+
getDefaultJsonParser() {
|
|
29
|
+
return (_request, body, done) => done(null, body);
|
|
30
|
+
},
|
|
31
|
+
addContentTypeParser(type, options, parser) {
|
|
32
|
+
this.contentTypeParsers.set(type, { options, parser });
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function createReply() {
|
|
38
|
+
return {
|
|
39
|
+
statusCode: 200,
|
|
40
|
+
headers: {},
|
|
41
|
+
code(value) {
|
|
42
|
+
this.statusCode = value;
|
|
43
|
+
return this;
|
|
44
|
+
},
|
|
45
|
+
header(name, value) {
|
|
46
|
+
this.headers[name.toLowerCase()] = value;
|
|
47
|
+
return this;
|
|
48
|
+
},
|
|
49
|
+
hasHeader(name) {
|
|
50
|
+
return Object.hasOwn(this.headers, name.toLowerCase());
|
|
51
|
+
},
|
|
52
|
+
send(value) {
|
|
53
|
+
this.payload = value;
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
test("HttpProvider registers routes that invoke actions directly", async () => {
|
|
60
|
+
const fastify = createFastifyStub();
|
|
61
|
+
const feature = defineFeature({
|
|
62
|
+
id: "feature.http-proof",
|
|
63
|
+
domain: "http-proof",
|
|
64
|
+
requires: { http: "runtime.http" },
|
|
65
|
+
setup({ http }) {
|
|
66
|
+
http.router.get("/proof", {
|
|
67
|
+
surface: "app",
|
|
68
|
+
visibility: "public"
|
|
69
|
+
}, async (request, reply) => {
|
|
70
|
+
const result = await request.executeAction({ actionId: "http-proof.read" });
|
|
71
|
+
reply.code(200).send(result);
|
|
72
|
+
});
|
|
73
|
+
return {};
|
|
74
|
+
},
|
|
75
|
+
actions: [{
|
|
76
|
+
id: "http-proof.read",
|
|
77
|
+
kind: "query",
|
|
78
|
+
channels: ["api"],
|
|
79
|
+
surfaces: ["app"],
|
|
80
|
+
input: { schema: createSchema({}) },
|
|
81
|
+
idempotency: "none",
|
|
82
|
+
async execute(_input, context) {
|
|
83
|
+
return {
|
|
84
|
+
channel: context.channel,
|
|
85
|
+
surface: context.surface,
|
|
86
|
+
visibility: context.visibilityContext.visibility,
|
|
87
|
+
hasRequest: Boolean(context.requestMeta.request)
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}]
|
|
91
|
+
});
|
|
92
|
+
const runtime = createCapabilityRuntime({
|
|
93
|
+
inputs: { "runtime.fastify": fastify },
|
|
94
|
+
providers: [createActionProvider(), HttpProvider, feature]
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
await runtime.start();
|
|
98
|
+
assert.equal(fastify.routes.length, 1);
|
|
99
|
+
assert.equal(fastify.routes[0].url, "/proof");
|
|
100
|
+
|
|
101
|
+
const request = { id: "req-1", routeOptions: { config: fastify.routes[0].config } };
|
|
102
|
+
const reply = createReply();
|
|
103
|
+
await fastify.routes[0].handler(request, reply);
|
|
104
|
+
|
|
105
|
+
assert.equal(reply.statusCode, 200);
|
|
106
|
+
assert.deepEqual(reply.payload, {
|
|
107
|
+
channel: "api",
|
|
108
|
+
surface: "app",
|
|
109
|
+
visibility: "public",
|
|
110
|
+
hasRequest: true
|
|
111
|
+
});
|
|
112
|
+
assert.equal(Object.hasOwn(request, "scope"), false);
|
|
113
|
+
await runtime.shutdown();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("direct HTTP action execution preserves request-scoped stream dependencies", async () => {
|
|
117
|
+
const fastify = createFastifyStub();
|
|
118
|
+
const streamWriter = Object.freeze({ id: "writer-1" });
|
|
119
|
+
const abortSignal = Object.freeze({ aborted: false });
|
|
120
|
+
const feature = defineFeature({
|
|
121
|
+
id: "feature.stream-proof",
|
|
122
|
+
domain: "stream-proof",
|
|
123
|
+
requires: { http: "runtime.http" },
|
|
124
|
+
setup({ http }) {
|
|
125
|
+
http.router.post("/stream-proof", { surface: "app", visibility: "public" }, async (request, reply) => {
|
|
126
|
+
const result = await request.executeAction({
|
|
127
|
+
actionId: "stream-proof.run",
|
|
128
|
+
deps: { streamWriter, abortSignal }
|
|
129
|
+
});
|
|
130
|
+
reply.code(200).send(result);
|
|
131
|
+
});
|
|
132
|
+
return {};
|
|
133
|
+
},
|
|
134
|
+
actions: [{
|
|
135
|
+
id: "stream-proof.run",
|
|
136
|
+
kind: "stream",
|
|
137
|
+
channels: ["api"],
|
|
138
|
+
surfaces: ["app"],
|
|
139
|
+
input: { schema: createSchema({}) },
|
|
140
|
+
idempotency: "none",
|
|
141
|
+
async execute(_input, _context, deps) {
|
|
142
|
+
return {
|
|
143
|
+
writerId: deps.streamWriter.id,
|
|
144
|
+
aborted: deps.abortSignal.aborted
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}]
|
|
148
|
+
});
|
|
149
|
+
const runtime = createCapabilityRuntime({
|
|
150
|
+
inputs: { "runtime.fastify": fastify },
|
|
151
|
+
providers: [createActionProvider(), HttpProvider, feature]
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
await runtime.start();
|
|
155
|
+
const request = { routeOptions: { config: fastify.routes[0].config } };
|
|
156
|
+
const reply = createReply();
|
|
157
|
+
await fastify.routes[0].handler(request, reply);
|
|
158
|
+
|
|
159
|
+
assert.deepEqual(reply.payload, { writerId: "writer-1", aborted: false });
|
|
160
|
+
await runtime.shutdown();
|
|
161
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { normalizeObject, normalizeText } from "../../shared/support/normalize.js";
|
|
2
|
+
import { normalizeRouteVisibilityToken } from "../../shared/support/visibility.js";
|
|
3
|
+
import { resolveDefaultSurfaceId } from "../support/appConfig.js";
|
|
4
|
+
import { RouteRegistrationError } from "./lib/errors.js";
|
|
5
|
+
|
|
6
|
+
function resolveSurface(request, explicitSurface = "", defaultSurfaceId = "") {
|
|
7
|
+
return (
|
|
8
|
+
normalizeText(explicitSurface).toLowerCase() ||
|
|
9
|
+
normalizeText(request?.routeOptions?.config?.surface).toLowerCase() ||
|
|
10
|
+
normalizeText(request?.surface).toLowerCase() ||
|
|
11
|
+
resolveDefaultSurfaceId(null, { defaultSurfaceId })
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function routeVisibility(request, payload = {}) {
|
|
16
|
+
const routeConfig = normalizeObject(request?.routeOptions?.config);
|
|
17
|
+
return normalizeRouteVisibilityToken(
|
|
18
|
+
Object.hasOwn(routeConfig, "visibility")
|
|
19
|
+
? routeConfig.visibility
|
|
20
|
+
: payload.visibility
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function attachDirectRequestActionExecutor({
|
|
25
|
+
actions,
|
|
26
|
+
request,
|
|
27
|
+
property = "executeAction",
|
|
28
|
+
defaultChannel = "api",
|
|
29
|
+
defaultSurfaceId = ""
|
|
30
|
+
} = {}) {
|
|
31
|
+
if (!actions || typeof actions.execute !== "function") {
|
|
32
|
+
throw new RouteRegistrationError("Direct request action execution requires the runtime.actions capability.");
|
|
33
|
+
}
|
|
34
|
+
if (!request || typeof request !== "object") {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const propertyName = normalizeText(property) || "executeAction";
|
|
38
|
+
if (typeof request[propertyName] === "function") {
|
|
39
|
+
return request[propertyName];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const executeAction = async (payload = {}) => {
|
|
43
|
+
const source = normalizeObject(payload);
|
|
44
|
+
const channel = normalizeText(source.channel || defaultChannel).toLowerCase() || "api";
|
|
45
|
+
const context = Object.freeze({
|
|
46
|
+
...normalizeObject(source.context),
|
|
47
|
+
channel,
|
|
48
|
+
surface: resolveSurface(request, source.surface, defaultSurfaceId),
|
|
49
|
+
routeVisibility: routeVisibility(request, source),
|
|
50
|
+
requestMeta: Object.freeze({
|
|
51
|
+
...normalizeObject(source.context?.requestMeta),
|
|
52
|
+
request
|
|
53
|
+
})
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const actionId = source.actionId;
|
|
57
|
+
const version = source.version == null ? null : source.version;
|
|
58
|
+
const definition = actions.getDefinition(actionId, version);
|
|
59
|
+
const execute = definition.kind === "stream" ? actions.executeStream : actions.execute;
|
|
60
|
+
return execute({
|
|
61
|
+
actionId,
|
|
62
|
+
version,
|
|
63
|
+
input: normalizeObject(source.input),
|
|
64
|
+
context,
|
|
65
|
+
deps: normalizeObject(source.deps)
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
Object.defineProperty(request, propertyName, {
|
|
70
|
+
configurable: true,
|
|
71
|
+
enumerable: false,
|
|
72
|
+
writable: true,
|
|
73
|
+
value: executeAction
|
|
74
|
+
});
|
|
75
|
+
return executeAction;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export { attachDirectRequestActionExecutor };
|
package/server/http/index.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { createCapabilityHttpRuntime } from "./capabilityHttpRuntime.js";
|
|
2
|
+
export { HttpProvider } from "./HttpProvider.js";
|
package/server/http/lib/index.js
CHANGED
|
@@ -2,16 +2,4 @@ export { HttpKernelError, RouteDefinitionError, RouteRegistrationError } from ".
|
|
|
2
2
|
export { HttpRouter, createRouter, joinPath } from "./router.js";
|
|
3
3
|
export { defineRouteValidator, compileRouteValidator, resolveRouteValidatorOptions } from "./routeValidator.js";
|
|
4
4
|
export { BaseController, DEFAULT_DOMAIN_ERROR_STATUS_BY_CODE, resolveDomainErrorStatus } from "./controller.js";
|
|
5
|
-
export {
|
|
6
|
-
defaultMissingHandler,
|
|
7
|
-
defaultApplyRoutePolicy,
|
|
8
|
-
normalizeRoutePolicyConfig,
|
|
9
|
-
registerRoutes,
|
|
10
|
-
registerHttpRuntime,
|
|
11
|
-
createHttpRuntime
|
|
12
|
-
} from "./kernel.js";
|
|
13
|
-
export {
|
|
14
|
-
resolveRouteVisibilityResolvers,
|
|
15
|
-
registerRouteVisibilityResolver,
|
|
16
|
-
resolveRouteVisibilityContext
|
|
17
|
-
} from "../../registries/routeVisibilityResolverRegistry.js";
|
|
5
|
+
export { defaultMissingHandler, registerRoutes } from "./routeRegistration.js";
|
|
@@ -6,8 +6,7 @@ import { defaultMissingHandler } from "../../support/defaultMissingHandler.js";
|
|
|
6
6
|
import { registerJsonApiContentTypeParser } from "../../runtime/fastifyBootstrap.js";
|
|
7
7
|
import { RouteRegistrationError } from "./errors.js";
|
|
8
8
|
import { executeMiddlewareStack, normalizeRuntimeMiddlewareConfig, resolveRouteMiddlewareHandlers } from "./middlewareRuntime.js";
|
|
9
|
-
import {
|
|
10
|
-
import { attachRequestActionExecutor } from "./requestActionExecutor.js";
|
|
9
|
+
import { attachDirectRequestActionExecutor } from "../directRequestActionExecutor.js";
|
|
11
10
|
import { normalizeRouteOutputTransform, normalizeRouteTransport } from "./routeTransport.js";
|
|
12
11
|
|
|
13
12
|
const { structuredClone: cloneRouteSchema } = globalThis;
|
|
@@ -291,23 +290,21 @@ function registerRoutes(
|
|
|
291
290
|
fastify,
|
|
292
291
|
{
|
|
293
292
|
routes = [],
|
|
294
|
-
app = null,
|
|
295
293
|
applyRoutePolicy = defaultApplyRoutePolicy,
|
|
296
294
|
missingHandler = defaultMissingHandler,
|
|
297
|
-
enableRequestScope = true,
|
|
298
|
-
requestScopeProperty = "scope",
|
|
299
295
|
requestActionExecutorProperty = "executeAction",
|
|
300
|
-
actionExecutorToken = "actionExecutor",
|
|
301
296
|
requestActionDefaultChannel = "api",
|
|
302
297
|
requestActionDefaultSurface = "",
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
middleware = {}
|
|
298
|
+
middleware = {},
|
|
299
|
+
actions = null
|
|
306
300
|
} = {}
|
|
307
301
|
) {
|
|
308
302
|
if (!fastify || typeof fastify.route !== "function") {
|
|
309
303
|
throw new RouteRegistrationError("registerRoutes requires a Fastify instance.");
|
|
310
304
|
}
|
|
305
|
+
if (!actions || typeof actions.execute !== "function") {
|
|
306
|
+
throw new RouteRegistrationError("registerRoutes requires the runtime.actions capability.");
|
|
307
|
+
}
|
|
311
308
|
|
|
312
309
|
const normalizedRoutes = normalizeArray(routes);
|
|
313
310
|
const publicRoutes = normalizedRoutes.filter((route) => route?.internal !== true);
|
|
@@ -353,23 +350,10 @@ function registerRoutes(
|
|
|
353
350
|
request.routeOptions.config = normalizeObject(routeOptions?.config);
|
|
354
351
|
}
|
|
355
352
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
app,
|
|
359
|
-
request,
|
|
360
|
-
reply,
|
|
361
|
-
requestScopeProperty,
|
|
362
|
-
requestScopeIdPrefix,
|
|
363
|
-
requestIdResolver
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
attachRequestActionExecutor({
|
|
368
|
-
app,
|
|
353
|
+
attachDirectRequestActionExecutor({
|
|
354
|
+
actions,
|
|
369
355
|
request,
|
|
370
|
-
|
|
371
|
-
requestActionExecutorProperty,
|
|
372
|
-
actionExecutorToken,
|
|
356
|
+
property: requestActionExecutorProperty,
|
|
373
357
|
defaultChannel: requestActionDefaultChannel,
|
|
374
358
|
defaultSurfaceId: routeActionDefaultSurface
|
|
375
359
|
});
|
package/server/platform/index.js
CHANGED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { createActionProvider } from "../actions/actionProvider.js";
|
|
2
|
+
import { createCapabilityRuntime } from "../../shared/capabilities/runtime.js";
|
|
3
|
+
import { HttpProvider } from "../http/HttpProvider.js";
|
|
4
|
+
import { BootstrapProvider } from "../runtime/BootstrapProvider.js";
|
|
5
|
+
import { EventProvider } from "../runtime/EventProvider.js";
|
|
6
|
+
import {
|
|
7
|
+
collectGlobalUiPaths,
|
|
8
|
+
resolveInstalledJskitPackages,
|
|
9
|
+
resolvePackageLoadOrder,
|
|
10
|
+
validatePackageCapabilities
|
|
11
|
+
} from "./providerRuntime/packageCatalog.js";
|
|
12
|
+
import {
|
|
13
|
+
appendCapabilityProviders,
|
|
14
|
+
loadCapabilityProviders
|
|
15
|
+
} from "./providerRuntime/capabilityProviderLoader.js";
|
|
16
|
+
|
|
17
|
+
function normalizeInputCapabilities({ inputs, config, env, logger, fastify, appRoot }) {
|
|
18
|
+
const source = inputs && typeof inputs === "object" && !Array.isArray(inputs) ? inputs : {};
|
|
19
|
+
const normalized = {
|
|
20
|
+
...source,
|
|
21
|
+
"runtime.app-root": appRoot,
|
|
22
|
+
"runtime.config": Object.freeze({ ...(config && typeof config === "object" ? config : {}) }),
|
|
23
|
+
"runtime.env": Object.freeze({ ...(env && typeof env === "object" ? env : {}) }),
|
|
24
|
+
"runtime.logger": logger || console,
|
|
25
|
+
...(fastify && typeof fastify.route === "function" ? { "runtime.fastify": fastify } : {})
|
|
26
|
+
};
|
|
27
|
+
return Object.freeze(normalized);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function builtinCapabilityProviders(inputCapabilities) {
|
|
31
|
+
return Object.fromEntries([
|
|
32
|
+
["runtime.actions", ["@jskit-ai/kernel"]],
|
|
33
|
+
["runtime.events", ["@jskit-ai/kernel"]],
|
|
34
|
+
...(Object.hasOwn(inputCapabilities, "runtime.fastify")
|
|
35
|
+
? [
|
|
36
|
+
["runtime.bootstrap", ["@jskit-ai/kernel"]],
|
|
37
|
+
["runtime.http", ["@jskit-ai/kernel"]]
|
|
38
|
+
]
|
|
39
|
+
: []),
|
|
40
|
+
...Object.keys(inputCapabilities).map((capabilityId) => [capabilityId, ["application"]])
|
|
41
|
+
]);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function createInstalledRuntime({
|
|
45
|
+
appRoot,
|
|
46
|
+
profile = "",
|
|
47
|
+
providers = [],
|
|
48
|
+
inputs = {},
|
|
49
|
+
config = {},
|
|
50
|
+
env = {},
|
|
51
|
+
logger = console,
|
|
52
|
+
fastify = null
|
|
53
|
+
} = {}) {
|
|
54
|
+
if (!appRoot || typeof appRoot !== "string") {
|
|
55
|
+
throw new TypeError("createInstalledRuntime requires appRoot.");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const inputCapabilities = normalizeInputCapabilities({ inputs, config, env, logger, fastify, appRoot });
|
|
59
|
+
const installedPackages = await resolveInstalledJskitPackages({ appRoot });
|
|
60
|
+
validatePackageCapabilities(installedPackages, {
|
|
61
|
+
builtinProvidersByCapability: builtinCapabilityProviders(inputCapabilities)
|
|
62
|
+
});
|
|
63
|
+
const orderedPackages = resolvePackageLoadOrder(installedPackages);
|
|
64
|
+
const orderedProviders = [];
|
|
65
|
+
const seenProviderIds = new Map();
|
|
66
|
+
const providerPackageOrder = [];
|
|
67
|
+
|
|
68
|
+
appendCapabilityProviders({
|
|
69
|
+
providers: [
|
|
70
|
+
EventProvider,
|
|
71
|
+
createActionProvider({ logger }),
|
|
72
|
+
...(fastify && typeof fastify.route === "function" ? [HttpProvider, BootstrapProvider] : [])
|
|
73
|
+
],
|
|
74
|
+
sourceId: "@jskit-ai/kernel",
|
|
75
|
+
seenProviderIds,
|
|
76
|
+
orderedProviders
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
for (const packageEntry of orderedPackages) {
|
|
80
|
+
const packageProviders = await loadCapabilityProviders({ packageEntry });
|
|
81
|
+
if (packageProviders.length === 0) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
providerPackageOrder.push(packageEntry.packageId);
|
|
85
|
+
appendCapabilityProviders({
|
|
86
|
+
providers: packageProviders,
|
|
87
|
+
sourceId: packageEntry.packageId,
|
|
88
|
+
seenProviderIds,
|
|
89
|
+
orderedProviders
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
appendCapabilityProviders({
|
|
94
|
+
providers: Array.isArray(providers) ? providers : [],
|
|
95
|
+
sourceId: "application",
|
|
96
|
+
seenProviderIds,
|
|
97
|
+
orderedProviders
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const runtime = createCapabilityRuntime({
|
|
101
|
+
providers: orderedProviders,
|
|
102
|
+
inputs: inputCapabilities,
|
|
103
|
+
profile
|
|
104
|
+
});
|
|
105
|
+
await runtime.start();
|
|
106
|
+
|
|
107
|
+
if (fastify && typeof fastify.addHook === "function") {
|
|
108
|
+
fastify.addHook("onClose", async () => {
|
|
109
|
+
await runtime.shutdown();
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return Object.freeze({
|
|
114
|
+
runtime,
|
|
115
|
+
packageOrder: Object.freeze(orderedPackages.map((entry) => entry.packageId)),
|
|
116
|
+
providerPackageOrder: Object.freeze(providerPackageOrder),
|
|
117
|
+
globalUiPaths: collectGlobalUiPaths(orderedPackages),
|
|
118
|
+
diagnostics: runtime.diagnostics()
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export { createInstalledRuntime };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import { defineProvider } from "@jskit-ai/kernel/shared/capabilities";
|
|
7
|
+
import { createInstalledRuntime } from "@jskit-ai/kernel/server/platform";
|
|
8
|
+
|
|
9
|
+
test("installed runtime composes explicit package providers and application inputs", async (context) => {
|
|
10
|
+
const appRoot = await mkdtemp(path.join(tmpdir(), "jskit-installed-runtime-"));
|
|
11
|
+
context.after(() => rm(appRoot, { recursive: true, force: true }));
|
|
12
|
+
const packageRoot = path.join(appRoot, "packages", "example");
|
|
13
|
+
await mkdir(path.join(packageRoot, "src"), { recursive: true });
|
|
14
|
+
await writeFile(
|
|
15
|
+
path.join(appRoot, "package.json"),
|
|
16
|
+
JSON.stringify({
|
|
17
|
+
name: "fixture-app",
|
|
18
|
+
private: true,
|
|
19
|
+
dependencies: { "@fixture/example": "file:packages/example" }
|
|
20
|
+
})
|
|
21
|
+
);
|
|
22
|
+
await writeFile(
|
|
23
|
+
path.join(packageRoot, "package.json"),
|
|
24
|
+
JSON.stringify({
|
|
25
|
+
name: "@fixture/example",
|
|
26
|
+
version: "0.1.0",
|
|
27
|
+
type: "module",
|
|
28
|
+
jskit: {
|
|
29
|
+
kind: "runtime",
|
|
30
|
+
capabilities: {
|
|
31
|
+
requires: ["runtime.env"],
|
|
32
|
+
provides: ["feature.example"]
|
|
33
|
+
},
|
|
34
|
+
runtime: {
|
|
35
|
+
server: {
|
|
36
|
+
providers: [{ entrypoint: "src/provider.js", export: "ExampleProvider" }]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
);
|
|
42
|
+
await writeFile(
|
|
43
|
+
path.join(packageRoot, "src", "provider.js"),
|
|
44
|
+
[
|
|
45
|
+
"export const ExampleProvider = {",
|
|
46
|
+
" id: 'feature.example',",
|
|
47
|
+
" requires: { env: 'runtime.env' },",
|
|
48
|
+
" provides: { example: 'feature.example' },",
|
|
49
|
+
" setup({ env }) { return { example: { name: env.APP_NAME } }; }",
|
|
50
|
+
"};"
|
|
51
|
+
].join("\n")
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
let observed = null;
|
|
55
|
+
const observer = defineProvider({
|
|
56
|
+
id: "fixture.observer",
|
|
57
|
+
requires: {
|
|
58
|
+
actions: "runtime.actions",
|
|
59
|
+
example: "feature.example",
|
|
60
|
+
logger: "runtime.logger"
|
|
61
|
+
},
|
|
62
|
+
setup(dependencies) {
|
|
63
|
+
observed = dependencies;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
const runtime = await createInstalledRuntime({
|
|
67
|
+
appRoot,
|
|
68
|
+
env: { APP_NAME: "Books" },
|
|
69
|
+
logger: { info() {} },
|
|
70
|
+
providers: [observer]
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
assert.equal(observed.example.name, "Books");
|
|
74
|
+
assert.equal(typeof observed.actions.execute, "function");
|
|
75
|
+
assert.equal(typeof observed.logger.info, "function");
|
|
76
|
+
assert.deepEqual(runtime.packageOrder, ["@fixture/example"]);
|
|
77
|
+
assert.deepEqual(runtime.providerPackageOrder, ["@fixture/example"]);
|
|
78
|
+
assert.deepEqual(runtime.diagnostics.providerOrder, [
|
|
79
|
+
"feature.example",
|
|
80
|
+
"runtime.events",
|
|
81
|
+
"runtime.actions",
|
|
82
|
+
"fixture.observer"
|
|
83
|
+
]);
|
|
84
|
+
assert.equal(Object.hasOwn(runtime, "app"), false);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("installed runtime attaches one idempotent Fastify shutdown hook", async (context) => {
|
|
88
|
+
const appRoot = await mkdtemp(path.join(tmpdir(), "jskit-installed-runtime-close-"));
|
|
89
|
+
context.after(() => rm(appRoot, { recursive: true, force: true }));
|
|
90
|
+
await writeFile(path.join(appRoot, "package.json"), JSON.stringify({ name: "fixture-app", private: true }));
|
|
91
|
+
const hooks = [];
|
|
92
|
+
const fastify = {
|
|
93
|
+
addHook(name, handler) {
|
|
94
|
+
hooks.push({ name, handler });
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const installed = await createInstalledRuntime({ appRoot, fastify });
|
|
98
|
+
const closeHook = hooks.find((entry) => entry.name === "onClose");
|
|
99
|
+
assert.ok(closeHook);
|
|
100
|
+
await closeHook.handler();
|
|
101
|
+
await closeHook.handler();
|
|
102
|
+
assert.equal(installed.runtime.diagnostics().lifecycleState, "stopped");
|
|
103
|
+
});
|