@pulse-compute/provider-node 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 +60 -1
- package/package.json +61 -6
- package/src/capabilities.js +56 -0
- package/src/compiler/assets-provider-proof.js +434 -0
- package/src/compiler/backend-json-request-body-proof.js +90 -0
- package/src/compiler/index.js +38 -0
- package/src/compiler/node-adapter.js +452 -0
- package/src/compiler/node-compiled-wasm-assets-lifecycle-proof.js +435 -0
- package/src/compiler/request-json-body-proof.js +181 -0
- package/src/compiler/route-handler-compiled-wasm-bridge.js +323 -0
- package/src/compiler/route-handler-effect-proof.js +215 -0
- package/src/compiler/route-handler-live-origin-proof.js +372 -0
- package/src/compiler/schema-decode-result-proof.js +180 -0
- package/src/compiler/schema-response-codec-proof.js +149 -0
- package/src/events/reference-adapter.js +145 -0
- package/src/index.js +57 -0
- package/src/javascript/assets-adapter.js +68 -0
- package/src/javascript/bindings-adapter.js +120 -0
- package/src/javascript/fetch-adapter.js +187 -0
- package/src/javascript/grip-broadcast.js +243 -0
- package/src/javascript/jwt-verifier.js +137 -0
- package/src/javascript/lifecycle.js +69 -0
- package/src/javascript/node-adapter.js +281 -0
- package/src/javascript/package-effects.js +78 -0
- package/src/javascript/runtime-host.js +165 -0
- package/src/javascript/source-package.js +439 -0
- package/src/javascript/support.js +205 -0
- package/src/javascript/target-support-policy.js +134 -0
- package/src/javascript/target.js +37 -0
- package/src/javascript/test-runtime.js +111 -0
- package/src/native/target.js +78 -0
- package/src/runtime/assets-provider.js +776 -0
- package/src/runtime/canonical-api-runtime.js +632 -0
- package/src/runtime/compiled-wasm-node-adapter-kv.js +15 -0
- package/src/runtime/index.js +17 -0
- package/src/runtime/jwt-verifier.js +264 -0
- package/src/runtime/route-handler-effects.js +757 -0
- package/src/toolchain.js +258 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { Readable } = require('node:stream');
|
|
4
|
+
const { pipeline } = require('node:stream/promises');
|
|
5
|
+
const runtimeHost = require('@pulse-compute/runtime/host');
|
|
6
|
+
const { executeNodeJavascriptApplication } = require('./runtime-host.js');
|
|
7
|
+
const {
|
|
8
|
+
createNodeJavascriptBindingCapabilities,
|
|
9
|
+
withNodeBindingCapabilities
|
|
10
|
+
} = require('./bindings-adapter.js');
|
|
11
|
+
|
|
12
|
+
const NODE_JAVASCRIPT_REQUEST_ADAPTER_VERSION = 'pulse.node-javascript-request-adapter.v1';
|
|
13
|
+
const DEFAULT_MAX_BODY_BYTES = 1024 * 1024;
|
|
14
|
+
|
|
15
|
+
class PulseNodeJavascriptRequestError extends Error {
|
|
16
|
+
constructor(code, message, detail) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = 'PulseNodeJavascriptRequestError';
|
|
19
|
+
this.code = code;
|
|
20
|
+
this.detail = detail && Object.freeze(detail);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeRawHeaders(rawHeaders) {
|
|
25
|
+
if (!Array.isArray(rawHeaders)) return Object.freeze([]);
|
|
26
|
+
const pairs = [];
|
|
27
|
+
for (let index = 0; index < rawHeaders.length; index += 2) {
|
|
28
|
+
const name = rawHeaders[index];
|
|
29
|
+
const value = rawHeaders[index + 1];
|
|
30
|
+
if (name !== undefined && value !== undefined) pairs.push(Object.freeze([String(name), String(value)]));
|
|
31
|
+
}
|
|
32
|
+
return Object.freeze(pairs);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function nodeRequestHeaderPairs(request) {
|
|
36
|
+
if (Array.isArray(request && request.rawHeaders) && request.rawHeaders.length > 0) {
|
|
37
|
+
return normalizeRawHeaders(request.rawHeaders);
|
|
38
|
+
}
|
|
39
|
+
const pairs = [];
|
|
40
|
+
for (const [name, value] of Object.entries(request && request.headers || {})) {
|
|
41
|
+
if (Array.isArray(value)) {
|
|
42
|
+
for (const item of value) pairs.push(Object.freeze([String(name), String(item)]));
|
|
43
|
+
} else if (value !== undefined) {
|
|
44
|
+
pairs.push(Object.freeze([String(name), String(value)]));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return Object.freeze(pairs);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function requestOrigin(request, options = {}) {
|
|
51
|
+
if (options.baseUrl) return new URL(String(options.baseUrl)).origin;
|
|
52
|
+
const encrypted = Boolean(request && request.socket && request.socket.encrypted);
|
|
53
|
+
const protocol = options.protocol || (encrypted ? 'https' : 'http');
|
|
54
|
+
const host = request && request.headers && request.headers.host
|
|
55
|
+
? String(request.headers.host)
|
|
56
|
+
: String(options.host || '127.0.0.1');
|
|
57
|
+
return `${protocol}://${host}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function requestUrl(request, options = {}) {
|
|
61
|
+
const raw = request && request.url ? String(request.url) : '/';
|
|
62
|
+
return new URL(raw, requestOrigin(request, options)).toString();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function bodyAllowed(method) {
|
|
66
|
+
const normalized = String(method || 'GET').toUpperCase();
|
|
67
|
+
return normalized !== 'GET' && normalized !== 'HEAD';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function readNodeRequestBody(request, maxBytes = DEFAULT_MAX_BODY_BYTES) {
|
|
71
|
+
const limit = Number(maxBytes);
|
|
72
|
+
if (!Number.isSafeInteger(limit) || limit <= 0) throw new TypeError('Pulse Node maxBodyBytes must be a positive safe integer.');
|
|
73
|
+
const chunks = [];
|
|
74
|
+
let bytes = 0;
|
|
75
|
+
try {
|
|
76
|
+
for await (const chunk of request) {
|
|
77
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
78
|
+
bytes += buffer.byteLength;
|
|
79
|
+
if (bytes > limit) {
|
|
80
|
+
throw new PulseNodeJavascriptRequestError(
|
|
81
|
+
'PULSE_REQUEST_BODY_TOO_LARGE',
|
|
82
|
+
`Request body exceeds ${limit} bytes.`,
|
|
83
|
+
{ maxBytes: limit, bytes }
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
chunks.push(buffer);
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (error instanceof PulseNodeJavascriptRequestError) throw error;
|
|
90
|
+
throw new PulseNodeJavascriptRequestError(
|
|
91
|
+
'PULSE_NODE_REQUEST_READ_FAILED',
|
|
92
|
+
'Pulse could not read the Node request body.',
|
|
93
|
+
{ causeName: error && error.name }
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
return Buffer.concat(chunks);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function nodeRequestToWebRequest(request, options = {}) {
|
|
100
|
+
if (!request || typeof request !== 'object') throw new TypeError('Pulse Node request adaptation requires an IncomingMessage-like object.');
|
|
101
|
+
const method = String(request.method || 'GET').toUpperCase();
|
|
102
|
+
const headerPairs = nodeRequestHeaderPairs(request);
|
|
103
|
+
const headers = new Headers();
|
|
104
|
+
for (const [name, value] of headerPairs) headers.append(name, value);
|
|
105
|
+
const init = { method, headers };
|
|
106
|
+
if (bodyAllowed(method)) {
|
|
107
|
+
const body = await readNodeRequestBody(request, options.maxRequestBodyBytes ?? options.maxBodyBytes);
|
|
108
|
+
if (body.byteLength > 0) {
|
|
109
|
+
init.body = body;
|
|
110
|
+
init.duplex = 'half';
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return Object.freeze({
|
|
114
|
+
request: new Request(requestUrl(request, options), init),
|
|
115
|
+
headerPairs,
|
|
116
|
+
method
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function groupHeaderPairs(pairs) {
|
|
121
|
+
const order = [];
|
|
122
|
+
const groups = new Map();
|
|
123
|
+
for (const [name, value] of pairs || []) {
|
|
124
|
+
const displayName = String(name);
|
|
125
|
+
const key = displayName.toLowerCase();
|
|
126
|
+
if (!groups.has(key)) {
|
|
127
|
+
groups.set(key, { name: displayName, values: [] });
|
|
128
|
+
order.push(key);
|
|
129
|
+
}
|
|
130
|
+
groups.get(key).values.push(String(value));
|
|
131
|
+
}
|
|
132
|
+
return Object.freeze(order.map((key) => Object.freeze({
|
|
133
|
+
name: groups.get(key).name,
|
|
134
|
+
values: Object.freeze(groups.get(key).values)
|
|
135
|
+
})));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function statusAllowsBody(status) {
|
|
139
|
+
const value = Number(status);
|
|
140
|
+
return value !== 204 && value !== 205 && value !== 304 && !(value >= 100 && value < 200);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function writeWebResponseToNode(response, nodeResponse, options = {}) {
|
|
144
|
+
if (!(response instanceof Response)) throw new TypeError('Pulse Node response adaptation requires a Web Response.');
|
|
145
|
+
if (!nodeResponse || typeof nodeResponse.end !== 'function') throw new TypeError('Pulse Node response adaptation requires a ServerResponse-like object.');
|
|
146
|
+
|
|
147
|
+
nodeResponse.statusCode = response.status;
|
|
148
|
+
if (response.statusText && typeof nodeResponse.statusMessage === 'string') nodeResponse.statusMessage = response.statusText;
|
|
149
|
+
for (const group of groupHeaderPairs(runtimeHost.responseHeaderPairs(response))) {
|
|
150
|
+
nodeResponse.setHeader(group.name, group.values.length === 1 ? group.values[0] : [...group.values]);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const method = String(options.requestMethod || 'GET').toUpperCase();
|
|
154
|
+
if (method === 'HEAD' || !statusAllowsBody(response.status) || response.body == null) {
|
|
155
|
+
nodeResponse.end();
|
|
156
|
+
return response;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (typeof Readable.fromWeb === 'function') {
|
|
160
|
+
await pipeline(Readable.fromWeb(response.body), nodeResponse);
|
|
161
|
+
return response;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
nodeResponse.end(Buffer.from(await response.arrayBuffer()));
|
|
165
|
+
return response;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function resolveRequestOption(value, context) {
|
|
169
|
+
return typeof value === 'function' ? value(context) : value;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function createNodeJavascriptHandler(application, options = {}) {
|
|
173
|
+
const normalizedApplication = options.getApplication ? null : runtimeHost.normalizeApplication(application);
|
|
174
|
+
const hasDynamicBindings = [options.config, options.secrets, options.kv]
|
|
175
|
+
.some((value) => typeof value === 'function');
|
|
176
|
+
const staticBindings = hasDynamicBindings
|
|
177
|
+
? undefined
|
|
178
|
+
: createNodeJavascriptBindingCapabilities({
|
|
179
|
+
config: options.config,
|
|
180
|
+
secrets: options.secrets,
|
|
181
|
+
kv: options.kv,
|
|
182
|
+
maxBindingNameBytes: options.maxBindingNameBytes,
|
|
183
|
+
maxBindingValueBytes: options.maxBindingValueBytes,
|
|
184
|
+
maxKvNamespaceBytes: options.maxKvNamespaceBytes,
|
|
185
|
+
maxKvKeyBytes: options.maxKvKeyBytes,
|
|
186
|
+
maxKvValueBytes: options.maxKvValueBytes,
|
|
187
|
+
maxKvValueDepth: options.maxKvValueDepth,
|
|
188
|
+
maxKvValueEntries: options.maxKvValueEntries
|
|
189
|
+
});
|
|
190
|
+
return async function pulseNodeJavascriptHandler(request, response) {
|
|
191
|
+
const adapted = await nodeRequestToWebRequest(request, options);
|
|
192
|
+
const requestContext = Object.freeze({
|
|
193
|
+
request,
|
|
194
|
+
response,
|
|
195
|
+
webRequest: adapted.request,
|
|
196
|
+
headerPairs: adapted.headerPairs
|
|
197
|
+
});
|
|
198
|
+
const requestedCapabilities = await resolveRequestOption(options.capabilities, requestContext);
|
|
199
|
+
const effectAdapter = await resolveRequestOption(options.effectAdapter, requestContext);
|
|
200
|
+
const schemaCodecs = await resolveRequestOption(options.schemaCodecs, requestContext);
|
|
201
|
+
const config = hasDynamicBindings ? await resolveRequestOption(options.config, requestContext) : options.config;
|
|
202
|
+
const secrets = hasDynamicBindings ? await resolveRequestOption(options.secrets, requestContext) : options.secrets;
|
|
203
|
+
const kv = hasDynamicBindings ? await resolveRequestOption(options.kv, requestContext) : options.kv;
|
|
204
|
+
const bindings = staticBindings || createNodeJavascriptBindingCapabilities({
|
|
205
|
+
config, secrets, kv,
|
|
206
|
+
maxBindingNameBytes: options.maxBindingNameBytes,
|
|
207
|
+
maxBindingValueBytes: options.maxBindingValueBytes,
|
|
208
|
+
maxKvNamespaceBytes: options.maxKvNamespaceBytes,
|
|
209
|
+
maxKvKeyBytes: options.maxKvKeyBytes,
|
|
210
|
+
maxKvValueBytes: options.maxKvValueBytes,
|
|
211
|
+
maxKvValueDepth: options.maxKvValueDepth,
|
|
212
|
+
maxKvValueEntries: options.maxKvValueEntries
|
|
213
|
+
});
|
|
214
|
+
const capabilities = effectAdapter === undefined
|
|
215
|
+
? withNodeBindingCapabilities(requestedCapabilities, { bindings })
|
|
216
|
+
: requestedCapabilities;
|
|
217
|
+
const started = process.hrtime.bigint();
|
|
218
|
+
const activeApplication = options.getApplication
|
|
219
|
+
? runtimeHost.normalizeApplication(options.getApplication())
|
|
220
|
+
: normalizedApplication;
|
|
221
|
+
const webResponse = await executeNodeJavascriptApplication(activeApplication, adapted.request, {
|
|
222
|
+
capabilities,
|
|
223
|
+
effectAdapter,
|
|
224
|
+
config,
|
|
225
|
+
secrets,
|
|
226
|
+
kv,
|
|
227
|
+
application: options.application,
|
|
228
|
+
requestHeaders: adapted.headerPairs,
|
|
229
|
+
signal: options.signal || adapted.request.signal,
|
|
230
|
+
fetchImplementation: options.fetchImplementation,
|
|
231
|
+
assetsLookup: options.assetsLookup,
|
|
232
|
+
gripBroadcast: options.gripBroadcast,
|
|
233
|
+
jwtCaptureWallClock: options.jwtCaptureWallClock,
|
|
234
|
+
assets: options.assets,
|
|
235
|
+
grip: options.grip,
|
|
236
|
+
maxEffects: options.maxEffects,
|
|
237
|
+
maxRequestBodyBytes: options.maxRequestBodyBytes ?? options.maxBodyBytes,
|
|
238
|
+
maxFetchBodyBytes: options.maxFetchBodyBytes,
|
|
239
|
+
maxStructuredBodyBytes: options.maxStructuredBodyBytes,
|
|
240
|
+
maxBindingNameBytes: options.maxBindingNameBytes,
|
|
241
|
+
maxBindingValueBytes: options.maxBindingValueBytes,
|
|
242
|
+
maxKvNamespaceBytes: options.maxKvNamespaceBytes,
|
|
243
|
+
maxKvKeyBytes: options.maxKvKeyBytes,
|
|
244
|
+
maxKvValueBytes: options.maxKvValueBytes,
|
|
245
|
+
maxKvValueDepth: options.maxKvValueDepth,
|
|
246
|
+
maxKvValueEntries: options.maxKvValueEntries,
|
|
247
|
+
schemaCodecs,
|
|
248
|
+
strict: options.strict === true,
|
|
249
|
+
provider: options.provider || 'node',
|
|
250
|
+
redactionValues: options.redactionValues,
|
|
251
|
+
onJsonTrace: options.onJsonTrace,
|
|
252
|
+
onEffectObservation: options.onEffectObservation,
|
|
253
|
+
onEffectSummary: options.onEffectSummary
|
|
254
|
+
});
|
|
255
|
+
await writeWebResponseToNode(webResponse, response, { requestMethod: adapted.method });
|
|
256
|
+
if (typeof options.onRequest === 'function') {
|
|
257
|
+
options.onRequest(Object.freeze({
|
|
258
|
+
method: adapted.method,
|
|
259
|
+
url: adapted.request.url,
|
|
260
|
+
path: new URL(adapted.request.url).pathname,
|
|
261
|
+
status: webResponse.status,
|
|
262
|
+
durationMs: Number(process.hrtime.bigint() - started) / 1e6
|
|
263
|
+
}));
|
|
264
|
+
}
|
|
265
|
+
return webResponse;
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
module.exports = Object.freeze({
|
|
270
|
+
NODE_JAVASCRIPT_REQUEST_ADAPTER_VERSION,
|
|
271
|
+
DEFAULT_MAX_BODY_BYTES,
|
|
272
|
+
PulseNodeJavascriptRequestError,
|
|
273
|
+
createNodeJavascriptHandler,
|
|
274
|
+
groupHeaderPairs,
|
|
275
|
+
nodeRequestHeaderPairs,
|
|
276
|
+
nodeRequestToWebRequest,
|
|
277
|
+
normalizeRawHeaders,
|
|
278
|
+
readNodeRequestBody,
|
|
279
|
+
requestUrl,
|
|
280
|
+
writeWebResponseToNode
|
|
281
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const NODE_JAVASCRIPT_PACKAGE_EFFECTS_VERSION = 'pulse.node-javascript-package-effects.v1';
|
|
4
|
+
|
|
5
|
+
class PulseNodeJavascriptPackageEffectError extends Error {
|
|
6
|
+
constructor(code, message, detail) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'PulseNodeJavascriptPackageEffectError';
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.detail = detail && Object.freeze({ ...detail });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function unavailableGrip() {
|
|
15
|
+
throw new PulseNodeJavascriptPackageEffectError(
|
|
16
|
+
'PULSE_GRIP_BROADCAST_CAPABILITY_REQUIRED',
|
|
17
|
+
'GRIP broadcast requires an explicitly configured provider publish capability.',
|
|
18
|
+
{ package: '@pulse-compute/grip', operation: 'broadcast', configuration: 'grip.publishEndpoint' }
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function unavailable(effect) {
|
|
23
|
+
throw new PulseNodeJavascriptPackageEffectError(
|
|
24
|
+
'PULSE_RUNTIME_CAPABILITY_UNAVAILABLE',
|
|
25
|
+
`Pulse capability ${effect.capability || effect.kind} is unavailable in the Node JavaScript realization.`,
|
|
26
|
+
{ package: effect.package, contractId: effect.contractId, operation: effect.operation }
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function createNodeJavascriptPackageEffectCapabilities(options = {}) {
|
|
31
|
+
const assetsLookup = options.assetsLookup || (options.assets && options.assets.lookup);
|
|
32
|
+
const gripBroadcast = options.gripBroadcast || (options.grip && options.grip.broadcast);
|
|
33
|
+
const jwtVerify = options.jwtVerify || (options.jwt && options.jwt.verify);
|
|
34
|
+
return Object.freeze({
|
|
35
|
+
version: NODE_JAVASCRIPT_PACKAGE_EFFECTS_VERSION,
|
|
36
|
+
async effect(effect, execution) {
|
|
37
|
+
if (effect && effect.contractId === 'pulse.assets' && effect.operation === 'lookup') {
|
|
38
|
+
if (typeof assetsLookup !== 'function') return unavailable(effect);
|
|
39
|
+
return assetsLookup(effect.payload, execution);
|
|
40
|
+
}
|
|
41
|
+
if (effect && effect.contractId === 'pulse.grip' && effect.operation === 'broadcast') {
|
|
42
|
+
if (typeof gripBroadcast !== 'function') return unavailableGrip();
|
|
43
|
+
return gripBroadcast(effect.payload, execution);
|
|
44
|
+
}
|
|
45
|
+
if (effect && effect.contractId === 'pulse.jwt' && effect.operation === 'verify') {
|
|
46
|
+
if (typeof jwtVerify !== 'function') return unavailable(effect);
|
|
47
|
+
return jwtVerify(effect, execution);
|
|
48
|
+
}
|
|
49
|
+
return unavailable(effect || {});
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function withNodePackageEffectCapabilities(capabilities, options = {}) {
|
|
55
|
+
const base = capabilities && typeof capabilities === 'object' ? capabilities : {};
|
|
56
|
+
const packageCapabilities = createNodeJavascriptPackageEffectCapabilities(options);
|
|
57
|
+
return Object.freeze({
|
|
58
|
+
...base,
|
|
59
|
+
effect(effect, execution) {
|
|
60
|
+
if (effect && (
|
|
61
|
+
effect.contractId === 'pulse.assets'
|
|
62
|
+
|| effect.contractId === 'pulse.grip'
|
|
63
|
+
|| effect.contractId === 'pulse.jwt'
|
|
64
|
+
)) {
|
|
65
|
+
return packageCapabilities.effect(effect, execution);
|
|
66
|
+
}
|
|
67
|
+
if (typeof base.effect === 'function') return base.effect(effect, execution);
|
|
68
|
+
return unavailable(effect || {});
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = Object.freeze({
|
|
74
|
+
NODE_JAVASCRIPT_PACKAGE_EFFECTS_VERSION,
|
|
75
|
+
PulseNodeJavascriptPackageEffectError,
|
|
76
|
+
createNodeJavascriptPackageEffectCapabilities,
|
|
77
|
+
withNodePackageEffectCapabilities
|
|
78
|
+
});
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const runtimeHost = require('@pulse-compute/runtime/host');
|
|
4
|
+
const {
|
|
5
|
+
createNodeJavascriptBindingCapabilities,
|
|
6
|
+
nodeJavascriptRedactionValues,
|
|
7
|
+
withNodeBindingCapabilities
|
|
8
|
+
} = require('./bindings-adapter.js');
|
|
9
|
+
const { createNodeJavascriptFixtureFetch, withNodeFetchCapability } = require('./fetch-adapter.js');
|
|
10
|
+
const { createNodeJavascriptAssetsLookup } = require('./assets-adapter.js');
|
|
11
|
+
const { createNodeJavascriptGripBroadcast } = require('./grip-broadcast.js');
|
|
12
|
+
const { createNodeJavascriptJwtVerify } = require('./jwt-verifier.js');
|
|
13
|
+
const { createNodeJavascriptPackageEffectCapabilities, withNodePackageEffectCapabilities } = require('./package-effects.js');
|
|
14
|
+
const { NODE_JAVASCRIPT_TARGET_DESCRIPTOR } = require('./target.js');
|
|
15
|
+
|
|
16
|
+
const NODE_JAVASCRIPT_RUNTIME_HOST_VERSION = 'pulse.node-javascript-runtime-host.v2';
|
|
17
|
+
|
|
18
|
+
function isNodeJavascriptApplication(value) {
|
|
19
|
+
return runtimeHost.isRouterApplication(value) || typeof value === 'function';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function assertNodeJavascriptApplication(value) {
|
|
23
|
+
return runtimeHost.normalizeApplication(value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function nodeJavascriptProviderCapabilities(options) {
|
|
27
|
+
if (options.effectAdapter !== undefined) {
|
|
28
|
+
if (options.eventAdapter !== undefined) {
|
|
29
|
+
throw new TypeError('Pulse Node JavaScript execution cannot combine effectAdapter and eventAdapter.');
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
const bindingOptions = {
|
|
34
|
+
...(Object.prototype.hasOwnProperty.call(options, 'config') ? { config: options.config } : {}),
|
|
35
|
+
...(Object.prototype.hasOwnProperty.call(options, 'secrets') ? { secrets: options.secrets } : {}),
|
|
36
|
+
...(Object.prototype.hasOwnProperty.call(options, 'kv') ? { kv: options.kv } : {}),
|
|
37
|
+
maxBindingNameBytes: options.maxBindingNameBytes,
|
|
38
|
+
maxBindingValueBytes: options.maxBindingValueBytes,
|
|
39
|
+
maxKvNamespaceBytes: options.maxKvNamespaceBytes,
|
|
40
|
+
maxKvKeyBytes: options.maxKvKeyBytes,
|
|
41
|
+
maxKvValueBytes: options.maxKvValueBytes,
|
|
42
|
+
maxKvValueDepth: options.maxKvValueDepth,
|
|
43
|
+
maxKvValueEntries: options.maxKvValueEntries
|
|
44
|
+
};
|
|
45
|
+
const bindingCapabilities = withNodeBindingCapabilities(options.capabilities, bindingOptions);
|
|
46
|
+
const configuredGripBroadcast = options.gripBroadcast
|
|
47
|
+
|| (options.grip && options.grip.broadcast)
|
|
48
|
+
|| (options.grip && options.grip.publishEndpoint
|
|
49
|
+
? createNodeJavascriptGripBroadcast({
|
|
50
|
+
...options.grip,
|
|
51
|
+
fetchImplementation: options.gripFetchImplementation || options.fetchImplementation,
|
|
52
|
+
secretLookup: bindingCapabilities && bindingCapabilities.secret
|
|
53
|
+
})
|
|
54
|
+
: undefined);
|
|
55
|
+
const configuredAssetsLookup = options.assetsLookup
|
|
56
|
+
|| (bindingCapabilities && createNodeJavascriptAssetsLookup(bindingCapabilities));
|
|
57
|
+
const configuredJwtVerify = bindingCapabilities
|
|
58
|
+
&& createNodeJavascriptJwtVerify({
|
|
59
|
+
captureWallClock: options.jwtCaptureWallClock,
|
|
60
|
+
secretLookup: bindingCapabilities.secret
|
|
61
|
+
});
|
|
62
|
+
const capabilities = withNodePackageEffectCapabilities(
|
|
63
|
+
withNodeFetchCapability(bindingCapabilities, { fetchImplementation: options.fetchImplementation }),
|
|
64
|
+
{
|
|
65
|
+
assetsLookup: configuredAssetsLookup,
|
|
66
|
+
gripBroadcast: configuredGripBroadcast,
|
|
67
|
+
jwtVerify: configuredJwtVerify,
|
|
68
|
+
assets: options.assets,
|
|
69
|
+
grip: options.grip
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
if (options.eventAdapter === undefined) return capabilities;
|
|
73
|
+
if (!options.eventAdapter || typeof options.eventAdapter.acceptOutbound !== 'function') {
|
|
74
|
+
throw new TypeError('Pulse Node JavaScript eventAdapter must expose acceptOutbound(frame, execution).');
|
|
75
|
+
}
|
|
76
|
+
return Object.freeze({
|
|
77
|
+
...capabilities,
|
|
78
|
+
emit(frame, execution) { return options.eventAdapter.acceptOutbound(frame, execution); }
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function executeNodeJavascriptApplication(application, request, options = {}) {
|
|
83
|
+
const capabilities = nodeJavascriptProviderCapabilities(options);
|
|
84
|
+
return runtimeHost.executeApplication(assertNodeJavascriptApplication(application), request, {
|
|
85
|
+
capabilities,
|
|
86
|
+
effectAdapter: options.effectAdapter,
|
|
87
|
+
application: options.application,
|
|
88
|
+
requestHeaders: options.requestHeaders,
|
|
89
|
+
signal: options.signal,
|
|
90
|
+
maxEffects: options.maxEffects,
|
|
91
|
+
maxRequestBodyBytes: options.maxRequestBodyBytes,
|
|
92
|
+
maxFetchBodyBytes: options.maxFetchBodyBytes,
|
|
93
|
+
maxStructuredBodyBytes: options.maxStructuredBodyBytes,
|
|
94
|
+
maxBindingNameBytes: options.maxBindingNameBytes,
|
|
95
|
+
maxBindingValueBytes: options.maxBindingValueBytes,
|
|
96
|
+
maxKvNamespaceBytes: options.maxKvNamespaceBytes,
|
|
97
|
+
maxKvKeyBytes: options.maxKvKeyBytes,
|
|
98
|
+
maxKvValueBytes: options.maxKvValueBytes,
|
|
99
|
+
maxKvValueDepth: options.maxKvValueDepth,
|
|
100
|
+
maxKvValueEntries: options.maxKvValueEntries,
|
|
101
|
+
schemaCodecs: options.schemaCodecs,
|
|
102
|
+
strict: options.strict === true,
|
|
103
|
+
target: 'javascript',
|
|
104
|
+
provider: options.provider || 'node',
|
|
105
|
+
reporting: options.reporting,
|
|
106
|
+
log: options.log,
|
|
107
|
+
redactionValues: nodeJavascriptRedactionValues(options),
|
|
108
|
+
onLogObservation: options.onLogObservation,
|
|
109
|
+
onJsonTrace: options.onJsonTrace,
|
|
110
|
+
onEffectObservation: options.onEffectObservation,
|
|
111
|
+
onEffectSummary: options.onEffectSummary
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function executeNodeJavascriptEvent(application, frame, options = {}) {
|
|
116
|
+
return runtimeHost.executeEvent(assertNodeJavascriptApplication(application), frame, {
|
|
117
|
+
capabilities: nodeJavascriptProviderCapabilities(options),
|
|
118
|
+
effectAdapter: options.effectAdapter,
|
|
119
|
+
application: options.application,
|
|
120
|
+
signal: options.signal,
|
|
121
|
+
maxEffects: options.maxEffects,
|
|
122
|
+
maxBindingNameBytes: options.maxBindingNameBytes,
|
|
123
|
+
maxBindingValueBytes: options.maxBindingValueBytes,
|
|
124
|
+
maxKvNamespaceBytes: options.maxKvNamespaceBytes,
|
|
125
|
+
maxKvKeyBytes: options.maxKvKeyBytes,
|
|
126
|
+
maxKvValueBytes: options.maxKvValueBytes,
|
|
127
|
+
maxKvValueDepth: options.maxKvValueDepth,
|
|
128
|
+
maxKvValueEntries: options.maxKvValueEntries,
|
|
129
|
+
eventLimits: options.eventLimits,
|
|
130
|
+
schemaCodecs: options.schemaCodecs,
|
|
131
|
+
strict: true,
|
|
132
|
+
target: 'javascript',
|
|
133
|
+
provider: options.provider || 'node',
|
|
134
|
+
reporting: options.reporting,
|
|
135
|
+
redactionValues: nodeJavascriptRedactionValues(options),
|
|
136
|
+
onLogObservation: options.onLogObservation,
|
|
137
|
+
onEffectObservation: options.onEffectObservation,
|
|
138
|
+
onEffectSummary: options.onEffectSummary
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = Object.freeze({
|
|
143
|
+
NODE_JAVASCRIPT_RUNTIME_HOST_VERSION,
|
|
144
|
+
NODE_JAVASCRIPT_TARGET_DESCRIPTOR,
|
|
145
|
+
PulseRuntimeContractError: runtimeHost.PulseRuntimeContractError,
|
|
146
|
+
PulseUnhandledError: runtimeHost.PulseUnhandledError,
|
|
147
|
+
JAVASCRIPT_EFFECT_PROTOCOL_VERSION: runtimeHost.JAVASCRIPT_EFFECT_PROTOCOL_VERSION,
|
|
148
|
+
JAVASCRIPT_EFFECT_ADAPTER_VERSION: runtimeHost.JAVASCRIPT_EFFECT_ADAPTER_VERSION,
|
|
149
|
+
JAVASCRIPT_EFFECT_OBSERVATION_VERSION: runtimeHost.JAVASCRIPT_EFFECT_OBSERVATION_VERSION,
|
|
150
|
+
createJavascriptEffectAdapter: runtimeHost.createJavascriptEffectAdapter,
|
|
151
|
+
createNodeJavascriptAssetsLookup,
|
|
152
|
+
createNodeJavascriptBindingCapabilities,
|
|
153
|
+
createNodeJavascriptFixtureFetch,
|
|
154
|
+
createNodeJavascriptGripBroadcast,
|
|
155
|
+
createNodeJavascriptJwtVerify,
|
|
156
|
+
createNodeJavascriptPackageEffectCapabilities,
|
|
157
|
+
createCapabilityEffectAdapter: runtimeHost.createCapabilityEffectAdapter,
|
|
158
|
+
isPulseJavascriptEffect: runtimeHost.isPulseJavascriptEffect,
|
|
159
|
+
assertNodeJavascriptApplication,
|
|
160
|
+
createOpaqueFetchResponse: runtimeHost.createOpaqueFetchResponse,
|
|
161
|
+
executeNodeJavascriptApplication,
|
|
162
|
+
executeNodeJavascriptEvent,
|
|
163
|
+
isNodeJavascriptApplication,
|
|
164
|
+
wrapStructuredResponse: runtimeHost.wrapStructuredResponse
|
|
165
|
+
});
|