@pulse-compute/provider-fastly 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 (69) hide show
  1. package/README.md +53 -1
  2. package/dist/config-schema.json +77 -0
  3. package/dist/index.d.ts +61 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +59 -0
  6. package/dist/index.js.map +1 -0
  7. package/package.json +88 -5
  8. package/src/build/canonical-target.js +436 -0
  9. package/src/build/entities-native.js +564 -0
  10. package/src/build/fixed-memory-runtime.as.ts +148 -0
  11. package/src/build/kv-native.as.ts +229 -0
  12. package/src/build/kv-native.js +20 -0
  13. package/src/build/native-http-effects.js +871 -0
  14. package/src/build/native-http-shell.js +703 -0
  15. package/src/build/native-platform-capabilities.js +2196 -0
  16. package/src/build/s3-native.as.ts +215 -0
  17. package/src/build/s3-native.js +30 -0
  18. package/src/compiler/assets-package-out-parity-proof.js +338 -0
  19. package/src/compiler/assets-provider-proof.js +499 -0
  20. package/src/compiler/config-provider-resolution.js +573 -0
  21. package/src/compiler/fastly-adapter.js +564 -0
  22. package/src/compiler/fastly-command-entry.js +390 -0
  23. package/src/compiler/fastly-config-secret-serve.js +323 -0
  24. package/src/compiler/fastly-hostcall-binding.js +598 -0
  25. package/src/compiler/fastly-readiness.js +477 -0
  26. package/src/compiler/index.js +40 -0
  27. package/src/compiler/lifecycle-parity-contract.js +70 -0
  28. package/src/compiler/lifecycle-parity-proof.js +260 -0
  29. package/src/compiler/route-handler-effect-proof.js +255 -0
  30. package/src/compiler/runtime-provider-kv.js +237 -0
  31. package/src/compiler/runtime-provider-shape.js +109 -0
  32. package/src/config-api.js +63 -0
  33. package/src/config-schema.d.ts +13 -0
  34. package/src/config-schema.js +27 -0
  35. package/src/config-schema.json +77 -0
  36. package/src/index.js +3 -0
  37. package/src/index.ts +118 -0
  38. package/src/javascript/assets-adapter.js +122 -0
  39. package/src/javascript/bindings-adapter.js +237 -0
  40. package/src/javascript/errors.js +26 -0
  41. package/src/javascript/fetch-adapter.js +93 -0
  42. package/src/javascript/grip-broadcast.js +261 -0
  43. package/src/javascript/jwt-verifier.js +137 -0
  44. package/src/javascript/lifecycle.js +55 -0
  45. package/src/javascript/local-loader.js +138 -0
  46. package/src/javascript/logging.js +26 -0
  47. package/src/javascript/package-effects.js +68 -0
  48. package/src/javascript/runtime-host.js +108 -0
  49. package/src/javascript/sha256.js +128 -0
  50. package/src/javascript/source-package.js +776 -0
  51. package/src/javascript/support.js +142 -0
  52. package/src/javascript/target-support-policy.js +264 -0
  53. package/src/javascript/target.js +73 -0
  54. package/src/javascript/test-runtime.js +407 -0
  55. package/src/provider-contract.js +79 -0
  56. package/src/runtime/assets-provider.js +464 -0
  57. package/src/runtime/canonical-api-runtime.js +601 -0
  58. package/src/runtime/index.js +28 -0
  59. package/src/runtime/provider-primitives-contract.js +272 -0
  60. package/src/runtime/provider-primitives.js +82 -0
  61. package/src/runtime/route-handler-effects.js +507 -0
  62. package/src/testing/conditional-kv-host.js +109 -0
  63. package/src/testing/fastly-cli.js +538 -0
  64. package/src/testing/native-http-effects-host.js +390 -0
  65. package/src/testing/native-http-shell-host.js +190 -0
  66. package/src/testing/native-platform-capabilities-host.js +676 -0
  67. package/src/toolchain/config.js +131 -0
  68. package/src/toolchain/index.js +345 -0
  69. package/src/toolchain/s3.js +26 -0
