@pulse-compute/provider-node 0.0.0 → 1.0.0-beta.2

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.
Files changed (42) hide show
  1. package/README.md +61 -1
  2. package/package.json +62 -6
  3. package/src/capabilities.js +65 -0
  4. package/src/compiler/assets-provider-proof.js +434 -0
  5. package/src/compiler/backend-json-request-body-proof.js +90 -0
  6. package/src/compiler/index.js +38 -0
  7. package/src/compiler/node-adapter.js +452 -0
  8. package/src/compiler/node-compiled-wasm-assets-lifecycle-proof.js +435 -0
  9. package/src/compiler/request-json-body-proof.js +181 -0
  10. package/src/compiler/route-handler-compiled-wasm-bridge.js +323 -0
  11. package/src/compiler/route-handler-effect-proof.js +215 -0
  12. package/src/compiler/route-handler-live-origin-proof.js +372 -0
  13. package/src/compiler/schema-decode-result-proof.js +180 -0
  14. package/src/compiler/schema-response-codec-proof.js +149 -0
  15. package/src/config/s3.js +20 -0
  16. package/src/events/reference-adapter.js +145 -0
  17. package/src/index.js +57 -0
  18. package/src/javascript/assets-adapter.js +68 -0
  19. package/src/javascript/bindings-adapter.js +90 -0
  20. package/src/javascript/fetch-adapter.js +187 -0
  21. package/src/javascript/grip-broadcast.js +243 -0
  22. package/src/javascript/jwt-verifier.js +137 -0
  23. package/src/javascript/lifecycle.js +69 -0
  24. package/src/javascript/node-adapter.js +284 -0
  25. package/src/javascript/package-effects.js +84 -0
  26. package/src/javascript/runtime-host.js +170 -0
  27. package/src/javascript/s3.js +21 -0
  28. package/src/javascript/source-package.js +439 -0
  29. package/src/javascript/support.js +205 -0
  30. package/src/javascript/target-support-policy.js +136 -0
  31. package/src/javascript/target.js +37 -0
  32. package/src/javascript/test-runtime.js +112 -0
  33. package/src/native/target.js +78 -0
  34. package/src/runtime/assets-provider.js +776 -0
  35. package/src/runtime/canonical-api-runtime.js +604 -0
  36. package/src/runtime/compiled-wasm-node-adapter-kv.js +15 -0
  37. package/src/runtime/conditional-kv.js +93 -0
  38. package/src/runtime/index.js +18 -0
  39. package/src/runtime/jwt-verifier.js +264 -0
  40. package/src/runtime/route-handler-effects.js +757 -0
  41. package/src/runtime/s3-reader.js +131 -0
  42. package/src/toolchain.js +269 -0
