@pulse-compute/provider-fastly 1.0.0-beta.10 → 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 +6 -6
- package/package.json +11 -11
- package/src/build/canonical-target.js +6 -0
- package/src/build/effect-invocations.js +6 -4
- package/src/build/host-extensions.js +254 -0
- package/src/build/lifecycle.js +50 -0
- package/src/build/native-body-forwarding.as.ts +28 -44
- package/src/build/native-body-forwarding.js +15 -11
- 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 +13 -8
- package/src/build/native-fetch-deadlines.js +13 -4
- package/src/build/native-fetch-errors.js +2 -2
- 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-header-lookup.js +2 -2
- package/src/build/native-http-effects.js +10 -4
- package/src/build/native-platform-capabilities.js +89 -39
- package/src/build/native-request-body.js +6 -5
- package/src/build/{request-budget.as.ts → request-budget-source.js} +12 -4
- package/src/build/request-budget.js +7 -8
- package/src/build/response-authority.js +56 -0
- package/src/javascript/fetch-adapter.js +3 -0
- package/src/javascript/runtime-host.js +1 -0
- package/src/provider-contract.js +2 -1
- package/src/testing/native-http-effects-host.js +9 -0
- package/src/testing/native-platform-capabilities-host.js +10 -0
- package/src/toolchain/index.js +2 -1
- package/src/toolchain/native-execution.js +7 -2
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Adapted from HEX research ecc36468b5900a0de57a415379b85d12c5026f3f; structural emission.
|
|
4
|
+
|
|
5
|
+
const { httpLifecycle } = require('@pulse-compute/wasm-contracts/provider/toolchain');
|
|
6
|
+
if (httpLifecycle.VERSION !== 'pulse.http-lifecycle.v1') throw bindingError('Unsupported HTTP lifecycle contract version.');
|
|
7
|
+
const { AUTHORITY } = httpLifecycle;
|
|
8
|
+
// Fastly owns this diagnostic/runtime encoding; shared contracts use names.
|
|
9
|
+
const STATES = Object.freeze({ owned: 0, 'outcome-unknown': 1, transferred: 2, completed: 3 });
|
|
10
|
+
if (AUTHORITY.states.length !== Object.keys(STATES).length || AUTHORITY.states.some(state => !Object.hasOwn(STATES, state)))
|
|
11
|
+
throw bindingError('Fastly response-authority encoding does not cover the shared contract.');
|
|
12
|
+
const dispatch = AUTHORITY.transitions.dispatch;
|
|
13
|
+
const transfer = AUTHORITY.transitions.transfer;
|
|
14
|
+
const complete = AUTHORITY.transitions.complete;
|
|
15
|
+
const mayRespond = AUTHORITY.mayRespond.map(state => `__pulse_response_authority == ${STATES[state]}`).join(' || ');
|
|
16
|
+
|
|
17
|
+
function bindingError(message) {
|
|
18
|
+
return Object.assign(new Error(message), { code: 'PULSE_FASTLY_LIFECYCLE_HOOK_INVALID' });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Driver-owned downstream authority, selected only by services that need it.
|
|
22
|
+
// This is not resource ownership: streaming writers and borrowed request handles
|
|
23
|
+
// still require their service's qualified disposal policy.
|
|
24
|
+
function runtimeSource() {
|
|
25
|
+
return `
|
|
26
|
+
// 0 owned; 1 dispatch outcome unknown; 2 transferred; 3 locally completed.
|
|
27
|
+
// Completion is not a delivery receipt. Nonzero status or trap never reopens it.
|
|
28
|
+
let __pulse_response_authority: i32 = ${STATES[AUTHORITY.initial]}
|
|
29
|
+
function __pulse_response_owned(): bool { return ${mayRespond} }
|
|
30
|
+
function __pulse_response_begin(): bool {
|
|
31
|
+
if (!__pulse_response_owned()) { __pulse_fastly_fail(PULSE_ERROR_STATE, 282, -1); return false }
|
|
32
|
+
__pulse_response_authority = ${STATES[dispatch.to]}
|
|
33
|
+
return true
|
|
34
|
+
}
|
|
35
|
+
function __pulse_response_transferred(): void {
|
|
36
|
+
if (__pulse_response_authority == ${STATES[transfer.from]}) __pulse_response_authority = ${STATES[transfer.to]}
|
|
37
|
+
}
|
|
38
|
+
function __pulse_response_complete(): void {
|
|
39
|
+
if (__pulse_response_authority == ${STATES[complete.from]}) __pulse_response_authority = ${STATES[complete.to]}
|
|
40
|
+
}
|
|
41
|
+
function fastly_http_resp_send_downstream(handle: i32, body: i32, streaming: i32): i32 {
|
|
42
|
+
if (!__pulse_response_begin()) return PULSE_ERROR_STATE
|
|
43
|
+
const status = __pulse_response_send_raw(handle, body, streaming)
|
|
44
|
+
if (status == 0) {
|
|
45
|
+
__pulse_response_transferred()
|
|
46
|
+
if (streaming == 0) __pulse_response_complete()
|
|
47
|
+
}
|
|
48
|
+
return status
|
|
49
|
+
}
|
|
50
|
+
`;
|
|
51
|
+
}
|
|
52
|
+
function hooks() {
|
|
53
|
+
return [{ point: 'response.authority', owner: 'driver.response', order: -100,
|
|
54
|
+
body: ' return __pulse_response_owned()' }];
|
|
55
|
+
}
|
|
56
|
+
module.exports = { STATES, runtimeSource, hooks };
|
|
@@ -53,7 +53,9 @@ function createFastlyJavascriptFetchCapability(options = {}) {
|
|
|
53
53
|
...(configuredBackend ? { backend: configuredBackend } : {})
|
|
54
54
|
};
|
|
55
55
|
try {
|
|
56
|
+
execution.fetchTiming?.begin();
|
|
56
57
|
const response = await implementation(String(url), requestInit);
|
|
58
|
+
execution.fetchTiming?.headers();
|
|
57
59
|
if (!(response instanceof Response) && !runtimeHost.isPulseFetchResponse(response)) {
|
|
58
60
|
throw fastlyJavascriptProviderError(
|
|
59
61
|
'PULSE_FASTLY_FETCH_RESPONSE_INVALID',
|
|
@@ -63,6 +65,7 @@ function createFastlyJavascriptFetchCapability(options = {}) {
|
|
|
63
65
|
}
|
|
64
66
|
return response;
|
|
65
67
|
} catch (error) {
|
|
68
|
+
execution.fetchTiming?.fail();
|
|
66
69
|
if (isPulseRuntimeError(error)) throw error;
|
|
67
70
|
if (requestInit.signal && requestInit.signal.aborted) {
|
|
68
71
|
const reason = requestInit.signal.reason;
|
|
@@ -72,6 +72,7 @@ async function executeFastlyJavascriptApplication(application, request, options
|
|
|
72
72
|
maxEffects: options.maxEffects,
|
|
73
73
|
maxRequestBodyBytes: options.maxRequestBodyBytes,
|
|
74
74
|
maxFetchBodyBytes: options.maxFetchBodyBytes,
|
|
75
|
+
fetchClock: options.fetchClock,
|
|
75
76
|
maxStructuredBodyBytes: options.maxStructuredBodyBytes,
|
|
76
77
|
maxBindingNameBytes: options.maxBindingNameBytes,
|
|
77
78
|
maxBindingValueBytes: options.maxBindingValueBytes,
|
package/src/provider-contract.js
CHANGED
|
@@ -12,12 +12,13 @@ const FASTLY_PROVIDER_DESCRIPTOR = canonical.normalizeDescriptor({
|
|
|
12
12
|
deployable: true,
|
|
13
13
|
localExecution: true,
|
|
14
14
|
capabilities: [
|
|
15
|
-
'request', 'request.body.forward', 'response.json', 'response.text', 'response.custom', 'fetch',
|
|
15
|
+
'host.extension', 'request', 'request.body.forward', 'response.json', 'response.text', 'response.custom', 'fetch',
|
|
16
16
|
'time.wall-clock', 'crypto.digestText', 'config.get', 'secret.get', 'kv.get', 'kv.put', 'kv.getVersioned', 'kv.insertIfAbsent', 'kv.compareAndSwap', 'kv.list', 'kv.delete', 'assets.lookup',
|
|
17
17
|
'grip.channel', 'grip.hold', 'grip.publish', 'grip.broadcast', 'jwt.verify', 'jwt.sign',
|
|
18
18
|
'opaque.pass-through', 's3.head', 's3.getText', 's3.putText', 's3.getBody'
|
|
19
19
|
],
|
|
20
20
|
lowering: {
|
|
21
|
+
'host.extension': 'fastly.native.terminal-request-backend.v1',
|
|
21
22
|
request: 'fastly.compute.request',
|
|
22
23
|
'request.body.forward': 'fastly.native.body-forwarding',
|
|
23
24
|
'response.json': 'fastly.compute.response.json',
|
|
@@ -223,6 +223,15 @@ function executeFastlyNativeHttpEffects(input, options = {}) {
|
|
|
223
223
|
trace.push({ module: 'fastly_http_req', name: 'header_insert', handle: Number(handle), header: [name, value] });
|
|
224
224
|
return FASTLY_STATUS_OK;
|
|
225
225
|
},
|
|
226
|
+
header_append(handle, namePointer, nameLength, valuePointer, valueLength) {
|
|
227
|
+
const request = requests.get(Number(handle));
|
|
228
|
+
if (!request) return FASTLY_STATUS_BADF;
|
|
229
|
+
const name = readUtf8(namePointer, nameLength);
|
|
230
|
+
const value = readUtf8(valuePointer, valueLength);
|
|
231
|
+
request.headers.push([name, value]);
|
|
232
|
+
trace.push({ module: 'fastly_http_req', name: 'header_append', handle: Number(handle), header: [name, value] });
|
|
233
|
+
return FASTLY_STATUS_OK;
|
|
234
|
+
},
|
|
226
235
|
send_async(handle, bodyHandle, backendPointer, backendLength, pendingOut) {
|
|
227
236
|
const request = requests.get(Number(handle));
|
|
228
237
|
const body = bodies.get(Number(bodyHandle));
|
|
@@ -483,6 +483,16 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
483
483
|
trace.push({ module: 'fastly_http_req', name: 'header_insert', handle: Number(handle), header: redactRuntimeHeaders([[name, value]], privateUrls.has(request.url))[0] });
|
|
484
484
|
return FASTLY_STATUS_OK;
|
|
485
485
|
},
|
|
486
|
+
header_append(handle, namePointer, nameLength, valuePointer, valueLength) {
|
|
487
|
+
const request = requests.get(Number(handle));
|
|
488
|
+
if (!request) return FASTLY_STATUS_BADF;
|
|
489
|
+
const name = readUtf8(namePointer, nameLength);
|
|
490
|
+
const value = readUtf8(valuePointer, valueLength);
|
|
491
|
+
if (name.toLowerCase() === 'authorization' && value.includes('/s3/aws4_request')) privateUrls.add(request.url);
|
|
492
|
+
request.headers.push([name, value]);
|
|
493
|
+
trace.push({ module: 'fastly_http_req', name: 'header_append', handle: Number(handle), header: redactRuntimeHeaders([[name, value]], privateUrls.has(request.url))[0] });
|
|
494
|
+
return FASTLY_STATUS_OK;
|
|
495
|
+
},
|
|
486
496
|
cache_override_set(handle, tag, ttl, swr) {
|
|
487
497
|
const request = requests.get(Number(handle)); if (!request) return FASTLY_STATUS_BADF;
|
|
488
498
|
request.cacheOverride = Number(tag); return FASTLY_STATUS_OK;
|
package/src/toolchain/index.js
CHANGED
|
@@ -168,6 +168,7 @@ function createDriver() {
|
|
|
168
168
|
version: 'pulse.provider-target-descriptor.v1',
|
|
169
169
|
target: 'native',
|
|
170
170
|
targetId: 'fastly-compute-native',
|
|
171
|
+
hostExtensions: require('../build/host-extensions.js').ADMISSION,
|
|
171
172
|
runtimeClass: 'native',
|
|
172
173
|
status: 'supported',
|
|
173
174
|
capabilities: Object.freeze([
|
|
@@ -361,7 +362,7 @@ function createDriver() {
|
|
|
361
362
|
providerPackage: providerBuild.packageFile,
|
|
362
363
|
providerSourceEntry: providerBuild.sourceEntryFile
|
|
363
364
|
}),
|
|
364
|
-
build: { ...providerBuild.build, reportAttribution: native.reportAttribution, buildObservation: native.buildObservation },
|
|
365
|
+
build: { ...providerBuild.build, reportAttribution: native.reportAttribution, hostExtensions: native.manifest.hostExtensions, buildObservation: native.buildObservation },
|
|
365
366
|
realization: publicRealization,
|
|
366
367
|
packaging: audit ? Object.freeze({
|
|
367
368
|
version: PROVIDER_PACKAGING_AUDIT_VERSION,
|
|
@@ -28,9 +28,11 @@ function prepareFastlyNativeExecution(invocation) {
|
|
|
28
28
|
target: 'fastly-compute-native',
|
|
29
29
|
wasmSha256: native.manifest.wasm.sha256,
|
|
30
30
|
providerRealityValidated: false,
|
|
31
|
-
effectObservations: 'unavailable'
|
|
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.'
|
|
32
34
|
});
|
|
33
|
-
|
|
35
|
+
const execute = (options = {}) => {
|
|
34
36
|
if (options.liveFetch === true) throw new TypeError('Fastly Native fixture execution cannot perform live fetches.');
|
|
35
37
|
const kvStores = {};
|
|
36
38
|
for (const [logical, physical] of Object.entries(bindings.kv || {})) {
|
|
@@ -73,6 +75,9 @@ function prepareFastlyNativeExecution(invocation) {
|
|
|
73
75
|
evidence
|
|
74
76
|
});
|
|
75
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 }));
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
module.exports = { prepareFastlyNativeExecution };
|