@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,131 @@
1
+ 'use strict';
2
+ const https = require('node:https');
3
+ const { Readable } = require('node:stream');
4
+ const { performance } = require('node:perf_hooks');
5
+ const protocol = require('@pulse-compute/s3/provider');
6
+
7
+ function originRequest(request, signal) {
8
+ return new Promise((resolve, reject) => {
9
+ const req = https.request(request.url, { method: request.method, headers: request.headers, signal, maxHeaderSize: 16384 }, (response) => {
10
+ let headers = [];
11
+ try {
12
+ const decoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
13
+ // Node exposes HTTP header octets as Latin-1 strings. Interpret the
14
+ // bounded protocol metadata as UTF-8, matching the Native host ABI.
15
+ for (let i = 0; i < response.rawHeaders.length; i += 2) headers.push([response.rawHeaders[i], decoder.decode(Buffer.from(response.rawHeaders[i + 1], 'latin1'))]);
16
+ } catch { headers = null; }
17
+ resolve({ status: response.statusCode, headers, body: response, close: () => response.destroy() });
18
+ });
19
+ req.once('error', reject);
20
+ req.end(request.body);
21
+ });
22
+ }
23
+ async function fetchFixture(fetch, request, signal) {
24
+ const pending = Promise.resolve().then(() => fetch(request.url, { method: request.method, headers: request.headers, ...(request.body === undefined ? {} : { body: request.body }), redirect: 'manual', cache: 'no-store', signal }));
25
+ pending.then((response) => { if (signal.aborted && response.body) void response.body.cancel().catch(() => {}); }, () => {});
26
+ const response = await abortable(pending, signal);
27
+ const body = response.body ? Readable.fromWeb(response.body, { signal }) : null;
28
+ // A deadline can fire before iteration begins; always observe the stream error.
29
+ if (body) body.on('error', () => {});
30
+ return { status: response.status, headers: [...response.headers], body, close: () => { if (body) body.destroy(); } };
31
+ }
32
+ function abortable(promise, signal) {
33
+ return new Promise((resolve, reject) => {
34
+ const abort = () => reject(signal.reason);
35
+ signal.addEventListener('abort', abort, { once: true });
36
+ promise.then(resolve, reject).finally(() => signal.removeEventListener('abort', abort));
37
+ if (signal.aborted) abort();
38
+ });
39
+ }
40
+ async function readS3(effect, options, lookup) {
41
+ const operation = effect.operation, put = operation === 'putText';
42
+ const payload = effect.payload || {};
43
+ if (!['head', 'getText', 'putText'].includes(operation) || effect.kind !== `s3.${operation}` || effect.capability !== effect.kind
44
+ || effect.package !== '@pulse-compute/s3' || effect.contractId !== 'pulse.s3' || effect.providerKind !== 's3') throw new TypeError('Invalid S3 package effect authority.');
45
+ let dispatched = false, status;
46
+ const failure = (reason) => put ? protocol.putFailure(reason, dispatched, status) : protocol.failure(reason, status);
47
+ if (options.signal && options.signal.aborted) throw options.signal.reason || new Error('Request cancelled.');
48
+ const configured = typeof payload.binding === 'string' && options.s3 && Object.hasOwn(options.s3, payload.binding) && options.s3[payload.binding];
49
+ let binding;
50
+ try { binding = protocol.normalizeBinding(configured); } catch { return failure('configuration'); }
51
+ const controller = new AbortController();
52
+ const onAbort = () => controller.abort(options.signal.reason);
53
+ if (options.signal) options.signal.addEventListener('abort', onAbort, { once: true });
54
+ let timedOut = false;
55
+ const deadline = performance.now() + binding.timeoutMs;
56
+ const check = () => {
57
+ if (performance.now() >= deadline) { timedOut = true; controller.abort(); }
58
+ if (controller.signal.aborted) throw controller.signal.reason || new Error('S3 deadline expired.');
59
+ };
60
+ const timer = setTimeout(() => { timedOut = true; controller.abort(); }, binding.timeoutMs);
61
+ const wait = async (value) => { const result = await abortable(Promise.resolve(value), controller.signal); check(); return result; };
62
+ let response;
63
+ try {
64
+ const key = protocol.encodeKey(payload.key);
65
+ if (key === null) return failure('invalid-key');
66
+ let body, contentType;
67
+ if (put) {
68
+ if (typeof payload.text !== 'string' || !payload.text.isWellFormed()) return failure('invalid-text');
69
+ body = protocol.bytes(payload.text);
70
+ if (body.length > binding.maxTextBytes) return failure('too-large');
71
+ try { contentType = protocol.normalizePutOptions({ contentType: payload.contentType }).contentType; }
72
+ catch { return failure('configuration'); }
73
+ }
74
+ let accessId, secret, token;
75
+ try {
76
+ accessId = await wait(lookup(binding.accessKeyIdSecret));
77
+ secret = await wait(lookup(binding.secretAccessKeySecret));
78
+ token = binding.sessionTokenSecret === undefined ? undefined : await wait(lookup(binding.sessionTokenSecret));
79
+ } catch (error) { check(); return failure('credentials'); }
80
+ if (!protocol.credentialsValid(accessId, secret, token)) return failure('credentials');
81
+ for (const value of [accessId, secret, token]) if (typeof options.registerRedactionValue === 'function' && typeof value === 'string') options.registerRedactionValue(value);
82
+ const crypto = options.cryptoVerifier && options.cryptoVerifier.bytes;
83
+ const algorithms = options.cryptoRealization && options.cryptoRealization.algorithms;
84
+ const realization = options.cryptoTarget === 'javascript' ? 'runtime-builtin' : 'guest-source:pulse-hmac-as';
85
+ if (!crypto || !Array.isArray(algorithms) || ['SHA-256', 'HMAC-SHA256'].some((algorithm) => !algorithms.some((entry) => entry.algorithm === algorithm && entry.available && entry.realization === realization))) return failure('configuration');
86
+ let request;
87
+ try {
88
+ request = await wait(protocol.signRequest({ binding, method: put ? 'PUT' : operation === 'head' ? 'HEAD' : 'GET', encodedKey: key, accessId, secret, token, now: Date.now(), body, contentType }, crypto));
89
+ } catch (error) { check(); return failure('configuration'); }
90
+ check();
91
+ // The send primitive may have side effects even if it throws synchronously.
92
+ dispatched = true;
93
+ response = options.fetchImplementation
94
+ ? await fetchFixture(options.fetchImplementation, request, controller.signal)
95
+ : await originRequest(request, controller.signal);
96
+ status = response.status;
97
+ check();
98
+ const rejected = put ? protocol.putStatusResult(status) : protocol.statusResult(status);
99
+ if (rejected && (!put || rejected.status === 'unknown')) return rejected;
100
+ const metadata = protocol.metadataFromHeaders(response.headers, operation === 'head');
101
+ if (!metadata) return failure('protocol');
102
+ if (operation === 'head') return protocol.normalizeResult(operation, { status: 'found', ...metadata });
103
+ const maximum = put ? rejected ? binding.maxTextBytes : 0 : binding.maxTextBytes;
104
+ if (metadata.byteLength > maximum) return failure(put ? 'protocol' : 'too-large');
105
+ const chunks = []; let total = 0;
106
+ if (response.body) for await (const chunk of response.body) {
107
+ check(); total += chunk.length;
108
+ if (total > maximum) return failure(put ? 'protocol' : 'too-large');
109
+ if (!put) chunks.push(Buffer.from(chunk));
110
+ }
111
+ check();
112
+ if (metadata.byteLength !== undefined && metadata.byteLength !== total) return failure('protocol');
113
+ if (put) return rejected || protocol.normalizeResult(operation, { status: 'stored', byteLength: body.length, sha256: request.headers['x-amz-content-sha256'], ...(metadata.etag === undefined ? {} : { etag: metadata.etag }) });
114
+ const raw = Buffer.concat(chunks, total);
115
+ const digest = protocol.hex(await wait(crypto.sha256(raw)));
116
+ let text;
117
+ try { text = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(raw); }
118
+ catch { return failure('invalid-utf8'); }
119
+ check();
120
+ return protocol.normalizeResult(operation, { status: 'found', ...metadata, byteLength: total, text, sha256: digest });
121
+ } catch (error) {
122
+ if (options.signal && options.signal.aborted) throw options.signal.reason || error;
123
+ return failure(timedOut ? 'timeout' : status === undefined ? 'transport' : 'protocol');
124
+ } finally {
125
+ clearTimeout(timer);
126
+ if (options.signal) options.signal.removeEventListener('abort', onAbort);
127
+ controller.abort();
128
+ if (response) { try { await response.close(); } catch {} }
129
+ }
130
+ }
131
+ module.exports = { readS3, originRequest };
@@ -0,0 +1,269 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const {
5
+ PROVIDER_DRIVER_VERSION,
6
+ PROVIDER_TARGET_RESULT_VERSION,
7
+ PROVIDER_PACKAGING_AUDIT_VERSION,
8
+ defineProviderToolchain,
9
+ defineProviderDriver,
10
+ normalizeProviderTargetResult,
11
+ normalizeProviderJavascriptPackageResult
12
+ } = require('@pulse-compute/wasm-contracts/provider/toolchain');
13
+ const canonicalProvider = require('@pulse-compute/wasm-contracts/provider/canonical-provider');
14
+ const nodeRuntime = require('./runtime/canonical-api-runtime.js');
15
+ const { NODE_NATIVE_TARGET_DESCRIPTOR } = require('./native/target.js');
16
+ const { NODE_JAVASCRIPT_TARGET_DESCRIPTOR } = require('./javascript/target.js');
17
+ const { NODE_JAVASCRIPT_TARGET_SUPPORT_DECLARATION } = require('./javascript/support.js');
18
+ const {
19
+ classifyNodeJavascriptCapability,
20
+ classifyNodeJavascriptProviderRequirement
21
+ } = require('./javascript/target-support-policy.js');
22
+ const nodeJavascriptHost = require('./javascript/runtime-host.js');
23
+ const nodeJavascriptLifecycle = require('./javascript/lifecycle.js');
24
+ const nodeJavascriptTestRuntime = require('./javascript/test-runtime.js');
25
+ const { createNodeEventReferenceAdapter } = require('./events/reference-adapter.js');
26
+ const { writeNodeJavascriptSourcePackage } = require('./javascript/source-package.js');
27
+ const packageManifest = require('../package.json');
28
+
29
+ const reportingTargetCapability = Object.freeze({
30
+ supported: true,
31
+ default: 'info',
32
+ levels: Object.freeze(['off', 'error', 'warn', 'info', 'debug'])
33
+ });
34
+
35
+ function genericProviderConfig(value) {
36
+ const input = value && typeof value === 'object' ? value : {};
37
+ const bindings = input.bindings || {};
38
+ if (Object.keys(bindings).some((key) => key !== 's3')) throw new TypeError('Unknown Node binding field.');
39
+ return Object.freeze({ kind: 'node', bindings: Object.freeze({ s3: require('./config/s3.js').normalizeNodeS3(bindings.s3) }), local: Object.freeze({}) });
40
+ }
41
+
42
+ function nodeRealization(nativeArtifact) {
43
+ return Object.freeze({
44
+ target: 'node-native-host',
45
+ provider: 'node',
46
+ providerNeutralWasm: true,
47
+ nativeWasm: true,
48
+ compiledWasmPresent: true,
49
+ sourceOnly: false,
50
+ sourceOnlySupported: false,
51
+ javascriptRuntime: false,
52
+ jsComputeRuntime: false,
53
+ wasm: nativeArtifact.manifest.wasm,
54
+ wat: nativeArtifact.manifest.wat,
55
+ importModules: nativeArtifact.manifest.importModules,
56
+ imports: nativeArtifact.manifest.imports,
57
+ exports: nativeArtifact.manifest.exports,
58
+ packageRealizationArtifacts: nativeArtifact.manifest.packageRealizationArtifacts,
59
+ compiler: Object.freeze({
60
+ package: nativeArtifact.manifest.assemblyScript.package,
61
+ version: nativeArtifact.manifest.assemblyScript.version,
62
+ targetCompilerVersion: nativeArtifact.compilerVersion
63
+ }),
64
+ optimization: nativeArtifact.manifest.optimization
65
+ });
66
+ }
67
+
68
+ function targetSupportContext(_compiled, _project, declaration) {
69
+ const gates = declaration.availability && declaration.availability.gates || [];
70
+ return Object.freeze({
71
+ bindingsRedaction: gates.some((entry) => entry.id === 'bindings-redaction' && entry.status === 'satisfied'),
72
+ gripRealized: gates.some((entry) => entry.id === 'grip-readiness' && entry.evidence && entry.evidence.status === 'passed')
73
+ });
74
+ }
75
+
76
+ async function executeNodeEventTestCase(invocation = {}) {
77
+ const target = String(invocation.target || '');
78
+ if (!['javascript', 'native'].includes(target)) {
79
+ throw new TypeError(`Node event test execution does not support target ${JSON.stringify(target)}.`);
80
+ }
81
+ const testCase = invocation.testCase || {};
82
+ const options = invocation.options || {};
83
+ const adapter = createNodeEventReferenceAdapter({
84
+ id: `pulse-test-${target}`,
85
+ maxQueueDepth: 1024
86
+ });
87
+ let executionEvidence = null;
88
+ const common = {
89
+ application: options.application,
90
+ config: testCase.config || {},
91
+ secrets: testCase.secrets || {},
92
+ kv: testCase.kv || {},
93
+ grip: testCase.grip || {},
94
+ fetches: testCase.fetches || {},
95
+ eventAdapter: adapter,
96
+ schemaCodecs: options.schemaCodecs,
97
+ reporting: options.reporting,
98
+ maxEffects: options.maxEffects,
99
+ maxBindingNameBytes: options.maxBindingNameBytes,
100
+ maxBindingValueBytes: options.maxBindingValueBytes,
101
+ maxKvNamespaceBytes: options.maxKvNamespaceBytes,
102
+ maxKvKeyBytes: options.maxKvKeyBytes,
103
+ maxKvValueBytes: options.maxKvValueBytes,
104
+ maxKvValueDepth: options.maxKvValueDepth,
105
+ maxKvValueEntries: options.maxKvValueEntries
106
+ };
107
+ const result = target === 'javascript'
108
+ ? await nodeJavascriptHost.executeNodeJavascriptEvent(invocation.application, testCase.event, {
109
+ ...common,
110
+ fetchImplementation: nodeJavascriptHost.createNodeJavascriptFixtureFetch(
111
+ testCase.fetches || {},
112
+ options.networkFetch === true ? globalThis.fetch : undefined
113
+ ),
114
+ onEffectSummary(value) { executionEvidence = value; }
115
+ })
116
+ : await nodeRuntime.executeNodeNativeEvent(invocation.application, testCase.event, {
117
+ ...common,
118
+ onEventExecutionEvidence(value) { executionEvidence = value; }
119
+ });
120
+ return Object.freeze({
121
+ version: 'pulse.provider-event-test-result.v1',
122
+ target,
123
+ result,
124
+ emittedFrames: adapter.acceptedFrames(),
125
+ adapter: adapter.summary(),
126
+ executionEvidence
127
+ });
128
+ }
129
+
130
+ function createDriver() {
131
+ return defineProviderDriver({
132
+ version: PROVIDER_DRIVER_VERSION,
133
+ id: 'node',
134
+ descriptor: nodeRuntime.NODE_PROVIDER_DESCRIPTOR,
135
+ executable: true,
136
+ localExecution: true,
137
+ deployable: true,
138
+ deploymentValidated: false,
139
+ sourcePackage: false,
140
+ compiledWasm: true,
141
+ defaultBuildMode: 'native-provider',
142
+ sourceOnlySupported: false,
143
+ targetSupport: Object.freeze({ javascript: NODE_JAVASCRIPT_TARGET_SUPPORT_DECLARATION }),
144
+ normalizeConfig: genericProviderConfig,
145
+ configReference: Object.freeze({
146
+ sections: [{ id: 'node', title: 'Node provider options', description: 'Provider-owned Node profile configuration.' }],
147
+ fields: [{ section: 'node', path: 'node.bindings.s3', type: 'Readonly<Record<string, S3Binding>>', default: '`{}`', scope: 'Node Native and JavaScript S3',
148
+ description: 'Maps literal logical names to fixed HTTPS endpoint, bucket, region, accessKeyIdSecret, secretAccessKeySecret, optional sessionTokenSecret, maxTextBytes (1–32768, default 32768) and timeoutMs (1–30000, default 10000).',
149
+ security: 'Only named credential references are configuration. Runtime keys cannot override authority.' }]
150
+ }),
151
+ defaultLocalNetworkFetch: true,
152
+ projectConfigDocument(config) { return Object.keys(config.bindings.s3).length ? config : undefined; },
153
+ initTemplate() {
154
+ return Object.freeze({ profileFragment: '', dependencies: Object.freeze({}) });
155
+ },
156
+ executionOptions(config, values) {
157
+ return Object.freeze({
158
+ ...values,
159
+ s3: config.bindings.s3,
160
+ providerAdapter: nodeRuntime.createNodeProviderAdapter()
161
+ });
162
+ },
163
+ javascript: Object.freeze({
164
+ kind: 'reference-node',
165
+ localEmulation: false,
166
+ buildTimeLoader: false,
167
+ validateApplication: nodeJavascriptHost.assertNodeJavascriptApplication,
168
+ targetSupportPolicy: Object.freeze({
169
+ providerId: 'node',
170
+ declaration: NODE_JAVASCRIPT_TARGET_SUPPORT_DECLARATION,
171
+ context: targetSupportContext,
172
+ classifyCapability: classifyNodeJavascriptCapability,
173
+ classifyProviderRequirement: classifyNodeJavascriptProviderRequirement
174
+ }),
175
+ writeSourcePackage(invocation) {
176
+ return normalizeProviderJavascriptPackageResult(writeNodeJavascriptSourcePackage({
177
+ plan: invocation.applicationPlan,
178
+ projectRoot: invocation.project.root,
179
+ outDir: invocation.project.outDir,
180
+ schemaBundle: invocation.javascript.schemaBundle
181
+ }), { provider: 'node' });
182
+ },
183
+ describeSourcePackage(sourcePackage) {
184
+ return Object.freeze({
185
+ deploymentCandidate: false,
186
+ downstreamJavascriptRuntimeWasm: false,
187
+ application: Object.freeze({
188
+ entry: path.basename(sourcePackage.paths.entry),
189
+ modules: sourcePackage.manifest.summary.modules
190
+ }),
191
+ files: Object.freeze({})
192
+ });
193
+ },
194
+ executeTestCase(application, testCase, options) {
195
+ return nodeJavascriptTestRuntime.executeNodeJavascriptTestCase(application, testCase, options);
196
+ },
197
+ createServer: nodeJavascriptLifecycle.createNodeJavascriptServer,
198
+ createFixtureFetch: nodeJavascriptHost.createNodeJavascriptFixtureFetch
199
+ }),
200
+ events: Object.freeze({
201
+ targets: Object.freeze(['javascript', 'native']),
202
+ executeTestCase: executeNodeEventTestCase
203
+ }),
204
+ targets: Object.freeze({
205
+ native: Object.freeze({
206
+ ...NODE_NATIVE_TARGET_DESCRIPTOR,
207
+ target: 'native',
208
+ targetId: 'node-native-host',
209
+ runtimeClass: 'native',
210
+ status: 'supported',
211
+ automaticFallback: false,
212
+ reporting: reportingTargetCapability,
213
+ commands: Object.freeze({ compile: true, inspect: true, doctor: true, build: true, test: true, dev: true })
214
+ }),
215
+ javascript: NODE_JAVASCRIPT_TARGET_DESCRIPTOR
216
+ }),
217
+ execute: nodeRuntime.executeCanonicalProgram,
218
+ createLoweringPlan(metadata, config = {}) {
219
+ require('./config/s3.js').validateNodeS3Operations(metadata, config.bindings || {});
220
+ return canonicalProvider.createProviderLoweringPlan(metadata, nodeRuntime.NODE_PROVIDER_DESCRIPTOR, {
221
+ grip: 'node-reference-broadcaster'
222
+ });
223
+ },
224
+ inspectRealization(invocation) {
225
+ const realization = nodeRealization(invocation.nativeArtifact);
226
+ return normalizeProviderTargetResult({
227
+ version: PROVIDER_TARGET_RESULT_VERSION,
228
+ action: 'inspect-native',
229
+ provider: 'node',
230
+ target: 'native',
231
+ status: 'inspected',
232
+ automaticFallback: false,
233
+ files: Object.freeze({}),
234
+ build: null,
235
+ realization,
236
+ packaging: null
237
+ });
238
+ },
239
+ writeTarget(invocation) {
240
+ const realization = nodeRealization(invocation.nativeArtifact);
241
+ const audit = invocation.nativeArtifact.guestAudit;
242
+ return normalizeProviderTargetResult({
243
+ version: PROVIDER_TARGET_RESULT_VERSION,
244
+ action: 'write-native',
245
+ provider: 'node',
246
+ target: 'native',
247
+ status: 'built',
248
+ automaticFallback: false,
249
+ files: Object.freeze({}),
250
+ build: realization,
251
+ realization,
252
+ packaging: audit ? Object.freeze({
253
+ version: PROVIDER_PACKAGING_AUDIT_VERSION,
254
+ artifactSha256: invocation.nativeArtifact.finalArtifact.sha256,
255
+ auditedSha256: audit.finalArtifact.sha256,
256
+ authorized: audit.providerPackaging.authorized === true
257
+ }) : null
258
+ });
259
+ }
260
+ });
261
+ }
262
+
263
+ module.exports = defineProviderToolchain({
264
+ version: 'pulse.provider-toolchain.v1',
265
+ id: 'node',
266
+ packageName: packageManifest.name,
267
+ packageVersion: packageManifest.version,
268
+ createDriver
269
+ });