@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,93 @@
1
+ 'use strict';
2
+
3
+ const { randomUUID } = require('node:crypto');
4
+ const host = require('@pulse-compute/runtime/host');
5
+
6
+ // One explicit instance is one local authority. Reuse it across requests/targets
7
+ // to exercise shared storage. Identity/sequence injection is for deterministic
8
+ // host tests, never application input. This is not durable production storage.
9
+ function createNodeKvReference(options = {}) {
10
+ const identity = options.kvInstanceId === undefined ? randomUUID() : options.kvInstanceId;
11
+ if (typeof identity !== 'string' || !/^[a-zA-Z0-9_-]{1,64}$/.test(identity)) throw new TypeError('Node KV instance identity must be bounded ASCII.');
12
+ let sequence = options.kvGenerationStart === undefined ? 0n : BigInt(options.kvGenerationStart);
13
+ if (sequence < 0n || sequence >= (1n << 128n)) throw new TypeError('Node KV generation sequence is out of range.');
14
+ const stores = new Map(), records = new Map();
15
+ const hooks = options.kvHooks || {};
16
+ function nextGeneration() {
17
+ if (sequence >= (1n << 128n) - 1n) throw new Error('Node KV generation space exhausted.');
18
+ sequence += 1n;
19
+ return `node-kv-v1:${identity}:${sequence.toString(16).padStart(32, '0')}`;
20
+ }
21
+ function namespace(name) {
22
+ host.normalizeKvNamespace(name, options);
23
+ if (!stores.has(name)) { stores.set(name, new Map()); records.set(name, new Map()); }
24
+ return { values: stores.get(name), entries: records.get(name) };
25
+ }
26
+ function put(name, key, value) {
27
+ const { entries, values } = namespace(name);
28
+ const wire = host.encodeConditionalKvValue(value, options);
29
+ const detached = host.decodeConditionalKvValue(wire, options);
30
+ const generation = nextGeneration();
31
+ entries.set(key, { wire, generation }); values.set(key, detached);
32
+ }
33
+ // Reject accessors instead of executing configuration as ambient code.
34
+ function entries(input) {
35
+ if (input === undefined || input === null) return [];
36
+ if (typeof input !== 'object' || Array.isArray(input) || ![null, Object.prototype].includes(Object.getPrototypeOf(input))) throw new TypeError('Node KV seed must be a data record.');
37
+ return Reflect.ownKeys(input).map((key) => {
38
+ const d = Object.getOwnPropertyDescriptor(input, key);
39
+ if (typeof key !== 'string' || !d.enumerable || !Object.prototype.hasOwnProperty.call(d, 'value')) throw new TypeError('Node KV seed must contain data properties.');
40
+ return [key, d.value];
41
+ });
42
+ }
43
+ for (const [name, values] of entries(options.kv)) for (const [key, value] of entries(values)) {
44
+ host.normalizeKvKey(key, options); put(name, key, value);
45
+ }
46
+ function apply(effect) {
47
+ const { entries } = namespace(effect.namespace);
48
+ const current = entries.get(effect.key);
49
+ if (effect.kind === 'kv.getVersioned') return current
50
+ ? Object.freeze({ status: 'found', generation: current.generation, value: host.decodeConditionalKvValue(current.wire, options) })
51
+ : Object.freeze({ status: 'not-found' });
52
+ if (effect.kind === 'kv.compareAndSwap' && !new RegExp(`^node-kv-v1:${identity}:[0-9a-f]{32}$`).test(effect.generation)) {
53
+ return Object.freeze({ status: 'not-stored', reason: 'invalid-generation' });
54
+ }
55
+ // No await, callback, or provider GET/PUT between predicate and mutation.
56
+ const matches = effect.kind === 'kv.insertIfAbsent' ? !current : current && current.generation === effect.generation;
57
+ if (!matches) return Object.freeze({ status: 'conflict' });
58
+ put(effect.namespace, effect.key, effect.value);
59
+ return Object.freeze({ status: 'stored' });
60
+ }
61
+ async function prepareConditionalKv(effect, execution = {}) {
62
+ if (hooks.prepare) await hooks.prepare(effect, execution);
63
+ return async () => {
64
+ if (hooks.dispatch) await hooks.dispatch(effect, execution);
65
+ if (execution.signal && execution.signal.aborted) throw new Error('Node KV operation stopped before acceptance.');
66
+ const result = apply(effect);
67
+ if (result.status === 'found' && hooks.read) return hooks.read(result, effect, execution);
68
+ if (result.status === 'stored' && hooks.afterCommit) await hooks.afterCommit(effect, execution);
69
+ return result;
70
+ };
71
+ }
72
+ return Object.freeze({
73
+ version: 'pulse.node-kv-reference.v1',
74
+ prepareConditionalKv,
75
+ // Historical Assets reads use this semantic view, never the generation table.
76
+ legacyStores() { return stores; },
77
+ kv(name) {
78
+ const { values } = namespace(name);
79
+ const conditional = (operation, key, generation, value) => {
80
+ const admitted = host.admitConditionalKv({ kind: `kv.${operation}`, namespace: name, key, generation, value }, options);
81
+ return host.executeConditionalKv(admitted, prepareConditionalKv, {}, options);
82
+ };
83
+ return Object.freeze({
84
+ get(key) { host.normalizeKvKey(key, options); return host.cloneKvValue(values.get(key), { ...options, allowUndefined: true }); },
85
+ put(key, value) { host.normalizeKvKey(key, options); put(name, key, value); return true; },
86
+ getVersioned(key) { return conditional('getVersioned', key); },
87
+ insertIfAbsent(key, value) { return conditional('insertIfAbsent', key, undefined, value); },
88
+ compareAndSwap(key, generation, value) { return conditional('compareAndSwap', key, generation, value); }
89
+ });
90
+ }
91
+ });
92
+ }
93
+ module.exports = Object.freeze({ createNodeKvReference });
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ const compiledWasmNodeAdapterKv = require('./compiled-wasm-node-adapter-kv.js');
4
+ const assetsProvider = require('./assets-provider.js');
5
+ const routeHandlerEffects = require('./route-handler-effects.js');
6
+ const canonicalApiRuntime = require('./canonical-api-runtime.js');
7
+
8
+ module.exports = {
9
+ ...require('./conditional-kv.js'),
10
+ compiledWasmNodeAdapterKv,
11
+ assetsProvider,
12
+ routeHandlerEffects,
13
+ canonicalApiRuntime,
14
+ ...compiledWasmNodeAdapterKv,
15
+ ...assetsProvider,
16
+ ...routeHandlerEffects,
17
+ ...canonicalApiRuntime
18
+ };
@@ -0,0 +1,264 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ CRYPTO_ES256_GUEST_LINKED_IMPLEMENTATION,
5
+ CRYPTO_ES256_GUEST_LINKED_REALIZATION,
6
+ CRYPTO_GUEST_SOURCE_IMPLEMENTATION
7
+ } = require('@pulse-compute/wasm-contracts/crypto/contracts');
8
+
9
+ const NODE_NATIVE_JWT_VERIFIER_VERSION = 'pulse.node-native-jwt-verifier.v1';
10
+ const NODE_NATIVE_JWT_REALIZATIONS = Object.freeze({
11
+ HS256: Object.freeze({
12
+ algorithm: 'HS256',
13
+ realization: 'guest-source:pulse-hmac-as',
14
+ implementation: CRYPTO_GUEST_SOURCE_IMPLEMENTATION,
15
+ guestUnitRequired: false
16
+ }),
17
+ ES256: Object.freeze({
18
+ algorithm: 'ES256',
19
+ realization: CRYPTO_ES256_GUEST_LINKED_REALIZATION,
20
+ implementation: CRYPTO_ES256_GUEST_LINKED_IMPLEMENTATION,
21
+ guestUnitRequired: true
22
+ })
23
+ });
24
+ const NODE_NATIVE_JWT_REALIZATION = Object.freeze({
25
+ ...NODE_NATIVE_JWT_REALIZATIONS.HS256,
26
+ semanticOwner: '@pulse-compute/crypto',
27
+ automaticFallback: false
28
+ });
29
+
30
+ let providerModule;
31
+
32
+ function dataRecord(value) {
33
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
34
+ let prototype;
35
+ try { prototype = Object.getPrototypeOf(value); }
36
+ catch { return undefined; }
37
+ if (prototype !== Object.prototype && prototype !== null) return undefined;
38
+ const output = new Map();
39
+ try {
40
+ for (const key of Reflect.ownKeys(value)) {
41
+ if (typeof key !== 'string') return undefined;
42
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
43
+ if (
44
+ !descriptor
45
+ || !descriptor.enumerable
46
+ || !Object.prototype.hasOwnProperty.call(descriptor, 'value')
47
+ ) return undefined;
48
+ output.set(key, descriptor.value);
49
+ }
50
+ } catch {
51
+ return undefined;
52
+ }
53
+ return output;
54
+ }
55
+
56
+ async function loadJwtProvider() {
57
+ if (!providerModule) providerModule = import('@pulse-compute/jwt/provider');
58
+ return providerModule;
59
+ }
60
+
61
+ function requestActive(executionOptions, jwtProvider) {
62
+ const execution = dataRecord(executionOptions);
63
+ const signal = execution && execution.get('signal');
64
+ if (signal && signal.aborted) {
65
+ throw jwtProvider.jwtError('PULSE_JWT_OPERATION_FAILED', {
66
+ category: 'request-cancelled',
67
+ automaticFallback: false
68
+ });
69
+ }
70
+ }
71
+
72
+ function requireCryptoVerifier(executionOptions, jwtProvider, algorithm) {
73
+ const execution = dataRecord(executionOptions);
74
+ const realization = dataRecord(execution && execution.get('cryptoRealization'));
75
+ const verifier = execution && execution.get('cryptoVerifier');
76
+ const expected = NODE_NATIVE_JWT_REALIZATIONS[algorithm];
77
+ const algorithms = realization && realization.get('algorithms');
78
+ const selected = Array.isArray(algorithms)
79
+ ? algorithms.map(dataRecord).find((entry) => (
80
+ entry
81
+ && expected
82
+ && entry.get('algorithm') === expected.algorithm
83
+ && entry.get('realization') === expected.realization
84
+ && entry.get('implementation') === expected.implementation
85
+ && entry.get('guestUnitRequired') === expected.guestUnitRequired
86
+ && entry.get('available') === true
87
+ ))
88
+ : undefined;
89
+ if (
90
+ !expected
91
+ || !realization
92
+ || realization.get('semanticOwner') !== '@pulse-compute/crypto'
93
+ || realization.get('automaticFallback') !== false
94
+ || !selected
95
+ || !dataRecord(verifier)
96
+ ) {
97
+ throw jwtProvider.jwtError('PULSE_JWT_TARGET_UNSUPPORTED', {
98
+ category: 'crypto-realization',
99
+ realization: expected && expected.realization,
100
+ implementation: expected && expected.implementation,
101
+ automaticFallback: false
102
+ });
103
+ }
104
+ return verifier;
105
+ }
106
+
107
+ function keyArtifact(executionOptions, artifactId, jwtProvider) {
108
+ const execution = dataRecord(executionOptions);
109
+ const artifacts = execution && execution.get('packageArtifacts');
110
+ const matches = Array.isArray(artifacts)
111
+ ? artifacts.filter((artifact) => {
112
+ const record = dataRecord(artifact);
113
+ return record && record.get('id') === artifactId;
114
+ })
115
+ : [];
116
+ if (matches.length !== 1) {
117
+ throw jwtProvider.jwtError('PULSE_JWT_KEY_INVALID', {
118
+ category: 'key-artifact',
119
+ });
120
+ }
121
+ const artifact = dataRecord(matches[0]);
122
+ const data = artifact && dataRecord(artifact.get('data'));
123
+ if (
124
+ !artifact
125
+ || artifact.get('version') !== 'pulse.jwt-es256-key-artifact.v1'
126
+ || artifact.get('contractId') !== 'pulse.jwt'
127
+ || artifact.get('package') !== '@pulse-compute/jwt'
128
+ || artifact.get('kind') !== 'jwt-es256-static-public-key'
129
+ || !data
130
+ ) {
131
+ throw jwtProvider.jwtError('PULSE_JWT_KEY_INVALID', {
132
+ category: 'key-artifact',
133
+ });
134
+ }
135
+ return Object.freeze(Object.fromEntries(data));
136
+ }
137
+
138
+ function effectInput(effect, executionOptions, jwtProvider) {
139
+ const record = dataRecord(effect);
140
+ const payload = dataRecord(record && record.get('payload'));
141
+ const resource = dataRecord(record && record.get('resource'));
142
+ if (
143
+ !record
144
+ || record.get('kind') !== 'jwt.verify'
145
+ || record.get('contractId') !== 'pulse.jwt'
146
+ || record.get('operation') !== 'verify'
147
+ || !payload
148
+ || !resource
149
+ ) {
150
+ throw jwtProvider.jwtError('PULSE_JWT_OPERATION_FAILED', {
151
+ category: 'verifier-input'
152
+ });
153
+ }
154
+ const authorization = payload.get('token');
155
+ if (authorization !== undefined && typeof authorization !== 'string') {
156
+ throw jwtProvider.jwtError('PULSE_JWT_BEARER_INVALID', {
157
+ category: 'authorization'
158
+ });
159
+ }
160
+ const keyType = resource.get('keyType');
161
+ const binding = resource.get('secretBinding');
162
+ const artifactId = resource.get('keyArtifactId');
163
+ let key;
164
+ if (keyType === 'secret') {
165
+ if (
166
+ artifactId !== null
167
+ || typeof binding !== 'string'
168
+ || binding.length === 0
169
+ ) throw jwtProvider.jwtError('PULSE_JWT_KEY_INVALID', {
170
+ category: 'secret-binding'
171
+ });
172
+ key = Object.freeze({ type: 'secret', binding });
173
+ } else if (keyType === 'jwk' || keyType === 'jwks') {
174
+ if (binding !== null || typeof artifactId !== 'string' || artifactId.length === 0) {
175
+ throw jwtProvider.jwtError('PULSE_JWT_KEY_INVALID', {
176
+ category: 'key-artifact'
177
+ });
178
+ }
179
+ key = keyArtifact(executionOptions, artifactId, jwtProvider);
180
+ if (key.type !== keyType) throw jwtProvider.jwtError('PULSE_JWT_KEY_INVALID', {
181
+ category: 'key-artifact'
182
+ });
183
+ } else {
184
+ throw jwtProvider.jwtError('PULSE_JWT_KEY_INVALID', { category: 'key' });
185
+ }
186
+ const policy = Object.fromEntries(
187
+ [...payload.entries()].filter(([name]) => name !== 'token')
188
+ );
189
+ return Object.freeze({
190
+ token: jwtProvider.bearerFromAuthorizationValue(authorization),
191
+ options: Object.freeze({
192
+ ...policy,
193
+ key
194
+ })
195
+ });
196
+ }
197
+
198
+ function createNodeNativeJwtVerify(baseOptions = {}) {
199
+ const secretLookup = baseOptions.secretLookup;
200
+ const captureWallClock = baseOptions.captureWallClock
201
+ || (() => Object.freeze({
202
+ unixEpochSeconds: Date.now() / 1000,
203
+ trusted: true
204
+ }));
205
+ return async function verifyNodeNativeJwt(effect, executionOptions = {}) {
206
+ const jwtProvider = await loadJwtProvider();
207
+ requestActive(executionOptions, jwtProvider);
208
+ const input = effectInput(effect, executionOptions, jwtProvider);
209
+ const selectedCrypto = requireCryptoVerifier(
210
+ executionOptions,
211
+ jwtProvider,
212
+ input.options.algorithms[0]
213
+ );
214
+ const execution = dataRecord(executionOptions);
215
+ const executionClock = execution && execution.get('captureJwtWallClock');
216
+ const host = {
217
+ async captureWallClock() {
218
+ requestActive(executionOptions, jwtProvider);
219
+ const captured = await (
220
+ typeof executionClock === 'function'
221
+ ? executionClock()
222
+ : captureWallClock()
223
+ );
224
+ requestActive(executionOptions, jwtProvider);
225
+ return captured;
226
+ },
227
+ resolveSecret: typeof secretLookup === 'function'
228
+ ? async (binding) => {
229
+ requestActive(executionOptions, jwtProvider);
230
+ const secret = await secretLookup(binding, executionOptions);
231
+ requestActive(executionOptions, jwtProvider);
232
+ return secret;
233
+ }
234
+ : undefined,
235
+ registerSensitiveValue(value) {
236
+ const register = execution && execution.get('registerRedactionValue');
237
+ if (typeof register === 'function') register(value);
238
+ }
239
+ };
240
+ const validateSchemaValue = execution && execution.get('validateSchemaValue');
241
+ if (typeof validateSchemaValue === 'function') {
242
+ host.validateClaims = async (schemaId, claims, context) => {
243
+ requestActive(executionOptions, jwtProvider);
244
+ const result = await validateSchemaValue(schemaId, claims, context);
245
+ requestActive(executionOptions, jwtProvider);
246
+ return result;
247
+ };
248
+ }
249
+ const result = await jwtProvider.verifyJwtWithCrypto(
250
+ input,
251
+ Object.freeze(host),
252
+ selectedCrypto
253
+ );
254
+ requestActive(executionOptions, jwtProvider);
255
+ return result;
256
+ };
257
+ }
258
+
259
+ module.exports = Object.freeze({
260
+ NODE_NATIVE_JWT_VERIFIER_VERSION,
261
+ NODE_NATIVE_JWT_REALIZATION,
262
+ NODE_NATIVE_JWT_REALIZATIONS,
263
+ createNodeNativeJwtVerify
264
+ });