@pulse-compute/provider-fastly 1.0.0-beta.4 → 1.0.0-beta.5

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.
@@ -1,6 +1,8 @@
1
1
  'use strict';
2
+ const applicationErrors = require('./native-application-errors.js');
2
3
 
3
4
  const crypto = require('node:crypto');
5
+ const { nativeStringFields, nativeStringConcat, nativeStringTrim, nativeStringIndex, needsNativeValueFailureGuard } = require('./native-string-values.js');
4
6
  const conditionalKv = require('./kv-native.js');
5
7
  const fs = require('node:fs');
6
8
  const os = require('node:os');
@@ -102,6 +104,8 @@ const FASTLY_NATIVE_PLATFORM_CAPABILITIES_IMPORTS = Object.freeze([
102
104
  Object.freeze(['fastly_http_req', 'method_get']),
103
105
  Object.freeze(['fastly_http_req', 'uri_get']),
104
106
  Object.freeze(['fastly_http_req', 'header_value_get']),
107
+ Object.freeze(['fastly_http_req', 'header_names_get']),
108
+ Object.freeze(['fastly_http_req', 'header_values_get']),
105
109
  Object.freeze(['fastly_http_req', 'new']),
106
110
  Object.freeze(['fastly_http_req', 'method_set']),
107
111
  Object.freeze(['fastly_http_req', 'uri_set']),
@@ -157,7 +161,9 @@ const FASTLY_NATIVE_PLATFORM_EFFECT_KIND = Object.freeze({
157
161
  's3.putText': 14,
158
162
  'kv.getVersioned': 15,
159
163
  'kv.insertIfAbsent': 16,
160
- 'kv.compareAndSwap': 17
164
+ 'kv.compareAndSwap': 17,
165
+ 'time.now': 18,
166
+ 'crypto.digestText': 19
161
167
  });
162
168
 
163
169
  const FASTLY_NATIVE_PLATFORM_CAPABILITIES_ALLOWED_IMPORTS = new Set([
@@ -165,6 +171,8 @@ const FASTLY_NATIVE_PLATFORM_CAPABILITIES_ALLOWED_IMPORTS = new Set([
165
171
  ]);
166
172
 
167
173
  const FASTLY_NATIVE_PLATFORM_EFFECT_KINDS = Object.freeze([
174
+ 'time.now',
175
+ 'crypto.digestText',
168
176
  'fetch',
169
177
  'config.get',
170
178
  'secret.get',
@@ -183,6 +191,8 @@ const FASTLY_NATIVE_PLATFORM_EFFECT_KINDS = Object.freeze([
183
191
  ]);
184
192
 
185
193
  const FASTLY_NATIVE_PLATFORM_CAPABILITY_KINDS = new Set([
194
+ 'time.now',
195
+ 'crypto.digestText',
186
196
  'config.get',
187
197
  'secret.get',
188
198
  'kv.get',
@@ -591,10 +601,12 @@ function validatePlanBoundary(plan, options = {}) {
591
601
  && effect.capability === 'jwt.verify';
592
602
  const s3 = effect.package === '@pulse-compute/s3' && effect.contractId === 'pulse.s3'
593
603
  && ['head', 'getText', 'putText'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
594
- return !grip && !assets && !jwt && !s3;
604
+ const digest = effect.package === '@pulse-compute/crypto' && effect.contractId === 'pulse.crypto'
605
+ && effect.operation === 'digestText' && effect.kind === 'crypto.digestText' && effect.capability === effect.kind;
606
+ return !grip && !assets && !jwt && !s3 && !digest;
595
607
  });
596
608
  if (unsupportedPackages.length > 0) {
597
- fail('Fastly native realization only accepts the trusted first-party Assets, GRIP, and JWT package contracts.', 'PULSE_FASTLY_NATIVE_PLATFORM_PACKAGE_UNSUPPORTED', {
609
+ 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', {
598
610
  effects: unsupportedPackages.map((entry) => ({ id: entry.id, kind: entry.kind, package: entry.package, contractId: entry.contractId }))
599
611
  });
600
612
  }
@@ -613,10 +625,12 @@ function validatePlanBoundary(plan, options = {}) {
613
625
  && effect.capability === 'jwt.verify';
614
626
  const s3 = effect.package === '@pulse-compute/s3' && effect.contractId === 'pulse.s3'
615
627
  && ['head', 'getText', 'putText'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
616
- return !grip && !assets && !jwt && !s3;
628
+ const digest = effect.package === '@pulse-compute/crypto' && effect.contractId === 'pulse.crypto'
629
+ && effect.operation === 'digestText' && effect.kind === 'crypto.digestText' && effect.capability === effect.kind;
630
+ return !grip && !assets && !jwt && !s3 && !digest;
617
631
  });
618
632
  if (unsupportedDeclarations.length > 0) {
619
- fail('Fastly native package realization is restricted to the Assets, GRIP, and JWT contracts.', 'PULSE_FASTLY_NATIVE_PLATFORM_PACKAGE_UNSUPPORTED', { effects: unsupportedDeclarations });
633
+ 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 });
620
634
  }
621
635
  for (const effect of effects.filter((entry) => entry.kind === 'jwt.verify')) {
622
636
  const resource = effect.resource;
@@ -821,6 +835,7 @@ function resolveCapabilityBindings(plan, options = {}) {
821
835
  }
822
836
 
823
837
  return Object.freeze({
838
+ maxDurationMs: require('@pulse-compute/runtime/host').normalizeRequestDuration(options.maxDurationMs),
824
839
  configStore,
825
840
  secretStore,
826
841
  s3: require('../toolchain/s3.js').resolveFastlyS3(effects.map((effect) => ({ ...effect, payload: { contentType: literalObjectField(inputExpression(effect, 'payload'), 'contentType') || undefined } })), source.s3),
@@ -872,6 +887,7 @@ function requiredImportsForPlan(plan, bindings) {
872
887
  'fastly_http_body:new',
873
888
  'fastly_http_body:write'
874
889
  ]);
890
+ if (bindings && bindings.maxDurationMs !== undefined) { keys.add('wasi_snapshot_preview1:clock_time_get'); keys.add('fastly_async_io:select'); }
875
891
  const kinds = new Set((plan.effects || []).map((effect) => effect.kind));
876
892
  if (kinds.has('fetch') || kinds.has('grip.publish') || kinds.has('grip.broadcast')) {
877
893
  for (const key of [
@@ -892,7 +908,7 @@ function requiredImportsForPlan(plan, bindings) {
892
908
  for (const key of ['fastly_secret_store:open', 'fastly_secret_store:get', 'fastly_secret_store:plaintext']) keys.add(key);
893
909
  }
894
910
  for (const kind of conditionalKv.KV_CONDITIONAL_KINDS) if (kinds.has(kind)) for (const key of conditionalKv.kvImports(kind)) keys.add(key);
895
- if (kinds.has('jwt.verify')) keys.add('wasi_snapshot_preview1:clock_time_get');
911
+ if (kinds.has('jwt.verify') || kinds.has('time.now')) keys.add('wasi_snapshot_preview1:clock_time_get');
896
912
  if (kinds.has('kv.get') || kinds.has('kv.put') || kinds.has('assets.lookup')) keys.add('fastly_kv_store:open');
897
913
  if (kinds.has('kv.get') || kinds.has('assets.lookup')) for (const key of ['fastly_kv_store:lookup', 'fastly_kv_store:lookup_wait_v2', 'fastly_http_body:read']) keys.add(key);
898
914
  if (kinds.has('kv.put')) for (const key of ['fastly_kv_store:insert', 'fastly_kv_store:insert_wait']) keys.add(key);
@@ -947,11 +963,13 @@ function generateSchemaRuntime(plan) {
947
963
  body.push(' const output = host_value_object()');
948
964
  fields.forEach(({ field, apply }, fieldIndex) => {
949
965
  body.push(` const key_${currentIndex}_${fieldIndex} = __pulse_fastly_string_value(${quote(field.name)})`);
966
+ if (!field.required) body.push(` if (__pulse_fastly_find(input, ${quote(field.name)}) >= 0) {`);
950
967
  body.push(` const field_${currentIndex}_${fieldIndex} = host_value_property(valueHandle, key_${currentIndex}_${fieldIndex})`);
951
968
  body.push(` if (__pulse_fastly_value(field_${currentIndex}_${fieldIndex}).kind == PULSE_VALUE_UNDEFINED) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 51, -1); return 0 }`);
952
969
  body.push(` const projected_${currentIndex}_${fieldIndex} = ${apply}(field_${currentIndex}_${fieldIndex})`);
953
970
  body.push(` if (projected_${currentIndex}_${fieldIndex} <= 0) return 0`);
954
971
  body.push(` host_value_object_set(output, key_${currentIndex}_${fieldIndex}, projected_${currentIndex}_${fieldIndex})`);
972
+ if (!field.required) body.push(' }');
955
973
  });
956
974
  body.push(' return output');
957
975
  } else if (node.kind === 'string') {
@@ -1075,6 +1093,8 @@ function effectResultSource(plan, bindings) {
1075
1093
 
1076
1094
  function effectDispatchSource(plan) {
1077
1095
  const handlerByKind = Object.freeze({
1096
+ 'time.now': '__pulse_fastly_time_begin',
1097
+ 'crypto.digestText': '__pulse_fastly_digest_begin',
1078
1098
  fetch: '__pulse_fastly_fetch_begin',
1079
1099
  'config.get': '__pulse_fastly_config_begin',
1080
1100
  'secret.get': '__pulse_fastly_secret_begin',
@@ -1094,7 +1114,7 @@ function effectDispatchSource(plan) {
1094
1114
  const kinds = [...new Set((plan.effects || []).map((effect) => effect.kind))];
1095
1115
  const lines = [
1096
1116
  'function host_effect_begin(effectIndex: i32, payload: i32): void {',
1097
- ...((plan.schemas && plan.schemas.references || []).some(entry => entry.usage === 'value-encode') ? [' if (__pulse_fastly_last_error != PULSE_ERROR_NONE) return'] : []),
1117
+ ...(needsNativeValueFailureGuard(plan) ? [' if (__pulse_fastly_last_error != PULSE_ERROR_NONE) return'] : []),
1098
1118
  ' if (effectIndex < 0 || effectIndex >= PULSE_FASTLY_EFFECT_COUNT || unchecked(__pulse_fastly_pending_mode[effectIndex]) != PULSE_FASTLY_PENDING_NONE) { __pulse_fastly_fail(PULSE_ERROR_STATE, 46, effectIndex); return }',
1099
1119
  ' const payloadValue = __pulse_fastly_value(payload)',
1100
1120
  ' if (payloadValue.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 47, effectIndex); return }',
@@ -1263,19 +1283,35 @@ const PULSE_ERROR_SCHEMA: i32 = 1005
1263
1283
  const PULSE_ERROR_TRANSPORT: i32 = 1006
1264
1284
  const PULSE_ERROR_STATE: i32 = 1007
1265
1285
  const PULSE_ERROR_JWT: i32 = 1008
1286
+ const PULSE_ERROR_REQUEST_BODY: i32 = 1009
1266
1287
 
1267
1288
  class __PulseFastlyValue {
1268
1289
  kind: i32 = PULSE_VALUE_UNDEFINED
1269
1290
  boolean: i32 = 0
1270
1291
  number: f64 = 0.0
1271
- text: string = ""
1272
- keys: Array<string> = new Array<string>()
1273
- values: Array<i32> = new Array<i32>()
1292
+ ${nativeStringFields}
1293
+ // Scalar handles do not need collection backing stores.
1294
+ private keysStorage: Array<string> | null = null
1295
+ get keys(): Array<string> {
1296
+ if (this.keysStorage === null) this.keysStorage = new Array<string>()
1297
+ return this.keysStorage!
1298
+ }
1299
+ // Scalar handles do not need collection backing stores.
1300
+ private valuesStorage: Array<i32> | null = null
1301
+ get values(): Array<i32> {
1302
+ if (this.valuesStorage === null) this.valuesStorage = new Array<i32>()
1303
+ return this.valuesStorage!
1304
+ }
1274
1305
  responseHandle: i32 = 0
1275
1306
  bodyHandle: i32 = 0
1276
1307
  bodyLoaded: i32 = 0
1277
1308
  status: i32 = 200
1278
- headers: Array<__PulseFastlyHeader> = new Array<__PulseFastlyHeader>()
1309
+ // Scalar handles do not need collection backing stores.
1310
+ private headersStorage: Array<__PulseFastlyHeader> | null = null
1311
+ get headers(): Array<__PulseFastlyHeader> {
1312
+ if (this.headersStorage === null) this.headersStorage = new Array<__PulseFastlyHeader>()
1313
+ return this.headersStorage!
1314
+ }
1279
1315
  immutable: bool = false
1280
1316
  }
1281
1317
  class __PulseFastlyHeader {
@@ -1405,6 +1441,12 @@ const PULSE_FASTLY_PENDING_NONE: i32 = 0
1405
1441
  const PULSE_FASTLY_PENDING_ASYNC: i32 = 1
1406
1442
  const PULSE_FASTLY_PENDING_READY: i32 = 2
1407
1443
  @lazy const __pulse_fastly_values = new Array<__PulseFastlyValue>()
1444
+ // Primitive handles have value semantics. Bounded caches keep admitted scalar
1445
+ // validation loops from retaining a fresh boxed value for every iteration.
1446
+ @lazy const __pulse_fastly_scalar_handles = new StaticArray<i32>(4)
1447
+ @lazy const __pulse_fastly_integer_handles = new StaticArray<i32>(65536)
1448
+ @lazy const __pulse_fastly_character_handles = new StaticArray<i32>(65537)
1449
+ @lazy const __pulse_fastly_literal_handles = new Map<string, i32>()
1408
1450
  @lazy const __pulse_fastly_pending = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT)
1409
1451
  @lazy const __pulse_fastly_pending_mode = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT)
1410
1452
  @lazy const __pulse_fastly_payloads = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT)
@@ -1440,8 +1482,18 @@ function __pulse_fastly_utf8(value: string): ArrayBuffer { return String.UTF8.en
1440
1482
  function __pulse_fastly_decode(buffer: Uint8Array, length: i32): string { return String.UTF8.decodeUnsafe(buffer.dataStart, length, false) }
1441
1483
  function __pulse_fastly_put(value: __PulseFastlyValue): i32 { __pulse_fastly_values.push(value); return __pulse_fastly_values.length }
1442
1484
  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]) }
1485
+ ${nativeStringConcat}
1486
+ ${nativeStringTrim}
1487
+ ${nativeStringIndex}
1443
1488
  function __pulse_fastly_deep_freeze(handle: i32): void { const value = __pulse_fastly_value(handle); if (value.immutable) return; value.immutable = true; if (value.kind == PULSE_VALUE_ARRAY || value.kind == PULSE_VALUE_OBJECT) for (let index = 0; index < value.values.length; index += 1) __pulse_fastly_deep_freeze(unchecked(value.values[index])) }
1444
- function __pulse_fastly_string_value(value: string): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_STRING; item.text = value; return __pulse_fastly_put(item) }
1489
+ function __pulse_fastly_string_value(value: string): i32 {
1490
+ const index = value.length == 0 ? 65536 : value.length == 1 ? value.charCodeAt(0) : -1;
1491
+ if (index >= 0) { const cached = unchecked(__pulse_fastly_character_handles[index]); if (cached != 0) return cached }
1492
+ const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_STRING; item.text = value;
1493
+ const handle = __pulse_fastly_put(item);
1494
+ if (index >= 0) unchecked(__pulse_fastly_character_handles[index] = handle);
1495
+ return handle
1496
+ }
1445
1497
  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 }
1446
1498
  function __pulse_fastly_number_string(value: f64): string { if (value >= -9007199254740991.0 && value <= 9007199254740991.0 && Math.floor(value) == value) return i64(value).toString(); return value.toString() }
1447
1499
  function __pulse_fastly_string(handle: i32): string { const value = __pulse_fastly_value(handle); if (value.kind == PULSE_VALUE_STRING) return value.text; if (value.kind == PULSE_VALUE_NUMBER) return __pulse_fastly_number_string(value.number); if (value.kind == PULSE_VALUE_BOOLEAN) return value.boolean != 0 ? "true" : "false"; if (value.kind == PULSE_VALUE_NULL) return "null"; if (value.kind == PULSE_VALUE_UNDEFINED) return "undefined"; return __pulse_fastly_json(handle, 0) }
@@ -1480,21 +1532,26 @@ function __pulse_fastly_quote(value: string): string {
1480
1532
  out[cursor] = 34;
1481
1533
  return String.UTF16.decodeUnsafe(out.dataStart, out.byteLength);
1482
1534
  }
1483
- function __pulse_fastly_json(handle: i32, depth: i32): string {
1484
- if (depth > ${plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 128 : 64}) { __pulse_fastly_fail(PULSE_ERROR_JSON, 2, -1); return "null" }
1535
+ function __pulse_fastly_json_parts(handle: i32, depth: i32, parts: Array<string>): void {
1536
+ if (depth > ${plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 128 : 64}) { __pulse_fastly_fail(PULSE_ERROR_JSON, 2, -1); parts.push("null"); return }
1485
1537
  const value = __pulse_fastly_value(handle)
1486
- if (value.kind == PULSE_VALUE_UNDEFINED || value.kind == PULSE_VALUE_NULL) return "null"
1487
- if (value.kind == PULSE_VALUE_BOOLEAN) return value.boolean != 0 ? "true" : "false"
1488
- if (value.kind == PULSE_VALUE_NUMBER) return __pulse_fastly_number_string(value.number)
1489
- if (value.kind == PULSE_VALUE_STRING) return __pulse_fastly_quote(value.text)
1490
- if (value.kind == PULSE_VALUE_ARRAY) { let out = "["; for (let i = 0; i < value.values.length; i += 1) { if (i > 0) out += ","; out += __pulse_fastly_json(unchecked(value.values[i]), depth + 1) } return out + "]" }
1491
- if (value.kind == PULSE_VALUE_OBJECT) { let out = "{"; for (let i = 0; i < value.keys.length; i += 1) { if (i > 0) out += ","; out += __pulse_fastly_quote(unchecked(value.keys[i])) + ":" + __pulse_fastly_json(unchecked(value.values[i]), depth + 1) } return out + "}" }
1492
- return "null"
1538
+ if (value.kind == PULSE_VALUE_BOOLEAN) { parts.push(value.boolean != 0 ? "true" : "false"); return }
1539
+ if (value.kind == PULSE_VALUE_NUMBER) { parts.push(__pulse_fastly_number_string(value.number)); return }
1540
+ if (value.kind == PULSE_VALUE_STRING) { parts.push(__pulse_fastly_quote(value.text)); return }
1541
+ if (value.kind == PULSE_VALUE_ARRAY) { parts.push("["); for (let i = 0; i < value.values.length; i += 1) { if (i > 0) parts.push(","); __pulse_fastly_json_parts(unchecked(value.values[i]), depth + 1, parts) } parts.push("]"); return }
1542
+ if (value.kind == PULSE_VALUE_OBJECT) { parts.push("{"); for (let i = 0; i < value.keys.length; i += 1) { if (i > 0) parts.push(","); parts.push(__pulse_fastly_quote(unchecked(value.keys[i]))); parts.push(":"); __pulse_fastly_json_parts(unchecked(value.values[i]), depth + 1, parts) } parts.push("}"); return }
1543
+ parts.push("null")
1544
+ }
1545
+ function __pulse_fastly_json(handle: i32, depth: i32): string {
1546
+ // Join once, avoiding a complete copy of a multi-MiB leaf at every ancestor
1547
+ // and at every following member in the response or effect result.
1548
+ const parts = new Array<string>(); __pulse_fastly_json_parts(handle, depth, parts);
1549
+ return parts.join("")
1493
1550
  }
1494
1551
  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 }
1495
1552
  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) }
1496
1553
  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)) }
1497
- function __pulse_fastly_read_body(handle: i32, effectIndex: i32): string {
1554
+ function __pulse_fastly_read_body(handle: i32, effectIndex: i32, requestText: bool = false): string {
1498
1555
  const chunks = new Array<Uint8Array>(); let total = 0;
1499
1556
  while (true) {
1500
1557
  const buffer = new Uint8Array(PULSE_FASTLY_BUFFER_BYTES), read = __pulse_fastly_out_i32();
@@ -1504,36 +1561,55 @@ function __pulse_fastly_read_body(handle: i32, effectIndex: i32): string {
1504
1561
  if (count < 0 || count > buffer.length) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 20, effectIndex); return "" }
1505
1562
  if (!count) break;
1506
1563
  total += count;
1507
- if (total > __PULSE_SCHEMA_MAX_BYTES && __PULSE_SCHEMA_MAX_BYTES > 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 21, effectIndex); return "" }
1564
+ 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 "" }
1508
1565
  chunks.push(buffer.subarray(0, count));
1509
1566
  }
1510
1567
  // UTF-8 scalars may straddle host read chunks. Decode the bounded body once.
1511
1568
  const bytes = new Uint8Array(total); let offset = 0;
1512
1569
  for (let i = 0; i < chunks.length; i++) { bytes.set(chunks[i], offset); offset += chunks[i].length; }
1570
+ if (requestText && !__pulse_request_utf8_valid(bytes)) { __pulse_fastly_fail(PULSE_ERROR_REQUEST_BODY, 24, effectIndex); return "" }
1513
1571
  return __pulse_fastly_decode(bytes, total);
1514
1572
  }
1515
1573
  function __pulse_fastly_request_header_text(name: string): string { const nameBytes = __pulse_fastly_utf8(name); const buffer = new Uint8Array(PULSE_FASTLY_BUFFER_BYTES); const written = __pulse_fastly_out_i32(); const status = fastly_http_req_header_value_get(__pulse_fastly_request_handle, changetype<usize>(nameBytes), nameBytes.byteLength, buffer.dataStart, PULSE_FASTLY_BUFFER_BYTES, changetype<usize>(written)); if (status == FASTLY_STATUS_NONE) return ""; if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 22, -1); return "" } return __pulse_fastly_decode(buffer, __pulse_fastly_out_value(written)) }
1516
1574
  function __pulse_fastly_fetch_header_text(responseHandle: i32, name: string): string { const nameBytes = __pulse_fastly_utf8(name); const buffer = new Uint8Array(PULSE_FASTLY_BUFFER_BYTES); const written = __pulse_fastly_out_i32(); const status = fastly_http_resp_header_value_get(responseHandle, changetype<usize>(nameBytes), nameBytes.byteLength, buffer.dataStart, PULSE_FASTLY_BUFFER_BYTES, changetype<usize>(written)); if (status == FASTLY_STATUS_NONE) return ""; if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 23, -1); return "" } return __pulse_fastly_decode(buffer, __pulse_fastly_out_value(written)) }
1517
1575
  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 }
