@pulse-compute/provider-fastly 1.0.0-beta.1 → 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 (68) hide show
  1. package/README.md +7 -6
  2. package/dist/config-schema.json +30 -0
  3. package/dist/index.d.ts +16 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +8 -0
  6. package/dist/index.js.map +1 -1
  7. package/package.json +11 -10
  8. package/src/build/canonical-target.js +35 -2
  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 +6 -11
  13. package/src/build/kv-native.as.ts +331 -0
  14. package/src/build/kv-native.js +27 -0
  15. package/src/build/native-application-errors.js +159 -0
  16. package/src/build/native-binary-resources.js +30 -0
  17. package/src/build/native-body-forwarding.as.ts +341 -0
  18. package/src/build/native-body-forwarding.js +69 -0
  19. package/src/build/native-dynamic-backends.as.ts +115 -0
  20. package/src/build/native-dynamic-backends.js +52 -0
  21. package/src/build/native-fetch-deadlines.as.ts +164 -0
  22. package/src/build/native-fetch-deadlines.js +27 -0
  23. package/src/build/native-fetch-errors.js +38 -0
  24. package/src/build/native-handle-ownership.js +127 -0
  25. package/src/build/native-header-lookup.js +50 -0
  26. package/src/build/native-http-effects.js +155 -32
  27. package/src/build/native-platform-capabilities.js +755 -210
  28. package/src/build/native-request-body.js +44 -0
  29. package/src/build/native-request-headers.js +93 -0
  30. package/src/build/native-send-error-detail.as.ts +12 -0
  31. package/src/build/native-stdio.js +45 -0
  32. package/src/build/native-string-values.js +143 -0
  33. package/src/build/native-transport-details.as.ts +84 -0
  34. package/src/build/native-value-budget.js +90 -0
  35. package/src/build/package-native-application.js +114 -0
  36. package/src/build/request-budget.as.ts +50 -0
  37. package/src/build/request-budget.js +47 -0
  38. package/src/build/s3-body.as.ts +97 -0
  39. package/src/build/s3-native.as.ts +228 -0
  40. package/src/build/s3-native.js +30 -0
  41. package/src/build/schema-admission.js +83 -0
  42. package/src/build/schema-nested-json.js +93 -0
  43. package/src/build/schema-scalar-record.js +41 -0
  44. package/src/build/time-native.js +49 -0
  45. package/src/build/wasm-budget.js +28 -0
  46. package/src/config-api.js +6 -0
  47. package/src/config-schema.json +30 -0
  48. package/src/index.ts +25 -0
  49. package/src/javascript/jwt-verifier.js +14 -4
  50. package/src/javascript/logging.js +11 -2
  51. package/src/javascript/package-effects.js +5 -2
  52. package/src/javascript/runtime-host.js +7 -3
  53. package/src/javascript/source-package.js +4 -2
  54. package/src/javascript/target-support-policy.js +20 -3
  55. package/src/javascript/target.js +17 -1
  56. package/src/javascript/test-runtime.js +1 -0
  57. package/src/provider-contract.js +22 -6
  58. package/src/runtime/canonical-api-runtime.js +6 -0
  59. package/src/testing/conditional-kv-host.js +155 -0
  60. package/src/testing/native-dynamic-backends-host.js +39 -0
  61. package/src/testing/native-http-effects-host.js +9 -1
  62. package/src/testing/native-platform-capabilities-host.js +349 -24
  63. package/src/toolchain/body-forwarding.js +25 -0
  64. package/src/toolchain/config.js +16 -0
  65. package/src/toolchain/index.js +48 -5
  66. package/src/toolchain/logging.js +18 -0
  67. package/src/toolchain/native-execution.js +78 -0
  68. package/src/toolchain/s3.js +26 -0
@@ -1,4 +1,6 @@
1
1
  'use strict';
2
+ const { normalizeFastlyMaxWasmBytes } = require('../build/wasm-budget.js');
3
+ const { normalizeFastlyS3 } = require('./s3.js');
2
4
 
