@pulse-compute/provider-fastly 1.0.0-beta.7 → 1.0.0-beta.8
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 +2 -0
- package/src/build/entities-native.js +2 -0
- package/src/build/kv-native.as.ts +10 -6
- package/src/build/native-application-errors.js +14 -8
- package/src/build/native-fetch-deadlines.as.ts +161 -0
- package/src/build/native-fetch-deadlines.js +27 -0
- package/src/build/native-fetch-errors.js +40 -0
- package/src/build/native-header-lookup.js +54 -0
- package/src/build/native-http-effects.js +2 -1
- package/src/build/native-platform-capabilities.js +55 -16
- package/src/build/request-budget.js +3 -1
- package/src/testing/native-platform-capabilities-host.js +46 -16
- package/src/toolchain/index.js +3 -1
- package/src/toolchain/native-execution.js +1 -0
package/README.md
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
> **Supported entry points:** `@pulse-compute/provider-fastly`, `@pulse-compute/provider-fastly/contract`, `@pulse-compute/provider-fastly/toolchain`, `@pulse-compute/provider-fastly/config-schema`, `@pulse-compute/provider-fastly/runtime/canonical-api-runtime`, `@pulse-compute/provider-fastly/build/canonical-target`, `@pulse-compute/provider-fastly/testing/fastly-cli`<br>
|
|
8
8
|
> **Stability:** The listed entry points are supported for the Beta; all other exported subpaths are implementation-only.<br>
|
|
9
9
|
> **npm:** [`@pulse-compute/provider-fastly`](https://www.npmjs.com/package/@pulse-compute/provider-fastly)<br>
|
|
10
|
-
> **Canonical documentation:** [Package guide](https://pulsecompute.io/v1.0.0-beta.
|
|
10
|
+
> **Canonical documentation:** [Package guide](https://pulsecompute.io/v1.0.0-beta.8/packages/provider-fastly/)
|
|
11
11
|
>
|
|
12
|
-
> This release-status block is generated from the synchronized `Pulse 1.0.0-beta.
|
|
12
|
+
> This release-status block is generated from the synchronized `Pulse 1.0.0-beta.8` package policy.
|
|
13
13
|
<!-- pulse-package-status:end -->
|
|
14
14
|
|
|
15
15
|
Fastly provider configuration, canonical capability lowering, local conformance execution, Compute target generation, and explicit external-tool boundaries.
|
|
@@ -49,7 +49,7 @@ trusted wall-clock authority. It does not fall back to Node execution.
|
|
|
49
49
|
|
|
50
50
|
## Documentation
|
|
51
51
|
|
|
52
|
-
- [Fastly package guide](https://pulsecompute.io/v1.0.0-beta.
|
|
53
|
-
- [Project configuration](https://pulsecompute.io/v1.0.0-beta.
|
|
54
|
-
- [Fastly capabilities guide](https://pulsecompute.io/v1.0.0-beta.
|
|
55
|
-
- [Environment variables](https://pulsecompute.io/v1.0.0-beta.
|
|
52
|
+
- [Fastly package guide](https://pulsecompute.io/v1.0.0-beta.8/packages/provider-fastly/)
|
|
53
|
+
- [Project configuration](https://pulsecompute.io/v1.0.0-beta.8/reference/project-config/#fastly-provider-options)
|
|
54
|
+
- [Fastly capabilities guide](https://pulsecompute.io/v1.0.0-beta.8/guides/fastly-capabilities/)
|
|
55
|
+
- [Environment variables](https://pulsecompute.io/v1.0.0-beta.8/reference/environment/#pulse-fastly-bin)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pulse-compute/provider-fastly",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.8",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": "^22.14.0 || ^24.0.0"
|
|
@@ -59,15 +59,15 @@
|
|
|
59
59
|
}
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@pulse-compute/crypto": "1.0.0-beta.
|
|
63
|
-
"@pulse-compute/jwt": "1.0.0-beta.
|
|
64
|
-
"@pulse-compute/runtime": "1.0.0-beta.
|
|
65
|
-
"@pulse-compute/s3": "1.0.0-beta.
|
|
66
|
-
"@pulse-compute/wasm-build-support": "1.0.0-beta.
|
|
67
|
-
"@pulse-compute/wasm-contracts": "1.0.0-beta.
|
|
68
|
-
"@pulse-compute/wasm-guest-link": "1.0.0-beta.
|
|
69
|
-
"@pulse-compute/wasm-host-runtime": "1.0.0-beta.
|
|
70
|
-
"@pulse-compute/wasm-runtime-core-as": "1.0.0-beta.
|
|
62
|
+
"@pulse-compute/crypto": "1.0.0-beta.8",
|
|
63
|
+
"@pulse-compute/jwt": "1.0.0-beta.8",
|
|
64
|
+
"@pulse-compute/runtime": "1.0.0-beta.8",
|
|
65
|
+
"@pulse-compute/s3": "1.0.0-beta.8",
|
|
66
|
+
"@pulse-compute/wasm-build-support": "1.0.0-beta.8",
|
|
67
|
+
"@pulse-compute/wasm-contracts": "1.0.0-beta.8",
|
|
68
|
+
"@pulse-compute/wasm-guest-link": "1.0.0-beta.8",
|
|
69
|
+
"@pulse-compute/wasm-host-runtime": "1.0.0-beta.8",
|
|
70
|
+
"@pulse-compute/wasm-runtime-core-as": "1.0.0-beta.8",
|
|
71
71
|
"esbuild": "0.28.1"
|
|
72
72
|
},
|
|
73
73
|
"types": "./dist/index.d.ts",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"url": "git+https://github.com/pulse-compute/pulse.git",
|
|
88
88
|
"directory": "packages/provider-fastly"
|
|
89
89
|
},
|
|
90
|
-
"homepage": "https://pulsecompute.io/v1.0.0-beta.
|
|
90
|
+
"homepage": "https://pulsecompute.io/v1.0.0-beta.8/packages/provider-fastly/",
|
|
91
91
|
"bugs": {
|
|
92
92
|
"url": "https://github.com/pulse-compute/pulse/issues"
|
|
93
93
|
},
|
|
@@ -244,6 +244,7 @@ function inspectFastlyCanonicalTarget(options = {}) {
|
|
|
244
244
|
|| (options.native && options.native.guestUnits),
|
|
245
245
|
nativeOptimization: options.nativeOptimization
|
|
246
246
|
|| (options.experimentalNativeSize === true ? 'experimental-native-size' : undefined),
|
|
247
|
+
elideJsonCacheArena: options.elideJsonCacheArena,
|
|
247
248
|
emitWat: options.emitWat,
|
|
248
249
|
timeoutMs: options.compileTimeoutMs
|
|
249
250
|
});
|
|
@@ -320,6 +321,7 @@ function writeFastlyCanonicalTarget(options = {}) {
|
|
|
320
321
|
|| (options.native && options.native.guestUnits),
|
|
321
322
|
nativeOptimization: options.nativeOptimization
|
|
322
323
|
|| (options.experimentalNativeSize === true ? 'experimental-native-size' : undefined),
|
|
324
|
+
elideJsonCacheArena: options.elideJsonCacheArena,
|
|
323
325
|
emitWat: options.emitWat,
|
|
324
326
|
compileTimeoutMs: options.compileTimeoutMs
|
|
325
327
|
});
|
|
@@ -496,6 +496,7 @@ function compileFastlyEntitiesNative(sourceArtifact, options = {}) {
|
|
|
496
496
|
'--path', path.join(compilerFallback, 'node_modules'),
|
|
497
497
|
'--path', path.dirname(jsonAsRoot)
|
|
498
498
|
];
|
|
499
|
+
require('@pulse-compute/wasm-build-support/native-optimization').appendJsonCacheArenaArgs(args, options.elideJsonCacheArena, true);
|
|
499
500
|
const startedAt = Date.now();
|
|
500
501
|
const result = spawnSync(asc.executable, args, {
|
|
501
502
|
cwd: staging,
|
|
@@ -546,6 +547,7 @@ function compileFastlyEntitiesNative(sourceArtifact, options = {}) {
|
|
|
546
547
|
package: 'assemblyscript',
|
|
547
548
|
version: JSON.parse(fs.readFileSync(path.join(asc.packageRoot, 'package.json'), 'utf8')).version,
|
|
548
549
|
jsonAs: jsonAsManifest.version,
|
|
550
|
+
elideDisabledCacheArena: options.elideJsonCacheArena === true,
|
|
549
551
|
durationMs: Date.now() - startedAt
|
|
550
552
|
}),
|
|
551
553
|
policy: generated.policy
|
|
@@ -80,12 +80,16 @@ function __kv_number(value: f64): string {
|
|
|
80
80
|
// Count the portable serialized value budget, including this boundary case.
|
|
81
81
|
return text.endsWith('.0') ? text.substring(0, text.length - 2) : text;
|
|
82
82
|
}
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
// Object field names are complete quoted tokens, not substring secrets: a field
|
|
84
|
+
// named "id" must not corrupt "considered", "invalidAcks", or numeric metrics.
|
|
85
|
+
// Values, lookup keys and generations retain raw and JSON-escaped protection.
|
|
86
|
+
function __kv_private(text: string, field: bool = false): string {
|
|
87
87
|
const encoded = __pulse_fastly_quote(text);
|
|
88
|
-
__pulse_fastly_remember_secret(encoded
|
|
88
|
+
if (field) __pulse_fastly_remember_secret(encoded, true);
|
|
89
|
+
else {
|
|
90
|
+
__pulse_fastly_remember_secret(text);
|
|
91
|
+
__pulse_fastly_remember_secret(encoded.substring(1, encoded.length - 1));
|
|
92
|
+
}
|
|
89
93
|
return encoded;
|
|
90
94
|
}
|
|
91
95
|
class __KvEncoder {
|
|
@@ -107,7 +111,7 @@ class __KvEncoder {
|
|
|
107
111
|
const object = value.kind == PULSE_VALUE_OBJECT; this.add(object ? '{' : '[');
|
|
108
112
|
for (let i = 0; i < value.values.length && !this.reason.length; i++) {
|
|
109
113
|
if (i) this.add(',');
|
|
110
|
-
if (object) this.add(__kv_private(value.keys[i]) + ':');
|
|
114
|
+
if (object) this.add(__kv_private(value.keys[i], true) + ':');
|
|
111
115
|
this.visit(value.values[i], depth + 1);
|
|
112
116
|
}
|
|
113
117
|
this.add(object ? '}' : ']');
|
|
@@ -4,8 +4,13 @@ function enabled(plan) {
|
|
|
4
4
|
return (plan.routing?.entries || []).some(entry => entry.kind === 'error');
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
// Fetch-only applications also drain failed groups before their terminal response.
|
|
8
|
+
function settlementEnabled(plan) {
|
|
9
|
+
return enabled(plan) || (!plan.packages?.application && (plan.effects || []).some(effect => effect.kind === 'fetch'));
|
|
10
|
+
}
|
|
11
|
+
|
|
7
12
|
// Capture provider diagnostics before clearing them. A failed continuation is
|
|
8
|
-
// drained once; only
|
|
13
|
+
// drained once; only an admitted application error can enter the next Router lane.
|
|
9
14
|
function runtimeSource(plan) {
|
|
10
15
|
const jwt = ['TOKEN_REQUIRED', 'BEARER_INVALID', 'MALFORMED', 'LIMIT_EXCEEDED',
|
|
11
16
|
'ALGORITHM_NOT_ALLOWED', 'KEY_INVALID', 'SIGNATURE_INVALID', 'CLOCK_INVALID',
|
|
@@ -14,6 +19,7 @@ function runtimeSource(plan) {
|
|
|
14
19
|
let __pulse_application_schema: i32 = 0
|
|
15
20
|
let __pulse_application_failed_schema: i32 = 0
|
|
16
21
|
function __pulse_application_code(): string {
|
|
22
|
+
if (__pulse_fastly_last_error == PULSE_ERROR_TRANSPORT && __pulse_fetch_failed()) return __pulse_fastly_error_stage == 271 ? "PULSE_FETCH_TIMEOUT" : "PULSE_FETCH_NETWORK"
|
|
17
23
|
if (__pulse_fastly_last_error == PULSE_ERROR_REQUEST_BODY)
|
|
18
24
|
return __pulse_fastly_error_stage == 21 ? "PULSE_BODY_TOO_LARGE" : "PULSE_REQUEST_BODY_INVALID_UTF8"
|
|
19
25
|
if (__pulse_fastly_jwt_error > 0) {
|
|
@@ -40,7 +46,7 @@ function __pulse_application_clear(): void {
|
|
|
40
46
|
__pulse_fastly_last_error = 0; __pulse_fastly_error_stage = 0; __pulse_fastly_error_effect = -1
|
|
41
47
|
__pulse_fastly_jwt_error = 0; __pulse_application_failed_schema = 0
|
|
42
48
|
}
|
|
43
|
-
${(plan?.effects || []).length > 0 ? settlementSource() : ''}
|
|
49
|
+
${(plan?.effects || []).length > 0 ? settlementSource(plan) : ''}
|
|
44
50
|
function host_router_error_take(): i32 {
|
|
45
51
|
if (__pulse_fastly_last_error == 0) return 0
|
|
46
52
|
const code = __pulse_application_code()
|
|
@@ -52,7 +58,7 @@ function host_router_error_take(): i32 {
|
|
|
52
58
|
const error = host_value_object()
|
|
53
59
|
host_value_object_set(error, __pulse_fastly_string_value("code"), __pulse_fastly_string_value(code))
|
|
54
60
|
host_value_object_set(error, __pulse_fastly_string_value("name"), __pulse_fastly_string_value("PulseApplicationError"))
|
|
55
|
-
host_value_object_set(error, __pulse_fastly_string_value("message"), __pulse_fastly_string_value("Pulse application data validation failed."))
|
|
61
|
+
host_value_object_set(error, __pulse_fastly_string_value("message"), __pulse_fastly_string_value(code == "PULSE_FETCH_TIMEOUT" ? "Fetch header deadline exceeded." : code == "PULSE_FETCH_NETWORK" ? "Fetch transport failed." : "Pulse application data validation failed."))
|
|
56
62
|
return error
|
|
57
63
|
}
|
|
58
64
|
`;
|
|
@@ -61,7 +67,7 @@ function host_router_error_take(): i32 {
|
|
|
61
67
|
// The caller retains the static pending-slot guards. The shared body owns one
|
|
62
68
|
// site's resolution and authenticated settlement; diagnostics carry the saved
|
|
63
69
|
// failure between calls without allocating an accumulator or merging slot state.
|
|
64
|
-
function settlementSource() {
|
|
70
|
+
function settlementSource(plan) {
|
|
65
71
|
return `
|
|
66
72
|
// Returns 0 for recoverable/no failure, 1 for fatal, -1 for rejected settlement.
|
|
67
73
|
function __pulse_application_settle_effect(index: i32, fatal: bool): i32 {
|
|
@@ -78,7 +84,7 @@ function __pulse_application_settle_effect(index: i32, fatal: bool): i32 {
|
|
|
78
84
|
if (result <= 0 && __pulse_fastly_last_error == 0) __pulse_fastly_fail(PULSE_ERROR_STATE, 101, index)
|
|
79
85
|
} else if (failure == 0) __pulse_fastly_fail(PULSE_ERROR_STATE, 101, index)
|
|
80
86
|
if (__pulse_fastly_last_error != 0) {
|
|
81
|
-
const terminal = __pulse_application_code().length == 0
|
|
87
|
+
const terminal = ${enabled(plan) ? '__pulse_application_code().length == 0' : 'true'}
|
|
82
88
|
if (failure == 0 || (!fatal && terminal)) {
|
|
83
89
|
failure = __pulse_fastly_last_error; stage = __pulse_fastly_error_stage; effect = __pulse_fastly_error_effect
|
|
84
90
|
jwt = __pulse_fastly_jwt_error; schema = __pulse_application_failed_schema; fatal = terminal
|
|
@@ -99,13 +105,13 @@ function __pulse_application_settle_effect(index: i32, fatal: bool): i32 {
|
|
|
99
105
|
// Preserve the fatal-return boundary consumed by request-budget instrumentation.
|
|
100
106
|
function driverLoop(plan) {
|
|
101
107
|
return ` while (runStatus == 1) {
|
|
102
|
-
let fatal = __pulse_fastly_last_error != 0 && __pulse_application_code().length == 0
|
|
108
|
+
let fatal = __pulse_fastly_last_error != 0${enabled(plan) ? ' && __pulse_application_code().length == 0' : ''}
|
|
103
109
|
${(plan.effects || []).map((_, index) => ` if (__pulse_effect_pending_${index} != 0) {
|
|
104
110
|
const settled = __pulse_application_settle_effect(${index}, fatal)
|
|
105
111
|
if (settled < 0) return
|
|
106
112
|
fatal = settled != 0
|
|
107
113
|
}`).join('\n')}
|
|
108
|
-
if (fatal) return
|
|
114
|
+
${enabled(plan) ? 'if (fatal) return' : 'if (fatal) { __pulse_fastly_jwt_send_error(); return }'}
|
|
109
115
|
runStatus = pulse_resume()
|
|
110
116
|
}`;
|
|
111
117
|
}
|
|
@@ -137,4 +143,4 @@ function instrument(source) {
|
|
|
137
143
|
return source;
|
|
138
144
|
}
|
|
139
145
|
|
|
140
|
-
module.exports = Object.freeze({ enabled, runtimeSource, driverLoop, instrument });
|
|
146
|
+
module.exports = Object.freeze({ enabled, settlementEnabled, runtimeSource, driverLoop, instrument });
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// Per-invocation, per-admission header deadlines. A reused effect site gets a new
|
|
2
|
+
// clock sample; waiting on an earlier group member cannot replenish this budget.
|
|
3
|
+
@external('fastly_async_io', 'select')
|
|
4
|
+
declare function __pulse_fetch_select(handles: usize, count: i32, timeout: i32, done: usize): i32
|
|
5
|
+
@external('fastly_http_req', 'pending_req_poll_v2')
|
|
6
|
+
declare function __pulse_fetch_poll(handle: i32, detail: usize, done: usize, response: usize, body: usize): i32
|
|
7
|
+
@external('fastly_http_resp', 'close')
|
|
8
|
+
declare function __pulse_fetch_close_response(handle: i32): i32
|
|
9
|
+
const __pulse_fetch_timeout = new StaticArray<f64>(PULSE_FASTLY_EFFECT_COUNT)
|
|
10
|
+
const __pulse_fetch_started = new StaticArray<i64>(PULSE_FASTLY_EFFECT_COUNT)
|
|
11
|
+
const __pulse_fetch_completed = new StaticArray<bool>(PULSE_FASTLY_EFFECT_COUNT)
|
|
12
|
+
// Zero = headers, positive = Fastly status, -1 = timeout, -2 = protocol/clock.
|
|
13
|
+
const __pulse_fetch_outcome = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT)
|
|
14
|
+
const __pulse_fetch_retired = new Array<i32>()
|
|
15
|
+
let __pulse_fetch_last_clock: i64 = -1
|
|
16
|
+
let __pulse_fetch_retired_total: i32 = 0
|
|
17
|
+
export function pulse_fastly_fetch_retired(): i32 { return __pulse_fetch_retired_total }
|
|
18
|
+
export function pulse_fastly_fetch_unreaped(): i32 { return __pulse_fetch_retired.length }
|
|
19
|
+
function __pulse_fetch_clock(index: i32): i64 {
|
|
20
|
+
const out = new StaticArray<i64>(1)
|
|
21
|
+
if (wasi_snapshot_preview1_clock_time_get(1, 1, changetype<usize>(out)) != 0
|
|
22
|
+
|| out[0] < 0 || out[0] < __pulse_fetch_last_clock) {
|
|
23
|
+
__pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 272, index); return -1
|
|
24
|
+
}
|
|
25
|
+
__pulse_fetch_last_clock = out[0]; return out[0]
|
|
26
|
+
}
|
|
27
|
+
function __pulse_fetch_begin_deadline(index: i32, init: __PulseFastlyValue): void {
|
|
28
|
+
__pulse_fetch_timeout[index] = 0; __pulse_fetch_completed[index] = false; __pulse_fetch_outcome[index] = 0
|
|
29
|
+
const field = __pulse_fastly_find(init, 'timeoutMs')
|
|
30
|
+
if (field < 0) return
|
|
31
|
+
const value = __pulse_fastly_value(unchecked(init.values[field]))
|
|
32
|
+
if (value.kind == PULSE_VALUE_UNDEFINED) return
|
|
33
|
+
const ms = value.number
|
|
34
|
+
if (value.kind != PULSE_VALUE_NUMBER || ms <= 0 || ms > 9007199254740991 || Math.floor(ms) != ms) {
|
|
35
|
+
__pulse_fastly_fail(PULSE_ERROR_VALUE, 270, index); return
|
|
36
|
+
}
|
|
37
|
+
__pulse_fetch_timeout[index] = ms
|
|
38
|
+
__pulse_fetch_started[index] = __pulse_fetch_clock(index)
|
|
39
|
+
}
|
|
40
|
+
function __pulse_fetch_remaining(index: i32, now: i64): f64 {
|
|
41
|
+
if (__pulse_fetch_timeout[index] == 0) return 2147483647.0
|
|
42
|
+
// Subtract elapsed nanoseconds before converting. Do not overflow by adding
|
|
43
|
+
// a safe-integer millisecond option to an i64 nanosecond clock.
|
|
44
|
+
return __pulse_fetch_timeout[index] - f64(now - __pulse_fetch_started[index]) / 1000000.0
|
|
45
|
+
}
|
|
46
|
+
function __pulse_fetch_before_send(index: i32): bool {
|
|
47
|
+
if (__pulse_fastly_effect_kind(index) != 1 || __pulse_fetch_timeout[index] == 0) return true
|
|
48
|
+
const now = __pulse_fetch_clock(index)
|
|
49
|
+
if (now < 0) return false
|
|
50
|
+
if (__pulse_fetch_remaining(index, now) > 0) return true
|
|
51
|
+
__pulse_fastly_fail(PULSE_ERROR_TRANSPORT, 271, index); return false
|
|
52
|
+
}
|
|
53
|
+
function __pulse_fetch_active(index: i32): bool {
|
|
54
|
+
return __pulse_fastly_effect_kind(index) == 1 && __pulse_fastly_pending_mode[index] == PULSE_FASTLY_PENDING_ASYNC
|
|
55
|
+
}
|
|
56
|
+
function __pulse_fetch_has_deadlines(): bool {
|
|
57
|
+
for (let i = 0; i < PULSE_FASTLY_EFFECT_COUNT; i++)
|
|
58
|
+
if (__pulse_fetch_active(i) && __pulse_fetch_timeout[i] > 0) return true
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
function __pulse_fetch_complete(index: i32, outcome: i32, result: i32 = 0): void {
|
|
62
|
+
__pulse_fastly_pending_mode[index] = PULSE_FASTLY_PENDING_READY
|
|
63
|
+
__pulse_fastly_pending[index] = 0; __pulse_fastly_ready[index] = result
|
|
64
|
+
__pulse_fetch_completed[index] = true; __pulse_fetch_outcome[index] = outcome
|
|
65
|
+
}
|
|
66
|
+
function __pulse_fetch_retire(index: i32, outcome: i32): void {
|
|
67
|
+
// The Fastly ABI has no pending-request cancellation hostcall. Retain explicit
|
|
68
|
+
// ownership until invocation cleanup; never wait, retry, or claim rollback.
|
|
69
|
+
__pulse_fetch_retired.push(__pulse_fastly_pending[index]); __pulse_fetch_retired_total++
|
|
70
|
+
__pulse_fetch_complete(index, outcome)
|
|
71
|
+
}
|
|
72
|
+
function __pulse_fetch_retire_all(): void {
|
|
73
|
+
for (let i = 0; i < PULSE_FASTLY_EFFECT_COUNT; i++) if (__pulse_fetch_active(i)) __pulse_fetch_retire(i, -2)
|
|
74
|
+
}
|
|
75
|
+
function __pulse_fetch_reap(): void {
|
|
76
|
+
const detail = new StaticArray<i32>(4), done = new StaticArray<i32>(1)
|
|
77
|
+
const response = new StaticArray<i32>(1), body = new StaticArray<i32>(1)
|
|
78
|
+
for (let i = __pulse_fetch_retired.length - 1; i >= 0; i--) {
|
|
79
|
+
done[0] = 0
|
|
80
|
+
const status = __pulse_fetch_poll(__pulse_fetch_retired[i], changetype<usize>(detail), changetype<usize>(done), changetype<usize>(response), changetype<usize>(body))
|
|
81
|
+
if (status == 0 && done[0] == 0) continue
|
|
82
|
+
if (status == 0 && done[0] == 1) { __pulse_fetch_close_response(response[0]); __pulse_fetch_close_body(body[0]) }
|
|
83
|
+
__pulse_fetch_retired[i] = __pulse_fetch_retired[__pulse_fetch_retired.length - 1]; __pulse_fetch_retired.pop()
|
|
84
|
+
}
|
|
85
|
+
// Unready handles remain platform-owned until host teardown. No guest task
|
|
86
|
+
// survives _start and no response/body is exposed for an expired operation.
|
|
87
|
+
}
|
|
88
|
+
// Request-budget instrumentation replaces these hooks when maxDurationMs exists.
|
|
89
|
+
function __pulse_fetch_request_check(index: i32): bool { return true }
|
|
90
|
+
function __pulse_fetch_request_remaining(): i32 { return 2147483647 }
|
|
91
|
+
function __pulse_fetch_take(index: i32): i32 {
|
|
92
|
+
const outcome = __pulse_fetch_outcome[index], result = __pulse_fastly_ready[index]
|
|
93
|
+
__pulse_fetch_completed[index] = false; __pulse_fastly_pending_mode[index] = PULSE_FASTLY_PENDING_NONE
|
|
94
|
+
__pulse_fastly_ready[index] = 0
|
|
95
|
+
if (outcome == -1) __pulse_fastly_fail(PULSE_ERROR_TRANSPORT, 271, index)
|
|
96
|
+
else if (outcome == -2) __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 272, index)
|
|
97
|
+
else if (outcome != 0) __pulse_fetch_fail(outcome, 81, index)
|
|
98
|
+
return outcome == 0 ? result : 0
|
|
99
|
+
}
|
|
100
|
+
function __pulse_fetch_wait(index: i32): i32 {
|
|
101
|
+
const handles = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT), selected = new StaticArray<i32>(1)
|
|
102
|
+
const detail = new StaticArray<i32>(4), done = new StaticArray<i32>(1)
|
|
103
|
+
const response = new StaticArray<i32>(1), body = new StaticArray<i32>(1)
|
|
104
|
+
while (!__pulse_fetch_completed[index]) {
|
|
105
|
+
if (!__pulse_fetch_request_check(index)) { __pulse_fetch_retire_all(); return 0 }
|
|
106
|
+
let now = __pulse_fetch_clock(index)
|
|
107
|
+
if (now < 0) { __pulse_fetch_retire_all(); return 0 }
|
|
108
|
+
let count = 0, remaining = 2147483647.0
|
|
109
|
+
// Observe all dispatched fetches, not just the first static slot. Otherwise
|
|
110
|
+
// a slow earlier sibling could turn timely later headers into a false timeout.
|
|
111
|
+
for (let i = 0; i < PULSE_FASTLY_EFFECT_COUNT; i++) {
|
|
112
|
+
if (!__pulse_fetch_active(i)) continue
|
|
113
|
+
if (__pulse_fetch_remaining(i, now) <= 0) { __pulse_fetch_retire(i, -1); continue }
|
|
114
|
+
done[0] = 0
|
|
115
|
+
const status = __pulse_fetch_poll(__pulse_fastly_pending[i], changetype<usize>(detail), changetype<usize>(done), changetype<usize>(response), changetype<usize>(body))
|
|
116
|
+
now = __pulse_fetch_clock(i)
|
|
117
|
+
if (status != 0) {
|
|
118
|
+
__pulse_fetch_complete(i, now < 0 ? -2 : __pulse_fetch_remaining(i, now) <= 0 ? -1 : status)
|
|
119
|
+
if (now < 0) { __pulse_fetch_retire_all(); return 0 }
|
|
120
|
+
continue
|
|
121
|
+
}
|
|
122
|
+
if (done[0] != 0 && done[0] != 1) {
|
|
123
|
+
__pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 272, i); __pulse_fetch_retire_all(); return 0
|
|
124
|
+
}
|
|
125
|
+
if (now < 0 || __pulse_fetch_remaining(i, now) <= 0) {
|
|
126
|
+
if (done[0] == 1) {
|
|
127
|
+
__pulse_fetch_close_response(response[0]); __pulse_fetch_close_body(body[0]); __pulse_fetch_complete(i, now < 0 ? -2 : -1)
|
|
128
|
+
} else __pulse_fetch_retire(i, now < 0 ? -2 : -1)
|
|
129
|
+
if (now < 0) { __pulse_fetch_retire_all(); return 0 }
|
|
130
|
+
continue
|
|
131
|
+
}
|
|
132
|
+
if (done[0] == 1) {
|
|
133
|
+
const value = new __PulseFastlyValue(); value.kind = PULSE_VALUE_FETCH
|
|
134
|
+
value.responseHandle = response[0]; value.bodyHandle = body[0]
|
|
135
|
+
__pulse_fetch_complete(i, 0, __pulse_fastly_put(value)); continue
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (__pulse_fetch_completed[index]) break
|
|
139
|
+
if (!__pulse_fetch_request_check(index)) { __pulse_fetch_retire_all(); return 0 }
|
|
140
|
+
now = __pulse_fetch_clock(index)
|
|
141
|
+
if (now < 0) { __pulse_fetch_retire_all(); return 0 }
|
|
142
|
+
for (let i = 0; i < PULSE_FASTLY_EFFECT_COUNT; i++) {
|
|
143
|
+
if (!__pulse_fetch_active(i)) continue
|
|
144
|
+
const budget = __pulse_fetch_remaining(i, now)
|
|
145
|
+
if (budget <= 0) { __pulse_fetch_retire(i, -1); continue }
|
|
146
|
+
handles[count++] = __pulse_fastly_pending[i]; remaining = Math.min(remaining, budget)
|
|
147
|
+
}
|
|
148
|
+
if (__pulse_fetch_completed[index]) break
|
|
149
|
+
remaining = Math.min(remaining, f64(__pulse_fetch_request_remaining()))
|
|
150
|
+
if (count == 0 || remaining <= 0) {
|
|
151
|
+
__pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 272, index); __pulse_fetch_retire_all(); return 0
|
|
152
|
+
}
|
|
153
|
+
selected[0] = -2
|
|
154
|
+
const status = __pulse_fetch_select(changetype<usize>(handles), count, i32(Math.ceil(remaining)), changetype<usize>(selected))
|
|
155
|
+
const after = __pulse_fetch_clock(index)
|
|
156
|
+
if (status != 0 || selected[0] < -1 || selected[0] >= count || after < 0 || (selected[0] == -1 && after <= now)) {
|
|
157
|
+
__pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 272, index); __pulse_fetch_retire_all(); return 0
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return __pulse_fetch_take(index)
|
|
161
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
function enabled(plan) { return (plan.effects || []).some(effect => effect.kind === 'fetch'); }
|
|
5
|
+
function runtimeSource(plan) {
|
|
6
|
+
if (!enabled(plan)) return `function __pulse_fetch_has_deadlines(): bool { return false }
|
|
7
|
+
function __pulse_fetch_request_check(index: i32): bool { return true }
|
|
8
|
+
function __pulse_fetch_request_remaining(): i32 { return 2147483647 }`;
|
|
9
|
+
return fs.readFileSync(path.join(__dirname,'native-fetch-deadlines.as.ts'),'utf8');
|
|
10
|
+
}
|
|
11
|
+
function instrument(input, plan) {
|
|
12
|
+
if (!enabled(plan)) return input;
|
|
13
|
+
let source = input;
|
|
14
|
+
const replace = (before,after) => {
|
|
15
|
+
if (!source.includes(before)) throw new Error('Fastly fetch deadline boundary drift: ' + before.slice(0,80));
|
|
16
|
+
source = source.replace(before,after);
|
|
17
|
+
};
|
|
18
|
+
replace('const init = initIndex >= 0 ? __pulse_fastly_value(unchecked(payload.values[initIndex])) : new __PulseFastlyValue(); let method',
|
|
19
|
+
'const init = initIndex >= 0 ? __pulse_fastly_value(unchecked(payload.values[initIndex])) : new __PulseFastlyValue(); __pulse_fetch_begin_deadline(effectIndex, init); if (__pulse_fastly_last_error != 0) return; let method');
|
|
20
|
+
replace('const backendBytes = __pulse_fastly_utf8(backend); const pendingOut',
|
|
21
|
+
'if (!__pulse_fetch_before_send(effectIndex)) { __pulse_fetch_close_request(requestHandle); __pulse_fetch_close_body(bodyHandle); return; } const backendBytes = __pulse_fastly_utf8(backend); const pendingOut');
|
|
22
|
+
replace('function __pulse_fastly_wait_fetch(effectIndex: i32): i32 {',
|
|
23
|
+
'function __pulse_fastly_wait_fetch(effectIndex: i32): i32 { if (__pulse_fastly_effect_kind(effectIndex) == 1 && (__pulse_fetch_completed[effectIndex] || __pulse_fetch_has_deadlines())) return __pulse_fetch_wait(effectIndex);');
|
|
24
|
+
replace('function __pulse_invocation_close(): void {', 'function __pulse_invocation_close(): void { __pulse_fetch_reap();');
|
|
25
|
+
return source;
|
|
26
|
+
}
|
|
27
|
+
module.exports = {runtimeSource,instrument};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function runtimeSource() {
|
|
4
|
+
return `
|
|
5
|
+
@external("fastly_http_req", "close")
|
|
6
|
+
declare function __pulse_fetch_close_request(handle: i32): i32
|
|
7
|
+
@external("fastly_http_body", "close")
|
|
8
|
+
declare function __pulse_fetch_close_body(handle: i32): i32
|
|
9
|
+
function __pulse_fetch_fail(status: i32, stage: i32, effect: i32): void {
|
|
10
|
+
// Fastly ERROR and HTTP parse/user/incomplete/size/status errors represent
|
|
11
|
+
// failed exchanges. Invalid arguments, handles and unsupported ABI stay fatal.
|
|
12
|
+
const network = __pulse_fastly_effect_kind(effect) != 1 || status == 1 || status == 7 || status == 8 || status == 9 || status == 11 || status == 12;
|
|
13
|
+
__pulse_fastly_fail(network ? PULSE_ERROR_TRANSPORT : PULSE_ERROR_HOSTCALL, stage, effect);
|
|
14
|
+
}
|
|
15
|
+
function __pulse_fetch_failed(): bool {
|
|
16
|
+
return (__pulse_fastly_last_error == PULSE_ERROR_TRANSPORT || __pulse_fastly_last_error == PULSE_ERROR_HOSTCALL || __pulse_fastly_last_error == PULSE_ERROR_VALUE)
|
|
17
|
+
&& (__pulse_fastly_error_stage == 77 || __pulse_fastly_error_stage == 81 || (__pulse_fastly_error_stage >= 270 && __pulse_fastly_error_stage <= 272))
|
|
18
|
+
&& __pulse_fastly_effect_kind(__pulse_fastly_error_effect) == 1;
|
|
19
|
+
}
|
|
20
|
+
function __pulse_fetch_send_error(): bool {
|
|
21
|
+
if (!__pulse_fetch_failed()) return false;
|
|
22
|
+
const output = new __PulseFastlyValue(); output.kind = PULSE_VALUE_RESPONSE;
|
|
23
|
+
const network = __pulse_fastly_last_error == PULSE_ERROR_TRANSPORT;
|
|
24
|
+
output.status = __pulse_fastly_error_stage == 270 ? 400 : __pulse_fastly_error_stage == 271 ? 504 : network ? 502 : 500;
|
|
25
|
+
output.text = __pulse_fastly_error_stage == 270 ? "Invalid fetch timeoutMs" : __pulse_fastly_error_stage == 271 ? "Gateway Timeout" : network ? "Bad Gateway" : "Internal Server Error";
|
|
26
|
+
output.headers.push(new __PulseFastlyHeader("content-type", "text/plain; charset=utf-8"));
|
|
27
|
+
__pulse_fastly_send_result(__pulse_fastly_put(output));
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
`;
|
|
31
|
+
}
|
|
32
|
+
function instrument(source) {
|
|
33
|
+
// Keep the fatal marker for the deadline owner, which runs after this pass.
|
|
34
|
+
source = source.replace('if (fatal) return',
|
|
35
|
+
'if (fatal && __pulse_fetch_failed()) { __pulse_fastly_jwt_send_error(); return; }\n if (fatal) return');
|
|
36
|
+
const marker = 'function __pulse_fastly_jwt_send_error(): void {';
|
|
37
|
+
if (!source.includes(marker)) throw new Error('Fastly fetch error boundary drift.');
|
|
38
|
+
return source.replace(marker, marker + ' if (__pulse_fetch_send_error()) return;');
|
|
39
|
+
}
|
|
40
|
+
module.exports = {runtimeSource, instrument};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function runtimeSource() {
|
|
4
|
+
return `
|
|
5
|
+
@external("fastly_http_resp", "header_values_get")
|
|
6
|
+
declare function __pulse_response_header_values(handle: i32, name: usize, length: i32, buffer: usize, size: i32, cursor: i32, end: usize, written: usize): i32
|
|
7
|
+
function __pulse_header_lookup_failed(): bool {
|
|
8
|
+
return __pulse_fastly_last_error == PULSE_ERROR_HOSTCALL && (__pulse_fastly_error_stage == 22 || __pulse_fastly_error_stage == 23);
|
|
9
|
+
}
|
|
10
|
+
function __pulse_header_lookup(handle: i32, name: string, response: bool): string | null {
|
|
11
|
+
const key = __pulse_fastly_utf8(name), buffer = new Uint8Array(PULSE_FASTLY_BUFFER_BYTES);
|
|
12
|
+
const end = new StaticArray<i64>(1), written = new StaticArray<i32>(1);
|
|
13
|
+
written[0] = -1;
|
|
14
|
+
// The single-value getter reports INVALID_ARGUMENT for absence on Viceroy.
|
|
15
|
+
// The list ABI distinguishes no values (zero bytes) from an empty value (NUL).
|
|
16
|
+
const status = response
|
|
17
|
+
? __pulse_response_header_values(handle, changetype<usize>(key), key.byteLength, buffer.dataStart, buffer.length, 0, changetype<usize>(end), changetype<usize>(written))
|
|
18
|
+
: __pulse_request_header_values(handle, changetype<usize>(key), key.byteLength, buffer.dataStart, buffer.length, 0, changetype<usize>(end), changetype<usize>(written));
|
|
19
|
+
const count = written[0], next = end[0];
|
|
20
|
+
if (status == FASTLY_STATUS_OK && count == 0 && (next == -1 || next == 0)) return null;
|
|
21
|
+
if (status != FASTLY_STATUS_OK || count <= 0 || count > buffer.length
|
|
22
|
+
|| (next != -1 && (next <= 0 || next > 4294967295)) || buffer[count - 1] != 0) {
|
|
23
|
+
__pulse_fastly_fail(PULSE_ERROR_HOSTCALL, response ? 23 : 22, -1); return null;
|
|
24
|
+
}
|
|
25
|
+
// Pulse's singular accessor returns the first value, including an empty one.
|
|
26
|
+
for (let i = 0; i < count; i++) {
|
|
27
|
+
if (buffer[i] == 0) return String.UTF8.decodeUnsafe(buffer.dataStart, i, false);
|
|
28
|
+
}
|
|
29
|
+
__pulse_fastly_fail(PULSE_ERROR_HOSTCALL, response ? 23 : 22, -1); return null;
|
|
30
|
+
}
|
|
31
|
+
function __pulse_header_send_error(): bool {
|
|
32
|
+
if (!__pulse_header_lookup_failed()) return false;
|
|
33
|
+
const output = new __PulseFastlyValue(); output.kind = PULSE_VALUE_RESPONSE;
|
|
34
|
+
output.status = 500; output.text = "Internal Server Error";
|
|
35
|
+
output.headers.push(new __PulseFastlyHeader("content-type", "text/plain; charset=utf-8"));
|
|
36
|
+
// Keep the original hostcall stage available to diagnostics.
|
|
37
|
+
__pulse_fastly_send_result(__pulse_fastly_put(output));
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
`;
|
|
41
|
+
}
|
|
42
|
+
function instrument(source) {
|
|
43
|
+
source = source.replace(/function (host_\w+)\(([^)]*)\): (i32|void) \{/g,
|
|
44
|
+
(declaration, name, _args, result) => name === 'host_router_error_take' ? declaration
|
|
45
|
+
: `${declaration} if (__pulse_header_lookup_failed()) return${result === 'void' ? '' : ' 0'};`);
|
|
46
|
+
// Header reads performed while settling an effect have a separate fatal exit.
|
|
47
|
+
// Retain its marker so request-budget instrumentation still owns timeout priority.
|
|
48
|
+
source = source.replace('if (fatal) return',
|
|
49
|
+
'if (fatal && __pulse_header_lookup_failed()) { __pulse_fastly_jwt_send_error(); return; }\n if (fatal) return');
|
|
50
|
+
const marker = 'function __pulse_fastly_jwt_send_error(): void {';
|
|
51
|
+
if (!source.includes(marker)) throw new Error('Fastly header-lookup error boundary drift.');
|
|
52
|
+
return source.replace(marker, marker + ' if (__pulse_header_send_error()) return;');
|
|
53
|
+
}
|
|
54
|
+
module.exports = {runtimeSource, instrument};
|
|
@@ -880,6 +880,7 @@ function compileFastlyNativeHttpEffectsPlan(plan, options = {}) {
|
|
|
880
880
|
'--path', jsonAs.dependencyRoot
|
|
881
881
|
);
|
|
882
882
|
}
|
|
883
|
+
require('@pulse-compute/wasm-build-support/native-optimization').appendJsonCacheArenaArgs(args, options.elideJsonCacheArena, schemaCodecsActive);
|
|
883
884
|
const startedAt = Date.now();
|
|
884
885
|
const result = spawnSync(asc.executable, args, {
|
|
885
886
|
cwd: stagingDir,
|
|
@@ -910,7 +911,7 @@ function compileFastlyNativeHttpEffectsPlan(plan, options = {}) {
|
|
|
910
911
|
compilerVersion: FASTLY_NATIVE_HTTP_EFFECTS_COMPILER_VERSION,
|
|
911
912
|
assemblyScript: Object.freeze({ package: 'assemblyscript', version: require(path.join(asc.packageRoot, 'package.json')).version }),
|
|
912
913
|
jsonAs: jsonAs
|
|
913
|
-
? Object.freeze({ package: 'json-as', version: jsonAs.version, transform: true, strict: true, mode: 'NAIVE', fastPath: false })
|
|
914
|
+
? Object.freeze({ package: 'json-as', version: jsonAs.version, transform: true, strict: true, mode: 'NAIVE', fastPath: false, elideDisabledCacheArena: options.elideJsonCacheArena === true })
|
|
914
915
|
: undefined,
|
|
915
916
|
wasm: Object.freeze({ bytes: inspection.bytes, sha256: inspection.sha256, magic: inspection.magic }),
|
|
916
917
|
wat: Object.freeze({ bytes: Buffer.byteLength(wat), sha256: sha256(wat) }),
|
|
@@ -126,6 +126,7 @@ const FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORTS = Object.freeze([
|
|
|
126
126
|
Object.freeze(['fastly_http_resp', 'new']),
|
|
127
127
|
Object.freeze(['fastly_http_resp', 'header_append']),
|
|
128
128
|
Object.freeze(['fastly_http_resp', 'header_value_get']),
|
|
129
|
+
Object.freeze(['fastly_http_resp', 'header_values_get']),
|
|
129
130
|
Object.freeze(['fastly_http_resp', 'status_get']),
|
|
130
131
|
Object.freeze(['fastly_http_resp', 'status_set']),
|
|
131
132
|
Object.freeze(['fastly_http_resp', 'send_downstream']),
|
|
@@ -988,6 +989,7 @@ function requiredImportsForPlan(plan, bindings) {
|
|
|
988
989
|
'fastly_http_req:header_insert', 'fastly_http_req:send_async', 'fastly_http_req:pending_req_wait'
|
|
989
990
|
]) keys.add(key);
|
|
990
991
|
}
|
|
992
|
+
if (kinds.has('fetch')) for (const key of ['wasi_snapshot_preview1:clock_time_get', 'fastly_async_io:select', 'fastly_http_req:pending_req_poll_v2', 'fastly_http_resp:close', 'fastly_http_body:close']) keys.add(key);
|
|
991
993
|
if (planRequiresFetchBodyRead(plan)) keys.add('fastly_http_body:read');
|
|
992
994
|
if (kinds.has('config.get')) for (const key of ['fastly_config_store:open', 'fastly_config_store:get']) keys.add(key);
|
|
993
995
|
const gripAuthenticationRequired = kinds.has('grip.broadcast')
|
|
@@ -1651,6 +1653,7 @@ const PULSE_FASTLY_PENDING_READY: i32 = 2
|
|
|
1651
1653
|
@lazy const __pulse_fastly_ready = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT)
|
|
1652
1654
|
@lazy const __pulse_fastly_grip_channels = new Array<string>()
|
|
1653
1655
|
@lazy const __pulse_fastly_redactions = new Array<string>()
|
|
1656
|
+
@lazy const __pulse_fastly_field_redactions = new Array<string>()
|
|
1654
1657
|
let __pulse_fastly_request_handle: i32 = 0
|
|
1655
1658
|
let __pulse_fastly_request_body_handle: i32 = 0
|
|
1656
1659
|
let __pulse_fastly_request_method: string = ""
|
|
@@ -1695,9 +1698,31 @@ function __pulse_fastly_string_value(value: string): i32 {
|
|
|
1695
1698
|
function __pulse_fastly_find(object: __PulseFastlyValue, key: string): i32 { for (let i = 0; i < object.keys.length; i += 1) if (unchecked(object.keys[i]) == key) return i; return -1 }
|
|
1696
1699
|
function __pulse_fastly_number_string(value: f64): string { if (value >= -9007199254740991.0 && value <= 9007199254740991.0 && Math.floor(value) == value) return i64(value).toString(); return value.toString() }
|
|
1697
1700
|
function __pulse_fastly_string(handle: i32): string { const value = __pulse_fastly_value(handle); if (value.kind == PULSE_VALUE_STRING) return value.text; if (value.kind == PULSE_VALUE_NUMBER) return __pulse_fastly_number_string(value.number); if (value.kind == PULSE_VALUE_BOOLEAN) return value.boolean != 0 ? "true" : "false"; if (value.kind == PULSE_VALUE_NULL) return "null"; if (value.kind == PULSE_VALUE_UNDEFINED) return "undefined"; return __pulse_fastly_json(handle, 0) }
|
|
1698
|
-
function __pulse_fastly_remember_secret(value: string): string { if (value.length == 0) return value; for (let i = 0; i <
|
|
1701
|
+
function __pulse_fastly_remember_secret(value: string, field: bool = false): string { if (value.length == 0) return value; const values = field ? __pulse_fastly_field_redactions : __pulse_fastly_redactions; for (let i = 0; i < values.length; i += 1) if (unchecked(values[i]) == value) return value; values.push(value); return value }
|
|
1699
1702
|
function __pulse_fastly_replace_secret(input: string, secret: string): string { if (secret.length == 0) return input; let source = input; let output = ""; while (true) { const index = source.indexOf(secret); if (index < 0) return output + source; output += source.substring(0, index) + "<redacted>"; source = source.substring(index + secret.length) } }
|
|
1700
|
-
|
|
1703
|
+
// Walk complete JSON string tokens, including escaped quotes. Substring matching
|
|
1704
|
+
// a quoted field would still corrupt an unrelated token such as "prefix\\\"id".
|
|
1705
|
+
function __pulse_fastly_redact_fields(input: string): string {
|
|
1706
|
+
if (__pulse_fastly_field_redactions.length == 0) return input;
|
|
1707
|
+
let output = "", start = 0, cursor = 0;
|
|
1708
|
+
while (cursor < input.length) {
|
|
1709
|
+
if (input.charCodeAt(cursor) != 34) { cursor += 1; continue; }
|
|
1710
|
+
const begin = cursor++;
|
|
1711
|
+
while (cursor < input.length) {
|
|
1712
|
+
const code = input.charCodeAt(cursor++);
|
|
1713
|
+
if (code == 92 && cursor < input.length) cursor += 1;
|
|
1714
|
+
else if (code == 34) break;
|
|
1715
|
+
}
|
|
1716
|
+
const token = input.substring(begin, cursor);
|
|
1717
|
+
for (let i = 0; i < __pulse_fastly_field_redactions.length; i += 1) {
|
|
1718
|
+
if (token != unchecked(__pulse_fastly_field_redactions[i])) continue;
|
|
1719
|
+
output += input.substring(start, begin) + __pulse_fastly_quote("<redacted>");
|
|
1720
|
+
start = cursor; break;
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
return output + input.substring(start);
|
|
1724
|
+
}
|
|
1725
|
+
function __pulse_fastly_redact(input: string): string { let output = __pulse_fastly_redact_fields(input); for (let i = 0; i < __pulse_fastly_redactions.length; i += 1) output = __pulse_fastly_replace_secret(output, unchecked(__pulse_fastly_redactions[i])); return output }
|
|
1701
1726
|
function host_log(level: i32, messageHandle: i32): void { let endpoint = __pulse_fastly_log_handle; if (endpoint == -2) return; if (endpoint == -1) { const name = __pulse_fastly_utf8("stdout"); const out = __pulse_fastly_out_i32(); if (fastly_log_endpoint_get(changetype<usize>(name), name.byteLength, changetype<usize>(out)) != FASTLY_STATUS_OK) { __pulse_fastly_log_handle = -2; return } endpoint = __pulse_fastly_out_value(out); __pulse_fastly_log_handle = endpoint } let label = "unknown"; if (level == 1) label = "error"; else if (level == 2) label = "warn"; else if (level == 3) label = "info"; else if (level == 4) label = "debug"; const bytes = __pulse_fastly_utf8("[pulse:" + label + "] " + __pulse_fastly_redact(__pulse_fastly_string(messageHandle)) + "\n"); const written = __pulse_fastly_out_i32(); fastly_log_write(endpoint, changetype<usize>(bytes), bytes.byteLength, changetype<usize>(written)) }
|
|
1702
1727
|
function __pulse_fastly_number(handle: i32): f64 { const value = __pulse_fastly_value(handle); if (value.kind == PULSE_VALUE_NUMBER) return value.number; if (value.kind == PULSE_VALUE_BOOLEAN) return value.boolean != 0 ? 1.0 : 0.0; if (value.kind == PULSE_VALUE_STRING) return F64.parseFloat(value.text); return 0.0 }
|
|
1703
1728
|
function __pulse_fastly_hex(value: i32): string { return String.fromCharCode(value < 10 ? 48 + value : 87 + value) }
|
|
@@ -1768,8 +1793,8 @@ function __pulse_fastly_read_body(handle: i32, effectIndex: i32, requestText: bo
|
|
|
1768
1793
|
if (requestText && !__pulse_request_utf8_valid(bytes)) { __pulse_fastly_fail(PULSE_ERROR_REQUEST_BODY, 24, effectIndex); return "" }
|
|
1769
1794
|
return __pulse_fastly_decode(bytes, total);
|
|
1770
1795
|
}
|
|
1771
|
-
function __pulse_fastly_request_header_text(name: string): string { const
|
|
1772
|
-
function __pulse_fastly_fetch_header_text(responseHandle: i32, name: string): string { const
|
|
1796
|
+
function __pulse_fastly_request_header_text(name: string): string { const value = __pulse_header_lookup(__pulse_fastly_request_handle, name, false); return value === null ? "" : value }
|
|
1797
|
+
function __pulse_fastly_fetch_header_text(responseHandle: i32, name: string): string { const value = __pulse_header_lookup(responseHandle, name, true); return value === null ? "" : value }
|
|
1773
1798
|
function __pulse_fastly_fetch_body(value: __PulseFastlyValue): string { if (value.bodyLoaded == 0) { value.text = __pulse_fastly_read_body(value.bodyHandle, -1); value.bodyLoaded = 1 } return value.text }
|
|
1774
1799
|
|
|
1775
1800
|
function __pulse_fastly_scalar(kind: i32, boolean: i32, slot: i32): i32 {
|
|
@@ -1819,7 +1844,7 @@ function host_router_match(path: i32, pattern: i32): i32 { return host_value_boo
|
|
|
1819
1844
|
function host_router_param(path: i32, pattern: i32, name: i32): i32 { const value = __pulse_router_param_text(__pulse_fastly_string(path), __pulse_fastly_string(pattern), __pulse_fastly_string(name)); return value === null ? host_value_undefined() : __pulse_fastly_string_value(value) }
|
|
1820
1845
|
function host_request_path(): i32 { return __pulse_fastly_string_value(__pulse_fastly_request_path) }
|
|
1821
1846
|
function host_request_headers(): i32 { return __pulse_request_headers_read() }
|
|
1822
|
-
function host_request_header(name: i32): i32 { const value =
|
|
1847
|
+
function host_request_header(name: i32): i32 { const value = __pulse_header_lookup(__pulse_fastly_request_handle, __pulse_fastly_string(name), false); return value === null ? host_value_undefined() : __pulse_fastly_string_value(value) }
|
|
1823
1848
|
function host_request_text(): i32 { if (__pulse_request_body_failure != 0) { __pulse_fastly_fail(PULSE_ERROR_REQUEST_BODY, __pulse_request_body_failure, -1); return 0 } if (__pulse_fastly_request_body_loaded == 0) { __pulse_fastly_request_body = __pulse_fastly_read_body(__pulse_fastly_request_body_handle, -1, true); __pulse_fastly_request_body_loaded = 1; if (__pulse_fastly_last_error == PULSE_ERROR_REQUEST_BODY) __pulse_request_body_failure = __pulse_fastly_error_stage } if (__pulse_fastly_last_error != 0) return 0; return __pulse_fastly_string_value(__pulse_fastly_request_body) }
|
|
1824
1849
|
function host_request_json(schema: i32): i32 { const parsed = ${hasJsonAdmission(plan) ? '__pulse_fastly_schema_parse_json(__pulse_fastly_string(host_request_text()), schema)' : '__pulse_fastly_parse_json(__pulse_fastly_string(host_request_text()))'}; if (parsed <= 0) return 0; const schemaValue = __pulse_fastly_value(schema); return schemaValue.kind == PULSE_VALUE_STRING ? __pulse_fastly_schema_apply(schemaValue.text, parsed, false) : parsed }
|
|
1825
1850
|
function __pulse_fastly_headers_from_options(options: __PulseFastlyValue, output: __PulseFastlyValue): void { const index = __pulse_fastly_find(options, "headers"); if (index < 0) return; const headers = __pulse_fastly_value(unchecked(options.values[index])); if (headers.kind == PULSE_VALUE_OBJECT) { for (let i = 0; i < headers.keys.length; i += 1) { const name = unchecked(headers.keys[i]); const value = __pulse_fastly_value(unchecked(headers.values[i])); if (value.kind == PULSE_VALUE_ARRAY) { for (let j = 0; j < value.values.length; j += 1) output.headers.push(new __PulseFastlyHeader(name, __pulse_fastly_string(unchecked(value.values[j])))) } else output.headers.push(new __PulseFastlyHeader(name, __pulse_fastly_string(unchecked(headers.values[i])))) } return } if (headers.kind == PULSE_VALUE_ARRAY) { for (let i = 0; i < headers.values.length; i += 1) { const pair = __pulse_fastly_value(unchecked(headers.values[i])); if (pair.kind != PULSE_VALUE_ARRAY || pair.values.length < 2) continue; output.headers.push(new __PulseFastlyHeader(__pulse_fastly_string(unchecked(pair.values[0])), __pulse_fastly_string(unchecked(pair.values[1])))) } } }
|
|
@@ -1858,7 +1883,7 @@ function host_grip_handoff(optionsHandle: i32): i32 { const options = __pulse_fa
|
|
|
1858
1883
|
function host_kv_namespace(name: i32): i32 { return name }
|
|
1859
1884
|
function host_fetch_text(responseHandle: i32): i32 { const response = __pulse_fastly_value(responseHandle); if (response.kind != PULSE_VALUE_FETCH) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 42, -1); return 0 } return __pulse_fastly_string_value(__pulse_fastly_fetch_body(response)) }
|
|
1860
1885
|
function host_fetch_json(responseHandle: i32, schemaHandle: i32): i32 { const response = __pulse_fastly_value(responseHandle); if (response.kind != PULSE_VALUE_FETCH) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 43, -1); return 0 } if (__PULSE_SCHEMA_REQUIRE_JSON) { const contentType = __pulse_fastly_fetch_header_text(response.responseHandle, "content-type"); if (contentType.length == 0 || contentType.toLowerCase().indexOf("json") < 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 44, -1); return 0 } } const parsed = ${hasJsonAdmission(plan) ? '__pulse_fastly_schema_parse_json(__pulse_fastly_fetch_body(response), schemaHandle)' : '__pulse_fastly_parse_json(__pulse_fastly_fetch_body(response))'}; if (parsed <= 0) return 0; const schema = __pulse_fastly_value(schemaHandle); return schema.kind == PULSE_VALUE_STRING ? __pulse_fastly_schema_apply(schema.text, parsed, false) : parsed }
|
|
1861
|
-
function host_fetch_header(responseHandle: i32, nameHandle: i32): i32 { const response = __pulse_fastly_value(responseHandle); if (response.kind != PULSE_VALUE_FETCH) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 45, -1); return 0 } const value =
|
|
1886
|
+
function host_fetch_header(responseHandle: i32, nameHandle: i32): i32 { const response = __pulse_fastly_value(responseHandle); if (response.kind != PULSE_VALUE_FETCH) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 45, -1); return 0 } const value = __pulse_header_lookup(response.responseHandle, __pulse_fastly_string(nameHandle), true); return value === null ? host_value_undefined() : __pulse_fastly_string_value(value) }
|
|
1862
1887
|
function __pulse_fastly_payload_field(payload: __PulseFastlyValue, name: string): i32 { const index = __pulse_fastly_find(payload, name); return index < 0 ? host_value_undefined() : unchecked(payload.values[index]) }
|
|
1863
1888
|
function __pulse_fastly_ready_effect(effectIndex: i32, handle: i32): void { unchecked(__pulse_fastly_ready[effectIndex] = handle); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_READY) }
|
|
1864
1889
|
function __pulse_fastly_open_config_store(effectIndex: i32): i32 { const nameBytes = __pulse_fastly_utf8(__pulse_fastly_config_store(effectIndex)); const out = __pulse_fastly_out_i32(); const status = fastly_config_store_open(changetype<usize>(nameBytes), nameBytes.byteLength, changetype<usize>(out)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 110, effectIndex); return 0 } return __pulse_fastly_out_value(out) }
|
|
@@ -1880,7 +1905,7 @@ function __pulse_fastly_jwt_capture_clock(effectIndex: i32): f64 { __pulse_fastl
|
|
|
1880
1905
|
function __pulse_fastly_jwt_validate_schema(effectIndex: i32, schemaId: string, claimsHandle: i32): i32 { __pulse_fastly_jwt_schema_calls += 1; __pulse_fastly_jwt_event_order += "H"; if (__pulse_fastly_jwt_case_id() == "claims-schema-failure-after-registered-claims") return 0; return __pulse_fastly_schema_apply(schemaId, claimsHandle, false) }
|
|
1881
1906
|
function __pulse_fastly_jwt_sign_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const result = pulse_jwt_fastly_sign(effectIndex, unchecked(__pulse_fastly_payloads[effectIndex])); if (result > 0 && __pulse_fastly_last_error == 0) __pulse_fastly_ready_effect(effectIndex, result) }
|
|
1882
1907
|
function __pulse_fastly_jwt_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const result = pulse_jwt_fastly_verify(effectIndex, unchecked(__pulse_fastly_payloads[effectIndex])); if (result > 0 && __pulse_fastly_last_error == 0) __pulse_fastly_ready_effect(effectIndex, result) }
|
|
1883
|
-
function __pulse_fastly_start_request(effectIndex: i32, url: string, method: string, headers: __PulseFastlyValue, body: string, backend: string): void { const requestOut = __pulse_fastly_out_i32(); let status = fastly_http_req_new(changetype<usize>(requestOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 70, effectIndex); return } const requestHandle = __pulse_fastly_out_value(requestOut); const bodyOut = __pulse_fastly_out_i32(); status = fastly_http_body_new(changetype<usize>(bodyOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 71, effectIndex); return } const bodyHandle = __pulse_fastly_out_value(bodyOut); const methodBytes = __pulse_fastly_utf8(method); status = fastly_http_req_method_set(requestHandle, changetype<usize>(methodBytes), methodBytes.byteLength); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 72, effectIndex); return } const urlBytes = __pulse_fastly_utf8(url); status = fastly_http_req_uri_set(requestHandle, changetype<usize>(urlBytes), urlBytes.byteLength); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 73, effectIndex); return } if (headers.kind == PULSE_VALUE_OBJECT) { for (let i = 0; i < headers.keys.length; i += 1) { const nameBytes = __pulse_fastly_utf8(unchecked(headers.keys[i])); const valueBytes = __pulse_fastly_utf8(__pulse_fastly_string(unchecked(headers.values[i]))); status = fastly_http_req_header_insert(requestHandle, changetype<usize>(nameBytes), nameBytes.byteLength, changetype<usize>(valueBytes), valueBytes.byteLength); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 74, effectIndex); return } } } if (body.length > 0) { status = __pulse_fastly_write_body(bodyHandle, body); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 75, effectIndex); return } } if (backend.length == 0) { __pulse_fastly_fail(PULSE_ERROR_STATE, 76, effectIndex); return } const backendBytes = __pulse_fastly_utf8(backend); const pendingOut = __pulse_fastly_out_i32(); status = fastly_http_req_send_async(requestHandle, bodyHandle, changetype<usize>(backendBytes), backendBytes.byteLength, changetype<usize>(pendingOut)); if (status != FASTLY_STATUS_OK) {
|
|
1908
|
+
function __pulse_fastly_start_request(effectIndex: i32, url: string, method: string, headers: __PulseFastlyValue, body: string, backend: string): void { const requestOut = __pulse_fastly_out_i32(); let status = fastly_http_req_new(changetype<usize>(requestOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 70, effectIndex); return } const requestHandle = __pulse_fastly_out_value(requestOut); const bodyOut = __pulse_fastly_out_i32(); status = fastly_http_body_new(changetype<usize>(bodyOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 71, effectIndex); return } const bodyHandle = __pulse_fastly_out_value(bodyOut); const methodBytes = __pulse_fastly_utf8(method); status = fastly_http_req_method_set(requestHandle, changetype<usize>(methodBytes), methodBytes.byteLength); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 72, effectIndex); return } const urlBytes = __pulse_fastly_utf8(url); status = fastly_http_req_uri_set(requestHandle, changetype<usize>(urlBytes), urlBytes.byteLength); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 73, effectIndex); return } if (headers.kind == PULSE_VALUE_OBJECT) { for (let i = 0; i < headers.keys.length; i += 1) { const nameBytes = __pulse_fastly_utf8(unchecked(headers.keys[i])); const valueBytes = __pulse_fastly_utf8(__pulse_fastly_string(unchecked(headers.values[i]))); status = fastly_http_req_header_insert(requestHandle, changetype<usize>(nameBytes), nameBytes.byteLength, changetype<usize>(valueBytes), valueBytes.byteLength); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 74, effectIndex); return } } } if (body.length > 0) { status = __pulse_fastly_write_body(bodyHandle, body); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 75, effectIndex); return } } if (backend.length == 0) { __pulse_fastly_fail(PULSE_ERROR_STATE, 76, effectIndex); return } const backendBytes = __pulse_fastly_utf8(backend); const pendingOut = __pulse_fastly_out_i32(); status = fastly_http_req_send_async(requestHandle, bodyHandle, changetype<usize>(backendBytes), backendBytes.byteLength, changetype<usize>(pendingOut)); if (status != FASTLY_STATUS_OK) { __pulse_fetch_close_request(requestHandle); __pulse_fetch_close_body(bodyHandle); __pulse_fetch_fail(status, 77, effectIndex); return } unchecked(__pulse_fastly_pending[effectIndex] = __pulse_fastly_out_value(pendingOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_ASYNC) }
|
|
1884
1909
|
function __pulse_fastly_fetch_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const urlIndex = __pulse_fastly_find(payload, "url"); if (urlIndex < 0) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 48, effectIndex); return } const url = __pulse_fastly_string(unchecked(payload.values[urlIndex])); const initIndex = __pulse_fastly_find(payload, "init"); const init = initIndex >= 0 ? __pulse_fastly_value(unchecked(payload.values[initIndex])) : new __PulseFastlyValue(); let method = "GET"; let body = ""; let headers = new __PulseFastlyValue(); headers.kind = PULSE_VALUE_OBJECT; if (init.kind == PULSE_VALUE_OBJECT) { const methodIndex = __pulse_fastly_find(init, "method"); if (methodIndex >= 0) method = __pulse_fastly_string(unchecked(init.values[methodIndex])).toUpperCase(); const headersIndex = __pulse_fastly_find(init, "headers"); if (headersIndex >= 0) headers = __pulse_fastly_value(unchecked(init.values[headersIndex])); const bodyIndex = __pulse_fastly_find(init, "body"); const jsonIndex = __pulse_fastly_find(init, "json"); if (bodyIndex >= 0) { const bodyValue = unchecked(init.values[bodyIndex]); const bodyObject = __pulse_fastly_value(bodyValue); body = bodyObject.kind == PULSE_VALUE_STRING ? bodyObject.text : __pulse_fastly_json(bodyValue, 0) } else if (jsonIndex >= 0) { body = __pulse_fastly_fetch_json_body(init, unchecked(init.values[jsonIndex])); if (__pulse_fastly_last_error != 0) return; headers = __pulse_fastly_fetch_json_headers(headers) } } __pulse_fastly_start_request(effectIndex, url, method, headers, body, __pulse_fastly_backend(effectIndex)) }
|
|
1885
1910
|
function __pulse_fastly_kv_get_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const store = __pulse_fastly_open_kv_store(effectIndex); if (__pulse_fastly_last_error != 0) return; const key = __pulse_fastly_string(__pulse_fastly_payload_field(payload, "key")); const keyBytes = __pulse_fastly_utf8(key); const lookupOut = __pulse_fastly_out_i32(); const config = new StaticArray<i32>(1); const status = fastly_kv_store_lookup(store, changetype<usize>(keyBytes), keyBytes.byteLength, 0, changetype<usize>(config), changetype<usize>(lookupOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 116, effectIndex); return } unchecked(__pulse_fastly_pending[effectIndex] = __pulse_fastly_out_value(lookupOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_ASYNC) }
|
|
1886
1911
|
${assetBeginSource}
|
|
@@ -1892,7 +1917,7 @@ function __pulse_fastly_grip_hold_begin(effectIndex: i32, payload: __PulseFastly
|
|
|
1892
1917
|
${gripSecretSource}
|
|
1893
1918
|
function __pulse_fastly_grip_publish_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const valueHandle = __pulse_fastly_payload_field(payload, "payload"); const value = __pulse_fastly_value(valueHandle); if (value.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 122, effectIndex); return } const headers = new __PulseFastlyValue(); headers.kind = PULSE_VALUE_OBJECT; headers.keys.push("content-type"); headers.values.push(__pulse_fastly_string_value("application/json; charset=utf-8")); headers.keys.push("accept"); headers.values.push(__pulse_fastly_string_value("application/json")); ${gripAuthorizationSource} const body = __pulse_fastly_json(valueHandle, 0); if (String.UTF8.encode(body, false).byteLength > PULSE_FASTLY_BUFFER_BYTES) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 145, effectIndex); return } __pulse_fastly_start_request(effectIndex, __pulse_fastly_grip_publish_url(effectIndex), "POST", headers, body, __pulse_fastly_backend(effectIndex)) }
|
|
1894
1919
|
${effectDispatchSource(plan)}
|
|
1895
|
-
function __pulse_fastly_wait_fetch(effectIndex: i32): i32 { if (unchecked(__pulse_fastly_pending_mode[effectIndex]) != PULSE_FASTLY_PENDING_ASYNC) { __pulse_fastly_fail(PULSE_ERROR_STATE, 80, effectIndex); return 0 } const pending = unchecked(__pulse_fastly_pending[effectIndex]); const responseOut = __pulse_fastly_out_i32(); const bodyOut = __pulse_fastly_out_i32(); const status = fastly_http_req_pending_req_wait(pending, changetype<usize>(responseOut), changetype<usize>(bodyOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_NONE); unchecked(__pulse_fastly_pending[effectIndex] = 0); if (status != FASTLY_STATUS_OK) {
|
|
1920
|
+
function __pulse_fastly_wait_fetch(effectIndex: i32): i32 { if (unchecked(__pulse_fastly_pending_mode[effectIndex]) != PULSE_FASTLY_PENDING_ASYNC) { __pulse_fastly_fail(PULSE_ERROR_STATE, 80, effectIndex); return 0 } const pending = unchecked(__pulse_fastly_pending[effectIndex]); const responseOut = __pulse_fastly_out_i32(); const bodyOut = __pulse_fastly_out_i32(); const status = fastly_http_req_pending_req_wait(pending, changetype<usize>(responseOut), changetype<usize>(bodyOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_NONE); unchecked(__pulse_fastly_pending[effectIndex] = 0); if (status != FASTLY_STATUS_OK) { __pulse_fetch_fail(status, 81, effectIndex); return 0 } const value = new __PulseFastlyValue(); value.kind = PULSE_VALUE_FETCH; value.responseHandle = __pulse_fastly_out_value(responseOut); value.bodyHandle = __pulse_fastly_out_value(bodyOut); return __pulse_fastly_put(value) }
|
|
1896
1921
|
function __pulse_fastly_wait_grip_publish(effectIndex: i32): i32 { const responseHandle = __pulse_fastly_wait_fetch(effectIndex); if (responseHandle <= 0) return 0; const response = __pulse_fastly_value(responseHandle); if (response.kind != PULSE_VALUE_FETCH) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 129, effectIndex); return 0 } const statusOut = __pulse_fastly_out_i32(); const statusCall = fastly_http_resp_status_get(response.responseHandle, changetype<usize>(statusOut)); if (statusCall != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 130, effectIndex); return 0 } const originStatus = __pulse_fastly_out_value(statusOut); if (originStatus < 200 || originStatus > 299) { __pulse_fastly_fail(PULSE_ERROR_TRANSPORT, 131, effectIndex); return 0 } const outer = __pulse_fastly_value(unchecked(__pulse_fastly_payloads[effectIndex])); const publicationHandle = __pulse_fastly_payload_field(outer, "payload"); const publication = __pulse_fastly_value(publicationHandle); if (publication.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 132, effectIndex); return 0 } const receipt = host_value_object(); host_value_object_set(receipt, __pulse_fastly_string_value("published"), host_value_boolean(1)); for (let i = 0; i < publication.keys.length; i += 1) host_value_object_set(receipt, __pulse_fastly_string_value(unchecked(publication.keys[i])), unchecked(publication.values[i])); const options = host_value_object(); host_value_object_set(options, __pulse_fastly_string_value("status"), host_value_number(202.0)); return host_response_json(receipt, options) }
|
|
1897
1922
|
function __pulse_fastly_wait_grip_broadcast(effectIndex: i32): i32 { const responseHandle = __pulse_fastly_wait_fetch(effectIndex); if (responseHandle <= 0) return 0; const response = __pulse_fastly_value(responseHandle); if (response.kind != PULSE_VALUE_FETCH) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 142, effectIndex); return 0 } const statusOut = __pulse_fastly_out_i32(); const statusCall = fastly_http_resp_status_get(response.responseHandle, changetype<usize>(statusOut)); if (statusCall != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 143, effectIndex); return 0 } const originStatus = __pulse_fastly_out_value(statusOut); if (originStatus < 200 || originStatus > 299) { __pulse_fastly_fail(PULSE_ERROR_TRANSPORT, 144, effectIndex); return 0 } const receipt = host_value_object(); host_value_object_set(receipt, __pulse_fastly_string_value("accepted"), host_value_boolean(1)); host_value_object_set(receipt, __pulse_fastly_string_value("status"), host_value_number(f64(originStatus))); return receipt }
|
|
1898
1923
|
function __pulse_fastly_wait_ready(effectIndex: i32): i32 { if (unchecked(__pulse_fastly_pending_mode[effectIndex]) != PULSE_FASTLY_PENDING_READY) { __pulse_fastly_fail(PULSE_ERROR_STATE, 124, effectIndex); return 0 } const value = unchecked(__pulse_fastly_ready[effectIndex]); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_NONE); unchecked(__pulse_fastly_pending[effectIndex] = 0); unchecked(__pulse_fastly_ready[effectIndex] = 0); return value }
|
|
@@ -1949,7 +1974,7 @@ function __pulse_fastly_run_invocation(): void {
|
|
|
1949
1974
|
if (__pulse_fastly_last_error != 0) { __pulse_fastly_jwt_send_error(); return }
|
|
1950
1975
|
__pulse_fastly_request_path = __pulse_fastly_path(__pulse_fastly_request_url)
|
|
1951
1976
|
let runStatus = pulse_start()
|
|
1952
|
-
${packageApplication.enabled(plan) ? packageApplication.driverLoop() : applicationErrors.
|
|
1977
|
+
${packageApplication.enabled(plan) ? packageApplication.driverLoop() : applicationErrors.settlementEnabled(plan) ? applicationErrors.driverLoop(plan) : ` while (runStatus == 1 && __pulse_fastly_last_error == 0) {
|
|
1953
1978
|
for (let effectIndex = 0; effectIndex < PULSE_FASTLY_EFFECT_COUNT; effectIndex += 1) {
|
|
1954
1979
|
if (unchecked(__pulse_fastly_pending_mode[effectIndex]) == PULSE_FASTLY_PENDING_NONE) continue
|
|
1955
1980
|
const invocation = unchecked(__pulse_invocation_tickets[effectIndex])
|
|
@@ -2052,14 +2077,20 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
2052
2077
|
conditionalKv.kvNativeSource(plan),
|
|
2053
2078
|
require('./native-request-body.js').runtimeSource(),
|
|
2054
2079
|
require('./native-request-headers.js').runtimeSource(),
|
|
2055
|
-
|
|
2080
|
+
require('./native-header-lookup.js').runtimeSource(),
|
|
2081
|
+
require('./native-fetch-errors.js').runtimeSource(),
|
|
2082
|
+
require('./native-fetch-deadlines.js').runtimeSource(plan),
|
|
2083
|
+
applicationErrors.settlementEnabled(plan) ? applicationErrors.runtimeSource(plan) : '',
|
|
2056
2084
|
effectInvocations.runtimeSource({ packageCompletion: packageApplication.enabled(plan) }),
|
|
2057
2085
|
portableSource,
|
|
2058
2086
|
driverSource(plan, { guestLinked: facts.guestUnits.length > 0 }),
|
|
2059
2087
|
''
|
|
2060
2088
|
].join('\n');
|
|
2061
|
-
if (applicationErrors.
|
|
2089
|
+
if (applicationErrors.settlementEnabled(plan)) source = applicationErrors.instrument(source);
|
|
2062
2090
|
source = require('./native-request-headers.js').instrument(source, applicationErrors.enabled(plan), /\bhost_request_headers\(/.test(portableSource));
|
|
2091
|
+
source = require('./native-header-lookup.js').instrument(source);
|
|
2092
|
+
source = require('./native-fetch-errors.js').instrument(source);
|
|
2093
|
+
source = require('./native-fetch-deadlines.js').instrument(source, plan);
|
|
2063
2094
|
source = require('./native-request-body.js').instrument(source, applicationErrors.enabled(plan));
|
|
2064
2095
|
if (packageApplication.enabled(plan)) source = packageApplication.instrument(source);
|
|
2065
2096
|
source = effectInvocations.instrument(source);
|
|
@@ -2089,6 +2120,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
2089
2120
|
portableSourceHash: portable.sourceHash,
|
|
2090
2121
|
schemaCodecs: portable.manifest.schemaCodecs,
|
|
2091
2122
|
handlerBodies: portable.manifest.handlerBodies,
|
|
2123
|
+
...(portable.manifest.reportOwnership ? { reportOwnership: portable.manifest.reportOwnership } : {}),
|
|
2092
2124
|
dispatcher: portable.manifest.dispatcher,
|
|
2093
2125
|
stages: portable.manifest.stages,
|
|
2094
2126
|
helperBodies: portable.manifest.helperBodies,
|
|
@@ -2139,7 +2171,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
2139
2171
|
providerNeutralInput: true,
|
|
2140
2172
|
javascriptRuntime: false,
|
|
2141
2173
|
jsComputeRuntime: false,
|
|
2142
|
-
wasi: bindings.maxDurationMs !== undefined || hasJwt || hasSign || bindings.s3.length || plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 'clock_time_get only' : false,
|
|
2174
|
+
wasi: plan.effects.some(effect => effect.kind === 'fetch') || bindings.maxDurationMs !== undefined || hasJwt || hasSign || bindings.s3.length || plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 'clock_time_get only' : false,
|
|
2143
2175
|
effects: FASTLY_NATIVE_PLATFORM_EFFECT_KINDS.join(', '),
|
|
2144
2176
|
continuations: true,
|
|
2145
2177
|
groupedEffects: 'existing fetch start-before-wait ordering remains intact',
|
|
@@ -2304,9 +2336,10 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2304
2336
|
'--path', jsonAs.dependencyRoot
|
|
2305
2337
|
);
|
|
2306
2338
|
}
|
|
2339
|
+
require('@pulse-compute/wasm-build-support/native-optimization').appendJsonCacheArenaArgs(args, options.elideJsonCacheArena, schemaCodecsActive);
|
|
2307
2340
|
const readReportCapture = options.reportCapture === false ? () => null : prepareReportCapture(args, stagingDir, generated.manifest,
|
|
2308
2341
|
require(path.join(asc.packageRoot, 'package.json')).version, 'fastly-native-platform-capabilities.as/');
|
|
2309
|
-
const startedAt =
|
|
2342
|
+
const startedAt = process.hrtime.bigint();
|
|
2310
2343
|
const result = spawnSync(asc.executable, args, {
|
|
2311
2344
|
cwd: stagingDir,
|
|
2312
2345
|
encoding: 'utf8',
|
|
@@ -2316,7 +2349,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2316
2349
|
? { ...process.env, JSON_STRICT: 'true', JSON_USE_FAST_PATH: '0', JSON_MODE: 'NAIVE' }
|
|
2317
2350
|
: process.env
|
|
2318
2351
|
});
|
|
2319
|
-
const assemblyScriptDurationMs =
|
|
2352
|
+
const assemblyScriptDurationMs = Math.round(Number(process.hrtime.bigint() - startedAt) / 1e6);
|
|
2320
2353
|
if (result.error || result.status !== 0 || !fs.existsSync(assemblyScriptWasmFile)) {
|
|
2321
2354
|
fail('AssemblyScript failed to compile the Fastly native platform capabilities module.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_ASC_FAILED', {
|
|
2322
2355
|
status: result.status,
|
|
@@ -2454,7 +2487,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2454
2487
|
compilerVersion: FASTLY_NATIVE_PLATFORM_CAPABILITIES_COMPILER_VERSION,
|
|
2455
2488
|
assemblyScript: Object.freeze({ package: 'assemblyscript', version: require(path.join(asc.packageRoot, 'package.json')).version }),
|
|
2456
2489
|
jsonAs: jsonAs
|
|
2457
|
-
? Object.freeze({ package: 'json-as', version: jsonAs.version, transform: true, strict: true, mode: 'NAIVE', fastPath: false })
|
|
2490
|
+
? Object.freeze({ package: 'json-as', version: jsonAs.version, transform: true, strict: true, mode: 'NAIVE', fastPath: false, elideDisabledCacheArena: options.elideJsonCacheArena === true })
|
|
2458
2491
|
: undefined,
|
|
2459
2492
|
optimization,
|
|
2460
2493
|
wasm: Object.freeze({ bytes: inspection.bytes, sha256: inspection.sha256, magic: inspection.magic }),
|
|
@@ -2474,11 +2507,17 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2474
2507
|
providerPackaging: realization.guestLink.providerPackaging
|
|
2475
2508
|
} : {})
|
|
2476
2509
|
});
|
|
2477
|
-
const durationMs =
|
|
2510
|
+
const durationMs = Math.round(Number(process.hrtime.bigint() - startedAt) / 1e6);
|
|
2478
2511
|
return Object.freeze({
|
|
2479
2512
|
version: FASTLY_NATIVE_PLATFORM_CAPABILITIES_VERSION,
|
|
2480
2513
|
compilerVersion: FASTLY_NATIVE_PLATFORM_CAPABILITIES_COMPILER_VERSION,
|
|
2481
2514
|
reportAttribution: readReportCapture(wasm),
|
|
2515
|
+
buildObservation: require('@pulse-compute/wasm-build-support/native-optimization').observeAssemblyScript(args, {
|
|
2516
|
+
version: require(path.join(asc.packageRoot, 'package.json')).version,
|
|
2517
|
+
assemblyScriptRoot: asc.packageRoot, timeoutMs: Number(options.timeoutMs || 180000),
|
|
2518
|
+
compilerVersion: FASTLY_NATIVE_PLATFORM_CAPABILITIES_COMPILER_VERSION,
|
|
2519
|
+
durationMs: assemblyScriptDurationMs, jsonAsVersion: jsonAs && jsonAs.version
|
|
2520
|
+
}),
|
|
2482
2521
|
plan,
|
|
2483
2522
|
generated,
|
|
2484
2523
|
source: generated.source,
|
|
@@ -23,7 +23,7 @@ function instrumentRequestBudget(input, duration, plan) {
|
|
|
23
23
|
replace('function __pulse_fastly_resolve_effect(effectIndex: i32): i32 {', `function __pulse_fastly_resolve_effect(effectIndex: i32): i32 {
|
|
24
24
|
if (!__request_check(effectIndex)) return 0;
|
|
25
25
|
const kind = __pulse_fastly_effect_kind(effectIndex);
|
|
26
|
-
if (unchecked(__pulse_fastly_pending_mode[effectIndex]) == PULSE_FASTLY_PENDING_ASYNC && kind != 12 && kind != 13 && kind != 14 && kind != 21 && ${plan.effects.filter(e=>e.kind.startsWith('kv.')&&['kv.getVersioned','kv.insertIfAbsent','kv.compareAndSwap'].includes(e.kind)).map(e=>'effectIndex != '+plan.effects.indexOf(e)).join(' && ')||'true'}) {
|
|
26
|
+
if (unchecked(__pulse_fastly_pending_mode[effectIndex]) == PULSE_FASTLY_PENDING_ASYNC && !(kind == 1 && __pulse_fetch_has_deadlines()) && kind != 12 && kind != 13 && kind != 14 && kind != 21 && ${plan.effects.filter(e=>e.kind.startsWith('kv.')&&['kv.getVersioned','kv.insertIfAbsent','kv.compareAndSwap'].includes(e.kind)).map(e=>'effectIndex != '+plan.effects.indexOf(e)).join(' && ')||'true'}) {
|
|
27
27
|
if (!__request_wait(unchecked(__pulse_fastly_pending[effectIndex]), effectIndex)) return 0;
|
|
28
28
|
}`);
|
|
29
29
|
replace(' const buffer = new Uint8Array(PULSE_FASTLY_BUFFER_BYTES), read = __pulse_fastly_out_i32();', ' if (!__request_wait(handle, effectIndex)) return "";\n const buffer = new Uint8Array(PULSE_FASTLY_BUFFER_BYTES), read = __pulse_fastly_out_i32();');
|
|
@@ -40,6 +40,8 @@ function instrumentRequestBudget(input, duration, plan) {
|
|
|
40
40
|
if (source.includes('__kv_deadline[index] = start +')) replace('__kv_deadline[index] = start + i64(__KV_timeoutMs) * 1000000;', '__kv_deadline[index] = min<i64>(__request_deadline, start + i64(__KV_timeoutMs) * 1000000);');
|
|
41
41
|
if (source.includes('__s3_deadlines[index] = start +')) replace('__s3_deadlines[index] = start + i64(binding.timeout) * 1000000;', '__s3_deadlines[index] = min<i64>(__request_deadline, start + i64(binding.timeout) * 1000000);');
|
|
42
42
|
if (source.includes('function __pulse_fastly_write_binary(')) replace('const written = new StaticArray<i32>(1); let offset = 0;\n while (offset < data.length) {', 'const written = new StaticArray<i32>(1); let offset = 0;\n while (offset < data.length) { if (!__request_check()) return 1;');
|
|
43
|
+
replace('function __pulse_fetch_request_check(index: i32): bool { return true }', 'function __pulse_fetch_request_check(index: i32): bool { return __request_check(index) }');
|
|
44
|
+
replace('function __pulse_fetch_request_remaining(): i32 { return 2147483647 }', 'function __pulse_fetch_request_remaining(): i32 { return __request_remaining() }');
|
|
43
45
|
return source + '\n' + fs.readFileSync(path.join(__dirname,'request-budget.as.ts'),'utf8');
|
|
44
46
|
}
|
|
45
47
|
module.exports = {instrumentRequestBudget};
|
|
@@ -4,6 +4,7 @@ const fs = require('node:fs');
|
|
|
4
4
|
const path = require('node:path');
|
|
5
5
|
|
|
6
6
|
const FASTLY_STATUS_OK = 0;
|
|
7
|
+
const FASTLY_STATUS_INVALID_ARGUMENT = 2;
|
|
7
8
|
const FASTLY_STATUS_ERROR = 1;
|
|
8
9
|
const FASTLY_STATUS_BADF = 3;
|
|
9
10
|
const FASTLY_STATUS_BUFLEN = 4;
|
|
@@ -229,7 +230,7 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
229
230
|
new Uint8Array(memory().buffer, Number(buffer), output.length).set(output);
|
|
230
231
|
return FASTLY_STATUS_OK;
|
|
231
232
|
}
|
|
232
|
-
function requestHeaderList(values, buffer, size, cursor, end, written, kind) {
|
|
233
|
+
function requestHeaderList(values, buffer, size, cursor, end, written, kind, lookupKind) {
|
|
233
234
|
const limit = Math.min(Number(size), options.requestHeaderPageBytes ?? Number(size));
|
|
234
235
|
let index = Number(cursor), count = 0;
|
|
235
236
|
if (index < 0 || index > values.length) return FASTLY_STATUS_ERROR;
|
|
@@ -245,7 +246,7 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
245
246
|
writeU32(written, count);
|
|
246
247
|
view().setBigInt64(Number(end), index === values.length ? -1n : BigInt(index), true);
|
|
247
248
|
// Fault injection is host-fixture-only; neither values nor faults enter traces.
|
|
248
|
-
const fault = options.requestHeaderFault?.({kind, cursor: Number(cursor)});
|
|
249
|
+
const fault = (lookupKind && options.headerLookupFault?.({kind:lookupKind})) || options.requestHeaderFault?.({kind, cursor: Number(cursor)});
|
|
249
250
|
if (fault?.written !== undefined) writeU32(written, fault.written);
|
|
250
251
|
if (fault?.cursor !== undefined) view().setBigInt64(Number(end), BigInt(fault.cursor), true);
|
|
251
252
|
if (fault?.unterminated && count) new Uint8Array(memory().buffer)[Number(buffer) + count - 1] = 65;
|
|
@@ -254,14 +255,17 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
254
255
|
const imports = {
|
|
255
256
|
fastly_async_io: {
|
|
256
257
|
select(handles, count, timeout, done) {
|
|
257
|
-
if (Number(count)
|
|
258
|
-
const
|
|
259
|
-
const
|
|
260
|
-
if (!item) return FASTLY_STATUS_BADF;
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
258
|
+
if (Number(count) < 1 || Number(timeout) <= 0) return FASTLY_STATUS_ERROR;
|
|
259
|
+
const selected = Array.from({length:Number(count)},(_,i) => view().getUint32(Number(handles)+4*i,true));
|
|
260
|
+
const items = selected.map(handle => pending.get(handle) || bodies.get(handle));
|
|
261
|
+
if (items.some(item => !item)) return FASTLY_STATUS_BADF;
|
|
262
|
+
const waits = items.map(item => Math.max(0, Number(item.readyAt || 0) - monotonicMs));
|
|
263
|
+
const wait = Math.min(...waits), timedOut = wait >= Number(timeout);
|
|
264
|
+
const fault = options.asyncSelectFault?.({handles:selected,timeout:Number(timeout),now:monotonicMs});
|
|
265
|
+
monotonicMs += fault?.advanceMs ?? Math.min(wait,Number(timeout));
|
|
266
|
+
writeU32(done, fault?.selected ?? (timedOut ? 0xffffffff : waits.indexOf(wait)));
|
|
267
|
+
trace.push({ module: 'fastly_async_io', name: 'select', handles:selected, timeout:Number(timeout), timedOut, now:monotonicMs });
|
|
268
|
+
if (fault?.status) return fault.status;
|
|
265
269
|
return FASTLY_STATUS_OK;
|
|
266
270
|
}
|
|
267
271
|
},
|
|
@@ -325,7 +329,11 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
325
329
|
trace.push({ module: 'fastly_log', name: 'write', endpoint, accepted: false });
|
|
326
330
|
return FASTLY_STATUS_ERROR;
|
|
327
331
|
}
|
|
328
|
-
const
|
|
332
|
+
const emitted = readUtf8(messagePointer, messageLength);
|
|
333
|
+
// Test-only assertion seam before proof-trace redaction. Never retain raw
|
|
334
|
+
// guest bytes in the returned evidence, even when the guest regresses.
|
|
335
|
+
if (typeof options.onLogWrite === 'function') options.onLogWrite(emitted);
|
|
336
|
+
const message = redactText(emitted);
|
|
329
337
|
writeU32(writtenOut, Buffer.byteLength(message));
|
|
330
338
|
logs.push(Object.freeze({ endpoint, message }));
|
|
331
339
|
trace.push({ module: 'fastly_log', name: 'write', endpoint, message, accepted: true });
|
|
@@ -360,7 +368,8 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
360
368
|
const request = requests.get(Number(handle));
|
|
361
369
|
if (!request) return FASTLY_STATUS_BADF;
|
|
362
370
|
const name = readUtf8(pointer, length).toLowerCase();
|
|
363
|
-
|
|
371
|
+
if (!/^[!#$%&'*+.^_`|~0-9a-z-]+$/.test(name)) return FASTLY_STATUS_INVALID_ARGUMENT;
|
|
372
|
+
return requestHeaderList(request.headers.filter(([key]) => key.toLowerCase() === name).map(([, value]) => value), buffer, size, cursor, end, written, 'values', 'request');
|
|
364
373
|
},
|
|
365
374
|
header_value_get(handle, namePointer, nameLength, valuePointer, valueLength, writtenOut) {
|
|
366
375
|
const request = requests.get(Number(handle));
|
|
@@ -368,7 +377,7 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
368
377
|
trace.push({ module: 'fastly_http_req', name: 'header_value_get', handle: Number(handle), header: name });
|
|
369
378
|
if (!request) return FASTLY_STATUS_BADF;
|
|
370
379
|
const value = headerValue(request.headers, name);
|
|
371
|
-
return value === undefined ?
|
|
380
|
+
return value === undefined ? FASTLY_STATUS_INVALID_ARGUMENT : writeUtf8(value, valuePointer, valueLength, writtenOut);
|
|
372
381
|
},
|
|
373
382
|
new(handleOut) {
|
|
374
383
|
const handle = alloc('request');
|
|
@@ -413,6 +422,7 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
413
422
|
close(handle) { return requests.delete(Number(handle)) ? FASTLY_STATUS_OK : FASTLY_STATUS_BADF; },
|
|
414
423
|
pending_req_poll_v2(handle, detail, done, responseOut, bodyOut) {
|
|
415
424
|
const item = pending.get(Number(handle)); if (!item) return FASTLY_STATUS_BADF;
|
|
425
|
+
trace.push({module:'fastly_http_req',name:'pending_req_poll_v2',pendingHandle:Number(handle)});
|
|
416
426
|
if (monotonicMs < item.readyAt) { writeU32(done, 0); return FASTLY_STATUS_OK; }
|
|
417
427
|
writeU32(done, 1); return imports.fastly_http_req.pending_req_wait(handle, responseOut, bodyOut);
|
|
418
428
|
},
|
|
@@ -430,7 +440,7 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
430
440
|
}
|
|
431
441
|
const pendingHandle = alloc('pending');
|
|
432
442
|
const capturedBody = bodyBytes(bodyHandle);
|
|
433
|
-
pending.set(pendingHandle, { request: { ...request, headers: request.headers.map((entry) => [...entry]), body: capturedBody }, backend, fixture, readyAt: monotonicMs + Number(fixture.delayMs || 0) });
|
|
443
|
+
pending.set(pendingHandle, { request: { ...request, headers: request.headers.map((entry) => [...entry]), body: capturedBody }, backend, fixture, readyAt: fixture.neverHeaders ? Infinity : monotonicMs + Number(fixture.delayMs || 0) });
|
|
434
444
|
if (typeof options.onOutboundRequest === 'function') options.onOutboundRequest({ ...request, backend, headers: request.headers.map((entry) => [...entry]), body: capturedBody });
|
|
435
445
|
if (fixture.sendStatus) { pending.delete(pendingHandle); return Number(fixture.sendStatus); }
|
|
436
446
|
if (request.headers.some(([name, value]) => name.toLowerCase() === 'authorization' && value.startsWith('AWS4-HMAC-SHA256 '))) privateUrls.add(request.url);
|
|
@@ -576,13 +586,21 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
576
586
|
trace.push({ module: 'fastly_http_resp', name: 'header_append', handle: Number(handle), header: [name, value] });
|
|
577
587
|
return FASTLY_STATUS_OK;
|
|
578
588
|
},
|
|
589
|
+
header_values_get(handle, pointer, length, buffer, size, cursor, end, written) {
|
|
590
|
+
trace.push({module:'fastly_http_resp', name:'header_values_get', cursor:Number(cursor)});
|
|
591
|
+
const response = responses.get(Number(handle));
|
|
592
|
+
if (!response) return FASTLY_STATUS_BADF;
|
|
593
|
+
const name = readUtf8(pointer, length).toLowerCase();
|
|
594
|
+
if (!/^[!#$%&'*+.^_`|~0-9a-z-]+$/.test(name)) return FASTLY_STATUS_INVALID_ARGUMENT;
|
|
595
|
+
return requestHeaderList(response.headers.filter(([key]) => key.toLowerCase() === name).map(([, value]) => value), buffer, size, cursor, end, written, 'response-values', 'response');
|
|
596
|
+
},
|
|
579
597
|
header_value_get(handle, namePointer, nameLength, valuePointer, valueLength, writtenOut) {
|
|
580
598
|
const response = responses.get(Number(handle));
|
|
581
599
|
const name = readUtf8(namePointer, nameLength);
|
|
582
600
|
trace.push({ module: 'fastly_http_resp', name: 'header_value_get', handle: Number(handle), header: name });
|
|
583
601
|
if (!response) return FASTLY_STATUS_BADF;
|
|
584
602
|
const value = headerValue(response.headers, name);
|
|
585
|
-
return value === undefined ?
|
|
603
|
+
return value === undefined ? FASTLY_STATUS_INVALID_ARGUMENT : writeUtf8(value, valuePointer, valueLength, writtenOut);
|
|
586
604
|
},
|
|
587
605
|
status_get(handle, statusOut) {
|
|
588
606
|
const response = responses.get(Number(handle));
|
|
@@ -702,7 +720,16 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
702
720
|
const handledJwtError = options.allowHandledJwtError === true
|
|
703
721
|
&& lastError === 1008
|
|
704
722
|
&& Boolean(downstream);
|
|
705
|
-
|
|
723
|
+
const handledHeaderError = options.allowHandledHeaderError === true
|
|
724
|
+
&& lastError === 1003 && (errorStage === 22 || errorStage === 23) && downstream?.status === 500;
|
|
725
|
+
const handledTransportError = options.allowHandledTransportError === true
|
|
726
|
+
&& (errorStage === 77 || errorStage === 81)
|
|
727
|
+
&& ((lastError === 1006 && downstream?.status === 502) || (lastError === 1003 && downstream?.status === 500));
|
|
728
|
+
const handledDeadlineError = options.allowHandledFetchDeadlineError === true
|
|
729
|
+
&& ((lastError === 1006 && errorStage === 271 && downstream?.status === 504)
|
|
730
|
+
|| (lastError === 1001 && errorStage === 270 && downstream?.status === 400)
|
|
731
|
+
|| (lastError === 1003 && errorStage === 272 && downstream?.status === 500));
|
|
732
|
+
if (lastError !== FASTLY_STATUS_OK && !handledDeadlineError && !handledTransportError && !handledHeaderError && !handledJwtError && !(options.allowAbortedResponse === true && downstream?.aborted)) {
|
|
706
733
|
throw new FastlyNativePlatformCapabilitiesMockError('Fastly native platform capabilities module reported an execution error.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_MOCK_EXECUTION_FAILED', { lastError, errorStage, errorEffect, pulseLastError, trace });
|
|
707
734
|
}
|
|
708
735
|
if (!downstream) {
|
|
@@ -729,6 +756,9 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
729
756
|
outboundRequests: Object.freeze(outboundRequests),
|
|
730
757
|
stores: Object.freeze({ config: snapshotStores(configStores), secrets: Object.fromEntries([...secretStores].map(([name, values]) => [name, Object.fromEntries([...values].map(([key]) => [key, '[REDACTED]']))])), kv: snapshotStores(kvStores, true) }),
|
|
731
758
|
kvEvidence,
|
|
759
|
+
monotonicMs,
|
|
760
|
+
transportResources: Object.freeze({pendingRequests: pending.size,
|
|
761
|
+
outboundRequests: requests.size - Number(requests.has(downstreamRequestHandle))}),
|
|
732
762
|
instance
|
|
733
763
|
});
|
|
734
764
|
}
|
package/src/toolchain/index.js
CHANGED
|
@@ -285,6 +285,7 @@ function createDriver() {
|
|
|
285
285
|
realizationArtifacts: invocation.nativeArtifact.realizationArtifacts,
|
|
286
286
|
guestUnits: invocation.nativeArtifact.guestUnits,
|
|
287
287
|
nativeOptimization: invocation.optimization,
|
|
288
|
+
elideJsonCacheArena: invocation.nativeArtifact.manifest.elideJsonCacheArena === true,
|
|
288
289
|
emitWat: invocation.nativeArtifact.manifest.wat?.emitted === true,
|
|
289
290
|
compileTimeoutMs: invocation.timeoutMs
|
|
290
291
|
});
|
|
@@ -314,6 +315,7 @@ function createDriver() {
|
|
|
314
315
|
realizationArtifacts: invocation.nativeArtifact.realizationArtifacts,
|
|
315
316
|
guestUnits: invocation.nativeArtifact.guestUnits,
|
|
316
317
|
nativeOptimization: invocation.optimization,
|
|
318
|
+
elideJsonCacheArena: invocation.nativeArtifact.manifest.elideJsonCacheArena === true,
|
|
317
319
|
emitWat: invocation.nativeArtifact.manifest.wat?.emitted === true,
|
|
318
320
|
compileTimeoutMs: invocation.timeoutMs
|
|
319
321
|
});
|
|
@@ -350,7 +352,7 @@ function createDriver() {
|
|
|
350
352
|
providerPackage: providerBuild.packageFile,
|
|
351
353
|
providerSourceEntry: providerBuild.sourceEntryFile
|
|
352
354
|
}),
|
|
353
|
-
build: { ...providerBuild.build, reportAttribution: native.reportAttribution },
|
|
355
|
+
build: { ...providerBuild.build, reportAttribution: native.reportAttribution, buildObservation: native.buildObservation },
|
|
354
356
|
realization: publicRealization,
|
|
355
357
|
packaging: audit ? Object.freeze({
|
|
356
358
|
version: PROVIDER_PACKAGING_AUDIT_VERSION,
|
|
@@ -18,6 +18,7 @@ function prepareFastlyNativeExecution(invocation) {
|
|
|
18
18
|
realizationArtifacts: invocation.nativeArtifact.realizationArtifacts,
|
|
19
19
|
guestUnits: invocation.nativeArtifact.guestUnits,
|
|
20
20
|
nativeOptimization: invocation.optimization,
|
|
21
|
+
elideJsonCacheArena: invocation.nativeArtifact.manifest.elideJsonCacheArena === true,
|
|
21
22
|
emitWat: invocation.nativeArtifact.manifest.wat?.emitted === true,
|
|
22
23
|
compileTimeoutMs: invocation.timeoutMs || undefined
|
|
23
24
|
});
|