1518
1576
 
1519
- function host_value_undefined(): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_UNDEFINED; return __pulse_fastly_put(item) }
1520
- function host_value_null(): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_NULL; return __pulse_fastly_put(item) }
1521
- function host_value_boolean(value: i32): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_BOOLEAN; item.boolean = value != 0 ? 1 : 0; return __pulse_fastly_put(item) }
1522
- function host_value_number(value: f64): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_NUMBER; item.number = value; return __pulse_fastly_put(item) }
1523
- function host_value_string(pointer: i32, length: i32): i32 { return __pulse_fastly_string_value(changetype<string>(pointer)) }
1577
+ function __pulse_fastly_scalar(kind: i32, boolean: i32, slot: i32): i32 {
1578
+ const cached = unchecked(__pulse_fastly_scalar_handles[slot]); if (cached != 0) return cached;
1579
+ const item = new __PulseFastlyValue(); item.kind = kind; item.boolean = boolean;
1580
+ const handle = __pulse_fastly_put(item); unchecked(__pulse_fastly_scalar_handles[slot] = handle); return handle
1581
+ }
1582
+ function host_value_undefined(): i32 { return __pulse_fastly_scalar(PULSE_VALUE_UNDEFINED, 0, 0) }
1583
+ function host_value_null(): i32 { return __pulse_fastly_scalar(PULSE_VALUE_NULL, 0, 1) }
1584
+ function host_value_boolean(value: i32): i32 { return __pulse_fastly_scalar(PULSE_VALUE_BOOLEAN, value != 0 ? 1 : 0, value != 0 ? 3 : 2) }
1585
+ function host_value_number(value: f64): i32 {
1586
+ // Preserve negative zero, NaN, infinities and nonintegral values exactly.
1587
+ const cacheable = value >= 0 && value < 65536 && Math.floor(value) == value && (value != 0 || 1.0 / value > 0);
1588
+ const index = cacheable ? i32(value) : -1;
1589
+ if (index >= 0) { const cached = unchecked(__pulse_fastly_integer_handles[index]); if (cached != 0) return cached }
1590
+ const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_NUMBER; item.number = value;
1591
+ const handle = __pulse_fastly_put(item); if (index >= 0) unchecked(__pulse_fastly_integer_handles[index] = handle); return handle
1592
+ }
1593
+ function host_value_string(pointer: i32, length: i32): i32 {
1594
+ const text = changetype<string>(pointer);
1595
+ if (text.length > 1 && text.length <= 128 && __pulse_fastly_literal_handles.has(text)) return __pulse_fastly_literal_handles.get(text);
1596
+ const handle = __pulse_fastly_string_value(text);
1597
+ if (text.length > 1 && text.length <= 128 && __pulse_fastly_literal_handles.size < 1024) __pulse_fastly_literal_handles.set(text, handle);
1598
+ return handle
1599
+ }
1524
1600
  function host_value_array(): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_ARRAY; return __pulse_fastly_put(item) }
