@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
package/src/javascript/target.js
CHANGED
|
@@ -14,6 +14,16 @@ 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
|
+
}, {
|
|
22
|
+
algorithm: 'SHA-256',
|
|
23
|
+
realization: 'runtime-builtin',
|
|
24
|
+
implemented: true,
|
|
25
|
+
status: 'implemented-d1'
|
|
26
|
+
}, {
|
|
17
27
|
algorithm: 'HS256',
|
|
18
28
|
realization: 'runtime-builtin',
|
|
19
29
|
implemented: true,
|
|
@@ -23,6 +33,11 @@ const FASTLY_JAVASCRIPT_TARGET_DESCRIPTOR = normalizeJavascriptTargetDescriptor(
|
|
|
23
33
|
realization: 'runtime-builtin',
|
|
24
34
|
implemented: true,
|
|
25
35
|
status: 'implemented-h4'
|
|
36
|
+
}, {
|
|
37
|
+
algorithm: 'RS256',
|
|
38
|
+
realization: 'runtime-builtin',
|
|
39
|
+
implemented: true,
|
|
40
|
+
status: 'implemented-h4'
|
|
26
41
|
}]
|
|
27
42
|
}),
|
|
28
43
|
capabilities: {
|
|
@@ -45,7 +60,7 @@ const FASTLY_JAVASCRIPT_TARGET_DESCRIPTOR = normalizeJavascriptTargetDescriptor(
|
|
|
45
60
|
kv: true,
|
|
46
61
|
parallel: true
|
|
47
62
|
},
|
|
48
|
-
packages: ['pulse.assets', 'pulse.grip', 'pulse.jwt'],
|
|
63
|
+
packages: ['pulse.assets', 'pulse.crypto', 'pulse.grip', 'pulse.jwt'],
|
|
49
64
|
restrictions: {
|
|
50
65
|
fetch: 'origin-backend-map-or-dynamic-opt-in',
|
|
51
66
|
kv: 'logical-store-map-required',
|
package/src/provider-contract.js
CHANGED
|
@@ -13,9 +13,9 @@ const FASTLY_PROVIDER_DESCRIPTOR = canonical.normalizeDescriptor({
|
|
|
13
13
|
localExecution: true,
|
|
14
14
|
capabilities: [
|
|
15
15
|
'request', 'response.json', 'response.text', 'response.custom', 'fetch',
|
|
16
|
-
'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'
|
|
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', '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',
|
|
@@ -23,6 +23,8 @@ const FASTLY_PROVIDER_DESCRIPTOR = canonical.normalizeDescriptor({
|
|
|
23
23
|
'response.text': 'fastly.compute.response.text',
|
|
24
24
|
'response.custom': 'fastly.compute.response.custom',
|
|
25
25
|
fetch: 'fastly.backend.fetch',
|
|
26
|
+
'time.wall-clock': 'wasi.clock_time_get.realtime',
|
|
27
|
+
'crypto.digestText': 'pulse.crypto.guest-source.sha256',
|
|
26
28
|
'config.get': 'fastly.config-store.get',
|
|
27
29
|
'secret.get': 'fastly.secret-store.get',
|
|
28
30
|
'kv.get': 'fastly.kv-store.get',
|
|
@@ -36,8 +38,10 @@ const FASTLY_PROVIDER_DESCRIPTOR = canonical.normalizeDescriptor({
|
|
|
36
38
|
'grip.publish': 'fastly.fanout.publish',
|
|
37
39
|
'grip.broadcast': 'fastly.grip.publish-control',
|
|
38
40
|
'jwt.verify': 'fastly.native.jwt.verify',
|
|
41
|
+
'jwt.sign': 'fastly.native.jwt.sign',
|
|
39
42
|
's3.head': 'fastly.native.s3.head',
|
|
40
43
|
's3.getText': 'fastly.native.s3.getText',
|
|
44
|
+
's3.getBody': 'fastly.native.s3.getBody',
|
|
41
45
|
's3.putText': 'fastly.native.s3.putText',
|
|
42
46
|
'opaque.pass-through': 'fastly.response-body.stream'
|
|
43
47
|
}
|
|
@@ -344,6 +344,8 @@ function createFastlyProviderAdapter(baseOptions = {}) {
|
|
|
344
344
|
async dispatchEffect(effect, executionOptions = {}) {
|
|
345
345
|
const state = stateFor(executionOptions);
|
|
346
346
|
if (effect.kind === 'fetch') return dispatchFetch(effect, executionOptions);
|
|
347
|
+
if (effect.kind === 'crypto.digestText') return require('@pulse-compute/crypto/provider').createJavascriptTextDigest(baseOptions)(effect, executionOptions);
|
|
348
|
+
if (effect.kind === 'time.now') return require('@pulse-compute/runtime/host').readWallTime(baseOptions.wallClock === undefined ? () => Date.now() : baseOptions.wallClock);
|
|
347
349
|
if (effect.kind === 'config.get') return state.surface.config.adapter.get(effect.name);
|
|
348
350
|
if (effect.kind === 'secret.get') {
|
|
349
351
|
const secret = state.surface.secret.adapter.get(effect.name);
|
|
@@ -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
|
}
|
|
@@ -143,7 +143,8 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
143
143
|
const downstreamRequestHandle = 1;
|
|
144
144
|
const downstreamBodyHandle = 2;
|
|
145
145
|
requests.set(downstreamRequestHandle, { method, url, headers: requestHeaders.map((entry) => [...entry]) });
|
|
146
|
-
bodies.set(downstreamBodyHandle, { bytes: normalizeBody(requestInput.body), readOffset: 0, writes: []
|
|
146
|
+
bodies.set(downstreamBodyHandle, { bytes: normalizeBody(requestInput.body), readOffset: 0, writes: [],
|
|
147
|
+
readyAt: monotonicMs + Number(requestInput.readyDelayMs || 0), fixture: requestInput });
|
|
147
148
|
let downstream;
|
|
148
149
|
|
|
149
150
|
function alloc(kind = 'default') {
|
|
@@ -203,6 +204,15 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
203
204
|
body = JSON.stringify(normalized.json);
|
|
204
205
|
if (!headers.some(([name]) => name.toLowerCase() === 'content-type')) headers.push(['content-type', 'application/json; charset=utf-8']);
|
|
205
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
|
+
}
|
|
206
216
|
responses.set(responseHandle, {
|
|
207
217
|
status: Number(normalized.status || 200),
|
|
208
218
|
headers,
|
|
@@ -219,6 +229,28 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
219
229
|
new Uint8Array(memory().buffer, Number(buffer), output.length).set(output);
|
|
220
230
|
return FASTLY_STATUS_OK;
|
|
221
231
|
}
|
|
232
|
+
function requestHeaderList(values, buffer, size, cursor, end, written, kind) {
|
|
233
|
+
const limit = Math.min(Number(size), options.requestHeaderPageBytes ?? Number(size));
|
|
234
|
+
let index = Number(cursor), count = 0;
|
|
235
|
+
if (index < 0 || index > values.length) return FASTLY_STATUS_ERROR;
|
|
236
|
+
while (index < values.length) {
|
|
237
|
+
const bytes = Buffer.from(String(values[index]) + '\0');
|
|
238
|
+
if (count + bytes.length > limit) {
|
|
239
|
+
if (count === 0) { writeU32(written, bytes.length); return FASTLY_STATUS_BUFLEN; }
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
new Uint8Array(memory().buffer, Number(buffer) + count, bytes.length).set(bytes);
|
|
243
|
+
count += bytes.length; index++;
|
|
244
|
+
}
|
|
245
|
+
writeU32(written, count);
|
|
246
|
+
view().setBigInt64(Number(end), index === values.length ? -1n : BigInt(index), true);
|
|
247
|
+
// Fault injection is host-fixture-only; neither values nor faults enter traces.
|
|
248
|
+
const fault = options.requestHeaderFault?.({kind, cursor: Number(cursor)});
|
|
249
|
+
if (fault?.written !== undefined) writeU32(written, fault.written);
|
|
250
|
+
if (fault?.cursor !== undefined) view().setBigInt64(Number(end), BigInt(fault.cursor), true);
|
|
251
|
+
if (fault?.unterminated && count) new Uint8Array(memory().buffer)[Number(buffer) + count - 1] = 65;
|
|
252
|
+
return fault?.status ?? FASTLY_STATUS_OK;
|
|
253
|
+
}
|
|
222
254
|
const imports = {
|
|
223
255
|
fastly_async_io: {
|
|
224
256
|
select(handles, count, timeout, done) {
|
|
@@ -235,6 +267,19 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
235
267
|
},
|
|
236
268
|
wasi_snapshot_preview1: {
|
|
237
269
|
clock_time_get(clockId, precision, timeOut) {
|
|
270
|
+
if (Number(clockId) === 1 && typeof options.monotonicClock === 'function') {
|
|
271
|
+
const sample = options.monotonicClock(monotonicMs);
|
|
272
|
+
if (sample.status) return sample.status;
|
|
273
|
+
writeU64(timeOut, sample.nanoseconds);
|
|
274
|
+
return FASTLY_STATUS_OK;
|
|
275
|
+
}
|
|
276
|
+
if (Number(clockId) === 0 && typeof options.realtimeClock === 'function') {
|
|
277
|
+
const sample = options.realtimeClock();
|
|
278
|
+
trace.push({ module: 'wasi_snapshot_preview1', name: 'clock_time_get', clockId: 0, precision: String(precision), status: sample.status || 0 });
|
|
279
|
+
if (sample.status) return sample.status;
|
|
280
|
+
writeU64(timeOut, sample.nanoseconds);
|
|
281
|
+
return FASTLY_STATUS_OK;
|
|
282
|
+
}
|
|
238
283
|
const seconds = Number.isFinite(Number(options.clockUnixSeconds))
|
|
239
284
|
? Number(options.clockUnixSeconds)
|
|
240
285
|
: Math.floor(Date.now() / 1000);
|
|
@@ -304,6 +349,19 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
304
349
|
trace.push({ module: 'fastly_http_req', name: 'uri_get', handle: Number(handle) });
|
|
305
350
|
return request ? writeUtf8(request.url, buffer, bufferLength, writtenOut) : FASTLY_STATUS_BADF;
|
|
306
351
|
},
|
|
352
|
+
header_names_get(handle, buffer, size, cursor, end, written) {
|
|
353
|
+
trace.push({module: 'fastly_http_req', name: 'header_names_get', cursor: Number(cursor)});
|
|
354
|
+
const request = requests.get(Number(handle));
|
|
355
|
+
if (!request) return FASTLY_STATUS_BADF;
|
|
356
|
+
return requestHeaderList([...new Set(request.headers.map(([name]) => name.toLowerCase()))], buffer, size, cursor, end, written, 'names');
|
|
357
|
+
},
|
|
358
|
+
header_values_get(handle, pointer, length, buffer, size, cursor, end, written) {
|
|
359
|
+
trace.push({module: 'fastly_http_req', name: 'header_values_get', cursor: Number(cursor)});
|
|
360
|
+
const request = requests.get(Number(handle));
|
|
361
|
+
if (!request) return FASTLY_STATUS_BADF;
|
|
362
|
+
const name = readUtf8(pointer, length).toLowerCase();
|
|
363
|
+
return requestHeaderList(request.headers.filter(([key]) => key.toLowerCase() === name).map(([, value]) => value), buffer, size, cursor, end, written, 'values');
|
|
364
|
+
},
|
|
307
365
|
header_value_get(handle, namePointer, nameLength, valuePointer, valueLength, writtenOut) {
|
|
308
366
|
const request = requests.get(Number(handle));
|
|
309
367
|
const name = readUtf8(namePointer, nameLength);
|
|
@@ -558,7 +616,7 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
558
616
|
}
|
|
559
617
|
},
|
|
560
618
|
fastly_http_body: {
|
|
561
|
-
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; },
|
|
562
620
|
new(handleOut) {
|
|
563
621
|
const handle = alloc('body');
|
|
564
622
|
bodies.set(handle, { bytes: Buffer.alloc(0), readOffset: 0, writes: [] });
|
|
@@ -583,6 +641,7 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
583
641
|
write(handle, buffer, bufferLength, end, writtenOut) {
|
|
584
642
|
const body = bodies.get(Number(handle));
|
|
585
643
|
if (!body) return FASTLY_STATUS_BADF;
|
|
644
|
+
if (downstream?.streaming && Number(handle) === downstream.bodyHandle && options.downstreamBodyWriteStatus) return Number(options.downstreamBodyWriteStatus);
|
|
586
645
|
if (requests.size > 1 && options.outboundBodyWriteStatus) return Number(options.outboundBodyWriteStatus);
|
|
587
646
|
const length = Math.min(Number(bufferLength), Number(options.bodyWriteChunkBytes || bufferLength));
|
|
588
647
|
const bytes = readBytes(buffer, length);
|
|
@@ -603,9 +662,11 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
603
662
|
// Throwing stops the Wasm invocation; it never fabricates an effect result.
|
|
604
663
|
const checkActive = () => { if (options.signal && options.signal.aborted) throw options.signal.reason || new Error('Request cancelled.'); };
|
|
605
664
|
checkActive();
|
|
606
|
-
if (options.signal) for (const namespace of Object.
|
|
665
|
+
if (options.signal || options.hostcallDelayMs) for (const [moduleName, namespace] of Object.entries(imports)) {
|
|
607
666
|
for (const [name, hostcall] of Object.entries(namespace)) if (typeof hostcall === 'function') namespace[name] = (...args) => {
|
|
608
|
-
checkActive(); const result = hostcall(...args);
|
|
667
|
+
checkActive(); const result = hostcall(...args);
|
|
668
|
+
monotonicMs += Number(options.hostcallDelayMs?.[`${moduleName}.${name}`] || 0);
|
|
669
|
+
checkActive(); return result;
|
|
609
670
|
};
|
|
610
671
|
}
|
|
611
672
|
const module = new WebAssembly.Module(wasm);
|
|
@@ -614,7 +675,16 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
614
675
|
throw new FastlyNativePlatformCapabilitiesMockError('Pass98 native module is missing _start.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_MOCK_START_MISSING');
|
|
615
676
|
}
|
|
616
677
|
if (options.conditionalKv && options.conditionalKv.deadlineNs !== undefined) instance.exports.pulse_fastly_kv_request_deadline(BigInt(options.conditionalKv.deadlineNs));
|
|
617
|
-
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
|
+
}
|
|
618
688
|
for (const entry of trace) if (entry.url && privateUrls.has(entry.url)) entry.url = '[REDACTED]';
|
|
619
689
|
const lastError = typeof instance.exports.pulse_fastly_last_error === 'function'
|
|
620
690
|
? Number(instance.exports.pulse_fastly_last_error())
|
|
@@ -628,10 +698,11 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
628
698
|
const pulseLastError = typeof instance.exports.pulse_last_error_code === 'function'
|
|
629
699
|
? Number(instance.exports.pulse_last_error_code())
|
|
630
700
|
: undefined;
|
|
701
|
+
if (downstream?.streaming && !downstream.finished) { downstream.bodyBytes = bodyBytes(downstream.bodyHandle); downstream.aborted = true; }
|
|
631
702
|
const handledJwtError = options.allowHandledJwtError === true
|
|
632
703
|
&& lastError === 1008
|
|
633
704
|
&& Boolean(downstream);
|
|
634
|
-
if (lastError !== FASTLY_STATUS_OK && !handledJwtError) {
|
|
705
|
+
if (lastError !== FASTLY_STATUS_OK && !handledJwtError && !(options.allowAbortedResponse === true && downstream?.aborted)) {
|
|
635
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 });
|
|
636
707
|
}
|
|
637
708
|
if (!downstream) {
|
|
@@ -648,6 +719,7 @@ function executeFastlyNativePlatformCapabilities(input, options = {}) {
|
|
|
648
719
|
bodyBytes: downstream.bodyBytes,
|
|
649
720
|
sent: true,
|
|
650
721
|
streaming: downstream.streaming,
|
|
722
|
+
aborted: downstream.aborted === true,
|
|
651
723
|
origin: downstream.origin,
|
|
652
724
|
responseHandle: downstream.responseHandle,
|
|
653
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) {
|
|
@@ -33,6 +34,7 @@ function normalizeFastlyProviderConfig(value) {
|
|
|
33
34
|
: {};
|
|
34
35
|
return Object.freeze({
|
|
35
36
|
kind: 'fastly',
|
|
37
|
+
maxDurationMs: require('@pulse-compute/runtime/host').normalizeRequestDuration(input.maxDurationMs),
|
|
36
38
|
version: input.version ? String(input.version) : undefined,
|
|
37
39
|
bindings: Object.freeze({
|
|
38
40
|
configStore: String(bindings.configStore || 'pulse_config'),
|
|
@@ -54,6 +56,7 @@ function normalizeFastlyProviderConfig(value) {
|
|
|
54
56
|
dynamicBackends: bindings.dynamicBackends === true
|
|
55
57
|
}),
|
|
56
58
|
build: Object.freeze({
|
|
59
|
+
maxWasmBytes: normalizeFastlyMaxWasmBytes(input.build?.maxWasmBytes),
|
|
57
60
|
name: String(input.build && input.build.name || 'pulse-app'),
|
|
58
61
|
description: String(input.build && input.build.description || 'Pulse application'),
|
|
59
62
|
authors: Object.freeze(Array.isArray(input.build && input.build.authors) ? input.build.authors.map(String) : [])
|
|
@@ -67,6 +70,7 @@ function fastlyProjectConfigDocument(config) {
|
|
|
67
70
|
return Object.freeze({
|
|
68
71
|
kind: 'fastly',
|
|
69
72
|
bindings: config.bindings,
|
|
73
|
+
maxDurationMs: config.maxDurationMs,
|
|
70
74
|
build: config.build,
|
|
71
75
|
local: config.local,
|
|
72
76
|
providerSpecificUserland: false
|
|
@@ -100,7 +104,10 @@ const FASTLY_PROJECT_CONFIG_REFERENCE = Object.freeze({
|
|
|
100
104
|
})
|
|
101
105
|
]),
|
|
102
106
|
fields: Object.freeze([
|
|
103
|
-
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.'}),
|
|
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.'}),
|
|
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.' }),
|
|
104
111
|
Object.freeze({ section: 'fastly', path: 'fastly.configStore', type: 'string', default: '`pulse_config`', scope: 'Fastly config capability', description: 'Fastly Config Store resource name.' }),
|
|
105
112
|
Object.freeze({ section: 'fastly', path: 'fastly.secretStore', type: 'string', default: '`pulse_secrets`', scope: 'Fastly secret capability', description: 'Fastly Secret Store resource name.', security: 'The name is build metadata; secret values are never embedded by this option.' }),
|
|
106
113
|
Object.freeze({ section: 'fastly', path: 'fastly.kv', type: 'Readonly<Record<string, string>>', default: '`{}`', scope: 'Fastly KV capability', description: 'Maps logical ctx.kv names to Fastly KV Store resource names.', diagnostics: Object.freeze(['PULSE_FASTLY_KV_BINDINGS_INVALID']) }),
|
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({
|
|
@@ -332,7 +361,7 @@ function createDriver() {
|
|
|
332
361
|
});
|
|
333
362
|
},
|
|
334
363
|
executionOptions(providerConfig = {}, values = {}) {
|
|
335
|
-
return { ...values, bindings: providerConfig.bindings };
|
|
364
|
+
return { ...values, maxDurationMs: providerConfig.maxDurationMs, bindings: providerConfig.bindings };
|
|
336
365
|
}
|
|
337
366
|
});
|
|
338
367
|
}
|
|
@@ -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 };
|