@pulse-compute/provider-fastly 1.0.0-beta.4 → 1.0.0-beta.6
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 +10 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
- package/src/build/canonical-target.js +15 -2
- package/src/build/digest-native.js +25 -0
- package/src/build/effect-invocations.js +49 -0
- package/src/build/embedded-assets.as.ts +32 -0
- package/src/build/kv-native.as.ts +18 -8
- package/src/build/native-application-errors.js +140 -0
- package/src/build/native-http-effects.js +87 -22
- package/src/build/native-platform-capabilities.js +480 -151
- 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/native-value-budget.js +58 -0
- package/src/build/package-native-application.js +94 -0
- package/src/build/request-budget.as.ts +49 -0
- package/src/build/request-budget.js +45 -0
- package/src/build/s3-body.as.ts +97 -0
- package/src/build/s3-native.as.ts +12 -4
- package/src/build/s3-native.js +2 -2
- package/src/build/schema-admission.js +83 -0
- package/src/build/schema-nested-json.js +93 -0
- package/src/build/schema-scalar-record.js +41 -0
- package/src/build/time-native.js +49 -0
- package/src/build/wasm-budget.js +28 -0
- package/src/config-api.js +3 -0
- package/src/config-schema.json +10 -0
- package/src/index.ts +9 -0
- package/src/javascript/jwt-verifier.js +14 -4
- package/src/javascript/package-effects.js +5 -2
- package/src/javascript/runtime-host.js +5 -2
- package/src/javascript/target-support-policy.js +18 -5
- package/src/javascript/target.js +16 -1
- package/src/provider-contract.js +7 -3
- package/src/runtime/canonical-api-runtime.js +2 -0
- package/src/testing/native-http-effects-host.js +5 -1
- package/src/testing/native-platform-capabilities-host.js +78 -6
- package/src/toolchain/config.js +8 -1
- package/src/toolchain/index.js +31 -2
- package/src/toolchain/native-execution.js +1 -0
- package/src/toolchain/s3.js +2 -2
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
@external('fastly_http_resp', 'header_values_get') declare function __s3_header_values(response: i32, name: usize, length: i32, buffer: usize, size: i32, cursor: i32, end: usize, written: usize): i32
|
|
11
11
|
|
|
12
12
|
class __PulseS3Binding {
|
|
13
|
+
method: string = ''; range: string = ''; ifNoneMatch: string = '';
|
|
13
14
|
contentType: string = ''; endpoint: string = ''; bucket: string = ''; region: string = ''; backend: string = '';
|
|
14
15
|
id: string = ''; secret: string = ''; token: string = ''; max: i32 = 32768; timeout: i32 = 10000;
|
|
15
16
|
}
|
|
@@ -35,6 +36,7 @@ function __s3_result(status: string, reason: string = '', http: i32 = 0): i32 {
|
|
|
35
36
|
return result;
|
|
36
37
|
}
|
|
37
38
|
function __s3_failure(index: i32, reason: string, http: i32 = 0): i32 {
|
|
39
|
+
if (__pulse_fastly_effect_kind(index) == 21) return __s3_body_failure(reason);
|
|
38
40
|
return __s3_result(__pulse_fastly_effect_kind(index) == 14 ? (__s3_dispatched[index] ? 'unknown' : 'not-stored') : 'failed', reason, http);
|
|
39
41
|
}
|
|
40
42
|
function __s3_fail(index: i32, reason: string): void { __pulse_fastly_ready_effect(index, __s3_failure(index, reason)) }
|
|
@@ -71,7 +73,9 @@ function __pulse_fastly_s3_begin(index: i32, outer: __PulseFastlyValue): void {
|
|
|
71
73
|
let data = new Uint8Array(0);
|
|
72
74
|
if (put) {
|
|
73
75
|
const text = __pulse_fastly_value(__pulse_fastly_payload_field(payload, 'text'));
|
|
74
|
-
if (text.kind != PULSE_VALUE_STRING
|
|
76
|
+
if (text.kind != PULSE_VALUE_STRING) { __s3_fail(index, 'invalid-text'); return; }
|
|
77
|
+
if (text.text.length > binding.max) { __s3_fail(index, 'too-large'); return; }
|
|
78
|
+
if (!__pulse_s3_scalar(text.text)) { __s3_fail(index, 'invalid-text'); return; }
|
|
75
79
|
data = __pulse_s3_bytes(text.text);
|
|
76
80
|
if (data.length > binding.max) { __s3_fail(index, 'too-large'); return; }
|
|
77
81
|
__pulse_fastly_remember_secret(text.text);
|
|
@@ -82,10 +86,10 @@ function __pulse_fastly_s3_begin(index: i32, outer: __PulseFastlyValue): void {
|
|
|
82
86
|
const token = binding.token.length ? __s3_secret(store[0], binding.token) : '';
|
|
83
87
|
if (__s3_remaining(index) == 0) { __s3_fail(index, 'timeout'); return; }
|
|
84
88
|
if (id === null || secret === null || token === null || !__pulse_s3_credentials(id, secret, token)) { __s3_fail(index, 'credentials'); return; }
|
|
85
|
-
const method = put ? 'PUT' : __pulse_fastly_effect_kind(index) == 12 ? 'HEAD' : 'GET', path = '/' + binding.bucket + '/' + encoded;
|
|
89
|
+
const method = binding.method.length ? binding.method : put ? 'PUT' : __pulse_fastly_effect_kind(index) == 12 ? 'HEAD' : 'GET', path = '/' + binding.bucket + '/' + encoded;
|
|
86
90
|
const now = __s3_clock(0);
|
|
87
91
|
if (now < 0 || !__pulse_s3_date(now / 1000000000).length) { __s3_fail(index, 'configuration'); return; }
|
|
88
|
-
const signature = __pulse_s3_sign(method, path, binding.endpoint.slice(8), binding.region, id, secret, token, now / 1000000000, data, binding.contentType);
|
|
92
|
+
const signature = __pulse_s3_sign(method, path, binding.endpoint.slice(8), binding.region, id, secret, token, now / 1000000000, data, binding.contentType, binding.range, binding.ifNoneMatch);
|
|
89
93
|
__s3_sent_bytes[index] = data.length; __s3_digests[index] = signature.digest;
|
|
90
94
|
__pulse_fastly_remember_secret(signature.digest);
|
|
91
95
|
__pulse_fastly_remember_secret(signature.authorization); __pulse_fastly_remember_secret(binding.endpoint + path);
|
|
@@ -98,6 +102,8 @@ function __pulse_fastly_s3_begin(index: i32, outer: __PulseFastlyValue): void {
|
|
|
98
102
|
&& __s3_header(req, 'host', binding.endpoint.slice(8)) && __s3_header(req, 'x-amz-date', signature.date)
|
|
99
103
|
&& __s3_header(req, 'x-amz-content-sha256', signature.digest) && __s3_header(req, 'authorization', signature.authorization)
|
|
100
104
|
&& __s3_header(req, 'accept-encoding', 'identity');
|
|
105
|
+
if (ok && binding.range.length) ok = __s3_header(req, 'range', binding.range);
|
|
106
|
+
if (ok && binding.ifNoneMatch.length) ok = __s3_header(req, 'if-none-match', binding.ifNoneMatch);
|
|
101
107
|
if (ok && put) ok = __s3_header(req, 'content-type', binding.contentType) && __s3_header(req, 'content-length', data.length.toString());
|
|
102
108
|
if (ok && token.length) ok = __s3_header(req, 'x-amz-security-token', token);
|
|
103
109
|
if (!ok || fastly_http_body_new(changetype<usize>(body)) != 0) { __s3_close_req(req); __s3_fail(index, 'transport'); return; }
|
|
@@ -133,6 +139,7 @@ function __s3_list(buffer: Uint8Array, size: i32): Array<string> | null {
|
|
|
133
139
|
return result;
|
|
134
140
|
}
|
|
135
141
|
function __s3_read_response(index: i32, response: i32, body: i32, status: i32): i32 {
|
|
142
|
+
if (__pulse_fastly_effect_kind(index) == 21) return __s3_body_response(index, response, body, status);
|
|
136
143
|
const put = __pulse_fastly_effect_kind(index) == 14;
|
|
137
144
|
const rejected = put && status >= 400 && status <= 499 && status != 408;
|
|
138
145
|
if (!put && status == 404) return __s3_result('not-found');
|
|
@@ -211,5 +218,6 @@ function __pulse_fastly_s3_wait(index: i32): i32 {
|
|
|
211
218
|
let result: i32;
|
|
212
219
|
if (fastly_http_resp_status_get(response[0], changetype<usize>(code)) != 0) result = __s3_failure(index, 'protocol');
|
|
213
220
|
else result = __s3_remaining(index) == 0 ? __s3_failure(index, 'timeout', code[0]) : __s3_read_response(index, response[0], body[0], code[0]);
|
|
214
|
-
|
|
221
|
+
if (!__pulse_fastly_value(result).s3Body) __s3_close_body(body[0]);
|
|
222
|
+
__s3_close_resp(response[0]); return result;
|
|
215
223
|
}
|
package/src/build/s3-native.js
CHANGED
|
@@ -21,10 +21,10 @@ function s3NativeSource(plan, bindings) {
|
|
|
21
21
|
const lines = ['function __pulse_fastly_s3_binding(index: i32): __PulseS3Binding {', 'const value = new __PulseS3Binding();', 'switch (index) {'];
|
|
22
22
|
for (const binding of bindings.s3) {
|
|
23
23
|
lines.push(`case ${binding.index}:`);
|
|
24
|
-
for (const [field, input] of Object.entries({ contentType: 'contentType', endpoint: 'endpoint', bucket: 'bucket', region: 'region', backend: 'backend', id: 'accessKeyIdSecret', secret: 'secretAccessKeySecret', token: 'sessionTokenSecret' })) lines.push(`value.${field} = ${JSON.stringify(binding[input] || '')};`);
|
|
24
|
+
for (const [field, input] of Object.entries({ method: 'method', range: 'range', ifNoneMatch: 'ifNoneMatch', contentType: 'contentType', endpoint: 'endpoint', bucket: 'bucket', region: 'region', backend: 'backend', id: 'accessKeyIdSecret', secret: 'secretAccessKeySecret', token: 'sessionTokenSecret' })) lines.push(`value.${field} = ${JSON.stringify(binding[input] || '')};`);
|
|
25
25
|
lines.push(`value.max = ${binding.maxTextBytes}; value.timeout = ${binding.timeoutMs}; break;`);
|
|
26
26
|
}
|
|
27
27
|
lines.push('default: unreachable();', '}', 'return value;', '}');
|
|
28
|
-
return [contribution.source, fs.readFileSync(path.join(__dirname, 's3-native.as.ts'), 'utf8'), lines.join('\n')].join('\n');
|
|
28
|
+
return [contribution.source, fs.readFileSync(path.join(__dirname, 's3-native.as.ts'), 'utf8'), fs.readFileSync(path.join(__dirname, 's3-body.as.ts'), 'utf8'), lines.join('\n')].join('\n');
|
|
29
29
|
}
|
|
30
30
|
module.exports = { S3_IMPORTS, s3NativeSource };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { schemaAdmission } = require('@pulse-compute/wasm-runtime-core-as/compiler');
|
|
4
|
+
|
|
5
|
+
function fastlyJsonValueAdmissionSource() {
|
|
6
|
+
return `
|
|
7
|
+
class __PulseFastlyAdmissionFrame {
|
|
8
|
+
index: i32 = 0
|
|
9
|
+
constructor(public handle: i32) {}
|
|
10
|
+
}
|
|
11
|
+
function __pulse_fastly_admission_enter(handle: i32, budget: __PulseJsonAdmissionBudget,
|
|
12
|
+
stack: Array<__PulseFastlyAdmissionFrame>, active: Set<i32>): bool {
|
|
13
|
+
if (handle <= 0 || handle > __pulse_fastly_values.length) return budget.fail(12)
|
|
14
|
+
const value = __pulse_fastly_values[handle - 1]
|
|
15
|
+
const array = value.kind == PULSE_VALUE_ARRAY
|
|
16
|
+
const object = value.kind == PULSE_VALUE_OBJECT
|
|
17
|
+
if (!budget.node(stack.length + (array || object ? 1 : 0))) return false
|
|
18
|
+
if (value.kind == PULSE_VALUE_NULL) return budget.addBytes(4)
|
|
19
|
+
if (value.kind == PULSE_VALUE_BOOLEAN) return budget.addBytes(value.boolean != 0 ? 4 : 5)
|
|
20
|
+
if (value.kind == PULSE_VALUE_NUMBER) return isFinite(value.number) ? budget.addBytes(24) : budget.fail(11)
|
|
21
|
+
if (value.kind == PULSE_VALUE_STRING) return budget.stringBytes(value.text, false)
|
|
22
|
+
if (!array && !object) return budget.fail(12)
|
|
23
|
+
if (active.has(handle)) return budget.fail(13)
|
|
24
|
+
if (array && value.values.length > budget.limits.maxArrayItems) return budget.fail(6)
|
|
25
|
+
if (object && value.keys.length > budget.limits.maxObjectMembers) return budget.fail(5)
|
|
26
|
+
if (object && value.keys.length != value.values.length) return budget.fail(12)
|
|
27
|
+
if (!budget.addBytes(2)) return false
|
|
28
|
+
active.add(handle)
|
|
29
|
+
stack.push(new __PulseFastlyAdmissionFrame(handle))
|
|
30
|
+
return true
|
|
31
|
+
}
|
|
32
|
+
function __pulse_fastly_admit_bounded_value(handle: i32, limits: __PulseJsonAdmissionLimits): __PulseJsonAdmissionBudget {
|
|
33
|
+
const budget = new __PulseJsonAdmissionBudget(limits)
|
|
34
|
+
const stack = new Array<__PulseFastlyAdmissionFrame>()
|
|
35
|
+
const active = new Set<i32>()
|
|
36
|
+
if (!__pulse_fastly_admission_enter(handle, budget, stack, active)) return budget
|
|
37
|
+
while (stack.length > 0) {
|
|
38
|
+
const frame = stack[stack.length - 1]
|
|
39
|
+
const value = __pulse_fastly_values[frame.handle - 1]
|
|
40
|
+
if (frame.index == value.values.length) { active.delete(frame.handle); stack.pop(); continue }
|
|
41
|
+
const index = frame.index++
|
|
42
|
+
if (index > 0 && !budget.addBytes(1)) return budget
|
|
43
|
+
if (value.kind == PULSE_VALUE_OBJECT) {
|
|
44
|
+
if (!budget.stringBytes(value.keys[index], true) || !budget.addBytes(1)) return budget
|
|
45
|
+
}
|
|
46
|
+
if (!__pulse_fastly_admission_enter(value.values[index], budget, stack, active)) return budget
|
|
47
|
+
}
|
|
48
|
+
return budget
|
|
49
|
+
}
|
|
50
|
+
`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Standalone composition used by focused admission fixtures.
|
|
54
|
+
function generateFastlyJsonAdmission(limits, parserMaxDepth = 64) {
|
|
55
|
+
const generated = schemaAdmission.generateJsonAdmission(limits);
|
|
56
|
+
if (!Number.isInteger(parserMaxDepth) || parserMaxDepth < 1 || parserMaxDepth > 0x7fffffff
|
|
57
|
+
|| generated.identity.limits.maxDepth > parserMaxDepth) {
|
|
58
|
+
throw new TypeError('JSON admission depth exceeds the selected Fastly parser capacity.');
|
|
59
|
+
}
|
|
60
|
+
return Object.freeze({ identity: generated.identity, source: `${generated.source}
|
|
61
|
+
${fastlyJsonValueAdmissionSource()}
|
|
62
|
+
function __pulse_fastly_admit_value(handle: i32): __PulseJsonAdmissionBudget {
|
|
63
|
+
return __pulse_fastly_admit_bounded_value(handle, __pulse_json_limits())
|
|
64
|
+
}
|
|
65
|
+
function __pulse_fastly_admitted_json(text: string, duplicateMode: i32 = 0): i32 {
|
|
66
|
+
const admission = __pulse_json_admit(text, duplicateMode)
|
|
67
|
+
if (admission.failure != 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 57, -1); return 0 }
|
|
68
|
+
return __pulse_fastly_parse_json(text)
|
|
69
|
+
}
|
|
70
|
+
function __pulse_fastly_admitted_json_text(handle: i32): string {
|
|
71
|
+
const admission = __pulse_fastly_admit_value(handle)
|
|
72
|
+
if (admission.failure != 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 57, -1); return '' }
|
|
73
|
+
const text = __pulse_fastly_json(handle, 0)
|
|
74
|
+
if (String.UTF8.byteLength(text) > admission.limits.maxTextBytes) {
|
|
75
|
+
__pulse_fastly_fail(PULSE_ERROR_SCHEMA, 57, -1)
|
|
76
|
+
return ''
|
|
77
|
+
}
|
|
78
|
+
return text
|
|
79
|
+
}
|
|
80
|
+
` });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = { generateFastlyJsonAdmission, fastlyJsonValueAdmissionSource };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { schemaHasNestedJson, schemaHasScalarRecord } = require('@pulse-compute/wasm-contracts/schema-json/registry');
|
|
4
|
+
const { fastlyJsonValueAdmissionSource } = require('./schema-admission.js');
|
|
5
|
+
|
|
6
|
+
function hasJsonAdmission(plan) { return (plan.schemas?.registry?.schemas || []).some(schema => schema.jsonLimits); }
|
|
7
|
+
function tracksJsonDuplicates(plan) {
|
|
8
|
+
return (plan.schemas?.registry?.schemas || []).some(schema => schemaHasNestedJson(schema.root) || schemaHasScalarRecord(schema.root));
|
|
9
|
+
}
|
|
10
|
+
function schemaParserDepth(plan, baseline = 64) {
|
|
11
|
+
return Math.max(baseline, ...(plan.schemas?.registry?.schemas || []).map(schema => schema.jsonLimits?.maxDepth || 0));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function nestedJsonProjectionLines(node) {
|
|
15
|
+
return [
|
|
16
|
+
...(node.kind === 'json-object' ? [' if (input.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 52, -1); return 0 }'] : []),
|
|
17
|
+
' return __pulse_fastly_copy_json(valueHandle, checkDuplicates)'
|
|
18
|
+
];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function schemaFetchJsonSource() {
|
|
22
|
+
return `
|
|
23
|
+
function __pulse_fastly_fetch_json_headers(input: __PulseFastlyValue): __PulseFastlyValue {
|
|
24
|
+
for (let index = 0; index < input.keys.length; index++) if (input.keys[index].toLowerCase() == "content-type") return input
|
|
25
|
+
const output = new __PulseFastlyValue()
|
|
26
|
+
output.kind = PULSE_VALUE_OBJECT
|
|
27
|
+
for (let index = 0; index < input.keys.length; index++) {
|
|
28
|
+
output.keys.push(input.keys[index]); output.values.push(input.values[index])
|
|
29
|
+
}
|
|
30
|
+
output.keys.push("content-type")
|
|
31
|
+
output.values.push(__pulse_fastly_string_value("application/json; charset=utf-8"))
|
|
32
|
+
return output
|
|
33
|
+
}
|
|
34
|
+
function __pulse_fastly_fetch_json_body(init: __PulseFastlyValue, valueHandle: i32): string {
|
|
35
|
+
const index = __pulse_fastly_find(init, "schema")
|
|
36
|
+
let value = valueHandle
|
|
37
|
+
if (index >= 0) {
|
|
38
|
+
const schema = __pulse_fastly_value(init.values[index])
|
|
39
|
+
if (schema.kind != PULSE_VALUE_STRING || schema.text.length == 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 55, -1); return '' }
|
|
40
|
+
value = __pulse_fastly_schema_apply(schema.text, valueHandle, true)
|
|
41
|
+
if (value <= 0 || __pulse_fastly_last_error != 0) return ''
|
|
42
|
+
}
|
|
43
|
+
return __pulse_fastly_json(value, 0)
|
|
44
|
+
}
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function schemaJsonAdmissionSource(plan) {
|
|
49
|
+
const schemas = plan.schemas?.registry?.schemas || [];
|
|
50
|
+
if (!hasJsonAdmission(plan)) return '';
|
|
51
|
+
const nested = schemas.some(schema => schemaHasNestedJson(schema.root));
|
|
52
|
+
return `${fastlyJsonValueAdmissionSource()}
|
|
53
|
+
function __pulse_fastly_schema_parse_json(text: string, schemaHandle: i32): i32 {
|
|
54
|
+
const schema = __pulse_fastly_value(schemaHandle)
|
|
55
|
+
if (schema.kind == PULSE_VALUE_STRING) {
|
|
56
|
+
${schemas.map((schema, index) => schema.jsonLimits ? ` if (schema.text == ${JSON.stringify(schema.id)} && __pulse_schema_json_scan_${index}(text, 0).failure != 0) {
|
|
57
|
+
__pulse_fastly_fail(PULSE_ERROR_SCHEMA, 57, -1); return 0
|
|
58
|
+
}` : '').join('\n')}
|
|
59
|
+
}
|
|
60
|
+
return __pulse_fastly_parse_json(text)
|
|
61
|
+
}
|
|
62
|
+
${nested ? `
|
|
63
|
+
class __PulseFastlyJsonCopyFrame {
|
|
64
|
+
index: i32 = 0
|
|
65
|
+
constructor(public input: i32, public output: i32) {}
|
|
66
|
+
}
|
|
67
|
+
function __pulse_fastly_json_copy_enter(handle: i32, duplicates: bool, frames: Array<__PulseFastlyJsonCopyFrame>): i32 {
|
|
68
|
+
const value = __pulse_fastly_value(handle)
|
|
69
|
+
if (value.kind != PULSE_VALUE_ARRAY && value.kind != PULSE_VALUE_OBJECT) return handle
|
|
70
|
+
if (duplicates && value.duplicateJsonKeys) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 57, -1); return 0 }
|
|
71
|
+
const copy = value.kind == PULSE_VALUE_ARRAY ? host_value_array() : host_value_object()
|
|
72
|
+
frames.push(new __PulseFastlyJsonCopyFrame(handle, copy))
|
|
73
|
+
return copy
|
|
74
|
+
}
|
|
75
|
+
function __pulse_fastly_copy_json(handle: i32, duplicates: bool): i32 {
|
|
76
|
+
const frames = new Array<__PulseFastlyJsonCopyFrame>()
|
|
77
|
+
const output = __pulse_fastly_json_copy_enter(handle, duplicates, frames)
|
|
78
|
+
while (frames.length > 0) {
|
|
79
|
+
const frame = frames[frames.length - 1]
|
|
80
|
+
const value = __pulse_fastly_value(frame.input)
|
|
81
|
+
if (frame.index == value.values.length) { frames.pop(); continue }
|
|
82
|
+
const index = frame.index++
|
|
83
|
+
const copy = __pulse_fastly_json_copy_enter(value.values[index], duplicates, frames)
|
|
84
|
+
if (copy <= 0) return 0
|
|
85
|
+
if (value.kind == PULSE_VALUE_ARRAY) host_value_array_push(frame.output, copy)
|
|
86
|
+
else host_value_object_set(frame.output, __pulse_fastly_string_value(value.keys[index]), copy)
|
|
87
|
+
}
|
|
88
|
+
return output
|
|
89
|
+
}
|
|
90
|
+
` : ''}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = { hasJsonAdmission, tracksJsonDuplicates, schemaParserDepth, nestedJsonProjectionLines, schemaJsonAdmissionSource, schemaFetchJsonSource };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { schemaHasScalarRecord } = require('@pulse-compute/wasm-contracts/schema-json/registry');
|
|
4
|
+
|
|
5
|
+
function hasScalarRecords(plan) {
|
|
6
|
+
return (plan.schemas?.registry?.schemas || []).some(schema => schemaHasScalarRecord(schema.root));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Both Fastly Native realizations validate handles before json-as sees them.
|
|
10
|
+
function scalarRecordProjectionLines(node) {
|
|
11
|
+
const limits = node.limits;
|
|
12
|
+
return [
|
|
13
|
+
' if (input.kind != PULSE_VALUE_OBJECT || (checkDuplicates && input.duplicateJsonKeys)) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 56, -1); return 0 }',
|
|
14
|
+
` if (input.keys.length > ${limits.maxKeys}) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 56, -1); return 0 }`,
|
|
15
|
+
' const keys = input.keys.slice().sort()',
|
|
16
|
+
' const output = host_value_object()',
|
|
17
|
+
' let bytes = 2',
|
|
18
|
+
' for (let i = 0; i < keys.length; i++) {',
|
|
19
|
+
' const key = keys[i]',
|
|
20
|
+
` if (key.length > ${limits.maxKeyLength}) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 56, -1); return 0 }`,
|
|
21
|
+
' const keyHandle = __pulse_fastly_string_value(key)',
|
|
22
|
+
' const itemHandle = host_value_property(valueHandle, keyHandle)',
|
|
23
|
+
' const item = __pulse_fastly_value(itemHandle)',
|
|
24
|
+
' let size = 0',
|
|
25
|
+
' if (item.kind == PULSE_VALUE_STRING) {',
|
|
26
|
+
` if (item.text.length > ${limits.maxStringLength}) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 56, -1); return 0 }`,
|
|
27
|
+
' size = __pulse_scalar_record_string_bytes(item.text)',
|
|
28
|
+
' } else if (item.kind == PULSE_VALUE_NUMBER && isFinite(item.number)) {',
|
|
29
|
+
` size = ${limits.numberBytes}`,
|
|
30
|
+
' } else if (item.kind == PULSE_VALUE_BOOLEAN) size = item.boolean != 0 ? 4 : 5',
|
|
31
|
+
' else if (item.kind == PULSE_VALUE_NULL) size = 4',
|
|
32
|
+
' else { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 56, -1); return 0 }',
|
|
33
|
+
' bytes += (i == 0 ? 0 : 1) + __pulse_scalar_record_string_bytes(key) + 1 + size',
|
|
34
|
+
` if (bytes > ${limits.maxBytes}) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 56, -1); return 0 }`,
|
|
35
|
+
' host_value_object_set(output, keyHandle, itemHandle)',
|
|
36
|
+
' }',
|
|
37
|
+
' return output'
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = { hasScalarRecords, scalarRecordProjectionLines };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Provider-owned realtime sampling. The portable guest still suspends through
|
|
4
|
+
// effect_begin; no clock import is exposed to application-authored code.
|
|
5
|
+
function timeNativeSource(plan) {
|
|
6
|
+
if (!(plan.effects || []).some(effect => effect.kind === 'time.now')) return '';
|
|
7
|
+
return `
|
|
8
|
+
function __pulse_time_pad(value: i32, width: i32): string {
|
|
9
|
+
let text = value.toString()
|
|
10
|
+
while (text.length < width) text = "0" + text
|
|
11
|
+
return text
|
|
12
|
+
}
|
|
13
|
+
function __pulse_time_iso(ms: u64): string {
|
|
14
|
+
// Gregorian civil date from whole days since the Unix epoch.
|
|
15
|
+
const z = <i32>(ms / 86400000) + 719468
|
|
16
|
+
const era = z / 146097
|
|
17
|
+
const doe = z - era * 146097
|
|
18
|
+
const yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365
|
|
19
|
+
let year = yoe + era * 400
|
|
20
|
+
const doy = doe - (365 * yoe + yoe / 4 - yoe / 100)
|
|
21
|
+
const mp = (5 * doy + 2) / 153
|
|
22
|
+
const day = doy - (153 * mp + 2) / 5 + 1
|
|
23
|
+
const month = mp + (mp < 10 ? 3 : -9)
|
|
24
|
+
if (month <= 2) year += 1
|
|
25
|
+
const rest = <i32>(ms % 86400000)
|
|
26
|
+
return __pulse_time_pad(year, 4) + "-" + __pulse_time_pad(month, 2) + "-" + __pulse_time_pad(day, 2)
|
|
27
|
+
+ "T" + __pulse_time_pad(rest / 3600000, 2) + ":" + __pulse_time_pad((rest / 60000) % 60, 2)
|
|
28
|
+
+ ":" + __pulse_time_pad((rest / 1000) % 60, 2) + "." + __pulse_time_pad(rest % 1000, 3) + "Z"
|
|
29
|
+
}
|
|
30
|
+
function __pulse_fastly_time_begin(effectIndex: i32, payload: __PulseFastlyValue): void {
|
|
31
|
+
const out = new StaticArray<u64>(1)
|
|
32
|
+
const status = wasi_snapshot_preview1_clock_time_get(0, 1000000, changetype<usize>(out))
|
|
33
|
+
const result = host_value_object()
|
|
34
|
+
if (status != 0) {
|
|
35
|
+
host_value_object_set(result, __pulse_fastly_string_value("status"), __pulse_fastly_string_value("failed"))
|
|
36
|
+
host_value_object_set(result, __pulse_fastly_string_value("reason"), __pulse_fastly_string_value("unavailable"))
|
|
37
|
+
} else {
|
|
38
|
+
// Divide integer nanoseconds before conversion to f64 to retain millisecond precision.
|
|
39
|
+
const ms = unchecked(out[0]) / 1000000
|
|
40
|
+
host_value_object_set(result, __pulse_fastly_string_value("status"), __pulse_fastly_string_value("ok"))
|
|
41
|
+
host_value_object_set(result, __pulse_fastly_string_value("unixEpochMs"), host_value_number(<f64>ms))
|
|
42
|
+
host_value_object_set(result, __pulse_fastly_string_value("iso8601"), __pulse_fastly_string_value(__pulse_time_iso(ms)))
|
|
43
|
+
}
|
|
44
|
+
__pulse_fastly_ready_effect(effectIndex, result)
|
|
45
|
+
}
|
|
46
|
+
`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = Object.freeze({ timeNativeSource });
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { fastlyConfigDefault } = require('../config-schema.js');
|
|
4
|
+
const DEFAULT_FASTLY_MAX_WASM_BYTES = fastlyConfigDefault('fastly.build.maxWasmBytes');
|
|
5
|
+
|
|
6
|
+
function normalizeFastlyMaxWasmBytes(value) {
|
|
7
|
+
if (value === undefined) return DEFAULT_FASTLY_MAX_WASM_BYTES;
|
|
8
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
9
|
+
const error = new TypeError('fastly.build.maxWasmBytes must be a positive safe integer byte count.');
|
|
10
|
+
error.code = 'PULSE_FASTLY_MAX_WASM_BYTES_INVALID';
|
|
11
|
+
throw error;
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function assertFastlyWasmBudget(bytes, value) {
|
|
17
|
+
const maxBytes = normalizeFastlyMaxWasmBytes(value);
|
|
18
|
+
if (bytes > maxBytes) {
|
|
19
|
+
const error = new Error('Generated Fastly Native module exceeds the configured Pulse compilation budget.');
|
|
20
|
+
error.name = 'FastlyNativePlatformCapabilitiesError';
|
|
21
|
+
error.code = 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_WASM_TOO_LARGE';
|
|
22
|
+
error.detail = Object.freeze({ bytes, maxBytes });
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
return maxBytes;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = Object.freeze({ DEFAULT_FASTLY_MAX_WASM_BYTES, normalizeFastlyMaxWasmBytes, assertFastlyWasmBudget });
|
package/src/config-api.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { normalizeFastlyMaxWasmBytes } = require('./build/wasm-budget.js');
|
|
3
4
|
const { fastlyConfigDefault } = require('./config-schema.js');
|
|
4
5
|
|
|
5
6
|
const FASTLY_PROVIDER_API_VERSION = 'pulse.provider-fastly-api.v2';
|
|
@@ -38,6 +39,7 @@ function fastly(options = {}) {
|
|
|
38
39
|
const normalizedGrip = options.grip === undefined ? undefined : gripBindings(options.grip);
|
|
39
40
|
return Object.freeze({
|
|
40
41
|
kind: 'fastly',
|
|
42
|
+
...(options.maxDurationMs === undefined ? {} : { maxDurationMs: require('@pulse-compute/runtime/host').normalizeRequestDuration(options.maxDurationMs) }),
|
|
41
43
|
version: FASTLY_PROVIDER_API_VERSION,
|
|
42
44
|
bindings: Object.freeze({
|
|
43
45
|
configStore: String(options.configStore ?? fastlyConfigDefault('fastly.configStore')),
|
|
@@ -48,6 +50,7 @@ function fastly(options = {}) {
|
|
|
48
50
|
dynamicBackends: options.dynamicBackends === undefined ? fastlyConfigDefault('fastly.dynamicBackends') : options.dynamicBackends === true
|
|
49
51
|
}),
|
|
50
52
|
build: Object.freeze({
|
|
53
|
+
maxWasmBytes: normalizeFastlyMaxWasmBytes(options.maxWasmBytes),
|
|
51
54
|
name: String(options.name ?? fastlyConfigDefault('fastly.name')),
|
|
52
55
|
description: String(options.description ?? fastlyConfigDefault('fastly.description')),
|
|
53
56
|
authors: Object.freeze((Array.isArray(options.authors) ? options.authors : fastlyConfigDefault('fastly.authors')).map(String)),
|
package/src/config-schema.json
CHANGED
|
@@ -72,6 +72,16 @@
|
|
|
72
72
|
"fastly.local.networkFetch": {
|
|
73
73
|
"kind": "boolean",
|
|
74
74
|
"default": false
|
|
75
|
+
},
|
|
76
|
+
"fastly.maxDurationMs": {
|
|
77
|
+
"kind": "optional-number"
|
|
78
|
+
},
|
|
79
|
+
"fastly.build.maxWasmBytes": {
|
|
80
|
+
"kind": "positive-safe-integer",
|
|
81
|
+
"default": 4194304
|
|
82
|
+
},
|
|
83
|
+
"fastly.maxWasmBytes": {
|
|
84
|
+
"kind": "positive-safe-integer"
|
|
75
85
|
}
|
|
76
86
|
}
|
|
77
87
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { normalizeFastlyMaxWasmBytes } = require('../src/build/wasm-budget.js') as { normalizeFastlyMaxWasmBytes(value?: number): number };
|
|
2
|
+
const { normalizeRequestDuration } = require('@pulse-compute/runtime/host') as { normalizeRequestDuration(value?: number): number | undefined };
|
|
1
3
|
import FASTLY_CONFIG_SCHEMA from './config-schema.json';
|
|
2
4
|
|
|
3
5
|
type FastlySchemaRule = Readonly<{ kind: string; default?: unknown }>;
|
|
@@ -23,6 +25,9 @@ export interface PulseFastlyLocalOptions {
|
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export interface PulseFastlyProviderOptions {
|
|
28
|
+
/** Inclusive final Native Wasm byte budget; default 4 MiB. */
|
|
29
|
+
readonly maxWasmBytes?: number;
|
|
30
|
+
readonly maxDurationMs?: number;
|
|
26
31
|
readonly configStore?: string;
|
|
27
32
|
readonly secretStore?: string;
|
|
28
33
|
readonly kv?: Readonly<Record<string, string>>;
|
|
@@ -36,6 +41,7 @@ export interface PulseFastlyProviderOptions {
|
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
export interface PulseFastlyProviderConfig {
|
|
44
|
+
readonly maxDurationMs?: number;
|
|
39
45
|
readonly kind: 'fastly';
|
|
40
46
|
readonly version: typeof FASTLY_PROVIDER_API_VERSION;
|
|
41
47
|
readonly bindings: {
|
|
@@ -57,6 +63,7 @@ export interface PulseFastlyProviderConfig {
|
|
|
57
63
|
readonly dynamicBackends: boolean;
|
|
58
64
|
};
|
|
59
65
|
readonly build: {
|
|
66
|
+
readonly maxWasmBytes: number;
|
|
60
67
|
readonly name: string;
|
|
61
68
|
readonly description: string;
|
|
62
69
|
readonly authors: readonly string[];
|
|
@@ -97,6 +104,7 @@ export function fastly(options: PulseFastlyProviderOptions = {}): PulseFastlyPro
|
|
|
97
104
|
const normalizedGrip = options.grip === undefined ? undefined : gripBindings(options.grip);
|
|
98
105
|
return Object.freeze({
|
|
99
106
|
kind: 'fastly',
|
|
107
|
+
maxDurationMs: normalizeRequestDuration(options.maxDurationMs),
|
|
100
108
|
version: FASTLY_PROVIDER_API_VERSION,
|
|
101
109
|
bindings: Object.freeze({
|
|
102
110
|
configStore: String(options.configStore ?? fastlyDefaults['fastly.configStore']),
|
|
@@ -107,6 +115,7 @@ export function fastly(options: PulseFastlyProviderOptions = {}): PulseFastlyPro
|
|
|
107
115
|
dynamicBackends: options.dynamicBackends === undefined ? Boolean(fastlyDefaults['fastly.dynamicBackends']) : options.dynamicBackends === true
|
|
108
116
|
}),
|
|
109
117
|
build: Object.freeze({
|
|
118
|
+
maxWasmBytes: normalizeFastlyMaxWasmBytes(options.maxWasmBytes),
|
|
110
119
|
name: String(options.name ?? fastlyDefaults['fastly.name']),
|
|
111
120
|
description: String(options.description ?? fastlyDefaults['fastly.description']),
|
|
112
121
|
authors: Object.freeze((options.authors ?? (Array.isArray(fastlyDefaults['fastly.authors']) ? fastlyDefaults['fastly.authors'] : [])).map(String)),
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
+
normalizeJwtSignEffect,
|
|
4
5
|
normalizeJwtVerifyEffect
|
|
5
6
|
} = require('@pulse-compute/wasm-contracts/jwt/contracts');
|
|
6
7
|
const {
|
|
7
8
|
CRYPTO_ES256_RUNTIME_BUILTIN_IMPLEMENTATION,
|
|
9
|
+
CRYPTO_RS256_RUNTIME_BUILTIN_IMPLEMENTATION,
|
|
8
10
|
CRYPTO_RUNTIME_BUILTIN_IMPLEMENTATION
|
|
9
11
|
} = require('@pulse-compute/wasm-contracts/crypto/contracts');
|
|
10
12
|
|
|
@@ -16,6 +18,7 @@ const FASTLY_JAVASCRIPT_JWT_REALIZATIONS = Object.freeze({
|
|
|
16
18
|
implementation: CRYPTO_RUNTIME_BUILTIN_IMPLEMENTATION,
|
|
17
19
|
automaticFallback: false
|
|
18
20
|
}),
|
|
21
|
+
RS256: Object.freeze({ algorithm: 'RS256', realization: 'runtime-builtin', implementation: CRYPTO_RS256_RUNTIME_BUILTIN_IMPLEMENTATION, automaticFallback: false }),
|
|
19
22
|
ES256: Object.freeze({
|
|
20
23
|
algorithm: 'ES256',
|
|
21
24
|
realization: 'runtime-builtin',
|
|
@@ -83,12 +86,19 @@ function createFastlyJavascriptJwtVerify(options = {}) {
|
|
|
83
86
|
return async function verifyFastlyJavascriptJwt(effect, execution) {
|
|
84
87
|
const { jwtProvider, cryptoProvider } = await loadRuntimeModules();
|
|
85
88
|
assertRequestActive(execution, jwtProvider);
|
|
86
|
-
const
|
|
87
|
-
|
|
89
|
+
const signing = effect.operation === 'sign';
|
|
90
|
+
const input = signing ? normalizeJwtSignEffect(effect) : normalizeJwtVerifyEffect(effect);
|
|
91
|
+
if (!signing && execution && typeof execution.registerRedactionValue === 'function') {
|
|
88
92
|
execution.registerRedactionValue(input.token);
|
|
89
93
|
}
|
|
90
94
|
assertRequestActive(execution, jwtProvider);
|
|
91
|
-
const selectedCrypto =
|
|
95
|
+
const selectedCrypto = signing
|
|
96
|
+
? (input.options.algorithm === 'RS256'
|
|
97
|
+
? require('@pulse-compute/crypto/provider').bindJavascriptRs256Signer()
|
|
98
|
+
: input.options.algorithm === 'ES256'
|
|
99
|
+
? require('@pulse-compute/crypto/provider').bindJavascriptEs256Signer()
|
|
100
|
+
: require('@pulse-compute/crypto/provider').bindJavascriptDigestMac(['HMAC-SHA256']))
|
|
101
|
+
: assertRuntimeBuiltin(cryptoProvider, jwtProvider);
|
|
92
102
|
const host = {
|
|
93
103
|
async captureWallClock() {
|
|
94
104
|
assertRequestActive(execution, jwtProvider);
|
|
@@ -118,7 +128,7 @@ function createFastlyJavascriptJwtVerify(options = {}) {
|
|
|
118
128
|
return result;
|
|
119
129
|
};
|
|
120
130
|
}
|
|
121
|
-
const result = await jwtProvider.verifyJwtWithCrypto(
|
|
131
|
+
const result = await (signing ? jwtProvider.signJwtWithCrypto : jwtProvider.verifyJwtWithCrypto)(
|
|
122
132
|
input,
|
|
123
133
|
Object.freeze(host),
|
|
124
134
|
selectedCrypto
|
|
@@ -15,10 +15,12 @@ function unavailable(effect = {}) {
|
|
|
15
15
|
function createFastlyJavascriptPackageEffectCapabilities(options = {}) {
|
|
16
16
|
const assetsLookup = options.assetsLookup;
|
|
17
17
|
const gripBroadcast = options.gripBroadcast;
|
|
18
|
+
const digestText = require('@pulse-compute/crypto/provider').createJavascriptTextDigest(options);
|
|
18
19
|
const jwtVerify = options.jwtVerify;
|
|
19
20
|
return Object.freeze({
|
|
20
21
|
version: FASTLY_JAVASCRIPT_PACKAGE_EFFECTS_VERSION,
|
|
21
22
|
effect(effect, execution) {
|
|
23
|
+
if (effect && effect.contractId === 'pulse.crypto' && effect.operation === 'digestText') return digestText(effect, execution);
|
|
22
24
|
if (effect && effect.contractId === 'pulse.assets' && effect.operation === 'lookup') {
|
|
23
25
|
if (typeof assetsLookup !== 'function') return unavailable(effect);
|
|
24
26
|
return assetsLookup(effect.payload, execution);
|
|
@@ -33,7 +35,7 @@ function createFastlyJavascriptPackageEffectCapabilities(options = {}) {
|
|
|
33
35
|
}
|
|
34
36
|
return gripBroadcast(effect.payload, execution);
|
|
35
37
|
}
|
|
36
|
-
if (effect && effect.contractId === 'pulse.jwt' && effect.operation
|
|
38
|
+
if (effect && effect.contractId === 'pulse.jwt' && ['verify', 'sign'].includes(effect.operation)) {
|
|
37
39
|
if (typeof jwtVerify !== 'function') return unavailable(effect);
|
|
38
40
|
return jwtVerify(effect, execution);
|
|
39
41
|
}
|
|
@@ -49,7 +51,8 @@ function withFastlyPackageEffectCapabilities(capabilities, options = {}) {
|
|
|
49
51
|
...base,
|
|
50
52
|
effect(effect, execution) {
|
|
51
53
|
if (effect && (
|
|
52
|
-
effect.contractId === 'pulse.
|
|
54
|
+
effect.contractId === 'pulse.crypto'
|
|
55
|
+
|| effect.contractId === 'pulse.assets'
|
|
53
56
|
|| effect.contractId === 'pulse.grip'
|
|
54
57
|
|| effect.contractId === 'pulse.jwt'
|
|
55
58
|
)) {
|
|
@@ -45,10 +45,10 @@ function createFastlyJavascriptCapabilities(options = {}) {
|
|
|
45
45
|
: undefined;
|
|
46
46
|
return withFastlyPackageEffectCapabilities(
|
|
47
47
|
withFastlyFetchCapability(
|
|
48
|
-
withFastlyBindingCapabilities(options.capabilities, { ...options, bindingCapabilities }),
|
|
48
|
+
withFastlyBindingCapabilities({ time: () => runtimeHost.readWallTime(options.wallClock === undefined ? () => Date.now() : options.wallClock), ...options.capabilities }, { ...options, bindingCapabilities }),
|
|
49
49
|
{ ...options, fetchImplementation: options.fetchImplementation }
|
|
50
50
|
),
|
|
51
|
-
{ assetsLookup, gripBroadcast, jwtVerify }
|
|
51
|
+
{ assetsLookup, gripBroadcast, jwtVerify, digestSubtle: options.digestSubtle }
|
|
52
52
|
);
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -57,6 +57,9 @@ function requestHeaderPairs(request) {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
async function executeFastlyJavascriptApplication(application, request, options = {}) {
|
|
60
|
+
if (options.maxDurationMs !== undefined || options.requestBudget !== undefined) {
|
|
61
|
+
throw new runtimeHost.PulseRuntimeContractError('PULSE_REQUEST_DURATION_UNSUPPORTED', 'Fastly JavaScript does not implement the request deadline; select Native.');
|
|
62
|
+
}
|
|
60
63
|
const capabilities = options.effectAdapter === undefined
|
|
61
64
|
? createFastlyJavascriptCapabilities(options)
|
|
62
65
|
: undefined;
|
|
@@ -16,8 +16,11 @@ function blockedRestriction(id, restriction) {
|
|
|
16
16
|
|
|
17
17
|
function classifyFastlyJavascriptCapability(id, restrictions = {}) {
|
|
18
18
|
const capability = String(id);
|
|
19
|
+
if (capability === 'request.body.transform') return decision(capability, 'blocked', 'fastly-body-transform-unavailable', 'provider-fastly');
|
|
20
|
+
if (capability === 'request.body.forward' || capability === 'req.body') return decision(capability, 'blocked', 'fastly-incoming-body-forwarding-unavailable', 'provider-fastly');
|
|
21
|
+
if (capability === 'crypto.digestText') return decision(capability, 'eligible', 'fastly-javascript-crypto-sha256-runtime-builtin', 'provider-fastly');
|
|
19
22
|
if (['kv.getVersioned', 'kv.insertIfAbsent', 'kv.compareAndSwap'].includes(capability)) return decision(capability, 'blocked', 'fastly-conditional-kv-incomplete', 'provider-fastly');
|
|
20
|
-
if (['s3.head', 's3.getText', 's3.putText'].includes(capability)) return decision(capability, 'blocked', 'fastly-javascript-s3-raw-headers-unavailable', 'provider-fastly');
|
|
23
|
+
if (['s3.head', 's3.getText', 's3.putText', 's3.getBody'].includes(capability)) return decision(capability, 'blocked', 'fastly-javascript-s3-raw-headers-unavailable', 'provider-fastly');
|
|
21
24
|
if (isJavascriptCoreCapability(capability)) {
|
|
22
25
|
return decision(capability, 'eligible', 'fastly-request-router-runtime', 'provider-fastly');
|
|
23
26
|
}
|
|
@@ -81,9 +84,11 @@ function classifyFastlyJavascriptCapability(id, restrictions = {}) {
|
|
|
81
84
|
return decision(capability, 'eligible', 'fastly-javascript-provider-wall-clock-authority', 'provider-fastly');
|
|
82
85
|
}
|
|
83
86
|
if (
|
|
84
|
-
capability === 'jwt.
|
|
87
|
+
capability === 'jwt.sign'
|
|
88
|
+
|| capability === 'jwt.verify'
|
|
85
89
|
|| capability === 'jwt.verify.hs256'
|
|
86
90
|
|| capability === 'jwt.verify.es256'
|
|
91
|
+
|| capability === 'jwt.verify.rs256'
|
|
87
92
|
) {
|
|
88
93
|
return blockedRestriction(capability, restrictions.jwt)
|
|
89
94
|
|| decision(capability, 'eligible', 'fastly-javascript-jwt-crypto-runtime-builtin', 'provider-fastly');
|
|
@@ -102,8 +107,9 @@ function classifyFastlyJavascriptCapability(id, restrictions = {}) {
|
|
|
102
107
|
|
|
103
108
|
function classifyFastlyJavascriptProviderRequirement(id, restrictions = {}) {
|
|
104
109
|
const requirement = String(id);
|
|
110
|
+
if (requirement === 'crypto.digestText') return decision(requirement, 'eligible', 'fastly-javascript-crypto-sha256-runtime-builtin', 'provider-fastly');
|
|
105
111
|
if (['kv.getVersioned', 'kv.insertIfAbsent', 'kv.compareAndSwap'].includes(requirement)) return decision(requirement, 'blocked', 'fastly-conditional-kv-incomplete', 'provider-fastly');
|
|
106
|
-
if (['s3.head', 's3.getText', 's3.putText'].includes(requirement)) return decision(requirement, 'blocked', 'fastly-javascript-s3-raw-headers-unavailable', 'provider-fastly');
|
|
112
|
+
if (['s3.head', 's3.getText', 's3.putText', 's3.getBody'].includes(requirement)) return decision(requirement, 'blocked', 'fastly-javascript-s3-raw-headers-unavailable', 'provider-fastly');
|
|
107
113
|
if (requirement === 'request' || ['response.json', 'response.text', 'response.custom'].includes(requirement)) {
|
|
108
114
|
return decision(requirement, 'eligible', 'fastly-request-response-lifecycle', 'provider-fastly');
|
|
109
115
|
}
|
|
@@ -145,9 +151,11 @@ function classifyFastlyJavascriptProviderRequirement(id, restrictions = {}) {
|
|
|
145
151
|
return decision(requirement, 'eligible', 'fastly-javascript-provider-wall-clock-authority', 'provider-fastly');
|
|
146
152
|
}
|
|
147
153
|
if (
|
|
148
|
-
requirement === 'jwt.
|
|
154
|
+
requirement === 'jwt.sign'
|
|
155
|
+
|| requirement === 'jwt.verify'
|
|
149
156
|
|| requirement === 'jwt.verify.hs256'
|
|
150
157
|
|| requirement === 'jwt.verify.es256'
|
|
158
|
+
|| requirement === 'jwt.verify.rs256'
|
|
151
159
|
) {
|
|
152
160
|
return blockedRestriction(requirement, restrictions.jwt)
|
|
153
161
|
|| decision(requirement, 'eligible', 'fastly-javascript-jwt-crypto-runtime-builtin', 'provider-fastly');
|
|
@@ -169,6 +177,11 @@ function restriction(reasonId, configuration) {
|
|
|
169
177
|
}
|
|
170
178
|
|
|
171
179
|
function fastlyJavascriptProjectRestrictions(compiled, project) {
|
|
180
|
+
if (project.providerConfig?.maxDurationMs !== undefined) {
|
|
181
|
+
const error = new TypeError('fastly.maxDurationMs requires the Native target; Fastly JavaScript does not implement the request deadline.');
|
|
182
|
+
error.code = 'PULSE_REQUEST_DURATION_UNSUPPORTED';
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
172
185
|
const bindings = project.providerConfig && project.providerConfig.bindings || {};
|
|
173
186
|
const backends = bindings.backends && typeof bindings.backends === 'object' ? bindings.backends : {};
|
|
174
187
|
const kv = bindings.kv && typeof bindings.kv === 'object' ? bindings.kv : {};
|
|
@@ -181,7 +194,7 @@ function fastlyJavascriptProjectRestrictions(compiled, project) {
|
|
|
181
194
|
const secretSites = sites.filter((entry) => entry.kind === 'secret.get');
|
|
182
195
|
const jwtSites = sites.filter((entry) => (
|
|
183
196
|
entry.contractId === 'pulse.jwt'
|
|
184
|
-
&& (
|
|
197
|
+
&& (['verify', 'sign'].includes(entry.operation) || ['jwt.verify', 'jwt.sign'].includes(entry.kind))
|
|
185
198
|
));
|
|
186
199
|
const output = {};
|
|
187
200
|
|