@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.
- package/README.md +6 -6
- package/dist/config-schema.json +3 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
- package/src/build/canonical-target.js +5 -0
- package/src/build/digest-native.js +25 -0
- package/src/build/native-application-errors.js +117 -0
- package/src/build/native-http-effects.js +48 -12
- package/src/build/native-platform-capabilities.js +142 -39
- package/src/build/native-request-body.js +44 -0
- package/src/build/native-request-headers.js +93 -0
- package/src/build/native-string-values.js +113 -0
- package/src/build/request-budget.as.ts +49 -0
- package/src/build/request-budget.js +43 -0
- package/src/build/s3-native.as.ts +3 -1
- package/src/build/time-native.js +49 -0
- package/src/config-api.js +1 -0
- package/src/config-schema.json +3 -0
- package/src/index.ts +4 -0
- package/src/javascript/package-effects.js +4 -1
- package/src/javascript/runtime-host.js +5 -2
- package/src/javascript/target-support-policy.js +7 -0
- package/src/javascript/target.js +6 -1
- package/src/provider-contract.js +3 -1
- package/src/runtime/canonical-api-runtime.js +2 -0
- package/src/testing/native-platform-capabilities-host.js +54 -3
- package/src/toolchain/config.js +4 -1
- package/src/toolchain/index.js +1 -1
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
1484
|
-
if (depth > ${plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 128 : 64}) { __pulse_fastly_fail(PULSE_ERROR_JSON, 2, -1);
|
|
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 ==
|
|
1487
|
-
if (value.kind ==
|
|
1488
|
-
if (value.kind ==
|
|
1489
|
-
if (value.kind ==
|
|
1490
|
-
if (value.kind ==
|
|
1491
|
-
|
|
1492
|
-
|
|
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
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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 };
|