@pulse-compute/provider-fastly 1.0.0-beta.1 → 1.0.0-beta.11
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 +41 -2
- package/src/build/digest-native.js +25 -0
- package/src/build/effect-invocations.js +51 -0
- package/src/build/embedded-assets.as.ts +32 -0
- package/src/build/entities-native.js +6 -11
- package/src/build/host-extensions.js +254 -0
- package/src/build/kv-native.as.ts +331 -0
- package/src/build/kv-native.js +27 -0
- package/src/build/lifecycle.js +50 -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 +325 -0
- package/src/build/native-body-forwarding.js +73 -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-accounting.as.ts +133 -0
- package/src/build/native-fetch-accounting.js +84 -0
- package/src/build/native-fetch-bounds.js +47 -0
- package/src/build/native-fetch-cohorts.as.ts +126 -0
- package/src/build/native-fetch-cohorts.js +65 -0
- package/src/build/native-fetch-deadlines.as.ts +169 -0
- package/src/build/native-fetch-deadlines.js +36 -0
- package/src/build/native-fetch-errors.js +38 -0
- package/src/build/native-fetch-headers.js +52 -0
- package/src/build/native-fetch-outcomes.js +87 -0
- package/src/build/native-fetch-timing.as.ts +74 -0
- package/src/build/native-fetch-timing.js +50 -0
- package/src/build/native-forwarding-accounting.as.ts +153 -0
- package/src/build/native-forwarding-accounting.js +47 -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 +164 -35
- package/src/build/native-platform-capabilities.js +815 -220
- package/src/build/native-request-body.js +45 -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-source.js +58 -0
- package/src/build/request-budget.js +46 -0
- package/src/build/response-authority.js +56 -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/fetch-adapter.js +3 -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 +8 -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 +23 -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 +18 -1
- package/src/testing/native-platform-capabilities-host.js +359 -24
- package/src/toolchain/body-forwarding.js +25 -0
- package/src/toolchain/config.js +16 -0
- package/src/toolchain/index.js +49 -5
- package/src/toolchain/logging.js +18 -0
- package/src/toolchain/native-execution.js +83 -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,
|
|
@@ -166,17 +168,36 @@ function createDriver() {
|
|
|
166
168
|
version: 'pulse.provider-target-descriptor.v1',
|
|
167
169
|
target: 'native',
|
|
168
170
|
targetId: 'fastly-compute-native',
|
|
171
|
+
hostExtensions: require('../build/host-extensions.js').ADMISSION,
|
|
169
172
|
runtimeClass: 'native',
|
|
170
173
|
status: 'supported',
|
|
171
174
|
capabilities: Object.freeze([
|
|
175
|
+
's3.head', 's3.getText', 's3.getBody',
|
|
176
|
+
'kv.getVersioned', 'kv.insertIfAbsent', 'kv.compareAndSwap', 'kv.list', 'kv.delete',
|
|
172
177
|
'jwt.verify',
|
|
178
|
+
'jwt.sign',
|
|
173
179
|
'jwt.verify.hs256',
|
|
174
180
|
'jwt.verify.es256',
|
|
181
|
+
'jwt.verify.rs256',
|
|
175
182
|
'secret.get',
|
|
176
183
|
'time.wall-clock'
|
|
177
184
|
]),
|
|
178
185
|
keyTypes: Object.freeze(['secret', 'jwk', 'jwks']),
|
|
179
186
|
realizations: Object.freeze([
|
|
187
|
+
Object.freeze({
|
|
188
|
+
kind: 'package-native-application',
|
|
189
|
+
contractId: 'pulse.entities',
|
|
190
|
+
implementation: 'pulse.package-native-application.v1',
|
|
191
|
+
implemented: true,
|
|
192
|
+
automaticFallback: false
|
|
193
|
+
}),
|
|
194
|
+
Object.freeze({
|
|
195
|
+
kind: 'package-native-application',
|
|
196
|
+
contractId: 'pulse.mcp',
|
|
197
|
+
implementation: 'pulse.package-native-application.v2',
|
|
198
|
+
implemented: true,
|
|
199
|
+
automaticFallback: false
|
|
200
|
+
}),
|
|
180
201
|
Object.freeze({
|
|
181
202
|
kind: 'crypto-composed',
|
|
182
203
|
realization: 'guest-source:pulse-hmac-as',
|
|
@@ -202,6 +223,19 @@ function createDriver() {
|
|
|
202
223
|
guestUnitRequired: true,
|
|
203
224
|
portable: true,
|
|
204
225
|
automaticFallback: false
|
|
226
|
+
}),
|
|
227
|
+
Object.freeze({
|
|
228
|
+
kind: 'crypto-composed',
|
|
229
|
+
realization: 'guest-linked:pulse-rs256-bearssl-i31',
|
|
230
|
+
implementation: 'bearssl.0.6.rsa-i31.sha256.v1',
|
|
231
|
+
algorithms: Object.freeze(['RS256']),
|
|
232
|
+
keyTypes: Object.freeze(['jwk', 'jwks']),
|
|
233
|
+
implemented: true,
|
|
234
|
+
status: 'implemented-g4',
|
|
235
|
+
semanticOwner: '@pulse-compute/crypto',
|
|
236
|
+
guestUnitRequired: true,
|
|
237
|
+
portable: true,
|
|
238
|
+
automaticFallback: false
|
|
205
239
|
})
|
|
206
240
|
]),
|
|
207
241
|
jwt: Object.freeze({
|
|
@@ -221,7 +255,7 @@ function createDriver() {
|
|
|
221
255
|
automaticFallback: false,
|
|
222
256
|
crypto: defineCryptoTargetCapabilities({
|
|
223
257
|
target: 'native',
|
|
224
|
-
algorithms: [{
|
|
258
|
+
algorithms: [...['SHA-256', 'HMAC-SHA256'].map((algorithm) => ({ algorithm, realization: 'guest-source:pulse-hmac-as', implemented: true, status: 'implemented-o2' })), {
|
|
225
259
|
algorithm: 'HS256',
|
|
226
260
|
realization: 'guest-source:pulse-hmac-as',
|
|
227
261
|
implemented: true,
|
|
@@ -231,6 +265,11 @@ function createDriver() {
|
|
|
231
265
|
realization: 'guest-linked:pulse-es256-rustcrypto-p256',
|
|
232
266
|
implemented: true,
|
|
233
267
|
status: 'implemented-g4'
|
|
268
|
+
}, {
|
|
269
|
+
algorithm: 'RS256',
|
|
270
|
+
realization: 'guest-linked:pulse-rs256-bearssl-i31',
|
|
271
|
+
implemented: true,
|
|
272
|
+
status: 'implemented-g4'
|
|
234
273
|
}]
|
|
235
274
|
}),
|
|
236
275
|
finalWasmPolicy: fastlyFinalWasmPolicy,
|
|
@@ -240,8 +279,9 @@ function createDriver() {
|
|
|
240
279
|
javascript: FASTLY_JAVASCRIPT_TARGET_DESCRIPTOR
|
|
241
280
|
}),
|
|
242
281
|
execute: fastlyRuntime.executeCanonicalProgram,
|
|
282
|
+
prepareNativeExecution: require('./native-execution.js').prepareFastlyNativeExecution,
|
|
243
283
|
createLoweringPlan(metadata, providerConfig = {}) {
|
|
244
|
-
return createFastlyLoweringPlan(metadata, providerConfig.bindings || {});
|
|
284
|
+
return createFastlyLoweringPlan(metadata, providerConfig.bindings || {}, providerConfig);
|
|
245
285
|
},
|
|
246
286
|
inspectRealization(invocation) {
|
|
247
287
|
const realization = inspectFastlyCanonicalTarget({
|
|
@@ -255,6 +295,8 @@ function createDriver() {
|
|
|
255
295
|
realizationArtifacts: invocation.nativeArtifact.realizationArtifacts,
|
|
256
296
|
guestUnits: invocation.nativeArtifact.guestUnits,
|
|
257
297
|
nativeOptimization: invocation.optimization,
|
|
298
|
+
elideJsonCacheArena: invocation.nativeArtifact.manifest.elideJsonCacheArena === true,
|
|
299
|
+
emitWat: invocation.nativeArtifact.manifest.wat?.emitted === true,
|
|
258
300
|
compileTimeoutMs: invocation.timeoutMs
|
|
259
301
|
});
|
|
260
302
|
const { native, ...publicRealization } = realization;
|
|
@@ -283,6 +325,8 @@ function createDriver() {
|
|
|
283
325
|
realizationArtifacts: invocation.nativeArtifact.realizationArtifacts,
|
|
284
326
|
guestUnits: invocation.nativeArtifact.guestUnits,
|
|
285
327
|
nativeOptimization: invocation.optimization,
|
|
328
|
+
elideJsonCacheArena: invocation.nativeArtifact.manifest.elideJsonCacheArena === true,
|
|
329
|
+
emitWat: invocation.nativeArtifact.manifest.wat?.emitted === true,
|
|
286
330
|
compileTimeoutMs: invocation.timeoutMs
|
|
287
331
|
});
|
|
288
332
|
const providerBuild = writeFastlyCanonicalTarget({
|
|
@@ -318,7 +362,7 @@ function createDriver() {
|
|
|
318
362
|
providerPackage: providerBuild.packageFile,
|
|
319
363
|
providerSourceEntry: providerBuild.sourceEntryFile
|
|
320
364
|
}),
|
|
321
|
-
build: providerBuild.build,
|
|
365
|
+
build: { ...providerBuild.build, reportAttribution: native.reportAttribution, hostExtensions: native.manifest.hostExtensions, buildObservation: native.buildObservation },
|
|
322
366
|
realization: publicRealization,
|
|
323
367
|
packaging: audit ? Object.freeze({
|
|
324
368
|
version: PROVIDER_PACKAGING_AUDIT_VERSION,
|
|
@@ -329,7 +373,7 @@ function createDriver() {
|
|
|
329
373
|
});
|
|
330
374
|
},
|
|
331
375
|
executionOptions(providerConfig = {}, values = {}) {
|
|
332
|
-
return { ...values, bindings: providerConfig.bindings };
|
|
376
|
+
return { ...values, maxDurationMs: providerConfig.maxDurationMs, bindings: providerConfig.bindings };
|
|
333
377
|
}
|
|
334
378
|
});
|
|
335
379
|
}
|
|
@@ -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,83 @@
|
|
|
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
|
+
kvFixture: Object.freeze({ lifetime: 'invocation', initialization: 'supplied-fixtures', crossRequestPersistence: false }),
|
|
33
|
+
notice: 'KV fixture state is seeded from supplied fixtures for each invocation; writes do not persist across requests. Use the Fastly CLI/Viceroy workflow for local provider persistence and contention checks; deployed-service acceptance is separate.'
|
|
34
|
+
});
|
|
35
|
+
const execute = (options = {}) => {
|
|
36
|
+
if (options.liveFetch === true) throw new TypeError('Fastly Native fixture execution cannot perform live fetches.');
|
|
37
|
+
const kvStores = {};
|
|
38
|
+
for (const [logical, physical] of Object.entries(bindings.kv || {})) {
|
|
39
|
+
kvStores[physical] = { ...kvStores[physical], ...(options.kv && options.kv[logical] || {}) };
|
|
40
|
+
}
|
|
41
|
+
const authority = createConditionalKvAuthority();
|
|
42
|
+
for (const [store, values] of Object.entries(kvStores)) {
|
|
43
|
+
authority.stores.set(store, new Map());
|
|
44
|
+
for (const [key, value] of Object.entries(values)) {
|
|
45
|
+
authority.seed(store, key, Buffer.from(JSON.stringify({ __pulseKv: 1, value })));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const execution = executeFastlyNativePlatformCapabilities(native, {
|
|
49
|
+
request: options.request,
|
|
50
|
+
fixtures: options.fetches,
|
|
51
|
+
configStore: bindings.configStore,
|
|
52
|
+
config: options.config,
|
|
53
|
+
secretStore: bindings.secretStore,
|
|
54
|
+
secrets: options.secrets,
|
|
55
|
+
kvStores,
|
|
56
|
+
conditionalKv: { authority }
|
|
57
|
+
});
|
|
58
|
+
const contentType = execution.response.headers.find(([name]) => name.toLowerCase() === 'content-type');
|
|
59
|
+
const type = String(contentType && contentType[1] || '').toLowerCase();
|
|
60
|
+
return Object.freeze({
|
|
61
|
+
response: Object.freeze({
|
|
62
|
+
...execution.response,
|
|
63
|
+
kind: type.includes('application/json') || type.includes('+json') ? 'json' : type.startsWith('text/') ? 'text' : 'response',
|
|
64
|
+
// Forwarding filters origin metadata into a new response handle, so
|
|
65
|
+
// its streamed payload remains opaque even though origin is false.
|
|
66
|
+
// Pre-commit errors use ordinary non-streaming structured responses.
|
|
67
|
+
bodyClass: execution.response.origin === true
|
|
68
|
+
|| (native.plan.capabilities.includes('request.body.forward') && execution.response.streaming === 1)
|
|
69
|
+
? 'opaque' : 'structured'
|
|
70
|
+
}),
|
|
71
|
+
// The fixture ABI has hostcall traces, not canonical continuation telemetry.
|
|
72
|
+
effectCount: null,
|
|
73
|
+
continuations: Object.freeze([]),
|
|
74
|
+
resolutionOrder: Object.freeze([]),
|
|
75
|
+
evidence
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
// Public observation already returned with execution results, available before
|
|
79
|
+
// the first request without running a probe or acquiring additional authority.
|
|
80
|
+
return Object.freeze(Object.assign(execute, { evidence }));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
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 };
|