@pulse-compute/provider-fastly 1.0.0-beta.5 → 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 +7 -0
- package/dist/index.d.ts +3 -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 +10 -2
- 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 +49 -26
- package/src/build/native-http-effects.js +41 -12
- package/src/build/native-platform-capabilities.js +346 -120
- package/src/build/native-string-values.js +1 -1
- package/src/build/native-value-budget.js +58 -0
- package/src/build/package-native-application.js +94 -0
- package/src/build/request-budget.js +3 -1
- package/src/build/s3-body.as.ts +97 -0
- package/src/build/s3-native.as.ts +9 -3
- 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/wasm-budget.js +28 -0
- package/src/config-api.js +2 -0
- package/src/config-schema.json +7 -0
- package/src/index.ts +5 -0
- package/src/javascript/jwt-verifier.js +14 -4
- package/src/javascript/package-effects.js +1 -1
- package/src/javascript/target-support-policy.js +11 -5
- package/src/javascript/target.js +10 -0
- package/src/provider-contract.js +4 -2
- package/src/testing/native-http-effects-host.js +5 -1
- package/src/testing/native-platform-capabilities-host.js +24 -3
- package/src/toolchain/config.js +4 -0
- package/src/toolchain/index.js +30 -1
- package/src/toolchain/native-execution.js +1 -0
- package/src/toolchain/s3.js +2 -2
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const { normalizeFastlyMaxWasmBytes } = require('../src/build/wasm-budget.js') as { normalizeFastlyMaxWasmBytes(value?: number): number };
|
|
1
2
|
const { normalizeRequestDuration } = require('@pulse-compute/runtime/host') as { normalizeRequestDuration(value?: number): number | undefined };
|
|
2
3
|
import FASTLY_CONFIG_SCHEMA from './config-schema.json';
|
|
3
4
|
|
|
@@ -24,6 +25,8 @@ export interface PulseFastlyLocalOptions {
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
export interface PulseFastlyProviderOptions {
|
|
28
|
+
/** Inclusive final Native Wasm byte budget; default 4 MiB. */
|
|
29
|
+
readonly maxWasmBytes?: number;
|
|
27
30
|
readonly maxDurationMs?: number;
|
|
28
31
|
readonly configStore?: string;
|
|
29
32
|
readonly secretStore?: string;
|
|
@@ -60,6 +63,7 @@ export interface PulseFastlyProviderConfig {
|
|
|
60
63
|
readonly dynamicBackends: boolean;
|
|
61
64
|
};
|
|
62
65
|
readonly build: {
|
|
66
|
+
readonly maxWasmBytes: number;
|
|
63
67
|
readonly name: string;
|
|
64
68
|
readonly description: string;
|
|
65
69
|
readonly authors: readonly string[];
|
|
@@ -111,6 +115,7 @@ export function fastly(options: PulseFastlyProviderOptions = {}): PulseFastlyPro
|
|
|
111
115
|
dynamicBackends: options.dynamicBackends === undefined ? Boolean(fastlyDefaults['fastly.dynamicBackends']) : options.dynamicBackends === true
|
|
112
116
|
}),
|
|
113
117
|
build: Object.freeze({
|
|
118
|
+
maxWasmBytes: normalizeFastlyMaxWasmBytes(options.maxWasmBytes),
|
|
114
119
|
name: String(options.name ?? fastlyDefaults['fastly.name']),
|
|
115
120
|
description: String(options.description ?? fastlyDefaults['fastly.description']),
|
|
116
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
|
|
@@ -35,7 +35,7 @@ function createFastlyJavascriptPackageEffectCapabilities(options = {}) {
|
|
|
35
35
|
}
|
|
36
36
|
return gripBroadcast(effect.payload, execution);
|
|
37
37
|
}
|
|
38
|
-
if (effect && effect.contractId === 'pulse.jwt' && effect.operation
|
|
38
|
+
if (effect && effect.contractId === 'pulse.jwt' && ['verify', 'sign'].includes(effect.operation)) {
|
|
39
39
|
if (typeof jwtVerify !== 'function') return unavailable(effect);
|
|
40
40
|
return jwtVerify(effect, execution);
|
|
41
41
|
}
|
|
@@ -16,9 +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');
|
|
19
21
|
if (capability === 'crypto.digestText') return decision(capability, 'eligible', 'fastly-javascript-crypto-sha256-runtime-builtin', 'provider-fastly');
|
|
20
22
|
if (['kv.getVersioned', 'kv.insertIfAbsent', 'kv.compareAndSwap'].includes(capability)) return decision(capability, 'blocked', 'fastly-conditional-kv-incomplete', 'provider-fastly');
|
|
21
|
-
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');
|
|
22
24
|
if (isJavascriptCoreCapability(capability)) {
|
|
23
25
|
return decision(capability, 'eligible', 'fastly-request-router-runtime', 'provider-fastly');
|
|
24
26
|
}
|
|
@@ -82,9 +84,11 @@ function classifyFastlyJavascriptCapability(id, restrictions = {}) {
|
|
|
82
84
|
return decision(capability, 'eligible', 'fastly-javascript-provider-wall-clock-authority', 'provider-fastly');
|
|
83
85
|
}
|
|
84
86
|
if (
|
|
85
|
-
capability === 'jwt.
|
|
87
|
+
capability === 'jwt.sign'
|
|
88
|
+
|| capability === 'jwt.verify'
|
|
86
89
|
|| capability === 'jwt.verify.hs256'
|
|
87
90
|
|| capability === 'jwt.verify.es256'
|
|
91
|
+
|| capability === 'jwt.verify.rs256'
|
|
88
92
|
) {
|
|
89
93
|
return blockedRestriction(capability, restrictions.jwt)
|
|
90
94
|
|| decision(capability, 'eligible', 'fastly-javascript-jwt-crypto-runtime-builtin', 'provider-fastly');
|
|
@@ -105,7 +109,7 @@ function classifyFastlyJavascriptProviderRequirement(id, restrictions = {}) {
|
|
|
105
109
|
const requirement = String(id);
|
|
106
110
|
if (requirement === 'crypto.digestText') return decision(requirement, 'eligible', 'fastly-javascript-crypto-sha256-runtime-builtin', 'provider-fastly');
|
|
107
111
|
if (['kv.getVersioned', 'kv.insertIfAbsent', 'kv.compareAndSwap'].includes(requirement)) return decision(requirement, 'blocked', 'fastly-conditional-kv-incomplete', 'provider-fastly');
|
|
108
|
-
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');
|
|
109
113
|
if (requirement === 'request' || ['response.json', 'response.text', 'response.custom'].includes(requirement)) {
|
|
110
114
|
return decision(requirement, 'eligible', 'fastly-request-response-lifecycle', 'provider-fastly');
|
|
111
115
|
}
|
|
@@ -147,9 +151,11 @@ function classifyFastlyJavascriptProviderRequirement(id, restrictions = {}) {
|
|
|
147
151
|
return decision(requirement, 'eligible', 'fastly-javascript-provider-wall-clock-authority', 'provider-fastly');
|
|
148
152
|
}
|
|
149
153
|
if (
|
|
150
|
-
requirement === 'jwt.
|
|
154
|
+
requirement === 'jwt.sign'
|
|
155
|
+
|| requirement === 'jwt.verify'
|
|
151
156
|
|| requirement === 'jwt.verify.hs256'
|
|
152
157
|
|| requirement === 'jwt.verify.es256'
|
|
158
|
+
|| requirement === 'jwt.verify.rs256'
|
|
153
159
|
) {
|
|
154
160
|
return blockedRestriction(requirement, restrictions.jwt)
|
|
155
161
|
|| decision(requirement, 'eligible', 'fastly-javascript-jwt-crypto-runtime-builtin', 'provider-fastly');
|
|
@@ -188,7 +194,7 @@ function fastlyJavascriptProjectRestrictions(compiled, project) {
|
|
|
188
194
|
const secretSites = sites.filter((entry) => entry.kind === 'secret.get');
|
|
189
195
|
const jwtSites = sites.filter((entry) => (
|
|
190
196
|
entry.contractId === 'pulse.jwt'
|
|
191
|
-
&& (
|
|
197
|
+
&& (['verify', 'sign'].includes(entry.operation) || ['jwt.verify', 'jwt.sign'].includes(entry.kind))
|
|
192
198
|
));
|
|
193
199
|
const output = {};
|
|
194
200
|
|
package/src/javascript/target.js
CHANGED
|
@@ -14,6 +14,11 @@ const FASTLY_JAVASCRIPT_TARGET_DESCRIPTOR = normalizeJavascriptTargetDescriptor(
|
|
|
14
14
|
crypto: defineCryptoTargetCapabilities({
|
|
15
15
|
target: 'javascript',
|
|
16
16
|
algorithms: [{
|
|
17
|
+
algorithm: 'HMAC-SHA256',
|
|
18
|
+
realization: 'runtime-builtin',
|
|
19
|
+
implemented: true,
|
|
20
|
+
status: 'implemented-jwt-sign'
|
|
21
|
+
}, {
|
|
17
22
|
algorithm: 'SHA-256',
|
|
18
23
|
realization: 'runtime-builtin',
|
|
19
24
|
implemented: true,
|
|
@@ -28,6 +33,11 @@ const FASTLY_JAVASCRIPT_TARGET_DESCRIPTOR = normalizeJavascriptTargetDescriptor(
|
|
|
28
33
|
realization: 'runtime-builtin',
|
|
29
34
|
implemented: true,
|
|
30
35
|
status: 'implemented-h4'
|
|
36
|
+
}, {
|
|
37
|
+
algorithm: 'RS256',
|
|
38
|
+
realization: 'runtime-builtin',
|
|
39
|
+
implemented: true,
|
|
40
|
+
status: 'implemented-h4'
|
|
31
41
|
}]
|
|
32
42
|
}),
|
|
33
43
|
capabilities: {
|
package/src/provider-contract.js
CHANGED
|
@@ -14,8 +14,8 @@ const FASTLY_PROVIDER_DESCRIPTOR = canonical.normalizeDescriptor({
|
|
|
14
14
|
capabilities: [
|
|
15
15
|
'request', 'response.json', 'response.text', 'response.custom', 'fetch',
|
|
16
16
|
'time.wall-clock', 'crypto.digestText', 'config.get', 'secret.get', 'kv.get', 'kv.put', 'kv.getVersioned', 'kv.insertIfAbsent', 'kv.compareAndSwap', 'assets.lookup',
|
|
17
|
-
'grip.channel', 'grip.hold', 'grip.publish', 'grip.broadcast', 'jwt.verify',
|
|
18
|
-
'opaque.pass-through', 's3.head', 's3.getText', 's3.putText'
|
|
17
|
+
'grip.channel', 'grip.hold', 'grip.publish', 'grip.broadcast', 'jwt.verify', 'jwt.sign',
|
|
18
|
+
'opaque.pass-through', 's3.head', 's3.getText', 's3.putText', 's3.getBody'
|
|
19
19
|
],
|
|
20
20
|
lowering: {
|
|
21
21
|
request: 'fastly.compute.request',
|
|
@@ -38,8 +38,10 @@ const FASTLY_PROVIDER_DESCRIPTOR = canonical.normalizeDescriptor({
|
|
|
38
38
|
'grip.publish': 'fastly.fanout.publish',
|
|
39
39
|
'grip.broadcast': 'fastly.grip.publish-control',
|
|
40
40
|
'jwt.verify': 'fastly.native.jwt.verify',
|
|
41
|
+
'jwt.sign': 'fastly.native.jwt.sign',
|
|
41
42
|
's3.head': 'fastly.native.s3.head',
|
|
42
43
|
's3.getText': 'fastly.native.s3.getText',
|
|
44
|
+
's3.getBody': 'fastly.native.s3.getBody',
|
|
43
45
|
's3.putText': 'fastly.native.s3.putText',
|
|
44
46
|
'opaque.pass-through': 'fastly.response-body.stream'
|
|
45
47
|
}
|
|
@@ -80,6 +80,7 @@ function executeFastlyNativeHttpEffects(input, options = {}) {
|
|
|
80
80
|
const bodies = new Map();
|
|
81
81
|
const responses = new Map();
|
|
82
82
|
const pending = new Map();
|
|
83
|
+
const outboundRequests = [];
|
|
83
84
|
const downstreamRequestHandle = 1;
|
|
84
85
|
const downstreamBodyHandle = 2;
|
|
85
86
|
requests.set(downstreamRequestHandle, { method, url, headers: requestHeaders.map((entry) => [...entry]) });
|
|
@@ -230,7 +231,9 @@ function executeFastlyNativeHttpEffects(input, options = {}) {
|
|
|
230
231
|
return FASTLY_STATUS_ERROR;
|
|
231
232
|
}
|
|
232
233
|
const pendingHandle = alloc('pending');
|
|
233
|
-
|
|
234
|
+
const outbound = { ...request, headers: request.headers.map((entry) => [...entry]), body: bodyBytes(bodyHandle) };
|
|
235
|
+
outboundRequests.push(outbound);
|
|
236
|
+
pending.set(pendingHandle, { request: outbound, backend, fixture });
|
|
234
237
|
writeU32(pendingOut, pendingHandle);
|
|
235
238
|
trace.push({ module: 'fastly_http_req', name: 'send_async', handle: Number(handle), bodyHandle: Number(bodyHandle), pendingHandle, backend, method: request.method, url: request.url });
|
|
236
239
|
return FASTLY_STATUS_OK;
|
|
@@ -373,6 +376,7 @@ function executeFastlyNativeHttpEffects(input, options = {}) {
|
|
|
373
376
|
bodyHandle: downstream.bodyHandle
|
|
374
377
|
}),
|
|
375
378
|
trace: Object.freeze(trace.map((entry) => Object.freeze({ ...entry }))),
|
|
379
|
+
outboundRequests: Object.freeze(outboundRequests.map(entry => Object.freeze(entry))),
|
|
376
380
|
instance
|
|
377
381
|
});
|
|
378
382
|
}
|
|
@@ -204,6 +204,15 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
204
204
|
body = JSON.stringify(normalized.json);
|
|
205
205
|
if (!headers.some(([name]) => name.toLowerCase() === 'content-type')) headers.push(['content-type', 'application/json; charset=utf-8']);
|
|
206
206
|
}
|
|
207
|
+
// Ordinary Pulse harness fixtures use value/text; preserve the ABI fixture
|
|
208
|
+
// body's explicit precedence and its existing json shorthand.
|
|
209
|
+
if (body === undefined && Object.prototype.hasOwnProperty.call(normalized, 'value')) {
|
|
210
|
+
body = JSON.stringify(normalized.value);
|
|
211
|
+
if (!headers.some(([name]) => name.toLowerCase() === 'content-type')) headers.push(['content-type', 'application/json; charset=utf-8']);
|
|
212
|
+
} else if (body === undefined && Object.prototype.hasOwnProperty.call(normalized, 'text')) {
|
|
213
|
+
body = String(normalized.text);
|
|
214
|
+
if (!headers.some(([name]) => name.toLowerCase() === 'content-type')) headers.push(['content-type', 'text/plain; charset=utf-8']);
|
|
215
|
+
}
|
|
207
216
|
responses.set(responseHandle, {
|
|
208
217
|
status: Number(normalized.status || 200),
|
|
209
218
|
headers,
|
|
@@ -607,7 +616,7 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
607
616
|
}
|
|
608
617
|
},
|
|
609
618
|
fastly_http_body: {
|
|
610
|
-
close(handle) { trace.push({ module: 'fastly_http_body', name: 'close' }); return bodies.delete(Number(handle)) ? FASTLY_STATUS_OK : FASTLY_STATUS_BADF; },
|
|
619
|
+
close(handle) { trace.push({ module: 'fastly_http_body', name: 'close', handle: Number(handle) }); if (downstream?.streaming && downstream.bodyHandle === Number(handle)) { downstream.bodyBytes = bodyBytes(handle); downstream.finished = true; } return bodies.delete(Number(handle)) ? FASTLY_STATUS_OK : FASTLY_STATUS_BADF; },
|
|
611
620
|
new(handleOut) {
|
|
612
621
|
const handle = alloc('body');
|
|
613
622
|
bodies.set(handle, { bytes: Buffer.alloc(0), readOffset: 0, writes: [] });
|
|
@@ -632,6 +641,7 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
632
641
|
write(handle, buffer, bufferLength, end, writtenOut) {
|
|
633
642
|
const body = bodies.get(Number(handle));
|
|
634
643
|
if (!body) return FASTLY_STATUS_BADF;
|
|
644
|
+
if (downstream?.streaming && Number(handle) === downstream.bodyHandle && options.downstreamBodyWriteStatus) return Number(options.downstreamBodyWriteStatus);
|
|
635
645
|
if (requests.size > 1 && options.outboundBodyWriteStatus) return Number(options.outboundBodyWriteStatus);
|
|
636
646
|
const length = Math.min(Number(bufferLength), Number(options.bodyWriteChunkBytes || bufferLength));
|
|
637
647
|
const bytes = readBytes(buffer, length);
|
|
@@ -665,7 +675,16 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
665
675
|
throw new FastlyNativePlatformCapabilitiesMockError('Pass98 native module is missing _start.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_MOCK_START_MISSING');
|
|
666
676
|
}
|
|
667
677
|
if (options.conditionalKv && options.conditionalKv.deadlineNs !== undefined) instance.exports.pulse_fastly_kv_request_deadline(BigInt(options.conditionalKv.deadlineNs));
|
|
668
|
-
instance.exports._start();
|
|
678
|
+
try { instance.exports._start(); }
|
|
679
|
+
catch (error) {
|
|
680
|
+
if (Number(instance.exports.pulse_fastly_last_error?.()) !== 1010) throw error;
|
|
681
|
+
throw new FastlyNativePlatformCapabilitiesMockError('Native retained-value budget exceeded.',
|
|
682
|
+
'PULSE_RUNTIME_MEMORY_LIMIT_EXCEEDED', {
|
|
683
|
+
lastError: 1010, errorStage: Number(instance.exports.pulse_fastly_error_stage()), trace,
|
|
684
|
+
bytes: Number(instance.exports.pulse_fastly_memory_bytes()),
|
|
685
|
+
values: Number(instance.exports.pulse_fastly_memory_values())
|
|
686
|
+
});
|
|
687
|
+
}
|
|
669
688
|
for (const entry of trace) if (entry.url && privateUrls.has(entry.url)) entry.url = '[REDACTED]';
|
|
670
689
|
const lastError = typeof instance.exports.pulse_fastly_last_error === 'function'
|
|
671
690
|
? Number(instance.exports.pulse_fastly_last_error())
|
|
@@ -679,10 +698,11 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
679
698
|
const pulseLastError = typeof instance.exports.pulse_last_error_code === 'function'
|
|
680
699
|
? Number(instance.exports.pulse_last_error_code())
|
|
681
700
|
: undefined;
|
|
701
|
+
if (downstream?.streaming && !downstream.finished) { downstream.bodyBytes = bodyBytes(downstream.bodyHandle); downstream.aborted = true; }
|
|
682
702
|
const handledJwtError = options.allowHandledJwtError === true
|
|
683
703
|
&& lastError === 1008
|
|
684
704
|
&& Boolean(downstream);
|
|
685
|
-
if (lastError !== FASTLY_STATUS_OK && !handledJwtError) {
|
|
705
|
+
if (lastError !== FASTLY_STATUS_OK && !handledJwtError && !(options.allowAbortedResponse === true && downstream?.aborted)) {
|
|
686
706
|
throw new FastlyNativePlatformCapabilitiesMockError('Fastly native platform capabilities module reported an execution error.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_MOCK_EXECUTION_FAILED', { lastError, errorStage, errorEffect, pulseLastError, trace });
|
|
687
707
|
}
|
|
688
708
|
if (!downstream) {
|
|
@@ -699,6 +719,7 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
699
719
|
bodyBytes: downstream.bodyBytes,
|
|
700
720
|
sent: true,
|
|
701
721
|
streaming: downstream.streaming,
|
|
722
|
+
aborted: downstream.aborted === true,
|
|
702
723
|
origin: downstream.origin,
|
|
703
724
|
responseHandle: downstream.responseHandle,
|
|
704
725
|
bodyHandle: downstream.bodyHandle
|
package/src/toolchain/config.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
+
const { normalizeFastlyMaxWasmBytes } = require('../build/wasm-budget.js');
|
|
2
3
|
const { normalizeFastlyS3 } = require('./s3.js');
|
|
3
4
|
|
|
4
5
|
function providerConfigError(code, message, detail) {
|
|
@@ -55,6 +56,7 @@ function normalizeFastlyProviderConfig(value) {
|
|
|
55
56
|
dynamicBackends: bindings.dynamicBackends === true
|
|
56
57
|
}),
|
|
57
58
|
build: Object.freeze({
|
|
59
|
+
maxWasmBytes: normalizeFastlyMaxWasmBytes(input.build?.maxWasmBytes),
|
|
58
60
|
name: String(input.build && input.build.name || 'pulse-app'),
|
|
59
61
|
description: String(input.build && input.build.description || 'Pulse application'),
|
|
60
62
|
authors: Object.freeze(Array.isArray(input.build && input.build.authors) ? input.build.authors.map(String) : [])
|
|
@@ -102,6 +104,8 @@ const FASTLY_PROJECT_CONFIG_REFERENCE = Object.freeze({
|
|
|
102
104
|
})
|
|
103
105
|
]),
|
|
104
106
|
fields: Object.freeze([
|
|
107
|
+
Object.freeze({section:'fastly',path:'fastly.maxWasmBytes',type:'integer',allowed:'positive safe integer',default:'4194304 (4 MiB)',scope:'fastly() facade',description:'Facade option projected to build.maxWasmBytes. Conventional profiles configure fastly.build.maxWasmBytes.'}),
|
|
108
|
+
Object.freeze({section:'fastly',path:'fastly.build.maxWasmBytes',type:'integer',allowed:'positive safe integer',default:'4194304 (4 MiB)',scope:'Native final Wasm artifact',description:'Inclusive byte budget checked for newly compiled and reused artifacts. Raising it does not increase runtime memory or change Fastly service limits. The fastly() facade accepts maxWasmBytes.'}),
|
|
105
109
|
Object.freeze({section:'fastly',path:'fastly.maxDurationMs',type:'integer',allowed:'1–30000',default:'omitted',scope:'HTTP request execution',description:'One provider-owned monotonic budget through buffered response handoff; expiry attempts 504 before headers commit and never asserts rollback of dispatched writes. No CPU preemption or post-handoff delivery guarantee.'}),
|
|
106
110
|
Object.freeze({ section: 'fastly', path: 'fastly.bindings.s3', type: 'Readonly<Record<string, S3Binding & { backend: string }>>', default: '`{}`', scope: 'Fastly Native S3', description: 'Maps logical names to fixed HTTPS endpoint, bucket, region, named static backend, accessKeyIdSecret, secretAccessKeySecret, optional sessionTokenSecret, maxTextBytes (1–2097152, default 32768) and timeoutMs (1–30000, default 10000).', security: 'Credentials resolve through the configured Secret Store at execution. Dynamic backend authority and guest endpoint overrides are forbidden.' }),
|
|
107
111
|
Object.freeze({ section: 'fastly', path: 'fastly.configStore', type: 'string', default: '`pulse_config`', scope: 'Fastly config capability', description: 'Fastly Config Store resource name.' }),
|
package/src/toolchain/index.js
CHANGED
|
@@ -169,16 +169,25 @@ function createDriver() {
|
|
|
169
169
|
runtimeClass: 'native',
|
|
170
170
|
status: 'supported',
|
|
171
171
|
capabilities: Object.freeze([
|
|
172
|
-
's3.head', 's3.getText',
|
|
172
|
+
's3.head', 's3.getText', 's3.getBody',
|
|
173
173
|
'kv.getVersioned', 'kv.insertIfAbsent', 'kv.compareAndSwap',
|
|
174
174
|
'jwt.verify',
|
|
175
|
+
'jwt.sign',
|
|
175
176
|
'jwt.verify.hs256',
|
|
176
177
|
'jwt.verify.es256',
|
|
178
|
+
'jwt.verify.rs256',
|
|
177
179
|
'secret.get',
|
|
178
180
|
'time.wall-clock'
|
|
179
181
|
]),
|
|
180
182
|
keyTypes: Object.freeze(['secret', 'jwk', 'jwks']),
|
|
181
183
|
realizations: Object.freeze([
|
|
184
|
+
Object.freeze({
|
|
185
|
+
kind: 'package-native-application',
|
|
186
|
+
contractId: 'pulse.entities',
|
|
187
|
+
implementation: 'pulse.package-native-application.v1',
|
|
188
|
+
implemented: true,
|
|
189
|
+
automaticFallback: false
|
|
190
|
+
}),
|
|
182
191
|
Object.freeze({
|
|
183
192
|
kind: 'crypto-composed',
|
|
184
193
|
realization: 'guest-source:pulse-hmac-as',
|
|
@@ -204,6 +213,19 @@ function createDriver() {
|
|
|
204
213
|
guestUnitRequired: true,
|
|
205
214
|
portable: true,
|
|
206
215
|
automaticFallback: false
|
|
216
|
+
}),
|
|
217
|
+
Object.freeze({
|
|
218
|
+
kind: 'crypto-composed',
|
|
219
|
+
realization: 'guest-linked:pulse-rs256-bearssl-i31',
|
|
220
|
+
implementation: 'bearssl.0.6.rsa-i31.sha256.v1',
|
|
221
|
+
algorithms: Object.freeze(['RS256']),
|
|
222
|
+
keyTypes: Object.freeze(['jwk', 'jwks']),
|
|
223
|
+
implemented: true,
|
|
224
|
+
status: 'implemented-g4',
|
|
225
|
+
semanticOwner: '@pulse-compute/crypto',
|
|
226
|
+
guestUnitRequired: true,
|
|
227
|
+
portable: true,
|
|
228
|
+
automaticFallback: false
|
|
207
229
|
})
|
|
208
230
|
]),
|
|
209
231
|
jwt: Object.freeze({
|
|
@@ -233,6 +255,11 @@ function createDriver() {
|
|
|
233
255
|
realization: 'guest-linked:pulse-es256-rustcrypto-p256',
|
|
234
256
|
implemented: true,
|
|
235
257
|
status: 'implemented-g4'
|
|
258
|
+
}, {
|
|
259
|
+
algorithm: 'RS256',
|
|
260
|
+
realization: 'guest-linked:pulse-rs256-bearssl-i31',
|
|
261
|
+
implemented: true,
|
|
262
|
+
status: 'implemented-g4'
|
|
236
263
|
}]
|
|
237
264
|
}),
|
|
238
265
|
finalWasmPolicy: fastlyFinalWasmPolicy,
|
|
@@ -258,6 +285,7 @@ function createDriver() {
|
|
|
258
285
|
realizationArtifacts: invocation.nativeArtifact.realizationArtifacts,
|
|
259
286
|
guestUnits: invocation.nativeArtifact.guestUnits,
|
|
260
287
|
nativeOptimization: invocation.optimization,
|
|
288
|
+
emitWat: invocation.nativeArtifact.manifest.wat?.emitted === true,
|
|
261
289
|
compileTimeoutMs: invocation.timeoutMs
|
|
262
290
|
});
|
|
263
291
|
const { native, ...publicRealization } = realization;
|
|
@@ -286,6 +314,7 @@ function createDriver() {
|
|
|
286
314
|
realizationArtifacts: invocation.nativeArtifact.realizationArtifacts,
|
|
287
315
|
guestUnits: invocation.nativeArtifact.guestUnits,
|
|
288
316
|
nativeOptimization: invocation.optimization,
|
|
317
|
+
emitWat: invocation.nativeArtifact.manifest.wat?.emitted === true,
|
|
289
318
|
compileTimeoutMs: invocation.timeoutMs
|
|
290
319
|
});
|
|
291
320
|
const providerBuild = writeFastlyCanonicalTarget({
|
|
@@ -18,6 +18,7 @@ function prepareFastlyNativeExecution(invocation) {
|
|
|
18
18
|
realizationArtifacts: invocation.nativeArtifact.realizationArtifacts,
|
|
19
19
|
guestUnits: invocation.nativeArtifact.guestUnits,
|
|
20
20
|
nativeOptimization: invocation.optimization,
|
|
21
|
+
emitWat: invocation.nativeArtifact.manifest.wat?.emitted === true,
|
|
21
22
|
compileTimeoutMs: invocation.timeoutMs || undefined
|
|
22
23
|
});
|
|
23
24
|
const evidence = Object.freeze({
|
package/src/toolchain/s3.js
CHANGED
|
@@ -17,10 +17,10 @@ function normalizeFastlyS3(input = {}) {
|
|
|
17
17
|
function resolveFastlyS3(effects, input) {
|
|
18
18
|
const configured = normalizeFastlyS3(input);
|
|
19
19
|
return Object.freeze(effects.flatMap((effect, index) => {
|
|
20
|
-
if (!['s3.head', 's3.getText', 's3.putText'].includes(effect.kind)) return [];
|
|
20
|
+
if (!['s3.head', 's3.getText', 's3.putText', 's3.getBody'].includes(effect.kind)) return [];
|
|
21
21
|
const name = effect.resource && effect.resource.binding;
|
|
22
22
|
if (!Object.hasOwn(configured, name)) throw new TypeError('The required Fastly S3 binding is missing.');
|
|
23
|
-
return [Object.freeze({ index, ...configured[name], contentType: effect.operation === 'putText' ? require('@pulse-compute/s3/provider').normalizePutOptions({ contentType: effect.payload && effect.payload.contentType }).contentType : '' })];
|
|
23
|
+
return [Object.freeze({ index, ...configured[name], ...(effect.operation === 'getBody' ? require('@pulse-compute/s3/provider').normalizeBodyOptions(Object.fromEntries(['method', 'range', 'ifNoneMatch'].filter(key => effect.payload?.[key] !== undefined).map(key => [key, effect.payload[key]]))) : {}), contentType: effect.operation === 'putText' ? require('@pulse-compute/s3/provider').normalizePutOptions({ contentType: effect.payload && effect.payload.contentType }).contentType : '' })];
|
|
24
24
|
}));
|
|
25
25
|
}
|
|
26
26
|
module.exports = { normalizeFastlyS3, resolveFastlyS3 };
|