1525
1601
  function host_value_array_push(target: i32, value: i32): void { const item = __pulse_fastly_value(target); if (item.kind != PULSE_VALUE_ARRAY || item.immutable) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 30, -1); return } item.values.push(value) }
1526
1602
  function host_value_array_spread(target: i32, source: i32): void { const from = __pulse_fastly_value(source); if (from.kind != PULSE_VALUE_ARRAY) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 31, -1); return } for (let i = 0; i < from.values.length; i += 1) host_value_array_push(target, unchecked(from.values[i])) }
1527
1603
  function host_value_object(): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_OBJECT; return __pulse_fastly_put(item) }
1528
1604
  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) } }
1529
1605
  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])) }
1530
- 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.text.length : item.values.length); return host_value_undefined() }
1606
+ 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() }
1531
1607
  function host_value_property_set(target: i32, key: i32, value: i32): i32 { host_value_object_set(target, key, value); return value }
1532
- function host_value_element(target: i32, keyHandle: i32): i32 { const item = __pulse_fastly_value(target); 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) }
1608
+ 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) }
1533
1609
  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) }
1534
- function host_value_truthy(handle: i32): i32 { const value = __pulse_fastly_value(handle); if (value.kind == PULSE_VALUE_UNDEFINED || value.kind == PULSE_VALUE_NULL) return 0; if (value.kind == PULSE_VALUE_BOOLEAN) return value.boolean; if (value.kind == PULSE_VALUE_NUMBER) return value.number != 0.0 && !isNaN(value.number) ? 1 : 0; if (value.kind == PULSE_VALUE_STRING) return value.text.length > 0 ? 1 : 0; return 1 }
1610
+ function host_value_truthy(handle: i32): i32 { const value = __pulse_fastly_value(handle); if (value.kind == PULSE_VALUE_UNDEFINED || value.kind == PULSE_VALUE_NULL) return 0; if (value.kind == PULSE_VALUE_BOOLEAN) return value.boolean; if (value.kind == PULSE_VALUE_NUMBER) return value.number != 0.0 && !isNaN(value.number) ? 1 : 0; if (value.kind == PULSE_VALUE_STRING) return value.textLength > 0 ? 1 : 0; return 1 }
1535
1611
  function host_value_nullish(handle: i32): i32 { const kind = __pulse_fastly_value(handle).kind; return kind == PULSE_VALUE_UNDEFINED || kind == PULSE_VALUE_NULL ? 1 : 0 }
