@pulse-compute/provider-fastly 1.0.0-beta.10 → 1.0.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/package.json +11 -11
- package/src/build/canonical-target.js +6 -0
- package/src/build/effect-invocations.js +6 -4
- package/src/build/host-extensions.js +254 -0
- package/src/build/lifecycle.js +50 -0
- package/src/build/native-body-forwarding.as.ts +28 -44
- package/src/build/native-body-forwarding.js +15 -11
- package/src/build/native-fetch-accounting.as.ts +133 -0
- package/src/build/native-fetch-accounting.js +84 -0
- package/src/build/native-fetch-bounds.js +47 -0
- package/src/build/native-fetch-cohorts.as.ts +126 -0
- package/src/build/native-fetch-cohorts.js +65 -0
- package/src/build/native-fetch-deadlines.as.ts +13 -8
- package/src/build/native-fetch-deadlines.js +13 -4
- package/src/build/native-fetch-errors.js +2 -2
- package/src/build/native-fetch-headers.js +52 -0
- package/src/build/native-fetch-outcomes.js +87 -0
- package/src/build/native-fetch-timing.as.ts +74 -0
- package/src/build/native-fetch-timing.js +50 -0
- package/src/build/native-forwarding-accounting.as.ts +153 -0
- package/src/build/native-forwarding-accounting.js +47 -0
- package/src/build/native-header-lookup.js +2 -2
- package/src/build/native-http-effects.js +10 -4
- package/src/build/native-platform-capabilities.js +89 -39
- package/src/build/native-request-body.js +6 -5
- package/src/build/{request-budget.as.ts → request-budget-source.js} +12 -4
- package/src/build/request-budget.js +7 -8
- package/src/build/response-authority.js +56 -0
- package/src/javascript/fetch-adapter.js +3 -0
- package/src/javascript/runtime-host.js +1 -0
- package/src/provider-contract.js +2 -1
- package/src/testing/native-http-effects-host.js +9 -0
- package/src/testing/native-platform-capabilities-host.js +10 -0
- package/src/toolchain/index.js +2 -1
- package/src/toolchain/native-execution.js +7 -2
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Preserve ordered pairs (including repeated names) in the existing key/value
|
|
4
|
+
// carrier. Do not use object_set: it would replace duplicate keys.
|
|
5
|
+
function fetchHeadersSource() {
|
|
6
|
+
return `
|
|
7
|
+
function __pulse_fastly_fetch_header_valid(name: string, value: __PulseFastlyValue): bool {
|
|
8
|
+
if (name.length == 0 || value.kind != PULSE_VALUE_STRING) return false
|
|
9
|
+
for (let i = 0; i < name.length; i++) {
|
|
10
|
+
const c = name.charCodeAt(i)
|
|
11
|
+
if ((c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122)) continue
|
|
12
|
+
if ("!#$%&'*+-.^_\u0060|~".indexOf(name.charAt(i)) < 0) return false
|
|
13
|
+
}
|
|
14
|
+
for (let i = 0; i < value.text.length; i++) {
|
|
15
|
+
const c = value.text.charCodeAt(i)
|
|
16
|
+
if (c == 0 || c == 10 || c == 13 || c > 255) return false
|
|
17
|
+
}
|
|
18
|
+
return true
|
|
19
|
+
}
|
|
20
|
+
function __pulse_fastly_fetch_headers(input: __PulseFastlyValue, effectIndex: i32): __PulseFastlyValue {
|
|
21
|
+
const output = new __PulseFastlyValue()
|
|
22
|
+
output.kind = PULSE_VALUE_OBJECT
|
|
23
|
+
if (input.kind == PULSE_VALUE_UNDEFINED) return output
|
|
24
|
+
if (input.kind == PULSE_VALUE_OBJECT) {
|
|
25
|
+
for (let i = 0; i < input.keys.length; i++) {
|
|
26
|
+
if (!__pulse_fastly_fetch_header_valid(input.keys[i], __pulse_fastly_value(input.values[i]))) {
|
|
27
|
+
__pulse_fastly_fail(PULSE_ERROR_VALUE, 74, effectIndex); return output
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return input
|
|
31
|
+
}
|
|
32
|
+
if (input.kind == PULSE_VALUE_ARRAY) {
|
|
33
|
+
for (let i = 0; i < input.values.length; i++) {
|
|
34
|
+
const pair = __pulse_fastly_value(input.values[i])
|
|
35
|
+
if (pair.kind != PULSE_VALUE_ARRAY || pair.values.length != 2) {
|
|
36
|
+
__pulse_fastly_fail(PULSE_ERROR_VALUE, 74, effectIndex); return output
|
|
37
|
+
}
|
|
38
|
+
const name = __pulse_fastly_value(pair.values[0])
|
|
39
|
+
if (name.kind != PULSE_VALUE_STRING || !__pulse_fastly_fetch_header_valid(name.text, __pulse_fastly_value(pair.values[1]))) {
|
|
40
|
+
__pulse_fastly_fail(PULSE_ERROR_VALUE, 74, effectIndex); return output
|
|
41
|
+
}
|
|
42
|
+
output.keys.push(name.text); output.values.push(pair.values[1])
|
|
43
|
+
}
|
|
44
|
+
return output
|
|
45
|
+
}
|
|
46
|
+
__pulse_fastly_fail(PULSE_ERROR_VALUE, 74, effectIndex)
|
|
47
|
+
return output
|
|
48
|
+
}
|
|
49
|
+
`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { fetchHeadersSource };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function instrument(input, plan) {
|
|
4
|
+
const selected = plan.effects.flatMap((effect, index) => effect.kind === 'fetch' && effect.fetchErrors === 'collect' ? [index] : []);
|
|
5
|
+
const wrapped = plan.effects.flatMap((effect,index)=>effect.kind==='fetch' && (effect.fetchErrors==='collect'||effect.fetchTiming) ? [index] : []);
|
|
6
|
+
const hasTiming = plan.effects.some(effect=>effect.fetchTiming);
|
|
7
|
+
let source = input;
|
|
8
|
+
const replace = (before, after) => {
|
|
9
|
+
if (source.split(before).length !== 2) throw Error('Fetch outcome boundary drift: ' + before);
|
|
10
|
+
source = source.replace(before, after);
|
|
11
|
+
};
|
|
12
|
+
// Even an unselected operation may receive a dynamic init object. Fail closed
|
|
13
|
+
// if it tries to change the statically planned result shape before dispatch.
|
|
14
|
+
const selectedIndex = selected.map(i => `effectIndex == ${i}`).join(' || ') || 'false';
|
|
15
|
+
const anchor = 'headers.kind = PULSE_VALUE_OBJECT; if (init.kind == PULSE_VALUE_OBJECT) {';
|
|
16
|
+
replace(anchor, anchor + `
|
|
17
|
+
const errorsIndex = __pulse_fastly_find(init, 'errors');
|
|
18
|
+
const errors = errorsIndex >= 0 ? __pulse_fastly_value(init.values[errorsIndex]) : new __PulseFastlyValue();
|
|
19
|
+
const collect = errors.kind == PULSE_VALUE_STRING && errors.text == 'collect';
|
|
20
|
+
if ((errorsIndex >= 0 && errors.kind != PULSE_VALUE_UNDEFINED && !collect) || collect != (${selectedIndex})) {
|
|
21
|
+
__pulse_fastly_fail(PULSE_ERROR_VALUE, 48, effectIndex); return;
|
|
22
|
+
}
|
|
23
|
+
`);
|
|
24
|
+
if (!wrapped.length) return source;
|
|
25
|
+
replace('function __pulse_fastly_fetch_begin(effectIndex: i32, payload: __PulseFastlyValue): void {',
|
|
26
|
+
'function __pulse_fetch_begin_uncollected(effectIndex: i32, payload: __PulseFastlyValue): void {');
|
|
27
|
+
replace('function __pulse_fastly_resolve_effect(effectIndex: i32): i32 {',
|
|
28
|
+
'function __pulse_fetch_resolve_uncollected(effectIndex: i32): i32 {');
|
|
29
|
+
// Keep host status available only while an explicitly selected fetch projection
|
|
30
|
+
// runs. Schema, invalid handle/ABI and request-body errors stay fatal.
|
|
31
|
+
replace('if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 20, effectIndex); return "" }',
|
|
32
|
+
'if (status != FASTLY_STATUS_OK) { if (__pulse_fetch_projecting >= 0) __pulse_fetch_fail(status, 20, __pulse_fetch_projecting); else __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 20, effectIndex); return "" }');
|
|
33
|
+
source += `
|
|
34
|
+
// Per-site storage is reset on every admission; it never outlives invocation ownership.
|
|
35
|
+
const __pulse_fetch_collected_failure = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT)
|
|
36
|
+
let __pulse_fetch_projecting: i32 = -1
|
|
37
|
+
function __pulse_fetch_collects(index: i32): bool { return ${selected.map(i => `index == ${i}`).join(' || ') || 'false'} }
|
|
38
|
+
function __pulse_fetch_wraps(index: i32): bool { return ${wrapped.map(i => `index == ${i}`).join(' || ')} }
|
|
39
|
+
function __pulse_fetch_envelope(index: i32, value: i32, timeout: bool = false, rejected: bool = false): i32 {
|
|
40
|
+
const result = host_value_object()
|
|
41
|
+
host_value_object_set(result, __pulse_fastly_string_value('status'), __pulse_fastly_string_value(rejected ? 'rejected' : 'fulfilled'))
|
|
42
|
+
if (rejected) {
|
|
43
|
+
const error = host_value_object()
|
|
44
|
+
host_value_object_set(error, __pulse_fastly_string_value('code'), __pulse_fastly_string_value(timeout ? 'PULSE_FETCH_TIMEOUT' : 'PULSE_FETCH_NETWORK'))
|
|
45
|
+
host_value_object_set(result, __pulse_fastly_string_value('error'), error)
|
|
46
|
+
} else host_value_object_set(result, __pulse_fastly_string_value('value'), value)
|
|
47
|
+
${hasTiming ? "if (__pulse_timing_selected(index)) host_value_object_set(result, __pulse_fastly_string_value('elapsedMs'), __pulse_timing_elapsed(index))" : ''}
|
|
48
|
+
return result
|
|
49
|
+
}
|
|
50
|
+
function __pulse_fetch_collect_failure(index: i32): i32 {
|
|
51
|
+
if (!__pulse_fetch_collects(index) || !__pulse_fetch_request_check(index)) return 0
|
|
52
|
+
const stage = __pulse_fastly_error_stage
|
|
53
|
+
if (__pulse_fastly_last_error != PULSE_ERROR_TRANSPORT || __pulse_fastly_error_effect != index
|
|
54
|
+
|| (stage != 77 && stage != 81 && stage != 271 && stage != 20)) return 0
|
|
55
|
+
__pulse_fastly_last_error = 0; __pulse_fastly_error_stage = 0; __pulse_fastly_error_effect = -1
|
|
56
|
+
return __pulse_fetch_envelope(index, 0, stage == 271, true)
|
|
57
|
+
}
|
|
58
|
+
function __pulse_fastly_fetch_begin(effectIndex: i32, payload: __PulseFastlyValue): void {
|
|
59
|
+
__pulse_fetch_collected_failure[effectIndex] = 0
|
|
60
|
+
__pulse_fetch_begin_uncollected(effectIndex, payload)
|
|
61
|
+
if (__pulse_fastly_last_error == 0) return
|
|
62
|
+
const failure = __pulse_fetch_collect_failure(effectIndex)
|
|
63
|
+
if (failure <= 0) return
|
|
64
|
+
__pulse_fetch_collected_failure[effectIndex] = failure
|
|
65
|
+
__pulse_fastly_ready_effect(effectIndex, failure)
|
|
66
|
+
}
|
|
67
|
+
function __pulse_fastly_resolve_effect(effectIndex: i32): i32 {
|
|
68
|
+
const saved = __pulse_fetch_collected_failure[effectIndex]
|
|
69
|
+
if (saved > 0) {
|
|
70
|
+
if (!__pulse_fetch_request_check(effectIndex)) return 0
|
|
71
|
+
__pulse_fetch_collected_failure[effectIndex] = 0
|
|
72
|
+
__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_NONE
|
|
73
|
+
__pulse_fastly_ready[effectIndex] = 0
|
|
74
|
+
return saved
|
|
75
|
+
}
|
|
76
|
+
if (__pulse_fetch_wraps(effectIndex)) __pulse_fetch_projecting = effectIndex
|
|
77
|
+
const value = __pulse_fetch_resolve_uncollected(effectIndex)
|
|
78
|
+
__pulse_fetch_projecting = -1
|
|
79
|
+
if (!__pulse_fetch_wraps(effectIndex)) return value
|
|
80
|
+
if (!__pulse_fetch_request_check(effectIndex)) return 0
|
|
81
|
+
if (__pulse_fastly_last_error != 0) return __pulse_fetch_collect_failure(effectIndex)
|
|
82
|
+
return value > 0 ? __pulse_fetch_envelope(effectIndex, value) : 0
|
|
83
|
+
}
|
|
84
|
+
`;
|
|
85
|
+
return source;
|
|
86
|
+
}
|
|
87
|
+
module.exports = { instrument };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Optional transport observations reuse the fetch driver's existing wait set.
|
|
2
|
+
@external('fastly_async_io', 'is_ready') declare function __pulse_timing_ready(handle:i32,out:usize):i32
|
|
3
|
+
const __pulse_timing_starts = new StaticArray<i64>(PULSE_FASTLY_EFFECT_COUNT)
|
|
4
|
+
const __pulse_timing_ends = new StaticArray<i64>(PULSE_FASTLY_EFFECT_COUNT)
|
|
5
|
+
const __pulse_timing_clock_out = new StaticArray<i64>(1)
|
|
6
|
+
const __pulse_timing_io = new StaticArray<i32>(1)
|
|
7
|
+
class __PulseTimedBody {
|
|
8
|
+
value:i32; bytes:Uint8Array; total:i32=0; done:bool=false; last:i64=-2;
|
|
9
|
+
constructor(value:i32) { this.value=value; this.bytes=new Uint8Array(__PULSE_SCHEMA_MAX_BYTES+1); }
|
|
10
|
+
}
|
|
11
|
+
const __pulse_timing_bodies = new Array<__PulseTimedBody|null>(PULSE_FASTLY_EFFECT_COUNT)
|
|
12
|
+
function __pulse_timing_selected(index:i32):bool { return /*TIMED*/ }
|
|
13
|
+
function __pulse_timing_projected(index:i32):bool { return /*PROJECTED*/ }
|
|
14
|
+
function __pulse_timing_clock():i64 {
|
|
15
|
+
if(wasi_snapshot_preview1_clock_time_get(1,1,changetype<usize>(__pulse_timing_clock_out))!=0)return -1;
|
|
16
|
+
return __pulse_timing_clock_out[0]>=0?__pulse_timing_clock_out[0]:-1;
|
|
17
|
+
}
|
|
18
|
+
function __pulse_timing_reset(index:i32):void {
|
|
19
|
+
__pulse_timing_starts[index]=-1; __pulse_timing_ends[index]=-2; __pulse_timing_bodies[index]=null;
|
|
20
|
+
}
|
|
21
|
+
function __pulse_timing_begin(index:i32):void { if(__pulse_timing_selected(index))__pulse_timing_starts[index]=__pulse_timing_clock(); }
|
|
22
|
+
function __pulse_timing_end(index:i32):void { if(__pulse_timing_selected(index)&&__pulse_timing_ends[index]==-2)__pulse_timing_ends[index]=__pulse_timing_clock(); }
|
|
23
|
+
function __pulse_timing_elapsed(index:i32):i32 {
|
|
24
|
+
const a=__pulse_timing_starts[index],b=__pulse_timing_ends[index];
|
|
25
|
+
return a<0||b<a?host_value_null():host_value_number(f64(b-a)/1000000.0);
|
|
26
|
+
}
|
|
27
|
+
function __pulse_timing_active():bool {
|
|
28
|
+
for(let i=0;i<PULSE_FASTLY_EFFECT_COUNT;i++) {
|
|
29
|
+
if(__pulse_timing_selected(i)&&__pulse_fetch_active(i))return true;
|
|
30
|
+
const row=__pulse_timing_bodies[i];if(row!==null&&!row.done)return true;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
function __pulse_timing_headers(index:i32,value:i32):void {
|
|
35
|
+
if(!__pulse_timing_projected(index)){__pulse_fetch_complete(index,0,value);return;}
|
|
36
|
+
if(__PULSE_SCHEMA_MAX_BYTES<=0||__PULSE_SCHEMA_MAX_BYTES>1073741823){__pulse_fastly_fail(PULSE_ERROR_SCHEMA,21,index);return;}
|
|
37
|
+
// The member timeout ends at headers; enclosing authority still fences reads.
|
|
38
|
+
__pulse_fetch_timeout[index]=0;
|
|
39
|
+
__pulse_fastly_pending_mode[index]=PULSE_FASTLY_PENDING_READY;
|
|
40
|
+
__pulse_fastly_pending[index]=0;
|
|
41
|
+
__pulse_timing_bodies[index]=new __PulseTimedBody(value);
|
|
42
|
+
}
|
|
43
|
+
function __pulse_timing_read_round():void {
|
|
44
|
+
const out=__pulse_timing_io;
|
|
45
|
+
for(let i=0;i<PULSE_FASTLY_EFFECT_COUNT;i++) {
|
|
46
|
+
const row=__pulse_timing_bodies[i]; if(row===null||row.done)continue;
|
|
47
|
+
if(!__pulse_fetch_request_check(i))return;
|
|
48
|
+
const value=__pulse_fastly_value(row.value);
|
|
49
|
+
out[0]=-1;
|
|
50
|
+
if(__pulse_timing_ready(value.bodyHandle,changetype<usize>(out))!=0||out[0]<0||out[0]>1){__pulse_fastly_fail(PULSE_ERROR_HOSTCALL,272,i);return;}
|
|
51
|
+
if(out[0]==0)continue;
|
|
52
|
+
const capacity=min<i32>(PULSE_FASTLY_BUFFER_BYTES,row.bytes.length-row.total);
|
|
53
|
+
out[0]=-1;
|
|
54
|
+
const status=fastly_http_body_read(value.bodyHandle,row.bytes.dataStart+row.total,capacity,changetype<usize>(out));
|
|
55
|
+
if(status!=0){row.done=true;__pulse_fetch_complete(i,status);continue;}
|
|
56
|
+
if(out[0]<0||out[0]>capacity){__pulse_fastly_fail(PULSE_ERROR_HOSTCALL,20,i);return;}
|
|
57
|
+
if(out[0]==0){row.done=true;if(row.last!=-2)__pulse_timing_ends[i]=row.last;__pulse_fetch_complete(i,0,row.value);continue;}
|
|
58
|
+
const sample=__pulse_timing_clock();
|
|
59
|
+
row.last=row.last==-1||sample<row.last?-1:sample;
|
|
60
|
+
row.total+=out[0];
|
|
61
|
+
if(row.total>__PULSE_SCHEMA_MAX_BYTES){__pulse_fastly_fail(PULSE_ERROR_SCHEMA,21,i);return;}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function __pulse_timing_decode(value:__PulseFastlyValue):bool {
|
|
65
|
+
if(value.bodyLoaded!=0)return true;
|
|
66
|
+
for(let i=0;i<PULSE_FASTLY_EFFECT_COUNT;i++) {
|
|
67
|
+
const row=__pulse_timing_bodies[i];
|
|
68
|
+
if(row!==null&&row.done&&__pulse_fastly_value(row.value)===value){
|
|
69
|
+
value.text=__pulse_fastly_decode(row.bytes,row.total);value.bodyLoaded=1;
|
|
70
|
+
__pulse_timing_bodies[i]=null;return true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
function instrument(input, plan) {
|
|
5
|
+
const timed = plan.effects.flatMap((e,i)=>e.kind==='fetch' && e.fetchTiming ? [i] : []);
|
|
6
|
+
const projected = timed.filter(i=>plan.effects[i].decoder);
|
|
7
|
+
let source = input;
|
|
8
|
+
const replace = (a,b) => { if(source.split(a).length!==2)throw Error('Fetch timing boundary drift: '+a);source=source.replace(a,b); };
|
|
9
|
+
const selected = index => timed.map(i=>`${index} == ${i}`).join(' || ') || 'false';
|
|
10
|
+
// Dynamic init must not introduce optional instrumentation after compilation.
|
|
11
|
+
const anchor = 'headers.kind = PULSE_VALUE_OBJECT; if (init.kind == PULSE_VALUE_OBJECT) {';
|
|
12
|
+
replace(anchor, anchor+`
|
|
13
|
+
const timingIndex = __pulse_fastly_find(init, 'timing');
|
|
14
|
+
const timing = timingIndex >= 0 ? __pulse_fastly_value(init.values[timingIndex]) : new __PulseFastlyValue();
|
|
15
|
+
if ((timingIndex >= 0 && timing.kind != PULSE_VALUE_UNDEFINED && timing.kind != PULSE_VALUE_BOOLEAN)
|
|
16
|
+
|| (timing.kind == PULSE_VALUE_BOOLEAN && timing.boolean) != (${selected('effectIndex')})) {
|
|
17
|
+
__pulse_fastly_fail(PULSE_ERROR_VALUE, 48, effectIndex); return;
|
|
18
|
+
}
|
|
19
|
+
`);
|
|
20
|
+
if(!timed.length)return source;
|
|
21
|
+
replace('function __pulse_fetch_begin_deadline(index: i32, init: __PulseFastlyValue): void {',
|
|
22
|
+
'function __pulse_fetch_begin_deadline(index: i32, init: __PulseFastlyValue): void { __pulse_timing_reset(index);');
|
|
23
|
+
replace('status = fastly_http_req_send_async(requestHandle, bodyHandle,',
|
|
24
|
+
'__pulse_timing_begin(effectIndex); status = fastly_http_req_send_async(requestHandle, bodyHandle,');
|
|
25
|
+
replace('function __pulse_fetch_has_deadlines(): bool {',
|
|
26
|
+
'function __pulse_fetch_has_deadlines(): bool { if (__pulse_timing_active()) return true;');
|
|
27
|
+
replace('function __pulse_fetch_complete(index: i32, outcome: i32, result: i32 = 0): void {',
|
|
28
|
+
'function __pulse_fetch_complete(index: i32, outcome: i32, result: i32 = 0): void { __pulse_timing_end(index);');
|
|
29
|
+
replace('__pulse_fetch_complete(i, 0, __pulse_fastly_put(value)); continue',
|
|
30
|
+
'__pulse_timing_headers(i, __pulse_fastly_put(value)); continue');
|
|
31
|
+
replace('while (!__pulse_fetch_completed[index]) {',
|
|
32
|
+
'while (!__pulse_fetch_completed[index] || __pulse_timing_active()) {');
|
|
33
|
+
// Both early-return fences must wait for transport observations, never decoding.
|
|
34
|
+
source=source.replaceAll('if (__pulse_fetch_completed[index]) break',
|
|
35
|
+
'if (__pulse_fetch_completed[index] && !__pulse_timing_active()) break');
|
|
36
|
+
replace(' if (__pulse_fetch_completed[index] && !__pulse_timing_active()) break\n if (!__pulse_fetch_request_check(index))',
|
|
37
|
+
' __pulse_timing_read_round(); if (__pulse_fastly_last_error != 0) return 0;\n if (__pulse_fetch_completed[index] && !__pulse_timing_active()) break\n if (!__pulse_fetch_request_check(index))');
|
|
38
|
+
// Add active bodies to the same wait set. One pending request OR body per site.
|
|
39
|
+
replace(' remaining = Math.min(remaining, f64(__pulse_fetch_request_remaining()))',
|
|
40
|
+
' for (let i = 0; i < PULSE_FASTLY_EFFECT_COUNT; i++) { const row = __pulse_timing_bodies[i]; if (row !== null && !row.done) handles[count++] = __pulse_fastly_value(row.value).bodyHandle; }\n remaining = Math.min(remaining, f64(__pulse_fetch_request_remaining()))');
|
|
41
|
+
replace('function __pulse_fastly_fetch_body(value: __PulseFastlyValue): string {',
|
|
42
|
+
'function __pulse_fastly_fetch_body(value: __PulseFastlyValue): string { if (__pulse_timing_decode(value)) return value.text;');
|
|
43
|
+
replace('const failure = __pulse_fetch_collect_failure(effectIndex)',
|
|
44
|
+
'__pulse_timing_end(effectIndex); const failure = __pulse_fetch_collect_failure(effectIndex)');
|
|
45
|
+
source += fs.readFileSync(path.join(__dirname,'native-fetch-timing.as.ts'),'utf8')
|
|
46
|
+
.replace('/*TIMED*/',selected('index'))
|
|
47
|
+
.replace('/*PROJECTED*/',projected.map(i=>`index == ${i}`).join(' || ') || 'false');
|
|
48
|
+
return source;
|
|
49
|
+
}
|
|
50
|
+
module.exports = { instrument };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// One ledger is bound to this Wasm instance's single managed invocation. Raw
|
|
2
|
+
// memory/ABI access is trusted; no scalar resource identity crosses the host ABI.
|
|
3
|
+
// The entry guard prevents reuse; this closed, one-cohort profile never recycles
|
|
4
|
+
// slots. Descriptor positions/generation 1 are validated at the commit boundary.
|
|
5
|
+
let __fwd_ra: PulseResourceLedger | null = null;
|
|
6
|
+
let __fra_origin: i64 = 0;
|
|
7
|
+
let __fra_clock_failed: bool = false;
|
|
8
|
+
let __fra_reaped: bool = false;
|
|
9
|
+
function __fra_ns(slot: i32): u32 {
|
|
10
|
+
return slot == 0 || slot == 2 ? 1 : slot == 4 ? 4 : slot == 5 || slot == 7 || slot == 9 ? 2 : 3;
|
|
11
|
+
}
|
|
12
|
+
function __fra_now(): u64 {
|
|
13
|
+
if (__fra_clock_failed) return PRL_SAFE + 1;
|
|
14
|
+
const now = __request_clock();
|
|
15
|
+
if (now < __fra_origin || __request_expired) { __fra_clock_failed = true; return PRL_SAFE + 1; }
|
|
16
|
+
return u64(now - __fra_origin);
|
|
17
|
+
}
|
|
18
|
+
function __fra_ok(ok: bool): bool {
|
|
19
|
+
if (!ok) __fwd_fail(__request_expired ? 504 : 502);
|
|
20
|
+
return ok;
|
|
21
|
+
}
|
|
22
|
+
function __fra_start(): bool {
|
|
23
|
+
if (__fwd_ra !== null || __request_deadline < 0 || __request_last_clock < 0) return false;
|
|
24
|
+
__fra_origin = __request_deadline - i64(__FRA_DURATION);
|
|
25
|
+
const ledger = new PulseResourceLedger(1, 15, 11, __FRA_BACKING, 12, 1, __FRA_DURATION, __FRA_STORAGE);
|
|
26
|
+
__fwd_ra = ledger;
|
|
27
|
+
if (!ledger.begin(1, __fra_now(), 10, 2, 1, true)) return __fra_ok(false);
|
|
28
|
+
for (let i = 0; i < 11; i++)
|
|
29
|
+
if (!ledger.stageRecord(1, 1, __fra_ns(i), 1, i >= 9 ? 3 : 1, 0)) return __fra_ok(false);
|
|
30
|
+
if (!ledger.stageRecord(1, 2, 0, 1, 1, 33920)
|
|
31
|
+
|| !ledger.stageRecord(1, 2, 0, 1, 1, __FRA_ENCODING)
|
|
32
|
+
|| !ledger.stageRecord(1, 2, 0, 1, 3, 64)
|
|
33
|
+
|| !ledger.stageRecord(1, 3, 0, 1, 1, 0)
|
|
34
|
+
|| !ledger.commit(1, __fra_now())) return __fra_ok(false);
|
|
35
|
+
for (let i = 0; i < 15; i++)
|
|
36
|
+
if (ledger.reservedSlot(i) != i || ledger.reservedGeneration(i) != 1) return __fra_ok(false);
|
|
37
|
+
// Static scratch is present in the artifact, charged once before first use.
|
|
38
|
+
// Encoding allowance is consumed before any request/header UTF-8 allocation.
|
|
39
|
+
return __fra_ok(ledger.consume(1,11,1,1,33920,false,__fra_now())
|
|
40
|
+
&& ledger.consume(1,12,1,1,__FRA_ENCODING,false,__fra_now()));
|
|
41
|
+
}
|
|
42
|
+
function __fra_acquire(slot: i32): bool {
|
|
43
|
+
return __fwd_ra !== null && __fra_ok(__fwd_ra!.beginAcquire(1,slot,1,__fra_ns(slot),1,slot >= 9,__fra_now()));
|
|
44
|
+
}
|
|
45
|
+
function __fra_acquired(slot: i32, status: i32, handle: i32): i32 {
|
|
46
|
+
const ledger = __fwd_ra!;
|
|
47
|
+
if (status == 0) __fra_ok(ledger.acquired(1,slot,1,__fra_ns(slot),1,u32(handle)));
|
|
48
|
+
else { ledger.noAcquisition(1,slot,1,__fra_ns(slot),1,false); __fwd_fail(502); }
|
|
49
|
+
return status;
|
|
50
|
+
}
|
|
51
|
+
function __fra_incoming(request: usize, body: usize): i32 {
|
|
52
|
+
if (!__fra_start() || !__fra_acquire(0) || !__fra_acquire(1)) return 1;
|
|
53
|
+
const status = fastly_http_req_body_downstream_get(request,body);
|
|
54
|
+
__fra_acquired(0,status,load<i32>(request)); __fra_acquired(1,status,load<i32>(body));
|
|
55
|
+
return status;
|
|
56
|
+
}
|
|
57
|
+
function __fra_new(slot: i32, out: usize): i32 {
|
|
58
|
+
if (!__fra_acquire(slot)) return 1;
|
|
59
|
+
const ns = __fra_ns(slot);
|
|
60
|
+
const status = ns == 1 ? fastly_http_req_new(out) : ns == 2 ? fastly_http_resp_new(out) : fastly_http_body_new(out);
|
|
61
|
+
return __fra_acquired(slot,status,load<i32>(out));
|
|
62
|
+
}
|
|
63
|
+
function __fra_dispose(slot: i32, abandon: bool = false): i32 {
|
|
64
|
+
if (__fwd_ra === null) return 1;
|
|
65
|
+
const ledger = __fwd_ra!, at = slot * 8;
|
|
66
|
+
if (ledger.bank[at+5] != PRL_LIVE) return 1;
|
|
67
|
+
// Disposal does not replace a primary error and never replenishes time.
|
|
68
|
+
if (!ledger.beginDispose(1,slot,1,__fra_ns(slot),1,__fra_now())) return 1;
|
|
69
|
+
const handle = i32(ledger.bank[at+6]), ns = __fra_ns(slot);
|
|
70
|
+
const status = ns == 1 ? __pulse_fetch_close_request(handle) : ns == 2 ? __pulse_fetch_close_response(handle)
|
|
71
|
+
: abandon ? __fwd_abandon(handle) : __pulse_fetch_close_body(handle);
|
|
72
|
+
ledger.disposed(1,slot,1,ns,1,status == 0);
|
|
73
|
+
return status;
|
|
74
|
+
}
|
|
75
|
+
function __fra_dispatch(request: i32, body: i32, backend: usize, length: i32, out: usize): i32 {
|
|
76
|
+
if (__pulse_invocation_count != 1 || __pulse_invocation_tickets[__fwd_index] != 1
|
|
77
|
+
|| !__fra_acquire(4) || !__fra_acquire(5) || !__fra_acquire(6)) { __fwd_fail(502); return 1; }
|
|
78
|
+
const ledger = __fwd_ra!;
|
|
79
|
+
// Fence the possibly consumed request before send; upload stays guest-owned.
|
|
80
|
+
if (!ledger.beginDispose(1,2,1,1,1,__fra_now())) return 1;
|
|
81
|
+
const status = __fwd_dispatch(request,body,backend,length,out);
|
|
82
|
+
ledger.disposed(1,2,1,1,1,status == 0);
|
|
83
|
+
return __fra_acquired(4,status,load<i32>(out));
|
|
84
|
+
}
|
|
85
|
+
function __fra_poll(): i32 {
|
|
86
|
+
const ledger = __fwd_ra!;
|
|
87
|
+
// Outputs are fenced before dispatch and retained across qualified not-ready
|
|
88
|
+
// results. A failed/invalid poll is never retried, including during cleanup.
|
|
89
|
+
const status = __pulse_fetch_poll(__fwd_pending,__fwd_detail,__fwd_done,__fwd_response_out,__fwd_body_out);
|
|
90
|
+
const done = load<i32>(__fwd_done);
|
|
91
|
+
if (status != 0 || (done != 0 && done != 1)) {
|
|
92
|
+
ledger.uncertain(1,4,1,4,1);
|
|
93
|
+
ledger.noAcquisition(1,5,1,2,1,false); ledger.noAcquisition(1,6,1,3,1,false);
|
|
94
|
+
return status == 0 ? 1 : status;
|
|
95
|
+
}
|
|
96
|
+
if (done == 1) {
|
|
97
|
+
ledger.transferred(1,4,1,4,1);
|
|
98
|
+
__fra_acquired(5,0,load<i32>(__fwd_response_out)); __fra_acquired(6,0,load<i32>(__fwd_body_out));
|
|
99
|
+
}
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
function __fra_send(response: i32, body: i32, streaming: i32): i32 {
|
|
103
|
+
const ledger = __fwd_ra!, r = streaming == 1 ? 7 : 9, b = streaming == 1 ? 8 : 10;
|
|
104
|
+
if (!ledger.beginDispose(1,r,1,2,1,__fra_now())) return 1;
|
|
105
|
+
if (streaming == 0 && !ledger.beginDispose(1,b,1,3,1,__fra_now())) return 1;
|
|
106
|
+
const status = fastly_http_resp_send_downstream(response,body,streaming);
|
|
107
|
+
ledger.disposed(1,r,1,2,1,status == 0);
|
|
108
|
+
if (streaming == 0) ledger.disposed(1,b,1,3,1,status == 0);
|
|
109
|
+
return status;
|
|
110
|
+
}
|
|
111
|
+
function __fra_cleanup(final: bool = false): void {
|
|
112
|
+
if (__fwd_ra === null) return;
|
|
113
|
+
const ledger = __fwd_ra!;
|
|
114
|
+
ledger.close(1);
|
|
115
|
+
// One reserved, nonblocking observation may adopt already-fenced outputs.
|
|
116
|
+
// Never retry unknown outcomes, acquire new claims, or extend the deadline.
|
|
117
|
+
if (!__fra_reaped && ledger.bank[4*8+5] == PRL_LIVE) {
|
|
118
|
+
__fra_reaped = true;
|
|
119
|
+
if (ledger.cleanupStep(1,false,__fra_now())) {
|
|
120
|
+
__pulse_send_error_detail_reset(__fwd_detail); store<i32>(__fwd_done,0);
|
|
121
|
+
__fra_poll();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// No pending-request cancel ABI. Remaining claims stay held for host teardown.
|
|
125
|
+
for (let slot = 0; slot < 11; slot++) {
|
|
126
|
+
const state = ledger.bank[slot*8+5];
|
|
127
|
+
if (state == PRL_RESERVED && (slot < 9 || final)) ledger.unused(1,slot,1,__fra_ns(slot),1);
|
|
128
|
+
else if (state == PRL_LIVE && slot != 4) __fra_dispose(slot,slot == 3 || slot == 8);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function __fra_error(status: i32): void {
|
|
132
|
+
if (__fwd_ra === null || !__pulse_response_owned()) return;
|
|
133
|
+
const ledger = __fwd_ra!;
|
|
134
|
+
if (!ledger.consume(1,13,1,1,64,true,__fra_now())) return;
|
|
135
|
+
// A fixed exit body uses the already-reserved scratch, without serialization
|
|
136
|
+
// or an application value allocation. Authority fences the one final send.
|
|
137
|
+
const text = status == 413 ? 'Payload Too Large' : status == 504 ? 'Gateway Timeout' : status == 400 ? 'Bad Request' : 'Bad Gateway';
|
|
138
|
+
if (__fra_new(9,__fwd_io+52) != 0) return;
|
|
139
|
+
const response = load<i32>(__fwd_io+52);
|
|
140
|
+
if (__fra_new(10,__fwd_io+52) != 0) return;
|
|
141
|
+
const body = load<i32>(__fwd_io+52);
|
|
142
|
+
if (__fra_now() >= __FRA_DURATION || fastly_http_resp_status_set(response,status) != 0) return;
|
|
143
|
+
for (let i = 0; i < text.length; i++) store<u8>(__fwd_buffer+i,u8(text.charCodeAt(i)));
|
|
144
|
+
let offset = 0;
|
|
145
|
+
while (offset < text.length) {
|
|
146
|
+
if (__fra_now() >= __FRA_DURATION) return;
|
|
147
|
+
const status = fastly_http_body_write(body,__fwd_buffer+offset,text.length-offset,0,__fwd_io+52);
|
|
148
|
+
const written = load<i32>(__fwd_io+52);
|
|
149
|
+
if (status != 0 || written <= 0 || written > text.length-offset) return;
|
|
150
|
+
offset += written;
|
|
151
|
+
}
|
|
152
|
+
if (__fra_send(response,body,0) == 0) __pulse_fastly_last_error = 0; // delivery handled; ledger primary remains
|
|
153
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Trusted provider profile producer. Selection follows the existing canonical
|
|
3
|
+
// forwarding validator; no authored profile, test limit or emitter callback.
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { resourceAdmission: admission } = require('@pulse-compute/wasm-contracts/provider/toolchain');
|
|
7
|
+
const { resourceLedgerSource } = require('@pulse-compute/wasm-runtime-core-as/compiler/canonical-native');
|
|
8
|
+
const REALIZATION = 'fastly.native.forwarding.fixed-v1';
|
|
9
|
+
const empty = () => ({ handles: [], allocations: [], records: 0, cleanupSteps: 0 });
|
|
10
|
+
const fixed = (id, bytes) => ({ id, kind: 'fixed', bytes, copies: 1 });
|
|
11
|
+
function profile(plan, bindings) {
|
|
12
|
+
if (!plan.capabilities.includes('request.body.forward')) return null;
|
|
13
|
+
const effect = plan.effects[0];
|
|
14
|
+
const init = effect.inputs.find(x => x.name === 'init').value;
|
|
15
|
+
const headers = init.entries.find(x => x.key.value === 'headers')?.value.entries || [];
|
|
16
|
+
const url = effect.inputs.find(x => x.name === 'url').value;
|
|
17
|
+
const backend = bindings.backends.find(x => x.index === 0)?.backend;
|
|
18
|
+
if (plan.effects.length !== 1 || effect.grouped || url.kind !== 'literal' || typeof url.value !== 'string'
|
|
19
|
+
|| typeof backend !== 'string' || !backend || !Number.isInteger(bindings.maxDurationMs)
|
|
20
|
+
|| bindings.maxDurationMs < 1 || bindings.maxDurationMs > 30000) throw new TypeError('Unproved forwarding accounting profile');
|
|
21
|
+
const encodingBytes = Buffer.byteLength(url.value) + Buffer.byteLength(backend)
|
|
22
|
+
+ headers.reduce((n, h) => n + Buffer.byteLength(h.key.value) + Buffer.byteLength(h.value.value), 0);
|
|
23
|
+
const work = { handles: [{namespace:'http.request',count:2},{namespace:'http.body',count:4},
|
|
24
|
+
{namespace:'http.pending',count:1},{namespace:'http.response',count:2}],
|
|
25
|
+
allocations: [fixed('scratch',33920),fixed('encoding',encodingBytes)], records:1, cleanupSteps:0 };
|
|
26
|
+
const cleanup = {...empty(),cleanupSteps:10};
|
|
27
|
+
const exit = { handles:[{namespace:'http.response',count:1},{namespace:'http.body',count:1}],
|
|
28
|
+
allocations:[fixed('error-text',64)],records:0,cleanupSteps:2 };
|
|
29
|
+
const required = {handles:11,backingBytes:33984+encodingBytes,records:15,cleanupSteps:12,effects:1};
|
|
30
|
+
const result = admission.createPlan({version:admission.VERSION,realization:REALIZATION,
|
|
31
|
+
selected:[{site:'forward',count:1,profile:{version:admission.PROFILE_VERSION,owner:'provider-fastly',
|
|
32
|
+
operation:'bounded-forward',realization:REALIZATION,effects:1,work,cleanup,
|
|
33
|
+
exclusions:['request-context','value-wrappers','diagnostic-storage','driver-identity-storage','non-forwarding-driver-responses']}}],
|
|
34
|
+
exit,occupied:Object.fromEntries(admission.DIMENSIONS.map(k=>[k,0])),limits:required});
|
|
35
|
+
if (!result.admitted || Object.keys(required).some(k=>result.required[k]!==required[k])) throw new Error('Forwarding resource inventory drift');
|
|
36
|
+
const storageBytes = 1024 + result.required.records * 128;
|
|
37
|
+
if (storageBytes > 2147483647 || !Number.isSafeInteger(encodingBytes)) throw new RangeError('Forwarding resource representation');
|
|
38
|
+
return Object.freeze({ ...result, encodingBytes, storageBytes, durationNs:bindings.maxDurationMs*1000000,
|
|
39
|
+
selection:'canonical-terminal-forwarding', executionIdentity:'private-single-invocation-wasm-instance',
|
|
40
|
+
unprofiled:'general-fetch-decoding-cache-composition-and-javascript' });
|
|
41
|
+
}
|
|
42
|
+
function source(selected) {
|
|
43
|
+
if (!selected) return '';
|
|
44
|
+
return resourceLedgerSource(true) + `\nconst __FRA_ENCODING: u64 = ${selected.encodingBytes};\nconst __FRA_BACKING: u64 = ${selected.required.backingBytes};\nconst __FRA_STORAGE: u32 = ${selected.storageBytes};\nconst __FRA_DURATION: u64 = ${selected.durationNs};\n`
|
|
45
|
+
+ fs.readFileSync(path.join(__dirname,'native-forwarding-accounting.as.ts'),'utf8');
|
|
46
|
+
}
|
|
47
|
+
module.exports = { profile, source };
|
|
@@ -39,11 +39,11 @@ function __pulse_header_send_error(): bool {
|
|
|
39
39
|
}
|
|
40
40
|
`;
|
|
41
41
|
}
|
|
42
|
-
function instrument(source) {
|
|
42
|
+
function instrument(source, lifecycle) {
|
|
43
43
|
source = source.replace(/function (host_\w+)\(([^)]*)\): (i32|void) \{/g,
|
|
44
44
|
(declaration, name, _args, result) => name === 'host_router_error_take' ? declaration
|
|
45
45
|
: `${declaration} if (__pulse_header_lookup_failed()) return${result === 'void' ? '' : ' 0'};`);
|
|
46
|
-
const marker =
|
|
46
|
+
const marker = `function ${lifecycle?.legacyName('__pulse_fastly_jwt_send_error') || '__pulse_fastly_jwt_send_error'}(): void {`;
|
|
47
47
|
if (!source.includes(marker)) throw new Error('Fastly header-lookup error boundary drift.');
|
|
48
48
|
return source.replace(marker, marker + ' if (__pulse_header_send_error()) return;');
|
|
49
49
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { fetchHeadersSource } = require('./native-fetch-headers.js');
|
|
4
|
+
|
|
3
5
|
const dynamicBackends = require('./native-dynamic-backends.js');
|
|
4
6
|
|
|
5
7
|
const { scalarRecordProjectionLines } = require('./schema-scalar-record.js');
|
|
@@ -52,7 +54,7 @@ const FASTLY_NATIVE_HTTP_EFFECTS_IMPORTS = Object.freeze([
|
|
|
52
54
|
Object.freeze(['fastly_http_req', 'new']),
|
|
53
55
|
Object.freeze(['fastly_http_req', 'method_set']),
|
|
54
56
|
Object.freeze(['fastly_http_req', 'uri_set']),
|
|
55
|
-
Object.freeze(['fastly_http_req', '
|
|
57
|
+
Object.freeze(['fastly_http_req', 'header_append']),
|
|
56
58
|
Object.freeze(['fastly_http_req', 'send_async']),
|
|
57
59
|
Object.freeze(['fastly_http_req', 'pending_req_wait']),
|
|
58
60
|
Object.freeze(['fastly_http_resp', 'new']),
|
|
@@ -127,6 +129,9 @@ function validatePlanBoundary(plan) {
|
|
|
127
129
|
if (packageEffects.length > 0) {
|
|
128
130
|
fail('Pass97 does not realize package-owned Fastly effects; config, secret, KV, and GRIP are introduced in Pass98.', 'PULSE_FASTLY_NATIVE_HTTP_PLATFORM_EFFECTS_DEFERRED', { effects: packageEffects.map((entry) => entry.id) });
|
|
129
131
|
}
|
|
132
|
+
if (plan.effects?.some(effect => effect.fetchErrors || effect.fetchTiming)) {
|
|
133
|
+
fail('Collected fetch outcomes require the canonical platform realization.', 'PULSE_FASTLY_NATIVE_HTTP_EFFECT_KIND_UNSUPPORTED');
|
|
134
|
+
}
|
|
130
135
|
const unsupported = (plan.effects || []).filter((effect) => effect.kind !== 'fetch');
|
|
131
136
|
if (unsupported.length > 0) {
|
|
132
137
|
fail('Pass97 realizes only outbound HTTP fetch effects.', 'PULSE_FASTLY_NATIVE_HTTP_EFFECT_KIND_UNSUPPORTED', { effects: unsupported.map((entry) => ({ id: entry.id, kind: entry.kind })) });
|
|
@@ -277,6 +282,7 @@ function generateSchemaRuntime(plan) {
|
|
|
277
282
|
`const __PULSE_SCHEMA_REQUIRE_JSON: bool = ${registry.contentTypePolicy === 'require-json' ? 'true' : 'false'}`,
|
|
278
283
|
'',
|
|
279
284
|
schemaJsonAdmissionSource(plan),
|
|
285
|
+
fetchHeadersSource(),
|
|
280
286
|
schemaFetchJsonSource(),
|
|
281
287
|
...nodeLines,
|
|
282
288
|
'function __pulse_fastly_schema_apply(schemaId: string, valueHandle: i32, encode: bool): i32 {',
|
|
@@ -341,7 +347,7 @@ function fastlyRuntimeSource(plan, bindings) {
|
|
|
341
347
|
@external("fastly_http_req", "new") declare function fastly_http_req_new(handleOut: usize): i32
|
|
342
348
|
@external("fastly_http_req", "method_set") declare function fastly_http_req_method_set(handle: i32, method: usize, methodLength: i32): i32
|
|
343
349
|
@external("fastly_http_req", "uri_set") declare function fastly_http_req_uri_set(handle: i32, uri: usize, uriLength: i32): i32
|
|
344
|
-
@external("fastly_http_req", "
|
|
350
|
+
@external("fastly_http_req", "header_append") declare function fastly_http_req_header_append(handle: i32, name: usize, nameLength: i32, value: usize, valueLength: i32): i32
|
|
345
351
|
@external("fastly_http_req", "send_async") declare function fastly_http_req_send_async(handle: i32, body: i32, backend: usize, backendLength: i32, pendingOut: usize): i32
|
|
346
352
|
@external("fastly_http_req", "pending_req_wait") declare function fastly_http_req_pending_req_wait(handle: i32, responseOut: usize, bodyOut: usize): i32
|
|
347
353
|
@external("fastly_http_resp", "new") declare function fastly_http_resp_new(handleOut: usize): i32
|
|
@@ -682,7 +688,7 @@ function host_kv_namespace(name: i32): i32 { __pulse_fastly_fail(PULSE_ERROR_UNS
|
|
|
682
688
|
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)) }
|
|
683
689
|
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 }
|
|
684
690
|
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_fastly_fetch_header_text(response.responseHandle, __pulse_fastly_string(nameHandle)); return value.length == 0 ? host_value_undefined() : __pulse_fastly_string_value(value) }
|
|
685
|
-
function host_effect_begin(effectIndex: i32, payload: i32): void { ${needsNativeValueFailureGuard(plan) ? 'if (__pulse_fastly_last_error != PULSE_ERROR_NONE) return; ' : ''}if (effectIndex < 0 || effectIndex >= PULSE_FASTLY_EFFECT_COUNT || unchecked(__pulse_fastly_pending_active[effectIndex]) != 0) { __pulse_fastly_fail(PULSE_ERROR_STATE, 46, effectIndex); return } const payloadValue = __pulse_fastly_value(payload); if (payloadValue.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 47, effectIndex); return } const urlIndex = __pulse_fastly_find(payloadValue, "url"); if (urlIndex < 0) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 48, effectIndex); return } const url = __pulse_fastly_string(unchecked(payloadValue.values[urlIndex])); const initIndex = __pulse_fastly_find(payloadValue, "init"); const init = initIndex >= 0 ? __pulse_fastly_value(unchecked(payloadValue.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"); 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) }
|
|
691
|
+
function host_effect_begin(effectIndex: i32, payload: i32): void { ${needsNativeValueFailureGuard(plan) ? 'if (__pulse_fastly_last_error != PULSE_ERROR_NONE) return; ' : ''}if (effectIndex < 0 || effectIndex >= PULSE_FASTLY_EFFECT_COUNT || unchecked(__pulse_fastly_pending_active[effectIndex]) != 0) { __pulse_fastly_fail(PULSE_ERROR_STATE, 46, effectIndex); return } const payloadValue = __pulse_fastly_value(payload); if (payloadValue.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 47, effectIndex); return } const urlIndex = __pulse_fastly_find(payloadValue, "url"); if (urlIndex < 0) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 48, effectIndex); return } const url = __pulse_fastly_string(unchecked(payloadValue.values[urlIndex])); const initIndex = __pulse_fastly_find(payloadValue, "init"); const init = initIndex >= 0 ? __pulse_fastly_value(unchecked(payloadValue.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])); headers = __pulse_fastly_fetch_headers(headers, effectIndex); if (__pulse_fastly_last_error != 0) return; const bodyIndex = __pulse_fastly_find(init, "body"); 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) }
|
|
686
692
|
else {
|
|
687
693
|
const jsonIndex = __pulse_fastly_find(init, "json")
|
|
688
694
|
if (jsonIndex >= 0) {
|
|
@@ -696,7 +702,7 @@ function host_effect_begin(effectIndex: i32, payload: i32): void { ${needsNative
|
|
|
696
702
|
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)
|
|
697
703
|
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 }
|
|
698
704
|
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 }
|
|
699
|
-
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 =
|
|
705
|
+
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_append(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 } } }
|
|
700
706
|
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 } }
|
|
701
707
|
const backend = __pulse_fastly_backend(effectIndex); 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_fastly_fail(PULSE_ERROR_TRANSPORT, 77, effectIndex); return } unchecked(__pulse_fastly_pending[effectIndex] = __pulse_fastly_out_value(pendingOut)); unchecked(__pulse_fastly_pending_active[effectIndex] = 1); unchecked(__pulse_fastly_payloads[effectIndex] = payload)
|
|
702
708
|
}
|