@@ -0,0 +1,323 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const { getDefaultArtifactsDir } = require('@pulse-compute/wasm-build-support/artifacts-dir');
7
+ const { createPulseRuntimeConfigProvider, buildConfigProviderResolution } = require('./config-provider-resolution.js');
8
+ const { PACKAGE_VERSION, normalizeArtifact, normalizeDiagnostic } = loadContractsDiagnostics();
9
+
10
+
11
+ function loadContractsDiagnostics() {
12
+ try {
13
+ return require('@pulse-compute/wasm-contracts/diagnostics');
14
+ } catch (error) {
15
+ if (error && error.code === 'MODULE_NOT_FOUND' && String(error.message).includes('@pulse-compute/wasm-contracts')) {
16
+ return require('../../../../wasm/packages/contracts/src/diagnostics.js');
17
+ }
18
+ throw error;
19
+ }
20
+ }
21
+
22
+ const PHASE = '13E';
23
+ const CONTRACT_VERSION = 'pulsewasm.fastly-config-secret-serve.v1';
24
+ const RUNTIME_VERSION = 'pulsewasm.fastly-config-secret-runtime.v1';
25
+ const READINESS_VERSION = 'pulsewasm.fastly-config-secret-readiness.v1';
26
+ const SMOKE_VERSION = 'pulsewasm.fastly-config-secret-smoke.v1';
27
+
28
+ function sha256Text(text) { return crypto.createHash('sha256').update(String(text || ''), 'utf8').digest('hex'); }
29
+ function sha256Buffer(buffer) { return crypto.createHash('sha256').update(buffer).digest('hex'); }
30
+ function makeFile(file, text) { return { file, text, bytes: Buffer.byteLength(text, 'utf8'), sha256: sha256Text(text) }; }
31
+ function diag(code, message, hint, details) {
32
+ return normalizeDiagnostic({
33
+ phase: 'fastly-config-secret-serve',
34
+ severity: 'error',
35
+ code,
36
+ message,
37
+ hint,
38
+ details,
39
+ loc: { file: '<fastly-config-secret-serve>' }
40
+ });
41
+ }
42
+ function stripSecretFields(value) {
43
+ if (Array.isArray(value)) return value.map(stripSecretFields);
44
+ if (!value || typeof value !== 'object') return value;
45
+ const out = {};
46
+ for (const [key, child] of Object.entries(value)) {
47
+ if (key === 'Authorization' && typeof child === 'string') {
48
+ out[key] = '<redacted>';
49
+ } else {
50
+ out[key] = stripSecretFields(child);
51
+ }
52
+ }
53
+ return out;
54
+ }
55
+ function buildGeneratedModuleSource() {
56
+ return `/* Generated by PulseWasm.
57
+ * Fastly config/secret serve proof wrapper.
58
+ * Resolves symbolic config/secret refs before constructing the compiled-Wasm runtime.
59
+ */
60
+ 'use strict';
61
+
62
+ const path = require('node:path');
63
+ const { createPulseRuntimeConfigProvider } = require('../host/config-provider.cjs');
64
+ const { createPulseWasmCompiledHostRuntime } = require('../host/compiled-wasm-host-runtime.cjs');
65
+ const { createFastlyStreamHeaderAdapter } = require('../fastly/fastly-adapter.cjs');
66
+
67
+ function createFastlyConfigSecretServe(options = {}) {
68
+ const provider = createPulseRuntimeConfigProvider({
69
+ resolvedConfig: options.resolvedConfig,
70
+ configValues: options.configValues || {},
71
+ secretValues: options.secretValues || {}
72
+ });
73
+ const resolvedRuntime = provider.resolveRuntime();
74
+ const runtime = createPulseWasmCompiledHostRuntime({
75
+ wasmPath: options.wasmPath,
76
+ deploymentPosture: options.deploymentPosture || { engine: 'wasm' },
77
+ hostResponses: options.hostResponses || {}
78
+ });
79
+ const adapter = createFastlyStreamHeaderAdapter({ runtime });
80
+ return {
81
+ profile: provider.profile,
82
+ provider,
83
+ resolvedRuntime,
84
+ runtime,
85
+ adapter,
86
+ executeRequest(request) {
87
+ return adapter.executeRequest(request);
88
+ },
89
+ previewRuntime() {
90
+ return provider.previewRuntime();
91
+ }
92
+ };
93
+ }
94
+
95
+ module.exports = { createFastlyConfigSecretServe };
96
+ `;
97
+ }
98
+ function buildMarkdown(contract, runtimeArtifact, readiness, smoke) {
99
+ return [
100
+ '# PulseWasm Fastly Config / Secret Serve Proof',
101
+ '',
102
+ `Generated by: \`${contract.generatedBy}\``,
103
+ '',
104
+ '## What this proves',
105
+ '',
106
+ '- `$config` / `$secret` refs are resolved before request execution.',
107
+ '- Resolved runtime config stays outside the Wasm binary.',
108
+ '- The compiled-Wasm runtime still serves requests through the Fastly-shaped adapter.',
109
+ '- Missing config and missing secret values fail before serve.',
110
+ '',
111
+ '## Current status',
112
+ '',
113
+ `- profile: \`${contract.profile || '<none>'}\``,
114
+ `- config refs: ${contract.summary.configRefs}`,
115
+ `- secret refs: ${contract.summary.secretRefs}`,
116
+ `- serve-like smoke executed: ${smoke.executed}`,
117
+ `- failed checks: ${smoke.failedChecks}`,
118
+ `- manual Fastly serve still external: ${!readiness.realFastlyServeExecuted}`,
119
+ ''
120
+ ].join('\n');
121
+ }
122
+
123
+ function buildFastlyConfigSecretServe(options = {}) {
124
+ const generatedBy = options.generatedBy || PACKAGE_VERSION;
125
+ const cwd = options.cwd || process.cwd();
126
+ const outDir = options.outDir ? path.resolve(cwd, options.outDir) : getDefaultArtifactsDir(cwd);
127
+ const resolvedConfig = options.resolvedConfig;
128
+ const configValues = options.configValues || {};
129
+ const secretValues = options.secretValues || {};
130
+ const diagnostics = [];
131
+ const providerBuild = buildConfigProviderResolution({
132
+ resolvedConfig,
133
+ cwd,
134
+ generatedBy,
135
+ configValues,
136
+ secretValues
137
+ });
138
+
139
+ const generatedModuleFile = buildGeneratedModuleSource();
140
+ const generatedModulePath = path.join(outDir, 'generated', 'fastly', 'fastly-config-secret-serve.cjs');
141
+ fs.mkdirSync(path.dirname(generatedModulePath), { recursive: true });
142
+ fs.writeFileSync(generatedModulePath, generatedModuleFile);
143
+
144
+ const wasmPath = path.join(outDir, 'generated', 'compiled-wasm-runtime', 'pulsewasm-compiled-runtime.wasm');
145
+ if (!fs.existsSync(wasmPath)) {
146
+ diagnostics.push(diag(
147
+ 'PULSEWASM_FASTLY_CONFIG_SECRET_WASM_MISSING',
148
+ 'Compiled Wasm runtime candidate is missing for the Fastly config/secret proof.',
149
+ 'Generate the compiled runtime before running the Fastly config/secret serve proof.',
150
+ { wasmPath: path.relative(cwd, wasmPath), artifactDir: path.relative(cwd, outDir) }
151
+ ));
152
+ }
153
+
154
+ const hostRuntimeModulePath = path.join(outDir, 'generated', 'host', 'compiled-wasm-host-runtime.cjs');
155
+ const fastlyAdapterModulePath = path.join(outDir, 'generated', 'fastly', 'fastly-adapter.cjs');
156
+ const configProviderModulePath = path.join(outDir, 'generated', 'host', 'config-provider.cjs');
157
+ for (const [filePath, code] of [
158
+ [path.join(outDir, 'generated', 'host', 'config-provider.cjs'), providerBuild.files.find((f) => f.file.endsWith('config-provider.cjs')).text]
159
+ ]) {
160
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
161
+ fs.writeFileSync(filePath, code);
162
+ }
163
+
164
+ const contract = {
165
+ version: CONTRACT_VERSION,
166
+ generatedBy,
167
+ phase: PHASE,
168
+ status: diagnostics.length ? 'error' : 'ok',
169
+ profile: resolvedConfig.profile || null,
170
+ scope: [
171
+ 'resolve $config / $secret before request execution',
172
+ 'keep resolved values outside the Wasm binary',
173
+ 'drive the compiled-Wasm runtime through the Fastly-shaped adapter after provider resolution'
174
+ ],
175
+ summary: {
176
+ configRefs: providerBuild.artifact.summary.configRefs,
177
+ secretRefs: providerBuild.artifact.summary.secretRefs,
178
+ diagnostics: diagnostics.length
179
+ },
180
+ readiness: {
181
+ providerResolutionRequiredBeforeServe: true,
182
+ runtimeValuesEmbeddedIntoWasm: false,
183
+ manualFastlyServeExternal: true
184
+ }
185
+ };
186
+
187
+ const runtimeArtifact = {
188
+ version: RUNTIME_VERSION,
189
+ generatedBy,
190
+ phase: PHASE,
191
+ status: diagnostics.length ? 'error' : 'ok',
192
+ profile: resolvedConfig.profile || null,
193
+ resolvedRuntimePreview: stripSecretFields(providerBuild.resolution.previewRuntime),
194
+ providerMap: providerBuild.providerMap.fastly,
195
+ generatedModule: {
196
+ file: 'generated/fastly/fastly-config-secret-serve.cjs',
197
+ bytes: Buffer.byteLength(generatedModuleFile, 'utf8'),
198
+ sha256: sha256Text(generatedModuleFile)
199
+ }
200
+ };
201
+
202
+ const smoke = {
203
+ version: SMOKE_VERSION,
204
+ generatedBy,
205
+ phase: PHASE,
206
+ executed: false,
207
+ failedChecks: 0,
208
+ checks: [],
209
+ wasmContainsSecretBytes: false,
210
+ realFastlyServeExecuted: false
211
+ };
212
+
213
+ let smokeFailures = 0;
214
+ try {
215
+ const { createPulseRuntimeConfigProvider } = require(configProviderModulePath);
216
+ const { createPulseWasmCompiledHostRuntime } = require(hostRuntimeModulePath);
217
+ const { createFastlyStreamHeaderAdapter } = require(fastlyAdapterModulePath);
218
+ const provider = createPulseRuntimeConfigProvider({ resolvedConfig, configValues, secretValues });
219
+ const resolvedRuntime = provider.resolveRuntime();
220
+ smoke.executed = true;
221
+
222
+ const runtime = createPulseWasmCompiledHostRuntime({
223
+ wasmPath,
224
+ deploymentPosture: { engine: 'wasm' }
225
+ });
226
+ const adapter = createFastlyStreamHeaderAdapter({ runtime });
227
+
228
+ const extensionResult = adapter.executeRequest({
229
+ method: 'GET',
230
+ path: '/extension',
231
+ rawHeaders: ['X-Test', '1', 'X-Test', '2']
232
+ });
233
+ smoke.checks.push({ name: 'extension_status', expected: 220, actual: extensionResult.status });
234
+ if (extensionResult.status !== 220) smokeFailures += 1;
235
+
236
+ const streamResult = adapter.executeRequest({
237
+ method: 'GET',
238
+ path: '/stream',
239
+ rawHeaders: ['X-Test', '1']
240
+ });
241
+ smoke.checks.push({ name: 'stream_status', expected: 206, actual: streamResult.status });
242
+ if (streamResult.status !== 206) smokeFailures += 1;
243
+ smoke.checks.push({ name: 'stream_body', expected: 'asset:42', actual: streamResult.body });
244
+ if (streamResult.body !== 'asset:42') smokeFailures += 1;
245
+
246
+ smoke.checks.push({ name: 'config_resolved', expected: 'https://users.example.test', actual: resolvedRuntime.capabilities.backends.usersApi.baseUrl });
247
+ if (resolvedRuntime.capabilities.backends.usersApi.baseUrl !== 'https://users.example.test') smokeFailures += 1;
248
+ smoke.checks.push({ name: 'secret_resolved', expected: 'shh-token', actual: resolvedRuntime.capabilities.backends.usersApi.headers.Authorization });
249
+ if (resolvedRuntime.capabilities.backends.usersApi.headers.Authorization !== 'shh-token') smokeFailures += 1;
250
+
251
+ const wasmBytes = fs.readFileSync(wasmPath);
252
+ const secretNeedles = Object.values(secretValues).map((v) => Buffer.from(String(v), 'utf8'));
253
+ smoke.wasmContainsSecretBytes = secretNeedles.some((needle) => needle.length > 0 && wasmBytes.includes(needle));
254
+ smoke.checks.push({ name: 'secret_not_embedded_in_wasm', expected: false, actual: smoke.wasmContainsSecretBytes });
255
+ if (smoke.wasmContainsSecretBytes) smokeFailures += 1;
256
+
257
+ try {
258
+ createPulseRuntimeConfigProvider({ resolvedConfig, configValues: {}, secretValues }).resolveRuntime();
259
+ smoke.checks.push({ name: 'missing_config_caught', expected: true, actual: false });
260
+ smokeFailures += 1;
261
+ } catch (error) {
262
+ const ok = error.code === 'PULSEWASM_CONFIG_PROVIDER_MISSING_CONFIG';
263
+ smoke.checks.push({ name: 'missing_config_caught', expected: true, actual: ok });
264
+ if (!ok) smokeFailures += 1;
265
+ }
266
+
267
+ try {
268
+ createPulseRuntimeConfigProvider({ resolvedConfig, configValues, secretValues: {} }).resolveRuntime();
269
+ smoke.checks.push({ name: 'missing_secret_caught', expected: true, actual: false });
270
+ smokeFailures += 1;
271
+ } catch (error) {
272
+ const ok = error.code === 'PULSEWASM_CONFIG_PROVIDER_MISSING_SECRET';
273
+ smoke.checks.push({ name: 'missing_secret_caught', expected: true, actual: ok });
274
+ if (!ok) smokeFailures += 1;
275
+ }
276
+ } catch (error) {
277
+ diagnostics.push(diag(
278
+ error.code || 'PULSEWASM_FASTLY_CONFIG_SECRET_SMOKE_ERROR',
279
+ error.message,
280
+ 'Inspect the generated Fastly config/secret wrapper and provider mapping.',
281
+ { stack: String(error.stack || error) }
282
+ ));
283
+ smoke.failedChecks += 1;
284
+ }
285
+ smoke.failedChecks += smokeFailures;
286
+
287
+ const readiness = {
288
+ version: READINESS_VERSION,
289
+ generatedBy,
290
+ phase: PHASE,
291
+ readyForManualFastlyServeWithStores: !diagnostics.length && smoke.failedChecks === 0,
292
+ realFastlyServeExecuted: false,
293
+ providerResolutionRequiredBeforeServe: true,
294
+ runtimeValuesEmbeddedIntoWasm: smoke.wasmContainsSecretBytes ? true : false,
295
+ summary: {
296
+ providerResolved: !diagnostics.length,
297
+ smokeExecuted: smoke.executed,
298
+ failedChecks: smoke.failedChecks
299
+ }
300
+ };
301
+
302
+ const files = [
303
+ makeFile('generated/fastly/fastly-config-secret-serve.cjs', generatedModuleFile),
304
+ makeFile('generated/host/fastly-config-secret-serve.md', buildMarkdown(contract, runtimeArtifact, readiness, smoke))
305
+ ];
306
+
307
+ return {
308
+ contract: normalizeArtifact(contract, cwd),
309
+ runtimeArtifact: normalizeArtifact(runtimeArtifact, cwd),
310
+ readiness: normalizeArtifact(readiness, cwd),
311
+ smoke: normalizeArtifact(smoke, cwd),
312
+ diagnostics,
313
+ files
314
+ };
315
+ }
316
+
317
+ module.exports = {
318
+ CONTRACT_VERSION,
319
+ RUNTIME_VERSION,
320
+ READINESS_VERSION,
321
+ SMOKE_VERSION,
322
+ buildFastlyConfigSecretServe
323
+ };