@pulse-compute/provider-fastly 1.0.0-beta.5 → 1.0.0-beta.7
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 +354 -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 +31 -2
- package/src/toolchain/native-execution.js +1 -0
- package/src/toolchain/s3.js +2 -2
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
+
|
|
3
|
+
const { scalarRecordProjectionLines } = require('./schema-scalar-record.js');
|
|
4
|
+
const { hasJsonAdmission, tracksJsonDuplicates, schemaParserDepth, nestedJsonProjectionLines, schemaJsonAdmissionSource, schemaFetchJsonSource } = require('./schema-nested-json.js');
|
|
2
5
|
const applicationErrors = require('./native-application-errors.js');
|
|
6
|
+
const effectInvocations = require('./effect-invocations.js');
|
|
7
|
+
const packageApplication = require('./package-native-application.js');
|
|
8
|
+
const nativeValueBudget = require('./native-value-budget.js');
|
|
9
|
+
const { schemaNeedsValueProjection } = require('@pulse-compute/wasm-contracts/schema-json/registry');
|
|
3
10
|
|
|
4
11
|
const crypto = require('node:crypto');
|
|
5
12
|
const { nativeStringFields, nativeStringConcat, nativeStringTrim, nativeStringIndex, needsNativeValueFailureGuard } = require('./native-string-values.js');
|
|
@@ -15,6 +22,7 @@ const {
|
|
|
15
22
|
} = require('@pulse-compute/wasm-contracts/package/package-contract');
|
|
16
23
|
const {
|
|
17
24
|
EXPERIMENTAL_NATIVE_SIZE_OPTIMIZATION,
|
|
25
|
+
prepareReportCapture,
|
|
18
26
|
appendAssemblyScriptOptimizationArgs
|
|
19
27
|
} = require('@pulse-compute/wasm-build-support/native-optimization');
|
|
20
28
|
|
|
@@ -92,7 +100,10 @@ const FASTLY_NATIVE_PLATFORM_CAPABILITIES_VERSION = 'pulse.fastly-native-platfor
|
|
|
92
100
|
const FASTLY_NATIVE_PLATFORM_CAPABILITIES_GENERATOR_VERSION = 'pulse.fastly-native-platform-capabilities-generator.v1';
|
|
93
101
|
const FASTLY_NATIVE_PLATFORM_CAPABILITIES_COMPILER_VERSION = 'pulse.fastly-native-platform-capabilities-compiler.v1';
|
|
94
102
|
const FASTLY_NATIVE_PLATFORM_CAPABILITIES_ABI_VERSION = 1;
|
|
95
|
-
|
|
103
|
+
// Internal compilation budget, not a Fastly service limit. Keep a finite guard
|
|
104
|
+
// while allowing composed applications beyond the original 1 MiB proof fixture.
|
|
105
|
+
const { DEFAULT_FASTLY_MAX_WASM_BYTES, normalizeFastlyMaxWasmBytes, assertFastlyWasmBudget } = require('./wasm-budget.js');
|
|
106
|
+
const FASTLY_NATIVE_PLATFORM_CAPABILITIES_MAX_WASM_BYTES = DEFAULT_FASTLY_MAX_WASM_BYTES;
|
|
96
107
|
const FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES = 65536;
|
|
97
108
|
const FASTLY_NATIVE_SIZE_OPTIMIZATION = EXPERIMENTAL_NATIVE_SIZE_OPTIMIZATION;
|
|
98
109
|
|
|
@@ -156,9 +167,11 @@ const FASTLY_NATIVE_PLATFORM_EFFECT_KIND = Object.freeze({
|
|
|
156
167
|
'grip.broadcast': 9,
|
|
157
168
|
'assets.lookup': 10,
|
|
158
169
|
'jwt.verify': 11,
|
|
170
|
+
'jwt.sign': 20,
|
|
159
171
|
's3.head': 12,
|
|
160
172
|
's3.getText': 13,
|
|
161
173
|
's3.putText': 14,
|
|
174
|
+
's3.getBody': 21,
|
|
162
175
|
'kv.getVersioned': 15,
|
|
163
176
|
'kv.insertIfAbsent': 16,
|
|
164
177
|
'kv.compareAndSwap': 17,
|
|
@@ -185,9 +198,11 @@ const FASTLY_NATIVE_PLATFORM_EFFECT_KINDS = Object.freeze([
|
|
|
185
198
|
'grip.broadcast',
|
|
186
199
|
'assets.lookup',
|
|
187
200
|
'jwt.verify',
|
|
201
|
+
'jwt.sign',
|
|
188
202
|
's3.head',
|
|
189
203
|
's3.getText',
|
|
190
|
-
's3.putText'
|
|
204
|
+
's3.putText',
|
|
205
|
+
's3.getBody'
|
|
191
206
|
]);
|
|
192
207
|
|
|
193
208
|
const FASTLY_NATIVE_PLATFORM_CAPABILITY_KINDS = new Set([
|
|
@@ -204,9 +219,11 @@ const FASTLY_NATIVE_PLATFORM_CAPABILITY_KINDS = new Set([
|
|
|
204
219
|
'grip.publish',
|
|
205
220
|
'grip.broadcast',
|
|
206
221
|
'jwt.verify',
|
|
222
|
+
'jwt.sign',
|
|
207
223
|
's3.head',
|
|
208
224
|
's3.getText',
|
|
209
|
-
's3.putText'
|
|
225
|
+
's3.putText',
|
|
226
|
+
's3.getBody'
|
|
210
227
|
]);
|
|
211
228
|
|
|
212
229
|
class FastlyNativePlatformCapabilitiesError extends Error {
|
|
@@ -268,45 +285,38 @@ function packageFacts(_plan, options = {}) {
|
|
|
268
285
|
});
|
|
269
286
|
}
|
|
270
287
|
|
|
288
|
+
function jwtVerificationAlgorithm(effect) {
|
|
289
|
+
return effect.resource.keyType === 'secret' ? 'HS256' : String(effect.resource.keyArtifactId).startsWith('pulse.jwt.rs256-key.') ? 'RS256' : 'ES256';
|
|
290
|
+
}
|
|
291
|
+
|
|
271
292
|
function selectedJwtCrypto(plan) {
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
:
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
&& selected.kind === 'guest-linked'
|
|
294
|
-
&& selected.targetImplemented === true;
|
|
295
|
-
if ((!hs256 && !es256) || selected.automaticFallback !== false) {
|
|
296
|
-
fail(
|
|
297
|
-
'Fastly Native JWT selected an unavailable or non-exact crypto realization.',
|
|
298
|
-
'PULSE_FASTLY_NATIVE_JWT_CRYPTO_REALIZATION_INVALID',
|
|
299
|
-
{ selected, automaticFallback: false }
|
|
300
|
-
);
|
|
293
|
+
const effects = plan.effects || [];
|
|
294
|
+
const signing = effects.filter(effect => effect.kind === 'jwt.sign');
|
|
295
|
+
const verifies = effects.filter(effect => effect.kind === 'jwt.verify');
|
|
296
|
+
const required = new Set([
|
|
297
|
+
...signing.map(effect => literalObjectField(inputExpression(effect, 'payload'), 'algorithm') === 'HS256' ? 'HMAC-SHA256' : literalObjectField(inputExpression(effect, 'payload'), 'algorithm')),
|
|
298
|
+
...verifies.map(effect => jwtVerificationAlgorithm(effect))
|
|
299
|
+
]);
|
|
300
|
+
if (!required.size) return undefined;
|
|
301
|
+
const definitions = {
|
|
302
|
+
'HMAC-SHA256': ['guest-source:pulse-hmac-as', 'pulse-hmac-as.v1', 'guest-source'],
|
|
303
|
+
HS256: ['guest-source:pulse-hmac-as', 'pulse-hmac-as.v1', 'guest-source'],
|
|
304
|
+
RS256: ['guest-linked:pulse-rs256-bearssl-i31', 'bearssl.0.6.rsa-i31.sha256.v1', 'guest-linked'],
|
|
305
|
+
ES256: ['guest-linked:pulse-es256-rustcrypto-p256', 'rustcrypto.p256-0.13.2.ecdsa-0.16.9.sha2-0.10.9.v1', 'guest-linked']
|
|
306
|
+
};
|
|
307
|
+
for (const algorithm of required) {
|
|
308
|
+
const selected = plan.crypto?.algorithms.find(entry => entry.algorithm === algorithm);
|
|
309
|
+
const expected = definitions[algorithm];
|
|
310
|
+
if (!expected || !selected || selected.realization !== expected[0] || selected.implementation !== expected[1]
|
|
311
|
+
|| selected.kind !== expected[2] || selected.targetImplemented !== true || selected.automaticFallback !== false) {
|
|
312
|
+
fail('JWT requires its explicitly selected Crypto realization.', 'PULSE_FASTLY_NATIVE_JWT_CRYPTO_REALIZATION_INVALID');
|
|
313
|
+
}
|
|
301
314
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
guestUnitRequired: es256,
|
|
308
|
-
automaticFallback: false
|
|
309
|
-
});
|
|
315
|
+
const verificationAlgorithms = [...new Set(verifies.map(effect => jwtVerificationAlgorithm(effect)))];
|
|
316
|
+
if (verificationAlgorithms.length > 1) fail('Fastly Native JWT verification requires one exact algorithm.', 'PULSE_FASTLY_NATIVE_JWT_CRYPTO_REALIZATION_INVALID');
|
|
317
|
+
const algorithm = verificationAlgorithms[0] || (required.has('RS256') ? 'RS256' : required.has('ES256') ? 'ES256' : 'HMAC-SHA256');
|
|
318
|
+
return Object.freeze({ ...plan.crypto.algorithms.find(entry => entry.algorithm === algorithm),
|
|
319
|
+
guestUnitRequired: required.has('ES256') || required.has('RS256'), signingAlgorithms: signing.map(effect => literalObjectField(inputExpression(effect, 'payload'), 'algorithm')) });
|
|
310
320
|
}
|
|
311
321
|
|
|
312
322
|
function decodeP256Coordinate(value, artifactId, field) {
|
|
@@ -328,6 +338,26 @@ function decodeP256Coordinate(value, artifactId, field) {
|
|
|
328
338
|
return bytes;
|
|
329
339
|
}
|
|
330
340
|
|
|
341
|
+
function normalizedRs256Jwk(value, artifactId) {
|
|
342
|
+
const invalid = () => fail('Invalid bounded RSA public key artifact.', 'PULSE_FASTLY_NATIVE_JWT_KEY_ARTIFACT_INVALID', { artifactId });
|
|
343
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)
|
|
344
|
+
|| Object.keys(value).some(name => !['kty', 'n', 'e', 'alg', 'use', 'key_ops', 'kid'].includes(name))
|
|
345
|
+
|| value.kty !== 'RSA' || value.alg !== undefined && value.alg !== 'RS256'
|
|
346
|
+
|| value.use !== undefined && value.use !== 'sig'
|
|
347
|
+
|| value.key_ops !== undefined && (!Array.isArray(value.key_ops) || value.key_ops.length !== 1 || value.key_ops[0] !== 'verify')
|
|
348
|
+
|| value.kid !== undefined && (typeof value.kid !== 'string' || !value.kid.length || value.kid.includes('\0') || Buffer.byteLength(value.kid) > 256)) invalid();
|
|
349
|
+
const decode = (text, maximum) => {
|
|
350
|
+
if (typeof text !== 'string' || text.length > Math.ceil(maximum * 4 / 3) || !/^[A-Za-z0-9_-]+$/.test(text)) invalid();
|
|
351
|
+
const bytes = Buffer.from(text, 'base64url');
|
|
352
|
+
if (!bytes.length || bytes.length > maximum || bytes[0] === 0 || bytes.toString('base64url') !== text) invalid();
|
|
353
|
+
return bytes;
|
|
354
|
+
};
|
|
355
|
+
const n = decode(value.n, 512), e = decode(value.e, 4), k = n.length;
|
|
356
|
+
if (![256, 384, 512].includes(k) || n[0] < 128 || !(n[k-1] & 1) || e.readUIntBE(0, e.length) < 3 || !(e.at(-1) & 1)) invalid();
|
|
357
|
+
const bytes = Buffer.alloc(8+k); bytes.writeUInt32LE(k); n.copy(bytes, 4); e.copy(bytes, 8+k-e.length);
|
|
358
|
+
return Object.freeze({ kid: value.kid, bytes });
|
|
359
|
+
}
|
|
360
|
+
|
|
331
361
|
function normalizedEs256Jwk(value, artifactId) {
|
|
332
362
|
if (
|
|
333
363
|
!value
|
|
@@ -374,13 +404,14 @@ function resolveEs256KeyArtifacts(plan, facts) {
|
|
|
374
404
|
}
|
|
375
405
|
const artifactId = effect.resource.keyArtifactId;
|
|
376
406
|
const artifact = artifacts.get(artifactId);
|
|
407
|
+
const algorithm = jwtVerificationAlgorithm(effect).toLowerCase();
|
|
377
408
|
if (
|
|
378
409
|
!artifact
|
|
379
|
-
|| artifact.version !==
|
|
410
|
+
|| artifact.version !== `pulse.jwt-${algorithm}-key-artifact.v1`
|
|
380
411
|
|| artifact.contractId !== 'pulse.jwt'
|
|
381
412
|
|| artifact.package !== '@pulse-compute/jwt'
|
|
382
|
-
|| artifact.kind !==
|
|
383
|
-
|| artifact.mediaType !==
|
|
413
|
+
|| artifact.kind !== `jwt-${algorithm}-static-public-key`
|
|
414
|
+
|| artifact.mediaType !== `application/vnd.pulse.jwt-${algorithm}-key+json`
|
|
384
415
|
|| artifact.id !== artifactId
|
|
385
416
|
|| artifact.materialHash !== sha256(stableStringify(artifact.data))
|
|
386
417
|
|| !artifact.data
|
|
@@ -406,7 +437,7 @@ function resolveEs256KeyArtifacts(plan, facts) {
|
|
|
406
437
|
{ effectId: effect.id, artifactId, keyCount: keys && keys.length }
|
|
407
438
|
);
|
|
408
439
|
}
|
|
409
|
-
const normalized = keys.map((key) => normalizedEs256Jwk(key, artifactId));
|
|
440
|
+
const normalized = keys.map((key) => algorithm === 'rs256' ? normalizedRs256Jwk(key, artifactId) : normalizedEs256Jwk(key, artifactId));
|
|
410
441
|
const kids = normalized.map((key) => key.kid).filter((kid) => kid !== undefined);
|
|
411
442
|
if (new Set(kids).size !== kids.length) {
|
|
412
443
|
fail(
|
|
@@ -482,8 +513,71 @@ function es256KeyArtifactSource(records) {
|
|
|
482
513
|
return lines.join('\n');
|
|
483
514
|
}
|
|
484
515
|
|
|
516
|
+
function jwtSigningCryptoCompositionSource(selected) {
|
|
517
|
+
const algorithms = selected?.signingAlgorithms || [];
|
|
518
|
+
const hs = algorithms.includes('HS256');
|
|
519
|
+
const es = algorithms.includes('ES256');
|
|
520
|
+
const rs = algorithms.includes('RS256');
|
|
521
|
+
return `function __pulse_fastly_jwt_crypto_sign(algorithm: string, key: Uint8Array, data: Uint8Array, output: Uint8Array): i32 {
|
|
522
|
+
${hs ? `if (algorithm == "HS256") return pulse_crypto_hmac_sha256_bytes_v1(key.dataStart, key.length, data.dataStart, data.length, output.dataStart, output.length)` : ''}
|
|
523
|
+
${rs ? `if (algorithm == "RS256") return __pulse_fastly_rs256(true, key, data, output)` : ''}
|
|
524
|
+
${es ? `if (algorithm == "ES256") {
|
|
525
|
+
if (key.length != 96 || output.length != 64 || data.length > 16340) return -2
|
|
526
|
+
const pointer: usize = 524288
|
|
527
|
+
memory.fill(pointer, 0, 16640)
|
|
528
|
+
store<u32>(pointer, 0x32534550); store<u32>(pointer + 4, 2); store<u32>(pointer + 8, 64)
|
|
529
|
+
store<u32>(pointer + 12, (224 + data.length + 15) & ~15); store<u32>(pointer + 16, 1)
|
|
530
|
+
store<u32>(pointer + 24, 224); store<u32>(pointer + 28, data.length)
|
|
531
|
+
store<u32>(pointer + 32, 64); store<u32>(pointer + 36, 96)
|
|
532
|
+
store<u32>(pointer + 40, 160); store<u32>(pointer + 44, 64)
|
|
533
|
+
memory.copy(pointer + 64, key.dataStart, 96); memory.copy(pointer + 224, data.dataStart, data.length)
|
|
534
|
+
const status = __pulse_crypto_es256_sign_anchor(i32(pointer), 16640)
|
|
535
|
+
if (status == 1) memory.copy(output.dataStart, pointer + 160, 64)
|
|
536
|
+
memory.fill(pointer, 0, 16640); memory.fill(0, 0, 65536)
|
|
537
|
+
return status
|
|
538
|
+
}` : ''}
|
|
539
|
+
return -3
|
|
540
|
+
}`;
|
|
541
|
+
}
|
|
542
|
+
|
|
485
543
|
function jwtCryptoCompositionSource(selected) {
|
|
486
|
-
|
|
544
|
+
const rsa = selected && (selected.algorithm === 'RS256' || selected.signingAlgorithms?.includes('RS256'));
|
|
545
|
+
const helper = rsa ? rs256CompositionSource() : '';
|
|
546
|
+
return helper + jwtVerificationCompositionSource(selected);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function rs256CompositionSource() {
|
|
550
|
+
return `function __pulse_fastly_rs256(signing: bool, key: Uint8Array, data: Uint8Array, signature: Uint8Array): i32 {
|
|
551
|
+
if (key.length < 8 || data.length > 12288) return -2
|
|
552
|
+
const k = load<u32>(key.dataStart)
|
|
553
|
+
if ((k != 256 && k != 384 && k != 512) || signature.length != i32(k) || key.length != i32(signing ? 8 + k * 9 / 2 : 8 + k)) return -2
|
|
554
|
+
const pointer: usize = 524288
|
|
555
|
+
const output = (64 + key.length + 15) & ~15
|
|
556
|
+
const input = output + signature.length
|
|
557
|
+
memory.fill(pointer, 0, 16640)
|
|
558
|
+
store<u32>(pointer, 0x32534550); store<u32>(pointer + 4, 2); store<u32>(pointer + 8, 64)
|
|
559
|
+
store<u32>(pointer + 12, (input + data.length + 15) & ~15); store<u32>(pointer + 16, 2)
|
|
560
|
+
store<u32>(pointer + 24, input); store<u32>(pointer + 28, data.length)
|
|
561
|
+
store<u32>(pointer + 32, 64); store<u32>(pointer + 36, key.length)
|
|
562
|
+
store<u32>(pointer + 40, output); store<u32>(pointer + 44, k)
|
|
563
|
+
memory.copy(pointer + 64, key.dataStart, key.length); memory.copy(pointer + input, data.dataStart, data.length)
|
|
564
|
+
if (!signing) memory.copy(pointer + output, signature.dataStart, signature.length)
|
|
565
|
+
const status = signing ? __pulse_crypto_rs256_sign_anchor(i32(pointer), 16640) : __pulse_crypto_rs256_verify_anchor(i32(pointer), 16640)
|
|
566
|
+
if (signing && status == 1) memory.copy(signature.dataStart, pointer + output, signature.length)
|
|
567
|
+
memory.fill(pointer, 0, 16640); memory.fill(0, 0, 65536)
|
|
568
|
+
return status
|
|
569
|
+
}\n`;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function jwtVerificationCompositionSource(selected) {
|
|
573
|
+
if (selected?.algorithm === 'RS256') return `
|
|
574
|
+
function __pulse_fastly_jwt_capture_signing_input(input: Uint8Array): void { __pulse_fastly_jwt_record_signing_input(input.byteLength, "") }
|
|
575
|
+
function __pulse_fastly_jwt_crypto_verify(algorithm: string, key: Uint8Array, data: Uint8Array, signature: Uint8Array): i32 {
|
|
576
|
+
if (algorithm != "RS256") return -2
|
|
577
|
+
return __pulse_fastly_rs256(false, key, data, signature)
|
|
578
|
+
}`;
|
|
579
|
+
|
|
580
|
+
if (!selected || selected.algorithm === 'HMAC-SHA256') {
|
|
487
581
|
return [
|
|
488
582
|
'function __pulse_fastly_jwt_capture_signing_input(input: Uint8Array): void { void input }',
|
|
489
583
|
'function __pulse_fastly_jwt_crypto_verify(algorithm: string, key: Uint8Array, data: Uint8Array, signature: Uint8Array): i32 { void algorithm; void key; void data; void signature; return -3 }'
|
|
@@ -596,11 +690,10 @@ function validatePlanBoundary(plan, options = {}) {
|
|
|
596
690
|
&& effect.kind === 'assets.lookup';
|
|
597
691
|
const jwt = effect.package === '@pulse-compute/jwt'
|
|
598
692
|
&& effect.contractId === 'pulse.jwt'
|
|
599
|
-
&&
|
|
600
|
-
&& effect.operation ===
|
|
601
|
-
&& effect.capability === 'jwt.verify';
|
|
693
|
+
&& ['verify', 'sign'].includes(effect.operation)
|
|
694
|
+
&& effect.kind === `jwt.${effect.operation}` && effect.capability === effect.kind;
|
|
602
695
|
const s3 = effect.package === '@pulse-compute/s3' && effect.contractId === 'pulse.s3'
|
|
603
|
-
&& ['head', 'getText', 'putText'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
|
|
696
|
+
&& ['head', 'getText', 'putText', 'getBody'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
|
|
604
697
|
const digest = effect.package === '@pulse-compute/crypto' && effect.contractId === 'pulse.crypto'
|
|
605
698
|
&& effect.operation === 'digestText' && effect.kind === 'crypto.digestText' && effect.capability === effect.kind;
|
|
606
699
|
return !grip && !assets && !jwt && !s3 && !digest;
|
|
@@ -620,11 +713,10 @@ function validatePlanBoundary(plan, options = {}) {
|
|
|
620
713
|
&& effect.kind === 'assets.lookup';
|
|
621
714
|
const jwt = effect.package === '@pulse-compute/jwt'
|
|
622
715
|
&& effect.contractId === 'pulse.jwt'
|
|
623
|
-
&&
|
|
624
|
-
&& effect.operation ===
|
|
625
|
-
&& effect.capability === 'jwt.verify';
|
|
716
|
+
&& ['verify', 'sign'].includes(effect.operation)
|
|
717
|
+
&& effect.kind === `jwt.${effect.operation}` && effect.capability === effect.kind;
|
|
626
718
|
const s3 = effect.package === '@pulse-compute/s3' && effect.contractId === 'pulse.s3'
|
|
627
|
-
&& ['head', 'getText', 'putText'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
|
|
719
|
+
&& ['head', 'getText', 'putText', 'getBody'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
|
|
628
720
|
const digest = effect.package === '@pulse-compute/crypto' && effect.contractId === 'pulse.crypto'
|
|
629
721
|
&& effect.operation === 'digestText' && effect.kind === 'crypto.digestText' && effect.capability === effect.kind;
|
|
630
722
|
return !grip && !assets && !jwt && !s3 && !digest;
|
|
@@ -632,7 +724,7 @@ function validatePlanBoundary(plan, options = {}) {
|
|
|
632
724
|
if (unsupportedDeclarations.length > 0) {
|
|
633
725
|
fail('Fastly native package realization is restricted to the Assets, GRIP, JWT, S3, and Crypto digest contracts.', 'PULSE_FASTLY_NATIVE_PLATFORM_PACKAGE_UNSUPPORTED', { effects: unsupportedDeclarations });
|
|
634
726
|
}
|
|
635
|
-
for (const effect of effects.filter((entry) =>
|
|
727
|
+
for (const effect of effects.filter((entry) => ['jwt.verify', 'jwt.sign'].includes(entry.kind))) {
|
|
636
728
|
const resource = effect.resource;
|
|
637
729
|
const knownShape = resource
|
|
638
730
|
&& !Object.keys(resource)
|
|
@@ -647,7 +739,7 @@ function validatePlanBoundary(plan, options = {}) {
|
|
|
647
739
|
&& typeof resource.keyArtifactId === 'string'
|
|
648
740
|
&& resource.keyArtifactId.length > 0
|
|
649
741
|
&& resource.secretBinding === null;
|
|
650
|
-
if (!namedSecret && !staticPublicKey) {
|
|
742
|
+
if (!namedSecret && (!staticPublicKey || effect.kind === 'jwt.sign')) {
|
|
651
743
|
fail('Fastly Native JWT requires one exact named-secret or bounded static public-key descriptor.', 'PULSE_FASTLY_NATIVE_JWT_KEY_DESCRIPTOR_INVALID', {
|
|
652
744
|
effectId: effect.id,
|
|
653
745
|
resource,
|
|
@@ -713,8 +805,8 @@ function resolveCapabilityBindings(plan, options = {}) {
|
|
|
713
805
|
const effects = plan.effects || [];
|
|
714
806
|
const hasConfig = effects.some((effect) => effect.kind === 'config.get');
|
|
715
807
|
const hasSecret = effects.some((effect) => (
|
|
716
|
-
effect.kind === 'secret.get' || ['s3.head', 's3.getText', 's3.putText'].includes(effect.kind)
|
|
717
|
-
|| (
|
|
808
|
+
effect.kind === 'secret.get' || ['s3.head', 's3.getText', 's3.putText', 's3.getBody'].includes(effect.kind)
|
|
809
|
+
|| (['jwt.verify', 'jwt.sign'].includes(effect.kind) && effect.resource && effect.resource.keyType === 'secret')
|
|
718
810
|
));
|
|
719
811
|
const hasGripHold = effects.some((effect) => effect.kind === 'grip.hold');
|
|
720
812
|
const hasGripBroadcast = effects.some((effect) => effect.kind === 'grip.broadcast');
|
|
@@ -740,6 +832,7 @@ function resolveCapabilityBindings(plan, options = {}) {
|
|
|
740
832
|
for (const [index, effect] of effects.entries()) {
|
|
741
833
|
if (effect.kind !== 'kv.get' && effect.kind !== 'kv.put' && !conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind) && effect.kind !== 'assets.lookup') continue;
|
|
742
834
|
const packagePayload = inputExpression(effect, 'payload');
|
|
835
|
+
if (effect.kind === 'assets.lookup' && literalObjectField(packagePayload, 'embeddedId')) continue;
|
|
743
836
|
const logical = effect.resource && effect.resource.kind === 'literal'
|
|
744
837
|
&& effect.kind !== 'assets.lookup'
|
|
745
838
|
? String(effect.resource.value)
|
|
@@ -838,7 +931,7 @@ function resolveCapabilityBindings(plan, options = {}) {
|
|
|
838
931
|
maxDurationMs: require('@pulse-compute/runtime/host').normalizeRequestDuration(options.maxDurationMs),
|
|
839
932
|
configStore,
|
|
840
933
|
secretStore,
|
|
841
|
-
s3: require('../toolchain/s3.js').resolveFastlyS3(effects.map((effect) => ({ ...effect, payload:
|
|
934
|
+
s3: require('../toolchain/s3.js').resolveFastlyS3(effects.map((effect) => ({ ...effect, payload: Object.fromEntries(['contentType', 'method', 'range', 'ifNoneMatch'].map(name => [name, literalObjectField(inputExpression(effect, 'payload'), name) ?? undefined])) })), source.s3),
|
|
842
935
|
kv: Object.freeze(kv),
|
|
843
936
|
fetch: Object.freeze(fetch),
|
|
844
937
|
backends: Object.freeze(backends),
|
|
@@ -872,7 +965,7 @@ function planRequiresFetchBodyRead(plan) {
|
|
|
872
965
|
if (Array.isArray(value)) return value.some(visit);
|
|
873
966
|
return Object.values(value).some(visit);
|
|
874
967
|
}
|
|
875
|
-
return visit(plan.entry);
|
|
968
|
+
return visit(plan.entry) || visit(plan.handlers) || visit(plan.stages) || visit(plan.helpers);
|
|
876
969
|
}
|
|
877
970
|
|
|
878
971
|
function requiredImportsForPlan(plan, bindings) {
|
|
@@ -900,7 +993,7 @@ function requiredImportsForPlan(plan, bindings) {
|
|
|
900
993
|
const gripAuthenticationRequired = kinds.has('grip.broadcast')
|
|
901
994
|
&& (!bindings || Boolean(bindings.grip && bindings.grip.authentication));
|
|
902
995
|
const jwtSecretRequired = (plan.effects || []).some((effect) => (
|
|
903
|
-
|
|
996
|
+
['jwt.verify', 'jwt.sign'].includes(effect.kind)
|
|
904
997
|
&& effect.resource
|
|
905
998
|
&& effect.resource.keyType === 'secret'
|
|
906
999
|
));
|
|
@@ -908,7 +1001,7 @@ function requiredImportsForPlan(plan, bindings) {
|
|
|
908
1001
|
for (const key of ['fastly_secret_store:open', 'fastly_secret_store:get', 'fastly_secret_store:plaintext']) keys.add(key);
|
|
909
1002
|
}
|
|
910
1003
|
for (const kind of conditionalKv.KV_CONDITIONAL_KINDS) if (kinds.has(kind)) for (const key of conditionalKv.kvImports(kind)) keys.add(key);
|
|
911
|
-
if (kinds.has('jwt.verify') || kinds.has('time.now')) keys.add('wasi_snapshot_preview1:clock_time_get');
|
|
1004
|
+
if (kinds.has('jwt.verify') || kinds.has('jwt.sign') || kinds.has('time.now')) keys.add('wasi_snapshot_preview1:clock_time_get');
|
|
912
1005
|
if (kinds.has('kv.get') || kinds.has('kv.put') || kinds.has('assets.lookup')) keys.add('fastly_kv_store:open');
|
|
913
1006
|
if (kinds.has('kv.get') || kinds.has('assets.lookup')) for (const key of ['fastly_kv_store:lookup', 'fastly_kv_store:lookup_wait_v2', 'fastly_http_body:read']) keys.add(key);
|
|
914
1007
|
if (kinds.has('kv.put')) for (const key of ['fastly_kv_store:insert', 'fastly_kv_store:insert_wait']) keys.add(key);
|
|
@@ -917,7 +1010,7 @@ function requiredImportsForPlan(plan, bindings) {
|
|
|
917
1010
|
keys.add('fastly_log:endpoint_get');
|
|
918
1011
|
keys.add('fastly_log:write');
|
|
919
1012
|
}
|
|
920
|
-
if (kinds.has('s3.head') || kinds.has('s3.getText') || kinds.has('s3.putText')) for (const key of require('./s3-native.js').S3_IMPORTS) keys.add(key);
|
|
1013
|
+
if (kinds.has('s3.head') || kinds.has('s3.getText') || kinds.has('s3.putText') || kinds.has('s3.getBody')) for (const key of require('./s3-native.js').S3_IMPORTS) keys.add(key);
|
|
921
1014
|
return Object.freeze([...keys].sort());
|
|
922
1015
|
}
|
|
923
1016
|
|
|
@@ -928,15 +1021,40 @@ function stripPulseHostImports(source) {
|
|
|
928
1021
|
.join('\n');
|
|
929
1022
|
}
|
|
930
1023
|
|
|
1024
|
+
function schemaTextEncodeIds(plan) {
|
|
1025
|
+
// Keep the PS3 accounting/failure boundary and all unproved codec families.
|
|
1026
|
+
if (nativeValueBudget.hasBoundedReadLoop(plan)) return [];
|
|
1027
|
+
function closedText(node, depth = 0) {
|
|
1028
|
+
if (depth > 16) return false;
|
|
1029
|
+
if (node.kind === 'string' || node.kind === 'boolean') return true;
|
|
1030
|
+
if (node.kind === 'nullable') return closedText(node.value, depth);
|
|
1031
|
+
if (node.kind === 'array') return closedText(node.element, depth + 1);
|
|
1032
|
+
return node.kind === 'object' && !node.additionalProperties
|
|
1033
|
+
&& node.fields.every(field => closedText(field.value, depth + 1));
|
|
1034
|
+
}
|
|
1035
|
+
return ((plan.schemas && plan.schemas.registry && plan.schemas.registry.schemas) || [])
|
|
1036
|
+
.filter(schema => !schema.jsonLimits && schema.root.kind === 'object'
|
|
1037
|
+
&& schemaNeedsValueProjection(schema.root) && closedText(schema.root))
|
|
1038
|
+
.map(schema => schema.id);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
931
1041
|
function generateSchemaRuntime(plan) {
|
|
932
1042
|
const registry = plan.schemas && plan.schemas.registry ? plan.schemas.registry : { schemas: [], maxBytes: FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES, contentTypePolicy: 'accept-json-or-missing' };
|
|
933
1043
|
const schemas = registry.schemas || [];
|
|
1044
|
+
const textEncodeIds = new Set(schemaTextEncodeIds(plan));
|
|
934
1045
|
const nodeLines = [];
|
|
1046
|
+
const scalarProjectors = new Map();
|
|
1047
|
+
const flatObjectProjectors = new Map();
|
|
1048
|
+
const scalarKinds = ['string', 'boolean', 'i32', 'u32', 'f64', 'string-enum'];
|
|
935
1049
|
let nodeIndex = 0;
|
|
936
1050
|
|
|
937
1051
|
function emitNode(schemaIndex, node) {
|
|
938
1052
|
const currentIndex = nodeIndex++;
|
|
939
1053
|
const functionName = `__pulse_fastly_schema_${schemaIndex}_${currentIndex}`;
|
|
1054
|
+
const flatObject = node.kind === 'object' && !node.additionalProperties
|
|
1055
|
+
&& node.fields.every(field => scalarKinds.includes(field.value.kind));
|
|
1056
|
+
// Locals belong to this function, so equivalent flat bodies need no node ID.
|
|
1057
|
+
const localIndex = flatObject ? 'flat' : currentIndex;
|
|
940
1058
|
const child = node.kind === 'nullable'
|
|
941
1059
|
? emitNode(schemaIndex, node.value)
|
|
942
1060
|
: node.kind === 'array'
|
|
@@ -945,32 +1063,43 @@ function generateSchemaRuntime(plan) {
|
|
|
945
1063
|
const fields = node.kind === 'object'
|
|
946
1064
|
? node.fields.map((field) => Object.freeze({ field, apply: emitNode(schemaIndex, field.value) }))
|
|
947
1065
|
: [];
|
|
948
|
-
const body = [`function ${functionName}(valueHandle: i32): i32 {`, ' const input = __pulse_fastly_value(valueHandle)'];
|
|
1066
|
+
const body = [`function ${functionName}(valueHandle: i32, checkDuplicates: bool): i32 {`, ' const input = __pulse_fastly_value(valueHandle)'];
|
|
949
1067
|
if (node.kind === 'nullable') {
|
|
950
1068
|
body.push(' if (input.kind == PULSE_VALUE_NULL) return valueHandle');
|
|
951
|
-
body.push(` return ${child}(valueHandle)`);
|
|
1069
|
+
body.push(` return ${child}(valueHandle, checkDuplicates)`);
|
|
952
1070
|
} else if (node.kind === 'array') {
|
|
953
1071
|
body.push(' if (input.kind != PULSE_VALUE_ARRAY) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 52, -1); return 0 }');
|
|
954
1072
|
body.push(' const output = host_value_array()');
|
|
955
1073
|
body.push(' for (let index = 0; index < input.values.length; index += 1) {');
|
|
956
|
-
body.push(` const item = ${child}(unchecked(input.values[index]))`);
|
|
1074
|
+
body.push(` const item = ${child}(unchecked(input.values[index]), checkDuplicates)`);
|
|
957
1075
|
body.push(' if (item <= 0) return 0');
|
|
958
1076
|
body.push(' host_value_array_push(output, item)');
|
|
959
1077
|
body.push(' }');
|
|
960
1078
|
body.push(' return output');
|
|
1079
|
+
} else if (node.kind === 'json-value' || node.kind === 'json-object') {
|
|
1080
|
+
body.push(...nestedJsonProjectionLines(node));
|
|
1081
|
+
} else if (node.kind === 'scalar-record') {
|
|
1082
|
+
body.push(...scalarRecordProjectionLines(node));
|
|
961
1083
|
} else if (node.kind === 'object') {
|
|
962
1084
|
body.push(' if (input.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 50, -1); return 0 }');
|
|
1085
|
+
if (node.additionalProperties) body.push(' if (checkDuplicates && input.duplicateJsonKeys) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 57, -1); return 0 }');
|
|
963
1086
|
body.push(' const output = host_value_object()');
|
|
964
1087
|
fields.forEach(({ field, apply }, fieldIndex) => {
|
|
965
|
-
body.push(` const key_${
|
|
1088
|
+
body.push(` const key_${localIndex}_${fieldIndex} = __pulse_fastly_string_value(${quote(field.name)})`);
|
|
966
1089
|
if (!field.required) body.push(` if (__pulse_fastly_find(input, ${quote(field.name)}) >= 0) {`);
|
|
967
|
-
body.push(` const field_${
|
|
968
|
-
body.push(` if (__pulse_fastly_value(field_${
|
|
969
|
-
body.push(` const projected_${
|
|
970
|
-
body.push(` if (projected_${
|
|
971
|
-
body.push(` host_value_object_set(output, key_${
|
|
1090
|
+
body.push(` const field_${localIndex}_${fieldIndex} = host_value_property(valueHandle, key_${localIndex}_${fieldIndex})`);
|
|
1091
|
+
body.push(` if (__pulse_fastly_value(field_${localIndex}_${fieldIndex}).kind == PULSE_VALUE_UNDEFINED) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 51, -1); return 0 }`);
|
|
1092
|
+
body.push(` const projected_${localIndex}_${fieldIndex} = ${apply}(field_${localIndex}_${fieldIndex}, checkDuplicates)`);
|
|
1093
|
+
body.push(` if (projected_${localIndex}_${fieldIndex} <= 0) return 0`);
|
|
1094
|
+
body.push(` host_value_object_set(output, key_${localIndex}_${fieldIndex}, projected_${localIndex}_${fieldIndex})`);
|
|
972
1095
|
if (!field.required) body.push(' }');
|
|
973
1096
|
});
|
|
1097
|
+
if (node.additionalProperties) {
|
|
1098
|
+
body.push(' for (let index = 0; index < input.keys.length; index++) {', ' const key = unchecked(input.keys[index])');
|
|
1099
|
+
if (node.fields.length) body.push(` if (${node.fields.map(field => `key == ${quote(field.name)}`).join(' || ')}) continue`);
|
|
1100
|
+
body.push(' const item = __pulse_fastly_copy_json(unchecked(input.values[index]), checkDuplicates)',
|
|
1101
|
+
' if (item <= 0) return 0', ' host_value_object_set(output, __pulse_fastly_string_value(key), item)', ' }');
|
|
1102
|
+
}
|
|
974
1103
|
body.push(' return output');
|
|
975
1104
|
} else if (node.kind === 'string') {
|
|
976
1105
|
body.push(' if (input.kind != PULSE_VALUE_STRING) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 52, -1); return 0 }');
|
|
@@ -995,6 +1124,32 @@ function generateSchemaRuntime(plan) {
|
|
|
995
1124
|
throw new TypeError(`Unsupported Fastly native schema node ${String(node.kind)}.`);
|
|
996
1125
|
}
|
|
997
1126
|
body.push('}', '');
|
|
1127
|
+
// Only scalar validators preserve the original handle without allocating or
|
|
1128
|
+
// calling child projectors. Compare the exact emitted body, including enum
|
|
1129
|
+
// values/order and error stages.
|
|
1130
|
+
if (scalarKinds.includes(node.kind)) {
|
|
1131
|
+
const key = body.slice(1).join('\n');
|
|
1132
|
+
const existing = scalarProjectors.get(key);
|
|
1133
|
+
if (existing) return existing;
|
|
1134
|
+
const sharedName = `__pulse_fastly_schema_scalar_${scalarProjectors.size}`;
|
|
1135
|
+
scalarProjectors.set(key, sharedName);
|
|
1136
|
+
body[0] = `function ${sharedName}(valueHandle: i32, checkDuplicates: bool): i32 {`;
|
|
1137
|
+
nodeLines.push(...body);
|
|
1138
|
+
return sharedName;
|
|
1139
|
+
}
|
|
1140
|
+
// Share code only after child scalar identities are resolved. Exact bodies
|
|
1141
|
+
// retain field order, presence checks, validation and errors. Each call still
|
|
1142
|
+
// allocates its own output; open/nested/nullable/array bodies remain separate.
|
|
1143
|
+
if (flatObject) {
|
|
1144
|
+
const key = body.slice(1).join('\n');
|
|
1145
|
+
const existing = flatObjectProjectors.get(key);
|
|
1146
|
+
if (existing) return existing;
|
|
1147
|
+
const sharedName = `__pulse_fastly_schema_flat_object_${flatObjectProjectors.size}`;
|
|
1148
|
+
flatObjectProjectors.set(key, sharedName);
|
|
1149
|
+
body[0] = `function ${sharedName}(valueHandle: i32, checkDuplicates: bool): i32 {`;
|
|
1150
|
+
nodeLines.push(...body);
|
|
1151
|
+
return sharedName;
|
|
1152
|
+
}
|
|
998
1153
|
nodeLines.push(...body);
|
|
999
1154
|
return functionName;
|
|
1000
1155
|
}
|
|
@@ -1004,17 +1159,22 @@ function generateSchemaRuntime(plan) {
|
|
|
1004
1159
|
`const __PULSE_SCHEMA_MAX_BYTES: i32 = ${Number(registry.maxBytes || FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES)}`,
|
|
1005
1160
|
`const __PULSE_SCHEMA_REQUIRE_JSON: bool = ${registry.contentTypePolicy === 'require-json' ? 'true' : 'false'}`,
|
|
1006
1161
|
'',
|
|
1162
|
+
schemaJsonAdmissionSource(plan),
|
|
1163
|
+
schemaFetchJsonSource(),
|
|
1007
1164
|
...nodeLines,
|
|
1008
|
-
|
|
1165
|
+
`function __pulse_fastly_schema_apply(schemaId: string, valueHandle: i32, encode: bool${textEncodeIds.size ? ', textResult: bool = false' : ''}): i32 {`,
|
|
1009
1166
|
' if (schemaId.length == 0) return valueHandle'
|
|
1010
1167
|
];
|
|
1011
1168
|
for (const [schemaIndex, schema] of schemas.entries()) {
|
|
1012
1169
|
lines.push(`${schemaIndex === 0 ? ' if' : ' else if'} (schemaId == ${quote(schema.id)}) {`);
|
|
1013
|
-
lines.push(`
|
|
1170
|
+
if (schema.jsonLimits) lines.push(` if (__pulse_fastly_admit_bounded_value(valueHandle, __pulse_schema_json_limits_${schemaIndex}()).failure != 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 57, -1); return 0 }`);
|
|
1171
|
+
lines.push(` const projected = ${roots[schemaIndex]}(valueHandle, !encode)`);
|
|
1014
1172
|
lines.push(' if (projected <= 0) return 0');
|
|
1015
1173
|
lines.push(' const input = __pulse_fastly_json(projected, 0)');
|
|
1016
1174
|
lines.push(` const normalized = encode ? __pulse_schema_encode_${schemaIndex}(input) : __pulse_schema_decode_${schemaIndex}(input)`);
|
|
1017
|
-
lines.push(' return
|
|
1175
|
+
if (textEncodeIds.has(schema.id)) lines.push(' if (textResult && encode) return __pulse_fastly_string_value(normalized)');
|
|
1176
|
+
if (schema.jsonLimits) lines.push(' const output = __pulse_fastly_parse_json(normalized)', ' if (output > 0) __pulse_fastly_deep_freeze(output)', ' return output');
|
|
1177
|
+
else lines.push(' return __pulse_fastly_parse_json(normalized)');
|
|
1018
1178
|
lines.push(' }');
|
|
1019
1179
|
}
|
|
1020
1180
|
lines.push(' __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 55, -1)');
|
|
@@ -1037,7 +1197,7 @@ function effectResultSource(plan, bindings) {
|
|
|
1037
1197
|
lines.push(`function __pulse_fastly_secret_store(effectIndex: i32): string { return ${quote(bindings.secretStore)} }`, '');
|
|
1038
1198
|
lines.push('function __pulse_fastly_jwt_secret_binding(effectIndex: i32): string {', ' switch (effectIndex) {');
|
|
1039
1199
|
for (const [index, effect] of (plan.effects || []).entries()) {
|
|
1040
|
-
if (
|
|
1200
|
+
if (['jwt.verify', 'jwt.sign'].includes(effect.kind) && effect.resource.keyType === 'secret') {
|
|
1041
1201
|
lines.push(` case ${index}: return ${quote(effect.resource.secretBinding)}`);
|
|
1042
1202
|
}
|
|
1043
1203
|
}
|
|
@@ -1052,12 +1212,31 @@ function effectResultSource(plan, bindings) {
|
|
|
1052
1212
|
for (const binding of bindings.grip.publish) lines.push(` case ${binding.index}: return ${quote(binding.authSecretRef || '')}`);
|
|
1053
1213
|
lines.push(' default: return ""', ' }', '}', '');
|
|
1054
1214
|
|
|
1215
|
+
// A config lookup has the same ready-result handoff at every static site.
|
|
1216
|
+
// Keep the site indices in the switch; pass the active index through so the
|
|
1217
|
+
// pending slot and any state error still belong to the original invocation.
|
|
1218
|
+
const configSites = (plan.effects || []).flatMap((effect, index) => effect.kind === 'config.get' ? [index] : []);
|
|
1219
|
+
if (configSites.length > 1) {
|
|
1220
|
+
lines.push('function __pulse_fastly_resolve_config_get(effectIndex: i32): i32 {');
|
|
1221
|
+
lines.push(' const response = __pulse_fastly_wait_ready(effectIndex)');
|
|
1222
|
+
lines.push(' if (response <= 0) return 0');
|
|
1223
|
+
lines.push(' return response');
|
|
1224
|
+
lines.push('}', '');
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1055
1227
|
lines.push('function __pulse_fastly_resolve_effect(effectIndex: i32): i32 {');
|
|
1056
1228
|
lines.push(' switch (effectIndex) {');
|
|
1057
1229
|
for (const [index, effect] of (plan.effects || []).entries()) {
|
|
1230
|
+
if (effect.kind === 'config.get' && configSites.length > 1) {
|
|
1231
|
+
if (index === configSites[0]) {
|
|
1232
|
+
for (const site of configSites) lines.push(` case ${site}:`);
|
|
1233
|
+
lines.push(' return __pulse_fastly_resolve_config_get(effectIndex)');
|
|
1234
|
+
}
|
|
1235
|
+
continue;
|
|
1236
|
+
}
|
|
1058
1237
|
const result = effect.result || {};
|
|
1059
1238
|
const decoder = result.decoder;
|
|
1060
|
-
const waiter = conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind) ? '__pulse_fastly_kv_conditional_wait' : ['s3.head', 's3.getText', 's3.putText'].includes(effect.kind) ? '__pulse_fastly_s3_wait' : effect.kind === 'fetch'
|
|
1239
|
+
const waiter = conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind) ? '__pulse_fastly_kv_conditional_wait' : ['s3.head', 's3.getText', 's3.putText', 's3.getBody'].includes(effect.kind) ? '__pulse_fastly_s3_wait' : effect.kind === 'fetch'
|
|
1061
1240
|
? '__pulse_fastly_wait_fetch'
|
|
1062
1241
|
: effect.kind === 'grip.publish'
|
|
1063
1242
|
? '__pulse_fastly_wait_grip_publish'
|
|
@@ -1099,9 +1278,11 @@ function effectDispatchSource(plan) {
|
|
|
1099
1278
|
'config.get': '__pulse_fastly_config_begin',
|
|
1100
1279
|
'secret.get': '__pulse_fastly_secret_begin',
|
|
1101
1280
|
'jwt.verify': '__pulse_fastly_jwt_begin',
|
|
1281
|
+
'jwt.sign': '__pulse_fastly_jwt_sign_begin',
|
|
1102
1282
|
's3.head': '__pulse_fastly_s3_begin',
|
|
1103
1283
|
's3.getText': '__pulse_fastly_s3_begin',
|
|
1104
1284
|
's3.putText': '__pulse_fastly_s3_begin',
|
|
1285
|
+
's3.getBody': '__pulse_fastly_s3_begin',
|
|
1105
1286
|
'kv.get': '__pulse_fastly_kv_get_begin',
|
|
1106
1287
|
'kv.put': '__pulse_fastly_kv_put_begin',
|
|
1107
1288
|
...Object.fromEntries(conditionalKv.KV_CONDITIONAL_KINDS.map(kind => [kind, '__pulse_fastly_kv_conditional_begin'])),
|
|
@@ -1135,7 +1316,14 @@ function effectDispatchSource(plan) {
|
|
|
1135
1316
|
}
|
|
1136
1317
|
|
|
1137
1318
|
function fastlyRuntimeSource(plan, bindings, options = {}) {
|
|
1319
|
+
const textEncodeIds = schemaTextEncodeIds(plan);
|
|
1138
1320
|
const effectCount = (plan.effects || []).length;
|
|
1321
|
+
const hasAssets = (plan.effects || []).some(effect => effect.kind === 'assets.lookup');
|
|
1322
|
+
const assetBeginSource = hasAssets ? String.raw`function __pulse_fastly_assets_payload(payload: __PulseFastlyValue): __PulseFastlyValue { const value = __pulse_fastly_value(__pulse_fastly_payload_field(payload, "payload")); if (value.kind != PULSE_VALUE_OBJECT) __pulse_fastly_fail(PULSE_ERROR_VALUE, 146, -1); return value }
|
|
1323
|
+
function __pulse_fastly_assets_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const value = __pulse_fastly_assets_payload(payload); if (__pulse_fastly_last_error != 0) return; if (__pulse_fastly_value(__pulse_fastly_payload_field(value, 'embeddedId')).kind != PULSE_VALUE_UNDEFINED) { __pulse_fastly_ready_effect(effectIndex, __pulse_fastly_embedded_asset(value)); return; } const store = __pulse_fastly_open_kv_store(effectIndex); if (__pulse_fastly_last_error != 0) return; const key = __pulse_fastly_string(__pulse_fastly_payload_field(value, "key")); const keyBytes = __pulse_fastly_utf8(key); const lookupOut = __pulse_fastly_out_i32(); const config = new StaticArray<i32>(1); const status = fastly_kv_store_lookup(store, changetype<usize>(keyBytes), keyBytes.byteLength, 0, changetype<usize>(config), changetype<usize>(lookupOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 147, effectIndex); return } unchecked(__pulse_fastly_pending[effectIndex] = __pulse_fastly_out_value(lookupOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_ASYNC) }` : '';
|
|
1324
|
+
const assetResultSource = hasAssets ? String.raw`function __pulse_fastly_asset_content_type(key: string): string { const dot = key.lastIndexOf("."); const extension = dot < 0 ? "" : key.substring(dot + 1).toLowerCase(); if (extension == "css") return "text/css; charset=utf-8"; if (extension == "html") return "text/html; charset=utf-8"; if (extension == "js" || extension == "mjs") return "text/javascript; charset=utf-8"; if (extension == "json") return "application/json; charset=utf-8"; if (extension == "svg") return "image/svg+xml"; if (extension == "txt") return "text/plain; charset=utf-8"; if (extension == "webp") return "image/webp"; if (extension == "png") return "image/png"; if (extension == "jpg" || extension == "jpeg") return "image/jpeg"; if (extension == "gif") return "image/gif"; if (extension == "ico") return "image/x-icon"; return "application/octet-stream" }
|
|
1325
|
+
function __pulse_fastly_asset_text(text: string): string { const parser = new __PulseJsonParser(text); const parsed = parser.parse(); if (parser.failed || parsed <= 0) return text; const object = __pulse_fastly_value(parsed); if (object.kind != PULSE_VALUE_OBJECT) return text; const marker = __pulse_fastly_find(object, "__pulseKv"); const value = __pulse_fastly_find(object, "value"); if (marker < 0 || value < 0 || __pulse_fastly_number(unchecked(object.values[marker])) != 1.0) return text; return __pulse_fastly_string(unchecked(object.values[value])) }
|
|
1326
|
+
function __pulse_fastly_wait_assets(effectIndex: i32): i32 { if (__pulse_fastly_pending_mode[effectIndex] == PULSE_FASTLY_PENDING_READY) return __pulse_fastly_wait_ready(effectIndex); if (unchecked(__pulse_fastly_pending_mode[effectIndex]) != PULSE_FASTLY_PENDING_ASYNC) { __pulse_fastly_fail(PULSE_ERROR_STATE, 148, effectIndex); return 0 } const pending = unchecked(__pulse_fastly_pending[effectIndex]); const bodyOut = __pulse_fastly_out_i32(); const metadata = new Uint8Array(1); const written = __pulse_fastly_out_i32(); const generation = new StaticArray<i64>(1); const kvError = __pulse_fastly_out_i32(); const status = fastly_kv_store_lookup_wait_v2(pending, changetype<usize>(bodyOut), metadata.dataStart, 0, changetype<usize>(written), changetype<usize>(generation), changetype<usize>(kvError)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_NONE); unchecked(__pulse_fastly_pending[effectIndex] = 0); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 149, effectIndex); return 0 } const outer = __pulse_fastly_value(unchecked(__pulse_fastly_payloads[effectIndex])); const payload = __pulse_fastly_assets_payload(outer); if (__pulse_fastly_last_error != 0) return 0; const key = __pulse_fastly_string(__pulse_fastly_payload_field(payload, "key")); const output = new __PulseFastlyValue(); output.kind = PULSE_VALUE_RESPONSE; const errorCode = __pulse_fastly_out_value(kvError); output.status = errorCode == 3 ? 404 : 200; if (errorCode != 1 && errorCode != 3) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 150, effectIndex); return 0 } const headersHandle = __pulse_fastly_payload_field(payload, "headers"); const headers = __pulse_fastly_value(headersHandle); let hasContentType = false; if (headers.kind == PULSE_VALUE_OBJECT) { for (let i = 0; i < headers.keys.length; i += 1) { const name = unchecked(headers.keys[i]); if (name.toLowerCase() == "content-type") hasContentType = true; output.headers.push(new __PulseFastlyHeader(name, __pulse_fastly_string(unchecked(headers.values[i])))) } } if (!hasContentType) output.headers.push(new __PulseFastlyHeader("content-type", output.status == 200 ? __pulse_fastly_asset_content_type(key) : "text/plain; charset=utf-8")); const cacheHandle = __pulse_fastly_payload_field(payload, "cacheControl"); if (__pulse_fastly_value(cacheHandle).kind != PULSE_VALUE_UNDEFINED) output.headers.push(new __PulseFastlyHeader("cache-control", __pulse_fastly_string(cacheHandle))); const methodHandle = __pulse_fastly_payload_field(payload, "method"); const method = __pulse_fastly_value(methodHandle).kind == PULSE_VALUE_UNDEFINED ? "GET" : __pulse_fastly_string(methodHandle).toUpperCase(); if (output.status == 200 && method != "HEAD") { const text = __pulse_fastly_read_body(__pulse_fastly_out_value(bodyOut), effectIndex); if (__pulse_fastly_last_error != 0) return 0; output.text = __pulse_fastly_asset_text(text) } return __pulse_fastly_put(output) }` : '';
|
|
1139
1327
|
const jwtCrypto = options.jwtCrypto || selectedJwtCrypto(plan);
|
|
1140
1328
|
const es256KeyRecords = options.es256KeyRecords || Object.freeze([]);
|
|
1141
1329
|
const jwtReality = options.jwtReality && options.jwtReality.enabled === true
|
|
@@ -1214,7 +1402,7 @@ function __pulse_fastly_jwt_send_error(): void {}
|
|
|
1214
1402
|
const gripAuthorizationSource = gripAuthentication
|
|
1215
1403
|
? `const secret = __pulse_fastly_grip_secret(effectIndex, ${quote(gripAuthentication.secretRef)}); if (__pulse_fastly_last_error != 0) return; headers.keys.push("authorization"); headers.values.push(__pulse_fastly_string_value("Bearer " + secret));`
|
|
1216
1404
|
: '';
|
|
1217
|
-
|
|
1405
|
+
const source = String.raw`
|
|
1218
1406
|
@external("fastly_abi", "init") declare function fastly_abi_init(version: i64): i32
|
|
1219
1407
|
@external("fastly_http_req", "body_downstream_get") declare function fastly_http_req_body_downstream_get(requestOut: usize, bodyOut: usize): i32
|
|
1220
1408
|
@external("fastly_http_req", "method_get") declare function fastly_http_req_method_get(handle: i32, buffer: usize, bufferLength: i32, writtenOut: usize): i32
|
|
@@ -1286,6 +1474,7 @@ const PULSE_ERROR_JWT: i32 = 1008
|
|
|
1286
1474
|
const PULSE_ERROR_REQUEST_BODY: i32 = 1009
|
|
1287
1475
|
|
|
1288
1476
|
class __PulseFastlyValue {
|
|
1477
|
+
${tracksJsonDuplicates(plan) ? 'duplicateJsonKeys: bool = false' : ''}
|
|
1289
1478
|
kind: i32 = PULSE_VALUE_UNDEFINED
|
|
1290
1479
|
boolean: i32 = 0
|
|
1291
1480
|
number: f64 = 0.0
|
|
@@ -1305,6 +1494,9 @@ ${nativeStringFields}
|
|
|
1305
1494
|
responseHandle: i32 = 0
|
|
1306
1495
|
bodyHandle: i32 = 0
|
|
1307
1496
|
bodyLoaded: i32 = 0
|
|
1497
|
+
s3Body: bool = false
|
|
1498
|
+
s3Deadline: i64 = 0
|
|
1499
|
+
s3Length: i32 = 0
|
|
1308
1500
|
status: i32 = 200
|
|
1309
1501
|
// Scalar handles do not need collection backing stores.
|
|
1310
1502
|
private headersStorage: Array<__PulseFastlyHeader> | null = null
|
|
@@ -1312,6 +1504,7 @@ ${nativeStringFields}
|
|
|
1312
1504
|
if (this.headersStorage === null) this.headersStorage = new Array<__PulseFastlyHeader>()
|
|
1313
1505
|
return this.headersStorage!
|
|
1314
1506
|
}
|
|
1507
|
+
binaryBody: Uint8Array | null = null
|
|
1315
1508
|
immutable: bool = false
|
|
1316
1509
|
}
|
|
1317
1510
|
class __PulseFastlyHeader {
|
|
@@ -1328,7 +1521,7 @@ class __PulseJsonParser {
|
|
|
1328
1521
|
parse(): i32 { this.skip(); const value = this.value(0); this.skip(); if (this.index != this.source.length) this.failed = true; return this.failed ? 0 : value }
|
|
1329
1522
|
value(depth: i32): i32 {
|
|
1330
1523
|
this.skip(); if (this.conditionalKv && (depth > ${conditionalKv.KV_CONDITIONAL_LIMITS.depth + 1} || ++this.entries > ${conditionalKv.KV_CONDITIONAL_LIMITS.entries + 2})) { this.failed = true; this.tooLarge = true; return 0 }
|
|
1331
|
-
if (this.index >= this.source.length || (!this.conditionalKv && depth > ${plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 128 : 64})) { this.failed = true; return 0 }
|
|
1524
|
+
if (this.index >= this.source.length || (!this.conditionalKv && depth > ${schemaParserDepth(plan, plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 128 : 64)})) { this.failed = true; return 0 }
|
|
1332
1525
|
const c = this.source.charCodeAt(this.index)
|
|
1333
1526
|
if (c == 34) return __pulse_fastly_string_value(this.string())
|
|
1334
1527
|
if (c == 123) return this.object(depth + 1)
|
|
@@ -1342,14 +1535,18 @@ class __PulseJsonParser {
|
|
|
1342
1535
|
string(): string {
|
|
1343
1536
|
// Find a bounded span, then decode each UTF-16 unit once. Concatenating one
|
|
1344
1537
|
// character at a time exhausts stub/fixed memory on a valid maximum KV value.
|
|
1345
|
-
const start = this.index + 1; let end = start;
|
|
1538
|
+
const start = this.index + 1; let end = start; let plain = true;
|
|
1346
1539
|
while (end < this.source.length) {
|
|
1347
1540
|
const c = this.source.charCodeAt(end);
|
|
1348
1541
|
if (c == 34) break;
|
|
1349
|
-
if (c == 92) end += 1;
|
|
1542
|
+
if (c == 92) { plain = false; end += 1; }
|
|
1543
|
+
else if (c < 32) plain = false;
|
|
1350
1544
|
end += 1;
|
|
1351
1545
|
}
|
|
1352
1546
|
if (end >= this.source.length) { this.failed = true; return "" }
|
|
1547
|
+
// A validated span needs one owned string, without a temporary UTF-16 array.
|
|
1548
|
+
// Keep escaped and malformed spans on the existing decoder path.
|
|
1549
|
+
if (plain) { this.index = end + 1; return this.source.substring(start, end); }
|
|
1353
1550
|
const output = new Uint16Array(end - start); let count = 0; this.index = start;
|
|
1354
1551
|
while (this.index < end) {
|
|
1355
1552
|
let c = this.source.charCodeAt(this.index++);
|
|
@@ -1427,6 +1624,7 @@ class __PulseJsonParser {
|
|
|
1427
1624
|
const key = this.string(); this.skip()
|
|
1428
1625
|
if (this.conditionalKv && __pulse_fastly_find(__pulse_fastly_value(output), key) >= 0) { this.failed = true; break }
|
|
1429
1626
|
if (this.index >= this.source.length || this.source.charCodeAt(this.index) != 58) { this.failed = true; break }
|
|
1627
|
+
${tracksJsonDuplicates(plan) ? 'if (__pulse_fastly_find(__pulse_fastly_value(output), key) >= 0) __pulse_fastly_value(output).duplicateJsonKeys = true' : ''}
|
|
1430
1628
|
this.index += 1; host_value_object_set(output, __pulse_fastly_string_value(key), this.value(depth)); this.skip()
|
|
1431
1629
|
if (this.index >= this.source.length) { this.failed = true; break }
|
|
1432
1630
|
const c = this.source.charCodeAt(this.index); this.index += 1
|
|
@@ -1533,7 +1731,7 @@ function __pulse_fastly_quote(value: string): string {
|
|
|
1533
1731
|
return String.UTF16.decodeUnsafe(out.dataStart, out.byteLength);
|
|
1534
1732
|
}
|
|
1535
1733
|
function __pulse_fastly_json_parts(handle: i32, depth: i32, parts: Array<string>): void {
|
|
1536
|
-
if (depth > ${plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 128 : 64}) { __pulse_fastly_fail(PULSE_ERROR_JSON, 2, -1); parts.push("null"); return }
|
|
1734
|
+
if (depth > ${schemaParserDepth(plan, plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 128 : 64)}) { __pulse_fastly_fail(PULSE_ERROR_JSON, 2, -1); parts.push("null"); return }
|
|
1537
1735
|
const value = __pulse_fastly_value(handle)
|
|
1538
1736
|
if (value.kind == PULSE_VALUE_BOOLEAN) { parts.push(value.boolean != 0 ? "true" : "false"); return }
|
|
1539
1737
|
if (value.kind == PULSE_VALUE_NUMBER) { parts.push(__pulse_fastly_number_string(value.number)); return }
|
|
@@ -1623,7 +1821,7 @@ function host_request_path(): i32 { return __pulse_fastly_string_value(__pulse_f
|
|
|
1623
1821
|
function host_request_headers(): i32 { return __pulse_request_headers_read() }
|
|
1624
1822
|
function host_request_header(name: i32): i32 { const value = __pulse_fastly_request_header_text(__pulse_fastly_string(name)); return value.length == 0 ? host_value_undefined() : __pulse_fastly_string_value(value) }
|
|
1625
1823
|
function host_request_text(): i32 { if (__pulse_request_body_failure != 0) { __pulse_fastly_fail(PULSE_ERROR_REQUEST_BODY, __pulse_request_body_failure, -1); return 0 } if (__pulse_fastly_request_body_loaded == 0) { __pulse_fastly_request_body = __pulse_fastly_read_body(__pulse_fastly_request_body_handle, -1, true); __pulse_fastly_request_body_loaded = 1; if (__pulse_fastly_last_error == PULSE_ERROR_REQUEST_BODY) __pulse_request_body_failure = __pulse_fastly_error_stage } if (__pulse_fastly_last_error != 0) return 0; return __pulse_fastly_string_value(__pulse_fastly_request_body) }
|
|
1626
|
-
function host_request_json(schema: i32): i32 { const parsed = __pulse_fastly_parse_json(__pulse_fastly_string(host_request_text())); if (parsed <= 0) return 0; const schemaValue = __pulse_fastly_value(schema); return schemaValue.kind == PULSE_VALUE_STRING ? __pulse_fastly_schema_apply(schemaValue.text, parsed, false) : parsed }
|
|
1824
|
+
function host_request_json(schema: i32): i32 { const parsed = ${hasJsonAdmission(plan) ? '__pulse_fastly_schema_parse_json(__pulse_fastly_string(host_request_text()), schema)' : '__pulse_fastly_parse_json(__pulse_fastly_string(host_request_text()))'}; if (parsed <= 0) return 0; const schemaValue = __pulse_fastly_value(schema); return schemaValue.kind == PULSE_VALUE_STRING ? __pulse_fastly_schema_apply(schemaValue.text, parsed, false) : parsed }
|
|
1627
1825
|
function __pulse_fastly_headers_from_options(options: __PulseFastlyValue, output: __PulseFastlyValue): void { const index = __pulse_fastly_find(options, "headers"); if (index < 0) return; const headers = __pulse_fastly_value(unchecked(options.values[index])); if (headers.kind == PULSE_VALUE_OBJECT) { for (let i = 0; i < headers.keys.length; i += 1) { const name = unchecked(headers.keys[i]); const value = __pulse_fastly_value(unchecked(headers.values[i])); if (value.kind == PULSE_VALUE_ARRAY) { for (let j = 0; j < value.values.length; j += 1) output.headers.push(new __PulseFastlyHeader(name, __pulse_fastly_string(unchecked(value.values[j])))) } else output.headers.push(new __PulseFastlyHeader(name, __pulse_fastly_string(unchecked(headers.values[i])))) } return } if (headers.kind == PULSE_VALUE_ARRAY) { for (let i = 0; i < headers.values.length; i += 1) { const pair = __pulse_fastly_value(unchecked(headers.values[i])); if (pair.kind != PULSE_VALUE_ARRAY || pair.values.length < 2) continue; output.headers.push(new __PulseFastlyHeader(__pulse_fastly_string(unchecked(pair.values[0])), __pulse_fastly_string(unchecked(pair.values[1])))) } } }
|
|
1628
1826
|
function __pulse_fastly_response(value: i32, optionsHandle: i32, json: bool): i32 { const output = new __PulseFastlyValue(); output.kind = PULSE_VALUE_RESPONSE; output.status = 200; const options = __pulse_fastly_value(optionsHandle); let bodyValue = value; if (options.kind == PULSE_VALUE_OBJECT) { const statusIndex = __pulse_fastly_find(options, "status"); if (statusIndex >= 0) output.status = i32(__pulse_fastly_number(unchecked(options.values[statusIndex]))); const schemaIndex = __pulse_fastly_find(options, "schema"); if (json && schemaIndex >= 0) { bodyValue = __pulse_fastly_schema_apply(__pulse_fastly_string(unchecked(options.values[schemaIndex])), value, true); if (bodyValue <= 0) return 0 } __pulse_fastly_headers_from_options(options, output) } output.text = json ? __pulse_fastly_json(bodyValue, 0) : __pulse_fastly_string(value); let hasContentType = false; for (let i = 0; i < output.headers.length; i += 1) if (unchecked(output.headers[i]).name.toLowerCase() == "content-type") hasContentType = true; if (!hasContentType) output.headers.push(new __PulseFastlyHeader("content-type", json ? "application/json; charset=utf-8" : "text/plain; charset=utf-8")); return __pulse_fastly_put(output) }
|
|
1629
1827
|
function host_response_json(value: i32, options: i32): i32 { return __pulse_fastly_response(value, options, true) }
|
|
@@ -1633,7 +1831,7 @@ function host_schema_decode(text: i32, schema: i32): i32 {
|
|
|
1633
1831
|
const input = __pulse_fastly_value(text)
|
|
1634
1832
|
if (input.kind != PULSE_VALUE_STRING) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 52, -1); return 0 }
|
|
1635
1833
|
if (String.UTF8.byteLength(input.text) > __PULSE_SCHEMA_MAX_BYTES) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 21, -1); return 0 }
|
|
1636
|
-
const parsed = __pulse_fastly_parse_json(input.text)
|
|
1834
|
+
const parsed = ${hasJsonAdmission(plan) ? '__pulse_fastly_schema_parse_json(input.text, schema)' : '__pulse_fastly_parse_json(input.text)'}
|
|
1637
1835
|
if (parsed <= 0) return 0
|
|
1638
1836
|
const decoded = __pulse_fastly_schema_apply(id.text, parsed, false)
|
|
1639
1837
|
if (decoded <= 0) return 0
|
|
@@ -1643,11 +1841,12 @@ function host_schema_decode(text: i32, schema: i32): i32 {
|
|
|
1643
1841
|
function host_schema_encode(value: i32, schema: i32): i32 {
|
|
1644
1842
|
const id = __pulse_fastly_value(schema)
|
|
1645
1843
|
if (id.kind != PULSE_VALUE_STRING || id.text.length == 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 55, -1); return 0 }
|
|
1646
|
-
const
|
|
1844
|
+
${textEncodeIds.length ? `const textResult = ${textEncodeIds.map(id => `id.text == ${quote(id)}`).join(' || ')}
|
|
1845
|
+
const projected = __pulse_fastly_schema_apply(id.text, value, true, textResult)` : 'const projected = __pulse_fastly_schema_apply(id.text, value, true)'}
|
|
1647
1846
|
if (projected <= 0) return 0
|
|
1648
|
-
const text = __pulse_fastly_json(projected, 0)
|
|
1847
|
+
const text = ${textEncodeIds.length ? 'textResult ? __pulse_fastly_string(projected) : ' : ''}__pulse_fastly_json(projected, 0)
|
|
1649
1848
|
if (String.UTF8.byteLength(text) > __PULSE_SCHEMA_MAX_BYTES) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 21, -1); return 0 }
|
|
1650
|
-
return __pulse_fastly_string_value(text)
|
|
1849
|
+
return ${textEncodeIds.length ? 'textResult ? projected : ' : ''}__pulse_fastly_string_value(text)
|
|
1651
1850
|
}
|
|
1652
1851
|
function host_response_text(value: i32, options: i32): i32 { return __pulse_fastly_response(value, options, false) }
|
|
1653
1852
|
function host_response_custom(specHandle: i32): i32 { const spec = __pulse_fastly_value(specHandle); if (spec.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 40, -1); return 0 } const output = new __PulseFastlyValue(); output.kind = PULSE_VALUE_RESPONSE; output.status = 200; const statusIndex = __pulse_fastly_find(spec, "status"); if (statusIndex >= 0) output.status = i32(__pulse_fastly_number(unchecked(spec.values[statusIndex]))); const bodyIndex = __pulse_fastly_find(spec, "body"); if (bodyIndex >= 0) output.text = __pulse_fastly_string(unchecked(spec.values[bodyIndex])); __pulse_fastly_headers_from_options(spec, output); return __pulse_fastly_put(output) }
|
|
@@ -1658,7 +1857,7 @@ function host_grip_subscribe(responseHandle: i32, optionsHandle: i32): i32 { con
|
|
|
1658
1857
|
function host_grip_handoff(optionsHandle: i32): i32 { const options = __pulse_fastly_value(optionsHandle); if (options.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 139, -1); return 0 } const output = new __PulseFastlyValue(); output.kind = PULSE_VALUE_RESPONSE; output.status = 200; const statusIndex = __pulse_fastly_find(options, "status"); if (statusIndex >= 0) output.status = i32(__pulse_fastly_number(unchecked(options.values[statusIndex]))); const bodyIndex = __pulse_fastly_find(options, "body"); if (bodyIndex >= 0) output.text = __pulse_fastly_string(unchecked(options.values[bodyIndex])); __pulse_fastly_headers_from_options(options, output); let hasContentType = false; for (let i = 0; i < output.headers.length; i += 1) if (unchecked(output.headers[i]).name.toLowerCase() == "content-type") hasContentType = true; if (!hasContentType) output.headers.push(new __PulseFastlyHeader("Content-Type", "application/websocket-events")); return __pulse_fastly_grip_frame(options, output) ? __pulse_fastly_put(output) : 0 }
|
|
1659
1858
|
function host_kv_namespace(name: i32): i32 { return name }
|
|
1660
1859
|
function host_fetch_text(responseHandle: i32): i32 { const response = __pulse_fastly_value(responseHandle); if (response.kind != PULSE_VALUE_FETCH) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 42, -1); return 0 } return __pulse_fastly_string_value(__pulse_fastly_fetch_body(response)) }
|
|
1661
|
-
function host_fetch_json(responseHandle: i32, schemaHandle: i32): i32 { const response = __pulse_fastly_value(responseHandle); if (response.kind != PULSE_VALUE_FETCH) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 43, -1); return 0 } if (__PULSE_SCHEMA_REQUIRE_JSON) { const contentType = __pulse_fastly_fetch_header_text(response.responseHandle, "content-type"); if (contentType.length == 0 || contentType.toLowerCase().indexOf("json") < 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 44, -1); return 0 } } const parsed = __pulse_fastly_parse_json(__pulse_fastly_fetch_body(response)); if (parsed <= 0) return 0; const schema = __pulse_fastly_value(schemaHandle); return schema.kind == PULSE_VALUE_STRING ? __pulse_fastly_schema_apply(schema.text, parsed, false) : parsed }
|
|
1860
|
+
function host_fetch_json(responseHandle: i32, schemaHandle: i32): i32 { const response = __pulse_fastly_value(responseHandle); if (response.kind != PULSE_VALUE_FETCH) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 43, -1); return 0 } if (__PULSE_SCHEMA_REQUIRE_JSON) { const contentType = __pulse_fastly_fetch_header_text(response.responseHandle, "content-type"); if (contentType.length == 0 || contentType.toLowerCase().indexOf("json") < 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 44, -1); return 0 } } const parsed = ${hasJsonAdmission(plan) ? '__pulse_fastly_schema_parse_json(__pulse_fastly_fetch_body(response), schemaHandle)' : '__pulse_fastly_parse_json(__pulse_fastly_fetch_body(response))'}; if (parsed <= 0) return 0; const schema = __pulse_fastly_value(schemaHandle); return schema.kind == PULSE_VALUE_STRING ? __pulse_fastly_schema_apply(schema.text, parsed, false) : parsed }
|
|
1662
1861
|
function host_fetch_header(responseHandle: i32, nameHandle: i32): i32 { const response = __pulse_fastly_value(responseHandle); if (response.kind != PULSE_VALUE_FETCH) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 45, -1); return 0 } const value = __pulse_fastly_fetch_header_text(response.responseHandle, __pulse_fastly_string(nameHandle)); return value.length == 0 ? host_value_undefined() : __pulse_fastly_string_value(value) }
|
|
1663
1862
|
function __pulse_fastly_payload_field(payload: __PulseFastlyValue, name: string): i32 { const index = __pulse_fastly_find(payload, name); return index < 0 ? host_value_undefined() : unchecked(payload.values[index]) }
|
|
1664
1863
|
function __pulse_fastly_ready_effect(effectIndex: i32, handle: i32): void { unchecked(__pulse_fastly_ready[effectIndex] = handle); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_READY) }
|
|
@@ -1679,12 +1878,12 @@ function __pulse_fastly_jwt_record_verified(): void { __pulse_fastly_jwt_verifie
|
|
|
1679
1878
|
function __pulse_fastly_jwt_record_signing_input(bytes: i32, digest: string): void { __pulse_fastly_jwt_signing_input_bytes = bytes; __pulse_fastly_jwt_signing_input_sha256 = digest }
|
|
1680
1879
|
function __pulse_fastly_jwt_capture_clock(effectIndex: i32): f64 { __pulse_fastly_jwt_clock_calls += 1; __pulse_fastly_jwt_event_order += "C"; const out = new StaticArray<i64>(1); const status = wasi_snapshot_preview1_clock_time_get(0, 1, changetype<usize>(out)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 226, effectIndex); return NaN } ${jwtClockOverride ? `return ${jwtClockOverride}` : 'return f64(load<i64>(changetype<usize>(out))) / 1000000000.0'} }
|
|
1681
1880
|
function __pulse_fastly_jwt_validate_schema(effectIndex: i32, schemaId: string, claimsHandle: i32): i32 { __pulse_fastly_jwt_schema_calls += 1; __pulse_fastly_jwt_event_order += "H"; if (__pulse_fastly_jwt_case_id() == "claims-schema-failure-after-registered-claims") return 0; return __pulse_fastly_schema_apply(schemaId, claimsHandle, false) }
|
|
1881
|
+
function __pulse_fastly_jwt_sign_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const result = pulse_jwt_fastly_sign(effectIndex, unchecked(__pulse_fastly_payloads[effectIndex])); if (result > 0 && __pulse_fastly_last_error == 0) __pulse_fastly_ready_effect(effectIndex, result) }
|
|
1682
1882
|
function __pulse_fastly_jwt_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const result = pulse_jwt_fastly_verify(effectIndex, unchecked(__pulse_fastly_payloads[effectIndex])); if (result > 0 && __pulse_fastly_last_error == 0) __pulse_fastly_ready_effect(effectIndex, result) }
|
|
1683
1883
|
function __pulse_fastly_start_request(effectIndex: i32, url: string, method: string, headers: __PulseFastlyValue, body: string, backend: string): void { const requestOut = __pulse_fastly_out_i32(); let status = fastly_http_req_new(changetype<usize>(requestOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 70, effectIndex); return } const requestHandle = __pulse_fastly_out_value(requestOut); const bodyOut = __pulse_fastly_out_i32(); status = fastly_http_body_new(changetype<usize>(bodyOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 71, effectIndex); return } const bodyHandle = __pulse_fastly_out_value(bodyOut); const methodBytes = __pulse_fastly_utf8(method); status = fastly_http_req_method_set(requestHandle, changetype<usize>(methodBytes), methodBytes.byteLength); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 72, effectIndex); return } const urlBytes = __pulse_fastly_utf8(url); status = fastly_http_req_uri_set(requestHandle, changetype<usize>(urlBytes), urlBytes.byteLength); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 73, effectIndex); return } if (headers.kind == PULSE_VALUE_OBJECT) { for (let i = 0; i < headers.keys.length; i += 1) { const nameBytes = __pulse_fastly_utf8(unchecked(headers.keys[i])); const valueBytes = __pulse_fastly_utf8(__pulse_fastly_string(unchecked(headers.values[i]))); status = fastly_http_req_header_insert(requestHandle, changetype<usize>(nameBytes), nameBytes.byteLength, changetype<usize>(valueBytes), valueBytes.byteLength); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 74, effectIndex); return } } } if (body.length > 0) { status = __pulse_fastly_write_body(bodyHandle, body); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 75, effectIndex); return } } if (backend.length == 0) { __pulse_fastly_fail(PULSE_ERROR_STATE, 76, effectIndex); return } const backendBytes = __pulse_fastly_utf8(backend); const pendingOut = __pulse_fastly_out_i32(); status = fastly_http_req_send_async(requestHandle, bodyHandle, changetype<usize>(backendBytes), backendBytes.byteLength, changetype<usize>(pendingOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_TRANSPORT, 77, effectIndex); return } unchecked(__pulse_fastly_pending[effectIndex] = __pulse_fastly_out_value(pendingOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_ASYNC) }
|
|
1684
|
-
function __pulse_fastly_fetch_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const urlIndex = __pulse_fastly_find(payload, "url"); if (urlIndex < 0) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 48, effectIndex); return } const url = __pulse_fastly_string(unchecked(payload.values[urlIndex])); const initIndex = __pulse_fastly_find(payload, "init"); const init = initIndex >= 0 ? __pulse_fastly_value(unchecked(payload.values[initIndex])) : new __PulseFastlyValue(); let method = "GET"; let body = ""; let headers = new __PulseFastlyValue(); headers.kind = PULSE_VALUE_OBJECT; if (init.kind == PULSE_VALUE_OBJECT) { const methodIndex = __pulse_fastly_find(init, "method"); if (methodIndex >= 0) method = __pulse_fastly_string(unchecked(init.values[methodIndex])).toUpperCase(); const headersIndex = __pulse_fastly_find(init, "headers"); if (headersIndex >= 0) headers = __pulse_fastly_value(unchecked(init.values[headersIndex])); const bodyIndex = __pulse_fastly_find(init, "body"); const jsonIndex = __pulse_fastly_find(init, "json"); if (bodyIndex >= 0) { const bodyValue = unchecked(init.values[bodyIndex]); const bodyObject = __pulse_fastly_value(bodyValue); body = bodyObject.kind == PULSE_VALUE_STRING ? bodyObject.text : __pulse_fastly_json(bodyValue, 0) } else if (jsonIndex >= 0) { body =
|
|
1884
|
+
function __pulse_fastly_fetch_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const urlIndex = __pulse_fastly_find(payload, "url"); if (urlIndex < 0) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 48, effectIndex); return } const url = __pulse_fastly_string(unchecked(payload.values[urlIndex])); const initIndex = __pulse_fastly_find(payload, "init"); const init = initIndex >= 0 ? __pulse_fastly_value(unchecked(payload.values[initIndex])) : new __PulseFastlyValue(); let method = "GET"; let body = ""; let headers = new __PulseFastlyValue(); headers.kind = PULSE_VALUE_OBJECT; if (init.kind == PULSE_VALUE_OBJECT) { const methodIndex = __pulse_fastly_find(init, "method"); if (methodIndex >= 0) method = __pulse_fastly_string(unchecked(init.values[methodIndex])).toUpperCase(); const headersIndex = __pulse_fastly_find(init, "headers"); if (headersIndex >= 0) headers = __pulse_fastly_value(unchecked(init.values[headersIndex])); const bodyIndex = __pulse_fastly_find(init, "body"); const jsonIndex = __pulse_fastly_find(init, "json"); if (bodyIndex >= 0) { const bodyValue = unchecked(init.values[bodyIndex]); const bodyObject = __pulse_fastly_value(bodyValue); body = bodyObject.kind == PULSE_VALUE_STRING ? bodyObject.text : __pulse_fastly_json(bodyValue, 0) } else if (jsonIndex >= 0) { body = __pulse_fastly_fetch_json_body(init, unchecked(init.values[jsonIndex])); if (__pulse_fastly_last_error != 0) return; headers = __pulse_fastly_fetch_json_headers(headers) } } __pulse_fastly_start_request(effectIndex, url, method, headers, body, __pulse_fastly_backend(effectIndex)) }
|
|
1685
1885
|
function __pulse_fastly_kv_get_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const store = __pulse_fastly_open_kv_store(effectIndex); if (__pulse_fastly_last_error != 0) return; const key = __pulse_fastly_string(__pulse_fastly_payload_field(payload, "key")); const keyBytes = __pulse_fastly_utf8(key); const lookupOut = __pulse_fastly_out_i32(); const config = new StaticArray<i32>(1); const status = fastly_kv_store_lookup(store, changetype<usize>(keyBytes), keyBytes.byteLength, 0, changetype<usize>(config), changetype<usize>(lookupOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 116, effectIndex); return } unchecked(__pulse_fastly_pending[effectIndex] = __pulse_fastly_out_value(lookupOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_ASYNC) }
|
|
1686
|
-
|
|
1687
|
-
function __pulse_fastly_assets_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const value = __pulse_fastly_assets_payload(payload); if (__pulse_fastly_last_error != 0) return; const store = __pulse_fastly_open_kv_store(effectIndex); if (__pulse_fastly_last_error != 0) return; const key = __pulse_fastly_string(__pulse_fastly_payload_field(value, "key")); const keyBytes = __pulse_fastly_utf8(key); const lookupOut = __pulse_fastly_out_i32(); const config = new StaticArray<i32>(1); const status = fastly_kv_store_lookup(store, changetype<usize>(keyBytes), keyBytes.byteLength, 0, changetype<usize>(config), changetype<usize>(lookupOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 147, effectIndex); return } unchecked(__pulse_fastly_pending[effectIndex] = __pulse_fastly_out_value(lookupOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_ASYNC) }
|
|
1886
|
+
${assetBeginSource}
|
|
1688
1887
|
function __pulse_fastly_kv_put_begin(effectIndex: i32, payload: __PulseFastlyValue): void { const store = __pulse_fastly_open_kv_store(effectIndex); if (__pulse_fastly_last_error != 0) return; const key = __pulse_fastly_string(__pulse_fastly_payload_field(payload, "key")); const value = __pulse_fastly_payload_field(payload, "value"); const bodyOut = __pulse_fastly_out_i32(); let status = fastly_http_body_new(changetype<usize>(bodyOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 117, effectIndex); return } const body = __pulse_fastly_out_value(bodyOut); status = __pulse_fastly_write_body(body, "{\"__pulseKv\":1,\"value\":" + __pulse_fastly_json(value, 0) + "}"); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 118, effectIndex); return } const keyBytes = __pulse_fastly_utf8(key); const insertOut = __pulse_fastly_out_i32(); const config = new StaticArray<i64>(4); status = fastly_kv_store_insert(store, changetype<usize>(keyBytes), keyBytes.byteLength, body, 0, changetype<usize>(config), changetype<usize>(insertOut)); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 119, effectIndex); return } unchecked(__pulse_fastly_pending[effectIndex] = __pulse_fastly_out_value(insertOut)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_ASYNC) }
|
|
1689
1888
|
function __pulse_fastly_grip_add_channel(channel: string): void { if (channel.length == 0) return; for (let i = 0; i < __pulse_fastly_grip_channels.length; i += 1) if (unchecked(__pulse_fastly_grip_channels[i]) == channel) return; __pulse_fastly_grip_channels.push(channel) }
|
|
1690
1889
|
function __pulse_fastly_grip_payload(payload: __PulseFastlyValue): __PulseFastlyValue { return __pulse_fastly_value(__pulse_fastly_payload_field(payload, "payload")) }
|
|
@@ -1698,19 +1897,22 @@ function __pulse_fastly_wait_grip_publish(effectIndex: i32): i32 { const respons
|
|
|
1698
1897
|
function __pulse_fastly_wait_grip_broadcast(effectIndex: i32): i32 { const responseHandle = __pulse_fastly_wait_fetch(effectIndex); if (responseHandle <= 0) return 0; const response = __pulse_fastly_value(responseHandle); if (response.kind != PULSE_VALUE_FETCH) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 142, effectIndex); return 0 } const statusOut = __pulse_fastly_out_i32(); const statusCall = fastly_http_resp_status_get(response.responseHandle, changetype<usize>(statusOut)); if (statusCall != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 143, effectIndex); return 0 } const originStatus = __pulse_fastly_out_value(statusOut); if (originStatus < 200 || originStatus > 299) { __pulse_fastly_fail(PULSE_ERROR_TRANSPORT, 144, effectIndex); return 0 } const receipt = host_value_object(); host_value_object_set(receipt, __pulse_fastly_string_value("accepted"), host_value_boolean(1)); host_value_object_set(receipt, __pulse_fastly_string_value("status"), host_value_number(f64(originStatus))); return receipt }
|
|
1699
1898
|
function __pulse_fastly_wait_ready(effectIndex: i32): i32 { if (unchecked(__pulse_fastly_pending_mode[effectIndex]) != PULSE_FASTLY_PENDING_READY) { __pulse_fastly_fail(PULSE_ERROR_STATE, 124, effectIndex); return 0 } const value = unchecked(__pulse_fastly_ready[effectIndex]); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_NONE); unchecked(__pulse_fastly_pending[effectIndex] = 0); unchecked(__pulse_fastly_ready[effectIndex] = 0); return value }
|
|
1700
1899
|
function __pulse_fastly_wait_kv_get(effectIndex: i32): i32 { if (unchecked(__pulse_fastly_pending_mode[effectIndex]) == PULSE_FASTLY_PENDING_READY) return __pulse_fastly_wait_ready(effectIndex); if (unchecked(__pulse_fastly_pending_mode[effectIndex]) != PULSE_FASTLY_PENDING_ASYNC) { __pulse_fastly_fail(PULSE_ERROR_STATE, 125, effectIndex); return 0 } const pending = unchecked(__pulse_fastly_pending[effectIndex]); const bodyOut = __pulse_fastly_out_i32(); const metadata = new Uint8Array(1); const written = __pulse_fastly_out_i32(); const generation = new StaticArray<i64>(1); const kvError = __pulse_fastly_out_i32(); const status = fastly_kv_store_lookup_wait_v2(pending, changetype<usize>(bodyOut), metadata.dataStart, 0, changetype<usize>(written), changetype<usize>(generation), changetype<usize>(kvError)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_NONE); unchecked(__pulse_fastly_pending[effectIndex] = 0); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 125, effectIndex); return 0 } const errorCode = __pulse_fastly_out_value(kvError); if (errorCode == 3) return host_value_undefined(); if (errorCode != 1) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 128, effectIndex); return 0 } const text = __pulse_fastly_read_body(__pulse_fastly_out_value(bodyOut), effectIndex); if (__pulse_fastly_last_error != 0) return 0; const parser = new __PulseJsonParser(text); const parsed = parser.parse(); if (parser.failed || parsed <= 0) return __pulse_fastly_string_value(text); const object = __pulse_fastly_value(parsed); if (object.kind == PULSE_VALUE_OBJECT) { const marker = __pulse_fastly_find(object, "__pulseKv"); const value = __pulse_fastly_find(object, "value"); if (marker >= 0 && value >= 0 && __pulse_fastly_number(unchecked(object.values[marker])) == 1.0) return unchecked(object.values[value]) } return parsed }
|
|
1701
|
-
|
|
1702
|
-
function __pulse_fastly_asset_text(text: string): string { const parser = new __PulseJsonParser(text); const parsed = parser.parse(); if (parser.failed || parsed <= 0) return text; const object = __pulse_fastly_value(parsed); if (object.kind != PULSE_VALUE_OBJECT) return text; const marker = __pulse_fastly_find(object, "__pulseKv"); const value = __pulse_fastly_find(object, "value"); if (marker < 0 || value < 0 || __pulse_fastly_number(unchecked(object.values[marker])) != 1.0) return text; return __pulse_fastly_string(unchecked(object.values[value])) }
|
|
1703
|
-
function __pulse_fastly_wait_assets(effectIndex: i32): i32 { if (unchecked(__pulse_fastly_pending_mode[effectIndex]) != PULSE_FASTLY_PENDING_ASYNC) { __pulse_fastly_fail(PULSE_ERROR_STATE, 148, effectIndex); return 0 } const pending = unchecked(__pulse_fastly_pending[effectIndex]); const bodyOut = __pulse_fastly_out_i32(); const metadata = new Uint8Array(1); const written = __pulse_fastly_out_i32(); const generation = new StaticArray<i64>(1); const kvError = __pulse_fastly_out_i32(); const status = fastly_kv_store_lookup_wait_v2(pending, changetype<usize>(bodyOut), metadata.dataStart, 0, changetype<usize>(written), changetype<usize>(generation), changetype<usize>(kvError)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_NONE); unchecked(__pulse_fastly_pending[effectIndex] = 0); if (status != FASTLY_STATUS_OK) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 149, effectIndex); return 0 } const outer = __pulse_fastly_value(unchecked(__pulse_fastly_payloads[effectIndex])); const payload = __pulse_fastly_assets_payload(outer); if (__pulse_fastly_last_error != 0) return 0; const key = __pulse_fastly_string(__pulse_fastly_payload_field(payload, "key")); const output = new __PulseFastlyValue(); output.kind = PULSE_VALUE_RESPONSE; const errorCode = __pulse_fastly_out_value(kvError); output.status = errorCode == 3 ? 404 : 200; if (errorCode != 1 && errorCode != 3) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 150, effectIndex); return 0 } const headersHandle = __pulse_fastly_payload_field(payload, "headers"); const headers = __pulse_fastly_value(headersHandle); let hasContentType = false; if (headers.kind == PULSE_VALUE_OBJECT) { for (let i = 0; i < headers.keys.length; i += 1) { const name = unchecked(headers.keys[i]); if (name.toLowerCase() == "content-type") hasContentType = true; output.headers.push(new __PulseFastlyHeader(name, __pulse_fastly_string(unchecked(headers.values[i])))) } } if (!hasContentType) output.headers.push(new __PulseFastlyHeader("content-type", output.status == 200 ? __pulse_fastly_asset_content_type(key) : "text/plain; charset=utf-8")); const cacheHandle = __pulse_fastly_payload_field(payload, "cacheControl"); if (__pulse_fastly_value(cacheHandle).kind != PULSE_VALUE_UNDEFINED) output.headers.push(new __PulseFastlyHeader("cache-control", __pulse_fastly_string(cacheHandle))); const methodHandle = __pulse_fastly_payload_field(payload, "method"); const method = __pulse_fastly_value(methodHandle).kind == PULSE_VALUE_UNDEFINED ? "GET" : __pulse_fastly_string(methodHandle).toUpperCase(); if (output.status == 200 && method != "HEAD") { const text = __pulse_fastly_read_body(__pulse_fastly_out_value(bodyOut), effectIndex); if (__pulse_fastly_last_error != 0) return 0; output.text = __pulse_fastly_asset_text(text) } return __pulse_fastly_put(output) }
|
|
1900
|
+
${assetResultSource}
|
|
1704
1901
|
function __pulse_fastly_wait_kv_put(effectIndex: i32): i32 { if (unchecked(__pulse_fastly_pending_mode[effectIndex]) != PULSE_FASTLY_PENDING_ASYNC) { __pulse_fastly_fail(PULSE_ERROR_STATE, 126, effectIndex); return 0 } const pending = unchecked(__pulse_fastly_pending[effectIndex]); const kvError = __pulse_fastly_out_i32(); const status = fastly_kv_store_insert_wait(pending, changetype<usize>(kvError)); unchecked(__pulse_fastly_pending_mode[effectIndex] = PULSE_FASTLY_PENDING_NONE); unchecked(__pulse_fastly_pending[effectIndex] = 0); if (status != FASTLY_STATUS_OK || __pulse_fastly_out_value(kvError) != 1) { __pulse_fastly_fail(PULSE_ERROR_HOSTCALL, 127, effectIndex); return 0 } return host_value_boolean(1) }
|
|
1705
1902
|
function __pulse_fastly_write_body(bodyHandle: i32, value: string): i32 { const bytes = __pulse_fastly_utf8(value); let offset = 0; while (offset < bytes.byteLength) { const written = __pulse_fastly_out_i32(); const status = fastly_http_body_write(bodyHandle, changetype<usize>(bytes) + offset, bytes.byteLength - offset, 0, changetype<usize>(written)); if (status != FASTLY_STATUS_OK) return status; const count = __pulse_fastly_out_value(written); if (count <= 0) return 1; offset += count } return FASTLY_STATUS_OK }
|
|
1706
|
-
function __pulse_fastly_send_result(handle: i32): i32 { const value = __pulse_fastly_value(handle); if (value.kind == PULSE_VALUE_FETCH) return fastly_http_resp_send_downstream(value.responseHandle, value.bodyHandle, 0); if (value.kind != PULSE_VALUE_RESPONSE) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 90, -1); return PULSE_ERROR_VALUE } const responseOut = __pulse_fastly_out_i32(); let status = fastly_http_resp_new(changetype<usize>(responseOut)); if (status != FASTLY_STATUS_OK) return status; const responseHandle = __pulse_fastly_out_value(responseOut); const bodyOut = __pulse_fastly_out_i32(); status = fastly_http_body_new(changetype<usize>(bodyOut)); if (status != FASTLY_STATUS_OK) return status; const bodyHandle = __pulse_fastly_out_value(bodyOut); status = fastly_http_resp_status_set(responseHandle, value.status); if (status != FASTLY_STATUS_OK) return status; for (let i = 0; i < value.headers.length; i += 1) { const header = unchecked(value.headers[i]); const nameBytes = __pulse_fastly_utf8(header.name); const valueBytes = __pulse_fastly_utf8(header.value); status = fastly_http_resp_header_append(responseHandle, changetype<usize>(nameBytes), nameBytes.byteLength, changetype<usize>(valueBytes), valueBytes.byteLength); if (status != FASTLY_STATUS_OK) return status } status = __pulse_fastly_write_body(bodyHandle, value.text); if (status != FASTLY_STATUS_OK) return status; return fastly_http_resp_send_downstream(responseHandle, bodyHandle, 0) }
|
|
1903
|
+
function __pulse_fastly_send_result(handle: i32): i32 { const value = __pulse_fastly_value(handle); ${bindings.s3.length ? 'if (value.s3Body) return __s3_send_body(value);' : ''} if (value.kind == PULSE_VALUE_FETCH) return fastly_http_resp_send_downstream(value.responseHandle, value.bodyHandle, 0); if (value.kind != PULSE_VALUE_RESPONSE) { __pulse_fastly_fail(PULSE_ERROR_VALUE, 90, -1); return PULSE_ERROR_VALUE } const responseOut = __pulse_fastly_out_i32(); let status = fastly_http_resp_new(changetype<usize>(responseOut)); if (status != FASTLY_STATUS_OK) return status; const responseHandle = __pulse_fastly_out_value(responseOut); const bodyOut = __pulse_fastly_out_i32(); status = fastly_http_body_new(changetype<usize>(bodyOut)); if (status != FASTLY_STATUS_OK) return status; const bodyHandle = __pulse_fastly_out_value(bodyOut); status = fastly_http_resp_status_set(responseHandle, value.status); if (status != FASTLY_STATUS_OK) return status; for (let i = 0; i < value.headers.length; i += 1) { const header = unchecked(value.headers[i]); const nameBytes = __pulse_fastly_utf8(header.name); const valueBytes = __pulse_fastly_utf8(header.value); status = fastly_http_resp_header_append(responseHandle, changetype<usize>(nameBytes), nameBytes.byteLength, changetype<usize>(valueBytes), valueBytes.byteLength); if (status != FASTLY_STATUS_OK) return status } status = __pulse_fastly_write_body(bodyHandle, value.text); if (status != FASTLY_STATUS_OK) return status; return fastly_http_resp_send_downstream(responseHandle, bodyHandle, 0) }
|
|
1707
1904
|
|
|
1708
1905
|
${jwtEvidenceSource}
|
|
1709
1906
|
${es256KeyArtifactSource(es256KeyRecords)}
|
|
1710
1907
|
${jwtCryptoCompositionSource(jwtCrypto)}
|
|
1908
|
+
${jwtSigningCryptoCompositionSource(jwtCrypto)}
|
|
1711
1909
|
${generateSchemaRuntime(plan)}
|
|
1712
1910
|
${effectResultSource(plan, bindings)}
|
|
1713
1911
|
`;
|
|
1912
|
+
if (!hasAssets) return source;
|
|
1913
|
+
const writer = 'status = __pulse_fastly_write_body(bodyHandle, value.text);';
|
|
1914
|
+
if (!source.includes(writer)) throw new Error('Embedded asset response writer boundary drift.');
|
|
1915
|
+
return source.replace(writer, 'status = value.binaryBody !== null ? __pulse_fastly_write_binary(bodyHandle, value.binaryBody!) : __pulse_fastly_write_body(bodyHandle, value.text);');
|
|
1714
1916
|
}
|
|
1715
1917
|
|
|
1716
1918
|
function driverSource(plan, options = {}) {
|
|
@@ -1727,6 +1929,13 @@ export function pulse_fastly_jwt_crypto_calls(): i32 { return __pulse_fastly_jwt
|
|
|
1727
1929
|
export function pulse_fastly_jwt_clock_calls(): i32 { return __pulse_fastly_jwt_clock_calls }
|
|
1728
1930
|
export function pulse_fastly_jwt_schema_calls(): i32 { return __pulse_fastly_jwt_schema_calls }
|
|
1729
1931
|
export function ${entryName}(): void {
|
|
1932
|
+
if (__pulse_invocation_started) { __pulse_fastly_fail(PULSE_ERROR_STATE, 171, -1); return }
|
|
1933
|
+
__pulse_invocation_started = true
|
|
1934
|
+
__pulse_fastly_run_invocation()
|
|
1935
|
+
${plan.effects.some(e => e.kind === 's3.getBody') ? '__s3_close_bodies()' : ''}
|
|
1936
|
+
__pulse_invocation_close()
|
|
1937
|
+
}
|
|
1938
|
+
function __pulse_fastly_run_invocation(): void {
|
|
1730
1939
|
__pulse_fastly_last_error = fastly_abi_init(PULSE_FASTLY_EFFECTS_ABI_VERSION)
|
|
1731
1940
|
if (__pulse_fastly_last_error != FASTLY_STATUS_OK) { __pulse_fastly_jwt_send_error(); return }
|
|
1732
1941
|
const handles = new StaticArray<i32>(2)
|
|
@@ -1740,12 +1949,13 @@ export function ${entryName}(): void {
|
|
|
1740
1949
|
if (__pulse_fastly_last_error != 0) { __pulse_fastly_jwt_send_error(); return }
|
|
1741
1950
|
__pulse_fastly_request_path = __pulse_fastly_path(__pulse_fastly_request_url)
|
|
1742
1951
|
let runStatus = pulse_start()
|
|
1743
|
-
${applicationErrors.enabled(plan) ? applicationErrors.driverLoop(plan) : ` while (runStatus == 1 && __pulse_fastly_last_error == 0) {
|
|
1952
|
+
${packageApplication.enabled(plan) ? packageApplication.driverLoop() : applicationErrors.enabled(plan) ? applicationErrors.driverLoop(plan) : ` while (runStatus == 1 && __pulse_fastly_last_error == 0) {
|
|
1744
1953
|
for (let effectIndex = 0; effectIndex < PULSE_FASTLY_EFFECT_COUNT; effectIndex += 1) {
|
|
1745
1954
|
if (unchecked(__pulse_fastly_pending_mode[effectIndex]) == PULSE_FASTLY_PENDING_NONE) continue
|
|
1955
|
+
const invocation = unchecked(__pulse_invocation_tickets[effectIndex])
|
|
1746
1956
|
const result = __pulse_fastly_resolve_effect(effectIndex)
|
|
1747
1957
|
if (result <= 0 || __pulse_fastly_last_error != 0) { __pulse_fastly_jwt_send_error(); return }
|
|
1748
|
-
if (
|
|
1958
|
+
if (__pulse_invocation_settle(effectIndex, invocation, result) != 1) { __pulse_fastly_fail(PULSE_ERROR_STATE, 101, effectIndex); __pulse_fastly_jwt_send_error(); return }
|
|
1749
1959
|
}
|
|
1750
1960
|
runStatus = pulse_resume()
|
|
1751
1961
|
}`}
|
|
@@ -1764,7 +1974,7 @@ function capabilityImports(effect) {
|
|
|
1764
1974
|
if (effect.kind === 'time.now') return ['wasi_snapshot_preview1.clock_time_get'];
|
|
1765
1975
|
if (effect.kind === 'config.get') return ['fastly_config_store.open', 'fastly_config_store.get'];
|
|
1766
1976
|
if (effect.kind === 'secret.get') return ['fastly_secret_store.open', 'fastly_secret_store.get', 'fastly_secret_store.plaintext'];
|
|
1767
|
-
if (
|
|
1977
|
+
if (['jwt.verify', 'jwt.sign'].includes(effect.kind)) {
|
|
1768
1978
|
return [
|
|
1769
1979
|
...(effect.resource && effect.resource.keyType === 'secret'
|
|
1770
1980
|
? [
|
|
@@ -1796,7 +2006,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
1796
2006
|
validatePlanBoundary(plan, options);
|
|
1797
2007
|
const facts = packageFacts(plan, options);
|
|
1798
2008
|
const jwtCrypto = selectedJwtCrypto(plan);
|
|
1799
|
-
const es256KeyRecords = jwtCrypto && jwtCrypto.algorithm
|
|
2009
|
+
const es256KeyRecords = jwtCrypto && ['ES256', 'RS256'].includes(jwtCrypto.algorithm)
|
|
1800
2010
|
? resolveEs256KeyArtifacts(plan, facts)
|
|
1801
2011
|
: Object.freeze([]);
|
|
1802
2012
|
if (jwtCrypto && jwtCrypto.guestUnitRequired && facts.guestUnits.length !== 1) {
|
|
@@ -1820,10 +2030,11 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
1820
2030
|
);
|
|
1821
2031
|
}
|
|
1822
2032
|
const bindings = resolveCapabilityBindings(plan, options);
|
|
1823
|
-
const portable = generateCanonicalNativeAssemblyScript(plan, options);
|
|
2033
|
+
const portable = packageApplication.enabled(plan) ? packageApplication.portable(plan) : generateCanonicalNativeAssemblyScript(plan, options);
|
|
1824
2034
|
const portableSource = stripPulseHostImports(portable.source);
|
|
1825
2035
|
const hasJwt = (plan.effects || []).some((effect) => effect.kind === 'jwt.verify');
|
|
1826
|
-
const
|
|
2036
|
+
const hasSign = plan.effects.some(effect => effect.kind === 'jwt.sign');
|
|
2037
|
+
const jwtSource = hasJwt || hasSign ? pulseJwtAssemblyScriptSource({ includeSigning: hasSign }) : undefined;
|
|
1827
2038
|
let source = [
|
|
1828
2039
|
'/* Generated by Pulse Pass98 Fastly native platform capability compiler. */',
|
|
1829
2040
|
'export function __pulse_fastly_abort(message: string | null, fileName: string | null = null, line: u32 = 0, column: u32 = 0): void { unreachable() }',
|
|
@@ -1835,12 +2046,14 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
1835
2046
|
}),
|
|
1836
2047
|
jwtSource ? jwtSource.source : '',
|
|
1837
2048
|
require('./s3-native.js').s3NativeSource(plan, bindings),
|
|
2049
|
+
plan.effects.some(e => e.kind === 'assets.lookup') ? require('@pulse-compute/wasm-contracts/assets/contracts').embeddedNativeSource() + fs.readFileSync(path.join(__dirname, 'embedded-assets.as.ts'), 'utf8') : '',
|
|
1838
2050
|
require('./time-native.js').timeNativeSource(plan),
|
|
1839
2051
|
require('./digest-native.js').digestNativeSource(plan),
|
|
1840
2052
|
conditionalKv.kvNativeSource(plan),
|
|
1841
2053
|
require('./native-request-body.js').runtimeSource(),
|
|
1842
2054
|
require('./native-request-headers.js').runtimeSource(),
|
|
1843
|
-
applicationErrors.enabled(plan) ? applicationErrors.runtimeSource() : '',
|
|
2055
|
+
applicationErrors.enabled(plan) ? applicationErrors.runtimeSource(plan) : '',
|
|
2056
|
+
effectInvocations.runtimeSource({ packageCompletion: packageApplication.enabled(plan) }),
|
|
1844
2057
|
portableSource,
|
|
1845
2058
|
driverSource(plan, { guestLinked: facts.guestUnits.length > 0 }),
|
|
1846
2059
|
''
|
|
@@ -1848,7 +2061,10 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
1848
2061
|
if (applicationErrors.enabled(plan)) source = applicationErrors.instrument(source);
|
|
1849
2062
|
source = require('./native-request-headers.js').instrument(source, applicationErrors.enabled(plan), /\bhost_request_headers\(/.test(portableSource));
|
|
1850
2063
|
source = require('./native-request-body.js').instrument(source, applicationErrors.enabled(plan));
|
|
2064
|
+
if (packageApplication.enabled(plan)) source = packageApplication.instrument(source);
|
|
2065
|
+
source = effectInvocations.instrument(source);
|
|
1851
2066
|
source = require('./request-budget.js').instrumentRequestBudget(source, bindings.maxDurationMs, plan);
|
|
2067
|
+
source = nativeValueBudget.instrument(source, plan);
|
|
1852
2068
|
const effectKinds = Object.freeze((plan.effects || []).reduce((output, effect) => {
|
|
1853
2069
|
output[effect.kind] = (output[effect.kind] || 0) + 1;
|
|
1854
2070
|
return output;
|
|
@@ -1872,10 +2088,14 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
1872
2088
|
portableGeneratorVersion: portable.version,
|
|
1873
2089
|
portableSourceHash: portable.sourceHash,
|
|
1874
2090
|
schemaCodecs: portable.manifest.schemaCodecs,
|
|
2091
|
+
handlerBodies: portable.manifest.handlerBodies,
|
|
2092
|
+
dispatcher: portable.manifest.dispatcher,
|
|
2093
|
+
stages: portable.manifest.stages,
|
|
2094
|
+
helperBodies: portable.manifest.helperBodies,
|
|
1875
2095
|
crypto: portable.manifest.crypto,
|
|
1876
2096
|
jwt: jwtSource
|
|
1877
2097
|
? Object.freeze({
|
|
1878
|
-
status: jwtCrypto && jwtCrypto.algorithm
|
|
2098
|
+
status: jwtCrypto && ['ES256', 'RS256'].includes(jwtCrypto.algorithm)
|
|
1879
2099
|
? 'implemented-g4'
|
|
1880
2100
|
: 'implemented-e2',
|
|
1881
2101
|
package: jwtSource.owner,
|
|
@@ -1894,6 +2114,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
1894
2114
|
})
|
|
1895
2115
|
: Object.freeze({ status: 'inactive' }),
|
|
1896
2116
|
effectCount: (plan.effects || []).length,
|
|
2117
|
+
effectInvocations: effectInvocations.policy,
|
|
1897
2118
|
effectKinds,
|
|
1898
2119
|
continuationCount: (plan.continuations || []).length,
|
|
1899
2120
|
groupedContinuationCount: (plan.continuations || []).filter((entry) => (entry.effectIds || []).length > 1).length,
|
|
@@ -1912,19 +2133,20 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
1912
2133
|
...(bindings.maxDurationMs === undefined ? [] : ['pulse_fastly_request_expired'])
|
|
1913
2134
|
]),
|
|
1914
2135
|
policy: Object.freeze({
|
|
2136
|
+
...(nativeValueBudget.hasBoundedReadLoop(plan) ? { readLoopMemory: nativeValueBudget.policy } : {}),
|
|
1915
2137
|
nativeFastly: true,
|
|
1916
2138
|
provider: 'fastly',
|
|
1917
2139
|
providerNeutralInput: true,
|
|
1918
2140
|
javascriptRuntime: false,
|
|
1919
2141
|
jsComputeRuntime: false,
|
|
1920
|
-
wasi: bindings.maxDurationMs !== undefined || hasJwt || bindings.s3.length || plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 'clock_time_get only' : false,
|
|
2142
|
+
wasi: bindings.maxDurationMs !== undefined || hasJwt || hasSign || bindings.s3.length || plan.effects.some(effect => conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind)) ? 'clock_time_get only' : false,
|
|
1921
2143
|
effects: FASTLY_NATIVE_PLATFORM_EFFECT_KINDS.join(', '),
|
|
1922
2144
|
continuations: true,
|
|
1923
2145
|
groupedEffects: 'existing fetch start-before-wait ordering remains intact',
|
|
1924
2146
|
config: 'Fastly Config Store open/get with undefined for missing values',
|
|
1925
2147
|
secrets: 'Fastly Secret Store open/get/plaintext; proof traces redact plaintext values',
|
|
1926
|
-
jwt: hasJwt
|
|
1927
|
-
? `package-owned compact-JWS
|
|
2148
|
+
jwt: hasJwt || hasSign
|
|
2149
|
+
? `package-owned compact-JWS, registered-claim, and bounded issuance semantics composed with ${jwtCrypto.realization}; provider-owned request, secret, clock, schema, and transport`
|
|
1928
2150
|
: 'inactive',
|
|
1929
2151
|
kv: 'Fastly KV Store async lookup/insert; conditional operations preserve u64 generations, bounded readiness, strict Pulse envelopes and uncertain dispatch',
|
|
1930
2152
|
assets: 'Fastly KV Store lookup with provider-owned content type, cache headers, and GET/HEAD body ownership',
|
|
@@ -1960,8 +2182,9 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
1960
2182
|
});
|
|
1961
2183
|
}
|
|
1962
2184
|
|
|
1963
|
-
function inspectFastlyNativePlatformCapabilitiesWasm(input) {
|
|
2185
|
+
function inspectFastlyNativePlatformCapabilitiesWasm(input, options = {}) {
|
|
1964
2186
|
const bytes = Buffer.isBuffer(input) ? input : fs.readFileSync(path.resolve(input));
|
|
2187
|
+
assertFastlyWasmBudget(bytes.length, options.maxWasmBytes);
|
|
1965
2188
|
if (!WebAssembly.validate(bytes)) fail('Generated Fastly native platform capabilities module is not valid WebAssembly.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_WASM_INVALID');
|
|
1966
2189
|
const module = new WebAssembly.Module(bytes);
|
|
1967
2190
|
const imports = WebAssembly.Module.imports(module).map((entry) => Object.freeze({ module: entry.module, name: entry.name, kind: entry.kind }));
|
|
@@ -1977,7 +2200,6 @@ function inspectFastlyNativePlatformCapabilitiesWasm(input) {
|
|
|
1977
2200
|
const requiredExports = ['_start', 'memory', 'pulse_fastly_last_error', 'pulse_fastly_error_stage', 'pulse_fastly_error_effect', 'pulse_start', 'pulse_resume', 'pulse_set_effect_result', 'pulse_result_handle'];
|
|
1978
2201
|
const missingExports = requiredExports.filter((name) => !exportNames.has(name));
|
|
1979
2202
|
if (missingExports.length > 0) fail('Generated Fastly native platform capabilities module is missing required exports.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_EXPORT_MISSING', { missingExports, exports });
|
|
1980
|
-
if (bytes.length > FASTLY_NATIVE_PLATFORM_CAPABILITIES_MAX_WASM_BYTES) fail('Generated Fastly native platform capabilities module exceeds the Pass98 size ceiling.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_WASM_TOO_LARGE', { bytes: bytes.length, maxBytes: FASTLY_NATIVE_PLATFORM_CAPABILITIES_MAX_WASM_BYTES });
|
|
1981
2203
|
return Object.freeze({
|
|
1982
2204
|
valid: true,
|
|
1983
2205
|
bytes: bytes.length,
|
|
@@ -1990,6 +2212,7 @@ function inspectFastlyNativePlatformCapabilitiesWasm(input) {
|
|
|
1990
2212
|
}
|
|
1991
2213
|
|
|
1992
2214
|
function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
2215
|
+
const maxWasmBytes = normalizeFastlyMaxWasmBytes(options.maxWasmBytes);
|
|
1993
2216
|
const generated = generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options);
|
|
1994
2217
|
const cwd = path.resolve(options.cwd || process.cwd());
|
|
1995
2218
|
const compilerFallback = path.resolve(__dirname, '..', '..', '..', '..', 'wasm', 'packages', 'compiler');
|
|
@@ -2022,6 +2245,8 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2022
2245
|
}
|
|
2023
2246
|
jsonAs = Object.freeze({ transform, dependencyRoot, version: packageJson.version });
|
|
2024
2247
|
}
|
|
2248
|
+
if (options.emitWat !== undefined && typeof options.emitWat !== 'boolean') throw new TypeError('emitWat must be a boolean.');
|
|
2249
|
+
const emitWat = options.emitWat === true;
|
|
2025
2250
|
const stagingDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pulse-fastly-native-platform-'));
|
|
2026
2251
|
const requestedOutDir = options.outDir ? path.resolve(options.outDir) : undefined;
|
|
2027
2252
|
const outputDir = requestedOutDir || stagingDir;
|
|
@@ -2029,6 +2254,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2029
2254
|
const sourceFile = path.join(stagingDir, 'fastly-native-platform-capabilities.as.ts');
|
|
2030
2255
|
const wasmFile = path.join(outputDir, options.wasmFile || 'fastly-native-platform-capabilities.wasm');
|
|
2031
2256
|
const watFile = path.join(outputDir, options.watFile || 'fastly-native-platform-capabilities.wat');
|
|
2257
|
+
if (!emitWat) fs.rmSync(watFile, { force: true });
|
|
2032
2258
|
const guestLinked = generated.guestUnits.length > 0;
|
|
2033
2259
|
// asc resolves custom runtimes like module names and appends ".ts".
|
|
2034
2260
|
const fixedMemoryRuntimeModule = path.join(__dirname, 'fixed-memory-runtime.as');
|
|
@@ -2045,16 +2271,16 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2045
2271
|
asc.script,
|
|
2046
2272
|
path.basename(sourceFile),
|
|
2047
2273
|
'--outFile', assemblyScriptWasmFile,
|
|
2048
|
-
'--textFile', assemblyScriptWatFile,
|
|
2049
2274
|
'--runtime', guestLinked
|
|
2050
2275
|
? fixedMemoryRuntimeModule
|
|
2051
2276
|
: (schemaCodecsActive ? 'incremental' : 'stub'),
|
|
2052
2277
|
'--noAssert',
|
|
2053
2278
|
'--optimize'
|
|
2054
2279
|
];
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2280
|
+
if (emitWat) args.push('--textFile', assemblyScriptWatFile);
|
|
2281
|
+
const optimization = appendAssemblyScriptOptimizationArgs(args, nativeOptimization, { guestLinked });
|
|
2282
|
+
if (!guestLinked && (nativeValueBudget.hasBoundedReadLoop(plan) || plan.effects.some(effect => effect.kind === 'crypto.digestText' || effect.kind.startsWith('s3.')))) {
|
|
2283
|
+
args.push('--maximumMemory', String(nativeValueBudget.policy.maximumMemoryPages));
|
|
2058
2284
|
}
|
|
2059
2285
|
if (generated.guestUnits.length > 0) {
|
|
2060
2286
|
args.push(
|
|
@@ -2078,6 +2304,8 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2078
2304
|
'--path', jsonAs.dependencyRoot
|
|
2079
2305
|
);
|
|
2080
2306
|
}
|
|
2307
|
+
const readReportCapture = options.reportCapture === false ? () => null : prepareReportCapture(args, stagingDir, generated.manifest,
|
|
2308
|
+
require(path.join(asc.packageRoot, 'package.json')).version, 'fastly-native-platform-capabilities.as/');
|
|
2081
2309
|
const startedAt = Date.now();
|
|
2082
2310
|
const result = spawnSync(asc.executable, args, {
|
|
2083
2311
|
cwd: stagingDir,
|
|
@@ -2132,7 +2360,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2132
2360
|
failureMessage: 'Fastly Native start wrapper composition failed.'
|
|
2133
2361
|
}
|
|
2134
2362
|
);
|
|
2135
|
-
runGuestTool(
|
|
2363
|
+
if (emitWat) runGuestTool(
|
|
2136
2364
|
'wasm-dis',
|
|
2137
2365
|
[wasmFile, '--mvp-features', '-o', watFile],
|
|
2138
2366
|
{
|
|
@@ -2162,7 +2390,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2162
2390
|
});
|
|
2163
2391
|
}
|
|
2164
2392
|
const primaryWasm = fs.readFileSync(wasmFile);
|
|
2165
|
-
const primaryWat =
|
|
2393
|
+
const primaryWat = emitWat ? fs.readFileSync(watFile, 'utf8') : '';
|
|
2166
2394
|
const primaryArtifact = Object.freeze({
|
|
2167
2395
|
bytes: primaryWasm.length,
|
|
2168
2396
|
sha256: sha256(primaryWasm)
|
|
@@ -2195,7 +2423,8 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2195
2423
|
realization = Object.freeze({
|
|
2196
2424
|
...realization,
|
|
2197
2425
|
wasm: stageResult.wasm,
|
|
2198
|
-
|
|
2426
|
+
// Required guest-link disassembly audits remain enabled.
|
|
2427
|
+
wat: emitWat ? stageResult.wat : '',
|
|
2199
2428
|
guestUnits: stageResult.guestUnits,
|
|
2200
2429
|
guestLink: Object.freeze({
|
|
2201
2430
|
version: stageResult.version,
|
|
@@ -2213,9 +2442,9 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2213
2442
|
const wat = realization.wat;
|
|
2214
2443
|
if (requestedOutDir && generated.guestUnits.length > 0) {
|
|
2215
2444
|
fs.writeFileSync(wasmFile, wasm);
|
|
2216
|
-
fs.writeFileSync(watFile, wat, 'utf8');
|
|
2445
|
+
if (emitWat) fs.writeFileSync(watFile, wat, 'utf8');
|
|
2217
2446
|
}
|
|
2218
|
-
const inspection = inspectFastlyNativePlatformCapabilitiesWasm(wasm);
|
|
2447
|
+
const inspection = inspectFastlyNativePlatformCapabilitiesWasm(wasm, { maxWasmBytes });
|
|
2219
2448
|
const imported = new Set(inspection.imports.map((entry) => `${entry.module}:${entry.name}`));
|
|
2220
2449
|
const requiredImports = requiredImportsForPlan(plan, generated.bindings);
|
|
2221
2450
|
const missingRequiredImports = requiredImports.filter((key) => !imported.has(key));
|
|
@@ -2229,7 +2458,9 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2229
2458
|
: undefined,
|
|
2230
2459
|
optimization,
|
|
2231
2460
|
wasm: Object.freeze({ bytes: inspection.bytes, sha256: inspection.sha256, magic: inspection.magic }),
|
|
2232
|
-
wat: Object.freeze(
|
|
2461
|
+
wat: Object.freeze(emitWat
|
|
2462
|
+
? { emitted: true, bytes: Buffer.byteLength(wat), sha256: sha256(wat) }
|
|
2463
|
+
: { emitted: false, bytes: 0, sha256: null }),
|
|
2233
2464
|
importModules: inspection.importModules,
|
|
2234
2465
|
imports: inspection.imports,
|
|
2235
2466
|
requiredImports,
|
|
@@ -2247,6 +2478,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2247
2478
|
return Object.freeze({
|
|
2248
2479
|
version: FASTLY_NATIVE_PLATFORM_CAPABILITIES_VERSION,
|
|
2249
2480
|
compilerVersion: FASTLY_NATIVE_PLATFORM_CAPABILITIES_COMPILER_VERSION,
|
|
2481
|
+
reportAttribution: readReportCapture(wasm),
|
|
2250
2482
|
plan,
|
|
2251
2483
|
generated,
|
|
2252
2484
|
source: generated.source,
|
|
@@ -2262,7 +2494,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2262
2494
|
assemblyScriptDurationMs,
|
|
2263
2495
|
primaryArtifact,
|
|
2264
2496
|
startWrapper,
|
|
2265
|
-
output: requestedOutDir ? Object.freeze({ outDir: outputDir, wasmFile, watFile }) : undefined
|
|
2497
|
+
output: requestedOutDir ? Object.freeze({ outDir: outputDir, wasmFile, watFile: emitWat ? watFile : null }) : undefined
|
|
2266
2498
|
});
|
|
2267
2499
|
} finally {
|
|
2268
2500
|
fs.rmSync(stagingDir, { recursive: true, force: true });
|
|
@@ -2280,10 +2512,12 @@ function writeFastlyNativePlatformCapabilitiesModule(compiled, outDir, options =
|
|
|
2280
2512
|
const manifestFile = path.join(target, options.manifestFile || 'fastly-native-platform-capabilities-manifest.json');
|
|
2281
2513
|
fs.writeFileSync(sourceFile, compiled.source, 'utf8');
|
|
2282
2514
|
fs.writeFileSync(wasmFile, compiled.wasm);
|
|
2283
|
-
|
|
2515
|
+
const emitWat = compiled.manifest.wat.emitted !== false;
|
|
2516
|
+
if (emitWat) fs.writeFileSync(watFile, compiled.wat, 'utf8');
|
|
2517
|
+
else fs.rmSync(watFile, { force: true });
|
|
2284
2518
|
fs.writeFileSync(planFile, `${stableStringify(compiled.plan, 2)}\n`, 'utf8');
|
|
2285
2519
|
fs.writeFileSync(manifestFile, `${stableStringify(compiled.manifest, 2)}\n`, 'utf8');
|
|
2286
|
-
return Object.freeze({ target, sourceFile, wasmFile, watFile, planFile, manifestFile, manifest: compiled.manifest });
|
|
2520
|
+
return Object.freeze({ target, sourceFile, wasmFile, watFile: emitWat ? watFile : null, planFile, manifestFile, manifest: compiled.manifest });
|
|
2287
2521
|
}
|
|
2288
2522
|
|
|
2289
2523
|
module.exports = Object.freeze({
|