@pulse-compute/provider-fastly 1.0.0-beta.3 → 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,6 +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 {',
1117
+ ...(needsNativeValueFailureGuard(plan) ? [' if (__pulse_fastly_last_error != PULSE_ERROR_NONE) return'] : []),
1097
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 }',
1098
1119
  ' const payloadValue = __pulse_fastly_value(payload)',
1099
1120
  ' if (payloadValue.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 47, effectIndex); return }',
@@ -1262,19 +1283,35 @@ const PULSE_ERROR_SCHEMA: i32 = 1005
1262
1283
  const PULSE_ERROR_TRANSPORT: i32 = 1006
1263
1284
  const PULSE_ERROR_STATE: i32 = 1007
1264
1285
  const PULSE_ERROR_JWT: i32 = 1008
1286
+ const PULSE_ERROR_REQUEST_BODY: i32 = 1009
1265
1287
 
1266
1288
  class __PulseFastlyValue {
1267
1289
  kind: i32 = PULSE_VALUE_UNDEFINED
1268
1290
  boolean: i32 = 0
1269
1291
  number: f64 = 0.0
1270
- text: string = ""
1271
- keys: Array<string> = new Array<string>()
1272
- 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
+ }
1273
1305
  responseHandle: i32 = 0
1274
1306
  bodyHandle: i32 = 0
1275
1307
  bodyLoaded: i32 = 0
1276
1308
  status: i32 = 200
1277
- 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
+ }
1278
1315
  immutable: bool = false
1279
1316
  }
1280
1317
  class __PulseFastlyHeader {
@@ -1404,6 +1441,12 @@ const PULSE_FASTLY_PENDING_NONE: i32 = 0
1404
1441
  const PULSE_FASTLY_PENDING_ASYNC: i32 = 1
1405
1442
  const PULSE_FASTLY_PENDING_READY: i32 = 2
1406
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>()
1407
1450
  @lazy const __pulse_fastly_pending = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT)
1408
1451
  @lazy const __pulse_fastly_pending_mode = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT)
1409
1452
  @lazy const __pulse_fastly_payloads = new StaticArray<i32>(PULSE_FASTLY_EFFECT_COUNT)
@@ -1439,8 +1482,18 @@ function __pulse_fastly_utf8(value: string): ArrayBuffer { return String.UTF8.en
1439
1482
  function __pulse_fastly_decode(buffer: Uint8Array, length: i32): string { return String.UTF8.decodeUnsafe(buffer.dataStart, length, false) }
1440
1483
  function __pulse_fastly_put(value: __PulseFastlyValue): i32 { __pulse_fastly_values.push(value); return __pulse_fastly_values.length }
1441
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}
1442
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])) }
1443
- 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
+ }
1444
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 }
1445
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() }
1446
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) }
@@ -1479,21 +1532,26 @@ function __pulse_fastly_quote(value: string): string {
1479
1532
  out[cursor] = 34;
1480
1533
  return String.UTF16.decodeUnsafe(out.dataStart, out.byteLength);
1481
1534
  }
1482
- function __pulse_fastly_json(handle: i32, depth: i32): string {
1483
- 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 }
1484
1537
  const value = __pulse_fastly_value(handle)
1485
- if (value.kind == PULSE_VALUE_UNDEFINED || value.kind == PULSE_VALUE_NULL) return "null"
1486
- if (value.kind == PULSE_VALUE_BOOLEAN) return value.boolean != 0 ? "true" : "false"
1487
- if (value.kind == PULSE_VALUE_NUMBER) return __pulse_fastly_number_string(value.number)
1488
- if (value.kind == PULSE_VALUE_STRING) return __pulse_fastly_quote(value.text)
1489
- 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 + "]" }
1490
- 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 + "}" }
1491
- 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("")
1492
1550
  }
