@pulse-compute/provider-fastly 0.0.0 → 1.0.0-beta.10

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 (103) hide show
  1. package/README.md +53 -1
  2. package/dist/config-schema.json +103 -0
  3. package/dist/index.d.ts +77 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +67 -0
  6. package/dist/index.js.map +1 -0
  7. package/package.json +88 -5
  8. package/src/build/canonical-target.js +469 -0
  9. package/src/build/digest-native.js +25 -0
  10. package/src/build/effect-invocations.js +49 -0
  11. package/src/build/embedded-assets.as.ts +32 -0
  12. package/src/build/entities-native.js +559 -0
  13. package/src/build/fixed-memory-runtime.as.ts +148 -0
  14. package/src/build/kv-native.as.ts +331 -0
  15. package/src/build/kv-native.js +27 -0
  16. package/src/build/native-application-errors.js +159 -0
  17. package/src/build/native-binary-resources.js +30 -0
  18. package/src/build/native-body-forwarding.as.ts +341 -0
  19. package/src/build/native-body-forwarding.js +69 -0
  20. package/src/build/native-dynamic-backends.as.ts +115 -0
  21. package/src/build/native-dynamic-backends.js +52 -0
  22. package/src/build/native-fetch-deadlines.as.ts +164 -0
  23. package/src/build/native-fetch-deadlines.js +27 -0
  24. package/src/build/native-fetch-errors.js +38 -0
  25. package/src/build/native-handle-ownership.js +127 -0
  26. package/src/build/native-header-lookup.js +50 -0
  27. package/src/build/native-http-effects.js +994 -0
  28. package/src/build/native-http-shell.js +703 -0
  29. package/src/build/native-platform-capabilities.js +2654 -0
  30. package/src/build/native-request-body.js +44 -0
  31. package/src/build/native-request-headers.js +93 -0
  32. package/src/build/native-send-error-detail.as.ts +12 -0
  33. package/src/build/native-stdio.js +45 -0
  34. package/src/build/native-string-values.js +143 -0
  35. package/src/build/native-transport-details.as.ts +84 -0
  36. package/src/build/native-value-budget.js +90 -0
  37. package/src/build/package-native-application.js +114 -0
  38. package/src/build/request-budget.as.ts +50 -0
  39. package/src/build/request-budget.js +47 -0
  40. package/src/build/s3-body.as.ts +97 -0
  41. package/src/build/s3-native.as.ts +228 -0
  42. package/src/build/s3-native.js +30 -0
  43. package/src/build/schema-admission.js +83 -0
  44. package/src/build/schema-nested-json.js +93 -0
  45. package/src/build/schema-scalar-record.js +41 -0
  46. package/src/build/time-native.js +49 -0
  47. package/src/build/wasm-budget.js +28 -0
  48. package/src/compiler/assets-package-out-parity-proof.js +338 -0
  49. package/src/compiler/assets-provider-proof.js +499 -0
  50. package/src/compiler/config-provider-resolution.js +573 -0
  51. package/src/compiler/fastly-adapter.js +564 -0
  52. package/src/compiler/fastly-command-entry.js +390 -0
  53. package/src/compiler/fastly-config-secret-serve.js +323 -0
  54. package/src/compiler/fastly-hostcall-binding.js +598 -0
  55. package/src/compiler/fastly-readiness.js +477 -0
  56. package/src/compiler/index.js +40 -0
  57. package/src/compiler/lifecycle-parity-contract.js +70 -0
  58. package/src/compiler/lifecycle-parity-proof.js +260 -0
  59. package/src/compiler/route-handler-effect-proof.js +255 -0
  60. package/src/compiler/runtime-provider-kv.js +237 -0
  61. package/src/compiler/runtime-provider-shape.js +109 -0
  62. package/src/config-api.js +69 -0
  63. package/src/config-schema.d.ts +13 -0
  64. package/src/config-schema.js +27 -0
  65. package/src/config-schema.json +103 -0
  66. package/src/index.js +3 -0
  67. package/src/index.ts +143 -0
  68. package/src/javascript/assets-adapter.js +122 -0
  69. package/src/javascript/bindings-adapter.js +237 -0
  70. package/src/javascript/errors.js +26 -0
  71. package/src/javascript/fetch-adapter.js +93 -0
  72. package/src/javascript/grip-broadcast.js +261 -0
  73. package/src/javascript/jwt-verifier.js +147 -0
  74. package/src/javascript/lifecycle.js +55 -0
  75. package/src/javascript/local-loader.js +138 -0
  76. package/src/javascript/logging.js +35 -0
  77. package/src/javascript/package-effects.js +71 -0
  78. package/src/javascript/runtime-host.js +112 -0
  79. package/src/javascript/sha256.js +128 -0
  80. package/src/javascript/source-package.js +778 -0
  81. package/src/javascript/support.js +142 -0
  82. package/src/javascript/target-support-policy.js +277 -0
  83. package/src/javascript/target.js +89 -0
  84. package/src/javascript/test-runtime.js +408 -0
  85. package/src/provider-contract.js +88 -0
  86. package/src/runtime/assets-provider.js +464 -0
  87. package/src/runtime/canonical-api-runtime.js +603 -0
  88. package/src/runtime/index.js +28 -0
  89. package/src/runtime/provider-primitives-contract.js +272 -0
  90. package/src/runtime/provider-primitives.js +82 -0
  91. package/src/runtime/route-handler-effects.js +507 -0
  92. package/src/testing/conditional-kv-host.js +155 -0
  93. package/src/testing/fastly-cli.js +538 -0
  94. package/src/testing/native-dynamic-backends-host.js +39 -0
  95. package/src/testing/native-http-effects-host.js +398 -0
  96. package/src/testing/native-http-shell-host.js +190 -0
  97. package/src/testing/native-platform-capabilities-host.js +924 -0
  98. package/src/toolchain/body-forwarding.js +25 -0
  99. package/src/toolchain/config.js +144 -0
  100. package/src/toolchain/index.js +386 -0
  101. package/src/toolchain/logging.js +18 -0
  102. package/src/toolchain/native-execution.js +78 -0
  103. package/src/toolchain/s3.js +26 -0