1536
- function host_value_binary(operator: i32, leftHandle: i32, rightHandle: i32): i32 { const left = __pulse_fastly_value(leftHandle); const right = __pulse_fastly_value(rightHandle); if (operator == 0 || operator == 2) { let equal = false; if (left.kind == right.kind) { if (left.kind == PULSE_VALUE_UNDEFINED || left.kind == PULSE_VALUE_NULL) equal = true; else if (left.kind == PULSE_VALUE_BOOLEAN) equal = left.boolean == right.boolean; else if (left.kind == PULSE_VALUE_NUMBER) equal = left.number == right.number; else if (left.kind == PULSE_VALUE_STRING) equal = left.text == right.text; else equal = leftHandle == rightHandle } return host_value_boolean(equal ? 1 : 0) } if (operator == 1 || operator == 3) { const result = host_value_binary(operator == 1 ? 0 : 2, leftHandle, rightHandle); return host_value_boolean(host_value_truthy(result) == 0 ? 1 : 0) } if (operator == 8) { if (left.kind == PULSE_VALUE_STRING || right.kind == PULSE_VALUE_STRING) return __pulse_fastly_string_value(__pulse_fastly_string(leftHandle) + __pulse_fastly_string(rightHandle)); return host_value_number(__pulse_fastly_number(leftHandle) + __pulse_fastly_number(rightHandle)) } if (operator == 4) return host_value_boolean(__pulse_fastly_number(leftHandle) < __pulse_fastly_number(rightHandle) ? 1 : 0); if (operator == 5) return host_value_boolean(__pulse_fastly_number(leftHandle) <= __pulse_fastly_number(rightHandle) ? 1 : 0); if (operator == 6) return host_value_boolean(__pulse_fastly_number(leftHandle) > __pulse_fastly_number(rightHandle) ? 1 : 0); if (operator == 7) return host_value_boolean(__pulse_fastly_number(leftHandle) >= __pulse_fastly_number(rightHandle) ? 1 : 0); if (operator == 9) return host_value_number(__pulse_fastly_number(leftHandle) - __pulse_fastly_number(rightHandle)); if (operator == 10) return host_value_number(__pulse_fastly_number(leftHandle) * __pulse_fastly_number(rightHandle)); if (operator == 11) return host_value_number(__pulse_fastly_number(leftHandle) / __pulse_fastly_number(rightHandle)); if (operator == 14) return host_value_truthy(leftHandle) != 0 ? rightHandle : leftHandle; if (operator == 15) return host_value_truthy(leftHandle) != 0 ? leftHandle : rightHandle; if (operator == 16) return host_value_nullish(leftHandle) == 0 ? leftHandle : rightHandle; __pulse_fastly_fail(PULSE_ERROR_UNSUPPORTED, 36, -1); return 0 }
1612
+ function host_value_binary(operator: i32, leftHandle: i32, rightHandle: i32): i32 { const left = __pulse_fastly_value(leftHandle); const right = __pulse_fastly_value(rightHandle); if (operator == 0 || operator == 2) { let equal = false; if (left.kind == right.kind) { if (left.kind == PULSE_VALUE_UNDEFINED || left.kind == PULSE_VALUE_NULL) equal = true; else if (left.kind == PULSE_VALUE_BOOLEAN) equal = left.boolean == right.boolean; else if (left.kind == PULSE_VALUE_NUMBER) equal = left.number == right.number; else if (left.kind == PULSE_VALUE_STRING) equal = left.text == right.text; else equal = leftHandle == rightHandle } return host_value_boolean(equal ? 1 : 0) } if (operator == 1 || operator == 3) { const result = host_value_binary(operator == 1 ? 0 : 2, leftHandle, rightHandle); return host_value_boolean(host_value_truthy(result) == 0 ? 1 : 0) } if (operator == 8) { if (left.kind == PULSE_VALUE_STRING || right.kind == PULSE_VALUE_STRING) return __pulse_fastly_concat(leftHandle, rightHandle); return host_value_number(__pulse_fastly_number(leftHandle) + __pulse_fastly_number(rightHandle)) } if (left.kind == PULSE_VALUE_STRING && right.kind == PULSE_VALUE_STRING) { if (operator == 4) return host_value_boolean(left.text < right.text ? 1 : 0); if (operator == 5) return host_value_boolean(left.text <= right.text ? 1 : 0); if (operator == 6) return host_value_boolean(left.text > right.text ? 1 : 0); if (operator == 7) return host_value_boolean(left.text >= right.text ? 1 : 0); } if (operator == 4) return host_value_boolean(__pulse_fastly_number(leftHandle) < __pulse_fastly_number(rightHandle) ? 1 : 0); if (operator == 5) return host_value_boolean(__pulse_fastly_number(leftHandle) <= __pulse_fastly_number(rightHandle) ? 1 : 0); if (operator == 6) return host_value_boolean(__pulse_fastly_number(leftHandle) > __pulse_fastly_number(rightHandle) ? 1 : 0); if (operator == 7) return host_value_boolean(__pulse_fastly_number(leftHandle) >= __pulse_fastly_number(rightHandle) ? 1 : 0); if (operator == 9) return host_value_number(__pulse_fastly_number(leftHandle) - __pulse_fastly_number(rightHandle)); if (operator == 10) return host_value_number(__pulse_fastly_number(leftHandle) * __pulse_fastly_number(rightHandle)); if (operator == 11) return host_value_number(__pulse_fastly_number(leftHandle) / __pulse_fastly_number(rightHandle)); if (operator == 12) return host_value_number(__pulse_fastly_number(leftHandle) % __pulse_fastly_number(rightHandle)); if (operator == 14) return host_value_truthy(leftHandle) != 0 ? rightHandle : leftHandle; if (operator == 15) return host_value_truthy(leftHandle) != 0 ? leftHandle : rightHandle; if (operator == 16) return host_value_nullish(leftHandle) == 0 ? leftHandle : rightHandle; __pulse_fastly_fail(PULSE_ERROR_UNSUPPORTED, 36, -1); return 0 }
1537
1613
  function host_value_unary(operator: i32, handle: i32): i32 { if (operator == 0) return host_value_boolean(host_value_truthy(handle) == 0 ? 1 : 0); if (operator == 1) return host_value_number(__pulse_fastly_number(handle)); if (operator == 2) return host_value_number(-__pulse_fastly_number(handle)); if (operator == 5) return host_value_undefined(); __pulse_fastly_fail(PULSE_ERROR_UNSUPPORTED, 37, -1); return 0 }