1493
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 }
1494
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) }
1495
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)) }
1496
- function __pulse_fastly_read_body(handle: i32, effectIndex: i32): string {
1554
+ function __pulse_fastly_read_body(handle: i32, effectIndex: i32, requestText: bool = false): string {
1497
1555
  const chunks = new Array<Uint8Array>(); let total = 0;
1498
1556
  while (true) {
1499
1557
  const buffer = new Uint8Array(PULSE_FASTLY_BUFFER_BYTES), read = __pulse_fastly_out_i32();
@@ -1503,36 +1561,55 @@ function __pulse_fastly_read_body(handle: i32, effectIndex: i32): string {
1503
1561
  if (count < 0 || count > buffer.length) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 20, effectIndex); return "" }
1504
1562
  if (!count) break;
1505
1563
  total += count;
1506
- 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 "" }
1507
1565
  chunks.push(buffer.subarray(0, count));
1508
1566
  }
1509
1567
  // UTF-8 scalars may straddle host read chunks. Decode the bounded body once.
1510
1568
  const bytes = new Uint8Array(total); let offset = 0;
1511
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 "" }
1512
1571
  return __pulse_fastly_decode(bytes, total);
1513
1572
  }
1514
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)) }
1515
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)) }
1516
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 }
1517
1576
 
1518
- function host_value_undefined(): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_UNDEFINED; return __pulse_fastly_put(item) }
1519
- function host_value_null(): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_NULL; return __pulse_fastly_put(item) }
1520
- 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) }
1521
- function host_value_number(value: f64): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_NUMBER; item.number = value; return __pulse_fastly_put(item) }
1522
- 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
+ }
1523
1600
  function host_value_array(): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_ARRAY; return __pulse_fastly_put(item) }
1524
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) }
1525
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])) }
1526
1603
  function host_value_object(): i32 { const item = new __PulseFastlyValue(); item.kind = PULSE_VALUE_OBJECT; return __pulse_fastly_put(item) }
1527
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) } }
1528
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])) }
1529
- 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() }
1530
1607
  function host_value_property_set(target: i32, key: i32, value: i32): i32 { host_value_object_set(target, key, value); return value }
1531
- 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) }
1532
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) }
1533
- 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 }
1534
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 }
1535
- 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 }
1536
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 }
1537
1614
 
1538
1615
  function host_request_method(): i32 { return __pulse_fastly_string_value(__pulse_fastly_request_method) }
@@ -1543,13 +1620,35 @@ function __pulse_router_param_text(path: string, pattern: string, name: string):
1543
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) }
1544
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) }
1545
1622
  function host_request_path(): i32 { return __pulse_fastly_string_value(__pulse_fastly_request_path) }
1546
- function host_request_headers(): i32 { return host_value_object() }
1623
+ function host_request_headers(): i32 { return __pulse_request_headers_read() }
1547
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) }
1548
- 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) }
1549
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 }
1550
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])))) } } }
1551
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) }
1552
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
+ }
1643
+ function host_schema_encode(value: i32, schema: i32): i32 {
1644
+ const id = __pulse_fastly_value(schema)
1645
+ if (id.kind != PULSE_VALUE_STRING || id.text.length == 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 55, -1); return 0 }
1646
+ const projected = __pulse_fastly_schema_apply(id.text, value, true)
1647
+ if (projected <= 0) return 0
1648
+ const text = __pulse_fastly_json(projected, 0)
1649
+ if (String.UTF8.byteLength(text) > __PULSE_SCHEMA_MAX_BYTES) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 21, -1); return 0 }
1650
+ return __pulse_fastly_string_value(text)
1651
+ }
1553
1652
  function host_response_text(value: i32, options: i32): i32 { return __pulse_fastly_response(value, options, false) }
1554
1653
  function host_response_custom(specHandle: i32): i32 { const spec = __pulse_fastly_value(specHandle); if (spec.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 40, -1); return 0 } const output = new __PulseFastlyValue(); output.kind = PULSE_VALUE_RESPONSE; output.status = 200; const statusIndex = __pulse_fastly_find(spec, "status"); if (statusIndex >= 0) output.status = i32(__pulse_fastly_number(unchecked(spec.values[statusIndex]))); const bodyIndex = __pulse_fastly_find(spec, "body"); if (bodyIndex >= 0) output.text = __pulse_fastly_string(unchecked(spec.values[bodyIndex])); __pulse_fastly_headers_from_options(spec, output); return __pulse_fastly_put(output) }
