@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.
- package/README.md +7 -6
- package/dist/config-schema.json +30 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/package.json +11 -10
- package/src/build/canonical-target.js +35 -2
- package/src/build/digest-native.js +25 -0
- package/src/build/effect-invocations.js +49 -0
- package/src/build/embedded-assets.as.ts +32 -0
- package/src/build/entities-native.js +6 -11
- package/src/build/kv-native.as.ts +331 -0
- package/src/build/kv-native.js +27 -0
- package/src/build/native-application-errors.js +159 -0
- package/src/build/native-binary-resources.js +30 -0
- package/src/build/native-body-forwarding.as.ts +341 -0
- package/src/build/native-body-forwarding.js +69 -0
- package/src/build/native-dynamic-backends.as.ts +115 -0
- package/src/build/native-dynamic-backends.js +52 -0
- package/src/build/native-fetch-deadlines.as.ts +164 -0
- package/src/build/native-fetch-deadlines.js +27 -0
- package/src/build/native-fetch-errors.js +38 -0
- package/src/build/native-handle-ownership.js +127 -0
- package/src/build/native-header-lookup.js +50 -0
- package/src/build/native-http-effects.js +155 -32
- package/src/build/native-platform-capabilities.js +755 -210
- package/src/build/native-request-body.js +44 -0
- package/src/build/native-request-headers.js +93 -0
- package/src/build/native-send-error-detail.as.ts +12 -0
- package/src/build/native-stdio.js +45 -0
- package/src/build/native-string-values.js +143 -0
- package/src/build/native-transport-details.as.ts +84 -0
- package/src/build/native-value-budget.js +90 -0
- package/src/build/package-native-application.js +114 -0
- package/src/build/request-budget.as.ts +50 -0
- package/src/build/request-budget.js +47 -0
- package/src/build/s3-body.as.ts +97 -0
- package/src/build/s3-native.as.ts +228 -0
- package/src/build/s3-native.js +30 -0
- package/src/build/schema-admission.js +83 -0
- package/src/build/schema-nested-json.js +93 -0
- package/src/build/schema-scalar-record.js +41 -0
- package/src/build/time-native.js +49 -0
- package/src/build/wasm-budget.js +28 -0
- package/src/config-api.js +6 -0
- package/src/config-schema.json +30 -0
- package/src/index.ts +25 -0
- package/src/javascript/jwt-verifier.js +14 -4
- package/src/javascript/logging.js +11 -2
- package/src/javascript/package-effects.js +5 -2
- package/src/javascript/runtime-host.js +7 -3
- package/src/javascript/source-package.js +4 -2
- package/src/javascript/target-support-policy.js +20 -3
- package/src/javascript/target.js +17 -1
- package/src/javascript/test-runtime.js +1 -0
- package/src/provider-contract.js +22 -6
- package/src/runtime/canonical-api-runtime.js +6 -0
- package/src/testing/conditional-kv-host.js +155 -0
- package/src/testing/native-dynamic-backends-host.js +39 -0
- package/src/testing/native-http-effects-host.js +9 -1
- package/src/testing/native-platform-capabilities-host.js +349 -24
- package/src/toolchain/body-forwarding.js +25 -0
- package/src/toolchain/config.js +16 -0
- package/src/toolchain/index.js +48 -5
- package/src/toolchain/logging.js +18 -0
- package/src/toolchain/native-execution.js +78 -0
- package/src/toolchain/s3.js +26 -0
package/src/toolchain/config.js
CHANGED
|
@@ -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']) }),
|
package/src/toolchain/index.js
CHANGED
|
@@ -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 };
|