@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.
Files changed (36) hide show
  1. package/README.md +6 -6
  2. package/package.json +11 -11
  3. package/src/build/canonical-target.js +6 -0
  4. package/src/build/effect-invocations.js +6 -4
  5. package/src/build/host-extensions.js +254 -0
  6. package/src/build/lifecycle.js +50 -0
  7. package/src/build/native-body-forwarding.as.ts +28 -44
  8. package/src/build/native-body-forwarding.js +15 -11
  9. package/src/build/native-fetch-accounting.as.ts +133 -0
  10. package/src/build/native-fetch-accounting.js +84 -0
  11. package/src/build/native-fetch-bounds.js +47 -0
  12. package/src/build/native-fetch-cohorts.as.ts +126 -0
  13. package/src/build/native-fetch-cohorts.js +65 -0
  14. package/src/build/native-fetch-deadlines.as.ts +13 -8
  15. package/src/build/native-fetch-deadlines.js +13 -4
  16. package/src/build/native-fetch-errors.js +2 -2
  17. package/src/build/native-fetch-headers.js +52 -0
  18. package/src/build/native-fetch-outcomes.js +87 -0
  19. package/src/build/native-fetch-timing.as.ts +74 -0
  20. package/src/build/native-fetch-timing.js +50 -0
  21. package/src/build/native-forwarding-accounting.as.ts +153 -0
  22. package/src/build/native-forwarding-accounting.js +47 -0
  23. package/src/build/native-header-lookup.js +2 -2
  24. package/src/build/native-http-effects.js +10 -4
  25. package/src/build/native-platform-capabilities.js +89 -39
  26. package/src/build/native-request-body.js +6 -5
  27. package/src/build/{request-budget.as.ts → request-budget-source.js} +12 -4
  28. package/src/build/request-budget.js +7 -8
  29. package/src/build/response-authority.js +56 -0
  30. package/src/javascript/fetch-adapter.js +3 -0
  31. package/src/javascript/runtime-host.js +1 -0
  32. package/src/provider-contract.js +2 -1
  33. package/src/testing/native-http-effects-host.js +9 -0
  34. package/src/testing/native-platform-capabilities-host.js +10 -0
  35. package/src/toolchain/index.js +2 -1
  36. package/src/toolchain/native-execution.js +7 -2