1538
1614
 
1539
1615
  function host_request_method(): i32 { return __pulse_fastly_string_value(__pulse_fastly_request_method) }
@@ -1544,13 +1620,26 @@ function __pulse_router_param_text(path: string, pattern: string, name: string):
1544
1620
  function host_router_match(path: i32, pattern: i32): i32 { return host_value_boolean(__pulse_router_match_text(__pulse_fastly_string(path), __pulse_fastly_string(pattern)) ? 1 : 0) }
1545
1621
  function host_router_param(path: i32, pattern: i32, name: i32): i32 { const value = __pulse_router_param_text(__pulse_fastly_string(path), __pulse_fastly_string(pattern), __pulse_fastly_string(name)); return value === null ? host_value_undefined() : __pulse_fastly_string_value(value) }
1546
1622
  function host_request_path(): i32 { return __pulse_fastly_string_value(__pulse_fastly_request_path) }
1547
- function host_request_headers(): i32 { return host_value_object() }
1623
+ function host_request_headers(): i32 { return __pulse_request_headers_read() }
1548
1624
  function host_request_header(name: i32): i32 { const value = __pulse_fastly_request_header_text(__pulse_fastly_string(name)); return value.length == 0 ? host_value_undefined() : __pulse_fastly_string_value(value) }