1555
1654
  function __pulse_fastly_grip_frame_channels(options: __PulseFastlyValue, output: __PulseFastlyValue): bool { const channelsIndex = __pulse_fastly_find(options, "channels"); const channelIndex = __pulse_fastly_find(options, "channel"); let count = 0; if (channelsIndex >= 0) { const channels = __pulse_fastly_value(unchecked(options.values[channelsIndex])); if (channels.kind != PULSE_VALUE_ARRAY) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 133, -1); return false } for (let i = 0; i < channels.values.length; i += 1) { const channel = __pulse_fastly_string(unchecked(channels.values[i])).trim(); if (channel.length == 0 || channel.indexOf(",") >= 0 || channel.indexOf("\r") >= 0 || channel.indexOf("\n") >= 0) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 134, -1); return false } output.headers.push(new __PulseFastlyHeader("Grip-Channel", channel)); count += 1 } } else if (channelIndex >= 0) { const channel = __pulse_fastly_string(unchecked(options.values[channelIndex])).trim(); if (channel.length == 0 || channel.indexOf(",") >= 0 || channel.indexOf("\r") >= 0 || channel.indexOf("\n") >= 0) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 134, -1); return false } output.headers.push(new __PulseFastlyHeader("Grip-Channel", channel)); count = 1 } if (count == 0) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 135, -1); return false } return true }