@@ -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');
@@ -7,6 +9,8 @@ const { hasJsonAdmission, tracksJsonDuplicates, schemaParserDepth, nestedJsonPro
7
9
  const { nativeLoggingSource } = require('./native-stdio.js');
8
10
  const applicationErrors = require('./native-application-errors.js');
9
11
  const effectInvocations = require('./effect-invocations.js');
12
+ const lifecycleBindings = require('./lifecycle.js');
13
+ const hostExtensions = require('./host-extensions.js');
10
14
  const packageApplication = require('./package-native-application.js');
11
15
  const nativeValueBudget = require('./native-value-budget.js');
12
16
  const { schemaNeedsValueProjection } = require('@pulse-compute/wasm-contracts/schema-json/registry');
@@ -126,6 +130,7 @@ const FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORTS = Object.freeze([
126
130
  Object.freeze(['fastly_http_req', 'method_set']),
127
131
  Object.freeze(['fastly_http_req', 'uri_set']),
128
132
  Object.freeze(['fastly_http_req', 'header_insert']),
133
+ Object.freeze(['fastly_http_req', 'header_append']),
129
134
  Object.freeze(['fastly_http_req', 'send_async']),
130
135
  Object.freeze(['fastly_http_req', 'pending_req_wait_v2']),
131
136
  Object.freeze(['fastly_http_resp', 'new']),
@@ -189,7 +194,8 @@ const FASTLY_NATIVE_PLATFORM_EFFECT_KIND = Object.freeze({
189
194
  'kv.insertIfAbsent': 16,
190
195
  'kv.compareAndSwap': 17,
191
196
  'time.now': 18,
192
- 'crypto.digestText': 19
197
+ 'crypto.digestText': 19,
198
+ 'host.extension': 24
193
199
  });
194
200
 
195
201
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_ALLOWED_IMPORTS = new Set([
@@ -198,6 +204,7 @@ const FASTLY_NATIVE_PLATFORM_CAPABILITIES_ALLOWED_IMPORTS = new Set([
198
204
  ]);
199
205
 
200
206
  const FASTLY_NATIVE_PLATFORM_EFFECT_KINDS = Object.freeze([
207
+ 'host.extension',
201
208
  'time.now',
202
209
  'crypto.digestText',
203
210
  'fetch',
@@ -220,6 +227,7 @@ const FASTLY_NATIVE_PLATFORM_EFFECT_KINDS = Object.freeze([
220
227
  ]);
221
228
 
222
229
  const FASTLY_NATIVE_PLATFORM_CAPABILITY_KINDS = new Set([
230
+ 'host.extension',
223
231
  'time.now',
224
232
  'crypto.digestText',
225
233
  'config.get',
@@ -684,6 +692,7 @@ function validatePlanBoundary(plan, options = {}) {
684
692
  if (!plan.ownership || plan.ownership.providerNeutral !== true || plan.ownership.javascriptRuntime !== false) {
685
693
  fail('Fastly native platform capability realization requires a provider-neutral plan without JavaScript runtime ownership.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_PLAN_OWNERSHIP_INVALID', { ownership: plan.ownership });
686
694
  }
695
+ const selectedExtensions = hostExtensions.select(plan, options);
687
696
  const effects = plan.effects || [];
688
697
  const unsupported = effects.filter((effect) => !FASTLY_NATIVE_PLATFORM_EFFECT_KINDS.includes(effect.kind));
689
698
  if (unsupported.length > 0) {
@@ -711,7 +720,8 @@ function validatePlanBoundary(plan, options = {}) {
711
720
  && ['head', 'getText', 'putText', 'getBody'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
712
721
  const digest = effect.package === '@pulse-compute/crypto' && effect.contractId === 'pulse.crypto'
713
722
  && effect.operation === 'digestText' && effect.kind === 'crypto.digestText' && effect.capability === effect.kind;
714
- return !grip && !assets && !jwt && !s3 && !digest;
723
+ const extension = effect.kind === 'host.extension' && selectedExtensions.some(item => hostExtensions.matches(effect, item.artifact));
724
+ return !grip && !assets && !jwt && !s3 && !digest && !extension;
715
725
  });
716
726
  if (unsupportedPackages.length > 0) {
717
727
  fail('Fastly native realization only accepts the trusted first-party Assets, GRIP, JWT, S3, and Crypto digest package contracts.', 'PULSE_FASTLY_NATIVE_PLATFORM_PACKAGE_UNSUPPORTED', {
@@ -734,7 +744,8 @@ function validatePlanBoundary(plan, options = {}) {
734
744
  && ['head', 'getText', 'putText', 'getBody'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
735
745
  const digest = effect.package === '@pulse-compute/crypto' && effect.contractId === 'pulse.crypto'
736
746
  && effect.operation === 'digestText' && effect.kind === 'crypto.digestText' && effect.capability === effect.kind;
737
- return !grip && !assets && !jwt && !s3 && !digest;
747
+ const extension = effect.kind === 'host.extension' && selectedExtensions.some(item => hostExtensions.matches(effect, item.artifact));
748
+ return !grip && !assets && !jwt && !s3 && !digest && !extension;
738
749
  });
739
750
  if (unsupportedDeclarations.length > 0) {
740
751
  fail('Fastly native package realization is restricted to the Assets, GRIP, JWT, S3, and Crypto digest contracts.', 'PULSE_FASTLY_NATIVE_PLATFORM_PACKAGE_UNSUPPORTED', { effects: unsupportedDeclarations });
@@ -1003,10 +1014,11 @@ function requiredImportsForPlan(plan, bindings) {
1003
1014
  if (kinds.has('fetch') || kinds.has('grip.publish') || kinds.has('grip.broadcast')) {
1004
1015
  for (const key of [
1005
1016
  'fastly_http_req:new', 'fastly_http_req:method_set', 'fastly_http_req:uri_set',
1006
- 'fastly_http_req:header_insert', 'fastly_http_req:send_async', 'fastly_http_req:pending_req_wait_v2'
1017
+ 'fastly_http_req:header_append', 'fastly_http_req:send_async', 'fastly_http_req:pending_req_wait_v2'
1007
1018
  ]) keys.add(key);
1008
1019
  }
1009
1020
  if (bindings && dynamicBackends.active(bindings)) keys.add('fastly_http_req:register_dynamic_backend');
1021
+ if (plan.effects.some(effect=>effect.fetchTiming)) keys.add('fastly_async_io:is_ready');
1010
1022
  if (kinds.has('fetch')) for (const key of ['wasi_snapshot_preview1:clock_time_get', 'fastly_async_io:select', 'fastly_http_req:pending_req_poll_v2', 'fastly_http_resp:close', 'fastly_http_body:close']) keys.add(key);
1011
1023
  if (planRequiresFetchBodyRead(plan)) keys.add('fastly_http_body:read');
1012
1024
  if (kinds.has('config.get')) for (const key of ['fastly_config_store:open', 'fastly_config_store:get']) keys.add(key);
@@ -1032,6 +1044,7 @@ function requiredImportsForPlan(plan, bindings) {
1032
1044
  }
1033
1045
  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);
1034
1046
  if (require('./native-body-forwarding.js').enabled(plan)) {
1047
+ keys.delete('fastly_http_req:header_append'); keys.add('fastly_http_req:header_insert');
1035
1048
  keys.delete('fastly_http_req:send_async'); keys.delete('fastly_http_req:pending_req_wait_v2');
1036
1049
  for (const key of require('./native-body-forwarding.js').IMPORTS) keys.add(key);
1037
1050
  keys.add('fastly_http_body:read');
@@ -1185,6 +1198,7 @@ function generateSchemaRuntime(plan) {
1185
1198
  `const __PULSE_SCHEMA_REQUIRE_JSON: bool = ${registry.contentTypePolicy === 'require-json' ? 'true' : 'false'}`,
1186
1199
  '',
1187
1200
  schemaJsonAdmissionSource(plan),
1201
+ fetchHeadersSource(),
1188
1202
  schemaFetchJsonSource(),
1189
1203
  ...nodeLines,
1190
1204
  `function __pulse_fastly_schema_apply(schemaId: string, valueHandle: i32, encode: bool${textEncodeIds.size ? ', textResult: bool = false' : ''}): i32 {`,
@@ -1315,7 +1329,8 @@ function effectDispatchSource(plan) {
1315
1329
  'grip.channel': '__pulse_fastly_grip_channel_begin',
1316
1330
  'grip.hold': '__pulse_fastly_grip_hold_begin',
1317
1331
  'grip.publish': '__pulse_fastly_grip_publish_begin',
1318
- 'grip.broadcast': '__pulse_fastly_grip_publish_begin'
1332
+ 'grip.broadcast': '__pulse_fastly_grip_publish_begin',
1333
+ 'host.extension': '__pulse_extension_begin'
1319
1334
  });
1320
1335
  const kinds = [...new Set((plan.effects || []).map((effect) => effect.kind))];
1321
1336
  const lines = [
@@ -1341,6 +1356,7 @@ function effectDispatchSource(plan) {
1341
1356
  }
1342
1357
 
1343
1358
  function fastlyRuntimeSource(plan, bindings, options = {}) {
1359
+ const lifecycle = options.lifecycle;
1344
1360
  const textEncodeIds = schemaTextEncodeIds(plan);
1345
1361
  const effectCount = (plan.effects || []).length;
1346
1362
  const hasAssets = (plan.effects || []).some(effect => effect.kind === 'assets.lookup');
@@ -1414,7 +1430,7 @@ function __pulse_fastly_jwt_attach_evidence(handle: i32): void { void handle }
1414
1430
  // their specific responses here. Every remaining fatal path needs a response,
1415
1431
  // independently of whether JWT evidence was requested for this build.
1416
1432
  const terminalErrorSource = `
1417
- function __pulse_fastly_jwt_send_error(): void {
1433
+ ${lifecycle?.active ? `function __pulse_fastly_jwt_send_error(): void {\n${lifecycle.site('response-error')}\n __pulse_fastly_jwt_send_error_impl()\n}\n` : ''}function ${lifecycle?.legacyName('__pulse_fastly_jwt_send_error') || '__pulse_fastly_jwt_send_error'}(): void {
1418
1434
  const output = new __PulseFastlyValue()
1419
1435
  output.kind = PULSE_VALUE_RESPONSE
1420
1436
  output.status = ${jwtReality ? '__pulse_fastly_jwt_error != 0 ? 400 : 500' : '500'}
@@ -1442,6 +1458,7 @@ function __pulse_fastly_jwt_send_error(): void {
1442
1458
  @external("fastly_http_req", "method_set") declare function fastly_http_req_method_set(handle: i32, method: usize, methodLength: i32): i32
1443
1459
  @external("fastly_http_req", "uri_set") declare function fastly_http_req_uri_set(handle: i32, uri: usize, uriLength: i32): i32
1444
1460
  @external("fastly_http_req", "header_insert") declare function fastly_http_req_header_insert(handle: i32, name: usize, nameLength: i32, value: usize, valueLength: i32): i32
1461
+ @external("fastly_http_req", "header_append") declare function fastly_http_req_header_append(handle: i32, name: usize, nameLength: i32, value: usize, valueLength: i32): i32
1445
1462
  @external("fastly_http_req", "send_async") declare function fastly_http_req_send_async(handle: i32, body: i32, backend: usize, backendLength: i32, pendingOut: usize): i32
1446
1463
  @external("fastly_http_req", "pending_req_wait_v2") declare function fastly_http_req_pending_req_wait_v2(handle: i32, detail: usize, responseOut: usize, bodyOut: usize): i32
1447
1464
  @external("fastly_http_resp", "new") declare function fastly_http_resp_new(handleOut: usize): i32
@@ -1449,7 +1466,7 @@ function __pulse_fastly_jwt_send_error(): void {
1449
1466
  @external("fastly_http_resp", "header_value_get") declare function fastly_http_resp_header_value_get(handle: i32, name: usize, nameLength: i32, value: usize, valueLength: i32, writtenOut: usize): i32
1450
1467
  @external("fastly_http_resp", "status_get") declare function fastly_http_resp_status_get(handle: i32, statusOut: usize): i32
1451
1468
  @external("fastly_http_resp", "status_set") declare function fastly_http_resp_status_set(handle: i32, status: i32): i32
1452
- @external("fastly_http_resp", "send_downstream") declare function fastly_http_resp_send_downstream(handle: i32, body: i32, streaming: i32): i32
1469
+ @external("fastly_http_resp", "send_downstream") declare function ${lifecycle?.authority ? '__pulse_response_send_raw' : 'fastly_http_resp_send_downstream'}(handle: i32, body: i32, streaming: i32): i32
1453
1470
  @external("fastly_http_body", "new") declare function fastly_http_body_new(handleOut: usize): i32
1454
1471
  @external("fastly_http_body", "read") declare function fastly_http_body_read(handle: i32, buffer: usize, bufferLength: i32, readOut: usize): i32
1455
1472
  @external("fastly_http_body", "write") declare function fastly_http_body_write(handle: i32, buffer: usize, bufferLength: i32, end: i32, writtenOut: usize): i32
@@ -1526,6 +1543,7 @@ ${nativeStringFields}
1526
1543
  }
1527
1544
  responseHandle: i32 = 0
1528
1545
  bodyHandle: i32 = 0
1546
+ ${options.fetchAccounting?.selection === 'canonical-fetch-cohorts' ? ' fetchHeaders: Array<__FCA_Header> | null = null;\n fetchJsonType: i32 = 0;' : ''}
1529
1547
  bodyLoaded: i32 = 0
1530
1548
  s3Body: bool = false
1531
1549
  s3Deadline: i64 = 0
@@ -1803,26 +1821,29 @@ function __pulse_fastly_json(handle: i32, depth: i32): string {
1803
1821
  }
1804
1822
  function __pulse_fastly_parse_json(text: string): i32 { const parser = new __PulseJsonParser(text); const value = parser.parse(); if (parser.failed || value <= 0) { __pulse_fastly_fail(PULSE_ERROR_JSON, 3, -1); return 0 } return value }
1805
1823
  function __pulse_fastly_path(uri: string): string { let start = 0; const scheme = uri.indexOf("://"); if (scheme >= 0) { const slash = uri.indexOf("/", scheme + 3); start = slash >= 0 ? slash : uri.length } let end = uri.length; const query = uri.indexOf("?", start); if (query >= 0 && query < end) end = query; const fragment = uri.indexOf("#", start); if (fragment >= 0 && fragment < end) end = fragment; return start >= end ? "/" : uri.substring(start, end) }
1806
- function __pulse_fastly_read_req_string(kind: i32): string { const buffer = new Uint8Array(PULSE_FASTLY_BUFFER_BYTES); const written = __pulse_fastly_out_i32(); const status = kind == 0 ? fastly_http_req_method_get(__pulse_fastly_request_handle, buffer.dataStart, PULSE_FASTLY_BUFFER_BYTES, changetype<usize>(written)) : fastly_http_req_uri_get(__pulse_fastly_request_handle, buffer.dataStart, PULSE_FASTLY_BUFFER_BYTES, changetype<usize>(written)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 10 + kind, -1); return "" } return __pulse_fastly_decode(buffer, __pulse_fastly_out_value(written)) }
1824
+ function __pulse_fastly_read_req_string(kind: i32): string { const buffer = new Uint8Array(PULSE_FASTLY_BUFFER_BYTES); const written = __pulse_fastly_out_i32(); const status = kind == 0 ? fastly_http_req_method_get(__pulse_fastly_request_handle, buffer.dataStart, PULSE_FASTLY_BUFFER_BYTES, changetype<usize>(written)) : fastly_http_req_uri_get(__pulse_fastly_request_handle, buffer.dataStart, PULSE_FASTLY_BUFFER_BYTES, changetype<usize>(written)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 10 + kind, -1); return "" } ${options.fetchAccounting?.selection === 'canonical-fetch-cohorts' ? 'if (__pulse_fastly_out_value(written)<0 || __pulse_fastly_out_value(written)>buffer.byteLength) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL,10+kind,-1);return ""; }' : ''} return __pulse_fastly_decode(buffer, __pulse_fastly_out_value(written)) }
1807
1825
  function __pulse_fastly_read_body(handle: i32, effectIndex: i32, requestText: bool = false): string {
1808
- const chunks = new Array<Uint8Array>(); let total = 0;
1826
+ // One retained input allocation, including a byte to distinguish exact-limit
1827
+ // EOF from overflow. Short reads never allocate another chunk or prefix.
1828
+ if (__PULSE_SCHEMA_MAX_BYTES <= 0 || __PULSE_SCHEMA_MAX_BYTES > 1073741823) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 21, effectIndex); return "" }
1829
+ const bytes = new Uint8Array(__PULSE_SCHEMA_MAX_BYTES + 1), read = __pulse_fastly_out_i32();
1830
+ let total = 0;
1809
1831
  while (true) {
1810
- const buffer = new Uint8Array(PULSE_FASTLY_BUFFER_BYTES), read = __pulse_fastly_out_i32();
1811
- const status = fastly_http_body_read(handle, buffer.dataStart, buffer.length, changetype<usize>(read));
1832
+ if (!__pulse_fastly_body_read_ready(handle, effectIndex)) return "";
1833
+ const capacity = min<i32>(PULSE_FASTLY_BUFFER_BYTES, bytes.length - total);
1834
+ const status = fastly_http_body_read(handle, bytes.dataStart + total, capacity, changetype<usize>(read));
1812
1835
  if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 20, effectIndex); return "" }
1813
1836
  const count = __pulse_fastly_out_value(read);
1814
- if (count < 0 || count > buffer.length) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 20, effectIndex); return "" }
1837
+ if (count < 0 || count > capacity) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 20, effectIndex); return "" }
1815
1838
  if (!count) break;
1816
1839
  total += count;
1817
- if (total > __PULSE_SCHEMA_MAX_BYTES && __PULSE_SCHEMA_MAX_BYTES > 0) { __pulse_fastly_fail(requestText ? PULSE_ERROR_REQUEST_BODY : PULSE_ERROR_SCHEMA, 21, effectIndex); return "" }
1818
- chunks.push(buffer.subarray(0, count));
1840
+ if (total > __PULSE_SCHEMA_MAX_BYTES) { __pulse_fastly_fail(requestText ? PULSE_ERROR_REQUEST_BODY : PULSE_ERROR_SCHEMA, 21, effectIndex); return "" }
1819
1841
  }
1820
1842
  // UTF-8 scalars may straddle host read chunks. Decode the bounded body once.
1821
- const bytes = new Uint8Array(total); let offset = 0;
1822
- for (let i = 0; i < chunks.length; i++) { bytes.set(chunks[i], offset); offset += chunks[i].length; }
1823
- if (requestText && !__pulse_request_utf8_valid(bytes)) { __pulse_fastly_fail(PULSE_ERROR_REQUEST_BODY, 24, effectIndex); return "" }
1843
+ if (requestText && !__pulse_request_utf8_valid(bytes, total)) { __pulse_fastly_fail(PULSE_ERROR_REQUEST_BODY, 24, effectIndex); return "" }
1824
1844
  return __pulse_fastly_decode(bytes, total);
1825
1845
  }
1846
+ function __pulse_fastly_body_read_ready(handle: i32, effectIndex: i32): bool { return true }
1826
1847
  function __pulse_fastly_request_header_text(name: string): string { const value = __pulse_header_lookup(__pulse_fastly_request_handle, name, false); return value === null ? "" : value }
1827
1848
  function __pulse_fastly_fetch_header_text(responseHandle: i32, name: string): string { const value = __pulse_header_lookup(responseHandle, name, true); return value === null ? "" : value }
1828
1849
  function __pulse_fastly_fetch_body(value: __PulseFastlyValue): string { if (value.bodyLoaded == 0) { value.text = __pulse_fastly_read_body(value.bodyHandle, -1); value.bodyLoaded = 1 } return value.text }
@@ -1864,7 +1885,7 @@ function host_value_array_spread(target: i32, source: i32): void { const from =
1864
1885
  function host_value_object(): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_OBJECT; return __pulse_fastly_put(item) }
1865
1886
  function host_value_object_set(target: i32, keyHandle: i32, value: i32): void { const item = __pulse_fastly_value(target); if (item.kind != PULSE_VALUE_OBJECT || item.immutable) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 32, -1); return } const key = __pulse_fastly_string(keyHandle); const index = __pulse_fastly_find(item, key); if (index >= 0) unchecked(item.values[index] = value); else { item.keys.push(key); item.values.push(value) } }
1866
1887
  function host_value_object_spread(target: i32, source: i32): void { const from = __pulse_fastly_value(source); if (from.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 33, -1); return } for (let i = 0; i < from.keys.length; i += 1) host_value_object_set(target, __pulse_fastly_string_value(unchecked(from.keys[i])), unchecked(from.values[i])) }
1867
- function host_value_property(target: i32, keyHandle: i32): i32 { const item = __pulse_fastly_value(target); const key = __pulse_fastly_string(keyHandle); if (item.kind == PULSE_VALUE_OBJECT) { const index = __pulse_fastly_find(item, key); return index >= 0 ? unchecked(item.values[index]) : host_value_undefined() } if (item.kind == PULSE_VALUE_FETCH && key == "status") { const out = __pulse_fastly_out_i32(); const status = fastly_http_resp_status_get(item.responseHandle, changetype<usize>(out)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 34, -1); return 0 } return host_value_number(__pulse_fastly_out_value(out)) } if ((item.kind == PULSE_VALUE_STRING || item.kind == PULSE_VALUE_ARRAY) && key == "length") return host_value_number(item.kind == PULSE_VALUE_STRING ? item.textLength : item.values.length); return host_value_undefined() }
1888
+ function host_value_property(target: i32, keyHandle: i32): i32 { const item = __pulse_fastly_value(target); const key = __pulse_fastly_string(keyHandle); if (item.kind == PULSE_VALUE_OBJECT) { const index = __pulse_fastly_find(item, key); return index >= 0 ? unchecked(item.values[index]) : host_value_undefined() } if (item.kind == PULSE_VALUE_FETCH && key == "status") { ${options.fetchAccounting?.selection === 'canonical-fetch-cohorts' ? 'return __fca_status(item);' : ''} const out = __pulse_fastly_out_i32(); const status = fastly_http_resp_status_get(item.responseHandle, changetype<usize>(out)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 34, -1); return 0 } return host_value_number(__pulse_fastly_out_value(out)) } if ((item.kind == PULSE_VALUE_STRING || item.kind == PULSE_VALUE_ARRAY) && key == "length") return host_value_number(item.kind == PULSE_VALUE_STRING ? item.textLength : item.values.length); return host_value_undefined() }
1868
1889
  function host_value_property_set(target: i32, key: i32, value: i32): i32 { host_value_object_set(target, key, value); return value }
1869
1890
  function host_value_element(target: i32, keyHandle: i32): i32 { const item = __pulse_fastly_value(target); if (item.kind == PULSE_VALUE_STRING) return __pulse_fastly_string_index(item, keyHandle); if (item.kind == PULSE_VALUE_ARRAY) { const index = i32(__pulse_fastly_number(keyHandle)); return index >= 0 && index < item.values.length ? unchecked(item.values[index]) : host_value_undefined() } return host_value_property(target, keyHandle) }
1870
1891
  function host_value_element_set(target: i32, keyHandle: i32, value: i32): i32 { const item = __pulse_fastly_value(target); if (item.immutable) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 35, -1); return 0 } if (item.kind == PULSE_VALUE_ARRAY) { const index = i32(__pulse_fastly_number(keyHandle)); if (index < 0) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 35, -1); return 0 } while (item.values.length <= index) item.values.push(host_value_undefined()); unchecked(item.values[index] = value); return value } return host_value_property_set(target, keyHandle, value) }
@@ -1920,8 +1941,8 @@ function host_grip_subscribe(responseHandle: i32, optionsHandle: i32): i32 { con
1920
1941
  function host_grip_handoff(optionsHandle: i32): i32 { const options = __pulse_fastly_value(optionsHandle); if (options.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 139, -1); return 0 } const output = new __PulseFastlyValue(); output.kind = PULSE_VALUE_RESPONSE; output.status = 200; const statusIndex = __pulse_fastly_find(options, "status"); if (statusIndex >= 0) output.status = i32(__pulse_fastly_number(unchecked(options.values[statusIndex]))); const bodyIndex = __pulse_fastly_find(options, "body"); if (bodyIndex >= 0) output.text = __pulse_fastly_string(unchecked(options.values[bodyIndex])); __pulse_fastly_headers_from_options(options, output); let hasContentType = false; for (let i = 0; i < output.headers.length; i += 1) if (unchecked(output.headers[i]).name.toLowerCase() == "content-type") hasContentType = true; if (!hasContentType) output.headers.push(new __PulseFastlyHeader("Content-Type", "application/websocket-events")); return __pulse_fastly_grip_frame(options, output) ? __pulse_fastly_put(output) : 0 }
1921
1942
  function host_kv_namespace(name: i32): i32 { return name }
1922
1943
  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)) }
1923
- 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 }
1924
- 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_header_lookup(response.responseHandle, __pulse_fastly_string(nameHandle), true); return value === null ? host_value_undefined() : __pulse_fastly_string_value(value) }
1944
+ 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) { ${options.fetchAccounting?.selection === 'canonical-fetch-cohorts' ? 'if (!__fca_json_type(response)) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA,44,-1); return 0; }' : `${options.fetchAccounting?.selection === 'canonical-single-get-json' ? 'if (!__fca_work()) return 0;' : ''} 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 }
1945
+ 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 = ${options.fetchAccounting?.selection === 'canonical-fetch-cohorts' ? '__fca_header(response, __pulse_fastly_string(nameHandle))' : '__pulse_header_lookup(response.responseHandle, __pulse_fastly_string(nameHandle), true)'}; return value === null ? host_value_undefined() : __pulse_fastly_string_value(value) }
1925
1946
  function __pulse_fastly_payload_field(payload: __PulseFastlyValue, name: string): i32 { const index = __pulse_fastly_find(payload, name); return index < 0 ? host_value_undefined() : unchecked(payload.values[index]) }
1926
1947
  function __pulse_fastly_ready_effect(effectIndex: i32, handle: i32): void { unchecked(__pulse_fastly_ready[effectIndex] = handle); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_READY) }
1927
1948
  function __pulse_fastly_open_config_store(effectIndex: i32): i32 { const nameBytes = __pulse_fastly_utf8(__pulse_fastly_config_store(effectIndex)); const out = __pulse_fastly_out_i32(); const status = fastly_config_store_open(changetype<usize>(nameBytes), nameBytes.byteLength, changetype<usize>(out)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 110, effectIndex); return 0 } return __pulse_fastly_out_value(out) }
@@ -1943,8 +1964,8 @@ function __pulse_fastly_jwt_capture_clock(effectIndex: i32): f64 { __pulse_fastl
1943
1964
  function __pulse_fastly_jwt_validate_schema(effectIndex: i32, schemaId: string, claimsHandle: i32): i32 { __pulse_fastly_jwt_schema_calls += 1; __pulse_fastly_jwt_event_order += "H"; if (__pulse_fastly_jwt_case_id() == "claims-schema-failure-after-registered-claims") return 0; return __pulse_fastly_schema_apply(schemaId, claimsHandle, false) }
1944
1965
  function __pulse_fastly_jwt_sign_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const result = pulse_jwt_fastly_sign(effectIndex, unchecked(__pulse_fastly_payloads[effectIndex])); if (result > 0 && __pulse_fastly_last_error == 0) __pulse_fastly_ready_effect(effectIndex, result) }
1945
1966
  function __pulse_fastly_jwt_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const result = pulse_jwt_fastly_verify(effectIndex, unchecked(__pulse_fastly_payloads[effectIndex])); if (result > 0 && __pulse_fastly_last_error == 0) __pulse_fastly_ready_effect(effectIndex, result) }
1946
- function __pulse_fastly_start_request(effectIndex: i32, url: string, method: string, headers: __PulseFastlyValue, body: string, backend: string): void { __pulse_transport_slots[effectIndex] = null; const requestOut = __pulse_fastly_out_i32(); let status = fastly_http_req_new(changetype<usize>(requestOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 70, effectIndex); return } const requestHandle = __pulse_fastly_out_value(requestOut); 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); 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 } 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 } 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_insert(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 } } } 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 } } 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_fetch_close_request(requestHandle); __pulse_fetch_close_body(bodyHandle); __pulse_transport_capture(effectIndex, status, 'send'); __pulse_fetch_fail(status, 77, effectIndex); return } unchecked(__pulse_fastly_pending[effectIndex] = __pulse_fastly_out_value(pendingOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_ASYNC) }
1947
- function __pulse_fastly_fetch_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const urlIndex = __pulse_fastly_find(payload, "url"); if (urlIndex < 0) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 48, effectIndex); return } const url = __pulse_fastly_string(unchecked(payload.values[urlIndex])); const initIndex = __pulse_fastly_find(payload, "init"); const init = initIndex >= 0 ? __pulse_fastly_value(unchecked(payload.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"); const jsonIndex = __pulse_fastly_find(init, "json"); 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) } else if (jsonIndex >= 0) { body = __pulse_fastly_fetch_json_body(init, unchecked(init.values[jsonIndex])); if (__pulse_fastly_last_error != 0) return; headers = __pulse_fastly_fetch_json_headers(headers) } } __pulse_fastly_start_request(effectIndex, url, method, headers, body, __pulse_fastly_backend(effectIndex)) }
1967
+ function __pulse_fastly_start_request(effectIndex: i32, url: string, method: string, headers: __PulseFastlyValue, body: string, backend: string): void { __pulse_transport_slots[effectIndex] = null; const requestOut = __pulse_fastly_out_i32(); let status = ${options.fetchAccounting ? '__fca_new(0,' : 'fastly_http_req_new('}changetype<usize>(requestOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 70, effectIndex); return } const requestHandle = __pulse_fastly_out_value(requestOut); const bodyOut = __pulse_fastly_out_i32(); status = ${options.fetchAccounting ? '__fca_new(1,' : '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); const methodBytes = __pulse_fastly_utf8(method); ${options.fetchAccounting ? 'if (!__fca_work()) return;' : ''} 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 } const urlBytes = __pulse_fastly_utf8(url); ${options.fetchAccounting ? 'if (!__fca_work()) return;' : ''} 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 } 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]))); ${options.fetchAccounting ? 'if (!__fca_work()) return;' : ''} 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 } } } if (body.length > 0) { status = ${options.fetchAccounting?.selection === 'canonical-fetch-cohorts' ? '__fca_write' : '__pulse_fastly_write_body'}(bodyHandle, body); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 75, effectIndex); return } } 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 = ${options.fetchAccounting ? '__fca_dispatch' : 'fastly_http_req_send_async'}(requestHandle, bodyHandle, changetype<usize>(backendBytes), backendBytes.byteLength, changetype<usize>(pendingOut)); if (status != FASTLY_STATUS_OK) { ${options.fetchAccounting ? '__fca_cleanup();' : '__pulse_fetch_close_request(requestHandle); __pulse_fetch_close_body(bodyHandle);'} __pulse_transport_capture(effectIndex, status, 'send'); __pulse_fetch_fail(status, 77, effectIndex); return } unchecked(__pulse_fastly_pending[effectIndex] = __pulse_fastly_out_value(pendingOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_ASYNC) }
1968
+ function __pulse_fastly_fetch_begin(effectIndex: i32, payload: __PulseFastlyValue): void { ${options.fetchAccounting ? options.fetchAccounting.selection === 'canonical-fetch-cohorts' ? 'if (!__fca_start(effectIndex)) return;' : 'if (!__fca_start()) return;' : ''} const urlIndex = __pulse_fastly_find(payload, "url"); if (urlIndex < 0) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 48, effectIndex); return } const url = __pulse_fastly_string(unchecked(payload.values[urlIndex])); const initIndex = __pulse_fastly_find(payload, "init"); const init = initIndex >= 0 ? __pulse_fastly_value(unchecked(payload.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"); const jsonIndex = __pulse_fastly_find(init, "json"); 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) } else if (jsonIndex >= 0) { body = __pulse_fastly_fetch_json_body(init, unchecked(init.values[jsonIndex])); if (__pulse_fastly_last_error != 0) return; headers = __pulse_fastly_fetch_json_headers(headers) } } __pulse_fastly_start_request(effectIndex, url, method, headers, body, __pulse_fastly_backend(effectIndex)) }
1948
1969
  function __pulse_fastly_kv_get_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const store = __pulse_fastly_open_kv_store(effectIndex); if (__pulse_fastly_last_error != 0) return; const key = __pulse_fastly_string(__pulse_fastly_payload_field(payload, "key")); const keyBytes = __pulse_fastly_utf8(key); const lookupOut = __pulse_fastly_out_i32(); const config = new StaticArray<i32>(1); const status = fastly_kv_store_lookup(store, changetype<usize>(keyBytes), keyBytes.byteLength, 0, changetype<usize>(config), changetype<usize>(lookupOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 116, effectIndex); return } unchecked(__pulse_fastly_pending[effectIndex] = __pulse_fastly_out_value(lookupOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_ASYNC) }
1949
1970
  ${assetBeginSource}
1950
1971
  function __pulse_fastly_kv_put_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const store = __pulse_fastly_open_kv_store(effectIndex); if (__pulse_fastly_last_error != 0) return; const key = __pulse_fastly_string(__pulse_fastly_payload_field(payload, "key")); const value = __pulse_fastly_payload_field(payload, "value"); const bodyOut = __pulse_fastly_out_i32(); let status = fastly_http_body_new(changetype<usize>(bodyOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 117, effectIndex); return } const body = __pulse_fastly_out_value(bodyOut); status = __pulse_fastly_write_body(body, "{\"__pulseKv\":1,\"value\":" + __pulse_fastly_json(value, 0) + "}"); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 118, effectIndex); return } const keyBytes = __pulse_fastly_utf8(key); const insertOut = __pulse_fastly_out_i32(); const config = new StaticArray<i64>(4); status = fastly_kv_store_insert(store, changetype<usize>(keyBytes), keyBytes.byteLength, body, 0, changetype<usize>(config), changetype<usize>(insertOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 119, effectIndex); return } unchecked(__pulse_fastly_pending[effectIndex] = __pulse_fastly_out_value(insertOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_ASYNC) }
@@ -1963,7 +1984,7 @@ function __pulse_fastly_wait_kv_get(effectIndex: i32): i32 { if (unchecked(__pul
1963
1984
  ${assetResultSource}
1964
1985
  function __pulse_fastly_wait_kv_put(effectIndex: i32): i32 { if (unchecked(__pulse_fastly_pending_mode[effectIndex]) != PULSE_FASTLY_PENDING_ASYNC) { __pulse_fastly_fail(PULSE_ERROR_STATE, 126, effectIndex); return 0 } const pending = unchecked(__pulse_fastly_pending[effectIndex]); const kvError = __pulse_fastly_out_i32(); const status = fastly_kv_store_insert_wait(pending, changetype<usize>(kvError)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_NONE); unchecked(__pulse_fastly_pending[effectIndex] = 0); if (status != FASTLY_STATUS_OK || __pulse_fastly_out_value(kvError) != 1) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 127, effectIndex); return 0 } return host_value_boolean(1) }
1965
1986
  function __pulse_fastly_write_body(bodyHandle: i32, value: string): i32 { const bytes = __pulse_fastly_utf8(value); let offset = 0; while (offset < bytes.byteLength) { const written = __pulse_fastly_out_i32(); const status = fastly_http_body_write(bodyHandle, changetype<usize>(bytes) + offset, bytes.byteLength - offset, 0, changetype<usize>(written)); if (status != FASTLY_STATUS_OK) return status; const count = __pulse_fastly_out_value(written); if (count <= 0) return 1; offset += count } return FASTLY_STATUS_OK }
1966
- function __pulse_fastly_send_result(handle: i32): i32 { const value = __pulse_fastly_value(handle); ${bindings.s3.length ? 'if (value.s3Body) return __s3_send_body(value);' : ''} if (value.kind == PULSE_VALUE_FETCH) return fastly_http_resp_send_downstream(value.responseHandle, value.bodyHandle, 0); if (value.kind != PULSE_VALUE_RESPONSE) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 90, -1); return PULSE_ERROR_VALUE } const responseOut = __pulse_fastly_out_i32(); let status = fastly_http_resp_new(changetype<usize>(responseOut)); if (status != FASTLY_STATUS_OK) return status; const responseHandle = __pulse_fastly_out_value(responseOut); const bodyOut = __pulse_fastly_out_i32(); status = fastly_http_body_new(changetype<usize>(bodyOut)); if (status != FASTLY_STATUS_OK) return status; const bodyHandle = __pulse_fastly_out_value(bodyOut); status = fastly_http_resp_status_set(responseHandle, value.status); if (status != FASTLY_STATUS_OK) return status; for (let i = 0; i < value.headers.length; i += 1) { const header = unchecked(value.headers[i]); const nameBytes = __pulse_fastly_utf8(header.name); const valueBytes = __pulse_fastly_utf8(header.value); status = fastly_http_resp_header_append(responseHandle, changetype<usize>(nameBytes), nameBytes.byteLength, changetype<usize>(valueBytes), valueBytes.byteLength); if (status != FASTLY_STATUS_OK) return status } status = __pulse_fastly_write_body(bodyHandle, value.text); if (status != FASTLY_STATUS_OK) return status; return fastly_http_resp_send_downstream(responseHandle, bodyHandle, 0) }
1987
+ ${lifecycle?.active ? `function __pulse_fastly_send_result(handle: i32): i32 {\n${lifecycle.site('response-send')}\n return __pulse_fastly_send_result_impl(handle)\n}\n` : ''}function ${lifecycle?.legacyName('__pulse_fastly_send_result') || '__pulse_fastly_send_result'}(handle: i32): i32 { const value = __pulse_fastly_value(handle); ${bindings.s3.length ? 'if (value.s3Body) return __s3_send_body(value);' : ''} if (value.kind == PULSE_VALUE_FETCH) return ${options.fetchAccounting?.selection === 'canonical-fetch-cohorts' ? '__fca_transfer(value.responseHandle, value.bodyHandle)' : 'fastly_http_resp_send_downstream(value.responseHandle, value.bodyHandle, 0)'}; if (value.kind != PULSE_VALUE_RESPONSE) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 90, -1); return PULSE_ERROR_VALUE } const responseOut = __pulse_fastly_out_i32(); let status = fastly_http_resp_new(changetype<usize>(responseOut)); if (status != FASTLY_STATUS_OK) return status; const responseHandle = __pulse_fastly_out_value(responseOut); const bodyOut = __pulse_fastly_out_i32(); status = fastly_http_body_new(changetype<usize>(bodyOut)); if (status != FASTLY_STATUS_OK) return status; const bodyHandle = __pulse_fastly_out_value(bodyOut); status = fastly_http_resp_status_set(responseHandle, value.status); if (status != FASTLY_STATUS_OK) return status; for (let i = 0; i < value.headers.length; i += 1) { const header = unchecked(value.headers[i]); const nameBytes = __pulse_fastly_utf8(header.name); const valueBytes = __pulse_fastly_utf8(header.value); status = fastly_http_resp_header_append(responseHandle, changetype<usize>(nameBytes), nameBytes.byteLength, changetype<usize>(valueBytes), valueBytes.byteLength); if (status != FASTLY_STATUS_OK) return status } status = __pulse_fastly_write_body(bodyHandle, value.text); if (status != FASTLY_STATUS_OK) return status; return fastly_http_resp_send_downstream(responseHandle, bodyHandle, 0) }
1967
1988
 
1968
1989
  ${jwtEvidenceSource}
1969
1990
  ${terminalErrorSource}
@@ -1980,6 +2001,7 @@ ${effectResultSource(plan, bindings)}
1980
2001
  }
1981
2002
 
1982
2003
  function driverSource(plan, options = {}) {
2004
+ const lifecycle = options.lifecycle;
1983
2005
  const entryName = options.guestLinked === true
1984
2006
  ? 'pulse_fastly_handle'
1985
2007
  : '_start';
@@ -1996,18 +2018,18 @@ export function ${entryName}(): void {
1996
2018
  if (__pulse_invocation_started) { __pulse_fastly_fail(PULSE_ERROR_STATE, 171, -1); return }
1997
2019
  __pulse_invocation_started = true
1998
2020
  __pulse_fastly_run_invocation()
1999
- ${plan.effects.some(e => e.kind === 's3.getBody') ? '__s3_close_bodies()' : ''}
2021
+ ${lifecycle?.site('invocation-end') ? lifecycle.site('invocation-end') + '\n' : ''} ${plan.effects.some(e => e.kind === 's3.getBody') ? '__s3_close_bodies()' : ''}
2000
2022
  __pulse_invocation_close()
2001
2023
  }
2002
2024
  function __pulse_fastly_run_invocation(): void {
2003
2025
  __pulse_fastly_last_error = fastly_abi_init(PULSE_FASTLY_EFFECTS_ABI_VERSION)
2004
2026
  if (__pulse_fastly_last_error != FASTLY_STATUS_OK) { __pulse_fastly_jwt_send_error(); return }
2005
2027
  const handles = new StaticArray<i32>(2)
2006
- const downstreamStatus = fastly_http_req_body_downstream_get(changetype<usize>(handles), changetype<usize>(handles) + 4)
2028
+ const downstreamStatus = ${options.resourceAccounting ? '__fra_incoming' : 'fastly_http_req_body_downstream_get'}(changetype<usize>(handles), changetype<usize>(handles) + 4)
2007
2029
  if (downstreamStatus != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 100, -1); __pulse_fastly_jwt_send_error(); return }
2008
2030
  __pulse_fastly_request_handle = load<i32>(changetype<usize>(handles))
2009
2031
  __pulse_fastly_request_body_handle = load<i32>(changetype<usize>(handles) + 4)
2010
- __pulse_fastly_request_method = __pulse_fastly_read_req_string(0)
2032
+ ${lifecycle?.site('request-acquired') ? lifecycle.site('request-acquired') + '\n' : ''} __pulse_fastly_request_method = __pulse_fastly_read_req_string(0)
2011
2033
  if (__pulse_fastly_last_error != 0) { __pulse_fastly_jwt_send_error(); return }
2012
2034
  __pulse_fastly_request_url = __pulse_fastly_read_req_string(1)
2013
2035
  if (__pulse_fastly_last_error != 0) { __pulse_fastly_jwt_send_error(); return }
@@ -2027,7 +2049,7 @@ ${packageApplication.enabled(plan) ? packageApplication.driverLoop() : applicati
2027
2049
  if (runStatus != 0) { __pulse_fastly_fail(PULSE_ERROR_STATE, 102, -1); __pulse_fastly_jwt_send_error(); return }
2028
2050
  const result = pulse_result_handle()
2029
2051
  if (result <= 0) { __pulse_fastly_fail(PULSE_ERROR_STATE, 103, -1); __pulse_fastly_jwt_send_error(); return }
2030
- __pulse_fastly_jwt_attach_evidence(result)
2052
+ ${lifecycle?.site('result-claim') ? lifecycle.site('result-claim') + '\n' : ''} __pulse_fastly_jwt_attach_evidence(result)
2031
2053
  const sendStatus = __pulse_fastly_send_result(result)
2032
2054
  if (sendStatus != FASTLY_STATUS_OK) __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 104, -1)
2033
2055
  }
@@ -2096,13 +2118,22 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
2096
2118
  const packageDuration = plan.packages?.application?.requestLimits?.maxDurationMs;
2097
2119
  if (packageDuration !== undefined) options = { ...options, maxDurationMs: Math.min(options.maxDurationMs ?? packageDuration, packageDuration) };
2098
2120
  const bindings = resolveCapabilityBindings(plan, options);
2121
+ const selectedExtensions = hostExtensions.select(plan, options);
2122
+ const extensionPolicy = selectedExtensions.length ? hostExtensions.policy(selectedExtensions,
2123
+ (options.targetDescriptor?.finalWasmPolicy?.permittedImports || FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORTS.map(([module, name]) => ({ module, name, kind: 'function' })))) : undefined;
2124
+ const forwardingAccounting = require('./native-forwarding-accounting.js').profile(plan, bindings);
2125
+ const fetchAccounting = require('./native-fetch-accounting.js').profile(plan, bindings, FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES)
2126
+ || require('./native-fetch-cohorts.js').profile(plan, bindings, FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES);
2127
+ const resourceAccounting = forwardingAccounting || fetchAccounting;
2128
+ // Trusted provider-internal services, never a project/package hook API.
2129
+ const lifecycle = lifecycleBindings.create(plan, bindings, [...require('./native-fetch-accounting.js').hooks(fetchAccounting), ...hostExtensions.hooks(selectedExtensions), ...(options.lifecycleServices || [])]);
2099
2130
  // Keep final provider eligibility authoritative over the portable artifact
2100
2131
  // and caller options. Only the selected crypto guest uses the linked policy;
2101
2132
  // that policy excludes active codecs regardless of JSON arena configuration.
2102
2133
  const numericRegions = facts.guestUnits.length === 0 ? 'fastly-numeric-regions-v2'
2103
2134
  : jwtCrypto && jwtCrypto.guestUnitRequired ? 'fastly-linked-numeric-regions-v1' : undefined;
2104
2135
  const portable = packageApplication.enabled(plan) ? packageApplication.portable(plan) : generateCanonicalNativeAssemblyScript(plan, {
2105
- ...options, numericRegions,
2136
+ ...options, numericRegions, lifecycle,
2106
2137
  handleOwnership: facts.guestUnits.length === 0 ? 'fastly-handle-tracing-v1' : undefined
2107
2138
  });
2108
2139
  nativeHandleOwnership.validateRoots(plan, portable.manifest);
@@ -2115,12 +2146,13 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
2115
2146
  'export function __pulse_fastly_abort(message: string | null, fileName: string | null = null, line: u32 = 0, column: u32 = 0): void { unreachable() }',
2116
2147
  '',
2117
2148
  fastlyRuntimeSource(plan, bindings, {
2118
- ...options,
2149
+ ...options, lifecycle, fetchAccounting,
2119
2150
  handleTracing: portable.manifest.handleOwnership?.enabled === true,
2120
2151
  jwtCrypto,
2121
2152
  es256KeyRecords
2122
2153
  }),
2123
2154
  jwtSource ? jwtSource.source : '',
2155
+ ...(selectedExtensions.length ? [hostExtensions.source(selectedExtensions)] : []),
2124
2156
  ((plan.effects || []).some(effect => ['fetch', 'grip.publish', 'grip.broadcast'].includes(effect.kind)) || bindings.s3.length > 0)
2125
2157
  ? fs.readFileSync(path.join(__dirname, 'native-send-error-detail.as.ts'), 'utf8') : '',
2126
2158
  require('./s3-native.js').s3NativeSource(plan, bindings),
@@ -2133,29 +2165,39 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
2133
2165
  require('./native-header-lookup.js').runtimeSource(),
2134
2166
  fs.readFileSync(path.join(__dirname, 'native-transport-details.as.ts'), 'utf8'),
2135
2167
  require('./native-fetch-errors.js').runtimeSource(dynamicBackends.active(bindings)),
2136
- require('./native-fetch-deadlines.js').runtimeSource(plan),
2168
+ require('./native-fetch-deadlines.js').runtimeSource(plan, fetchAccounting),
2137
2169
  applicationErrors.settlementEnabled(plan) ? applicationErrors.runtimeSource(plan) : '',
2138
- effectInvocations.runtimeSource({ packageCompletion: packageApplication.enabled(plan) }),
2170
+ effectInvocations.runtimeSource({ packageCompletion: packageApplication.enabled(plan), lifecycle }),
2139
2171
  ...(portable.manifest.numericRegions?.accounting === 'fastly-read-loop-v1' && portable.manifest.numericRegions.regions.length > 0
2140
2172
  ? [nativeValueBudget.numericRegionSource()] : []),
2141
2173
  portableSource,
2142
2174
  nativeHandleOwnership.providerSource(plan, portable.manifest.handleOwnership?.enabled === true),
2143
- driverSource(plan, { guestLinked: facts.guestUnits.length > 0 }),
2175
+ driverSource(plan, { guestLinked: facts.guestUnits.length > 0, lifecycle, resourceAccounting: forwardingAccounting }),
2176
+ ...(forwardingAccounting ? [require('./native-forwarding-accounting.js').source(forwardingAccounting)] : []),
2177
+ ...(fetchAccounting ? [require(fetchAccounting.selection === 'canonical-fetch-cohorts' ? './native-fetch-cohorts.js' : './native-fetch-accounting.js').source(fetchAccounting)] : []),
2144
2178
  ''
2145
2179
  ].join('\n');
2146
2180
  if (applicationErrors.settlementEnabled(plan)) source = applicationErrors.instrument(source);
2147
2181
  source = require('./native-request-headers.js').instrument(source, applicationErrors.enabled(plan), /\bhost_request_headers\(/.test(portableSource));
2148
- source = require('./native-header-lookup.js').instrument(source);
2149
- source = require('./native-fetch-errors.js').instrument(source);
2150
- source = require('./native-fetch-deadlines.js').instrument(source, plan);
2151
- source = require('./native-request-body.js').instrument(source, applicationErrors.enabled(plan));
2182
+ source = require('./native-header-lookup.js').instrument(source, lifecycle);
2183
+ source = require('./native-fetch-errors.js').instrument(source, lifecycle);
2184
+ source = require('./native-fetch-deadlines.js').instrument(source, plan, fetchAccounting);
2185
+ source = require('./native-request-body.js').instrument(source, applicationErrors.enabled(plan), lifecycle);
2152
2186
  if (packageApplication.enabled(plan)) source = packageApplication.instrument(source);
2153
2187
  source = effectInvocations.instrument(source);
2154
- source = require('./request-budget.js').instrumentRequestBudget(source, bindings.maxDurationMs, plan);
2188
+ source = require('./request-budget.js').instrumentRequestBudget(source, bindings.maxDurationMs, plan, lifecycle, fetchAccounting);
2155
2189
  source = packageApplication.terminalFailure(source, plan);
2156
2190
  source = nativeValueBudget.instrument(source, plan);
2157
2191
  source = require('./native-body-forwarding.js').instrument(source, plan, bindings);
2158
2192
  source = dynamicBackends.instrument(source, bindings, true, (options.bindings || options).backends);
2193
+ // Forwarding owns a specialized terminal fetch implementation. Admission
2194
+ // above rejects outcome/timing options; generic fetch anchors do not exist here.
2195
+ if (!require('./native-body-forwarding.js').enabled(plan)) {
2196
+ source = require('./native-fetch-outcomes.js').instrument(source, plan);
2197
+ source = require('./native-fetch-timing.js').instrument(source, plan);
2198
+ }
2199
+ lifecycle.assertSites();
2200
+ if (lifecycle.supportSource) source += '\n' + lifecycle.supportSource;
2159
2201
  const effectKinds = Object.freeze((plan.effects || []).reduce((output, effect) => {
2160
2202
  output[effect.kind] = (output[effect.kind] || 0) + 1;
2161
2203
  return output;
@@ -2170,6 +2212,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
2170
2212
  imports: Object.freeze([...capabilityImports(effect), ...(bindings.backends.some(b => b.index === index && b.dynamic) ? ['fastly_http_req.register_dynamic_backend'] : [])])
2171
2213
  })));
2172
2214
  const manifest = Object.freeze({
2215
+ ...(resourceAccounting ? { resourceAccounting } : {}),
2173
2216
  version: FASTLY_NATIVE_PLATFORM_CAPABILITIES_VERSION,
2174
2217
  generatorVersion: FASTLY_NATIVE_PLATFORM_CAPABILITIES_GENERATOR_VERSION,
2175
2218
  abiVersion: FASTLY_NATIVE_PLATFORM_CAPABILITIES_ABI_VERSION,
@@ -2215,6 +2258,8 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
2215
2258
  : Object.freeze({ status: 'inactive' }),
2216
2259
  effectCount: (plan.effects || []).length,
2217
2260
  effectInvocations: effectInvocations.policy,
2261
+ ...(lifecycle.active ? { lifecycle: lifecycle.plan } : {}),
2262
+ ...(selectedExtensions.length ? { hostExtensions: hostExtensions.evidence(selectedExtensions, lifecycle, extensionPolicy) } : {}),
2218
2263
  effectKinds,
2219
2264
  continuationCount: (plan.continuations || []).length,
2220
2265
  groupedContinuationCount: (plan.continuations || []).filter((entry) => (entry.effectIds || []).length > 1).length,
@@ -2293,7 +2338,9 @@ function inspectFastlyNativePlatformCapabilitiesWasm(input, options = {}) {
2293
2338
  const module = new WebAssembly.Module(bytes);
2294
2339
  const imports = WebAssembly.Module.imports(module).map((entry) => Object.freeze({ module: entry.module, name: entry.name, kind: entry.kind }));
2295
2340
  const exports = WebAssembly.Module.exports(module).map((entry) => Object.freeze({ name: entry.name, kind: entry.kind }));
2296
- const unexpected = imports.filter((entry) => !FASTLY_NATIVE_PLATFORM_CAPABILITIES_ALLOWED_IMPORTS.has(`${entry.module}:${entry.name}`));
2341
+ const permitted = options.extensionPolicy ? new Set(options.extensionPolicy.permittedImports.map(entry => `${entry.module}:${entry.name}:${entry.kind}`)) : null;
2342
+ if (options.extensionPolicy) require('@pulse-compute/wasm-contracts/provider/toolchain').hostExtensions.auditWasm(bytes, options.extensionPolicy);
2343
+ const unexpected = imports.filter((entry) => permitted ? !permitted.has(`${entry.module}:${entry.name}:${entry.kind}`) : entry.kind !== 'function' || !FASTLY_NATIVE_PLATFORM_CAPABILITIES_ALLOWED_IMPORTS.has(`${entry.module}:${entry.name}`));
2297
2344
  if (unexpected.length > 0) fail('Generated Fastly native platform capabilities module imports functions outside the Pass98 ABI.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORT_UNSUPPORTED', { unexpected, imports });
2298
2345
  const forbidden = imports.filter((entry) => /pulse_host|js[_-]?compute/i.test(`${entry.module}:${entry.name}`));
2299
2346
  if (forbidden.length > 0) fail('Generated Fastly native platform capabilities module imports a forbidden runtime.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_RUNTIME_FORBIDDEN', { forbidden });
@@ -2549,13 +2596,16 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2549
2596
  fs.writeFileSync(wasmFile, wasm);
2550
2597
  if (emitWat) fs.writeFileSync(watFile, wat, 'utf8');
2551
2598
  }
2552
- const inspection = inspectFastlyNativePlatformCapabilitiesWasm(wasm, { maxWasmBytes });
2599
+ const inspection = inspectFastlyNativePlatformCapabilitiesWasm(wasm, { maxWasmBytes, extensionPolicy: generated.manifest.hostExtensions?.importPolicy });
2553
2600
  const imported = new Set(inspection.imports.map((entry) => `${entry.module}:${entry.name}`));
2554
2601
  const requiredImports = requiredImportsForPlan(plan, generated.bindings);
2555
2602
  const missingRequiredImports = requiredImports.filter((key) => !imported.has(key));
2556
2603
  if (missingRequiredImports.length > 0) fail('Generated Fastly native platform capabilities module is missing plan-required host imports.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORT_MISSING', { missingRequiredImports, imports: inspection.imports, requiredImports });
2557
2604
  const manifest = Object.freeze({
2558
2605
  ...generated.manifest,
2606
+ ...(generated.manifest.hostExtensions ? { hostExtensions: Object.freeze({ ...generated.manifest.hostExtensions,
2607
+ finalWasmSha256: inspection.sha256,
2608
+ observedImports: require('@pulse-compute/wasm-contracts/provider/toolchain').hostExtensions.auditWasm(wasm, generated.manifest.hostExtensions.importPolicy).observedImports }) } : {}),
2559
2609
  compilerVersion: FASTLY_NATIVE_PLATFORM_CAPABILITIES_COMPILER_VERSION,
2560
2610
  assemblyScript: Object.freeze({ package: 'assemblyscript', version: require(path.join(asc.packageRoot, 'package.json')).version }),
2561
2611
  jsonAs: jsonAs
@@ -4,15 +4,16 @@ function runtimeSource() {
4
4
  return `
5
5
  let __pulse_request_body_failure: i32 = 0
6
6
  // Validate the bounded, assembled request bytes before any text projection.
7
- function __pulse_request_utf8_valid(bytes: Uint8Array): bool {
8
- for (let i = 0; i < bytes.length;) {
7
+ function __pulse_request_utf8_valid(bytes: Uint8Array, length: i32 = bytes.length): bool {
8
+ if (length < 0 || length > bytes.length) return false;
9
+ for (let i = 0; i < length;) {
9
10
  const lead = bytes[i++]; if (lead < 128) continue;
10
11
  let count = 0, minimum = 128, maximum = 191;
11
12
  if (lead >= 194 && lead <= 223) count = 1;
12
13
  else if (lead >= 224 && lead <= 239) { count = 2; if (lead == 224) minimum = 160; if (lead == 237) maximum = 159; }
13
14
  else if (lead >= 240 && lead <= 244) { count = 3; if (lead == 240) minimum = 144; if (lead == 244) maximum = 143; }
14
15
  else return false;
15
- if (i + count > bytes.length || i32(bytes[i]) < minimum || i32(bytes[i]) > maximum) return false;
16
+ if (i + count > length || i32(bytes[i]) < minimum || i32(bytes[i]) > maximum) return false;
16
17
  i++; for (let n = 1; n < count; n++, i++) if (bytes[i] < 128 || bytes[i] > 191) return false;
17
18
  }
18
19
  return true;
@@ -31,12 +32,12 @@ function __pulse_request_send_body_error(): bool {
31
32
  `;
32
33
  }
33
34
 
34
- function instrument(source, applicationErrors) {
35
+ function instrument(source, applicationErrors, lifecycle) {
35
36
  if (!applicationErrors) source = source.replace(/function (host_\w+)\(([^)]*)\): (i32|void) \{/g,
36
37
  (declaration, _name, _args, result) => `${declaration} if (__pulse_fastly_last_error == PULSE_ERROR_REQUEST_BODY) return${result === 'void' ? '' : ' 0'};`);
37
38
  // The existing terminal failure path also covers applications without an
38
39
  // error handler. Request-budget instrumentation subsequently takes priority.
39
- const marker = 'function __pulse_fastly_jwt_send_error(): void {';
40
+ const marker = `function ${lifecycle?.legacyName('__pulse_fastly_jwt_send_error') || '__pulse_fastly_jwt_send_error'}(): void {`;
40
41
  if (!source.includes(marker)) throw new Error('Fastly request-body error boundary drift.');
41
42
  return source.replace(marker, marker + ' if (__pulse_request_send_body_error()) return;');
42
43
  }
@@ -1,4 +1,8 @@
1
- // Provider-owned request lifecycle. No wall time or caller header grants time.
1
+ 'use strict';
2
+
3
+ function runtimeSource(lifecycle, forwarding = false, fetchAccounting = false) {
4
+ const name = lifecycle?.legacyName('__request_send_timeout') || '__request_send_timeout';
5
+ return `// Provider-owned request lifecycle. No wall time or caller header grants time.
2
6
  @external('fastly_async_io', 'select') declare function __request_select(handles: usize, count: i32, timeout: i32, done: usize): i32
3
7
  let __request_deadline: i64 = -1;
4
8
  let __request_last_clock: i64 = -1;
@@ -27,18 +31,19 @@ function __request_check(index: i32 = -1): bool {
27
31
  __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 260, index);
28
32
  return false;
29
33
  }
34
+ ${fetchAccounting ? 'const __request_wait_handles = new StaticArray<i32>(1), __request_wait_ready = new StaticArray<i32>(1);' : ''}
30
35
  function __request_wait(handle: i32, index: i32): bool {
31
36
  const remaining = __request_remaining();
32
37
  if (remaining <= 0) return __request_check(index);
33
- const handles = new StaticArray<i32>(1), ready = new StaticArray<i32>(1);
38
+ const handles = ${fetchAccounting ? '__request_wait_handles' : 'new StaticArray<i32>(1)'}, ready = ${fetchAccounting ? '__request_wait_ready' : 'new StaticArray<i32>(1)'};
34
39
  handles[0] = handle;
35
40
  const status = __request_select(changetype<usize>(handles), 1, remaining, changetype<usize>(ready));
36
41
  if (status != 0) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 261, index); return false; }
37
42
  if (ready[0] == -1) __request_expired = true;
38
43
  return __request_check(index);
39
44
  }
40
- function __request_send_timeout(): void {
41
- // A timeout says nothing about remote acceptance of dispatched writes.
45
+ ${lifecycle?.active ? `function __request_send_timeout(): void {\n${lifecycle.site('response-timeout')}\n ${name}()\n}\n` : ''}function ${name}(): void {
46
+ ${fetchAccounting ? ' if (__fca_error()) return;\n' : ''}${forwarding ? ' if (__fwd_ra !== null) { __fwd_fail(504); __fwd_error_response(); return; }\n' : ''} // A timeout says nothing about remote acceptance of dispatched writes.
42
47
  __request_error_response = true;
43
48
  __pulse_fastly_last_error = 0;
44
49
  const options = host_value_object();
@@ -48,3 +53,6 @@ function __request_send_timeout(): void {
48
53
  const status = __pulse_fastly_send_result(response);
49
54
  if (status != 0) __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 262, -1);
50
55
  }
56
+ `;
57
+ }
58
+ module.exports = { runtimeSource };
@@ -1,9 +1,7 @@
1
1
  'use strict';
2
- const fs = require('node:fs');
3
- const path = require('node:path');
4
2
  const {normalizeRequestDuration} = require('@pulse-compute/runtime/host');
5
3
 
6
- function instrumentRequestBudget(input, duration, plan) {
4
+ function instrumentRequestBudget(input, duration, plan, lifecycle, fetchAccounting) {
7
5
  const ms = normalizeRequestDuration(duration);
8
6
  if (ms === undefined) return input;
9
7
  let source = input;
@@ -26,14 +24,15 @@ function instrumentRequestBudget(input, duration, plan) {
26
24
  if (unchecked(__pulse_fastly_pending_mode[effectIndex]) == PULSE_FASTLY_PENDING_ASYNC && !(kind == 1 && __pulse_fetch_has_deadlines()) && kind != 12 && kind != 13 && kind != 14 && kind != 21 && ${plan.effects.filter(e=>e.kind.startsWith('kv.')&&['kv.getVersioned','kv.insertIfAbsent','kv.compareAndSwap','kv.list','kv.delete'].includes(e.kind)).map(e=>'effectIndex != '+plan.effects.indexOf(e)).join(' && ')||'true'}) {
27
25
  if (!__request_wait(unchecked(__pulse_fastly_pending[effectIndex]), effectIndex)) return 0;
28
26
  }`);
29
- replace(' const buffer = new Uint8Array(PULSE_FASTLY_BUFFER_BYTES), read = __pulse_fastly_out_i32();', ' if (!__request_wait(handle, effectIndex)) return "";\n const buffer = new Uint8Array(PULSE_FASTLY_BUFFER_BYTES), read = __pulse_fastly_out_i32();');
27
+ replace('function __pulse_fastly_body_read_ready(handle: i32, effectIndex: i32): bool { return true }', 'function __pulse_fastly_body_read_ready(handle: i32, effectIndex: i32): bool { return __request_wait(handle, effectIndex) }');
30
28
  replace('while (offset < bytes.byteLength) { const written', 'while (offset < bytes.byteLength) { if (!__request_check()) return 1; const written');
31
- replace('function __pulse_fastly_send_result(handle: i32): i32 {', 'function __pulse_fastly_send_result(handle: i32): i32 { if (!__request_check()) return 1;');
29
+ replace(`function ${lifecycle?.legacyName('__pulse_fastly_send_result') || '__pulse_fastly_send_result'}(handle: i32): i32 {`, `function ${lifecycle?.legacyName('__pulse_fastly_send_result') || '__pulse_fastly_send_result'}(handle: i32): i32 { if (!__request_check()) return 1;`);
32
30
  replace('return fastly_http_resp_send_downstream(responseHandle, bodyHandle, 0)', 'if (!__request_check()) return 1; return fastly_http_resp_send_downstream(responseHandle, bodyHandle, 0)');
33
31
  replace('if (sendStatus != FASTLY_STATUS_OK) __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 104, -1)', 'if (sendStatus != FASTLY_STATUS_OK) { if (__request_expired) { __request_send_timeout(); return; } __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 104, -1); }');
34
- replace('status = fastly_http_req_send_async(requestHandle', 'if (!__request_check(effectIndex)) return; status = fastly_http_req_send_async(requestHandle');
32
+ const send = fetchAccounting ? '__fca_dispatch' : 'fastly_http_req_send_async';
33
+ replace('status = ' + send + '(requestHandle', 'if (!__request_check(effectIndex)) return; status = ' + send + '(requestHandle');
35
34
  replace('function __pulse_fastly_ready_effect(effectIndex: i32, handle: i32): void {', 'function __pulse_fastly_ready_effect(effectIndex: i32, handle: i32): void { if (!__request_check(effectIndex)) return;');
36
- replace('function __pulse_fastly_jwt_send_error(): void {', 'function __pulse_fastly_jwt_send_error(): void { if (__request_expired || !__request_check()) { __request_send_timeout(); return; }');
35
+ replace(`function ${lifecycle?.legacyName('__pulse_fastly_jwt_send_error') || '__pulse_fastly_jwt_send_error'}(): void {`, `function ${lifecycle?.legacyName('__pulse_fastly_jwt_send_error') || '__pulse_fastly_jwt_send_error'}(): void { if (__request_expired || !__request_check()) { __request_send_timeout(); return; }`);
37
36
  source = source.replaceAll('runStatus = pulse_resume()', 'if (!__request_check()) { __request_send_timeout(); return; }\n runStatus = pulse_resume()');
38
37
  replace(' const result = pulse_result_handle()', ' if (!__request_check()) { __request_send_timeout(); return; }\n const result = pulse_result_handle()');
39
38
  if (source.includes('__kv_deadline[index] = start +')) replace('__kv_deadline[index] = start + i64(__KV_timeoutMs) * 1000000;', '__kv_deadline[index] = min<i64>(__request_deadline, start + i64(__KV_timeoutMs) * 1000000);');
@@ -42,6 +41,6 @@ function instrumentRequestBudget(input, duration, plan) {
42
41
  replace('function __pulse_fetch_request_check(index: i32): bool { return true }', 'function __pulse_fetch_request_check(index: i32): bool { return __request_check(index) }');
43
42
  replace('function __pulse_fetch_request_remaining(): i32 { return 2147483647 }', 'function __pulse_fetch_request_remaining(): i32 { return __request_remaining() }');
44
43
  if (plan.callables?.length || plan.binaryResources || plan.numericWorkspaces?.length) replace('function __pulse_resource_request_check(): bool { return true }', 'function __pulse_resource_request_check(): bool { return __request_check() }');
45
- return source + '\n' + fs.readFileSync(path.join(__dirname,'request-budget.as.ts'),'utf8');
44
+ return source + '\n' + require('./request-budget-source.js').runtimeSource(lifecycle, plan.capabilities.includes('request.body.forward'), fetchAccounting);
46
45
  }
47
46
  module.exports = {instrumentRequestBudget};