@pulse-compute/provider-fastly 1.0.0-beta.8 → 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 +5 -0
- package/src/build/entities-native.js +4 -13
- package/src/build/native-body-forwarding.as.ts +334 -0
- package/src/build/native-body-forwarding.js +69 -0
- package/src/build/native-handle-ownership.js +127 -0
- package/src/build/native-http-effects.js +4 -0
- package/src/build/native-platform-capabilities.js +45 -18
- 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/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 +93 -8
- package/src/toolchain/body-forwarding.js +25 -0
- package/src/toolchain/config.js +4 -0
- package/src/toolchain/index.js +1 -1
- package/src/toolchain/native-execution.js +6 -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 };
|
|
@@ -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)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { scalarRecordProjectionLines } = require('./schema-scalar-record.js');
|
|
4
4
|
const { hasJsonAdmission, tracksJsonDuplicates, schemaParserDepth, nestedJsonProjectionLines, schemaJsonAdmissionSource, schemaFetchJsonSource } = require('./schema-nested-json.js');
|
|
5
|
+
const { nativeStdioSource } = require('./native-stdio.js');
|
|
5
6
|
const applicationErrors = require('./native-application-errors.js');
|
|
6
7
|
const effectInvocations = require('./effect-invocations.js');
|
|
7
8
|
const packageApplication = require('./package-native-application.js');
|
|
@@ -88,6 +89,7 @@ function loadGuestToolchain() {
|
|
|
88
89
|
|
|
89
90
|
const { resolveAsc } = loadBuildSupport();
|
|
90
91
|
const { generateCanonicalNativeAssemblyScript } = loadNativeGenerator();
|
|
92
|
+
const nativeHandleOwnership = require('./native-handle-ownership.js');
|
|
91
93
|
const { pulseJwtAssemblyScriptSource } = loadJwtNativeSource();
|
|
92
94
|
const {
|
|
93
95
|
GUEST_LINK_STAGE_INVOCATION_VERSION,
|
|
@@ -108,6 +110,7 @@ const FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES = 65536;
|
|
|
108
110
|
const FASTLY_NATIVE_SIZE_OPTIMIZATION = EXPERIMENTAL_NATIVE_SIZE_OPTIMIZATION;
|
|
109
111
|
|
|
110
112
|
const FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORTS = Object.freeze([
|
|
113
|
+
...require('./native-body-forwarding.js').IMPORTS.map(key => Object.freeze(key.split(':'))),
|
|
111
114
|
...conditionalKv.KV_IMPORTS.map((key) => Object.freeze(key.split(':'))),
|
|
112
115
|
...require('./s3-native.js').S3_IMPORTS.map((key) => Object.freeze(key.split(':'))),
|
|
113
116
|
Object.freeze(['fastly_abi', 'init']),
|
|
@@ -143,8 +146,7 @@ const FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORTS = Object.freeze([
|
|
|
143
146
|
Object.freeze(['fastly_kv_store', 'lookup_wait_v2']),
|
|
144
147
|
Object.freeze(['fastly_kv_store', 'insert']),
|
|
145
148
|
Object.freeze(['fastly_kv_store', 'insert_wait']),
|
|
146
|
-
Object.freeze(['
|
|
147
|
-
Object.freeze(['fastly_log', 'write']),
|
|
149
|
+
Object.freeze(['wasi_snapshot_preview1', 'fd_write']),
|
|
148
150
|
Object.freeze(['wasi_snapshot_preview1', 'clock_time_get'])
|
|
149
151
|
].filter(([module, name], index, entries) => entries.findIndex((item) => item[0] === module && item[1] === name) === index));
|
|
150
152
|
|
|
@@ -663,6 +665,7 @@ function fastlyGuestLinkedStartWrapperWat() {
|
|
|
663
665
|
}
|
|
664
666
|
|
|
665
667
|
function validatePlanBoundary(plan, options = {}) {
|
|
668
|
+
require('./native-body-forwarding.js').validate(plan, options);
|
|
666
669
|
if (!plan || typeof plan !== 'object') throw new TypeError('Fastly native platform capability realization requires a canonical native plan.');
|
|
667
670
|
if (plan.version !== CANONICAL_NATIVE_PLAN_VERSION) {
|
|
668
671
|
fail(`Fastly native platform capability realization requires ${CANONICAL_NATIVE_PLAN_VERSION}.`, 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_PLAN_VERSION_UNSUPPORTED', { version: plan.version });
|
|
@@ -930,6 +933,7 @@ function resolveCapabilityBindings(plan, options = {}) {
|
|
|
930
933
|
|
|
931
934
|
return Object.freeze({
|
|
932
935
|
maxDurationMs: require('@pulse-compute/runtime/host').normalizeRequestDuration(options.maxDurationMs),
|
|
936
|
+
bodyForwarding: require('../toolchain/body-forwarding.js').normalizeBodyForwarding(options.bodyForwarding, options.maxDurationMs),
|
|
933
937
|
configStore,
|
|
934
938
|
secretStore,
|
|
935
939
|
s3: require('../toolchain/s3.js').resolveFastlyS3(effects.map((effect) => ({ ...effect, payload: Object.fromEntries(['contentType', 'method', 'range', 'ifNoneMatch'].map(name => [name, literalObjectField(inputExpression(effect, 'payload'), name) ?? undefined])) })), source.s3),
|
|
@@ -1009,10 +1013,14 @@ function requiredImportsForPlan(plan, bindings) {
|
|
|
1009
1013
|
if (kinds.has('kv.put')) for (const key of ['fastly_kv_store:insert', 'fastly_kv_store:insert_wait']) keys.add(key);
|
|
1010
1014
|
if (kinds.has('grip.hold')) keys.add('fastly_http_resp:header_append');
|
|
1011
1015
|
if (plan.logging && plan.logging.enabledStatements > 0) {
|
|
1012
|
-
keys.add('
|
|
1013
|
-
keys.add('fastly_log:write');
|
|
1016
|
+
keys.add('wasi_snapshot_preview1:fd_write');
|
|
1014
1017
|
}
|
|
1015
1018
|
if (kinds.has('s3.head') || kinds.has('s3.getText') || kinds.has('s3.putText') || kinds.has('s3.getBody')) for (const key of require('./s3-native.js').S3_IMPORTS) keys.add(key);
|
|
1019
|
+
if (require('./native-body-forwarding.js').enabled(plan)) {
|
|
1020
|
+
keys.delete('fastly_http_req:send_async'); keys.delete('fastly_http_req:pending_req_wait');
|
|
1021
|
+
for (const key of require('./native-body-forwarding.js').IMPORTS) keys.add(key);
|
|
1022
|
+
keys.add('fastly_http_body:read');
|
|
1023
|
+
}
|
|
1016
1024
|
return Object.freeze([...keys].sort());
|
|
1017
1025
|
}
|
|
1018
1026
|
|
|
@@ -1435,8 +1443,7 @@ function __pulse_fastly_jwt_send_error(): void {}
|
|
|
1435
1443
|
@external("fastly_kv_store", "lookup_wait_v2") declare function fastly_kv_store_lookup_wait_v2(handle: i32, bodyOut: usize, metadata: usize, metadataLength: i32, writtenOut: usize, generationOut: usize, kvErrorOut: usize): i32
|
|
1436
1444
|
@external("fastly_kv_store", "insert") declare function fastly_kv_store_insert(store: i32, key: usize, keyLength: i32, body: i32, configMask: i32, config: usize, insertOut: usize): i32
|
|
1437
1445
|
@external("fastly_kv_store", "insert_wait") declare function fastly_kv_store_insert_wait(handle: i32, kvErrorOut: usize): i32
|
|
1438
|
-
|
|
1439
|
-
@external("fastly_log", "write") declare function fastly_log_write(handle: i32, message: usize, messageLength: i32, writtenOut: usize): i32
|
|
1446
|
+
${nativeStdioSource}
|
|
1440
1447
|
@external("wasi_snapshot_preview1", "clock_time_get") declare function wasi_snapshot_preview1_clock_time_get(clockId: i32, precision: i64, timeOut: usize): i32
|
|
1441
1448
|
|
|
1442
1449
|
const PULSE_FASTLY_EFFECTS_ABI_VERSION: i64 = ${FASTLY_NATIVE_PLATFORM_CAPABILITIES_ABI_VERSION}
|
|
@@ -1508,6 +1515,7 @@ ${nativeStringFields}
|
|
|
1508
1515
|
}
|
|
1509
1516
|
binaryBody: Uint8Array | null = null
|
|
1510
1517
|
immutable: bool = false
|
|
1518
|
+
${nativeHandleOwnership.fields(options.handleTracing)}
|
|
1511
1519
|
}
|
|
1512
1520
|
class __PulseFastlyHeader {
|
|
1513
1521
|
constructor(public name: string, public value: string) {}
|
|
@@ -1640,7 +1648,7 @@ class __PulseJsonParser {
|
|
|
1640
1648
|
const PULSE_FASTLY_PENDING_NONE: i32 = 0
|
|
1641
1649
|
const PULSE_FASTLY_PENDING_ASYNC: i32 = 1
|
|
1642
1650
|
const PULSE_FASTLY_PENDING_READY: i32 = 2
|
|
1643
|
-
|
|
1651
|
+
${nativeHandleOwnership.tableSource(options.handleTracing)}
|
|
1644
1652
|
// Primitive handles have value semantics. Bounded caches keep admitted scalar
|
|
1645
1653
|
// validation loops from retaining a fresh boxed value for every iteration.
|
|
1646
1654
|
@lazy const __pulse_fastly_scalar_handles = new StaticArray<i32>(4)
|
|
@@ -1664,7 +1672,6 @@ let __pulse_fastly_request_body_loaded: i32 = 0
|
|
|
1664
1672
|
let __pulse_fastly_last_error: i32 = 0
|
|
1665
1673
|
let __pulse_fastly_error_stage: i32 = 0
|
|
1666
1674
|
let __pulse_fastly_error_effect: i32 = -1
|
|
1667
|
-
let __pulse_fastly_log_handle: i32 = -1
|
|
1668
1675
|
let __pulse_fastly_jwt_error: i32 = 0
|
|
1669
1676
|
let __pulse_fastly_jwt_secret_calls: i32 = 0
|
|
1670
1677
|
let __pulse_fastly_jwt_crypto_calls: i32 = 0
|
|
@@ -1681,8 +1688,6 @@ function __pulse_fastly_out_i32(): StaticArray<i32> { return new StaticArray<i32
|
|
|
1681
1688
|
function __pulse_fastly_out_value(out: StaticArray<i32>): i32 { return load<i32>(changetype<usize>(out)) }
|
|
1682
1689
|
function __pulse_fastly_utf8(value: string): ArrayBuffer { return String.UTF8.encode(value, false) }
|
|
1683
1690
|
function __pulse_fastly_decode(buffer: Uint8Array, length: i32): string { return String.UTF8.decodeUnsafe(buffer.dataStart, length, false) }
|
|
1684
|
-
function __pulse_fastly_put(value: __PulseFastlyValue): i32 { __pulse_fastly_values.push(value); return __pulse_fastly_values.length }
|
|
1685
|
-
function __pulse_fastly_value(handle: i32): __PulseFastlyValue { if (handle <= 0 || handle > __pulse_fastly_values.length) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 1, -1); return new __PulseFastlyValue() } return unchecked(__pulse_fastly_values[handle - 1]) }
|
|
1686
1691
|
${nativeStringConcat}
|
|
1687
1692
|
${nativeStringTrim}
|
|
1688
1693
|
${nativeStringIndex}
|
|
@@ -1692,7 +1697,7 @@ function __pulse_fastly_string_value(value: string): i32 {
|
|
|
1692
1697
|
if (index >= 0) { const cached = unchecked(__pulse_fastly_character_handles[index]); if (cached != 0) return cached }
|
|
1693
1698
|
const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_STRING; item.text = value;
|
|
1694
1699
|
const handle = __pulse_fastly_put(item);
|
|
1695
|
-
if (index >= 0) unchecked(__pulse_fastly_character_handles[index] = handle);
|
|
1700
|
+
if (index >= 0) { unchecked(__pulse_fastly_character_handles[index] = handle); ${options.handleTracing ? 'if (handle != 0) __pulse_handle_cache_roots.push(handle);' : ''} }
|
|
1696
1701
|
return handle
|
|
1697
1702
|
}
|
|
1698
1703
|
function __pulse_fastly_find(object: __PulseFastlyValue, key: string): i32 { for (let i = 0; i < object.keys.length; i += 1) if (unchecked(object.keys[i]) == key) return i; return -1 }
|
|
@@ -1723,7 +1728,7 @@ function __pulse_fastly_redact_fields(input: string): string {
|
|
|
1723
1728
|
return output + input.substring(start);
|
|
1724
1729
|
}
|
|
1725
1730
|
function __pulse_fastly_redact(input: string): string { let output = __pulse_fastly_redact_fields(input); for (let i = 0; i < __pulse_fastly_redactions.length; i += 1) output = __pulse_fastly_replace_secret(output, unchecked(__pulse_fastly_redactions[i])); return output }
|
|
1726
|
-
function host_log(level: i32, messageHandle: i32): void { let
|
|
1731
|
+
function host_log(level: i32, messageHandle: i32): void { let label = "unknown"; if (level == 1) label = "error"; else if (level == 2) label = "warn"; else if (level == 3) label = "info"; else if (level == 4) label = "debug"; const bytes = __pulse_fastly_utf8("[pulse:" + label + "] " + __pulse_fastly_redact(__pulse_fastly_string(messageHandle)) + "\n"); __pulse_fastly_write_stdout(bytes) }
|
|
1727
1732
|
function __pulse_fastly_number(handle: i32): f64 { const value = __pulse_fastly_value(handle); if (value.kind == PULSE_VALUE_NUMBER) return value.number; if (value.kind == PULSE_VALUE_BOOLEAN) return value.boolean != 0 ? 1.0 : 0.0; if (value.kind == PULSE_VALUE_STRING) return F64.parseFloat(value.text); return 0.0 }
|
|
1728
1733
|
function __pulse_fastly_hex(value: i32): string { return String.fromCharCode(value < 10 ? 48 + value : 87 + value) }
|
|
1729
1734
|
function __pulse_fastly_is_unpaired(value: string, i: i32): bool {
|
|
@@ -1800,7 +1805,7 @@ function __pulse_fastly_fetch_body(value: __PulseFastlyValue): string { if (valu
|
|
|
1800
1805
|
function __pulse_fastly_scalar(kind: i32, boolean: i32, slot: i32): i32 {
|
|
1801
1806
|
const cached = unchecked(__pulse_fastly_scalar_handles[slot]); if (cached != 0) return cached;
|
|
1802
1807
|
const item = new __PulseFastlyValue(); item.kind = kind; item.boolean = boolean;
|
|
1803
|
-
const handle = __pulse_fastly_put(item); unchecked(__pulse_fastly_scalar_handles[slot] = handle); return handle
|
|
1808
|
+
const handle = __pulse_fastly_put(item); unchecked(__pulse_fastly_scalar_handles[slot] = handle); ${options.handleTracing ? 'if (handle != 0) __pulse_handle_cache_roots.push(handle);' : ''} return handle
|
|
1804
1809
|
}
|
|
1805
1810
|
function host_value_undefined(): i32 { return __pulse_fastly_scalar(PULSE_VALUE_UNDEFINED, 0, 0) }
|
|
1806
1811
|
function host_value_null(): i32 { return __pulse_fastly_scalar(PULSE_VALUE_NULL, 0, 1) }
|
|
@@ -1811,13 +1816,13 @@ function host_value_number(value: f64): i32 {
|
|
|
1811
1816
|
const index = cacheable ? i32(value) : -1;
|
|
1812
1817
|
if (index >= 0) { const cached = unchecked(__pulse_fastly_integer_handles[index]); if (cached != 0) return cached }
|
|
1813
1818
|
const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_NUMBER; item.number = value;
|
|
1814
|
-
const handle = __pulse_fastly_put(item); if (index >= 0) unchecked(__pulse_fastly_integer_handles[index] = handle); return handle
|
|
1819
|
+
const handle = __pulse_fastly_put(item); if (index >= 0) { unchecked(__pulse_fastly_integer_handles[index] = handle); ${options.handleTracing ? 'if (handle != 0) __pulse_handle_cache_roots.push(handle);' : ''} } return handle
|
|
1815
1820
|
}
|
|
1816
1821
|
function host_value_string(pointer: i32, length: i32): i32 {
|
|
1817
1822
|
const text = changetype<string>(pointer);
|
|
1818
1823
|
if (text.length > 1 && text.length <= 128 && __pulse_fastly_literal_handles.has(text)) return __pulse_fastly_literal_handles.get(text);
|
|
1819
1824
|
const handle = __pulse_fastly_string_value(text);
|
|
1820
|
-
if (text.length > 1 && text.length <= 128 && __pulse_fastly_literal_handles.size < 1024) __pulse_fastly_literal_handles.set(text, handle);
|
|
1825
|
+
if (text.length > 1 && text.length <= 128 && __pulse_fastly_literal_handles.size < 1024) { __pulse_fastly_literal_handles.set(text, handle); ${options.handleTracing ? 'if (handle != 0) __pulse_handle_cache_roots.push(handle);' : ''} }
|
|
1821
1826
|
return handle
|
|
1822
1827
|
}
|
|
1823
1828
|
function host_value_array(): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_ARRAY; return __pulse_fastly_put(item) }
|
|
@@ -2055,7 +2060,16 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
2055
2060
|
);
|
|
2056
2061
|
}
|
|
2057
2062
|
const bindings = resolveCapabilityBindings(plan, options);
|
|
2058
|
-
|
|
2063
|
+
// Keep final provider eligibility authoritative over the portable artifact
|
|
2064
|
+
// and caller options. Only the selected crypto guest uses the linked policy;
|
|
2065
|
+
// that policy excludes active codecs regardless of JSON arena configuration.
|
|
2066
|
+
const numericRegions = facts.guestUnits.length === 0 ? 'fastly-numeric-regions-v2'
|
|
2067
|
+
: jwtCrypto && jwtCrypto.guestUnitRequired ? 'fastly-linked-numeric-regions-v1' : undefined;
|
|
2068
|
+
const portable = packageApplication.enabled(plan) ? packageApplication.portable(plan) : generateCanonicalNativeAssemblyScript(plan, {
|
|
2069
|
+
...options, numericRegions,
|
|
2070
|
+
handleOwnership: facts.guestUnits.length === 0 ? 'fastly-handle-tracing-v1' : undefined
|
|
2071
|
+
});
|
|
2072
|
+
nativeHandleOwnership.validateRoots(plan, portable.manifest);
|
|
2059
2073
|
const portableSource = stripPulseHostImports(portable.source);
|
|
2060
2074
|
const hasJwt = (plan.effects || []).some((effect) => effect.kind === 'jwt.verify');
|
|
2061
2075
|
const hasSign = plan.effects.some(effect => effect.kind === 'jwt.sign');
|
|
@@ -2066,6 +2080,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
2066
2080
|
'',
|
|
2067
2081
|
fastlyRuntimeSource(plan, bindings, {
|
|
2068
2082
|
...options,
|
|
2083
|
+
handleTracing: portable.manifest.handleOwnership?.enabled === true,
|
|
2069
2084
|
jwtCrypto,
|
|
2070
2085
|
es256KeyRecords
|
|
2071
2086
|
}),
|
|
@@ -2082,7 +2097,10 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
2082
2097
|
require('./native-fetch-deadlines.js').runtimeSource(plan),
|
|
2083
2098
|
applicationErrors.settlementEnabled(plan) ? applicationErrors.runtimeSource(plan) : '',
|
|
2084
2099
|
effectInvocations.runtimeSource({ packageCompletion: packageApplication.enabled(plan) }),
|
|
2100
|
+
...(portable.manifest.numericRegions?.accounting === 'fastly-read-loop-v1' && portable.manifest.numericRegions.regions.length > 0
|
|
2101
|
+
? [nativeValueBudget.numericRegionSource()] : []),
|
|
2085
2102
|
portableSource,
|
|
2103
|
+
nativeHandleOwnership.providerSource(plan, portable.manifest.handleOwnership?.enabled === true),
|
|
2086
2104
|
driverSource(plan, { guestLinked: facts.guestUnits.length > 0 }),
|
|
2087
2105
|
''
|
|
2088
2106
|
].join('\n');
|
|
@@ -2096,6 +2114,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
2096
2114
|
source = effectInvocations.instrument(source);
|
|
2097
2115
|
source = require('./request-budget.js').instrumentRequestBudget(source, bindings.maxDurationMs, plan);
|
|
2098
2116
|
source = nativeValueBudget.instrument(source, plan);
|
|
2117
|
+
source = require('./native-body-forwarding.js').instrument(source, plan, bindings);
|
|
2099
2118
|
const effectKinds = Object.freeze((plan.effects || []).reduce((output, effect) => {
|
|
2100
2119
|
output[effect.kind] = (output[effect.kind] || 0) + 1;
|
|
2101
2120
|
return output;
|
|
@@ -2119,6 +2138,14 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
2119
2138
|
portableGeneratorVersion: portable.version,
|
|
2120
2139
|
portableSourceHash: portable.sourceHash,
|
|
2121
2140
|
schemaCodecs: portable.manifest.schemaCodecs,
|
|
2141
|
+
...(portable.manifest.numericRegions ? { numericRegions: portable.manifest.numericRegions } : {}),
|
|
2142
|
+
handleOwnership: portable.manifest.handleOwnership ? Object.freeze({ ...portable.manifest.handleOwnership,
|
|
2143
|
+
providerRoots: portable.manifest.handleOwnership.enabled ? Object.freeze(nativeHandleOwnership.providerRoots(plan)) : Object.freeze([])
|
|
2144
|
+
}) : Object.freeze({
|
|
2145
|
+
policy: null, enabled: false,
|
|
2146
|
+
reason: packageApplication.enabled(plan) ? 'package-owned-emitter' : 'linked-runtime',
|
|
2147
|
+
roots: Object.freeze([]), checkpoints: 'none'
|
|
2148
|
+
}),
|
|
2122
2149
|
handlerBodies: portable.manifest.handlerBodies,
|
|
2123
2150
|
...(portable.manifest.reportOwnership ? { reportOwnership: portable.manifest.reportOwnership } : {}),
|
|
2124
2151
|
dispatcher: portable.manifest.dispatcher,
|
|
@@ -2171,7 +2198,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
2171
2198
|
providerNeutralInput: true,
|
|
2172
2199
|
javascriptRuntime: false,
|
|
2173
2200
|
jsComputeRuntime: false,
|
|
2174
|
-
wasi: plan.
|
|
2201
|
+
wasi: requiredImportsForPlan(plan, bindings).filter(key => key.startsWith('wasi_snapshot_preview1:')).map(key => key.split(':')[1]).join(', ').replace(/(.+)/, '$1 only') || false,
|
|
2175
2202
|
effects: FASTLY_NATIVE_PLATFORM_EFFECT_KINDS.join(', '),
|
|
2176
2203
|
continuations: true,
|
|
2177
2204
|
groupedEffects: 'existing fetch start-before-wait ordering remains intact',
|
|
@@ -2183,7 +2210,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
2183
2210
|
kv: 'Fastly KV Store async lookup/insert; conditional operations preserve u64 generations, bounded readiness, strict Pulse envelopes and uncertain dispatch',
|
|
2184
2211
|
assets: 'Fastly KV Store lookup with provider-owned content type, cache headers, and GET/HEAD body ownership',
|
|
2185
2212
|
grip: 'canonical stateless framing plus bound, secret-referenced broadcast HTTP realization and stable acknowledgement',
|
|
2186
|
-
logging: 'best-effort redacted
|
|
2213
|
+
logging: 'best-effort redacted WASI fd_write to stdout (descriptor 1), one attempt per statement',
|
|
2187
2214
|
schemas: 'fetch JSON decode and structured response encode',
|
|
2188
2215
|
opaqueResponse: 'origin fetch response/body handles and direct GRIP hold responses pass through without JavaScript runtime ownership',
|
|
2189
2216
|
transportErrors: 'host status failures remain distinct from HTTP response status',
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Shared by both Native emitters. Fastly's named logging endpoint ABI rejects
|
|
4
|
+
// stdout/stderr; Compute stdio uses WASI fd_write with stdout descriptor 1.
|
|
5
|
+
const nativeStdioSource = String.raw`
|
|
6
|
+
@external("wasi_snapshot_preview1", "fd_write")
|
|
7
|
+
declare function __pulse_fastly_fd_write(fd: i32, iovs: usize, count: i32, written: usize): i32
|
|
8
|
+
|
|
9
|
+
function __pulse_fastly_write_stdout(bytes: ArrayBuffer): void {
|
|
10
|
+
const iov = new StaticArray<usize>(2)
|
|
11
|
+
unchecked(iov[0] = changetype<usize>(bytes))
|
|
12
|
+
unchecked(iov[1] = bytes.byteLength)
|
|
13
|
+
const written = new StaticArray<i32>(1)
|
|
14
|
+
// One best-effort attempt: errors, zero progress, and short writes never
|
|
15
|
+
// retry, spin, change the response, or disable subsequent log statements.
|
|
16
|
+
__pulse_fastly_fd_write(1, changetype<usize>(iov), 1, changetype<usize>(written))
|
|
17
|
+
}
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
module.exports = { nativeStdioSource };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
//
|
|
3
|
+
// Store concatenation as shared, immutable prefixes instead of retaining a flat
|
|
4
|
+
// copy for every intermediate. Direct object edges preserve live rope children.
|
|
5
5
|
// This changes representation only: coercion happens at the original operation,
|
|
6
6
|
// and aliases keep their original string even after a later append.
|
|
7
7
|
const nativeStringFields = `
|
|
@@ -35,6 +35,38 @@ export function pulse_fastly_memory_values(): i64 { return __pulse_memory_values
|
|
|
35
35
|
`;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// These helpers are private to proven regions in an unlinked read-loop plan.
|
|
39
|
+
// Cache admissions use the original operations, so later boxed code sees the
|
|
40
|
+
// same cache and charges. Uncached numbers pay at each original evaluation;
|
|
41
|
+
// only their physical materialization is delayed until a region exit.
|
|
42
|
+
function numericRegionSource() {
|
|
43
|
+
return `
|
|
44
|
+
function __pulse_numeric_cacheable(value: f64): bool {
|
|
45
|
+
return value >= 0 && value < 65536 && Math.floor(value) == value && (value != 0 || 1.0 / value > 0)
|
|
46
|
+
}
|
|
47
|
+
function __pulse_numeric_charge_number(value: f64): f64 {
|
|
48
|
+
__pulse_memory_charge(0, 0)
|
|
49
|
+
if (__pulse_numeric_cacheable(value)) host_value_number(value)
|
|
50
|
+
else __pulse_memory_charge(1, ${policy.valueBytes})
|
|
51
|
+
return value
|
|
52
|
+
}
|
|
53
|
+
function __pulse_numeric_charge_boolean(value: bool): bool {
|
|
54
|
+
__pulse_memory_charge(0, 0)
|
|
55
|
+
host_value_boolean(value ? 1 : 0)
|
|
56
|
+
return value
|
|
57
|
+
}
|
|
58
|
+
function __pulse_numeric_truthy(value: f64): bool { return value != 0.0 && !isNaN(value) }
|
|
59
|
+
function __pulse_numeric_box(value: f64): i32 {
|
|
60
|
+
__pulse_memory_charge(0, 0)
|
|
61
|
+
if (__pulse_numeric_cacheable(value)) return host_value_number(value)
|
|
62
|
+
const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_NUMBER; item.number = value
|
|
63
|
+
// Already charged at the original expression, never a general put bypass.
|
|
64
|
+
__pulse_fastly_values.push(item)
|
|
65
|
+
return __pulse_fastly_values.length
|
|
66
|
+
}
|
|
67
|
+
`;
|
|
68
|
+
}
|
|
69
|
+
|
|
38
70
|
function instrument(source, plan) {
|
|
39
71
|
if (!hasBoundedReadLoop(plan)) return source;
|
|
40
72
|
function insert(needle, extra) {
|
|
@@ -55,4 +87,4 @@ function instrument(source, plan) {
|
|
|
55
87
|
return source + runtimeSource();
|
|
56
88
|
}
|
|
57
89
|
|
|
58
|
-
module.exports = { policy, hasBoundedReadLoop, runtimeSource, instrument };
|
|
90
|
+
module.exports = { policy, hasBoundedReadLoop, runtimeSource, numericRegionSource, instrument };
|
|
@@ -10,8 +10,8 @@ class __PulseFastlyAdmissionFrame {
|
|
|
10
10
|
}
|
|
11
11
|
function __pulse_fastly_admission_enter(handle: i32, budget: __PulseJsonAdmissionBudget,
|
|
12
12
|
stack: Array<__PulseFastlyAdmissionFrame>, active: Set<i32>): bool {
|
|
13
|
-
if (handle
|
|
14
|
-
const value =
|
|
13
|
+
if (!__pulse_fastly_has_value(handle)) return budget.fail(12)
|
|
14
|
+
const value = __pulse_fastly_value(handle)
|
|
15
15
|
const array = value.kind == PULSE_VALUE_ARRAY
|
|
16
16
|
const object = value.kind == PULSE_VALUE_OBJECT
|
|
17
17
|
if (!budget.node(stack.length + (array || object ? 1 : 0))) return false
|
|
@@ -36,7 +36,7 @@ function __pulse_fastly_admit_bounded_value(handle: i32, limits: __PulseJsonAdmi
|
|
|
36
36
|
if (!__pulse_fastly_admission_enter(handle, budget, stack, active)) return budget
|
|
37
37
|
while (stack.length > 0) {
|
|
38
38
|
const frame = stack[stack.length - 1]
|
|
39
|
-
const value =
|
|
39
|
+
const value = __pulse_fastly_value(frame.handle)
|
|
40
40
|
if (frame.index == value.values.length) { active.delete(frame.handle); stack.pop(); continue }
|
|
41
41
|
const index = frame.index++
|
|
42
42
|
if (index > 0 && !budget.addBytes(1)) return budget
|
package/src/config-api.js
CHANGED
|
@@ -39,6 +39,7 @@ function fastly(options = {}) {
|
|
|
39
39
|
const normalizedGrip = options.grip === undefined ? undefined : gripBindings(options.grip);
|
|
40
40
|
return Object.freeze({
|
|
41
41
|
kind: 'fastly',
|
|
42
|
+
...(options.bodyForwarding === undefined ? {} : {bodyForwarding: require('./toolchain/body-forwarding.js').normalizeBodyForwarding(options.bodyForwarding, options.maxDurationMs)}),
|
|
42
43
|
...(options.maxDurationMs === undefined ? {} : { maxDurationMs: require('@pulse-compute/runtime/host').normalizeRequestDuration(options.maxDurationMs) }),
|
|
43
44
|
version: FASTLY_PROVIDER_API_VERSION,
|
|
44
45
|
bindings: Object.freeze({
|
package/src/config-schema.json
CHANGED
|
@@ -82,6 +82,19 @@
|
|
|
82
82
|
},
|
|
83
83
|
"fastly.maxWasmBytes": {
|
|
84
84
|
"kind": "positive-safe-integer"
|
|
85
|
+
},
|
|
86
|
+
"fastly.bodyForwarding": {
|
|
87
|
+
"kind": "object"
|
|
88
|
+
},
|
|
89
|
+
"fastly.bodyForwarding.requestMaxBytes": {
|
|
90
|
+
"kind": "integer",
|
|
91
|
+
"minimum": 1,
|
|
92
|
+
"maximum": 1048576
|
|
93
|
+
},
|
|
94
|
+
"fastly.bodyForwarding.responseMaxBytes": {
|
|
95
|
+
"kind": "integer",
|
|
96
|
+
"minimum": 1,
|
|
97
|
+
"maximum": 1048576
|
|
85
98
|
}
|
|
86
99
|
}
|
|
87
100
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const { normalizeBodyForwarding } = require('../src/toolchain/body-forwarding.js') as { normalizeBodyForwarding(value: PulseFastlyBodyForwardingOptions | undefined, maxDurationMs?: number): PulseFastlyBodyForwardingOptions | undefined };
|
|
1
2
|
const { normalizeFastlyMaxWasmBytes } = require('../src/build/wasm-budget.js') as { normalizeFastlyMaxWasmBytes(value?: number): number };
|
|
2
3
|
const { normalizeRequestDuration } = require('@pulse-compute/runtime/host') as { normalizeRequestDuration(value?: number): number | undefined };
|
|
3
4
|
import FASTLY_CONFIG_SCHEMA from './config-schema.json';
|
|
@@ -24,10 +25,17 @@ export interface PulseFastlyLocalOptions {
|
|
|
24
25
|
readonly networkFetch?: boolean;
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
export interface PulseFastlyBodyForwardingOptions {
|
|
29
|
+
readonly requestMaxBytes: number;
|
|
30
|
+
readonly responseMaxBytes: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
export interface PulseFastlyProviderOptions {
|
|
28
34
|
/** Inclusive final Native Wasm byte budget; default 4 MiB. */
|
|
29
35
|
readonly maxWasmBytes?: number;
|
|
30
36
|
readonly maxDurationMs?: number;
|
|
37
|
+
/** Experimental Native metered pump; final close hands delivery to Fastly. */
|
|
38
|
+
readonly bodyForwarding?: PulseFastlyBodyForwardingOptions;
|
|
31
39
|
readonly configStore?: string;
|
|
32
40
|
readonly secretStore?: string;
|
|
33
41
|
readonly kv?: Readonly<Record<string, string>>;
|
|
@@ -42,6 +50,8 @@ export interface PulseFastlyProviderOptions {
|
|
|
42
50
|
|
|
43
51
|
export interface PulseFastlyProviderConfig {
|
|
44
52
|
readonly maxDurationMs?: number;
|
|
53
|
+
/** Experimental Native metered pump; final close hands delivery to Fastly. */
|
|
54
|
+
readonly bodyForwarding?: PulseFastlyBodyForwardingOptions;
|
|
45
55
|
readonly kind: 'fastly';
|
|
46
56
|
readonly version: typeof FASTLY_PROVIDER_API_VERSION;
|
|
47
57
|
readonly bindings: {
|
|
@@ -105,6 +115,7 @@ export function fastly(options: PulseFastlyProviderOptions = {}): PulseFastlyPro
|
|
|
105
115
|
return Object.freeze({
|
|
106
116
|
kind: 'fastly',
|
|
107
117
|
maxDurationMs: normalizeRequestDuration(options.maxDurationMs),
|
|
118
|
+
bodyForwarding: normalizeBodyForwarding(options.bodyForwarding, options.maxDurationMs),
|
|
108
119
|
version: FASTLY_PROVIDER_API_VERSION,
|
|
109
120
|
bindings: Object.freeze({
|
|
110
121
|
configStore: String(options.configStore ?? fastlyDefaults['fastly.configStore']),
|
package/src/provider-contract.js
CHANGED
|
@@ -12,13 +12,14 @@ const FASTLY_PROVIDER_DESCRIPTOR = canonical.normalizeDescriptor({
|
|
|
12
12
|
deployable: true,
|
|
13
13
|
localExecution: true,
|
|
14
14
|
capabilities: [
|
|
15
|
-
'request', 'response.json', 'response.text', 'response.custom', 'fetch',
|
|
15
|
+
'request', 'request.body.forward', 'response.json', 'response.text', 'response.custom', 'fetch',
|
|
16
16
|
'time.wall-clock', 'crypto.digestText', 'config.get', 'secret.get', 'kv.get', 'kv.put', 'kv.getVersioned', 'kv.insertIfAbsent', 'kv.compareAndSwap', 'assets.lookup',
|
|
17
17
|
'grip.channel', 'grip.hold', 'grip.publish', 'grip.broadcast', 'jwt.verify', 'jwt.sign',
|
|
18
18
|
'opaque.pass-through', 's3.head', 's3.getText', 's3.putText', 's3.getBody'
|
|
19
19
|
],
|
|
20
20
|
lowering: {
|
|
21
21
|
request: 'fastly.compute.request',
|
|
22
|
+
'request.body.forward': 'fastly.native.body-forwarding',
|
|
22
23
|
'response.json': 'fastly.compute.response.json',
|
|
23
24
|
'response.text': 'fastly.compute.response.text',
|
|
24
25
|
'response.custom': 'fastly.compute.response.custom',
|
|
@@ -62,10 +63,12 @@ function validateStaticBackendBindings(metadata = {}, bindings = {}) {
|
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
function createFastlyLoweringPlan(metadata, bindings = {}) {
|
|
66
|
+
function createFastlyLoweringPlan(metadata, bindings = {}, options = {}) {
|
|
67
|
+
const forwarding = require('./toolchain/body-forwarding.js').normalizeBodyForwarding(options.bodyForwarding, options.maxDurationMs);
|
|
68
|
+
const descriptor = forwarding ? FASTLY_PROVIDER_DESCRIPTOR : {...FASTLY_PROVIDER_DESCRIPTOR, capabilities: FASTLY_PROVIDER_DESCRIPTOR.capabilities.filter(id => id !== 'request.body.forward')};
|
|
66
69
|
require('./toolchain/s3.js').resolveFastlyS3(metadata.providerOperations || [], bindings.s3);
|
|
67
70
|
validateStaticBackendBindings(metadata, bindings);
|
|
68
|
-
return canonical.createProviderLoweringPlan(metadata,
|
|
71
|
+
return canonical.createProviderLoweringPlan(metadata, descriptor, {
|
|
69
72
|
...bindings,
|
|
70
73
|
grip: bindings.grip || 'fastly-fanout'
|
|
71
74
|
});
|