@@ -1641,7 +1740,7 @@ export function ${entryName}(): void {
1641
1740
  if (__pulse_fastly_last_error != 0) { __pulse_fastly_jwt_send_error(); return }
1642
1741
  __pulse_fastly_request_path = __pulse_fastly_path(__pulse_fastly_request_url)
1643
1742
  let runStatus = pulse_start()
1644
- while (runStatus == 1 && __pulse_fastly_last_error == 0) {
1743
+ ${applicationErrors.enabled(plan) ? applicationErrors.driverLoop(plan) : ` while (runStatus == 1 && __pulse_fastly_last_error == 0) {
1645
1744
  for (let effectIndex = 0; effectIndex < PULSE_FASTLY_EFFECT_COUNT; effectIndex += 1) {
1646
1745
  if (unchecked(__pulse_fastly_pending_mode[effectIndex]) == PULSE_FASTLY_PENDING_NONE) continue
1647
1746
  const result = __pulse_fastly_resolve_effect(effectIndex)
@@ -1649,7 +1748,7 @@ export function ${entryName}(): void {
1649
1748
  if (pulse_set_effect_result(effectIndex, result) != 1) { __pulse_fastly_fail(PULSE_ERROR_STATE, 101, effectIndex); __pulse_fastly_jwt_send_error(); return }
1650
1749
  }
1651
1750
  runStatus = pulse_resume()
1652
- }
1751
+ }`}
1653
1752
  if (__pulse_fastly_last_error != 0) { __pulse_fastly_jwt_send_error(); return }
1654
1753
  if (runStatus != 0) { __pulse_fastly_fail(PULSE_ERROR_STATE, 102, -1); __pulse_fastly_jwt_send_error(); return }
1655
1754
  const result = pulse_result_handle()
@@ -1662,6 +1761,7 @@ export function ${entryName}(): void {
1662
1761
  }
1663
1762
 
1664
1763
  function capabilityImports(effect) {
1764
+ if (effect.kind === 'time.now') return ['wasi_snapshot_preview1.clock_time_get'];
1665
1765
  if (effect.kind === 'config.get') return ['fastly_config_store.open', 'fastly_config_store.get'];
1666
1766
  if (effect.kind === 'secret.get') return ['fastly_secret_store.open', 'fastly_secret_store.get', 'fastly_secret_store.plaintext'];
1667
1767
  if (effect.kind === 'jwt.verify') {
@@ -1724,7 +1824,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1724
1824
  const portableSource = stripPulseHostImports(portable.source);
1725
1825
  const hasJwt = (plan.effects || []).some((effect) => effect.kind === 'jwt.verify');
1726
1826
  const jwtSource = hasJwt ? pulseJwtAssemblyScriptSource() : undefined;
1727
- const source = [
1827
+ let source = [
1728
1828
  '/* Generated by Pulse Pass98 Fastly native platform capability compiler. */',
1729
1829
  'export function __pulse_fastly_abort(message: string | null, fileName: string | null = null, line: u32 = 0, column: u32 = 0): void { unreachable() }',
1730
1830
  '',
@@ -1735,11 +1835,20 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1735
1835
  }),
1736
1836
  jwtSource ? jwtSource.source : '',
1737
1837
  require('./s3-native.js').s3NativeSource(plan, bindings),
1838
+ require('./time-native.js').timeNativeSource(plan),
1839
+ require('./digest-native.js').digestNativeSource(plan),
1738
1840
  conditionalKv.kvNativeSource(plan),
1841
+ require('./native-request-body.js').runtimeSource(),
1842
+ require('./native-request-headers.js').runtimeSource(),
1843
+ applicationErrors.enabled(plan) ? applicationErrors.runtimeSource() : '',
1739
1844
  portableSource,
1740
1845
  driverSource(plan, { guestLinked: facts.guestUnits.length > 0 }),
1741
1846
  ''
1742
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);
1743
1852
  const effectKinds = Object.freeze((plan.effects || []).reduce((output, effect) => {
1744
1853
  output[effect.kind] = (output[effect.kind] || 0) + 1;
1745
1854
  return output;
@@ -1799,7 +1908,8 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1799
1908
  'pulse_fastly_error_stage',
1800
1909
  'pulse_fastly_error_effect',
1801
1910
  'pulse_plan_hash_ptr',
1802
- 'pulse_plan_hash_length'
1911
+ 'pulse_plan_hash_length',
1912
+ ...(bindings.maxDurationMs === undefined ? [] : ['pulse_fastly_request_expired'])
1803
1913
  ]),
1804
1914
  policy: Object.freeze({
1805
1915
  nativeFastly: true,
@@ -1807,7 +1917,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
1807
1917
  providerNeutralInput: true,
1808
1918
  javascriptRuntime: false,
1809
1919
  jsComputeRuntime: false,
1810
- 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,
1811
1921
  effects: FASTLY_NATIVE_PLATFORM_EFFECT_KINDS.join(', '),
1812
1922
  continuations: true,
1813
1923
  groupedEffects: 'existing fetch start-before-wait ordering remains intact',
@@ -1943,6 +2053,9 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
1943
2053
  '--optimize'
1944
2054
  ];
1945
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
+ }
1946
2059
  if (generated.guestUnits.length > 0) {
1947
2060
  args.push(
1948
2061
  '--disable', 'bulk-memory',
@@ -1972,7 +2085,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
1972
2085
  maxBuffer: 64 * 1024 * 1024,
1973
2086
  timeout: Number(options.timeoutMs || 180000),
1974
2087
  env: schemaCodecsActive
1975
- ? { ...process.env, JSON_STRICT: 'true', JSON_USE_FAST_PATH: '0' }
2088
+ ? { ...process.env, JSON_STRICT: 'true', JSON_USE_FAST_PATH: '0', JSON_MODE: 'NAIVE' }
1976
2089
  : process.env
1977
2090
  });
1978
2091
  const assemblyScriptDurationMs = Date.now() - startedAt;
@@ -2112,7 +2225,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
2112
2225
  compilerVersion: FASTLY_NATIVE_PLATFORM_CAPABILITIES_COMPILER_VERSION,
2113
2226
  assemblyScript: Object.freeze({ package: 'assemblyscript', version: require(path.join(asc.packageRoot, 'package.json')).version }),
2114
2227
  jsonAs: jsonAs
2115
- ? Object.freeze({ package: 'json-as', version: jsonAs.version, transform: true, strict: true })
2228
+ ? Object.freeze({ package: 'json-as', version: jsonAs.version, transform: true, strict: true, mode: 'NAIVE', fastPath: false })
2116
2229
  : undefined,
2117
2230
  optimization,
2118
2231
  wasm: Object.freeze({ bytes: inspection.bytes, sha256: inspection.sha256, magic: inspection.magic }),
@@ -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 };