@@ -0,0 +1,261 @@
1
+ 'use strict';
2
+
3
+ const { fastlyJavascriptProviderError, isPulseRuntimeError } = require('./errors.js');
4
+
5
+ const FASTLY_JAVASCRIPT_GRIP_BROADCAST_VERSION = 'pulse.fastly-javascript-grip-broadcast.v1';
6
+ const DEFAULT_MAX_REQUEST_BYTES = 65536;
7
+ const DEFAULT_MAX_RESPONSE_BYTES = 65536;
8
+
9
+ function positiveSafeInteger(value, fallback, field) {
10
+ if (value === undefined) return fallback;
11
+ const number = Number(value);
12
+ if (!Number.isSafeInteger(number) || number <= 0) {
13
+ throw new TypeError(`Pulse Fastly JavaScript GRIP ${field} must be a positive safe integer.`);
14
+ }
15
+ return number;
16
+ }
17
+
18
+ function normalizeEndpoint(input) {
19
+ let endpoint;
20
+ try { endpoint = new URL(String(input || '')); }
21
+ catch (error) {
22
+ throw fastlyJavascriptProviderError(
23
+ 'PULSE_GRIP_BROADCAST_ENDPOINT_INVALID',
24
+ 'Fastly JavaScript GRIP publish endpoint must be an absolute HTTP or HTTPS URL.',
25
+ { operation: 'broadcast', configuration: 'bindings.grip.publishEndpoint' },
26
+ error
27
+ );
28
+ }
29
+ if (!['http:', 'https:'].includes(endpoint.protocol)) {
30
+ throw fastlyJavascriptProviderError(
31
+ 'PULSE_GRIP_BROADCAST_ENDPOINT_INVALID',
32
+ 'Fastly JavaScript GRIP publish endpoint must use HTTP or HTTPS.',
33
+ { operation: 'broadcast', protocol: endpoint.protocol }
34
+ );
35
+ }
36
+ return endpoint;
37
+ }
38
+
39
+ function normalizeAuthentication(input) {
40
+ if (input === undefined) return undefined;
41
+ const scheme = input && String(input.scheme || 'bearer').toLowerCase();
42
+ const secretRef = input && typeof input.secretRef === 'string' ? input.secretRef.trim() : '';
43
+ if (scheme !== 'bearer') {
44
+ throw fastlyJavascriptProviderError(
45
+ 'PULSE_GRIP_BROADCAST_AUTH_SCHEME_UNSUPPORTED',
46
+ 'Fastly JavaScript GRIP supports bearer authentication only.',
47
+ { operation: 'broadcast', scheme }
48
+ );
49
+ }
50
+ if (!secretRef) {
51
+ throw fastlyJavascriptProviderError(
52
+ 'PULSE_GRIP_BROADCAST_SECRET_REFERENCE_REQUIRED',
53
+ 'Fastly JavaScript GRIP bearer authentication requires a secret reference.',
54
+ { operation: 'broadcast', configuration: 'bindings.grip.authentication.secretRef' }
55
+ );
56
+ }
57
+ return Object.freeze({ scheme, secretRef });
58
+ }
59
+
60
+ function requestBody(payload) {
61
+ const channel = payload && typeof payload.channel === 'string' ? payload.channel.trim() : '';
62
+ if (!channel || /[,\r\n]/.test(channel)) {
63
+ throw fastlyJavascriptProviderError(
64
+ 'PULSE_GRIP_BROADCAST_CHANNEL_INVALID',
65
+ 'GRIP broadcast channel must be non-empty and contain no commas or line breaks.',
66
+ { operation: 'broadcast' }
67
+ );
68
+ }
69
+ if (!payload || !Object.prototype.hasOwnProperty.call(payload, 'data') || payload.data === undefined) {
70
+ throw fastlyJavascriptProviderError(
71
+ 'PULSE_GRIP_BROADCAST_MESSAGE_INVALID',
72
+ 'GRIP broadcast requires a JSON-compatible data value.',
73
+ { operation: 'broadcast' }
74
+ );
75
+ }
76
+ try {
77
+ const body = JSON.stringify({
78
+ channel,
79
+ data: payload.data,
80
+ ...(payload.event === undefined ? {} : { event: String(payload.event) }),
81
+ ...(payload.id === undefined ? {} : { id: String(payload.id) })
82
+ });
83
+ if (typeof body !== 'string') throw new TypeError('not serializable');
84
+ return body;
85
+ } catch (error) {
86
+ throw fastlyJavascriptProviderError(
87
+ 'PULSE_GRIP_BROADCAST_MESSAGE_INVALID',
88
+ 'GRIP broadcast message must be JSON-compatible.',
89
+ { operation: 'broadcast' },
90
+ error
91
+ );
92
+ }
93
+ }
94
+
95
+ async function readBoundedResponseText(response, maxBytes) {
96
+ const length = Number(response.headers && response.headers.get && response.headers.get('content-length'));
97
+ if (Number.isFinite(length) && length > maxBytes) {
98
+ throw fastlyJavascriptProviderError(
99
+ 'PULSE_GRIP_BROADCAST_RESPONSE_TOO_LARGE',
100
+ 'GRIP broadcast provider response exceeded the configured byte limit.',
101
+ { operation: 'broadcast', maxResponseBytes: maxBytes }
102
+ );
103
+ }
104
+ if (!response.body) return '';
105
+ const reader = response.body.getReader();
106
+ const decoder = new TextDecoder();
107
+ let bytes = 0;
108
+ let text = '';
109
+ try {
110
+ for (;;) {
111
+ const part = await reader.read();
112
+ if (part.done) break;
113
+ bytes += part.value.byteLength;
114
+ if (bytes > maxBytes) {
115
+ try { await reader.cancel(); } catch (_) { /* Best-effort host stream cleanup. */ }
116
+ throw fastlyJavascriptProviderError(
117
+ 'PULSE_GRIP_BROADCAST_RESPONSE_TOO_LARGE',
118
+ 'GRIP broadcast provider response exceeded the configured byte limit.',
119
+ { operation: 'broadcast', maxResponseBytes: maxBytes }
120
+ );
121
+ }
122
+ text += decoder.decode(part.value, { stream: true });
123
+ }
124
+ return text + decoder.decode();
125
+ } finally {
126
+ reader.releaseLock();
127
+ }
128
+ }
129
+
130
+ function normalizeAck(status, text) {
131
+ if (!text.trim()) return Object.freeze({ accepted: true, status });
132
+ let parsed;
133
+ try { parsed = JSON.parse(text); }
134
+ catch (error) {
135
+ throw fastlyJavascriptProviderError(
136
+ 'PULSE_GRIP_BROADCAST_RESPONSE_INVALID',
137
+ 'GRIP broadcast provider returned an invalid acknowledgement.',
138
+ { operation: 'broadcast', status },
139
+ error
140
+ );
141
+ }
142
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
143
+ throw fastlyJavascriptProviderError(
144
+ 'PULSE_GRIP_BROADCAST_RESPONSE_INVALID',
145
+ 'GRIP broadcast provider acknowledgement must be an object.',
146
+ { operation: 'broadcast', status }
147
+ );
148
+ }
149
+ return Object.freeze({
150
+ accepted: parsed.accepted !== false,
151
+ status,
152
+ ...(parsed.messageId === undefined ? {} : { messageId: String(parsed.messageId) })
153
+ });
154
+ }
155
+
156
+ function createFastlyJavascriptGripBroadcast(options = {}) {
157
+ const grip = options.grip && typeof options.grip === 'object' ? options.grip : {};
158
+ const endpoint = normalizeEndpoint(grip.publishEndpoint || grip.publishUrl);
159
+ const authentication = normalizeAuthentication(grip.authentication);
160
+ const bindings = options.bindings && typeof options.bindings === 'object' ? options.bindings : {};
161
+ const backends = bindings.backends && typeof bindings.backends === 'object' ? bindings.backends : {};
162
+ const backend = typeof grip.publishBackend === 'string' && grip.publishBackend.trim()
163
+ ? grip.publishBackend.trim()
164
+ : (typeof backends[endpoint.origin] === 'string' ? backends[endpoint.origin].trim() : '');
165
+ if (!backend && bindings.dynamicBackends !== true) {
166
+ throw fastlyJavascriptProviderError(
167
+ 'PULSE_GRIP_BROADCAST_BACKEND_REQUIRED',
168
+ 'Fastly JavaScript GRIP requires a named publish backend unless dynamic backends are enabled.',
169
+ { operation: 'broadcast', origin: endpoint.origin, configuration: 'bindings.grip.publishBackend' }
170
+ );
171
+ }
172
+ if (typeof options.fetchImplementation !== 'function') {
173
+ throw fastlyJavascriptProviderError(
174
+ 'PULSE_GRIP_BROADCAST_CAPABILITY_REQUIRED',
175
+ 'Fastly JavaScript GRIP requires the provider fetch implementation.',
176
+ { operation: 'broadcast' }
177
+ );
178
+ }
179
+ const maxRequestBytes = positiveSafeInteger(grip.maxRequestBytes, DEFAULT_MAX_REQUEST_BYTES, 'maxRequestBytes');
180
+ const maxResponseBytes = positiveSafeInteger(grip.maxResponseBytes, DEFAULT_MAX_RESPONSE_BYTES, 'maxResponseBytes');
181
+
182
+ return async function fastlyJavascriptGripBroadcast(payload, execution = {}) {
183
+ const body = requestBody(payload);
184
+ if (new TextEncoder().encode(body).byteLength > maxRequestBytes) {
185
+ throw fastlyJavascriptProviderError(
186
+ 'PULSE_GRIP_BROADCAST_REQUEST_TOO_LARGE',
187
+ 'GRIP broadcast request exceeded the configured byte limit.',
188
+ { operation: 'broadcast', maxRequestBytes }
189
+ );
190
+ }
191
+ const headers = new Headers({
192
+ accept: 'application/json',
193
+ 'content-type': 'application/json; charset=utf-8'
194
+ });
195
+ if (authentication) {
196
+ const secret = typeof options.secretLookup === 'function'
197
+ ? await options.secretLookup(authentication.secretRef)
198
+ : undefined;
199
+ if (typeof secret !== 'string' || secret.length === 0) {
200
+ throw fastlyJavascriptProviderError(
201
+ 'PULSE_GRIP_BROADCAST_SECRET_REQUIRED',
202
+ 'GRIP broadcast authentication secret is unavailable.',
203
+ { operation: 'broadcast', secretRef: authentication.secretRef }
204
+ );
205
+ }
206
+ if (typeof execution.registerRedactionValue === 'function') execution.registerRedactionValue(secret);
207
+ headers.set('authorization', `Bearer ${secret}`);
208
+ }
209
+
210
+ let response;
211
+ let text;
212
+ try {
213
+ response = await options.fetchImplementation(endpoint.href, {
214
+ method: 'POST',
215
+ headers,
216
+ body,
217
+ signal: execution.signal,
218
+ redirect: 'error',
219
+ ...(backend ? { backend } : {})
220
+ });
221
+ if (!(response instanceof Response)) {
222
+ throw fastlyJavascriptProviderError(
223
+ 'PULSE_GRIP_BROADCAST_RESPONSE_INVALID',
224
+ 'GRIP broadcast provider must return a Web Response.',
225
+ { operation: 'broadcast' }
226
+ );
227
+ }
228
+ text = await readBoundedResponseText(response, maxResponseBytes);
229
+ } catch (error) {
230
+ if (isPulseRuntimeError(error)) throw error;
231
+ if (execution.signal && execution.signal.aborted) {
232
+ throw fastlyJavascriptProviderError(
233
+ 'PULSE_GRIP_BROADCAST_ABORTED',
234
+ 'GRIP broadcast was cancelled with its owning request.',
235
+ { operation: 'broadcast' }
236
+ );
237
+ }
238
+ throw fastlyJavascriptProviderError(
239
+ 'PULSE_GRIP_BROADCAST_NETWORK',
240
+ 'GRIP broadcast provider request failed.',
241
+ { operation: 'broadcast' },
242
+ error
243
+ );
244
+ }
245
+ if (response.status < 200 || response.status > 299) {
246
+ throw fastlyJavascriptProviderError(
247
+ 'PULSE_GRIP_BROADCAST_REJECTED',
248
+ 'GRIP broadcast provider rejected the publication.',
249
+ { operation: 'broadcast', status: response.status }
250
+ );
251
+ }
252
+ return normalizeAck(response.status, text);
253
+ };
254
+ }
255
+
256
+ module.exports = Object.freeze({
257
+ FASTLY_JAVASCRIPT_GRIP_BROADCAST_VERSION,
258
+ DEFAULT_MAX_REQUEST_BYTES,
259
+ DEFAULT_MAX_RESPONSE_BYTES,
260
+ createFastlyJavascriptGripBroadcast
261
+ });
@@ -0,0 +1,147 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ normalizeJwtSignEffect,
5
+ normalizeJwtVerifyEffect
6
+ } = require('@pulse-compute/wasm-contracts/jwt/contracts');
7
+ const {
8
+ CRYPTO_ES256_RUNTIME_BUILTIN_IMPLEMENTATION,
9
+ CRYPTO_RS256_RUNTIME_BUILTIN_IMPLEMENTATION,
10
+ CRYPTO_RUNTIME_BUILTIN_IMPLEMENTATION
11
+ } = require('@pulse-compute/wasm-contracts/crypto/contracts');
12
+
13
+ const FASTLY_JAVASCRIPT_JWT_VERIFIER_VERSION = 'pulse.fastly-javascript-jwt-verifier.v2';
14
+ const FASTLY_JAVASCRIPT_JWT_REALIZATIONS = Object.freeze({
15
+ HS256: Object.freeze({
16
+ algorithm: 'HS256',
17
+ realization: 'runtime-builtin',
18
+ implementation: CRYPTO_RUNTIME_BUILTIN_IMPLEMENTATION,
19
+ automaticFallback: false
20
+ }),
21
+ RS256: Object.freeze({ algorithm: 'RS256', realization: 'runtime-builtin', implementation: CRYPTO_RS256_RUNTIME_BUILTIN_IMPLEMENTATION, automaticFallback: false }),
22
+ ES256: Object.freeze({
23
+ algorithm: 'ES256',
24
+ realization: 'runtime-builtin',
25
+ implementation: CRYPTO_ES256_RUNTIME_BUILTIN_IMPLEMENTATION,
26
+ automaticFallback: false
27
+ })
28
+ });
29
+ const FASTLY_JAVASCRIPT_JWT_REALIZATION =
30
+ FASTLY_JAVASCRIPT_JWT_REALIZATIONS.HS256;
31
+
32
+ let runtimeModules;
33
+
34
+ function loadRuntimeModules() {
35
+ if (!runtimeModules) {
36
+ runtimeModules = Promise.all([
37
+ import('@pulse-compute/jwt/provider'),
38
+ import('@pulse-compute/crypto')
39
+ ]).then(([jwtProvider, cryptoProvider]) => Object.freeze({
40
+ jwtProvider,
41
+ cryptoProvider
42
+ }));
43
+ }
44
+ return runtimeModules;
45
+ }
46
+
47
+ function fastlyWallClock() {
48
+ return Object.freeze({
49
+ unixEpochSeconds: Date.now() / 1000,
50
+ trusted: true
51
+ });
52
+ }
53
+
54
+ function assertRequestActive(execution, jwtProvider) {
55
+ if (execution && execution.signal && execution.signal.aborted) {
56
+ throw jwtProvider.jwtError('PULSE_JWT_OPERATION_FAILED', {
57
+ category: 'request-cancelled',
58
+ automaticFallback: false
59
+ });
60
+ }
61
+ }
62
+
63
+ function assertRuntimeBuiltin(cryptoProvider, jwtProvider) {
64
+ if (
65
+ !cryptoProvider
66
+ || cryptoProvider.CRYPTO_RUNTIME_BUILTIN_IMPLEMENTATION !== CRYPTO_RUNTIME_BUILTIN_IMPLEMENTATION
67
+ || !cryptoProvider.crypto
68
+ || !cryptoProvider.crypto.mac
69
+ || typeof cryptoProvider.crypto.mac.verify !== 'function'
70
+ || !cryptoProvider.crypto.signature
71
+ || typeof cryptoProvider.crypto.signature.verify !== 'function'
72
+ ) {
73
+ throw jwtProvider.jwtError('PULSE_JWT_TARGET_UNSUPPORTED', {
74
+ category: 'crypto-realization',
75
+ realization: FASTLY_JAVASCRIPT_JWT_REALIZATION.realization,
76
+ implementation: FASTLY_JAVASCRIPT_JWT_REALIZATION.implementation,
77
+ automaticFallback: false
78
+ });
79
+ }
80
+ return cryptoProvider.crypto;
81
+ }
82
+
83
+ function createFastlyJavascriptJwtVerify(options = {}) {
84
+ const captureWallClock = options.captureWallClock || fastlyWallClock;
85
+ const secretLookup = options.secretLookup;
86
+ return async function verifyFastlyJavascriptJwt(effect, execution) {
87
+ const { jwtProvider, cryptoProvider } = await loadRuntimeModules();
88
+ assertRequestActive(execution, jwtProvider);
89
+ const signing = effect.operation === 'sign';
90
+ const input = signing ? normalizeJwtSignEffect(effect) : normalizeJwtVerifyEffect(effect);
91
+ if (!signing && execution && typeof execution.registerRedactionValue === 'function') {
92
+ execution.registerRedactionValue(input.token);
93
+ }
94
+ assertRequestActive(execution, jwtProvider);
95
+ const selectedCrypto = signing
96
+ ? (input.options.algorithm === 'RS256'
97
+ ? require('@pulse-compute/crypto/provider').bindJavascriptRs256Signer()
98
+ : input.options.algorithm === 'ES256'
99
+ ? require('@pulse-compute/crypto/provider').bindJavascriptEs256Signer()
100
+ : require('@pulse-compute/crypto/provider').bindJavascriptDigestMac(['HMAC-SHA256']))
101
+ : assertRuntimeBuiltin(cryptoProvider, jwtProvider);
102
+ const host = {
103
+ async captureWallClock() {
104
+ assertRequestActive(execution, jwtProvider);
105
+ const captured = await captureWallClock();
106
+ assertRequestActive(execution, jwtProvider);
107
+ return captured;
108
+ },
109
+ resolveSecret: typeof secretLookup === 'function'
110
+ ? async (binding) => {
111
+ assertRequestActive(execution, jwtProvider);
112
+ const secret = await secretLookup(binding, execution);
113
+ assertRequestActive(execution, jwtProvider);
114
+ return secret;
115
+ }
116
+ : undefined,
117
+ registerSensitiveValue(value) {
118
+ if (execution && typeof execution.registerRedactionValue === 'function') {
119
+ execution.registerRedactionValue(value);
120
+ }
121
+ }
122
+ };
123
+ if (execution && typeof execution.validateSchemaValue === 'function') {
124
+ host.validateClaims = async (schemaId, claims, context) => {
125
+ assertRequestActive(execution, jwtProvider);
126
+ const result = await execution.validateSchemaValue(schemaId, claims, context);
127
+ assertRequestActive(execution, jwtProvider);
128
+ return result;
129
+ };
130
+ }
131
+ const result = await (signing ? jwtProvider.signJwtWithCrypto : jwtProvider.verifyJwtWithCrypto)(
132
+ input,
133
+ Object.freeze(host),
134
+ selectedCrypto
135
+ );
136
+ assertRequestActive(execution, jwtProvider);
137
+ return result;
138
+ };
139
+ }
140
+
141
+ module.exports = Object.freeze({
142
+ FASTLY_JAVASCRIPT_JWT_VERIFIER_VERSION,
143
+ FASTLY_JAVASCRIPT_JWT_REALIZATION,
144
+ FASTLY_JAVASCRIPT_JWT_REALIZATIONS,
145
+ createFastlyJavascriptJwtVerify,
146
+ fastlyWallClock
147
+ });
@@ -0,0 +1,55 @@
1
+ 'use strict';
2
+
3
+ const runtimeHost = require('@pulse-compute/runtime/host');
4
+ const { executeFastlyJavascriptApplication } = require('./runtime-host.js');
5
+
6
+ const FASTLY_JAVASCRIPT_LIFECYCLE_VERSION = 'pulse.fastly-javascript-lifecycle.v1';
7
+
8
+ function requestForInput(input) {
9
+ if (input instanceof Request) return input;
10
+ if (input && input.request instanceof Request) return input.request;
11
+ throw new TypeError('Pulse Fastly JavaScript lifecycle requires a FetchEvent or Web Request.');
12
+ }
13
+
14
+ function safeFailureResponse() {
15
+ return new Response('Internal Server Error', {
16
+ status: 500,
17
+ headers: { 'content-type': 'text/plain; charset=utf-8' }
18
+ });
19
+ }
20
+
21
+ function createFastlyJavascriptHandler(application, options = {}) {
22
+ const normalizedApplication = runtimeHost.normalizeApplication(application);
23
+ return async function pulseFastlyJavascriptHandler(input) {
24
+ try {
25
+ const request = requestForInput(input);
26
+ return await executeFastlyJavascriptApplication(normalizedApplication, request, {
27
+ ...options,
28
+ signal: options.signal || request.signal
29
+ });
30
+ } catch (error) {
31
+ if (typeof options.onError === 'function') {
32
+ try { options.onError(error); } catch (_) { /* Lifecycle evidence is best effort. */ }
33
+ }
34
+ return safeFailureResponse();
35
+ }
36
+ };
37
+ }
38
+
39
+ function installFastlyJavascriptApplication(application, options = {}) {
40
+ const handler = createFastlyJavascriptHandler(application, options);
41
+ const addEventListener = options.addEventListener || globalThis.addEventListener;
42
+ if (typeof addEventListener !== 'function') {
43
+ throw new TypeError('Pulse Fastly JavaScript lifecycle requires addEventListener.');
44
+ }
45
+ addEventListener('fetch', (event) => event.respondWith(handler(event)));
46
+ return handler;
47
+ }
48
+
49
+ module.exports = Object.freeze({
50
+ FASTLY_JAVASCRIPT_LIFECYCLE_VERSION,
51
+ createFastlyJavascriptHandler,
52
+ installFastlyJavascriptApplication,
53
+ requestForInput,
54
+ safeFailureResponse
55
+ });
@@ -0,0 +1,138 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const vm = require('node:vm');
5
+ const {
6
+ bundleFastlyJavascriptApplication
7
+ } = require('./source-package.js');
8
+ const {
9
+ assertFastlyJavascriptApplication
10
+ } = require('./runtime-host.js');
11
+
12
+ const FASTLY_JAVASCRIPT_LOCAL_LOADER_VERSION = 'pulse.fastly-javascript-local-loader.v1';
13
+ const LOCAL_EXTERNALS = Object.freeze({
14
+ '@pulse-compute/runtime': require('@pulse-compute/runtime'),
15
+ '@pulse-compute/runtime/host': require('@pulse-compute/runtime/host'),
16
+ '@pulse-compute/runtime/package': require('@pulse-compute/runtime/package')
17
+ });
18
+
19
+ class PulseFastlyJavascriptLocalLoaderError extends Error {
20
+ constructor(code, message, detail = {}) {
21
+ super(message);
22
+ this.name = 'PulseFastlyJavascriptLocalLoaderError';
23
+ this.code = code;
24
+ this.detail = Object.freeze({ ...detail });
25
+ }
26
+ }
27
+
28
+ function moduleDefault(exports) {
29
+ return exports && Object.prototype.hasOwnProperty.call(exports, 'default')
30
+ ? exports.default
31
+ : exports;
32
+ }
33
+
34
+ function loadFastlyJavascriptLocalApplication(plan, options = {}) {
35
+ if (!plan || plan.provider !== 'fastly' || plan.target !== 'javascript' || plan.loadable !== true) {
36
+ throw new PulseFastlyJavascriptLocalLoaderError(
37
+ 'PULSE_FASTLY_JAVASCRIPT_LOCAL_PLAN_UNAVAILABLE',
38
+ 'Fastly JavaScript provider emulation requires a loadable Fastly JavaScript application plan.',
39
+ {
40
+ provider: plan && plan.provider,
41
+ target: plan && plan.target,
42
+ blockers: plan && plan.blockers || [],
43
+ automaticFallback: false
44
+ }
45
+ );
46
+ }
47
+
48
+ const projectRoot = path.resolve(options.projectRoot || options.workspaceRoot || '.');
49
+ const closure = bundleFastlyJavascriptApplication(
50
+ plan,
51
+ projectRoot,
52
+ options.schemaBundle,
53
+ { format: 'cjs', externalRuntime: true }
54
+ );
55
+ const module = { exports: {} };
56
+ const externalRequire = (request) => {
57
+ if (Object.prototype.hasOwnProperty.call(LOCAL_EXTERNALS, request)) {
58
+ return LOCAL_EXTERNALS[request];
59
+ }
60
+ throw new PulseFastlyJavascriptLocalLoaderError(
61
+ 'PULSE_FASTLY_JAVASCRIPT_LOCAL_EXTERNAL_UNAVAILABLE',
62
+ `Bundled Fastly JavaScript provider emulation cannot load external module ${String(request)}.`,
63
+ { request: String(request), automaticFallback: false }
64
+ );
65
+ };
66
+
67
+ try {
68
+ const wrapper = new vm.Script(
69
+ `(function(module,exports,require,__filename,__dirname){\n${closure.source}\n})`,
70
+ { filename: 'pulse-fastly-javascript-local-application.cjs' }
71
+ ).runInThisContext();
72
+ wrapper(
73
+ module,
74
+ module.exports,
75
+ externalRequire,
76
+ path.join(projectRoot, 'pulse-fastly-javascript-local-application.cjs'),
77
+ projectRoot
78
+ );
79
+ } catch (error) {
80
+ if (error instanceof PulseFastlyJavascriptLocalLoaderError) throw error;
81
+ throw new PulseFastlyJavascriptLocalLoaderError(
82
+ error && error.code || 'PULSE_FASTLY_JAVASCRIPT_LOCAL_BUNDLE_LOAD_FAILED',
83
+ 'The bundled Fastly JavaScript application could not be loaded for provider emulation.',
84
+ {
85
+ causeName: error && error.name,
86
+ causeCode: error && error.code,
87
+ planHash: plan.planHash,
88
+ graphHash: plan.graph && plan.graph.graphHash,
89
+ automaticFallback: false
90
+ }
91
+ );
92
+ }
93
+
94
+ let application;
95
+ try {
96
+ application = assertFastlyJavascriptApplication(moduleDefault(module.exports));
97
+ } catch (error) {
98
+ throw new PulseFastlyJavascriptLocalLoaderError(
99
+ error && error.code || 'PULSE_FASTLY_JAVASCRIPT_APPLICATION_EXPORT_INVALID',
100
+ error && error.message || 'The bundled Fastly JavaScript application export is invalid.',
101
+ {
102
+ planHash: plan.planHash,
103
+ graphHash: plan.graph && plan.graph.graphHash,
104
+ automaticFallback: false
105
+ }
106
+ );
107
+ }
108
+
109
+ return Object.freeze({
110
+ version: FASTLY_JAVASCRIPT_LOCAL_LOADER_VERSION,
111
+ mode: 'provider-emulation',
112
+ provider: 'fastly',
113
+ target: 'javascript',
114
+ providerReality: false,
115
+ realityRunner: 'fastly compute serve',
116
+ automaticFallback: false,
117
+ plan,
118
+ application,
119
+ schemaCodecs: module.exports && module.exports.schemaCodecs || null,
120
+ entry: plan.workspace.entry,
121
+ modulesLoaded: plan.graph.modules.length,
122
+ packagesLoaded: Object.freeze(plan.packages
123
+ .filter((entry) => entry.status !== 'unavailable')
124
+ .map((entry) => entry.packageName)
125
+ .sort()),
126
+ closure: Object.freeze({
127
+ format: closure.format,
128
+ sourceHash: closure.sourceHash,
129
+ inputs: closure.inputs
130
+ })
131
+ });
132
+ }
133
+
134
+ module.exports = Object.freeze({
135
+ FASTLY_JAVASCRIPT_LOCAL_LOADER_VERSION,
136
+ PulseFastlyJavascriptLocalLoaderError,
137
+ loadFastlyJavascriptLocalApplication
138
+ });
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+
3
+ const { normalizeFastlyLogEndpoint, MAX_LOG_BYTES } = require('../toolchain/logging.js');
4
+
5
+ const FASTLY_JAVASCRIPT_LOGGING_VERSION = 'pulse.fastly-javascript-logging.v1';
6
+
7
+ function createFastlyJavascriptLogEmitter(options = {}) {
8
+ const endpoint = normalizeFastlyLogEndpoint(options.logEndpoint);
9
+ const output = options.console || globalThis.console;
10
+ const methods = Object.freeze({
11
+ 1: 'error',
12
+ 2: 'warn',
13
+ 3: 'info',
14
+ 4: 'debug'
15
+ });
16
+ return function fastlyJavascriptLog(level, message, event) {
17
+ const method = methods[Number(level)] || 'log';
18
+ const record = `[pulse][${event && event.name || method}] ${String(message)}`;
19
+ if (endpoint !== undefined) {
20
+ if (new TextEncoder().encode(record).byteLength > MAX_LOG_BYTES) return;
21
+ try { new options.Logger(endpoint).log(record); } catch { /* Best effort, no fallback or retry. */ }
22
+ return;
23
+ }
24
+ if (!output) return;
25
+ const write = typeof output[method] === 'function'
26
+ ? output[method]
27
+ : (typeof output.log === 'function' ? output.log : undefined);
28
+ try { if (write) write.call(output, record); } catch { /* Logging does not change the HTTP outcome. */ }
29
+ };
30
+ }
31
+
32
+ module.exports = Object.freeze({
33
+ FASTLY_JAVASCRIPT_LOGGING_VERSION,
34
+ createFastlyJavascriptLogEmitter
35
+ });