@pulse-compute/provider-fastly 1.0.0-beta.5 → 1.0.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/config-schema.json +7 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
- package/src/build/canonical-target.js +10 -2
- package/src/build/effect-invocations.js +49 -0
- package/src/build/embedded-assets.as.ts +32 -0
- package/src/build/kv-native.as.ts +18 -8
- package/src/build/native-application-errors.js +49 -26
- package/src/build/native-http-effects.js +41 -12
- package/src/build/native-platform-capabilities.js +346 -120
- package/src/build/native-string-values.js +1 -1
- package/src/build/native-value-budget.js +58 -0
- package/src/build/package-native-application.js +94 -0
- package/src/build/request-budget.js +3 -1
- package/src/build/s3-body.as.ts +97 -0
- package/src/build/s3-native.as.ts +9 -3
- package/src/build/s3-native.js +2 -2
- package/src/build/schema-admission.js +83 -0
- package/src/build/schema-nested-json.js +93 -0
- package/src/build/schema-scalar-record.js +41 -0
- package/src/build/wasm-budget.js +28 -0
- package/src/config-api.js +2 -0
- package/src/config-schema.json +7 -0
- package/src/index.ts +5 -0
- package/src/javascript/jwt-verifier.js +14 -4
- package/src/javascript/package-effects.js +1 -1
- package/src/javascript/target-support-policy.js +11 -5
- package/src/javascript/target.js +10 -0
- package/src/provider-contract.js +4 -2
- package/src/testing/native-http-effects-host.js +5 -1
- package/src/testing/native-platform-capabilities-host.js +24 -3
- package/src/toolchain/config.js +4 -0
- package/src/toolchain/index.js +30 -1
- package/src/toolchain/native-execution.js +1 -0
- package/src/toolchain/s3.js +2 -2
|
@@ -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');
|
|
@@ -92,7 +99,10 @@ const FASTLY_NATIVE_PLATFORM_CAPABILITIES_VERSION = 'pulse.fastly-native-platfor
|
|
|
92
99
|
const FASTLY_NATIVE_PLATFORM_CAPABILITIES_GENERATOR_VERSION = 'pulse.fastly-native-platform-capabilities-generator.v1';
|
|
93
100
|
const FASTLY_NATIVE_PLATFORM_CAPABILITIES_COMPILER_VERSION = 'pulse.fastly-native-platform-capabilities-compiler.v1';
|
|
94
101
|
const FASTLY_NATIVE_PLATFORM_CAPABILITIES_ABI_VERSION = 1;
|
|
95
|
-
|
|
102
|
+
// Internal compilation budget, not a Fastly service limit. Keep a finite guard
|
|
103
|
+
// while allowing composed applications beyond the original 1 MiB proof fixture.
|
|
104
|
+
const { DEFAULT_FASTLY_MAX_WASM_BYTES, normalizeFastlyMaxWasmBytes, assertFastlyWasmBudget } = require('./wasm-budget.js');
|
|
105
|
+
const FASTLY_NATIVE_PLATFORM_CAPABILITIES_MAX_WASM_BYTES = DEFAULT_FASTLY_MAX_WASM_BYTES;
|
|
96
106
|
const FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES = 65536;
|
|
97
107
|
const FASTLY_NATIVE_SIZE_OPTIMIZATION = EXPERIMENTAL_NATIVE_SIZE_OPTIMIZATION;
|
|
98
108
|
|
|
@@ -156,9 +166,11 @@ const FASTLY_NATIVE_PLATFORM_EFFECT_KIND = Object.freeze({
|
|
|
156
166
|
'grip.broadcast': 9,
|
|
157
167
|
'assets.lookup': 10,
|
|
158
168
|
'jwt.verify': 11,
|
|
169
|
+
'jwt.sign': 20,
|
|
159
170
|
's3.head': 12,
|
|
160
171
|
's3.getText': 13,
|
|
161
172
|
's3.putText': 14,
|
|
173
|
+
's3.getBody': 21,
|
|
162
174
|
'kv.getVersioned': 15,
|
|
163
175
|
'kv.insertIfAbsent': 16,
|
|
164
176
|
'kv.compareAndSwap': 17,
|
|
@@ -185,9 +197,11 @@ const FASTLY_NATIVE_PLATFORM_EFFECT_KINDS = Object.freeze([
|
|
|
185
197
|
'grip.broadcast',
|
|
186
198
|
'assets.lookup',
|
|
187
199
|
'jwt.verify',
|
|
200
|
+
'jwt.sign',
|
|
188
201
|
's3.head',
|
|
189
202
|
's3.getText',
|
|
190
|
-
's3.putText'
|
|
203
|
+
's3.putText',
|
|
204
|
+
's3.getBody'
|
|
191
205
|
]);
|
|
192
206
|
|
|
193
207
|
const FASTLY_NATIVE_PLATFORM_CAPABILITY_KINDS = new Set([
|
|
@@ -204,9 +218,11 @@ const FASTLY_NATIVE_PLATFORM_CAPABILITY_KINDS = new Set([
|
|
|
204
218
|
'grip.publish',
|
|
205
219
|
'grip.broadcast',
|
|
206
220
|
'jwt.verify',
|
|
221
|
+
'jwt.sign',
|
|
207
222
|
's3.head',
|
|
208
223
|
's3.getText',
|
|
209
|
-
's3.putText'
|
|
224
|
+
's3.putText',
|
|
225
|
+
's3.getBody'
|
|
210
226
|
]);
|
|
211
227
|
|
|
212
228
|
class FastlyNativePlatformCapabilitiesError extends Error {
|
|
@@ -268,45 +284,38 @@ function packageFacts(_plan, options = {}) {
|
|
|
268
284
|
});
|
|
269
285
|
}
|
|
270
286
|
|
|
287
|
+
function jwtVerificationAlgorithm(effect) {
|
|
288
|
+
return effect.resource.keyType === 'secret' ? 'HS256' : String(effect.resource.keyArtifactId).startsWith('pulse.jwt.rs256-key.') ? 'RS256' : 'ES256';
|
|
289
|
+
}
|
|
290
|
+
|
|
271
291
|
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
|
-
);
|
|
292
|
+
const effects = plan.effects || [];
|
|
293
|
+
const signing = effects.filter(effect => effect.kind === 'jwt.sign');
|
|
294
|
+
const verifies = effects.filter(effect => effect.kind === 'jwt.verify');
|
|
295
|
+
const required = new Set([
|
|
296
|
+
...signing.map(effect => literalObjectField(inputExpression(effect, 'payload'), 'algorithm') === 'HS256' ? 'HMAC-SHA256' : literalObjectField(inputExpression(effect, 'payload'), 'algorithm')),
|
|
297
|
+
...verifies.map(effect => jwtVerificationAlgorithm(effect))
|
|
298
|
+
]);
|
|
299
|
+
if (!required.size) return undefined;
|
|
300
|
+
const definitions = {
|
|
301
|
+
'HMAC-SHA256': ['guest-source:pulse-hmac-as', 'pulse-hmac-as.v1', 'guest-source'],
|
|
302
|
+
HS256: ['guest-source:pulse-hmac-as', 'pulse-hmac-as.v1', 'guest-source'],
|
|
303
|
+
RS256: ['guest-linked:pulse-rs256-bearssl-i31', 'bearssl.0.6.rsa-i31.sha256.v1', 'guest-linked'],
|
|
304
|
+
ES256: ['guest-linked:pulse-es256-rustcrypto-p256', 'rustcrypto.p256-0.13.2.ecdsa-0.16.9.sha2-0.10.9.v1', 'guest-linked']
|
|
305
|
+
};
|
|
306
|
+
for (const algorithm of required) {
|
|
307
|
+
const selected = plan.crypto?.algorithms.find(entry => entry.algorithm === algorithm);
|
|
308
|
+
const expected = definitions[algorithm];
|
|
309
|
+
if (!expected || !selected || selected.realization !== expected[0] || selected.implementation !== expected[1]
|
|
310
|
+
|| selected.kind !== expected[2] || selected.targetImplemented !== true || selected.automaticFallback !== false) {
|
|
311
|
+
fail('JWT requires its explicitly selected Crypto realization.', 'PULSE_FASTLY_NATIVE_JWT_CRYPTO_REALIZATION_INVALID');
|
|
312
|
+
}
|
|
301
313
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
guestUnitRequired: es256,
|
|
308
|
-
automaticFallback: false
|
|
309
|
-
});
|
|
314
|
+
const verificationAlgorithms = [...new Set(verifies.map(effect => jwtVerificationAlgorithm(effect)))];
|
|
315
|
+
if (verificationAlgorithms.length > 1) fail('Fastly Native JWT verification requires one exact algorithm.', 'PULSE_FASTLY_NATIVE_JWT_CRYPTO_REALIZATION_INVALID');
|
|
316
|
+
const algorithm = verificationAlgorithms[0] || (required.has('RS256') ? 'RS256' : required.has('ES256') ? 'ES256' : 'HMAC-SHA256');
|
|
317
|
+
return Object.freeze({ ...plan.crypto.algorithms.find(entry => entry.algorithm === algorithm),
|
|
318
|
+
guestUnitRequired: required.has('ES256') || required.has('RS256'), signingAlgorithms: signing.map(effect => literalObjectField(inputExpression(effect, 'payload'), 'algorithm')) });
|
|
310
319
|
}
|
|
311
320
|
|
|
312
321
|
function decodeP256Coordinate(value, artifactId, field) {
|
|
@@ -328,6 +337,26 @@ function decodeP256Coordinate(value, artifactId, field) {
|
|
|
328
337
|
return bytes;
|
|
329
338
|
}
|
|
330
339
|
|
|
340
|
+
function normalizedRs256Jwk(value, artifactId) {
|
|
341
|
+
const invalid = () => fail('Invalid bounded RSA public key artifact.', 'PULSE_FASTLY_NATIVE_JWT_KEY_ARTIFACT_INVALID', { artifactId });
|
|
342
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)
|
|
343
|
+
|| Object.keys(value).some(name => !['kty', 'n', 'e', 'alg', 'use', 'key_ops', 'kid'].includes(name))
|
|
344
|
+
|| value.kty !== 'RSA' || value.alg !== undefined && value.alg !== 'RS256'
|
|
345
|
+
|| value.use !== undefined && value.use !== 'sig'
|
|
346
|
+
|| value.key_ops !== undefined && (!Array.isArray(value.key_ops) || value.key_ops.length !== 1 || value.key_ops[0] !== 'verify')
|
|
347
|
+
|| value.kid !== undefined && (typeof value.kid !== 'string' || !value.kid.length || value.kid.includes('\0') || Buffer.byteLength(value.kid) > 256)) invalid();
|
|
348
|
+
const decode = (text, maximum) => {
|
|
349
|
+
if (typeof text !== 'string' || text.length > Math.ceil(maximum * 4 / 3) || !/^[A-Za-z0-9_-]+$/.test(text)) invalid();
|
|
350
|
+
const bytes = Buffer.from(text, 'base64url');
|
|
351
|
+
if (!bytes.length || bytes.length > maximum || bytes[0] === 0 || bytes.toString('base64url') !== text) invalid();
|
|
352
|
+
return bytes;
|
|
353
|
+
};
|
|
354
|
+
const n = decode(value.n, 512), e = decode(value.e, 4), k = n.length;
|
|
355
|
+
if (![256, 384, 512].includes(k) || n[0] < 128 || !(n[k-1] & 1) || e.readUIntBE(0, e.length) < 3 || !(e.at(-1) & 1)) invalid();
|
|
356
|
+
const bytes = Buffer.alloc(8+k); bytes.writeUInt32LE(k); n.copy(bytes, 4); e.copy(bytes, 8+k-e.length);
|
|
357
|
+
return Object.freeze({ kid: value.kid, bytes });
|
|
358
|
+
}
|
|
359
|
+
|
|
331
360
|
function normalizedEs256Jwk(value, artifactId) {
|
|
332
361
|
if (
|
|
333
362
|
!value
|
|
@@ -374,13 +403,14 @@ function resolveEs256KeyArtifacts(plan, facts) {
|
|
|
374
403
|
}
|
|
375
404
|
const artifactId = effect.resource.keyArtifactId;
|
|
376
405
|
const artifact = artifacts.get(artifactId);
|
|
406
|
+
const algorithm = jwtVerificationAlgorithm(effect).toLowerCase();
|
|
377
407
|
if (
|
|
378
408
|
!artifact
|
|
379
|
-
|| artifact.version !==
|
|
409
|
+
|| artifact.version !== `pulse.jwt-${algorithm}-key-artifact.v1`
|
|
380
410
|
|| artifact.contractId !== 'pulse.jwt'
|
|
381
411
|
|| artifact.package !== '@pulse-compute/jwt'
|
|
382
|
-
|| artifact.kind !==
|
|
383
|
-
|| artifact.mediaType !==
|
|
412
|
+
|| artifact.kind !== `jwt-${algorithm}-static-public-key`
|
|
413
|
+
|| artifact.mediaType !== `application/vnd.pulse.jwt-${algorithm}-key+json`
|
|
384
414
|
|| artifact.id !== artifactId
|
|
385
415
|
|| artifact.materialHash !== sha256(stableStringify(artifact.data))
|
|
386
416
|
|| !artifact.data
|
|
@@ -406,7 +436,7 @@ function resolveEs256KeyArtifacts(plan, facts) {
|
|
|
406
436
|
{ effectId: effect.id, artifactId, keyCount: keys && keys.length }
|
|
407
437
|
);
|
|
408
438
|
}
|
|
409
|
-
const normalized = keys.map((key) => normalizedEs256Jwk(key, artifactId));
|
|
439
|
+
const normalized = keys.map((key) => algorithm === 'rs256' ? normalizedRs256Jwk(key, artifactId) : normalizedEs256Jwk(key, artifactId));
|
|
410
440
|
const kids = normalized.map((key) => key.kid).filter((kid) => kid !== undefined);
|
|
411
441
|
if (new Set(kids).size !== kids.length) {
|
|
412
442
|
fail(
|
|
@@ -482,8 +512,71 @@ function es256KeyArtifactSource(records) {
|
|
|
482
512
|
return lines.join('\n');
|
|
483
513
|
}
|
|
484
514
|
|
|
515
|
+
function jwtSigningCryptoCompositionSource(selected) {
|
|
516
|
+
const algorithms = selected?.signingAlgorithms || [];
|
|
517
|
+
const hs = algorithms.includes('HS256');
|
|
518
|
+
const es = algorithms.includes('ES256');
|
|
519
|
+
const rs = algorithms.includes('RS256');
|
|
520
|
+
return `function __pulse_fastly_jwt_crypto_sign(algorithm: string, key: Uint8Array, data: Uint8Array, output: Uint8Array): i32 {
|
|
521
|
+
${hs ? `if (algorithm == "HS256") return pulse_crypto_hmac_sha256_bytes_v1(key.dataStart, key.length, data.dataStart, data.length, output.dataStart, output.length)` : ''}
|
|
522
|
+
${rs ? `if (algorithm == "RS256") return __pulse_fastly_rs256(true, key, data, output)` : ''}
|
|
523
|
+
${es ? `if (algorithm == "ES256") {
|
|
524
|
+
if (key.length != 96 || output.length != 64 || data.length > 16340) return -2
|
|
525
|
+
const pointer: usize = 524288
|
|
526
|
+
memory.fill(pointer, 0, 16640)
|
|
527
|
+
store<u32>(pointer, 0x32534550); store<u32>(pointer + 4, 2); store<u32>(pointer + 8, 64)
|
|
528
|
+
store<u32>(pointer + 12, (224 + data.length + 15) & ~15); store<u32>(pointer + 16, 1)
|
|
529
|
+
store<u32>(pointer + 24, 224); store<u32>(pointer + 28, data.length)
|
|
530
|
+
store<u32>(pointer + 32, 64); store<u32>(pointer + 36, 96)
|
|
531
|
+
store<u32>(pointer + 40, 160); store<u32>(pointer + 44, 64)
|
|
532
|
+
memory.copy(pointer + 64, key.dataStart, 96); memory.copy(pointer + 224, data.dataStart, data.length)
|
|
533
|
+
const status = __pulse_crypto_es256_sign_anchor(i32(pointer), 16640)
|
|
534
|
+
if (status == 1) memory.copy(output.dataStart, pointer + 160, 64)
|
|
535
|
+
memory.fill(pointer, 0, 16640); memory.fill(0, 0, 65536)
|
|
536
|
+
return status
|
|
537
|
+
}` : ''}
|
|
538
|
+
return -3
|
|
539
|
+
}`;
|
|
540
|
+
}
|
|
541
|
+
|
|
485
542
|
function jwtCryptoCompositionSource(selected) {
|
|
486
|
-
|
|
543
|
+
const rsa = selected && (selected.algorithm === 'RS256' || selected.signingAlgorithms?.includes('RS256'));
|
|
544
|
+
const helper = rsa ? rs256CompositionSource() : '';
|
|
545
|
+
return helper + jwtVerificationCompositionSource(selected);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function rs256CompositionSource() {
|
|
549
|
+
return `function __pulse_fastly_rs256(signing: bool, key: Uint8Array, data: Uint8Array, signature: Uint8Array): i32 {
|
|
550
|
+
if (key.length < 8 || data.length > 12288) return -2
|
|
551
|
+
const k = load<u32>(key.dataStart)
|
|
552
|
+
if ((k != 256 && k != 384 && k != 512) || signature.length != i32(k) || key.length != i32(signing ? 8 + k * 9 / 2 : 8 + k)) return -2
|
|
553
|
+
const pointer: usize = 524288
|
|
554
|
+
const output = (64 + key.length + 15) & ~15
|
|
555
|
+
const input = output + signature.length
|
|
556
|
+
memory.fill(pointer, 0, 16640)
|
|
557
|
+
store<u32>(pointer, 0x32534550); store<u32>(pointer + 4, 2); store<u32>(pointer + 8, 64)
|
|
558
|
+
store<u32>(pointer + 12, (input + data.length + 15) & ~15); store<u32>(pointer + 16, 2)
|
|
559
|
+
store<u32>(pointer + 24, input); store<u32>(pointer + 28, data.length)
|
|
560
|
+
store<u32>(pointer + 32, 64); store<u32>(pointer + 36, key.length)
|
|
561
|
+
store<u32>(pointer + 40, output); store<u32>(pointer + 44, k)
|
|
562
|
+
memory.copy(pointer + 64, key.dataStart, key.length); memory.copy(pointer + input, data.dataStart, data.length)
|
|
563
|
+
if (!signing) memory.copy(pointer + output, signature.dataStart, signature.length)
|
|
564
|
+
const status = signing ? __pulse_crypto_rs256_sign_anchor(i32(pointer), 16640) : __pulse_crypto_rs256_verify_anchor(i32(pointer), 16640)
|
|
565
|
+
if (signing && status == 1) memory.copy(signature.dataStart, pointer + output, signature.length)
|
|
566
|
+
memory.fill(pointer, 0, 16640); memory.fill(0, 0, 65536)
|
|
567
|
+
return status
|
|
568
|
+
}\n`;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function jwtVerificationCompositionSource(selected) {
|
|
572
|
+
if (selected?.algorithm === 'RS256') return `
|
|
573
|
+
function __pulse_fastly_jwt_capture_signing_input(input: Uint8Array): void { __pulse_fastly_jwt_record_signing_input(input.byteLength, "") }
|
|
574
|
+
function __pulse_fastly_jwt_crypto_verify(algorithm: string, key: Uint8Array, data: Uint8Array, signature: Uint8Array): i32 {
|
|
575
|
+
if (algorithm != "RS256") return -2
|
|
576
|
+
return __pulse_fastly_rs256(false, key, data, signature)
|
|
577
|
+
}`;
|
|
578
|
+
|
|
579
|
+
if (!selected || selected.algorithm === 'HMAC-SHA256') {
|
|
487
580
|
return [
|
|
488
581
|
'function __pulse_fastly_jwt_capture_signing_input(input: Uint8Array): void { void input }',
|
|
489
582
|
'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 +689,10 @@ function validatePlanBoundary(plan, options = {}) {
|
|
|
596
689
|
&& effect.kind === 'assets.lookup';
|
|
597
690
|
const jwt = effect.package === '@pulse-compute/jwt'
|
|
598
691
|
&& effect.contractId === 'pulse.jwt'
|
|
599
|
-
&&
|
|
600
|
-
&& effect.operation ===
|
|
601
|
-
&& effect.capability === 'jwt.verify';
|
|
692
|
+
&& ['verify', 'sign'].includes(effect.operation)
|
|
693
|
+
&& effect.kind === `jwt.${effect.operation}` && effect.capability === effect.kind;
|
|
602
694
|
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;
|
|
695
|
+
&& ['head', 'getText', 'putText', 'getBody'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
|
|
604
696
|
const digest = effect.package === '@pulse-compute/crypto' && effect.contractId === 'pulse.crypto'
|
|
605
697
|
&& effect.operation === 'digestText' && effect.kind === 'crypto.digestText' && effect.capability === effect.kind;
|
|
606
698
|
return !grip && !assets && !jwt && !s3 && !digest;
|
|
@@ -620,11 +712,10 @@ function validatePlanBoundary(plan, options = {}) {
|
|
|
620
712
|
&& effect.kind === 'assets.lookup';
|
|
621
713
|
const jwt = effect.package === '@pulse-compute/jwt'
|
|
622
714
|
&& effect.contractId === 'pulse.jwt'
|
|
623
|
-
&&
|
|
624
|
-
&& effect.operation ===
|
|
625
|
-
&& effect.capability === 'jwt.verify';
|
|
715
|
+
&& ['verify', 'sign'].includes(effect.operation)
|
|
716
|
+
&& effect.kind === `jwt.${effect.operation}` && effect.capability === effect.kind;
|
|
626
717
|
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;
|
|
718
|
+
&& ['head', 'getText', 'putText', 'getBody'].includes(effect.operation) && effect.kind === `s3.${effect.operation}` && effect.capability === effect.kind;
|
|
628
719
|
const digest = effect.package === '@pulse-compute/crypto' && effect.contractId === 'pulse.crypto'
|
|
629
720
|
&& effect.operation === 'digestText' && effect.kind === 'crypto.digestText' && effect.capability === effect.kind;
|
|
630
721
|
return !grip && !assets && !jwt && !s3 && !digest;
|
|
@@ -632,7 +723,7 @@ function validatePlanBoundary(plan, options = {}) {
|
|
|
632
723
|
if (unsupportedDeclarations.length > 0) {
|
|
633
724
|
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
725
|
}
|
|
635
|
-
for (const effect of effects.filter((entry) =>
|
|
726
|
+
for (const effect of effects.filter((entry) => ['jwt.verify', 'jwt.sign'].includes(entry.kind))) {
|
|
636
727
|
const resource = effect.resource;
|
|
637
728
|
const knownShape = resource
|
|
638
729
|
&& !Object.keys(resource)
|
|
@@ -647,7 +738,7 @@ function validatePlanBoundary(plan, options = {}) {
|
|
|
647
738
|
&& typeof resource.keyArtifactId === 'string'
|
|
648
739
|
&& resource.keyArtifactId.length > 0
|
|
649
740
|
&& resource.secretBinding === null;
|
|
650
|
-
if (!namedSecret && !staticPublicKey) {
|
|
741
|
+
if (!namedSecret && (!staticPublicKey || effect.kind === 'jwt.sign')) {
|
|
651
742
|
fail('Fastly Native JWT requires one exact named-secret or bounded static public-key descriptor.', 'PULSE_FASTLY_NATIVE_JWT_KEY_DESCRIPTOR_INVALID', {
|
|
652
743
|
effectId: effect.id,
|
|
653
744
|
resource,
|
|
@@ -713,8 +804,8 @@ function resolveCapabilityBindings(plan, options = {}) {
|
|
|
713
804
|
const effects = plan.effects || [];
|
|
714
805
|
const hasConfig = effects.some((effect) => effect.kind === 'config.get');
|
|
715
806
|
const hasSecret = effects.some((effect) => (
|
|
716
|
-
effect.kind === 'secret.get' || ['s3.head', 's3.getText', 's3.putText'].includes(effect.kind)
|
|
717
|
-
|| (
|
|
807
|
+
effect.kind === 'secret.get' || ['s3.head', 's3.getText', 's3.putText', 's3.getBody'].includes(effect.kind)
|
|
808
|
+
|| (['jwt.verify', 'jwt.sign'].includes(effect.kind) && effect.resource && effect.resource.keyType === 'secret')
|
|
718
809
|
));
|
|
719
810
|
const hasGripHold = effects.some((effect) => effect.kind === 'grip.hold');
|
|
720
811
|
const hasGripBroadcast = effects.some((effect) => effect.kind === 'grip.broadcast');
|
|
@@ -740,6 +831,7 @@ function resolveCapabilityBindings(plan, options = {}) {
|
|
|
740
831
|
for (const [index, effect] of effects.entries()) {
|
|
741
832
|
if (effect.kind !== 'kv.get' && effect.kind !== 'kv.put' && !conditionalKv.KV_CONDITIONAL_KINDS.includes(effect.kind) && effect.kind !== 'assets.lookup') continue;
|
|
742
833
|
const packagePayload = inputExpression(effect, 'payload');
|
|
834
|
+
if (effect.kind === 'assets.lookup' && literalObjectField(packagePayload, 'embeddedId')) continue;
|
|
743
835
|
const logical = effect.resource && effect.resource.kind === 'literal'
|
|
744
836
|
&& effect.kind !== 'assets.lookup'
|
|
745
837
|
? String(effect.resource.value)
|
|
@@ -838,7 +930,7 @@ function resolveCapabilityBindings(plan, options = {}) {
|
|
|
838
930
|
maxDurationMs: require('@pulse-compute/runtime/host').normalizeRequestDuration(options.maxDurationMs),
|
|
839
931
|
configStore,
|
|
840
932
|
secretStore,
|
|
841
|
-
s3: require('../toolchain/s3.js').resolveFastlyS3(effects.map((effect) => ({ ...effect, payload:
|
|
933
|
+
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
934
|
kv: Object.freeze(kv),
|
|
843
935
|
fetch: Object.freeze(fetch),
|
|
844
936
|
backends: Object.freeze(backends),
|
|
@@ -872,7 +964,7 @@ function planRequiresFetchBodyRead(plan) {
|
|
|
872
964
|
if (Array.isArray(value)) return value.some(visit);
|
|
873
965
|
return Object.values(value).some(visit);
|
|
874
966
|
}
|
|
875
|
-
return visit(plan.entry);
|
|
967
|
+
return visit(plan.entry) || visit(plan.handlers) || visit(plan.stages) || visit(plan.helpers);
|
|
876
968
|
}
|
|
877
969
|
|
|
878
970
|
function requiredImportsForPlan(plan, bindings) {
|
|
@@ -900,7 +992,7 @@ function requiredImportsForPlan(plan, bindings) {
|
|
|
900
992
|
const gripAuthenticationRequired = kinds.has('grip.broadcast')
|
|
901
993
|
&& (!bindings || Boolean(bindings.grip && bindings.grip.authentication));
|
|
902
994
|
const jwtSecretRequired = (plan.effects || []).some((effect) => (
|
|
903
|
-
|
|
995
|
+
['jwt.verify', 'jwt.sign'].includes(effect.kind)
|
|
904
996
|
&& effect.resource
|
|
905
997
|
&& effect.resource.keyType === 'secret'
|
|
906
998
|
));
|
|
@@ -908,7 +1000,7 @@ function requiredImportsForPlan(plan, bindings) {
|
|
|
908
1000
|
for (const key of ['fastly_secret_store:open', 'fastly_secret_store:get', 'fastly_secret_store:plaintext']) keys.add(key);
|
|
909
1001
|
}
|
|
910
1002
|
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');
|
|
1003
|
+
if (kinds.has('jwt.verify') || kinds.has('jwt.sign') || kinds.has('time.now')) keys.add('wasi_snapshot_preview1:clock_time_get');
|
|
912
1004
|
if (kinds.has('kv.get') || kinds.has('kv.put') || kinds.has('assets.lookup')) keys.add('fastly_kv_store:open');
|
|
913
1005
|
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
1006
|
if (kinds.has('kv.put')) for (const key of ['fastly_kv_store:insert', 'fastly_kv_store:insert_wait']) keys.add(key);
|
|
@@ -917,7 +1009,7 @@ function requiredImportsForPlan(plan, bindings) {
|
|
|
917
1009
|
keys.add('fastly_log:endpoint_get');
|
|
918
1010
|
keys.add('fastly_log:write');
|
|
919
1011
|
}
|
|
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);
|
|
1012
|
+
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
1013
|
return Object.freeze([...keys].sort());
|
|
922
1014
|
}
|
|
923
1015
|
|
|
@@ -928,15 +1020,40 @@ function stripPulseHostImports(source) {
|
|
|
928
1020
|
.join('\n');
|
|
929
1021
|
}
|
|
930
1022
|
|
|
1023
|
+
function schemaTextEncodeIds(plan) {
|
|
1024
|
+
// Keep the PS3 accounting/failure boundary and all unproved codec families.
|
|
1025
|
+
if (nativeValueBudget.hasBoundedReadLoop(plan)) return [];
|
|
1026
|
+
function closedText(node, depth = 0) {
|
|
1027
|
+
if (depth > 16) return false;
|
|
1028
|
+
if (node.kind === 'string' || node.kind === 'boolean') return true;
|
|
1029
|
+
if (node.kind === 'nullable') return closedText(node.value, depth);
|
|
1030
|
+
if (node.kind === 'array') return closedText(node.element, depth + 1);
|
|
1031
|
+
return node.kind === 'object' && !node.additionalProperties
|
|
1032
|
+
&& node.fields.every(field => closedText(field.value, depth + 1));
|
|
1033
|
+
}
|
|
1034
|
+
return ((plan.schemas && plan.schemas.registry && plan.schemas.registry.schemas) || [])
|
|
1035
|
+
.filter(schema => !schema.jsonLimits && schema.root.kind === 'object'
|
|
1036
|
+
&& schemaNeedsValueProjection(schema.root) && closedText(schema.root))
|
|
1037
|
+
.map(schema => schema.id);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
931
1040
|
function generateSchemaRuntime(plan) {
|
|
932
1041
|
const registry = plan.schemas && plan.schemas.registry ? plan.schemas.registry : { schemas: [], maxBytes: FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES, contentTypePolicy: 'accept-json-or-missing' };
|
|
933
1042
|
const schemas = registry.schemas || [];
|
|
1043
|
+
const textEncodeIds = new Set(schemaTextEncodeIds(plan));
|
|
934
1044
|
const nodeLines = [];
|
|
1045
|
+
const scalarProjectors = new Map();
|
|
1046
|
+
const flatObjectProjectors = new Map();
|
|
1047
|
+
const scalarKinds = ['string', 'boolean', 'i32', 'u32', 'f64', 'string-enum'];
|
|
935
1048
|
let nodeIndex = 0;
|
|
936
1049
|
|
|
937
1050
|
function emitNode(schemaIndex, node) {
|
|
938
1051
|
const currentIndex = nodeIndex++;
|
|
939
1052
|
const functionName = `__pulse_fastly_schema_${schemaIndex}_${currentIndex}`;
|
|
1053
|
+
const flatObject = node.kind === 'object' && !node.additionalProperties
|
|
1054
|
+
&& node.fields.every(field => scalarKinds.includes(field.value.kind));
|
|
1055
|
+
// Locals belong to this function, so equivalent flat bodies need no node ID.
|
|
1056
|
+
const localIndex = flatObject ? 'flat' : currentIndex;
|
|
940
1057
|
const child = node.kind === 'nullable'
|
|
941
1058
|
? emitNode(schemaIndex, node.value)
|
|
942
1059
|
: node.kind === 'array'
|
|
@@ -945,32 +1062,43 @@ function generateSchemaRuntime(plan) {
|
|
|
945
1062
|
const fields = node.kind === 'object'
|
|
946
1063
|
? node.fields.map((field) => Object.freeze({ field, apply: emitNode(schemaIndex, field.value) }))
|
|
947
1064
|
: [];
|
|
948
|
-
const body = [`function ${functionName}(valueHandle: i32): i32 {`, ' const input = __pulse_fastly_value(valueHandle)'];
|
|
1065
|
+
const body = [`function ${functionName}(valueHandle: i32, checkDuplicates: bool): i32 {`, ' const input = __pulse_fastly_value(valueHandle)'];
|
|
949
1066
|
if (node.kind === 'nullable') {
|
|
950
1067
|
body.push(' if (input.kind == PULSE_VALUE_NULL) return valueHandle');
|
|
951
|
-
body.push(` return ${child}(valueHandle)`);
|
|
1068
|
+
body.push(` return ${child}(valueHandle, checkDuplicates)`);
|
|
952
1069
|
} else if (node.kind === 'array') {
|
|
953
1070
|
body.push(' if (input.kind != PULSE_VALUE_ARRAY) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 52, -1); return 0 }');
|
|
954
1071
|
body.push(' const output = host_value_array()');
|
|
955
1072
|
body.push(' for (let index = 0; index < input.values.length; index += 1) {');
|
|
956
|
-
body.push(` const item = ${child}(unchecked(input.values[index]))`);
|
|
1073
|
+
body.push(` const item = ${child}(unchecked(input.values[index]), checkDuplicates)`);
|
|
957
1074
|
body.push(' if (item <= 0) return 0');
|
|
958
1075
|
body.push(' host_value_array_push(output, item)');
|
|
959
1076
|
body.push(' }');
|
|
960
1077
|
body.push(' return output');
|
|
1078
|
+
} else if (node.kind === 'json-value' || node.kind === 'json-object') {
|
|
1079
|
+
body.push(...nestedJsonProjectionLines(node));
|
|
1080
|
+
} else if (node.kind === 'scalar-record') {
|
|
1081
|
+
body.push(...scalarRecordProjectionLines(node));
|
|
961
1082
|
} else if (node.kind === 'object') {
|
|
962
1083
|
body.push(' if (input.kind != PULSE_VALUE_OBJECT) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 50, -1); return 0 }');
|
|
1084
|
+
if (node.additionalProperties) body.push(' if (checkDuplicates && input.duplicateJsonKeys) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 57, -1); return 0 }');
|
|
963
1085
|
body.push(' const output = host_value_object()');
|
|
964
1086
|
fields.forEach(({ field, apply }, fieldIndex) => {
|
|
965
|
-
body.push(` const key_${
|
|
1087
|
+
body.push(` const key_${localIndex}_${fieldIndex} = __pulse_fastly_string_value(${quote(field.name)})`);
|
|
966
1088
|
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_${
|
|
1089
|
+
body.push(` const field_${localIndex}_${fieldIndex} = host_value_property(valueHandle, key_${localIndex}_${fieldIndex})`);
|
|
1090
|
+
body.push(` if (__pulse_fastly_value(field_${localIndex}_${fieldIndex}).kind == PULSE_VALUE_UNDEFINED) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 51, -1); return 0 }`);
|
|
1091
|
+
body.push(` const projected_${localIndex}_${fieldIndex} = ${apply}(field_${localIndex}_${fieldIndex}, checkDuplicates)`);
|
|
1092
|
+
body.push(` if (projected_${localIndex}_${fieldIndex} <= 0) return 0`);
|
|
1093
|
+
body.push(` host_value_object_set(output, key_${localIndex}_${fieldIndex}, projected_${localIndex}_${fieldIndex})`);
|
|
972
1094
|
if (!field.required) body.push(' }');
|
|
973
1095
|
});
|
|
1096
|
+
if (node.additionalProperties) {
|
|
1097
|
+
body.push(' for (let index = 0; index < input.keys.length; index++) {', ' const key = unchecked(input.keys[index])');
|
|
1098
|
+
if (node.fields.length) body.push(` if (${node.fields.map(field => `key == ${quote(field.name)}`).join(' || ')}) continue`);
|
|
1099
|
+
body.push(' const item = __pulse_fastly_copy_json(unchecked(input.values[index]), checkDuplicates)',
|
|
1100
|
+
' if (item <= 0) return 0', ' host_value_object_set(output, __pulse_fastly_string_value(key), item)', ' }');
|
|
1101
|
+
}
|
|
974
1102
|
body.push(' return output');
|
|
975
1103
|
} else if (node.kind === 'string') {
|
|
976
1104
|
body.push(' if (input.kind != PULSE_VALUE_STRING) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 52, -1); return 0 }');
|
|
@@ -995,6 +1123,32 @@ function generateSchemaRuntime(plan) {
|
|
|
995
1123
|
throw new TypeError(`Unsupported Fastly native schema node ${String(node.kind)}.`);
|
|
996
1124
|
}
|
|
997
1125
|
body.push('}', '');
|
|
1126
|
+
// Only scalar validators preserve the original handle without allocating or
|
|
1127
|
+
// calling child projectors. Compare the exact emitted body, including enum
|
|
1128
|
+
// values/order and error stages.
|
|
1129
|
+
if (scalarKinds.includes(node.kind)) {
|
|
1130
|
+
const key = body.slice(1).join('\n');
|
|
1131
|
+
const existing = scalarProjectors.get(key);
|
|
1132
|
+
if (existing) return existing;
|
|
1133
|
+
const sharedName = `__pulse_fastly_schema_scalar_${scalarProjectors.size}`;
|
|
1134
|
+
scalarProjectors.set(key, sharedName);
|
|
1135
|
+
body[0] = `function ${sharedName}(valueHandle: i32, checkDuplicates: bool): i32 {`;
|
|
1136
|
+
nodeLines.push(...body);
|
|
1137
|
+
return sharedName;
|
|
1138
|
+
}
|
|
1139
|
+
// Share code only after child scalar identities are resolved. Exact bodies
|
|
1140
|
+
// retain field order, presence checks, validation and errors. Each call still
|
|
1141
|
+
// allocates its own output; open/nested/nullable/array bodies remain separate.
|
|
1142
|
+
if (flatObject) {
|
|
1143
|
+
const key = body.slice(1).join('\n');
|
|
1144
|
+
const existing = flatObjectProjectors.get(key);
|
|
1145
|
+
if (existing) return existing;
|
|
1146
|
+
const sharedName = `__pulse_fastly_schema_flat_object_${flatObjectProjectors.size}`;
|
|
1147
|
+
flatObjectProjectors.set(key, sharedName);
|
|
1148
|
+
body[0] = `function ${sharedName}(valueHandle: i32, checkDuplicates: bool): i32 {`;
|
|
1149
|
+
nodeLines.push(...body);
|
|
1150
|
+
return sharedName;
|
|
1151
|
+
}
|
|
998
1152
|
nodeLines.push(...body);
|
|
999
1153
|
return functionName;
|
|
1000
1154
|
}
|
|
@@ -1004,17 +1158,22 @@ function generateSchemaRuntime(plan) {
|
|
|
1004
1158
|
`const __PULSE_SCHEMA_MAX_BYTES: i32 = ${Number(registry.maxBytes || FASTLY_NATIVE_PLATFORM_CAPABILITIES_BUFFER_BYTES)}`,
|
|
1005
1159
|
`const __PULSE_SCHEMA_REQUIRE_JSON: bool = ${registry.contentTypePolicy === 'require-json' ? 'true' : 'false'}`,
|
|
1006
1160
|
'',
|
|
1161
|
+
schemaJsonAdmissionSource(plan),
|
|
1162
|
+
schemaFetchJsonSource(),
|
|
1007
1163
|
...nodeLines,
|
|
1008
|
-
|
|
1164
|
+
`function __pulse_fastly_schema_apply(schemaId: string, valueHandle: i32, encode: bool${textEncodeIds.size ? ', textResult: bool = false' : ''}): i32 {`,
|
|
1009
1165
|
' if (schemaId.length == 0) return valueHandle'
|
|
1010
1166
|
];
|
|
1011
1167
|
for (const [schemaIndex, schema] of schemas.entries()) {
|
|
1012
1168
|
lines.push(`${schemaIndex === 0 ? ' if' : ' else if'} (schemaId == ${quote(schema.id)}) {`);
|
|
1013
|
-
lines.push(`
|
|
1169
|
+
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 }`);
|
|
1170
|
+
lines.push(` const projected = ${roots[schemaIndex]}(valueHandle, !encode)`);
|
|
1014
1171
|
lines.push(' if (projected <= 0) return 0');
|
|
1015
1172
|
lines.push(' const input = __pulse_fastly_json(projected, 0)');
|
|
1016
1173
|
lines.push(` const normalized = encode ? __pulse_schema_encode_${schemaIndex}(input) : __pulse_schema_decode_${schemaIndex}(input)`);
|
|
1017
|
-
lines.push(' return
|
|
1174
|
+
if (textEncodeIds.has(schema.id)) lines.push(' if (textResult && encode) return __pulse_fastly_string_value(normalized)');
|
|
1175
|
+
if (schema.jsonLimits) lines.push(' const output = __pulse_fastly_parse_json(normalized)', ' if (output > 0) __pulse_fastly_deep_freeze(output)', ' return output');
|
|
1176
|
+
else lines.push(' return __pulse_fastly_parse_json(normalized)');
|
|
1018
1177
|
lines.push(' }');
|
|
1019
1178
|
}
|
|
1020
1179
|
lines.push(' __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 55, -1)');
|
|
@@ -1037,7 +1196,7 @@ function effectResultSource(plan, bindings) {
|
|
|
1037
1196
|
lines.push(`function __pulse_fastly_secret_store(effectIndex: i32): string { return ${quote(bindings.secretStore)} }`, '');
|
|
1038
1197
|
lines.push('function __pulse_fastly_jwt_secret_binding(effectIndex: i32): string {', ' switch (effectIndex) {');
|
|
1039
1198
|
for (const [index, effect] of (plan.effects || []).entries()) {
|
|
1040
|
-
if (
|
|
1199
|
+
if (['jwt.verify', 'jwt.sign'].includes(effect.kind) && effect.resource.keyType === 'secret') {
|
|
1041
1200
|
lines.push(` case ${index}: return ${quote(effect.resource.secretBinding)}`);
|
|
1042
1201
|
}
|
|
1043
1202
|
}
|
|
@@ -1052,12 +1211,31 @@ function effectResultSource(plan, bindings) {
|
|
|
1052
1211
|
for (const binding of bindings.grip.publish) lines.push(` case ${binding.index}: return ${quote(binding.authSecretRef || '')}`);
|
|
1053
1212
|
lines.push(' default: return ""', ' }', '}', '');
|
|
1054
1213
|
|
|
1214
|
+
// A config lookup has the same ready-result handoff at every static site.
|
|
1215
|
+
// Keep the site indices in the switch; pass the active index through so the
|
|
1216
|
+
// pending slot and any state error still belong to the original invocation.
|
|
1217
|
+
const configSites = (plan.effects || []).flatMap((effect, index) => effect.kind === 'config.get' ? [index] : []);
|
|
1218
|
+
if (configSites.length > 1) {
|
|
1219
|
+
lines.push('function __pulse_fastly_resolve_config_get(effectIndex: i32): i32 {');
|
|
1220
|
+
lines.push(' const response = __pulse_fastly_wait_ready(effectIndex)');
|
|
1221
|
+
lines.push(' if (response <= 0) return 0');
|
|
1222
|
+
lines.push(' return response');
|
|
1223
|
+
lines.push('}', '');
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1055
1226
|
lines.push('function __pulse_fastly_resolve_effect(effectIndex: i32): i32 {');
|
|
1056
1227
|
lines.push(' switch (effectIndex) {');
|
|
1057
1228
|
for (const [index, effect] of (plan.effects || []).entries()) {
|
|
1229
|
+
if (effect.kind === 'config.get' && configSites.length > 1) {
|
|
1230
|
+
if (index === configSites[0]) {
|
|
1231
|
+
for (const site of configSites) lines.push(` case ${site}:`);
|
|
1232
|
+
lines.push(' return __pulse_fastly_resolve_config_get(effectIndex)');
|
|
1233
|
+
}
|
|
1234
|
+
continue;
|
|
1235
|
+
}
|
|
1058
1236
|
const result = effect.result || {};
|
|
1059
1237
|
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'
|
|
1238
|
+
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
1239
|
? '__pulse_fastly_wait_fetch'
|
|
1062
1240
|
: effect.kind === 'grip.publish'
|
|
1063
1241
|
? '__pulse_fastly_wait_grip_publish'
|
|
@@ -1099,9 +1277,11 @@ function effectDispatchSource(plan) {
|
|
|
1099
1277
|
'config.get': '__pulse_fastly_config_begin',
|
|
1100
1278
|
'secret.get': '__pulse_fastly_secret_begin',
|
|
1101
1279
|
'jwt.verify': '__pulse_fastly_jwt_begin',
|
|
1280
|
+
'jwt.sign': '__pulse_fastly_jwt_sign_begin',
|
|
1102
1281
|
's3.head': '__pulse_fastly_s3_begin',
|
|
1103
1282
|
's3.getText': '__pulse_fastly_s3_begin',
|
|
1104
1283
|
's3.putText': '__pulse_fastly_s3_begin',
|
|
1284
|
+
's3.getBody': '__pulse_fastly_s3_begin',
|
|
1105
1285
|
'kv.get': '__pulse_fastly_kv_get_begin',
|
|
1106
1286
|
'kv.put': '__pulse_fastly_kv_put_begin',
|
|
1107
1287
|
...Object.fromEntries(conditionalKv.KV_CONDITIONAL_KINDS.map(kind => [kind, '__pulse_fastly_kv_conditional_begin'])),
|
|
@@ -1135,7 +1315,14 @@ function effectDispatchSource(plan) {
|
|
|
1135
1315
|
}
|
|
1136
1316
|
|
|
1137
1317
|
function fastlyRuntimeSource(plan, bindings, options = {}) {
|
|
1318
|
+
const textEncodeIds = schemaTextEncodeIds(plan);
|
|
1138
1319
|
const effectCount = (plan.effects || []).length;
|
|
1320
|
+
const hasAssets = (plan.effects || []).some(effect => effect.kind === 'assets.lookup');
|
|
1321
|
+
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 }
|
|
1322
|
+
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) }` : '';
|
|
1323
|
+
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" }
|
|
1324
|
+
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])) }
|
|
1325
|
+
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
1326
|
const jwtCrypto = options.jwtCrypto || selectedJwtCrypto(plan);
|
|
1140
1327
|
const es256KeyRecords = options.es256KeyRecords || Object.freeze([]);
|
|
1141
1328
|
const jwtReality = options.jwtReality && options.jwtReality.enabled === true
|
|
@@ -1214,7 +1401,7 @@ function __pulse_fastly_jwt_send_error(): void {}
|
|
|
1214
1401
|
const gripAuthorizationSource = gripAuthentication
|
|
1215
1402
|
? `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
1403
|
: '';
|
|
1217
|
-
|
|
1404
|
+
const source = String.raw`
|
|
1218
1405
|
@external("fastly_abi", "init") declare function fastly_abi_init(version: i64): i32
|
|
1219
1406
|
@external("fastly_http_req", "body_downstream_get") declare function fastly_http_req_body_downstream_get(requestOut: usize, bodyOut: usize): i32
|
|
1220
1407
|
@external("fastly_http_req", "method_get") declare function fastly_http_req_method_get(handle: i32, buffer: usize, bufferLength: i32, writtenOut: usize): i32
|
|
@@ -1286,6 +1473,7 @@ const PULSE_ERROR_JWT: i32 = 1008
|
|
|
1286
1473
|
const PULSE_ERROR_REQUEST_BODY: i32 = 1009
|
|
1287
1474
|
|
|
1288
1475
|
class __PulseFastlyValue {
|
|
1476
|
+
${tracksJsonDuplicates(plan) ? 'duplicateJsonKeys: bool = false' : ''}
|
|
1289
1477
|
kind: i32 = PULSE_VALUE_UNDEFINED
|
|
1290
1478
|
boolean: i32 = 0
|
|
1291
1479
|
number: f64 = 0.0
|
|
@@ -1305,6 +1493,9 @@ ${nativeStringFields}
|
|
|
1305
1493
|
responseHandle: i32 = 0
|
|
1306
1494
|
bodyHandle: i32 = 0
|
|
1307
1495
|
bodyLoaded: i32 = 0
|
|
1496
|
+
s3Body: bool = false
|
|
1497
|
+
s3Deadline: i64 = 0
|
|
1498
|
+
s3Length: i32 = 0
|
|
1308
1499
|
status: i32 = 200
|
|
1309
1500
|
// Scalar handles do not need collection backing stores.
|
|
1310
1501
|
private headersStorage: Array<__PulseFastlyHeader> | null = null
|
|
@@ -1312,6 +1503,7 @@ ${nativeStringFields}
|
|
|
1312
1503
|
if (this.headersStorage === null) this.headersStorage = new Array<__PulseFastlyHeader>()
|
|
1313
1504
|
return this.headersStorage!
|
|
1314
1505
|
}
|
|
1506
|
+
binaryBody: Uint8Array | null = null
|
|
1315
1507
|
immutable: bool = false
|
|
1316
1508
|
}
|
|
1317
1509
|
class __PulseFastlyHeader {
|
|
@@ -1328,7 +1520,7 @@ class __PulseJsonParser {
|
|
|
1328
1520
|
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
1521
|
value(depth: i32): i32 {
|
|
1330
1522
|
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 }
|
|
1523
|
+
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
1524
|
const c = this.source.charCodeAt(this.index)
|
|
1333
1525
|
if (c == 34) return __pulse_fastly_string_value(this.string())
|
|
1334
1526
|
if (c == 123) return this.object(depth + 1)
|
|
@@ -1342,14 +1534,18 @@ class __PulseJsonParser {
|
|
|
1342
1534
|
string(): string {
|
|
1343
1535
|
// Find a bounded span, then decode each UTF-16 unit once. Concatenating one
|
|
1344
1536
|
// character at a time exhausts stub/fixed memory on a valid maximum KV value.
|
|
1345
|
-
const start = this.index + 1; let end = start;
|
|
1537
|
+
const start = this.index + 1; let end = start; let plain = true;
|
|
1346
1538
|
while (end < this.source.length) {
|
|
1347
1539
|
const c = this.source.charCodeAt(end);
|
|
1348
1540
|
if (c == 34) break;
|
|
1349
|
-
if (c == 92) end += 1;
|
|
1541
|
+
if (c == 92) { plain = false; end += 1; }
|
|
1542
|
+
else if (c < 32) plain = false;
|
|
1350
1543
|
end += 1;
|
|
1351
1544
|
}
|
|
1352
1545
|
if (end >= this.source.length) { this.failed = true; return "" }
|
|
1546
|
+
// A validated span needs one owned string, without a temporary UTF-16 array.
|
|
1547
|
+
// Keep escaped and malformed spans on the existing decoder path.
|
|
1548
|
+
if (plain) { this.index = end + 1; return this.source.substring(start, end); }
|
|
1353
1549
|
const output = new Uint16Array(end - start); let count = 0; this.index = start;
|
|
1354
1550
|
while (this.index < end) {
|
|
1355
1551
|
let c = this.source.charCodeAt(this.index++);
|
|
@@ -1427,6 +1623,7 @@ class __PulseJsonParser {
|
|
|
1427
1623
|
const key = this.string(); this.skip()
|
|
1428
1624
|
if (this.conditionalKv && __pulse_fastly_find(__pulse_fastly_value(output), key) >= 0) { this.failed = true; break }
|
|
1429
1625
|
if (this.index >= this.source.length || this.source.charCodeAt(this.index) != 58) { this.failed = true; break }
|
|
1626
|
+
${tracksJsonDuplicates(plan) ? 'if (__pulse_fastly_find(__pulse_fastly_value(output), key) >= 0) __pulse_fastly_value(output).duplicateJsonKeys = true' : ''}
|
|
1430
1627
|
this.index += 1; host_value_object_set(output, __pulse_fastly_string_value(key), this.value(depth)); this.skip()
|
|
1431
1628
|
if (this.index >= this.source.length) { this.failed = true; break }
|
|
1432
1629
|
const c = this.source.charCodeAt(this.index); this.index += 1
|
|
@@ -1533,7 +1730,7 @@ function __pulse_fastly_quote(value: string): string {
|
|
|
1533
1730
|
return String.UTF16.decodeUnsafe(out.dataStart, out.byteLength);
|
|
1534
1731
|
}
|
|
1535
1732
|
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 }
|
|
1733
|
+
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
1734
|
const value = __pulse_fastly_value(handle)
|
|
1538
1735
|
if (value.kind == PULSE_VALUE_BOOLEAN) { parts.push(value.boolean != 0 ? "true" : "false"); return }
|
|
1539
1736
|
if (value.kind == PULSE_VALUE_NUMBER) { parts.push(__pulse_fastly_number_string(value.number)); return }
|
|
@@ -1623,7 +1820,7 @@ function host_request_path(): i32 { return __pulse_fastly_string_value(__pulse_f
|
|
|
1623
1820
|
function host_request_headers(): i32 { return __pulse_request_headers_read() }
|
|
1624
1821
|
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
1822
|
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 }
|
|
1823
|
+
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
1824
|
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
1825
|
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
1826
|
function host_response_json(value: i32, options: i32): i32 { return __pulse_fastly_response(value, options, true) }
|
|
@@ -1633,7 +1830,7 @@ function host_schema_decode(text: i32, schema: i32): i32 {
|
|
|
1633
1830
|
const input = __pulse_fastly_value(text)
|
|
1634
1831
|
if (input.kind != PULSE_VALUE_STRING) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 52, -1); return 0 }
|
|
1635
1832
|
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)
|
|
1833
|
+
const parsed = ${hasJsonAdmission(plan) ? '__pulse_fastly_schema_parse_json(input.text, schema)' : '__pulse_fastly_parse_json(input.text)'}
|
|
1637
1834
|
if (parsed <= 0) return 0
|
|
1638
1835
|
const decoded = __pulse_fastly_schema_apply(id.text, parsed, false)
|
|
1639
1836
|
if (decoded <= 0) return 0
|
|
@@ -1643,11 +1840,12 @@ function host_schema_decode(text: i32, schema: i32): i32 {
|
|
|
1643
1840
|
function host_schema_encode(value: i32, schema: i32): i32 {
|
|
1644
1841
|
const id = __pulse_fastly_value(schema)
|
|
1645
1842
|
if (id.kind != PULSE_VALUE_STRING || id.text.length == 0) { __pulse_fastly_fail(PULSE_ERROR_SCHEMA, 55, -1); return 0 }
|
|
1646
|
-
const
|
|
1843
|
+
${textEncodeIds.length ? `const textResult = ${textEncodeIds.map(id => `id.text == ${quote(id)}`).join(' || ')}
|
|
1844
|
+
const projected = __pulse_fastly_schema_apply(id.text, value, true, textResult)` : 'const projected = __pulse_fastly_schema_apply(id.text, value, true)'}
|
|
1647
1845
|
if (projected <= 0) return 0
|
|
1648
|
-
const text = __pulse_fastly_json(projected, 0)
|
|
1846
|
+
const text = ${textEncodeIds.length ? 'textResult ? __pulse_fastly_string(projected) : ' : ''}__pulse_fastly_json(projected, 0)
|
|
1649
1847
|
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)
|
|
1848
|
+
return ${textEncodeIds.length ? 'textResult ? projected : ' : ''}__pulse_fastly_string_value(text)
|
|
1651
1849
|
}
|
|
1652
1850
|
function host_response_text(value: i32, options: i32): i32 { return __pulse_fastly_response(value, options, false) }
|
|
1653
1851
|
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 +1856,7 @@ function host_grip_subscribe(responseHandle: i32, optionsHandle: i32): i32 { con
|
|
|
1658
1856
|
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
1857
|
function host_kv_namespace(name: i32): i32 { return name }
|
|
1660
1858
|
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 }
|
|
1859
|
+
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
1860
|
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
1861
|
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
1862
|
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 +1877,12 @@ function __pulse_fastly_jwt_record_verified(): void { __pulse_fastly_jwt_verifie
|
|
|
1679
1877
|
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
1878
|
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
1879
|
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) }
|
|
1880
|
+
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
1881
|
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
1882
|
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 =
|
|
1883
|
+
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
1884
|
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) }
|
|
1885
|
+
${assetBeginSource}
|
|
1688
1886
|
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
1887
|
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
1888
|
function __pulse_fastly_grip_payload(payload: __PulseFastlyValue): __PulseFastlyValue { return __pulse_fastly_value(__pulse_fastly_payload_field(payload, "payload")) }
|
|
@@ -1698,19 +1896,22 @@ function __pulse_fastly_wait_grip_publish(effectIndex: i32): i32 { const respons
|
|
|
1698
1896
|
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
1897
|
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
1898
|
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) }
|
|
1899
|
+
${assetResultSource}
|
|
1704
1900
|
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
1901
|
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) }
|
|
1902
|
+
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
1903
|
|
|
1708
1904
|
${jwtEvidenceSource}
|
|
1709
1905
|
${es256KeyArtifactSource(es256KeyRecords)}
|
|
1710
1906
|
${jwtCryptoCompositionSource(jwtCrypto)}
|
|
1907
|
+
${jwtSigningCryptoCompositionSource(jwtCrypto)}
|
|
1711
1908
|
${generateSchemaRuntime(plan)}
|
|
1712
1909
|
${effectResultSource(plan, bindings)}
|
|
1713
1910
|
`;
|
|
1911
|
+
if (!hasAssets) return source;
|
|
1912
|
+
const writer = 'status = __pulse_fastly_write_body(bodyHandle, value.text);';
|
|
1913
|
+
if (!source.includes(writer)) throw new Error('Embedded asset response writer boundary drift.');
|
|
1914
|
+
return source.replace(writer, 'status = value.binaryBody !== null ? __pulse_fastly_write_binary(bodyHandle, value.binaryBody!) : __pulse_fastly_write_body(bodyHandle, value.text);');
|
|
1714
1915
|
}
|
|
1715
1916
|
|
|
1716
1917
|
function driverSource(plan, options = {}) {
|
|
@@ -1727,6 +1928,13 @@ export function pulse_fastly_jwt_crypto_calls(): i32 { return __pulse_fastly_jwt
|
|
|
1727
1928
|
export function pulse_fastly_jwt_clock_calls(): i32 { return __pulse_fastly_jwt_clock_calls }
|
|
1728
1929
|
export function pulse_fastly_jwt_schema_calls(): i32 { return __pulse_fastly_jwt_schema_calls }
|
|
1729
1930
|
export function ${entryName}(): void {
|
|
1931
|
+
if (__pulse_invocation_started) { __pulse_fastly_fail(PULSE_ERROR_STATE, 171, -1); return }
|
|
1932
|
+
__pulse_invocation_started = true
|
|
1933
|
+
__pulse_fastly_run_invocation()
|
|
1934
|
+
${plan.effects.some(e => e.kind === 's3.getBody') ? '__s3_close_bodies()' : ''}
|
|
1935
|
+
__pulse_invocation_close()
|
|
1936
|
+
}
|
|
1937
|
+
function __pulse_fastly_run_invocation(): void {
|
|
1730
1938
|
__pulse_fastly_last_error = fastly_abi_init(PULSE_FASTLY_EFFECTS_ABI_VERSION)
|
|
1731
1939
|
if (__pulse_fastly_last_error != FASTLY_STATUS_OK) { __pulse_fastly_jwt_send_error(); return }
|
|
1732
1940
|
const handles = new StaticArray<i32>(2)
|
|
@@ -1740,12 +1948,13 @@ export function ${entryName}(): void {
|
|
|
1740
1948
|
if (__pulse_fastly_last_error != 0) { __pulse_fastly_jwt_send_error(); return }
|
|
1741
1949
|
__pulse_fastly_request_path = __pulse_fastly_path(__pulse_fastly_request_url)
|
|
1742
1950
|
let runStatus = pulse_start()
|
|
1743
|
-
${applicationErrors.enabled(plan) ? applicationErrors.driverLoop(plan) : ` while (runStatus == 1 && __pulse_fastly_last_error == 0) {
|
|
1951
|
+
${packageApplication.enabled(plan) ? packageApplication.driverLoop() : applicationErrors.enabled(plan) ? applicationErrors.driverLoop(plan) : ` while (runStatus == 1 && __pulse_fastly_last_error == 0) {
|
|
1744
1952
|
for (let effectIndex = 0; effectIndex < PULSE_FASTLY_EFFECT_COUNT; effectIndex += 1) {
|
|
1745
1953
|
if (unchecked(__pulse_fastly_pending_mode[effectIndex]) == PULSE_FASTLY_PENDING_NONE) continue
|
|
1954
|
+
const invocation = unchecked(__pulse_invocation_tickets[effectIndex])
|
|
1746
1955
|
const result = __pulse_fastly_resolve_effect(effectIndex)
|
|
1747
1956
|
if (result <= 0 || __pulse_fastly_last_error != 0) { __pulse_fastly_jwt_send_error(); return }
|
|
1748
|
-
if (
|
|
1957
|
+
if (__pulse_invocation_settle(effectIndex, invocation, result) != 1) { __pulse_fastly_fail(PULSE_ERROR_STATE, 101, effectIndex); __pulse_fastly_jwt_send_error(); return }
|
|
1749
1958
|
}
|
|
1750
1959
|
runStatus = pulse_resume()
|
|
1751
1960
|
}`}
|
|
@@ -1764,7 +1973,7 @@ function capabilityImports(effect) {
|
|
|
1764
1973
|
if (effect.kind === 'time.now') return ['wasi_snapshot_preview1.clock_time_get'];
|
|
1765
1974
|
if (effect.kind === 'config.get') return ['fastly_config_store.open', 'fastly_config_store.get'];
|
|
1766
1975
|
if (effect.kind === 'secret.get') return ['fastly_secret_store.open', 'fastly_secret_store.get', 'fastly_secret_store.plaintext'];
|
|
1767
|
-
if (
|
|
1976
|
+
if (['jwt.verify', 'jwt.sign'].includes(effect.kind)) {
|
|
1768
1977
|
return [
|
|
1769
1978
|
...(effect.resource && effect.resource.keyType === 'secret'
|
|
1770
1979
|
? [
|
|
@@ -1796,7 +2005,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
1796
2005
|
validatePlanBoundary(plan, options);
|
|
1797
2006
|
const facts = packageFacts(plan, options);
|
|
1798
2007
|
const jwtCrypto = selectedJwtCrypto(plan);
|
|
1799
|
-
const es256KeyRecords = jwtCrypto && jwtCrypto.algorithm
|
|
2008
|
+
const es256KeyRecords = jwtCrypto && ['ES256', 'RS256'].includes(jwtCrypto.algorithm)
|
|
1800
2009
|
? resolveEs256KeyArtifacts(plan, facts)
|
|
1801
2010
|
: Object.freeze([]);
|
|
1802
2011
|
if (jwtCrypto && jwtCrypto.guestUnitRequired && facts.guestUnits.length !== 1) {
|
|
@@ -1820,10 +2029,11 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
1820
2029
|
);
|
|
1821
2030
|
}
|
|
1822
2031
|
const bindings = resolveCapabilityBindings(plan, options);
|
|
1823
|
-
const portable = generateCanonicalNativeAssemblyScript(plan, options);
|
|
2032
|
+
const portable = packageApplication.enabled(plan) ? packageApplication.portable(plan) : generateCanonicalNativeAssemblyScript(plan, options);
|
|
1824
2033
|
const portableSource = stripPulseHostImports(portable.source);
|
|
1825
2034
|
const hasJwt = (plan.effects || []).some((effect) => effect.kind === 'jwt.verify');
|
|
1826
|
-
const
|
|
2035
|
+
const hasSign = plan.effects.some(effect => effect.kind === 'jwt.sign');
|
|
2036
|
+
const jwtSource = hasJwt || hasSign ? pulseJwtAssemblyScriptSource({ includeSigning: hasSign }) : undefined;
|
|
1827
2037
|
let source = [
|
|
1828
2038
|
'/* Generated by Pulse Pass98 Fastly native platform capability compiler. */',
|
|
1829
2039
|
'export function __pulse_fastly_abort(message: string | null, fileName: string | null = null, line: u32 = 0, column: u32 = 0): void { unreachable() }',
|
|
@@ -1835,12 +2045,14 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
1835
2045
|
}),
|
|
1836
2046
|
jwtSource ? jwtSource.source : '',
|
|
1837
2047
|
require('./s3-native.js').s3NativeSource(plan, bindings),
|
|
2048
|
+
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
2049
|
require('./time-native.js').timeNativeSource(plan),
|
|
1839
2050
|
require('./digest-native.js').digestNativeSource(plan),
|
|
1840
2051
|
conditionalKv.kvNativeSource(plan),
|
|
1841
2052
|
require('./native-request-body.js').runtimeSource(),
|
|
1842
2053
|
require('./native-request-headers.js').runtimeSource(),
|
|
1843
|
-
applicationErrors.enabled(plan) ? applicationErrors.runtimeSource() : '',
|
|
2054
|
+
applicationErrors.enabled(plan) ? applicationErrors.runtimeSource(plan) : '',
|
|
2055
|
+
effectInvocations.runtimeSource({ packageCompletion: packageApplication.enabled(plan) }),
|
|
1844
2056
|
portableSource,
|
|
1845
2057
|
driverSource(plan, { guestLinked: facts.guestUnits.length > 0 }),
|
|
1846
2058
|
''
|
|
@@ -1848,7 +2060,10 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
1848
2060
|
if (applicationErrors.enabled(plan)) source = applicationErrors.instrument(source);
|
|
1849
2061
|
source = require('./native-request-headers.js').instrument(source, applicationErrors.enabled(plan), /\bhost_request_headers\(/.test(portableSource));
|
|
1850
2062
|
source = require('./native-request-body.js').instrument(source, applicationErrors.enabled(plan));
|
|
2063
|
+
if (packageApplication.enabled(plan)) source = packageApplication.instrument(source);
|
|
2064
|
+
source = effectInvocations.instrument(source);
|
|
1851
2065
|
source = require('./request-budget.js').instrumentRequestBudget(source, bindings.maxDurationMs, plan);
|
|
2066
|
+
source = nativeValueBudget.instrument(source, plan);
|
|
1852
2067
|
const effectKinds = Object.freeze((plan.effects || []).reduce((output, effect) => {
|
|
1853
2068
|
output[effect.kind] = (output[effect.kind] || 0) + 1;
|
|
1854
2069
|
return output;
|
|
@@ -1875,7 +2090,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
1875
2090
|
crypto: portable.manifest.crypto,
|
|
1876
2091
|
jwt: jwtSource
|
|
1877
2092
|
? Object.freeze({
|
|
1878
|
-
status: jwtCrypto && jwtCrypto.algorithm
|
|
2093
|
+
status: jwtCrypto && ['ES256', 'RS256'].includes(jwtCrypto.algorithm)
|
|
1879
2094
|
? 'implemented-g4'
|
|
1880
2095
|
: 'implemented-e2',
|
|
1881
2096
|
package: jwtSource.owner,
|
|
@@ -1894,6 +2109,7 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
1894
2109
|
})
|
|
1895
2110
|
: Object.freeze({ status: 'inactive' }),
|
|
1896
2111
|
effectCount: (plan.effects || []).length,
|
|
2112
|
+
effectInvocations: effectInvocations.policy,
|
|
1897
2113
|
effectKinds,
|
|
1898
2114
|
continuationCount: (plan.continuations || []).length,
|
|
1899
2115
|
groupedContinuationCount: (plan.continuations || []).filter((entry) => (entry.effectIds || []).length > 1).length,
|
|
@@ -1912,19 +2128,20 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
1912
2128
|
...(bindings.maxDurationMs === undefined ? [] : ['pulse_fastly_request_expired'])
|
|
1913
2129
|
]),
|
|
1914
2130
|
policy: Object.freeze({
|
|
2131
|
+
...(nativeValueBudget.hasBoundedReadLoop(plan) ? { readLoopMemory: nativeValueBudget.policy } : {}),
|
|
1915
2132
|
nativeFastly: true,
|
|
1916
2133
|
provider: 'fastly',
|
|
1917
2134
|
providerNeutralInput: true,
|
|
1918
2135
|
javascriptRuntime: false,
|
|
1919
2136
|
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,
|
|
2137
|
+
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
2138
|
effects: FASTLY_NATIVE_PLATFORM_EFFECT_KINDS.join(', '),
|
|
1922
2139
|
continuations: true,
|
|
1923
2140
|
groupedEffects: 'existing fetch start-before-wait ordering remains intact',
|
|
1924
2141
|
config: 'Fastly Config Store open/get with undefined for missing values',
|
|
1925
2142
|
secrets: 'Fastly Secret Store open/get/plaintext; proof traces redact plaintext values',
|
|
1926
|
-
jwt: hasJwt
|
|
1927
|
-
? `package-owned compact-JWS
|
|
2143
|
+
jwt: hasJwt || hasSign
|
|
2144
|
+
? `package-owned compact-JWS, registered-claim, and bounded issuance semantics composed with ${jwtCrypto.realization}; provider-owned request, secret, clock, schema, and transport`
|
|
1928
2145
|
: 'inactive',
|
|
1929
2146
|
kv: 'Fastly KV Store async lookup/insert; conditional operations preserve u64 generations, bounded readiness, strict Pulse envelopes and uncertain dispatch',
|
|
1930
2147
|
assets: 'Fastly KV Store lookup with provider-owned content type, cache headers, and GET/HEAD body ownership',
|
|
@@ -1960,8 +2177,9 @@ function generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options =
|
|
|
1960
2177
|
});
|
|
1961
2178
|
}
|
|
1962
2179
|
|
|
1963
|
-
function inspectFastlyNativePlatformCapabilitiesWasm(input) {
|
|
2180
|
+
function inspectFastlyNativePlatformCapabilitiesWasm(input, options = {}) {
|
|
1964
2181
|
const bytes = Buffer.isBuffer(input) ? input : fs.readFileSync(path.resolve(input));
|
|
2182
|
+
assertFastlyWasmBudget(bytes.length, options.maxWasmBytes);
|
|
1965
2183
|
if (!WebAssembly.validate(bytes)) fail('Generated Fastly native platform capabilities module is not valid WebAssembly.', 'PULSE_FASTLY_NATIVE_PLATFORM_CAPABILITIES_WASM_INVALID');
|
|
1966
2184
|
const module = new WebAssembly.Module(bytes);
|
|
1967
2185
|
const imports = WebAssembly.Module.imports(module).map((entry) => Object.freeze({ module: entry.module, name: entry.name, kind: entry.kind }));
|
|
@@ -1977,7 +2195,6 @@ function inspectFastlyNativePlatformCapabilitiesWasm(input) {
|
|
|
1977
2195
|
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
2196
|
const missingExports = requiredExports.filter((name) => !exportNames.has(name));
|
|
1979
2197
|
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
2198
|
return Object.freeze({
|
|
1982
2199
|
valid: true,
|
|
1983
2200
|
bytes: bytes.length,
|
|
@@ -1990,6 +2207,7 @@ function inspectFastlyNativePlatformCapabilitiesWasm(input) {
|
|
|
1990
2207
|
}
|
|
1991
2208
|
|
|
1992
2209
|
function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
2210
|
+
const maxWasmBytes = normalizeFastlyMaxWasmBytes(options.maxWasmBytes);
|
|
1993
2211
|
const generated = generateFastlyNativePlatformCapabilitiesAssemblyScript(plan, options);
|
|
1994
2212
|
const cwd = path.resolve(options.cwd || process.cwd());
|
|
1995
2213
|
const compilerFallback = path.resolve(__dirname, '..', '..', '..', '..', 'wasm', 'packages', 'compiler');
|
|
@@ -2022,6 +2240,8 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2022
2240
|
}
|
|
2023
2241
|
jsonAs = Object.freeze({ transform, dependencyRoot, version: packageJson.version });
|
|
2024
2242
|
}
|
|
2243
|
+
if (options.emitWat !== undefined && typeof options.emitWat !== 'boolean') throw new TypeError('emitWat must be a boolean.');
|
|
2244
|
+
const emitWat = options.emitWat === true;
|
|
2025
2245
|
const stagingDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pulse-fastly-native-platform-'));
|
|
2026
2246
|
const requestedOutDir = options.outDir ? path.resolve(options.outDir) : undefined;
|
|
2027
2247
|
const outputDir = requestedOutDir || stagingDir;
|
|
@@ -2029,6 +2249,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2029
2249
|
const sourceFile = path.join(stagingDir, 'fastly-native-platform-capabilities.as.ts');
|
|
2030
2250
|
const wasmFile = path.join(outputDir, options.wasmFile || 'fastly-native-platform-capabilities.wasm');
|
|
2031
2251
|
const watFile = path.join(outputDir, options.watFile || 'fastly-native-platform-capabilities.wat');
|
|
2252
|
+
if (!emitWat) fs.rmSync(watFile, { force: true });
|
|
2032
2253
|
const guestLinked = generated.guestUnits.length > 0;
|
|
2033
2254
|
// asc resolves custom runtimes like module names and appends ".ts".
|
|
2034
2255
|
const fixedMemoryRuntimeModule = path.join(__dirname, 'fixed-memory-runtime.as');
|
|
@@ -2045,16 +2266,16 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2045
2266
|
asc.script,
|
|
2046
2267
|
path.basename(sourceFile),
|
|
2047
2268
|
'--outFile', assemblyScriptWasmFile,
|
|
2048
|
-
'--textFile', assemblyScriptWatFile,
|
|
2049
2269
|
'--runtime', guestLinked
|
|
2050
2270
|
? fixedMemoryRuntimeModule
|
|
2051
2271
|
: (schemaCodecsActive ? 'incremental' : 'stub'),
|
|
2052
2272
|
'--noAssert',
|
|
2053
2273
|
'--optimize'
|
|
2054
2274
|
];
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2275
|
+
if (emitWat) args.push('--textFile', assemblyScriptWatFile);
|
|
2276
|
+
const optimization = appendAssemblyScriptOptimizationArgs(args, nativeOptimization, { guestLinked });
|
|
2277
|
+
if (!guestLinked && (nativeValueBudget.hasBoundedReadLoop(plan) || plan.effects.some(effect => effect.kind === 'crypto.digestText' || effect.kind.startsWith('s3.')))) {
|
|
2278
|
+
args.push('--maximumMemory', String(nativeValueBudget.policy.maximumMemoryPages));
|
|
2058
2279
|
}
|
|
2059
2280
|
if (generated.guestUnits.length > 0) {
|
|
2060
2281
|
args.push(
|
|
@@ -2132,7 +2353,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2132
2353
|
failureMessage: 'Fastly Native start wrapper composition failed.'
|
|
2133
2354
|
}
|
|
2134
2355
|
);
|
|
2135
|
-
runGuestTool(
|
|
2356
|
+
if (emitWat) runGuestTool(
|
|
2136
2357
|
'wasm-dis',
|
|
2137
2358
|
[wasmFile, '--mvp-features', '-o', watFile],
|
|
2138
2359
|
{
|
|
@@ -2162,7 +2383,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2162
2383
|
});
|
|
2163
2384
|
}
|
|
2164
2385
|
const primaryWasm = fs.readFileSync(wasmFile);
|
|
2165
|
-
const primaryWat =
|
|
2386
|
+
const primaryWat = emitWat ? fs.readFileSync(watFile, 'utf8') : '';
|
|
2166
2387
|
const primaryArtifact = Object.freeze({
|
|
2167
2388
|
bytes: primaryWasm.length,
|
|
2168
2389
|
sha256: sha256(primaryWasm)
|
|
@@ -2195,7 +2416,8 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2195
2416
|
realization = Object.freeze({
|
|
2196
2417
|
...realization,
|
|
2197
2418
|
wasm: stageResult.wasm,
|
|
2198
|
-
|
|
2419
|
+
// Required guest-link disassembly audits remain enabled.
|
|
2420
|
+
wat: emitWat ? stageResult.wat : '',
|
|
2199
2421
|
guestUnits: stageResult.guestUnits,
|
|
2200
2422
|
guestLink: Object.freeze({
|
|
2201
2423
|
version: stageResult.version,
|
|
@@ -2213,9 +2435,9 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2213
2435
|
const wat = realization.wat;
|
|
2214
2436
|
if (requestedOutDir && generated.guestUnits.length > 0) {
|
|
2215
2437
|
fs.writeFileSync(wasmFile, wasm);
|
|
2216
|
-
fs.writeFileSync(watFile, wat, 'utf8');
|
|
2438
|
+
if (emitWat) fs.writeFileSync(watFile, wat, 'utf8');
|
|
2217
2439
|
}
|
|
2218
|
-
const inspection = inspectFastlyNativePlatformCapabilitiesWasm(wasm);
|
|
2440
|
+
const inspection = inspectFastlyNativePlatformCapabilitiesWasm(wasm, { maxWasmBytes });
|
|
2219
2441
|
const imported = new Set(inspection.imports.map((entry) => `${entry.module}:${entry.name}`));
|
|
2220
2442
|
const requiredImports = requiredImportsForPlan(plan, generated.bindings);
|
|
2221
2443
|
const missingRequiredImports = requiredImports.filter((key) => !imported.has(key));
|
|
@@ -2229,7 +2451,9 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2229
2451
|
: undefined,
|
|
2230
2452
|
optimization,
|
|
2231
2453
|
wasm: Object.freeze({ bytes: inspection.bytes, sha256: inspection.sha256, magic: inspection.magic }),
|
|
2232
|
-
wat: Object.freeze(
|
|
2454
|
+
wat: Object.freeze(emitWat
|
|
2455
|
+
? { emitted: true, bytes: Buffer.byteLength(wat), sha256: sha256(wat) }
|
|
2456
|
+
: { emitted: false, bytes: 0, sha256: null }),
|
|
2233
2457
|
importModules: inspection.importModules,
|
|
2234
2458
|
imports: inspection.imports,
|
|
2235
2459
|
requiredImports,
|
|
@@ -2262,7 +2486,7 @@ function compileFastlyNativePlatformCapabilitiesPlan(plan, options = {}) {
|
|
|
2262
2486
|
assemblyScriptDurationMs,
|
|
2263
2487
|
primaryArtifact,
|
|
2264
2488
|
startWrapper,
|
|
2265
|
-
output: requestedOutDir ? Object.freeze({ outDir: outputDir, wasmFile, watFile }) : undefined
|
|
2489
|
+
output: requestedOutDir ? Object.freeze({ outDir: outputDir, wasmFile, watFile: emitWat ? watFile : null }) : undefined
|
|
2266
2490
|
});
|
|
2267
2491
|
} finally {
|
|
2268
2492
|
fs.rmSync(stagingDir, { recursive: true, force: true });
|
|
@@ -2280,10 +2504,12 @@ function writeFastlyNativePlatformCapabilitiesModule(compiled, outDir, options =
|
|
|
2280
2504
|
const manifestFile = path.join(target, options.manifestFile || 'fastly-native-platform-capabilities-manifest.json');
|
|
2281
2505
|
fs.writeFileSync(sourceFile, compiled.source, 'utf8');
|
|
2282
2506
|
fs.writeFileSync(wasmFile, compiled.wasm);
|
|
2283
|
-
|
|
2507
|
+
const emitWat = compiled.manifest.wat.emitted !== false;
|
|
2508
|
+
if (emitWat) fs.writeFileSync(watFile, compiled.wat, 'utf8');
|
|
2509
|
+
else fs.rmSync(watFile, { force: true });
|
|
2284
2510
|
fs.writeFileSync(planFile, `${stableStringify(compiled.plan, 2)}\n`, 'utf8');
|
|
2285
2511
|
fs.writeFileSync(manifestFile, `${stableStringify(compiled.manifest, 2)}\n`, 'utf8');
|
|
2286
|
-
return Object.freeze({ target, sourceFile, wasmFile, watFile, planFile, manifestFile, manifest: compiled.manifest });
|
|
2512
|
+
return Object.freeze({ target, sourceFile, wasmFile, watFile: emitWat ? watFile : null, planFile, manifestFile, manifest: compiled.manifest });
|
|
2287
2513
|
}
|
|
2288
2514
|
|
|
2289
2515
|
module.exports = Object.freeze({
|