@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.
@@ -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) }),