1549
- function host_request_text(): i32 { if (__pulse_fastly_request_body_loaded == 0) { __pulse_fastly_request_body = __pulse_fastly_read_body(__pulse_fastly_request_body_handle, -1); __pulse_fastly_request_body_loaded = 1 } return __pulse_fastly_string_value(__pulse_fastly_request_body) }
1625
+ function host_request_text(): i32 { if (__pulse_request_body_failure != 0) { __pulse_fastly_fail(PULSE_ERROR_REQUEST_BODY, __pulse_request_body_failure, -1); return 0 } if (__pulse_fastly_request_body_loaded == 0) { __pulse_fastly_request_body = __pulse_fastly_read_body(__pulse_fastly_request_body_handle, -1, true); __pulse_fastly_request_body_loaded = 1; if (__pulse_fastly_last_error == PULSE_ERROR_REQUEST_BODY) __pulse_request_body_failure = __pulse_fastly_error_stage } if (__pulse_fastly_last_error != 0) return 0; return __pulse_fastly_string_value(__pulse_fastly_request_body) }
1550
1626
  function host_request_json(schema: i32): i32 { const parsed = __pulse_fastly_parse_json(__pulse_fastly_string(host_request_text())); if (parsed <= 0) return 0; const schemaValue = __pulse_fastly_value(schema); return schemaValue.kind == PULSE_VALUE_STRING ? __pulse_fastly_schema_apply(schemaValue.text, parsed, false) : parsed }
1551
1627
  function __pulse_fastly_headers_from_options(options: __PulseFastlyValue, output: __PulseFastlyValue): void { const index = __pulse_fastly_find(options, "headers"); if (index < 0) return; const headers = __pulse_fastly_value(unchecked(options.values[index])); if (headers.kind == PULSE_VALUE_OBJECT) { for (let i = 0; i < headers.keys.length; i += 1) { const name = unchecked(headers.keys[i]); const value = __pulse_fastly_value(unchecked(headers.values[i])); if (value.kind == PULSE_VALUE_ARRAY) { for (let j = 0; j < value.values.length; j += 1) output.headers.push(new __PulseFastlyHeader(name, __pulse_fastly_string(unchecked(value.values[j])))) } else output.headers.push(new __PulseFastlyHeader(name, __pulse_fastly_string(unchecked(headers.values[i])))) } return } if (headers.kind == PULSE_VALUE_ARRAY) { for (let i = 0; i < headers.values.length; i += 1) { const pair = __pulse_fastly_value(unchecked(headers.values[i])); if (pair.kind != PULSE_VALUE_ARRAY || pair.values.length < 2) continue; output.headers.push(new __PulseFastlyHeader(__pulse_fastly_string(unchecked(pair.values[0])), __pulse_fastly_string(unchecked(pair.values[1])))) } } }
1552
1628
  function __pulse_fastly_response(value: i32, optionsHandle: i32, json: bool): i32 { const output = new __PulseFastlyValue(); output.kind = PULSE_VALUE_RESPONSE; output.status = 200; const options = __pulse_fastly_value(optionsHandle); let bodyValue = value; if (options.kind == PULSE_VALUE_OBJECT) { const statusIndex = __pulse_fastly_find(options, "status"); if (statusIndex >= 0) output.status = i32(__pulse_fastly_number(unchecked(options.values[statusIndex]))); const schemaIndex = __pulse_fastly_find(options, "schema"); if (json && schemaIndex >= 0) { bodyValue = __pulse_fastly_schema_apply(__pulse_fastly_string(unchecked(options.values[schemaIndex])), value, true); if (bodyValue <= 0) return 0 } __pulse_fastly_headers_from_options(options, output) } output.text = json ? __pulse_fastly_json(bodyValue, 0) : __pulse_fastly_string(value); 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", json ? "application/json; charset=utf-8" : "text/plain; charset=utf-8")); return __pulse_fastly_put(output) }
1553
1629
  function host_response_json(value: i32, options: i32): i32 { return __pulse_fastly_response(value, options, true) }
1630
+ function host_schema_decode(text: i32, schema: i32): i32 {
1631
+ const id = __pulse_fastly_value(schema)
1632
+ if (id.kind != PULSE_VALUE_STRING || id.text.length == 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 55, -1); return 0 }
1633
+ const input = __pulse_fastly_value(text)
1634
+ if (input.kind != PULSE_VALUE_STRING) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 52, -1); return 0 }
1635
+ if (String.UTF8.byteLength(input.text) > __PULSE_SCHEMA_MAX_BYTES) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 21, -1); return 0 }
1636
+ const parsed = __pulse_fastly_parse_json(input.text)
1637
+ if (parsed <= 0) return 0
1638
+ const decoded = __pulse_fastly_schema_apply(id.text, parsed, false)
1639
+ if (decoded <= 0) return 0
1640
+ __pulse_fastly_deep_freeze(decoded)
1641
+ return decoded
1642
+ }
1554
1643
  function host_schema_encode(value: i32, schema: i32): i32 {
1555
1644
  const id = __pulse_fastly_value(schema)
1556
1645
  if (id.kind != PULSE_VALUE_STRING || id.text.length == 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 55, -1); return 0 }
