@pulse-compute/provider-fastly 1.0.0-beta.1 → 1.0.0-beta.10
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 +7 -6
- package/dist/config-schema.json +30 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/package.json +11 -10
- package/src/build/canonical-target.js +35 -2
- package/src/build/digest-native.js +25 -0
- package/src/build/effect-invocations.js +49 -0
- package/src/build/embedded-assets.as.ts +32 -0
- package/src/build/entities-native.js +6 -11
- package/src/build/kv-native.as.ts +331 -0
- package/src/build/kv-native.js +27 -0
- package/src/build/native-application-errors.js +159 -0
- package/src/build/native-binary-resources.js +30 -0
- package/src/build/native-body-forwarding.as.ts +341 -0
- package/src/build/native-body-forwarding.js +69 -0
- package/src/build/native-dynamic-backends.as.ts +115 -0
- package/src/build/native-dynamic-backends.js +52 -0
- package/src/build/native-fetch-deadlines.as.ts +164 -0
- package/src/build/native-fetch-deadlines.js +27 -0
- package/src/build/native-fetch-errors.js +38 -0
- package/src/build/native-handle-ownership.js +127 -0
- package/src/build/native-header-lookup.js +50 -0
- package/src/build/native-http-effects.js +155 -32
- package/src/build/native-platform-capabilities.js +755 -210
- package/src/build/native-request-body.js +44 -0
- package/src/build/native-request-headers.js +93 -0
- package/src/build/native-send-error-detail.as.ts +12 -0
- package/src/build/native-stdio.js +45 -0
- package/src/build/native-string-values.js +143 -0
- package/src/build/native-transport-details.as.ts +84 -0
- package/src/build/native-value-budget.js +90 -0
- package/src/build/package-native-application.js +114 -0
- package/src/build/request-budget.as.ts +50 -0
- package/src/build/request-budget.js +47 -0
- package/src/build/s3-body.as.ts +97 -0
- package/src/build/s3-native.as.ts +228 -0
- package/src/build/s3-native.js +30 -0
- package/src/build/schema-admission.js +83 -0
- package/src/build/schema-nested-json.js +93 -0
- package/src/build/schema-scalar-record.js +41 -0
- package/src/build/time-native.js +49 -0
- package/src/build/wasm-budget.js +28 -0
- package/src/config-api.js +6 -0
- package/src/config-schema.json +30 -0
- package/src/index.ts +25 -0
- package/src/javascript/jwt-verifier.js +14 -4
- package/src/javascript/logging.js +11 -2
- package/src/javascript/package-effects.js +5 -2
- package/src/javascript/runtime-host.js +7 -3
- package/src/javascript/source-package.js +4 -2
- package/src/javascript/target-support-policy.js +20 -3
- package/src/javascript/target.js +17 -1
- package/src/javascript/test-runtime.js +1 -0
- package/src/provider-contract.js +22 -6
- package/src/runtime/canonical-api-runtime.js +6 -0
- package/src/testing/conditional-kv-host.js +155 -0
- package/src/testing/native-dynamic-backends-host.js +39 -0
- package/src/testing/native-http-effects-host.js +9 -1
- package/src/testing/native-platform-capabilities-host.js +349 -24
- package/src/toolchain/body-forwarding.js +25 -0
- package/src/toolchain/config.js +16 -0
- package/src/toolchain/index.js +48 -5
- package/src/toolchain/logging.js +18 -0
- package/src/toolchain/native-execution.js +78 -0
- package/src/toolchain/s3.js +26 -0
|
@@ -337,9 +337,15 @@ function createFastlyProviderAdapter(baseOptions = {}) {
|
|
|
337
337
|
return Object.freeze({
|
|
338
338
|
id: 'fastly',
|
|
339
339
|
version: CANONICAL_FASTLY_RUNTIME_VERSION,
|
|
340
|
+
// This legacy JavaScript fixture surface has no Native KV host ABI. Keep
|
|
341
|
+
// that absence pre-dispatch/configuration, rather than pretending a failed
|
|
342
|
+
// fixture dispatch might have mutated storage. Real K3 evidence runs Wasm.
|
|
343
|
+
prepareConditionalKv() { return undefined; },
|
|
340
344
|
async dispatchEffect(effect, executionOptions = {}) {
|
|
341
345
|
const state = stateFor(executionOptions);
|
|
342
346
|
if (effect.kind === 'fetch') return dispatchFetch(effect, executionOptions);
|
|
347
|
+
if (effect.kind === 'crypto.digestText') return require('@pulse-compute/crypto/provider').createJavascriptTextDigest(baseOptions)(effect, executionOptions);
|
|
348
|
+
if (effect.kind === 'time.now') return require('@pulse-compute/runtime/host').readWallTime(baseOptions.wallClock === undefined ? () => Date.now() : baseOptions.wallClock);
|
|
343
349
|
if (effect.kind === 'config.get') return state.surface.config.adapter.get(effect.name);
|
|
344
350
|
if (effect.kind === 'secret.get') {
|
|
345
351
|
const secret = state.surface.secret.adapter.get(effect.name);
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Injected ABI authority for real generated Wasm evidence, never a service adapter.
|
|
4
|
+
// Raw persisted bytes and u64 generations remain independent of the Pulse codec.
|
|
5
|
+
function createConditionalKvAuthority() {
|
|
6
|
+
const stores = new Map(); let next = 1n;
|
|
7
|
+
return {
|
|
8
|
+
stores,
|
|
9
|
+
seed(store, key, wire, generation = next++) {
|
|
10
|
+
if (!stores.has(store)) stores.set(store, new Map());
|
|
11
|
+
stores.get(store).set(key, { wire: Buffer.from(wire), generation: BigInt(generation) });
|
|
12
|
+
},
|
|
13
|
+
get(store, key) { return stores.get(store)?.get(key); },
|
|
14
|
+
delete(store, key) { return stores.get(store).delete(key) ? 1 : 3; },
|
|
15
|
+
list(store, { limit, prefix = '', cursor }) {
|
|
16
|
+
// Test authority models a live ordered page independently of Pulse's parser.
|
|
17
|
+
let after;
|
|
18
|
+
if (cursor !== undefined) {
|
|
19
|
+
if (!/^abi-kv:/.test(cursor)) return { code: 2 };
|
|
20
|
+
try { const token = JSON.parse(Buffer.from(cursor.slice(7), 'base64url')); if (token.store !== store || token.prefix !== prefix || typeof token.after !== 'string') return { code: 2 }; after = token.after; }
|
|
21
|
+
catch (_) { return { code: 2 }; }
|
|
22
|
+
}
|
|
23
|
+
const all = [...stores.get(store).keys()].sort((a,b) => Buffer.compare(Buffer.from(a),Buffer.from(b))).filter(key => key.startsWith(prefix) && (after === undefined || Buffer.compare(Buffer.from(key), Buffer.from(after)) > 0));
|
|
24
|
+
const data = all.slice(0, limit), meta = { limit, ...(prefix ? { prefix } : {}) };
|
|
25
|
+
if (all.length > limit) meta.next_cursor = 'abi-kv:' + Buffer.from(JSON.stringify({ store, prefix, after: data[data.length - 1] })).toString('base64url');
|
|
26
|
+
return { code: 1, wire: Buffer.from(JSON.stringify({ data, meta })) };
|
|
27
|
+
},
|
|
28
|
+
apply(store, key, wire, mode, condition) {
|
|
29
|
+
const values = stores.get(store), current = values.get(key);
|
|
30
|
+
if (mode === 1 && current || condition !== undefined && (!current || current.generation !== condition)) return 4;
|
|
31
|
+
while (next === current?.generation) next++;
|
|
32
|
+
this.seed(store, key, wire, next++); return 1;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function attachConditionalKvHost(imports, host, options) {
|
|
38
|
+
const { view, writeU32, writeU64, readBytes, readUtf8, alloc, bodies, bodyBytes, trace, now, advance } = host;
|
|
39
|
+
const authority = options.authority, stores = new Map(), pending = new Map(), bodyFixtures = new Map();
|
|
40
|
+
let sendingResponse = false;
|
|
41
|
+
const newResponse = imports.fastly_http_resp.new;
|
|
42
|
+
imports.fastly_http_resp.new = (...args) => { sendingResponse = true; return newResponse(...args); };
|
|
43
|
+
const call = (stage, detail = {}) => {
|
|
44
|
+
const fault = typeof options.onCall === 'function' ? options.onCall(stage, detail) || {} : {};
|
|
45
|
+
advance(Number(fault.delayMs || 0));
|
|
46
|
+
trace.push({ module: 'pulse_kv_evidence', name: stage, hostStatus: fault.status ?? 0, ...(fault.kvError === undefined ? {} : { kvError: fault.kvError }) });
|
|
47
|
+
return fault;
|
|
48
|
+
};
|
|
49
|
+
imports.fastly_kv_store = {
|
|
50
|
+
open(ptr, len, out) {
|
|
51
|
+
const name = readUtf8(ptr, len), fault = call('open', { store: name });
|
|
52
|
+
if (fault.status) return fault.status;
|
|
53
|
+
if (!authority.stores.has(name)) return 10;
|
|
54
|
+
const handle = alloc('kvStore'); stores.set(handle, name); writeU32(out, handle); return 0;
|
|
55
|
+
},
|
|
56
|
+
lookup(store, ptr, len, mask, config, out) {
|
|
57
|
+
const name = stores.get(store), key = readUtf8(ptr, len), fault = call('lookup', { store: name, key, mask, config: readBytes(config, 4) });
|
|
58
|
+
if (fault.status) return fault.status;
|
|
59
|
+
const item = fault.observation || authority.get(name, key);
|
|
60
|
+
const handle = alloc('kvLookup'); pending.set(handle, { kind: 'lookup', item, readyAt: now() + Number(fault.readyDelayMs || 0), fault });
|
|
61
|
+
writeU32(out, handle); return 0;
|
|
62
|
+
},
|
|
63
|
+
lookup_wait_v2(handle, body, meta, capacity, written, generation, error) {
|
|
64
|
+
const item = pending.get(handle); if (!item || item.readyAt > now()) throw new Error('KV lookup wait called before readiness.');
|
|
65
|
+
pending.delete(handle); const fault = call('lookup_wait_v2', { capacity, observation: item.item });
|
|
66
|
+
writeU32(error, fault.kvError ?? (item.item ? 1 : 3)); writeU64(generation, fault.generation ?? item.item?.generation ?? 0n);
|
|
67
|
+
writeU32(written, fault.metadataLength ?? 0);
|
|
68
|
+
if (fault.status) return fault.status;
|
|
69
|
+
if ((fault.kvError ?? (item.item ? 1 : 3)) !== 1) return 0;
|
|
70
|
+
const handleOut = alloc('body'); const bytes = Buffer.from(fault.wire || item.item.wire);
|
|
71
|
+
bodies.set(handleOut, { bytes, readOffset: 0, writes: [], readyAt: now() + Number(fault.bodyDelayMs || 0) });
|
|
72
|
+
bodyFixtures.set(handleOut, fault); writeU32(body, handleOut); return 0;
|
|
73
|
+
},
|
|
74
|
+
insert(store, ptr, len, body, mask, config, out) {
|
|
75
|
+
const name = stores.get(store), key = readUtf8(ptr, len), wire = Buffer.from(bodyBytes(body));
|
|
76
|
+
const mode = view().getUint32(config, true), condition = mask & 32 ? view().getBigUint64(config + 24, true) : undefined;
|
|
77
|
+
const fault = call('insert', { store: name, key, wire, mode, mask, condition, config: readBytes(config, 32) });
|
|
78
|
+
let code = fault.kvError;
|
|
79
|
+
if (code === undefined && (!fault.status || fault.commit)) code = authority.apply(name, key, wire, mode, condition);
|
|
80
|
+
bodies.delete(body); // Host takes ownership of the body.
|
|
81
|
+
if (fault.status) return fault.status;
|
|
82
|
+
const handle = alloc('kvInsert'); pending.set(handle, { kind: 'insert', code, readyAt: now() + Number(fault.readyDelayMs || 0) });
|
|
83
|
+
writeU32(out, handle); return 0;
|
|
84
|
+
},
|
|
85
|
+
delete(store, ptr, len, mask, config, out) {
|
|
86
|
+
if (mask !== 0 || view().getUint32(config, true) !== 0) throw new Error('KV delete config must be empty.');
|
|
87
|
+
const name = stores.get(store), key = readUtf8(ptr, len), fault = call('delete', { store: name, key, mask });
|
|
88
|
+
const code = fault.kvError ?? ((!fault.status || fault.commit) ? authority.delete(name, key) : undefined);
|
|
89
|
+
if (fault.status) return fault.status;
|
|
90
|
+
const handle = alloc('kvDelete'); pending.set(handle, { kind: 'delete', code, readyAt: now() + Number(fault.readyDelayMs || 0) });
|
|
91
|
+
writeU32(out, handle); return 0;
|
|
92
|
+
},
|
|
93
|
+
delete_wait(handle, error) {
|
|
94
|
+
const item = pending.get(handle); if (!item || item.kind !== 'delete' || item.readyAt > now()) throw new Error('KV delete wait called before readiness.');
|
|
95
|
+
pending.delete(handle); const fault = call('delete_wait'); writeU32(error, fault.kvError ?? item.code ?? 0); return fault.status ?? 0;
|
|
96
|
+
},
|
|
97
|
+
list(store, mask, config, out) {
|
|
98
|
+
if ((mask & ~14) !== 0 || !(mask & 4) || view().getUint32(config, true) !== 0) throw new Error('KV list requires bounded strong mode.');
|
|
99
|
+
const cursor = mask & 2 ? readUtf8(view().getUint32(config + 4, true), view().getUint32(config + 8, true)) : undefined;
|
|
100
|
+
const limit = view().getUint32(config + 12, true);
|
|
101
|
+
const prefix = mask & 8 ? readUtf8(view().getUint32(config + 16, true), view().getUint32(config + 20, true)) : '';
|
|
102
|
+
if (limit < 1 || limit > 100 || cursor === '' || mask & 8 && !prefix) throw new Error('KV list config is invalid.');
|
|
103
|
+
const name = stores.get(store), fault = call('list', { store: name, mask, limit, prefix, cursor });
|
|
104
|
+
if (fault.status) return fault.status;
|
|
105
|
+
const result = authority.list(name, { limit, prefix, cursor });
|
|
106
|
+
const handle = alloc('kvList'); pending.set(handle, { kind: 'list', ...result, readyAt: now() + Number(fault.readyDelayMs || 0) });
|
|
107
|
+
writeU32(out, handle); return 0;
|
|
108
|
+
},
|
|
109
|
+
list_wait(handle, body, error) {
|
|
110
|
+
const item = pending.get(handle); if (!item || item.kind !== 'list' || item.readyAt > now()) throw new Error('KV list wait called before readiness.');
|
|
111
|
+
pending.delete(handle); const fault = call('list_wait'); writeU32(error, fault.kvError ?? item.code);
|
|
112
|
+
if (fault.status) return fault.status;
|
|
113
|
+
if ((fault.kvError ?? item.code) !== 1) return 0;
|
|
114
|
+
const bodyHandle = alloc('body'); bodies.set(bodyHandle, { bytes: Buffer.from(fault.wire ?? item.wire), readOffset: 0, writes: [], readyAt: now() + Number(fault.bodyDelayMs || 0) });
|
|
115
|
+
bodyFixtures.set(bodyHandle, fault); writeU32(body, bodyHandle); return 0;
|
|
116
|
+
},
|
|
117
|
+
insert_wait(handle, error) {
|
|
118
|
+
const item = pending.get(handle); if (!item || item.readyAt > now()) throw new Error('KV insert wait called before readiness.');
|
|
119
|
+
pending.delete(handle); const fault = call('insert_wait'); writeU32(error, fault.kvError ?? item.code ?? 0); return fault.status ?? 0;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const select = imports.fastly_async_io.select;
|
|
123
|
+
imports.fastly_async_io.select = (handles, count, timeout, done) => {
|
|
124
|
+
const handle = view().getUint32(handles, true), item = pending.get(handle) || (bodyFixtures.has(handle) && bodies.get(handle));
|
|
125
|
+
if (!item) return select(handles, count, timeout, done);
|
|
126
|
+
if (count !== 1 || timeout <= 0) throw new Error('KV select must be bounded.');
|
|
127
|
+
const fault = call('select', { kind: item.kind || 'body', timeout });
|
|
128
|
+
if (fault.status) return fault.status;
|
|
129
|
+
const wait = Math.max(0, item.readyAt - now()); advance(Math.min(wait, timeout));
|
|
130
|
+
writeU32(done, fault.readyIndex ?? (wait >= timeout ? 0xffffffff : 0)); return 0;
|
|
131
|
+
};
|
|
132
|
+
for (const name of ['new', 'write', 'read', 'close']) {
|
|
133
|
+
const original = imports.fastly_http_body[name];
|
|
134
|
+
imports.fastly_http_body[name] = (...args) => {
|
|
135
|
+
const [handle] = args;
|
|
136
|
+
if (sendingResponse) return original(...args);
|
|
137
|
+
if (name === 'read' && !bodyFixtures.has(handle)) return original(...args);
|
|
138
|
+
const fault = call('body_' + name, { handle });
|
|
139
|
+
if (fault.status) return fault.status;
|
|
140
|
+
const result = original(...args);
|
|
141
|
+
if (name === 'read') {
|
|
142
|
+
const fixture = bodyFixtures.get(handle), body = bodies.get(handle);
|
|
143
|
+
if (body) body.readyAt = now() + Number(fixture.chunkDelayMs || 0);
|
|
144
|
+
if (fault.written !== undefined) writeU32(args[3], fault.written);
|
|
145
|
+
}
|
|
146
|
+
if (name === 'write' && fault.written !== undefined) writeU32(args[4], fault.written);
|
|
147
|
+
if (name === 'close') bodyFixtures.delete(handle);
|
|
148
|
+
return result;
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
const clock = imports.wasi_snapshot_preview1.clock_time_get;
|
|
152
|
+
imports.wasi_snapshot_preview1.clock_time_get = (...args) => { const fault = call('clock'); return fault.status || clock(...args); };
|
|
153
|
+
return { pending, bodyFixtures };
|
|
154
|
+
}
|
|
155
|
+
module.exports = { createConditionalKvAuthority, attachConditionalKvHost };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Independent ABI oracle shared by the two injected Fastly hosts. This is test
|
|
4
|
+
// infrastructure, not a network implementation or a hosted capability probe.
|
|
5
|
+
const assert = require('node:assert/strict');
|
|
6
|
+
function createDynamicBackendHost({ view, readUtf8, trace, options, beforeRegister = () => {} }) {
|
|
7
|
+
const backends = new Map();
|
|
8
|
+
function register(np, nl, tp, tl, mask, config) {
|
|
9
|
+
beforeRegister();
|
|
10
|
+
assert.equal(config % 4, 0);
|
|
11
|
+
const words = Array.from({ length: 27 }, (_, i) => view().getUint32(config + i * 4, true));
|
|
12
|
+
const text = i => readUtf8(words[i], words[i + 1]);
|
|
13
|
+
const name = readUtf8(np, nl), target = readUtf8(tp, tl), host = text(0);
|
|
14
|
+
const tls = (mask & 32) !== 0;
|
|
15
|
+
assert.equal(mask, tls ? 2338 : 2, 'Only Host and URL-derived TLS settings are enabled');
|
|
16
|
+
const used = new Set(tls ? [0, 1, 7, 8, 13, 14] : [0, 1]);
|
|
17
|
+
words.forEach((word, i) => { if (!used.has(i)) assert.equal(word, 0, 'Unused ABI field ' + i); });
|
|
18
|
+
const record = { name, target, host, tls, certificateHostname: tls ? text(7) : '', sni: tls ? text(13) : '' };
|
|
19
|
+
if (tls) assert.equal(record.certificateHostname, record.sni);
|
|
20
|
+
let status = Number(options.dynamicBackendStatus || 0);
|
|
21
|
+
if (typeof options.onDynamicBackendRegistration === 'function') status = options.onDynamicBackendRegistration(record) ?? status;
|
|
22
|
+
if (backends.has(name) || (options.staticBackendNames || []).includes(name)) status = 1;
|
|
23
|
+
if (!status) backends.set(name, record);
|
|
24
|
+
trace.push({ ...record, module: 'fastly_http_req', backendName: name, name: 'register_dynamic_backend', status });
|
|
25
|
+
return status;
|
|
26
|
+
}
|
|
27
|
+
function route(name, request) {
|
|
28
|
+
const backend = backends.get(name);
|
|
29
|
+
if (!backend) return; // Statically configured names retain fixture routing.
|
|
30
|
+
const url = new URL(request.url);
|
|
31
|
+
assert.equal(backend.target, url.hostname + ':' + (url.port || (url.protocol === 'https:' ? '443' : '80')));
|
|
32
|
+
assert.equal(backend.tls, url.protocol === 'https:');
|
|
33
|
+
if (backend.tls) assert.equal(backend.sni, url.hostname);
|
|
34
|
+
request.headers = request.headers.filter(([key]) => key.toLowerCase() !== 'host');
|
|
35
|
+
request.headers.push(['host', backend.host]);
|
|
36
|
+
}
|
|
37
|
+
return { register, route };
|
|
38
|
+
}
|
|
39
|
+
module.exports = { createDynamicBackendHost };
|
|
@@ -80,6 +80,7 @@ function executeFastlyNativeHttpEffects(input, options = {}) {
|
|
|
80
80
|
const bodies = new Map();
|
|
81
81
|
const responses = new Map();
|
|
82
82
|
const pending = new Map();
|
|
83
|
+
const outboundRequests = [];
|
|
83
84
|
const downstreamRequestHandle = 1;
|
|
84
85
|
const downstreamBodyHandle = 2;
|
|
85
86
|
requests.set(downstreamRequestHandle, { method, url, headers: requestHeaders.map((entry) => [...entry]) });
|
|
@@ -111,6 +112,8 @@ function executeFastlyNativeHttpEffects(input, options = {}) {
|
|
|
111
112
|
function readBytes(pointer, length) { return Buffer.from(new Uint8Array(memory().buffer, Number(pointer), Number(length))); }
|
|
112
113
|
function readUtf8(pointer, length) { return readBytes(pointer, length).toString('utf8'); }
|
|
113
114
|
|
|
115
|
+
const dynamicBackends = require('./native-dynamic-backends-host.js').createDynamicBackendHost({ view, readUtf8, trace, options });
|
|
116
|
+
|
|
114
117
|
function writeUtf8(value, pointer, maxLength, writtenOut) {
|
|
115
118
|
const bytes = Buffer.from(String(value), 'utf8');
|
|
116
119
|
writeU32(writtenOut, bytes.length);
|
|
@@ -164,6 +167,7 @@ function executeFastlyNativeHttpEffects(input, options = {}) {
|
|
|
164
167
|
}
|
|
165
168
|
},
|
|
166
169
|
fastly_http_req: {
|
|
170
|
+
register_dynamic_backend: dynamicBackends.register,
|
|
167
171
|
body_downstream_get(requestOut, bodyOut) {
|
|
168
172
|
trace.push({ module: 'fastly_http_req', name: 'body_downstream_get' });
|
|
169
173
|
writeU32(requestOut, downstreamRequestHandle);
|
|
@@ -224,13 +228,16 @@ function executeFastlyNativeHttpEffects(input, options = {}) {
|
|
|
224
228
|
const body = bodies.get(Number(bodyHandle));
|
|
225
229
|
if (!request || !body) return FASTLY_STATUS_BADF;
|
|
226
230
|
const backend = readUtf8(backendPointer, backendLength);
|
|
231
|
+
dynamicBackends.route(backend, request);
|
|
227
232
|
const fixture = resolveFixture(options.fixtures, backend, request.method, request.url);
|
|
228
233
|
if (!fixture) {
|
|
229
234
|
trace.push({ module: 'fastly_http_req', name: 'send_async', handle: Number(handle), backend, method: request.method, url: request.url, missingFixture: true });
|
|
230
235
|
return FASTLY_STATUS_ERROR;
|
|
231
236
|
}
|
|
232
237
|
const pendingHandle = alloc('pending');
|
|
233
|
-
|
|
238
|
+
const outbound = { ...request, headers: request.headers.map((entry) => [...entry]), body: bodyBytes(bodyHandle) };
|
|
239
|
+
outboundRequests.push(outbound);
|
|
240
|
+
pending.set(pendingHandle, { request: outbound, backend, fixture });
|
|
234
241
|
writeU32(pendingOut, pendingHandle);
|
|
235
242
|
trace.push({ module: 'fastly_http_req', name: 'send_async', handle: Number(handle), bodyHandle: Number(bodyHandle), pendingHandle, backend, method: request.method, url: request.url });
|
|
236
243
|
return FASTLY_STATUS_OK;
|
|
@@ -373,6 +380,7 @@ function executeFastlyNativeHttpEffects(input, options = {}) {
|
|
|
373
380
|
bodyHandle: downstream.bodyHandle
|
|
374
381
|
}),
|
|
375
382
|
trace: Object.freeze(trace.map((entry) => Object.freeze({ ...entry }))),
|
|
383
|
+
outboundRequests: Object.freeze(outboundRequests.map(entry => Object.freeze(entry))),
|
|
376
384
|
instance
|
|
377
385
|
});
|
|
378
386
|
}
|