3
5
  function providerConfigError(code, message, detail) {
4
6
  const error = new TypeError(message);
@@ -32,11 +34,15 @@ function normalizeFastlyProviderConfig(value) {
32
34
  : {};
33
35
  return Object.freeze({
34
36
  kind: 'fastly',
37
+ bodyForwarding: require('./body-forwarding.js').normalizeBodyForwarding(input.bodyForwarding, input.maxDurationMs),
38
+ maxDurationMs: require('@pulse-compute/runtime/host').normalizeRequestDuration(input.maxDurationMs),
35
39
  version: input.version ? String(input.version) : undefined,
36
40
  bindings: Object.freeze({
41
+ ...(bindings.logEndpoint === undefined ? {} : { logEndpoint: require('./logging.js').normalizeFastlyLogEndpoint(bindings.logEndpoint) }),
37
42
  configStore: String(bindings.configStore || 'pulse_config'),
38
43
  secretStore: String(bindings.secretStore || 'pulse_secrets'),
39
44
  kv: normalizeStringMap(bindings.kv, 'PULSE_FASTLY_KV_BINDINGS_INVALID'),
45
+ s3: normalizeFastlyS3(bindings.s3),
40
46
  backends: normalizeStringMap(bindings.backends, 'PULSE_FASTLY_BACKEND_BINDINGS_INVALID'),
41
47
  grip: Object.freeze({
42
48
  fanoutBackend: gripInput.fanoutBackend === undefined ? undefined : String(gripInput.fanoutBackend),
@@ -52,6 +58,7 @@ function normalizeFastlyProviderConfig(value) {
52
58
  dynamicBackends: bindings.dynamicBackends === true
53
59
  }),
54
60
  build: Object.freeze({
61
+ maxWasmBytes: normalizeFastlyMaxWasmBytes(input.build?.maxWasmBytes),
55
62
  name: String(input.build && input.build.name || 'pulse-app'),
56
63
  description: String(input.build && input.build.description || 'Pulse application'),
57
64
  authors: Object.freeze(Array.isArray(input.build && input.build.authors) ? input.build.authors.map(String) : [])
@@ -65,6 +72,8 @@ function fastlyProjectConfigDocument(config) {
65
72
  return Object.freeze({
66
73
  kind: 'fastly',
67
74
  bindings: config.bindings,
75
+ maxDurationMs: config.maxDurationMs,
76
+ bodyForwarding: config.bodyForwarding,
68
77
  build: config.build,
69
78
  local: config.local,
70
79
  providerSpecificUserland: false
@@ -98,6 +107,13 @@ const FASTLY_PROJECT_CONFIG_REFERENCE = Object.freeze({
98
107
  })
99
108
  ]),
100
109
  fields: Object.freeze([
110
+ Object.freeze({ section: 'fastly', path: 'fastly.logEndpoint', type: 'string', default: 'omitted (stdout)', scope: 'fastly() facade; conventional profiles use fastly.bindings.logEndpoint', description: 'One case-sensitive named production log stream, 1–255 UTF-8 bytes, excluding stdout, stderr, fst_managed_ names and control characters. Records above 65536 UTF-8 bytes are dropped. One lookup and at most one write per record; errors and zero/short writes are dropped without retry or stdout fallback. Host acceptance does not prove downstream delivery.', diagnostics: Object.freeze(['PULSE_FASTLY_LOG_ENDPOINT_INVALID']) }),
111
+ Object.freeze({section:'fastly',path:'fastly.bodyForwarding',type:'object',default:'omitted',scope:'experimental Fastly Native forwarding',description:'Opt in to one terminal opaque POST with inline req.body() and a static backend. Requires maxDurationMs and both byte limits. Final close hands queued delivery to Fastly; it is not a client receipt.',diagnostics:Object.freeze(['PULSE_FASTLY_BODY_FORWARDING_INVALID'])}),
112
+ ...['requestMaxBytes','responseMaxBytes'].map(name => Object.freeze({section:'fastly',path:`fastly.bodyForwarding.${name}`,type:'integer',allowed:'1–1048576',default:'required with bodyForwarding',scope:'experimental Fastly Native forwarding',description:'Independent actual-byte bound, including EOF verification; no unbounded default. Fixed 16 KiB raw scratch is reused across the sequential upload and response pumps.'})),
113
+ Object.freeze({section:'fastly',path:'fastly.maxWasmBytes',type:'integer',allowed:'positive safe integer',default:'4194304 (4 MiB)',scope:'fastly() facade',description:'Facade option projected to build.maxWasmBytes. Conventional profiles configure fastly.build.maxWasmBytes.'}),
114
+ Object.freeze({section:'fastly',path:'fastly.build.maxWasmBytes',type:'integer',allowed:'positive safe integer',default:'4194304 (4 MiB)',scope:'Native final Wasm artifact',description:'Inclusive byte budget checked for newly compiled and reused artifacts. Raising it does not increase runtime memory or change Fastly service limits. The fastly() facade accepts maxWasmBytes.'}),
115
+ Object.freeze({section:'fastly',path:'fastly.maxDurationMs',type:'integer',allowed:'1–30000',default:'omitted',scope:'HTTP request execution',description:'One provider-owned monotonic budget through buffered response handoff; expiry attempts 504 before headers commit and never asserts rollback of dispatched writes. No CPU preemption or post-handoff delivery guarantee.'}),
116
+ Object.freeze({ section: 'fastly', path: 'fastly.bindings.s3', type: 'Readonly<Record<string, S3Binding & { backend: string }>>', default: '`{}`', scope: 'Fastly Native S3', description: 'Maps logical names to fixed HTTPS endpoint, bucket, region, named static backend, accessKeyIdSecret, secretAccessKeySecret, optional sessionTokenSecret, maxTextBytes (1–2097152, default 32768) and timeoutMs (1–30000, default 10000).', security: 'Credentials resolve through the configured Secret Store at execution. Dynamic backend authority and guest endpoint overrides are forbidden.' }),
101
117
  Object.freeze({ section: 'fastly', path: 'fastly.configStore', type: 'string', default: '`pulse_config`', scope: 'Fastly config capability', description: 'Fastly Config Store resource name.' }),
102
118
  Object.freeze({ section: 'fastly', path: 'fastly.secretStore', type: 'string', default: '`pulse_secrets`', scope: 'Fastly secret capability', description: 'Fastly Secret Store resource name.', security: 'The name is build metadata; secret values are never embedded by this option.' }),
103
119
  Object.freeze({ section: 'fastly', path: 'fastly.kv', type: 'Readonly<Record<string, string>>', default: '`{}`', scope: 'Fastly KV capability', description: 'Maps logical ctx.kv names to Fastly KV Store resource names.', diagnostics: Object.freeze(['PULSE_FASTLY_KV_BINDINGS_INVALID']) }),
@@ -38,7 +38,8 @@ const packageManifest = require('../../package.json');
38
38
  const reportingTargetCapability = Object.freeze({
39
39
  supported: true,
40
40
  default: 'info',
41
- levels: Object.freeze(['off', 'error', 'warn', 'info', 'debug'])
41
+ levels: Object.freeze(['off', 'error', 'warn', 'info', 'debug']),
42
+ destinations: Object.freeze(['stdout', 'configured-named-endpoint'])
42
43
  });
43
44
 
44
45
  const fastlyFinalWasmPolicy = defineFinalWasmPolicy({
@@ -124,6 +125,7 @@ function createDriver() {
124
125
  projectRoot: invocation.project.root,
125
126
  outDir: invocation.project.outDir,
126
127
  schemaBundle: invocation.javascript.schemaBundle,
128
+ binaryResources: invocation.javascript.binaryResources,
127
129
  providerConfig: invocation.providerConfig,
128
130
  reporting: invocation.javascript.reporting,
129
131
  strict: invocation.javascript.strict,
@@ -169,14 +171,32 @@ function createDriver() {
169
171
  runtimeClass: 'native',
170
172
  status: 'supported',
171
173
  capabilities: Object.freeze([
174
+ 's3.head', 's3.getText', 's3.getBody',
175
+ 'kv.getVersioned', 'kv.insertIfAbsent', 'kv.compareAndSwap', 'kv.list', 'kv.delete',
172
176
  'jwt.verify',
177
+ 'jwt.sign',
173
178
  'jwt.verify.hs256',
174
179
  'jwt.verify.es256',
180
+ 'jwt.verify.rs256',
175
181
  'secret.get',
176
182
  'time.wall-clock'
177
183
  ]),
178
184
  keyTypes: Object.freeze(['secret', 'jwk', 'jwks']),
179
185
  realizations: Object.freeze([
186
+ Object.freeze({
187
+ kind: 'package-native-application',
188
+ contractId: 'pulse.entities',
189
+ implementation: 'pulse.package-native-application.v1',
190
+ implemented: true,
191
+ automaticFallback: false
192
+ }),
193
+ Object.freeze({
194
+ kind: 'package-native-application',
195
+ contractId: 'pulse.mcp',
196
+ implementation: 'pulse.package-native-application.v2',
197
+ implemented: true,
198
+ automaticFallback: false
199
+ }),
180
200
  Object.freeze({
181
201
  kind: 'crypto-composed',
182
202
  realization: 'guest-source:pulse-hmac-as',
@@ -202,6 +222,19 @@ function createDriver() {
202
222
  guestUnitRequired: true,
203
223
  portable: true,
204
224
  automaticFallback: false
225
+ }),
226
+ Object.freeze({
227
+ kind: 'crypto-composed',
228
+ realization: 'guest-linked:pulse-rs256-bearssl-i31',
229
+ implementation: 'bearssl.0.6.rsa-i31.sha256.v1',
230
+ algorithms: Object.freeze(['RS256']),
231
+ keyTypes: Object.freeze(['jwk', 'jwks']),
232
+ implemented: true,
233
+ status: 'implemented-g4',
234
+ semanticOwner: '@pulse-compute/crypto',
235
+ guestUnitRequired: true,
236
+ portable: true,
237
+ automaticFallback: false
205
238
  })
206
239
  ]),
207
240
  jwt: Object.freeze({
@@ -221,7 +254,7 @@ function createDriver() {
221
254
  automaticFallback: false,
222
255
  crypto: defineCryptoTargetCapabilities({
223
256
  target: 'native',
224
- algorithms: [{
257
+ algorithms: [...['SHA-256', 'HMAC-SHA256'].map((algorithm) => ({ algorithm, realization: 'guest-source:pulse-hmac-as', implemented: true, status: 'implemented-o2' })), {
225
258
  algorithm: 'HS256',
226
259
  realization: 'guest-source:pulse-hmac-as',
227
260
  implemented: true,
@@ -231,6 +264,11 @@ function createDriver() {
231
264
  realization: 'guest-linked:pulse-es256-rustcrypto-p256',
232
265
  implemented: true,
233
266
  status: 'implemented-g4'
267
+ }, {
268
+ algorithm: 'RS256',
269
+ realization: 'guest-linked:pulse-rs256-bearssl-i31',
270
+ implemented: true,
271
+ status: 'implemented-g4'
234
272
  }]
235
273
  }),
236
274
  finalWasmPolicy: fastlyFinalWasmPolicy,
@@ -240,8 +278,9 @@ function createDriver() {
240
278
  javascript: FASTLY_JAVASCRIPT_TARGET_DESCRIPTOR
241
279
  }),
242
280
  execute: fastlyRuntime.executeCanonicalProgram,
281
+ prepareNativeExecution: require('./native-execution.js').prepareFastlyNativeExecution,
243
282
  createLoweringPlan(metadata, providerConfig = {}) {
244
- return createFastlyLoweringPlan(metadata, providerConfig.bindings || {});
283
+ return createFastlyLoweringPlan(metadata, providerConfig.bindings || {}, providerConfig);
245
284
  },
246
285
  inspectRealization(invocation) {
247
286
  const realization = inspectFastlyCanonicalTarget({
@@ -255,6 +294,8 @@ function createDriver() {
255
294
  realizationArtifacts: invocation.nativeArtifact.realizationArtifacts,
256
295
  guestUnits: invocation.nativeArtifact.guestUnits,
257
296
  nativeOptimization: invocation.optimization,
297
+ elideJsonCacheArena: invocation.nativeArtifact.manifest.elideJsonCacheArena === true,
298
+ emitWat: invocation.nativeArtifact.manifest.wat?.emitted === true,
258
299
  compileTimeoutMs: invocation.timeoutMs
259
300
  });
260
301
  const { native, ...publicRealization } = realization;
@@ -283,6 +324,8 @@ function createDriver() {
283
324
  realizationArtifacts: invocation.nativeArtifact.realizationArtifacts,
284
325
  guestUnits: invocation.nativeArtifact.guestUnits,
285
326
  nativeOptimization: invocation.optimization,
327
+ elideJsonCacheArena: invocation.nativeArtifact.manifest.elideJsonCacheArena === true,
328
+ emitWat: invocation.nativeArtifact.manifest.wat?.emitted === true,
286
329
  compileTimeoutMs: invocation.timeoutMs
287
330
  });
288
331
  const providerBuild = writeFastlyCanonicalTarget({
@@ -318,7 +361,7 @@ function createDriver() {
318
361
  providerPackage: providerBuild.packageFile,
319
362
  providerSourceEntry: providerBuild.sourceEntryFile
320
363
  }),
321
- build: providerBuild.build,
364
+ build: { ...providerBuild.build, reportAttribution: native.reportAttribution, buildObservation: native.buildObservation },
322
365
  realization: publicRealization,
323
366
  packaging: audit ? Object.freeze({
324
367
  version: PROVIDER_PACKAGING_AUDIT_VERSION,
@@ -329,7 +372,7 @@ function createDriver() {
329
372
  });
330
373
  },
331
374
  executionOptions(providerConfig = {}, values = {}) {
332
- return { ...values, bindings: providerConfig.bindings };
375
+ return { ...values, maxDurationMs: providerConfig.maxDurationMs, bindings: providerConfig.bindings };
333
376
  }
334
377
  });
335
378
  }
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ const MAX_LOG_BYTES = 65536;
4
+
5
+ function normalizeFastlyLogEndpoint(value) {
6
+ if (value === undefined) return undefined;
7
+ if (typeof value !== 'string' || !value || value.length > 255 || value.trim() !== value
8
+ || new TextEncoder().encode(value).byteLength > 255 || /[\u0000-\u001f\u007f]/u.test(value)
9
+ || /[\ud800-\udfff]/u.test(value.replace(/[\ud800-\udbff][\udc00-\udfff]/g, ''))
10
+ || /^(?:stdout$|stderr$|fst_managed_)/i.test(value)) {
11
+ const error = new TypeError('Fastly logEndpoint must be a non-reserved endpoint name of 1–255 UTF-8 bytes without control characters or surrounding whitespace.');
12
+ error.code = 'PULSE_FASTLY_LOG_ENDPOINT_INVALID';
13
+ throw error;
14
+ }
15
+ return value;
16
+ }
17
+
18
+ module.exports = { normalizeFastlyLogEndpoint, MAX_LOG_BYTES };
@@ -0,0 +1,78 @@
1
+ 'use strict';
2
+
3
+ const { inspectFastlyCanonicalTarget } = require('../build/canonical-target.js');
4
+ const { executeFastlyNativePlatformCapabilities } = require('../testing/native-platform-capabilities-host.js');
5
+ const { createConditionalKvAuthority } = require('../testing/conditional-kv-host.js');
6
+
7
+ // Execute the provider artifact against the existing fixture ABI. This is local
8
+ // conformance evidence, never Viceroy or deployed Fastly acceptance.
9
+ function prepareFastlyNativeExecution(invocation) {
10
+ const bindings = invocation.providerConfig.bindings;
11
+ const { native } = inspectFastlyCanonicalTarget({
12
+ plan: invocation.applicationPlan,
13
+ providerConfig: invocation.providerConfig,
14
+ projectRoot: invocation.project.root,
15
+ profile: invocation.project.profile,
16
+ targetDescriptor: invocation.selectedTarget,
17
+ synchronizedPackages: invocation.synchronizedPackages,
18
+ realizationArtifacts: invocation.nativeArtifact.realizationArtifacts,
19
+ guestUnits: invocation.nativeArtifact.guestUnits,
20
+ nativeOptimization: invocation.optimization,
21
+ elideJsonCacheArena: invocation.nativeArtifact.manifest.elideJsonCacheArena === true,
22
+ emitWat: invocation.nativeArtifact.manifest.wat?.emitted === true,
23
+ compileTimeoutMs: invocation.timeoutMs || undefined
24
+ });
25
+ const evidence = Object.freeze({
26
+ kind: 'fastly-native-fixture-abi',
27
+ provider: 'fastly',
28
+ target: 'fastly-compute-native',
29
+ wasmSha256: native.manifest.wasm.sha256,
30
+ providerRealityValidated: false,
31
+ effectObservations: 'unavailable'
32
+ });
33
+ return (options = {}) => {
34
+ if (options.liveFetch === true) throw new TypeError('Fastly Native fixture execution cannot perform live fetches.');
35
+ const kvStores = {};
36
+ for (const [logical, physical] of Object.entries(bindings.kv || {})) {
37
+ kvStores[physical] = { ...kvStores[physical], ...(options.kv && options.kv[logical] || {}) };
38
+ }
39
+ const authority = createConditionalKvAuthority();
40
+ for (const [store, values] of Object.entries(kvStores)) {
41
+ authority.stores.set(store, new Map());
42
+ for (const [key, value] of Object.entries(values)) {
43
+ authority.seed(store, key, Buffer.from(JSON.stringify({ __pulseKv: 1, value })));
44
+ }
45
+ }
46
+ const execution = executeFastlyNativePlatformCapabilities(native, {
47
+ request: options.request,
48
+ fixtures: options.fetches,
49
+ configStore: bindings.configStore,
50
+ config: options.config,
51
+ secretStore: bindings.secretStore,
52
+ secrets: options.secrets,
53
+ kvStores,
54
+ conditionalKv: { authority }
55
+ });
56
+ const contentType = execution.response.headers.find(([name]) => name.toLowerCase() === 'content-type');
57
+ const type = String(contentType && contentType[1] || '').toLowerCase();
58
+ return Object.freeze({
59
+ response: Object.freeze({
60
+ ...execution.response,
61
+ kind: type.includes('application/json') || type.includes('+json') ? 'json' : type.startsWith('text/') ? 'text' : 'response',
62
+ // Forwarding filters origin metadata into a new response handle, so
63
+ // its streamed payload remains opaque even though origin is false.
64
+ // Pre-commit errors use ordinary non-streaming structured responses.
65
+ bodyClass: execution.response.origin === true
66
+ || (native.plan.capabilities.includes('request.body.forward') && execution.response.streaming === 1)
67
+ ? 'opaque' : 'structured'
68
+ }),
69
+ // The fixture ABI has hostcall traces, not canonical continuation telemetry.
70
+ effectCount: null,
71
+ continuations: Object.freeze([]),
72
+ resolutionOrder: Object.freeze([]),
73
+ evidence
74
+ });
75
+ };
76
+ }
77
+
78
+ module.exports = { prepareFastlyNativeExecution };
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+ const { normalizeBinding, namePattern } = require('@pulse-compute/s3/provider');
3
+ function normalizeFastlyS3(input = {}) {
4
+ if (!input || typeof input !== 'object' || Array.isArray(input) || ![Object.prototype, null].includes(Object.getPrototypeOf(input))) throw new TypeError('Fastly S3 bindings must be a map.');
5
+ for (const key of Reflect.ownKeys(input)) {
6
+ const field = Object.getOwnPropertyDescriptor(input, key);
7
+ if (typeof key !== 'string' || !field.enumerable || !Object.hasOwn(field, 'value')) throw new TypeError('Fastly S3 bindings require data properties.');
8
+ }
9
+ return Object.freeze(Object.fromEntries(Object.entries(input).map(([name, value]) => {
10
+ if (!namePattern.test(name) || !value || typeof value !== 'object' || ![Object.prototype, null].includes(Object.getPrototypeOf(value))
11
+ || Reflect.ownKeys(value).some((key) => { const field = Object.getOwnPropertyDescriptor(value, key); return typeof key !== 'string' || !field.enumerable || !Object.hasOwn(field, 'value'); })) throw new TypeError('Fastly S3 binding is invalid.');
12
+ const { backend, ...binding } = value;
13
+ if (typeof backend !== 'string' || !namePattern.test(backend)) throw new TypeError('Fastly S3 requires a named static backend.');
14
+ return [name, Object.freeze({ ...normalizeBinding(binding), backend })];
15
+ })));
16
+ }
17
+ function resolveFastlyS3(effects, input) {
18
+ const configured = normalizeFastlyS3(input);
19
+ return Object.freeze(effects.flatMap((effect, index) => {
20
+ if (!['s3.head', 's3.getText', 's3.putText', 's3.getBody'].includes(effect.kind)) return [];
21
+ const name = effect.resource && effect.resource.binding;
22
+ if (!Object.hasOwn(configured, name)) throw new TypeError('The required Fastly S3 binding is missing.');
23
+ return [Object.freeze({ index, ...configured[name], ...(effect.operation === 'getBody' ? require('@pulse-compute/s3/provider').normalizeBodyOptions(Object.fromEntries(['method', 'range', 'ifNoneMatch'].filter(key => effect.payload?.[key] !== undefined).map(key => [key, effect.payload[key]]))) : {}), contentType: effect.operation === 'putText' ? require('@pulse-compute/s3/provider').normalizePutOptions({ contentType: effect.payload && effect.payload.contentType }).contentType : '' })];
24
+ }));
25
+ }
26
+ module.exports = { normalizeFastlyS3, resolveFastlyS3 };