@@ -1651,7 +1740,7 @@ export function ${entryName}(): void {
1651
1740
  if (__pulse_fastly_last_error != 0) { __pulse_fastly_jwt_send_error(); return }
1652
1741
  __pulse_fastly_request_path = __pulse_fastly_path(__pulse_fastly_request_url)
1653
1742
  let runStatus = pulse_start()
1654
- while (runStatus == 1 && __pulse_fastly_last_error == 0) {
1743
+ ${applicationErrors.enabled(plan) ? applicationErrors.driverLoop(plan) : ` while (runStatus == 1 && __pulse_fastly_last_error == 0) {
1655
1744
  for (let effectIndex = 0; effectIndex < PULSE_FASTLY_EFFECT_COUNT; effectIndex += 1) {
1656
1745
  if (unchecked(__pulse_fastly_pending_mode[effectIndex]) == PULSE_FASTLY_PENDING_NONE) continue
1657
1746
  const result = __pulse_fastly_resolve_effect(effectIndex)
@@ -1659,7 +1748,7 @@ export function ${entryName}(): void {
1659
1748
  if (pulse_set_effect_result(effectIndex, result) != 1) { __pulse_fastly_fail(PULSE_ERROR_STATE, 101, effectIndex); __pulse_fastly_jwt_send_error(); return }
1660
1749
  }
1661
1750
  runStatus = pulse_resume()
1662
- }
1751
+ }`}
1663
1752
  if (__pulse_fastly_last_error != 0) { __pulse_fastly_jwt_send_error(); return }
1664
1753
  if (runStatus != 0) { __pulse_fastly_fail(PULSE_ERROR_STATE, 102, -1); __pulse_fastly_jwt_send_error(); return }
1665
1754
  const result = pulse_result_handle()
@@ -1672,6 +1761,7 @@ export function ${entryName}(): void {
1672
1761
  }
1673
1762
 
1674
1763
  function capabilityImports(effect) {
1764
+ if (effect.kind === 'time.now') return ['wasi_snapshot_preview1.clock_time_get'];
1675
1765
  if (effect.kind === 'config.get') return ['fastly_config_store.open', 'fastly_config_store.get'];
1676
1766
  if (effect.kind === 'secret.get') return ['fastly_secret_store.open', 'fastly_secret_store.get', 'fastly_secret_store.plaintext'];
1677
1767
  if (effect.kind === 'jwt.verify') {
@@ -1734,7 +1824,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1734
1824
  const portableSource = stripPulseHostImports(portable.source);
1735
1825
  const hasJwt = (plan.effects || []).some((effect) => effect.kind === 'jwt.verify');
1736
1826
  const jwtSource = hasJwt ? pulseJwtAssemblyScriptSource() : undefined;
1737
- const source = [
1827
+ let source = [
1738
1828
  '/* Generated by Pulse Pass98 Fastly native platform capability compiler. */',
1739
1829
  'export function __pulse_fastly_abort(message: string | null, fileName: string | null = null, line: u32 = 0, column: u32 = 0): void { unreachable() }',
1740
1830
  '',
@@ -1745,11 +1835,20 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1745
1835
  }),
1746
1836
  jwtSource ? jwtSource.source : '',
1747
1837
  require('./s3-native.js').s3NativeSource(plan, bindings),
1838
+ require('./time-native.js').timeNativeSource(plan),
1839
+ require('./digest-native.js').digestNativeSource(plan),
1748
1840
  conditionalKv.kvNativeSource(plan),
1841
+ require('./native-request-body.js').runtimeSource(),
1842
+ require('./native-request-headers.js').runtimeSource(),
1843
+ applicationErrors.enabled(plan) ? applicationErrors.runtimeSource() : '',
1749
1844
  portableSource,
1750
1845
  driverSource(plan, { guestLinked: facts.guestUnits.length > 0 }),
1751
1846
  ''
1752
1847
  ].join('\n');
1848
+ if (applicationErrors.enabled(plan)) source = applicationErrors.instrument(source);
1849
+ source = require('./native-request-headers.js').instrument(source, applicationErrors.enabled(plan), /\bhost_request_headers\(/.test(portableSource));
1850
+ source = require('./native-request-body.js').instrument(source, applicationErrors.enabled(plan));
1851
+ source = require('./request-budget.js').instrumentRequestBudget(source, bindings.maxDurationMs, plan);
1753
1852
  const effectKinds = Object.freeze((plan.effects || []).reduce((output, effect) => {
1754
1853
  output[effect.kind] = (output[effect.kind] || 0) + 1;
1755
1854
  return output;
@@ -1809,7 +1908,8 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1809
1908
  'pulse_fastly_error_stage',
1810
1909
  'pulse_fastly_error_effect',
1811
1910
  'pulse_plan_hash_ptr',
1812
- 'pulse_plan_hash_length'
1911
+ 'pulse_plan_hash_length',
1912
+ ...(bindings.maxDurationMs === undefined ? [] : ['pulse_fastly_request_expired'])
1813
1913
  ]),
1814
1914
  policy: Object.freeze({
1815
1915
  nativeFastly: true,
@@ -1817,7 +1917,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1817
1917
  providerNeutralInput: true,
1818
1918
  javascriptRuntime: false,
1819
1919
  jsComputeRuntime: false,
1820
- wasi: hasJwt || bindings.s3.length || plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 'clock_time_get only' : false,
1920
+ wasi: bindings.maxDurationMs !== undefined || hasJwt || bindings.s3.length || plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 'clock_time_get only' : false,
1821
1921
  effects: FASTLY_NATIVE_PLATFORM_EFFECT_KINDS.join(', '),
1822
1922
  continuations: true,
1823
1923
  groupedEffects: 'existing fetch start-before-wait ordering remains intact',
@@ -1953,6 +2053,9 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
1953
2053
  '--optimize'
1954
2054
  ];
1955
2055
  const optimization = appendAssemblyScriptOptimizationArgs(args, nativeOptimization);
2056
+ if (!guestLinked && plan.effects.some(effect => effect.kind === 'crypto.digestText' || effect.kind.startsWith('s3.'))) {
2057
+ args.push('--maximumMemory', '4096');
2058
+ }
1956
2059
  if (generated.guestUnits.length > 0) {
1957
2060
  args.push(
1958
2061
  '--disable', 'bulk-memory',
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+
3
+ function runtimeSource() {
4
+ return `
5
+ let __pulse_request_body_failure: i32 = 0
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;) {
9
+ const lead = bytes[i++]; if (lead < 128) continue;
10
+ let count = 0, minimum = 128, maximum = 191;
11
+ if (lead >= 194 && lead <= 223) count = 1;
12
+ else if (lead >= 224 && lead <= 239) { count = 2; if (lead == 224) minimum = 160; if (lead == 237) maximum = 159; }
13
+ else if (lead >= 240 && lead <= 244) { count = 3; if (lead == 240) minimum = 144; if (lead == 244) maximum = 143; }
14
+ else return false;
15
+ if (i + count > bytes.length || i32(bytes[i]) < minimum || i32(bytes[i]) > maximum) return false;
16
+ i++; for (let n = 1; n < count; n++, i++) if (bytes[i] < 128 || bytes[i] > 191) return false;
17
+ }
18
+ return true;
19
+ }
20
+ function __pulse_request_send_body_error(): bool {
21
+ if (__pulse_fastly_last_error != PULSE_ERROR_REQUEST_BODY) return false;
22
+ const output = new __PulseFastlyValue(); output.kind = PULSE_VALUE_RESPONSE;
23
+ output.status = __pulse_fastly_error_stage == 21 ? 413 : 400;
24
+ output.text = output.status == 413 ? "Payload Too Large" : "Bad Request";
25
+ output.headers.push(new __PulseFastlyHeader("content-type", "text/plain; charset=utf-8"));
26
+ __pulse_fastly_last_error = 0; __pulse_fastly_error_stage = 0; __pulse_fastly_error_effect = -1;
27
+ const status = __pulse_fastly_send_result(__pulse_fastly_put(output));
28
+ if (status != FASTLY_STATUS_OK) __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 104, -1);
29
+ return true;
30
+ }
31
+ `;
32
+ }
33
+
34
+ function instrument(source, applicationErrors) {
35
+ if (!applicationErrors) source = source.replace(/function (host_\w+)\(([^)]*)\): (i32|void) \{/g,
36
+ (declaration, _name, _args, result) => `${declaration} if (__pulse_fastly_last_error == PULSE_ERROR_REQUEST_BODY) return${result === 'void' ? '' : ' 0'};`);
37
+ // The existing terminal failure path also covers applications without an
38
+ // error handler. Request-budget instrumentation subsequently takes priority.
39
+ const marker = 'function __pulse_fastly_jwt_send_error(): void {';
40
+ if (!source.includes(marker)) throw new Error('Fastly request-body error boundary drift.');
41
+ return source.replace(marker, marker + ' if (__pulse_request_send_body_error()) return;');
42
+ }
43
+
44
+ module.exports = { runtimeSource, instrument };
@@ -0,0 +1,93 @@
1
+ 'use strict';
2
+
3
+ // Fastly's multi-value ABI returns NUL-delimited pages and an i64 next cursor
4
+ // (-1 marks completion). See https://docs.rs/fastly/latest/src/fastly/abi.rs.html
5
+ // Keep this independent of outbound response headers.
6
+ function runtimeSource() {
7
+ return `
8
+ @external("fastly_http_req", "header_names_get")
9
+ declare function __pulse_request_header_names(handle: i32, buffer: usize, size: i32, cursor: i32, end: usize, written: usize): i32
10
+ @external("fastly_http_req", "header_values_get")
11
+ declare function __pulse_request_header_values(handle: i32, name: usize, length: i32, buffer: usize, size: i32, cursor: i32, end: usize, written: usize): i32
12
+ let __pulse_request_headers_snapshot: i32 = 0
13
+ let __pulse_request_headers_failed: bool = false
14
+ let __pulse_request_headers_bytes: i32 = 0
15
+ function __pulse_request_headers_fail(): void {
16
+ __pulse_request_headers_failed = true;
17
+ __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 24, -1);
18
+ }
19
+ function __pulse_request_header_list(name: string, names: bool): Array<string> {
20
+ const output = new Array<string>();
21
+ const buffer = new Uint8Array(PULSE_FASTLY_BUFFER_BYTES);
22
+ const end = new StaticArray<i64>(1), written = new StaticArray<i32>(1);
23
+ const key = __pulse_fastly_utf8(name);
24
+ let cursor: i64 = 0;
25
+ let bytes = 0;
26
+ for (let page = 0; page < 256; page++) {
27
+ end[0] = 0; written[0] = -1;
28
+ const status = names
29
+ ? __pulse_request_header_names(__pulse_fastly_request_handle, buffer.dataStart, buffer.length, i32(cursor), changetype<usize>(end), changetype<usize>(written))
30
+ : __pulse_request_header_values(__pulse_fastly_request_handle, changetype<usize>(key), key.byteLength, buffer.dataStart, buffer.length, i32(cursor), changetype<usize>(end), changetype<usize>(written));
31
+ const count = written[0], next = end[0];
32
+ if (status == FASTLY_STATUS_OK && count == 0 && cursor == 0 && next == 0) return output;
33
+ if (status != FASTLY_STATUS_OK || count < 0 || count > buffer.length
34
+ || (next != -1 && (next <= cursor || next > 4294967295 || count == 0))) {
35
+ __pulse_request_headers_fail(); return output;
36
+ }
37
+ bytes += count;
38
+ if (bytes > PULSE_FASTLY_BUFFER_BYTES) { __pulse_request_headers_fail(); return output; }
39
+ let start = 0;
40
+ for (let i = 0; i < count; i++) {
41
+ if (buffer[i] != 0) continue;
42
+ const size = i - start;
43
+ // Count name bytes once per returned pair, including both terminators.
44
+ if (!names) __pulse_request_headers_bytes += key.byteLength + size + 2;
45
+ if (output.length >= 256 || (names && size == 0) || __pulse_request_headers_bytes > PULSE_FASTLY_BUFFER_BYTES) {
46
+ __pulse_request_headers_fail(); return output;
47
+ }
48
+ output.push(String.UTF8.decodeUnsafe(buffer.dataStart + start, size, false));
49
+ start = i + 1;
50
+ }
51
+ if (start != count) { __pulse_request_headers_fail(); return output; }
52
+ if (next == -1) return output;
53
+ cursor = next;
54
+ }
55
+ __pulse_request_headers_fail(); return output;
56
+ }
57
+ function __pulse_request_headers_read(): i32 {
58
+ if (__pulse_request_headers_failed) { __pulse_request_headers_fail(); return 0; }
59
+ if (__pulse_request_headers_snapshot != 0) return __pulse_request_headers_snapshot;
60
+ const names = __pulse_request_header_list("", true);
61
+ if (__pulse_request_headers_failed) return 0;
62
+ const output = host_value_array();
63
+ let pairs = 0;
64
+ for (let n = 0; n < names.length; n++) {
65
+ const name = names[n].toLowerCase();
66
+ for (let previous = 0; previous < n; previous++) {
67
+ if (names[previous].toLowerCase() == name) { __pulse_request_headers_fail(); return 0; }
68
+ }
69
+ const values = __pulse_request_header_list(name, false);
70
+ if (__pulse_request_headers_failed) return 0;
71
+ if (values.length == 0 || pairs + values.length > 256) { __pulse_request_headers_fail(); return 0; }
72
+ pairs += values.length;
73
+ for (let v = 0; v < values.length; v++) {
74
+ const pair = host_value_array();
75
+ host_value_array_push(pair, __pulse_fastly_string_value(name));
76
+ host_value_array_push(pair, __pulse_fastly_string_value(values[v]));
77
+ host_value_array_push(output, pair);
78
+ }
79
+ }
80
+ __pulse_fastly_deep_freeze(output);
81
+ __pulse_request_headers_snapshot = output;
82
+ return output;
83
+ }
84
+ `;
85
+ }
86
+
87
+ function instrument(source, applicationErrors, used) {
88
+ if (applicationErrors || !used) return source;
89
+ return source.replace(/function (host_\w+)\(([^)]*)\): (i32|void) \{/g,
90
+ (declaration, _name, _args, result) => `${declaration} if (__pulse_request_headers_failed) return${result === 'void' ? '' : ' 0'};`);
91
+ }
92
+
93
+ module.exports = { runtimeSource, instrument };