@pulse-compute/provider-fastly 1.0.0-beta.7 → 1.0.0-beta.9
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/dist/config-schema.json +13 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
- package/src/build/canonical-target.js +7 -0
- package/src/build/entities-native.js +6 -13
- package/src/build/kv-native.as.ts +10 -6
- package/src/build/native-application-errors.js +14 -8
- package/src/build/native-body-forwarding.as.ts +334 -0
- package/src/build/native-body-forwarding.js +69 -0
- 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-handle-ownership.js +127 -0
- package/src/build/native-header-lookup.js +54 -0
- package/src/build/native-http-effects.js +6 -1
- package/src/build/native-platform-capabilities.js +99 -33
- package/src/build/native-stdio.js +20 -0
- package/src/build/native-string-values.js +2 -2
- package/src/build/native-value-budget.js +33 -1
- package/src/build/request-budget.js +3 -1
- package/src/build/schema-admission.js +3 -3
- package/src/config-api.js +1 -0
- package/src/config-schema.json +13 -0
- package/src/index.ts +11 -0
- package/src/provider-contract.js +6 -3
- package/src/testing/native-platform-capabilities-host.js +139 -24
- package/src/toolchain/body-forwarding.js +25 -0
- package/src/toolchain/config.js +4 -0
- package/src/toolchain/index.js +4 -2
- package/src/toolchain/native-execution.js +7 -1
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function validateRoots(plan, manifest) {
|
|
4
|
+
if (!manifest.handleOwnership?.enabled) return;
|
|
5
|
+
const { CANONICAL_NATIVE_PURE_HELPER_VERSION } = require('@pulse-compute/wasm-contracts/handler/canonical-native-plan');
|
|
6
|
+
const expected = ['__pulse_result',
|
|
7
|
+
...((plan.helpers || []).some(h => h.version !== CANONICAL_NATIVE_PURE_HELPER_VERSION) ? ['__pulse_helper_result'] : []),
|
|
8
|
+
...(plan.locals || []).map((_, i) => `__pulse_local_${i}`),
|
|
9
|
+
...(plan.effects || []).map((_, i) => `__pulse_effect_result_${i}`),
|
|
10
|
+
...(manifest.requestState.enabled ? ['__pulse_state_keys', '__pulse_state_values'] : [])];
|
|
11
|
+
if (JSON.stringify(manifest.handleOwnership.roots) !== JSON.stringify(expected)) {
|
|
12
|
+
throw new Error('Fastly handle ownership root descriptor mismatch');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function providerRoots(plan) {
|
|
17
|
+
return ['__pulse_request_headers_snapshot', '__pulse_fastly_payloads', '__pulse_fastly_ready',
|
|
18
|
+
'__pulse_handle_cache_roots', '__pulse_handle_resource_roots',
|
|
19
|
+
...((plan.capabilities || []).includes('request.body.forward') ? ['__fwd_marker', '__fwd_result'] : [])];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function providerSource(plan, enabled, roots = providerRoots(plan)) {
|
|
23
|
+
if (!enabled) return '';
|
|
24
|
+
if (JSON.stringify(roots) !== JSON.stringify(providerRoots(plan))) throw new Error('Fastly provider root descriptor mismatch');
|
|
25
|
+
const arrays = new Set(['__pulse_fastly_payloads', '__pulse_fastly_ready', '__pulse_handle_cache_roots', '__pulse_handle_resource_roots']);
|
|
26
|
+
return ['function __pulse_handle_provider_roots(): void {',
|
|
27
|
+
...roots.map(name => arrays.has(name)
|
|
28
|
+
? ` for (let i = 0; i < ${name}.length; i++) __pulse_handle_mark(unchecked(${name}[i]))`
|
|
29
|
+
: ` __pulse_handle_mark(${name})`), '}'].join('\n');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// This source is composed only with the canonical emitter's complete, eligible
|
|
33
|
+
// root descriptor. Synchronous host operations never call the checkpoint.
|
|
34
|
+
function fields(enabled) {
|
|
35
|
+
return enabled ? `
|
|
36
|
+
traceHandleEdges(): void {
|
|
37
|
+
if (this.valuesStorage !== null) {
|
|
38
|
+
const values = this.valuesStorage!
|
|
39
|
+
for (let i = 0; i < values.length; i++) {
|
|
40
|
+
const handle = unchecked(values[i])
|
|
41
|
+
if (handle <= 0) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 1, -1); return }
|
|
42
|
+
__pulse_handle_mark(handle)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (this.textLeft !== null) __pulse_handle_work!.push(this.textLeft!)
|
|
46
|
+
if (this.textRight !== null) __pulse_handle_work!.push(this.textRight!)
|
|
47
|
+
}
|
|
48
|
+
` : '';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function tableSource(enabled) {
|
|
52
|
+
if (!enabled) return `
|
|
53
|
+
@lazy const __pulse_fastly_values = new Array<__PulseFastlyValue>()
|
|
54
|
+
function __pulse_fastly_has_value(handle: i32): bool { return handle > 0 && handle <= __pulse_fastly_values.length }
|
|
55
|
+
function __pulse_fastly_value_count(): i32 { return __pulse_fastly_values.length }
|
|
56
|
+
function __pulse_fastly_put(value: __PulseFastlyValue): i32 { __pulse_fastly_values.push(value); return __pulse_fastly_values.length }
|
|
57
|
+
function __pulse_fastly_value(handle: i32): __PulseFastlyValue { if (!__pulse_fastly_has_value(handle)) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 1, -1); return new __PulseFastlyValue() } return unchecked(__pulse_fastly_values[handle - 1]) }
|
|
58
|
+
`;
|
|
59
|
+
return `
|
|
60
|
+
@lazy let __pulse_fastly_values = new Map<i32, __PulseFastlyValue>()
|
|
61
|
+
// The existing caches never evict or replace entries. Record each insertion
|
|
62
|
+
// once so tracing is proportional to occupied slots, not 132,101 possible slots.
|
|
63
|
+
@lazy const __pulse_handle_cache_roots = new Array<i32>()
|
|
64
|
+
// Wrappers keep their existing request lifetime. Resource IDs are never traced
|
|
65
|
+
// as value handles; collection neither closes nor cancels a host resource.
|
|
66
|
+
@lazy const __pulse_handle_resource_roots = new Array<i32>()
|
|
67
|
+
let __pulse_handle_next: i32 = 0
|
|
68
|
+
let __pulse_handle_allocations: i32 = 0
|
|
69
|
+
let __pulse_handle_interval: i32 = 1024
|
|
70
|
+
let __pulse_handle_survivors: Map<i32, __PulseFastlyValue> | null = null
|
|
71
|
+
let __pulse_handle_work: Array<__PulseFastlyValue> | null = null
|
|
72
|
+
function __pulse_fastly_has_value(handle: i32): bool { return handle > 0 && __pulse_fastly_values.has(handle) }
|
|
73
|
+
function __pulse_fastly_value_count(): i32 { return __pulse_fastly_values.size }
|
|
74
|
+
function __pulse_fastly_put(value: __PulseFastlyValue): i32 {
|
|
75
|
+
// Provider-owned terminal error responses still allocate after a latched
|
|
76
|
+
// failure, just as on the retained path. Checkpoints remain suppressed.
|
|
77
|
+
if (__pulse_handle_next == i32.MAX_VALUE) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 1, -1); return 0 }
|
|
78
|
+
const handle = ++__pulse_handle_next
|
|
79
|
+
__pulse_fastly_values.set(handle, value)
|
|
80
|
+
if (value.kind == PULSE_VALUE_FETCH || value.s3Body || value.responseHandle != 0 || value.bodyHandle != 0) __pulse_handle_resource_roots.push(handle)
|
|
81
|
+
__pulse_handle_allocations++
|
|
82
|
+
return handle
|
|
83
|
+
}
|
|
84
|
+
function __pulse_fastly_value(handle: i32): __PulseFastlyValue {
|
|
85
|
+
if (!__pulse_fastly_has_value(handle)) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 1, -1); return new __PulseFastlyValue() }
|
|
86
|
+
return __pulse_fastly_values.get(handle)
|
|
87
|
+
}
|
|
88
|
+
function __pulse_handle_mark(handle: i32): void {
|
|
89
|
+
if (handle == 0 || __pulse_fastly_last_error != 0) return
|
|
90
|
+
const survivors = __pulse_handle_survivors!
|
|
91
|
+
if (survivors.has(handle)) return
|
|
92
|
+
if (!__pulse_fastly_has_value(handle)) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 1, -1); return }
|
|
93
|
+
const value = __pulse_fastly_values.get(handle)
|
|
94
|
+
survivors.set(handle, value)
|
|
95
|
+
__pulse_handle_work!.push(value)
|
|
96
|
+
}
|
|
97
|
+
@noinline
|
|
98
|
+
function __pulse_handle_checkpoint(): void {
|
|
99
|
+
if (__pulse_fastly_last_error != 0 || __pulse_error != 0 || __pulse_handle_allocations < __pulse_handle_interval) return
|
|
100
|
+
// Separate handle and object visitation: ropes retain direct objects, and
|
|
101
|
+
// multiple handle IDs can reference one object. Neither permits losing an ID.
|
|
102
|
+
__pulse_handle_survivors = new Map<i32, __PulseFastlyValue>()
|
|
103
|
+
__pulse_handle_work = new Array<__PulseFastlyValue>()
|
|
104
|
+
const visited = new Set<__PulseFastlyValue>()
|
|
105
|
+
__pulse_handle_guest_roots()
|
|
106
|
+
__pulse_handle_provider_roots()
|
|
107
|
+
const work = __pulse_handle_work!
|
|
108
|
+
while (work.length > 0 && __pulse_fastly_last_error == 0) {
|
|
109
|
+
const value = work.pop()
|
|
110
|
+
if (visited.has(value)) continue
|
|
111
|
+
visited.add(value)
|
|
112
|
+
value.traceHandleEdges()
|
|
113
|
+
}
|
|
114
|
+
// Publish only a complete trace. Rebuilding also drops historical Map
|
|
115
|
+
// insertion/deletion capacity; no append-only tombstone vector remains.
|
|
116
|
+
if (__pulse_fastly_last_error == 0) {
|
|
117
|
+
__pulse_fastly_values = __pulse_handle_survivors!
|
|
118
|
+
__pulse_handle_interval = max<i32>(1024, __pulse_fastly_values.size)
|
|
119
|
+
__pulse_handle_allocations = 0
|
|
120
|
+
}
|
|
121
|
+
__pulse_handle_survivors = null
|
|
122
|
+
__pulse_handle_work = null
|
|
123
|
+
}
|
|
124
|
+
`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = { fields, tableSource, validateRoots, providerRoots, providerSource };
|
|
@@ -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};
|
|
@@ -519,6 +519,10 @@ class __PulseJsonParser {
|
|
|
519
519
|
}
|
|
520
520
|
|
|
521
521
|
const __pulse_fastly_values = new Array<__PulseFastlyValue>()
|
|
522
|
+
// Shared schema admission uses storage-independent lookups. This retained
|
|
523
|
+
// HTTP emitter keeps its existing dense table and lifetime policy.
|
|
524
|
+
function __pulse_fastly_has_value(handle: i32): bool { return handle > 0 && handle <= __pulse_fastly_values.length }
|
|
525
|
+
function __pulse_fastly_value_count(): i32 { return __pulse_fastly_values.length }
|
|
522
526
|
const __pulse_fastly_pending = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT)
|
|
523
527
|
const __pulse_fastly_pending_active = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT)
|
|
524
528
|
const __pulse_fastly_payloads = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT)
|
|
@@ -880,6 +884,7 @@ function compileFastlyNativeHttpEffectsPlan(plan, options = {}) {
|
|
|
880
884
|
'--path', jsonAs.dependencyRoot
|
|
881
885
|
);
|
|
882
886
|
}
|
|
887
|
+
require('@pulse-compute/wasm-build-support/native-optimization').appendJsonCacheArenaArgs(args, options.elideJsonCacheArena, schemaCodecsActive);
|
|
883
888
|
const startedAt = Date.now();
|
|
884
889
|
const result = spawnSync(asc.executable, args, {
|
|
885
890
|
cwd: stagingDir,
|
|
@@ -910,7 +915,7 @@ function compileFastlyNativeHttpEffectsPlan(plan, options = {}) {
|
|
|
910
915
|
compilerVersion: FASTLY_NATIVE_HTTP_EFFECTS_COMPILER_VERSION,
|
|
911
916
|
assemblyScript: Object.freeze({ package: 'assemblyscript', version: require(path.join(asc.packageRoot, 'package.json')).version }),
|
|
912
917
|
jsonAs: jsonAs
|
|
913
|
-
? Object.freeze({ package: 'json-as', version: jsonAs.version, transform: true, strict: true, mode: 'NAIVE', fastPath: false })
|
|
918
|
+
? Object.freeze({ package: 'json-as', version: jsonAs.version, transform: true, strict: true, mode: 'NAIVE', fastPath: false, elideDisabledCacheArena: options.elideJsonCacheArena === true })
|
|
914
919
|
: undefined,
|
|
915
920
|
wasm: Object.freeze({ bytes: inspection.bytes, sha256: inspection.sha256, magic: inspection.magic }),
|
|
916
921
|
wat: Object.freeze({ bytes: Buffer.byteLength(wat), sha256: sha256(wat) }),
|