@@ -0,0 +1,284 @@
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
+ kvReference: options.kvReference,
183
+ maxBindingNameBytes: options.maxBindingNameBytes,
184
+ maxBindingValueBytes: options.maxBindingValueBytes,
185
+ maxKvNamespaceBytes: options.maxKvNamespaceBytes,
186
+ maxKvKeyBytes: options.maxKvKeyBytes,
187
+ maxKvValueBytes: options.maxKvValueBytes,
188
+ maxKvValueDepth: options.maxKvValueDepth,
189
+ maxKvValueEntries: options.maxKvValueEntries
190
+ });
191
+ return async function pulseNodeJavascriptHandler(request, response) {
192
+ const adapted = await nodeRequestToWebRequest(request, options);
193
+ const requestContext = Object.freeze({
194
+ request,
195
+ response,
196
+ webRequest: adapted.request,
197
+ headerPairs: adapted.headerPairs
198
+ });
199
+ const requestedCapabilities = await resolveRequestOption(options.capabilities, requestContext);
200
+ const effectAdapter = await resolveRequestOption(options.effectAdapter, requestContext);
201
+ const schemaCodecs = await resolveRequestOption(options.schemaCodecs, requestContext);
202
+ const config = hasDynamicBindings ? await resolveRequestOption(options.config, requestContext) : options.config;
203
+ const secrets = hasDynamicBindings ? await resolveRequestOption(options.secrets, requestContext) : options.secrets;
204
+ const kv = hasDynamicBindings ? await resolveRequestOption(options.kv, requestContext) : options.kv;
205
+ const bindings = staticBindings || createNodeJavascriptBindingCapabilities({
206
+ config, secrets, kv,
207
+ kvReference: options.kvReference,
208
+ maxBindingNameBytes: options.maxBindingNameBytes,
209
+ maxBindingValueBytes: options.maxBindingValueBytes,
210
+ maxKvNamespaceBytes: options.maxKvNamespaceBytes,
211
+ maxKvKeyBytes: options.maxKvKeyBytes,
212
+ maxKvValueBytes: options.maxKvValueBytes,
213
+ maxKvValueDepth: options.maxKvValueDepth,
214
+ maxKvValueEntries: options.maxKvValueEntries
215
+ });
216
+ const capabilities = effectAdapter === undefined
217
+ ? withNodeBindingCapabilities(requestedCapabilities, { bindings })
218
+ : requestedCapabilities;
219
+ const started = process.hrtime.bigint();
220
+ const activeApplication = options.getApplication
221
+ ? runtimeHost.normalizeApplication(options.getApplication())
222
+ : normalizedApplication;
223
+ const webResponse = await executeNodeJavascriptApplication(activeApplication, adapted.request, {
224
+ capabilities,
225
+ effectAdapter,
226
+ config,
227
+ secrets,
228
+ kv,
229
+ application: options.application,
230
+ requestHeaders: adapted.headerPairs,
231
+ signal: options.signal || adapted.request.signal,
232
+ fetchImplementation: options.fetchImplementation,
233
+ assetsLookup: options.assetsLookup,
234
+ gripBroadcast: options.gripBroadcast,
235
+ jwtCaptureWallClock: options.jwtCaptureWallClock,
236
+ assets: options.assets,
237
+ grip: options.grip,
238
+ maxEffects: options.maxEffects,
239
+ kvClock: options.kvClock, deadlineMonotonicMs: options.deadlineMonotonicMs,
240
+ maxRequestBodyBytes: options.maxRequestBodyBytes ?? options.maxBodyBytes,
241
+ maxFetchBodyBytes: options.maxFetchBodyBytes,
242
+ maxStructuredBodyBytes: options.maxStructuredBodyBytes,
243
+ maxBindingNameBytes: options.maxBindingNameBytes,
244
+ maxBindingValueBytes: options.maxBindingValueBytes,
245
+ maxKvNamespaceBytes: options.maxKvNamespaceBytes,
246
+ maxKvKeyBytes: options.maxKvKeyBytes,
247
+ maxKvValueBytes: options.maxKvValueBytes,
248
+ maxKvValueDepth: options.maxKvValueDepth,
249
+ maxKvValueEntries: options.maxKvValueEntries,
250
+ schemaCodecs,
251
+ strict: options.strict === true,
252
+ provider: options.provider || 'node',
253
+ redactionValues: options.redactionValues,
254
+ onJsonTrace: options.onJsonTrace,
255
+ onEffectObservation: options.onEffectObservation,
256
+ onEffectSummary: options.onEffectSummary
257
+ });
258
+ await writeWebResponseToNode(webResponse, response, { requestMethod: adapted.method });
259
+ if (typeof options.onRequest === 'function') {
260
+ options.onRequest(Object.freeze({
261
+ method: adapted.method,
262
+ url: adapted.request.url,
263
+ path: new URL(adapted.request.url).pathname,
264
+ status: webResponse.status,
265
+ durationMs: Number(process.hrtime.bigint() - started) / 1e6
266
+ }));
267
+ }
268
+ return webResponse;
269
+ };
270
+ }
271
+
272
+ module.exports = Object.freeze({
273
+ NODE_JAVASCRIPT_REQUEST_ADAPTER_VERSION,
274
+ DEFAULT_MAX_BODY_BYTES,
275
+ PulseNodeJavascriptRequestError,
276
+ createNodeJavascriptHandler,
277
+ groupHeaderPairs,
278
+ nodeRequestHeaderPairs,
279
+ nodeRequestToWebRequest,
280
+ normalizeRawHeaders,
281
+ readNodeRequestBody,
282
+ requestUrl,
283
+ writeWebResponseToNode
284
+ });
@@ -0,0 +1,84 @@
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
+ const s3 = options.s3;
35
+ return Object.freeze({
36
+ version: NODE_JAVASCRIPT_PACKAGE_EFFECTS_VERSION,
37
+ async effect(effect, execution) {
38
+ if (effect && effect.contractId === 'pulse.s3') {
39
+ if (typeof s3 !== 'function') return unavailable(effect);
40
+ return s3(effect, execution);
41
+ }
42
+ if (effect && effect.contractId === 'pulse.assets' && effect.operation === 'lookup') {
43
+ if (typeof assetsLookup !== 'function') return unavailable(effect);
44
+ return assetsLookup(effect.payload, execution);
45
+ }
46
+ if (effect && effect.contractId === 'pulse.grip' && effect.operation === 'broadcast') {
47
+ if (typeof gripBroadcast !== 'function') return unavailableGrip();
48
+ return gripBroadcast(effect.payload, execution);
49
+ }
50
+ if (effect && effect.contractId === 'pulse.jwt' && effect.operation === 'verify') {
51
+ if (typeof jwtVerify !== 'function') return unavailable(effect);
52
+ return jwtVerify(effect, execution);
53
+ }
54
+ return unavailable(effect || {});
55
+ }
56
+ });
57
+ }
58
+
59
+ function withNodePackageEffectCapabilities(capabilities, options = {}) {
60
+ const base = capabilities && typeof capabilities === 'object' ? capabilities : {};
61
+ const packageCapabilities = createNodeJavascriptPackageEffectCapabilities(options);
62
+ return Object.freeze({
63
+ ...base,
64
+ effect(effect, execution) {
65
+ if (effect && (
66
+ effect.contractId === 'pulse.assets'
67
+ || effect.contractId === 'pulse.grip'
68
+ || effect.contractId === 'pulse.jwt'
69
+ || effect.contractId === 'pulse.s3'
70
+ )) {
71
+ return packageCapabilities.effect(effect, execution);
72
+ }
73
+ if (typeof base.effect === 'function') return base.effect(effect, execution);
74
+ return unavailable(effect || {});
75
+ }
76
+ });
77
+ }
78
+
79
+ module.exports = Object.freeze({
80
+ NODE_JAVASCRIPT_PACKAGE_EFFECTS_VERSION,
81
+ PulseNodeJavascriptPackageEffectError,
82
+ createNodeJavascriptPackageEffectCapabilities,
83
+ withNodePackageEffectCapabilities
84
+ });
@@ -0,0 +1,170 @@
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 { createNodeJavascriptS3 } = require('./s3.js');
14
+ const { createNodeJavascriptPackageEffectCapabilities, withNodePackageEffectCapabilities } = require('./package-effects.js');
15
+ const { NODE_JAVASCRIPT_TARGET_DESCRIPTOR } = require('./target.js');
16
+
17
+ const NODE_JAVASCRIPT_RUNTIME_HOST_VERSION = 'pulse.node-javascript-runtime-host.v2';
18
+
19
+ function isNodeJavascriptApplication(value) {
20
+ return runtimeHost.isRouterApplication(value) || typeof value === 'function';
21
+ }
22
+
23
+ function assertNodeJavascriptApplication(value) {
24
+ return runtimeHost.normalizeApplication(value);
25
+ }
26
+
27
+ function nodeJavascriptProviderCapabilities(options) {
28
+ if (options.effectAdapter !== undefined) {
29
+ if (options.eventAdapter !== undefined) {
30
+ throw new TypeError('Pulse Node JavaScript execution cannot combine effectAdapter and eventAdapter.');
31
+ }
32
+ return undefined;
33
+ }
34
+ const bindingOptions = {
35
+ ...(Object.prototype.hasOwnProperty.call(options, 'config') ? { config: options.config } : {}),
36
+ ...(Object.prototype.hasOwnProperty.call(options, 'secrets') ? { secrets: options.secrets } : {}),
37
+ ...(Object.prototype.hasOwnProperty.call(options, 'kv') ? { kv: options.kv } : {}),
38
+ kvReference: options.kvReference, kvInstanceId: options.kvInstanceId, kvGenerationStart: options.kvGenerationStart, kvHooks: options.kvHooks,
39
+ maxBindingNameBytes: options.maxBindingNameBytes,
40
+ maxBindingValueBytes: options.maxBindingValueBytes,
41
+ maxKvNamespaceBytes: options.maxKvNamespaceBytes,
42
+ maxKvKeyBytes: options.maxKvKeyBytes,
43
+ maxKvValueBytes: options.maxKvValueBytes,
44
+ maxKvValueDepth: options.maxKvValueDepth,
45
+ maxKvValueEntries: options.maxKvValueEntries
46
+ };
47
+ const bindingCapabilities = withNodeBindingCapabilities(options.capabilities, bindingOptions);
48
+ const configuredGripBroadcast = options.gripBroadcast
49
+ || (options.grip && options.grip.broadcast)
50
+ || (options.grip && options.grip.publishEndpoint
51
+ ? createNodeJavascriptGripBroadcast({
52
+ ...options.grip,
53
+ fetchImplementation: options.gripFetchImplementation || options.fetchImplementation,
54
+ secretLookup: bindingCapabilities && bindingCapabilities.secret
55
+ })
56
+ : undefined);
57
+ const configuredAssetsLookup = options.assetsLookup
58
+ || (bindingCapabilities && createNodeJavascriptAssetsLookup(bindingCapabilities));
59
+ const configuredJwtVerify = bindingCapabilities
60
+ && createNodeJavascriptJwtVerify({
61
+ captureWallClock: options.jwtCaptureWallClock,
62
+ secretLookup: bindingCapabilities.secret
63
+ });
64
+ const capabilities = withNodePackageEffectCapabilities(
65
+ withNodeFetchCapability(bindingCapabilities, { fetchImplementation: options.fetchImplementation }),
66
+ {
67
+ assetsLookup: configuredAssetsLookup,
68
+ gripBroadcast: configuredGripBroadcast,
69
+ jwtVerify: configuredJwtVerify,
70
+ s3: createNodeJavascriptS3(options, bindingCapabilities.secret),
71
+ assets: options.assets,
72
+ grip: options.grip
73
+ }
74
+ );
75
+ if (options.eventAdapter === undefined) return capabilities;
76
+ if (!options.eventAdapter || typeof options.eventAdapter.acceptOutbound !== 'function') {
77
+ throw new TypeError('Pulse Node JavaScript eventAdapter must expose acceptOutbound(frame, execution).');
78
+ }
79
+ return Object.freeze({
80
+ ...capabilities,
81
+ emit(frame, execution) { return options.eventAdapter.acceptOutbound(frame, execution); }
82
+ });
83
+ }
84
+
85
+ async function executeNodeJavascriptApplication(application, request, options = {}) {
86
+ const capabilities = nodeJavascriptProviderCapabilities(options);
87
+ return runtimeHost.executeApplication(assertNodeJavascriptApplication(application), request, {
88
+ capabilities,
89
+ effectAdapter: options.effectAdapter,
90
+ application: options.application,
91
+ requestHeaders: options.requestHeaders,
92
+ signal: options.signal,
93
+ kvClock: options.kvClock, deadlineMonotonicMs: options.deadlineMonotonicMs,
94
+ maxEffects: options.maxEffects,
95
+ maxRequestBodyBytes: options.maxRequestBodyBytes,
96
+ maxFetchBodyBytes: options.maxFetchBodyBytes,
97
+ maxStructuredBodyBytes: options.maxStructuredBodyBytes,
98
+ maxBindingNameBytes: options.maxBindingNameBytes,
99
+ maxBindingValueBytes: options.maxBindingValueBytes,
100
+ maxKvNamespaceBytes: options.maxKvNamespaceBytes,
101
+ maxKvKeyBytes: options.maxKvKeyBytes,
102
+ maxKvValueBytes: options.maxKvValueBytes,
103
+ maxKvValueDepth: options.maxKvValueDepth,
104
+ maxKvValueEntries: options.maxKvValueEntries,
105
+ schemaCodecs: options.schemaCodecs,
106
+ strict: options.strict === true,
107
+ target: 'javascript',
108
+ provider: options.provider || 'node',
109
+ reporting: options.reporting,
110
+ log: options.log,
111
+ redactionValues: nodeJavascriptRedactionValues(options),
112
+ onLogObservation: options.onLogObservation,
113
+ onJsonTrace: options.onJsonTrace,
114
+ onEffectObservation: options.onEffectObservation,
115
+ onEffectSummary: options.onEffectSummary
116
+ });
117
+ }
118
+
119
+ async function executeNodeJavascriptEvent(application, frame, options = {}) {
120
+ return runtimeHost.executeEvent(assertNodeJavascriptApplication(application), frame, {
121
+ capabilities: nodeJavascriptProviderCapabilities(options),
122
+ effectAdapter: options.effectAdapter,
123
+ application: options.application,
124
+ signal: options.signal,
125
+ kvClock: options.kvClock, deadlineMonotonicMs: options.deadlineMonotonicMs,
126
+ maxEffects: options.maxEffects,
127
+ maxBindingNameBytes: options.maxBindingNameBytes,
128
+ maxBindingValueBytes: options.maxBindingValueBytes,
129
+ maxKvNamespaceBytes: options.maxKvNamespaceBytes,
130
+ maxKvKeyBytes: options.maxKvKeyBytes,
131
+ maxKvValueBytes: options.maxKvValueBytes,
132
+ maxKvValueDepth: options.maxKvValueDepth,
133
+ maxKvValueEntries: options.maxKvValueEntries,
134
+ eventLimits: options.eventLimits,
135
+ schemaCodecs: options.schemaCodecs,
136
+ strict: true,
137
+ target: 'javascript',
138
+ provider: options.provider || 'node',
139
+ reporting: options.reporting,
140
+ redactionValues: nodeJavascriptRedactionValues(options),
141
+ onLogObservation: options.onLogObservation,
142
+ onEffectObservation: options.onEffectObservation,
143
+ onEffectSummary: options.onEffectSummary
144
+ });
145
+ }
146
+
147
+ module.exports = Object.freeze({
148
+ NODE_JAVASCRIPT_RUNTIME_HOST_VERSION,
149
+ NODE_JAVASCRIPT_TARGET_DESCRIPTOR,
150
+ PulseRuntimeContractError: runtimeHost.PulseRuntimeContractError,
151
+ PulseUnhandledError: runtimeHost.PulseUnhandledError,
152
+ JAVASCRIPT_EFFECT_PROTOCOL_VERSION: runtimeHost.JAVASCRIPT_EFFECT_PROTOCOL_VERSION,
153
+ JAVASCRIPT_EFFECT_ADAPTER_VERSION: runtimeHost.JAVASCRIPT_EFFECT_ADAPTER_VERSION,
154
+ JAVASCRIPT_EFFECT_OBSERVATION_VERSION: runtimeHost.JAVASCRIPT_EFFECT_OBSERVATION_VERSION,
155
+ createJavascriptEffectAdapter: runtimeHost.createJavascriptEffectAdapter,
156
+ createNodeJavascriptAssetsLookup,
157
+ createNodeJavascriptBindingCapabilities,
158
+ createNodeJavascriptFixtureFetch,
159
+ createNodeJavascriptGripBroadcast,
160
+ createNodeJavascriptJwtVerify,
161
+ createNodeJavascriptPackageEffectCapabilities,
162
+ createCapabilityEffectAdapter: runtimeHost.createCapabilityEffectAdapter,
163
+ isPulseJavascriptEffect: runtimeHost.isPulseJavascriptEffect,
164
+ assertNodeJavascriptApplication,
165
+ createOpaqueFetchResponse: runtimeHost.createOpaqueFetchResponse,
166
+ executeNodeJavascriptApplication,
167
+ executeNodeJavascriptEvent,
168
+ isNodeJavascriptApplication,
169
+ wrapStructuredResponse: runtimeHost.wrapStructuredResponse
170
+ });
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+ const { bindJavascriptDigestMac } = require('@pulse-compute/crypto/provider');
3
+ const { normalizeNodeS3 } = require('../config/s3.js');
4
+ const { readS3 } = require('../runtime/s3-reader.js');
5
+
6
+ function createNodeJavascriptS3(options, secretLookup) {
7
+ const s3 = normalizeNodeS3(options.s3 || options.bindings && options.bindings.s3);
8
+ return (effect, execution = {}) => {
9
+ // This provider target explicitly selects Crypto's Web Crypto realization.
10
+ // Native execution supplies its own Wasm exports to the same Node transport.
11
+ let selected;
12
+ try { selected = bindJavascriptDigestMac(['SHA-256', 'HMAC-SHA256']); }
13
+ catch { selected = null; }
14
+ return readS3(effect, {
15
+ s3, signal: execution.signal, registerRedactionValue: execution.registerRedactionValue,
16
+ fetchImplementation: options.fetchImplementation, cryptoTarget: 'javascript',
17
+ cryptoVerifier: selected, cryptoRealization: selected && selected.realization
18
+ }, (name) => secretLookup(name, execution));
19
+ };
20
+ }
21
+ module.exports = { createNodeJavascriptS3 };