From c64ec03edf451a702d44b40c9154bb4c341c143e Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Thu, 30 Oct 2025 16:06:07 +0000 Subject: [PATCH 01/31] Make a start on image optimizer --- .../fixtures/app/src/image-optimizer.js | 20 + .../js-compute/fixtures/app/src/index.js | 13 +- .../app/src/request-auto-decompress.js | 8 +- .../js-compute/fixtures/app/tests.json | 2087 +++++++++++++---- runtime/fastly/CMakeLists.txt | 1 + runtime/fastly/builtins/fetch/fetch.cpp | 125 +- .../builtins/fetch/request-response.cpp | 38 +- .../fastly/builtins/fetch/request-response.h | 3 + .../builtins/image-optimizer-options.inc | 51 + runtime/fastly/builtins/image-optimizer.cpp | 156 ++ runtime/fastly/builtins/image-optimizer.h | 89 + runtime/fastly/host-api/fastly.h | 26 + runtime/fastly/host-api/host_api.cpp | 69 + runtime/fastly/host-api/host_api_fastly.h | 28 +- runtime/fastly/host-api/host_call.cpp | 10 + src/bundle.js | 8 + sy-test/.fastlyignore | 3 + sy-test/.gitignore | 3 + sy-test/README.md | 31 + sy-test/fastly.toml | 22 + sy-test/src/index.js | 17 + 21 files changed, 2287 insertions(+), 521 deletions(-) create mode 100644 integration-tests/js-compute/fixtures/app/src/image-optimizer.js create mode 100644 runtime/fastly/builtins/image-optimizer-options.inc create mode 100644 runtime/fastly/builtins/image-optimizer.cpp create mode 100644 runtime/fastly/builtins/image-optimizer.h create mode 100644 sy-test/.fastlyignore create mode 100644 sy-test/.gitignore create mode 100644 sy-test/README.md create mode 100644 sy-test/fastly.toml create mode 100644 sy-test/src/index.js diff --git a/integration-tests/js-compute/fixtures/app/src/image-optimizer.js b/integration-tests/js-compute/fixtures/app/src/image-optimizer.js new file mode 100644 index 0000000000..217cdc3351 --- /dev/null +++ b/integration-tests/js-compute/fixtures/app/src/image-optimizer.js @@ -0,0 +1,20 @@ +/* eslint-env serviceworker */ + +import { routes } from './routes.js'; +import { Region, Auto } from 'fastly:image-optimizer'; +import { + assert, + assertThrows, + assertRejects, + strictEqual, +} from './assertions.js'; + +routes.set('/image-optimizer/test', async () => { + const response = await fetch('https://http-me.glitch.me/image-jpeg', { + imageOptimizerOptions: { + region: Region.UsEast, + auto: Auto.AVIF + } + }); + +}); diff --git a/integration-tests/js-compute/fixtures/app/src/index.js b/integration-tests/js-compute/fixtures/app/src/index.js index 68b67a3fc2..a8a23d8569 100644 --- a/integration-tests/js-compute/fixtures/app/src/index.js +++ b/integration-tests/js-compute/fixtures/app/src/index.js @@ -28,6 +28,7 @@ import './geoip.js'; import './headers.js'; import './html-rewriter.js'; import './include-bytes.js'; +import './image-optimizer.js'; import './logger.js'; import './manual-framing-headers.js'; import './missing-backend.js'; @@ -94,12 +95,12 @@ async function app(event) { try { return (res = new Response( `The routeHandler for ${path} threw a [${error.constructor?.name ?? error.name}] error: ${error.message || error}` + - '\n' + - error.stack + - (fastly.debugMessages - ? '\n[DEBUG BUILD MESSAGES]:\n\n - ' + - fastly.debugMessages.join('\n - ') - : ''), + '\n' + + error.stack + + (fastly.debugMessages + ? '\n[DEBUG BUILD MESSAGES]:\n\n - ' + + fastly.debugMessages.join('\n - ') + : ''), { status: 500 }, )); } catch (errRes) { diff --git a/integration-tests/js-compute/fixtures/app/src/request-auto-decompress.js b/integration-tests/js-compute/fixtures/app/src/request-auto-decompress.js index 898e8a4946..439fe53e55 100644 --- a/integration-tests/js-compute/fixtures/app/src/request-auto-decompress.js +++ b/integration-tests/js-compute/fixtures/app/src/request-auto-decompress.js @@ -4,7 +4,7 @@ import { routes } from './routes.js'; // Request.fastly.decompressGzip option -- automatic gzip decompression of responses routes.set('/request/constructor/fastly/decompressGzip/true', async () => { - const request = new Request('https://httpbin.org/gzip', { + const request = new Request('https://localhost:7676/gzip', { headers: { accept: 'application/json', }, @@ -20,7 +20,7 @@ routes.set('/request/constructor/fastly/decompressGzip/true', async () => { }); routes.set('/request/constructor/fastly/decompressGzip/false', async () => { - const request = new Request('https://httpbin.org/gzip', { + const request = new Request('https://localhost:7676/gzip', { headers: { accept: 'application/json', }, @@ -38,7 +38,7 @@ routes.set('/request/constructor/fastly/decompressGzip/false', async () => { }); routes.set('/fetch/requestinit/fastly/decompressGzip/true', async () => { - const response = await fetch('https://httpbin.org/gzip', { + const response = await fetch('https://localhost:7676/gzip', { headers: { accept: 'application/json', }, @@ -53,7 +53,7 @@ routes.set('/fetch/requestinit/fastly/decompressGzip/true', async () => { }); routes.set('/fetch/requestinit/fastly/decompressGzip/false', async () => { - const response = await fetch('https://httpbin.org/gzip', { + const response = await fetch('https://localhost:7676/gzip', { headers: { accept: 'application/json', }, diff --git a/integration-tests/js-compute/fixtures/app/tests.json b/integration-tests/js-compute/fixtures/app/tests.json index 0b899223c7..f2690acde0 100644 --- a/integration-tests/js-compute/fixtures/app/tests.json +++ b/integration-tests/js-compute/fixtures/app/tests.json @@ -3,14 +3,22 @@ "downstream_response": { "status": 200, "headers": [ - ["FooName", "FooValue"], - ["BarName", "BarValue"] + [ + "FooName", + "FooValue" + ], + [ + "BarName", + "BarValue" + ] ], "body": "pong" } }, "GET /btoa": { - "environments": ["viceroy"], + "environments": [ + "viceroy" + ], "downstream_response": { "status": 200, "body": "ok" @@ -19,13 +27,18 @@ "GET /byob": { "downstream_response": { "status": 200, - "body": ["hey3"] + "body": [ + "hey3" + ] } }, "GET /byte-repeater": { "downstream_response": { "status": 200, - "body": ["11223344", "5566778899001122\n\n"] + "body": [ + "11223344", + "5566778899001122\n\n" + ] } }, "GET /cache-override/constructor/called-as-regular-function": {}, @@ -40,376 +53,586 @@ "GET /cache-override/fetch/mode-pass": { "flake": true }, - "GET /secret-store/exposed-as-global": { "flake": true }, - "GET /secret-store/interface": { "flake": true }, - "GET /secret-store/constructor/called-as-regular-function": { "flake": true }, + "GET /secret-store/exposed-as-global": { + "flake": true + }, + "GET /secret-store/interface": { + "flake": true + }, + "GET /secret-store/constructor/called-as-regular-function": { + "flake": true + }, "GET /secret-store/constructor/parameter-calls-7.1.17-ToString": { "flake": true }, - "GET /secret-store/constructor/empty-parameter": { "flake": true }, - "GET /secret-store/constructor/found-store": { "flake": true }, - "GET /secret-store/constructor/missing-store": { "flake": true }, - "GET /secret-store/constructor/invalid-name": { "flake": true }, - "GET /secret-store/get/called-as-constructor": { "flake": true }, - "GET /secret-store/get/called-unbound": { "flake": true }, + "GET /secret-store/constructor/empty-parameter": { + "flake": true + }, + "GET /secret-store/constructor/found-store": { + "flake": true + }, + "GET /secret-store/constructor/missing-store": { + "flake": true + }, + "GET /secret-store/constructor/invalid-name": { + "flake": true + }, + "GET /secret-store/get/called-as-constructor": { + "flake": true + }, + "GET /secret-store/get/called-unbound": { + "flake": true + }, "GET /secret-store/get/key-parameter-calls-7.1.17-ToString": { "flake": true }, - "GET /secret-store/get/key-parameter-not-supplied": { "flake": true }, - "GET /secret-store/get/key-parameter-empty-string": { "flake": true }, - "GET /secret-store/get/key-parameter-255-character-string": { "flake": true }, - "GET /secret-store/get/key-parameter-256-character-string": { "flake": true }, - "GET /secret-store/get/key-parameter-invalid-string": { "flake": true }, - "GET /secret-store/get/key-does-not-exist-returns-null": { "flake": true }, - "GET /secret-store/get/key-exists": { "flake": true }, - "GET /secret-store/from-bytes/invalid": { "flake": true }, - "GET /secret-store/from-bytes/valid": { "flake": true }, - "GET /secret-store-entry/interface": { "flake": true }, - "GET /secret-store-entry/plaintext": { "flake": true }, + "GET /secret-store/get/key-parameter-not-supplied": { + "flake": true + }, + "GET /secret-store/get/key-parameter-empty-string": { + "flake": true + }, + "GET /secret-store/get/key-parameter-255-character-string": { + "flake": true + }, + "GET /secret-store/get/key-parameter-256-character-string": { + "flake": true + }, + "GET /secret-store/get/key-parameter-invalid-string": { + "flake": true + }, + "GET /secret-store/get/key-does-not-exist-returns-null": { + "flake": true + }, + "GET /secret-store/get/key-exists": { + "flake": true + }, + "GET /secret-store/from-bytes/invalid": { + "flake": true + }, + "GET /secret-store/from-bytes/valid": { + "flake": true + }, + "GET /secret-store-entry/interface": { + "flake": true + }, + "GET /secret-store-entry/plaintext": { + "flake": true + }, "GET /simple-cache/interface": {}, "GET /simple-store/constructor/called-as-regular-function": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/constructor/throws": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/purge/called-as-constructor": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/purge/key-parameter-calls-7.1.17-ToString": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/purge/key-parameter-not-supplied": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/purge/key-parameter-empty-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/purge/key-parameter-8135-character-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/purge/key-parameter-8136-character-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/purge/options-parameter": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/purge/returns-undefined": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/called-as-constructor": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/key-parameter-calls-7.1.17-ToString": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/tll-parameter-7.1.4-ToNumber": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/no-parameters-supplied": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/key-parameter-empty-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/key-parameter-8135-character-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/key-parameter-8136-character-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/ttl-parameter-negative-number": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/ttl-parameter-NaN": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/ttl-parameter-Infinity": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-as-undefined": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-readablestream-missing-length-parameter": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-readablestream-negative-length-parameter": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-readablestream-nan-length-parameter": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-readablestream-negative-infinity-length-parameter": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-readablestream-positive-infinity-length-parameter": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/length-parameter-7.1.4-ToNumber": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-readablestream-empty": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-readablestream-locked": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-readablestream": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-URLSearchParams": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-strings": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-calls-7.1.17-ToString": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-buffer": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-arraybuffer": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-typed-arrays": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-dataview": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/returns-undefined": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/get/called-as-constructor": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/get/key-parameter-calls-7.1.17-ToString": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/get/key-parameter-not-supplied": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/get/key-parameter-empty-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/get/key-parameter-8135-character-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/get/key-parameter-8136-character-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/get/key-does-not-exist-returns-null": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/get/key-exists": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache-entry/interface": {}, "GET /simple-cache-entry/text/valid": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache-entry/json/valid": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache-entry/json/invalid": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache-entry/arrayBuffer/valid": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache-entry/body": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache-entry/bodyUsed": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache-entry/readablestream": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/rejection-rejects-outer": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/called-as-constructor": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/no-parameters-supplied": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/key-parameter-calls-7.1.17-ToString": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/key-parameter-empty-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/key-parameter-8135-character-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/key-parameter-8136-character-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/ttl-field-7.1.4-ToNumber": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/ttl-field-negative-number": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/ttl-field-NaN": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/ttl-field-Infinity": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-as-undefined": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-readablestream-missing-length-field": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-readablestream-negative-length-field": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-readablestream-nan-length-field": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-readablestream-negative-infinity-length-field": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-readablestream-positive-infinity-length-field": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/length-field-7.1.4-ToNumber": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-readablestream-empty": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-readablestream-locked": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-readablestream": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-URLSearchParams": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-strings": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-calls-7.1.17-ToString": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-buffer": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-typed-arrays": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-dataview": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/returns-SimpleCacheEntry": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/executes-the-set-method-when-key-not-in-cache": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/does-not-execute-the-set-method-when-key-is-in-cache": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/does-not-freeze-when-called-after-a-get": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /client/tlsJA3MD5": {}, "GET /client/tlsClientHello": {}, "GET /client/tlsClientCertificate": {}, "GET /client/tlsCipherOpensslName": {}, "GET /client/tlsProtocol": {}, - "GET /config-store": { "flake": true }, + "GET /config-store": { + "flake": true + }, "GET /crypto": { "downstream_response": { "status": 200, @@ -980,67 +1203,123 @@ "body": "ok" } }, - "GET /dictionary/exposed-as-global": { "flake": true }, - "GET /dictionary/interface": { "flake": true }, - "GET /dictionary/constructor/called-as-regular-function": { "flake": true }, + "GET /dictionary/exposed-as-global": { + "flake": true + }, + "GET /dictionary/interface": { + "flake": true + }, + "GET /dictionary/constructor/called-as-regular-function": { + "flake": true + }, "GET /dictionary/constructor/parameter-calls-7.1.17-ToString": { "flake": true }, - "GET /dictionary/constructor/empty-parameter": { "flake": true }, - "GET /dictionary/constructor/found": { "flake": true }, - "GET /dictionary/constructor/invalid-name": { "flake": true }, - "GET /dictionary/get/called-as-constructor": { "flake": true }, - "GET /dictionary/get/called-unbound": { "flake": true }, - "GET /dictionary/get/key-parameter-calls-7.1.17-ToString": { "flake": true }, - "GET /dictionary/get/key-parameter-not-supplied": { "flake": true }, - "GET /dictionary/get/key-parameter-empty-string": { "flake": true }, - "GET /dictionary/get/key-parameter-255-character-string": { "flake": true }, - "GET /dictionary/get/key-parameter-256-character-string": { "flake": true }, - "GET /dictionary/get/key-does-not-exist-returns-null": { "flake": true }, - "GET /dictionary/get/key-exists": { "flake": true }, + "GET /dictionary/constructor/empty-parameter": { + "flake": true + }, + "GET /dictionary/constructor/found": { + "flake": true + }, + "GET /dictionary/constructor/invalid-name": { + "flake": true + }, + "GET /dictionary/get/called-as-constructor": { + "flake": true + }, + "GET /dictionary/get/called-unbound": { + "flake": true + }, + "GET /dictionary/get/key-parameter-calls-7.1.17-ToString": { + "flake": true + }, + "GET /dictionary/get/key-parameter-not-supplied": { + "flake": true + }, + "GET /dictionary/get/key-parameter-empty-string": { + "flake": true + }, + "GET /dictionary/get/key-parameter-255-character-string": { + "flake": true + }, + "GET /dictionary/get/key-parameter-256-character-string": { + "flake": true + }, + "GET /dictionary/get/key-does-not-exist-returns-null": { + "flake": true + }, + "GET /dictionary/get/key-exists": { + "flake": true + }, "GET /env": { - "environments": ["viceroy"] + "environments": [ + "viceroy" + ] }, "GET /createFanoutHandoff": {}, "GET /createWebsocketHandoff": {}, "GET /fastly/now": {}, "GET /fastly/version": {}, "GET /fastly/getgeolocationforipaddress/interface": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /fastly/getgeolocationforipaddress/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /fastly/getgeolocationforipaddress/parameter-calls-7.1.17-ToString": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /fastly/getgeolocationforipaddress/parameter-not-supplied": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /fastly/getgeolocationforipaddress/parameter-empty-string": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /fastly/getgeolocationforipaddress/bad-ip": {}, "GET /fastly/getgeolocationforipaddress/parameter-ipv4-string": {}, "GET /fastly/getgeolocationforipaddress/parameter-compressed-ipv6-string": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /fastly/getgeolocationforipaddress/parameter-shortened-ipv6-string": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /fastly/getgeolocationforipaddress/parameter-expanded-ipv6-string": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /fastly/getgeolocationforipaddress/called-unbound": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /fastly:geolocation": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /includeBytes": {}, "GET /logger": { - "environments": ["viceroy"], - "logs": ["ComputeLog :: Hello!"] + "environments": [ + "viceroy" + ], + "logs": [ + "ComputeLog :: Hello!" + ] }, "GET /missing-backend": {}, "GET /multiple-set-cookie/response-init": { @@ -1051,16 +1330,46 @@ "Set-Cookie", "test=1; expires=Tue, 06-Dec-2022 12:34:56 GMT; Max-Age=60; Path=/; HttpOnly; Secure, test_id=1; Max-Age=60; Path=/; expires=Tue, 06-Dec-2022 12:34:56 GMT, test_id=1; Max-Age=60; Path=/; expires=Tue, 06-Dec-2022 12:34:56 GMT" ], - ["Set-Cookie", "test2=2"], - ["Set-Cookie", "test3=3"], - ["Set-Cookie", "test4=4"], - ["Set-Cookie", "test5=5"], - ["Set-Cookie", "test6=6"], - ["Set-Cookie", "test7=7"], - ["Set-Cookie", "test8=8"], - ["Set-Cookie", "test9=9"], - ["Set-Cookie", "test10=10"], - ["Set-Cookie", "test11=11"] + [ + "Set-Cookie", + "test2=2" + ], + [ + "Set-Cookie", + "test3=3" + ], + [ + "Set-Cookie", + "test4=4" + ], + [ + "Set-Cookie", + "test5=5" + ], + [ + "Set-Cookie", + "test6=6" + ], + [ + "Set-Cookie", + "test7=7" + ], + [ + "Set-Cookie", + "test8=8" + ], + [ + "Set-Cookie", + "test9=9" + ], + [ + "Set-Cookie", + "test10=10" + ], + [ + "Set-Cookie", + "test11=11" + ] ] } }, @@ -1072,16 +1381,46 @@ "Set-Cookie", "test=1; expires=Tue, 06-Dec-2022 12:34:56 GMT; Max-Age=60; Path=/; HttpOnly; Secure, test_id=1; Max-Age=60; Path=/; expires=Tue, 06-Dec-2022 12:34:56 GMT, test_id=1; Max-Age=60; Path=/; expires=Tue, 06-Dec-2022 12:34:56 GMT" ], - ["Set-Cookie", "test2=2"], - ["Set-Cookie", "test3=3"], - ["Set-Cookie", "test4=4"], - ["Set-Cookie", "test5=5"], - ["Set-Cookie", "test6=6"], - ["Set-Cookie", "test7=7"], - ["Set-Cookie", "test8=8"], - ["Set-Cookie", "test9=9"], - ["Set-Cookie", "test10=10"], - ["Set-Cookie", "test11=11"] + [ + "Set-Cookie", + "test2=2" + ], + [ + "Set-Cookie", + "test3=3" + ], + [ + "Set-Cookie", + "test4=4" + ], + [ + "Set-Cookie", + "test5=5" + ], + [ + "Set-Cookie", + "test6=6" + ], + [ + "Set-Cookie", + "test7=7" + ], + [ + "Set-Cookie", + "test8=8" + ], + [ + "Set-Cookie", + "test9=9" + ], + [ + "Set-Cookie", + "test10=10" + ], + [ + "Set-Cookie", + "test11=11" + ] ] } }, @@ -1089,43 +1428,84 @@ "downstream_response": { "status": 302, "headers": [ - ["Set-Cookie", "1=1; Path=/"], - ["Set-Cookie", "2=2; Path=/"], - ["Set-Cookie", "3=3; Path=/"], - ["Set-Cookie", "4=4; Path=/"], - ["Set-Cookie", "5=5; Path=/"], - ["Set-Cookie", "6=6; Path=/"], - ["Set-Cookie", "7=7; Path=/"], - ["Set-Cookie", "8=8; Path=/"], - ["Set-Cookie", "9=9; Path=/"], - ["Set-Cookie", "10=10; Path=/"], - ["Set-Cookie", "11=11; Path=/"] + [ + "Set-Cookie", + "1=1; Path=/" + ], + [ + "Set-Cookie", + "2=2; Path=/" + ], + [ + "Set-Cookie", + "3=3; Path=/" + ], + [ + "Set-Cookie", + "4=4; Path=/" + ], + [ + "Set-Cookie", + "5=5; Path=/" + ], + [ + "Set-Cookie", + "6=6; Path=/" + ], + [ + "Set-Cookie", + "7=7; Path=/" + ], + [ + "Set-Cookie", + "8=8; Path=/" + ], + [ + "Set-Cookie", + "9=9; Path=/" + ], + [ + "Set-Cookie", + "10=10; Path=/" + ], + [ + "Set-Cookie", + "11=11; Path=/" + ] ] } }, "GET /Performance/interface": { - "environments": ["viceroy"], + "environments": [ + "viceroy" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /globalThis.performance": { - "environments": ["viceroy"], + "environments": [ + "viceroy" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /globalThis.performance/now": { - "environments": ["viceroy"], + "environments": [ + "viceroy" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /globalThis.performance/timeOrigin": { - "environments": ["viceroy"], + "environments": [ + "viceroy" + ], "downstream_response": { "status": 200, "body": "ok" @@ -1148,8 +1528,12 @@ "flake": true, "downstream_response": { "status": 200, - "headers": { "content-type": "text/html" }, - "body": ["

blob

"] + "headers": { + "content-type": "text/html" + }, + "body": [ + "

blob

" + ] } }, "GET /response/stall": { @@ -1165,14 +1549,120 @@ "downstream_response": { "status": 200, "body_prefix": [ - 123, 10, 32, 32, 34, 97, 114, 103, 115, 34, 58, 32, 123, 125, 44, 32, - 10, 32, 32, 34, 100, 97, 116, 97, 34, 58, 32, 34, 100, 97, 116, 97, 58, - 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 111, 99, 116, - 101, 116, 45, 115, 116, 114, 101, 97, 109, 59, 98, 97, 115, 101, 54, 52, - 44, 85, 107, 108, 71, 82, 107, 65, 112, 65, 65, 66, 88, 82, 85, 74, 81, - 86, 108, 65, 52, 87, 65, 111, 65, 65, 65, 65, 69, 65, 65, 65, 65, 69, 81 + 123, + 10, + 32, + 32, + 34, + 97, + 114, + 103, + 115, + 34, + 58, + 32, + 123, + 125, + 44, + 32, + 10, + 32, + 32, + 34, + 100, + 97, + 116, + 97, + 34, + 58, + 32, + 34, + 100, + 97, + 116, + 97, + 58, + 97, + 112, + 112, + 108, + 105, + 99, + 97, + 116, + 105, + 111, + 110, + 47, + 111, + 99, + 116, + 101, + 116, + 45, + 115, + 116, + 114, + 101, + 97, + 109, + 59, + 98, + 97, + 115, + 101, + 54, + 52, + 44, + 85, + 107, + 108, + 71, + 82, + 107, + 65, + 112, + 65, + 65, + 66, + 88, + 82, + 85, + 74, + 81, + 86, + 108, + 65, + 52, + 87, + 65, + 111, + 65, + 65, + 65, + 65, + 69, + 65, + 65, + 65, + 65, + 69, + 81 ], - "body_suffix": [123, 10, 32, 32, 34, 97, 114, 103, 115, 34, 58, 32] + "body_suffix": [ + 123, + 10, + 32, + 32, + 34, + 97, + 114, + 103, + 115, + 34, + 58, + 32 + ] } }, "GET /response/ip-port-undefined": {}, @@ -1203,10 +1693,14 @@ "GET /setTimeout/200-ms": { "downstream_response": { "status": 200, - "body": ["START\nEND\n"] + "body": [ + "START\nEND\n" + ] } }, - "GET /setTimeout/fetch-timeout": { "flake": true }, + "GET /setTimeout/fetch-timeout": { + "flake": true + }, "GET /clearInterval/exposed-as-global": {}, "GET /clearInterval/interface": {}, "GET /clearInterval/called-as-constructor-function": {}, @@ -1244,7 +1738,10 @@ "downstream_request": { "method": "POST", "pathname": "/tee", - "headers": ["Content-Type", "application/json"], + "headers": [ + "Content-Type", + "application/json" + ], "body": "hello world!" }, "downstream_response": { @@ -1256,7 +1753,10 @@ "downstream_request": { "method": "GET", "pathname": "/tee/error", - "headers": ["Content-Type", "application/json"] + "headers": [ + "Content-Type", + "application/json" + ] }, "downstream_response": { "status": 200, @@ -1264,42 +1764,54 @@ } }, "GET /override-content-length/request/init/object-literal/true": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /override-content-length/request/init/object-literal/false": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /override-content-length/request/clone/true": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /override-content-length/request/clone/false": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /override-content-length/fetch/init/object-literal/true": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /override-content-length/fetch/init/object-literal/false": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "ok" @@ -1307,56 +1819,85 @@ }, "/override-content-length/response/clone/true": { "skip": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "/override-content-length/response/clone/false": { "skip": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /override-content-length/response/init/object-literal/true": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "meow", - "headers": { "content-length": "4" } + "headers": { + "content-length": "4" + } } }, "GET /override-content-length/response/init/object-literal/false": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "meow" } }, "GET /override-content-length/response/init/response-instance/true": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "meow", - "headers": { "content-length": "4" } + "headers": { + "content-length": "4" + } } }, "GET /override-content-length/response/init/response-instance/false": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "meow" } }, "GET /override-content-length/response/method/false": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /override-content-length/response/method/true": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, - "headers": { "content-length": "11" } + "headers": { + "content-length": "11" + } } }, "GET /headers/getsetcookie": { "downstream_response": { "status": 200, - "headers": [["name1=value1"], ["name2=value2"]] + "headers": [ + [ + "name1=value1" + ], + [ + "name2=value2" + ] + ] } }, "GET /headers/construct": { @@ -1371,948 +1912,1426 @@ "flake": true, "downstream_response": { "status": 200, - "headers": [["cuStom", "test"]] + "headers": [ + [ + "cuStom", + "test" + ] + ] } }, - "GET /headers/from-response/delete-invalid": { "flake": true }, + "GET /headers/from-response/delete-invalid": { + "flake": true + }, "GET /headers/from-response/set-delete": { "flake": true, "downstream_response": { "status": 200, - "headers": [["cuStom", "test"]] + "headers": [ + [ + "cuStom", + "test" + ] + ] } }, "GET /FastlyBody/interface": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/constructor/called-as-regular-function": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/constructor/called-as-constructor": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/called-as-constructor": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-not-supplied": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-wrong-type": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-readablestream-guest-backed": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-readablestream-host-backed": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-URLSearchParams": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-strings": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-calls-7.1.17-ToString": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-buffer": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-arraybuffer": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-typed-arrays": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-dataview": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/called-as-constructor": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-not-supplied": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-wrong-type": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-readablestream-guest-backed": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-readablestream-host-backed": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-readablestream-locked": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-URLSearchParams": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-strings": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-calls-7.1.17-ToString": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-buffer": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-arraybuffer": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-typed-arrays": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-dataview": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/concat/called-as-constructor": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/concat/dest-parameter-not-supplied": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/concat/dest-parameter-wrong-type": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/concat/concat-same-fastlybody-twice": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/concat/happy-path": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/called-as-constructor": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/chunkSize-parameter-not-supplied": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/chunkSize-parameter-wrong-type": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/chunkSize-parameter-negative": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/chunkSize-parameter-infinity": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/chunkSize-parameter-NaN": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/happy-path": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/close/called-as-constructor": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/close/called-once": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/close/called-twice": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/interface": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/constructor/called-as-regular-function": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/constructor/throws": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/called-as-constructor": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-parameter-calls-7.1.17-ToString": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-parameter-not-supplied": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-parameter-empty-string": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-parameter-8135-character-string": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-parameter-8136-character-string": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-does-not-exist-returns-null": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-exists": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/options-parameter-wrong-type": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/options-parameter-none": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/lookup/options-parameter-headers-field-wrong-type": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/lookup/options-parameter-headers-field-undefined": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/lookup/options-parameter-headers-field-valid-sequence": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/lookup/options-parameter-headers-field-valid-record": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/lookup/options-parameter-headers-field-valid-Headers-instance": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/key-parameter-calls-7.1.17-ToString": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/key-parameter-not-supplied": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/key-parameter-empty-string": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/key-parameter-8135-character-string": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/key-parameter-8136-character-string": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-wrong-type": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-headers-field-wrong-type": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-headers-field-undefined": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-headers-field-valid-sequence": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-headers-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-headers-field-valid-Headers-instance": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-maxAge-field-valid-record": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-maxAge-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-maxAge-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-maxAge-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-maxAge-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-initialAge-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-initialAge-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-initialAge-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-initialAge-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-initialAge-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-staleWhileRevalidate-field-valid-record": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-staleWhileRevalidate-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-staleWhileRevalidate-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-staleWhileRevalidate-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-staleWhileRevalidate-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-length-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-length-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-length-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-length-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-length-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-sensitive-field": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-vary-field": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-userMetadata-field/arraybuffer/empty": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-userMetadata-field/arraybuffer/not-empty": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-userMetadata-field/URLSearchParams": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-userMetadata-field/string": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/key-parameter-calls-7.1.17-ToString": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/key-parameter-not-supplied": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/key-parameter-empty-string": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/key-parameter-8135-character-string": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/key-parameter-8136-character-string": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/key-does-not-exist": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/key-exists": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/options-parameter-wrong-type": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/options-parameter-headers-field-wrong-type": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/options-parameter-headers-field-undefined": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/options-parameter-headers-field-valid-sequence": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/options-parameter-headers-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/options-parameter-headers-field-valid-Headers-instance": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/interface": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/constructor/called-as-regular-function": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/constructor/throws": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/close/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/close/called-unbound": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/close/called-on-instance": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/state/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/state/called-unbound": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/state/called-on-instance": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/userMetadata/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/userMetadata/called-unbound": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/userMetadata/called-on-instance": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/userMetadata/basic": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/called-unbound": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/called-on-instance": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/options-start-negative": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/options-start-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/options-start-Infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/options-start-valid": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/options-start-longer-than-body": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/options-end-negative": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/options-end-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/options-end-Infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/options-end-valid": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/options-end-zero": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/length/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/length/called-unbound": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/length/called-on-instance": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/maxAge/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/maxAge/called-unbound": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/maxAge/called-on-instance": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/staleWhileRevalidate/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/staleWhileRevalidate/called-unbound": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/staleWhileRevalidate/called-on-instance": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/age/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/age/called-unbound": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/age/called-on-instance": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/hits/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/hits/called-unbound": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/hits/called-on-instance": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/interface": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/entry-parameter-not-supplied": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-maxAge-field-valid-record": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-maxAge-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-maxAge-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-maxAge-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-maxAge-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-initialAge-field-valid-record": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-initialAge-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-initialAge-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-initialAge-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-initialAge-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-staleWhileRevalidate-field-valid-record": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-staleWhileRevalidate-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-staleWhileRevalidate-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-staleWhileRevalidate-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-staleWhileRevalidate-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-length-field-valid-record": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-length-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-length-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-length-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-length-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-sensitive-field": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-vary-field": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/entry-parameter-not-supplied": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-maxAge-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-maxAge-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-maxAge-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-maxAge-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-maxAge-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-initialAge-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-initialAge-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-initialAge-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-initialAge-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-initialAge-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-staleWhileRevalidate-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-staleWhileRevalidate-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-staleWhileRevalidate-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-staleWhileRevalidate-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-staleWhileRevalidate-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-length-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-length-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-length-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-length-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-length-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-sensitive-field": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/write-to-writer-and-read-from-reader": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-vary-field": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/entry-parameter-not-supplied": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-maxAge-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-maxAge-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-maxAge-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-maxAge-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-maxAge-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-initialAge-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-initialAge-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-initialAge-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-initialAge-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-initialAge-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-staleWhileRevalidate-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-staleWhileRevalidate-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-staleWhileRevalidate-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-staleWhileRevalidate-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-staleWhileRevalidate-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-length-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-length-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-length-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-length-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-length-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/write-to-writer-and-read-from-reader": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-vary-field": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-userMetadata-field": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/cancel/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/cancel/called-once": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/cancel/makes-entry-cancelled": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/cancel/called-twice-throws": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /rate-counter/interface": { "downstream_response": { @@ -2765,28 +3784,36 @@ } }, "GET /device/lookup/useragent-does-not-exist-returns-null": { - "environments": ["viceroy"], + "environments": [ + "viceroy" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /device/lookup/useragent-exists-all-fields-identified": { - "environments": ["viceroy"], + "environments": [ + "viceroy" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /device/lookup/useragent-exists-all-fields-identified-tojson": { - "environments": ["viceroy"], + "environments": [ + "viceroy" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /device/lookup/useragent-exists-some-fields-identified": { - "environments": ["viceroy"], + "environments": [ + "viceroy" + ], "downstream_response": { "status": 200, "body": "ok" @@ -2800,14 +3827,18 @@ }, "GET /core-cache/transaction-lookup-transaction-insert-vary-works": { "flake": true, - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /core-cache/lookup-insert-vary-works": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "ok" @@ -2817,147 +3848,252 @@ "downstream_response": { "status": 200, "body": "", - "headers": [["result", "GET"]] + "headers": [ + [ + "result", + "GET" + ] + ] } }, "get /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "GET"]] + "headers": [ + [ + "result", + "GET" + ] + ] } }, "GeT /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "GET"]] + "headers": [ + [ + "result", + "GET" + ] + ] } }, "HEAD /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "HEAD"]] + "headers": [ + [ + "result", + "HEAD" + ] + ] } }, "head /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "HEAD"]] + "headers": [ + [ + "result", + "HEAD" + ] + ] } }, "HeAd /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "HEAD"]] + "headers": [ + [ + "result", + "HEAD" + ] + ] } }, "OPTIONS /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "OPTIONS"]] + "headers": [ + [ + "result", + "OPTIONS" + ] + ] } }, "OPTioNS /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "OPTIONS"]] + "headers": [ + [ + "result", + "OPTIONS" + ] + ] } }, "options /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "OPTIONS"]] + "headers": [ + [ + "result", + "OPTIONS" + ] + ] } }, "POST /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "POST"]] + "headers": [ + [ + "result", + "POST" + ] + ] } }, "post /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "POST"]] + "headers": [ + [ + "result", + "POST" + ] + ] } }, "PosT /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "POST"]] + "headers": [ + [ + "result", + "POST" + ] + ] } }, "PUT /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "PUT"]] + "headers": [ + [ + "result", + "PUT" + ] + ] } }, "put /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "PUT"]] + "headers": [ + [ + "result", + "PUT" + ] + ] } }, "Put /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "PUT"]] + "headers": [ + [ + "result", + "PUT" + ] + ] } }, "DELETE /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "DELETE"]] + "headers": [ + [ + "result", + "DELETE" + ] + ] } }, "DeLeTe /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "DELETE"]] + "headers": [ + [ + "result", + "DELETE" + ] + ] } }, "delete /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "DELETE"]] + "headers": [ + [ + "result", + "DELETE" + ] + ] } }, "HELLO /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "HELLO"]] + "headers": [ + [ + "result", + "HELLO" + ] + ] } }, "hello /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "hello"]] + "headers": [ + [ + "result", + "hello" + ] + ] } }, "heLLo /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "heLLo"]] + "headers": [ + [ + "result", + "heLLo" + ] + ] } }, "!*+-.^_`|~1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ /request/method/host": { @@ -2980,7 +4116,9 @@ }, "GET /compute/get-vcpu-ms": { "flake": true, - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 @@ -2988,10 +4126,14 @@ }, "GET /compute/purge-surrogate-key-invalid": {}, "GET /compute/purge-surrogate-key-hard": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /compute/purge-surrogate-key-soft": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /html-rewriter/set-attribute": {}, "GET /html-rewriter/get-attribute": {}, @@ -3007,5 +4149,6 @@ "GET /html-rewriter/throw-in-handler": {}, "GET /html-rewriter/invalid-html": {}, "GET /html-rewriter/insertion-order": {}, - "GET /html-rewriter/escape-html": {} -} + "GET /html-rewriter/escape-html": {}, + "GET /image-optimizer/test": {} +} \ No newline at end of file diff --git a/runtime/fastly/CMakeLists.txt b/runtime/fastly/CMakeLists.txt index 3bed3fc4c5..877085f8c2 100644 --- a/runtime/fastly/CMakeLists.txt +++ b/runtime/fastly/CMakeLists.txt @@ -28,6 +28,7 @@ add_builtin(fastly::dictionary SRC builtins/dictionary.cpp) add_builtin(fastly::edge_rate_limiter SRC builtins/edge-rate-limiter.cpp) add_builtin(fastly::config_store SRC builtins/config-store.cpp) add_builtin(fastly::secret_store SRC builtins/secret-store.cpp) +add_builtin(fastly::image_optimizer SRC builtins/image-optimizer.cpp) add_builtin(fastly::fetch SRC diff --git a/runtime/fastly/builtins/fetch/fetch.cpp b/runtime/fastly/builtins/fetch/fetch.cpp index cb3d6588fe..d1858ee477 100644 --- a/runtime/fastly/builtins/fetch/fetch.cpp +++ b/runtime/fastly/builtins/fetch/fetch.cpp @@ -7,10 +7,12 @@ #include "../cache-override.h" #include "../fastly.h" #include "../fetch-event.h" +#include "../image-optimizer.h" #include "./request-response.h" #include "builtin.h" #include "encode.h" #include "extension-api.h" +#include using builtins::web::streams::NativeStreamSink; using builtins::web::streams::NativeStreamSource; @@ -119,26 +121,9 @@ bool must_use_guest_caching(JSContext *cx, HandleObject request) { } bool http_caching_unsupported = false; -bool should_use_guest_caching(JSContext *cx, HandleObject request, bool *should_use_cache) { - *should_use_cache = true; - - // If we previously found guest caching unsupported then remember that - if (http_caching_unsupported || !fastly::fastly::ENABLE_EXPERIMENTAL_HTTP_CACHE) { - if (must_use_guest_caching(cx, request)) { - if (!fastly::fastly::ENABLE_EXPERIMENTAL_HTTP_CACHE) { - JS_ReportErrorASCII(cx, "HTTP caching API is not enabled for JavaScript; enable it with " - "the --enable-http-cache flag " - "to the js-compute build command, or contact support for help"); - } else { - JS_ReportErrorASCII( - cx, - "HTTP caching API is not enabled for this service; please contact support for help"); - } - return false; - } - *should_use_cache = false; - return true; - } +enum CachingMode { Guest, Host, ImageOptimizer }; +bool get_caching_mode(JSContext *cx, HandleObject request, CachingMode *caching_mode) { + *caching_mode = CachingMode::Guest; // Check for pass cache override MOZ_ASSERT(Request::is_instance(request)); @@ -148,7 +133,7 @@ bool should_use_guest_caching(JSContext *cx, HandleObject request, bool *should_ if (cache_override) { if (CacheOverride::mode(cache_override) == CacheOverride::CacheOverrideMode::Pass) { // Pass requests have to go through the host for now - *should_use_cache = false; + *caching_mode = CachingMode::Host; return true; } } @@ -161,7 +146,38 @@ bool should_use_guest_caching(JSContext *cx, HandleObject request, bool *should_ } if (is_purge) { // We don't yet implement guest-side URL purges - *should_use_cache = false; + *caching_mode = CachingMode::Host; + return true; + } + + // Requests meant for Image Optimizer should not be cached at this point, + // as the caching behavior is determined by the origin image, which is + // fetched after the request reaches the Image Optimizer WASM service. + // The WASM service uses cache_on_behalf to insert the result into + // the service's cache. + auto image_optimizer_opts = + JS::GetReservedSlot(request, static_cast(Request::Slots::ImageOptimizerOptions)) + .toPrivate(); + if (image_optimizer_opts) { + *caching_mode = CachingMode::ImageOptimizer; + return true; + } + + // If we previously found guest caching unsupported then remember that + if (http_caching_unsupported || !fastly::fastly::ENABLE_EXPERIMENTAL_HTTP_CACHE) { + if (must_use_guest_caching(cx, request)) { + if (!fastly::fastly::ENABLE_EXPERIMENTAL_HTTP_CACHE) { + JS_ReportErrorASCII(cx, "HTTP caching API is not enabled for JavaScript; enable it with " + "the --enable-http-cache flag " + "to the js-compute build command, or contact support for help"); + } else { + JS_ReportErrorASCII( + cx, + "HTTP caching API is not enabled for this service; please contact support for help"); + } + return false; + } + *caching_mode = CachingMode::Host; return true; } @@ -177,7 +193,7 @@ bool should_use_guest_caching(JSContext *cx, HandleObject request, bool *should_ JS_ReportErrorASCII(cx, "HTTP caching API is not enabled; please contact support for help"); return false; } - *should_use_cache = false; + *caching_mode = CachingMode::Host; return true; } HANDLE_ERROR(cx, *err); @@ -188,8 +204,7 @@ bool should_use_guest_caching(JSContext *cx, HandleObject request, bool *should_ } // Sends the request body, resolving the response promise with the response -// The without_caching case is effectively pass semantics without cache hooks -template +template bool fetch_send_body(JSContext *cx, HandleObject request, JS::MutableHandleValue ret) { RootedString backend(cx, get_backend(cx, request)); if (!backend) { @@ -216,7 +231,7 @@ bool fetch_send_body(JSContext *cx, HandleObject request, JS::MutableHandleValue } // cache override only applies to requests with caching - if (!without_caching) { + if (caching_mode == CachingMode::Guest) { if (!Request::apply_cache_override(cx, request)) { return false; } @@ -236,8 +251,10 @@ bool fetch_send_body(JSContext *cx, HandleObject request, JS::MutableHandleValue return false; } + // The image optimizer does not support streaming, so never stream in this case bool streaming = false; - if (!RequestOrResponse::maybe_stream_body(cx, request, &streaming)) { + if (caching_mode != CachingMode::ImageOptimizer && + !RequestOrResponse::maybe_stream_body(cx, request, &streaming)) { return false; } @@ -245,10 +262,42 @@ bool fetch_send_body(JSContext *cx, HandleObject request, JS::MutableHandleValue { auto request_handle = Request::request_handle(request); auto body = RequestOrResponse::body_handle(request); - auto res = !without_caching - ? streaming ? request_handle.send_async_streaming(body, backend_chars) - : request_handle.send_async(body, backend_chars) - : request_handle.send_async_without_caching(body, backend_chars, streaming); + host_api::Result res; + switch (caching_mode) { + case CachingMode::Guest: { + if (streaming) { + res = request_handle.send_async_streaming(body, backend_chars); + } else { + res = request_handle.send_async(body, backend_chars); + } + break; + } + case CachingMode::Host: + res = request_handle.send_async_without_caching(body, backend_chars, streaming); + break; + case CachingMode::ImageOptimizer: { + auto config = reinterpret_cast( + JS::GetReservedSlot(request, static_cast(Request::Slots::ImageOptimizerOptions)) + .toPrivate()); + auto config_str = config->to_string(); + std::cerr << "sending config to image optimizer: " << config_str << std::endl; + auto res = request_handle.send_image_optimizer(body, backend_chars, config_str); + + if (auto *err = res.to_err()) { + HANDLE_IMAGE_OPTIMIZER_ERROR(cx, *err); + ret.setObject(*PromiseRejectedWithPendingError(cx)); + return true; + } + + JS::RootedObject response(cx, Response::create(cx, request, res.unwrap())); + JS::RootedValue response_val(cx, JS::ObjectValue(*response)); + if (!JS::ResolvePromise(cx, response_promise, response_val)) { + return false; + } + ret.setObject(*response_promise); + return true; + } + } if (auto *err = res.to_err()) { if (host_api::error_is_generic(*err) || host_api::error_is_invalid_argument(*err)) { @@ -1075,13 +1124,15 @@ bool fetch(JSContext *cx, unsigned argc, Value *vp) { } // Determine if we should use guest-side caching - bool should_use_guest_caching_out; - if (!should_use_guest_caching(cx, request, &should_use_guest_caching_out)) { + CachingMode caching_mode; + if (!get_caching_mode(cx, request, &caching_mode)) { return false; } - if (!should_use_guest_caching_out) { + if (caching_mode == CachingMode::Host) { DEBUG_LOG("HTTP Cache: Using traditional fetch without cache API") - return fetch_send_body(cx, request, args.rval()); + return fetch_send_body(cx, request, args.rval()); + } else if (caching_mode == CachingMode::ImageOptimizer) { + return fetch_send_body(cx, request, args.rval()); } // Check if request is actually cacheable @@ -1104,7 +1155,7 @@ bool fetch(JSContext *cx, unsigned argc, Value *vp) { // If not cacheable, fallback to non-caching path if (!is_cacheable) { DEBUG_LOG("HTTP Cache: Request not cacheable, using non-caching fetch") - return fetch_send_body(cx, request, args.rval()); + return fetch_send_body(cx, request, args.rval()); } // Lookup in cache @@ -1224,7 +1275,7 @@ bool fetch(JSContext *cx, unsigned argc, Value *vp) { } // request collapsing has been disabled: pass the original request to the origin without // updating the cache and without caching - return fetch_send_body(cx, request, args.rval()); + return fetch_send_body(cx, request, args.rval()); } else { JS::RootedValue stream_back_promise(cx, JS::ObjectValue(*JS::NewPromiseObject(cx, nullptr))); if (!fetch_send_body_with_cache_hooks(cx, request, cache_entry, &stream_back_promise)) { diff --git a/runtime/fastly/builtins/fetch/request-response.cpp b/runtime/fastly/builtins/fetch/request-response.cpp index f66ca2b4b1..865ad35f29 100644 --- a/runtime/fastly/builtins/fetch/request-response.cpp +++ b/runtime/fastly/builtins/fetch/request-response.cpp @@ -21,6 +21,7 @@ #include "../cache-simple.h" #include "../fastly.h" #include "../fetch-event.h" +#include "../image-optimizer.h" #include "../kv-store.h" #include "extension-api.h" #include "fetch.h" @@ -1989,6 +1990,19 @@ bool Request::set_cache_key(JSContext *cx, JS::HandleObject self, JS::HandleValu return true; } +bool Request::set_image_optimizer_options(JSContext *cx, JS::HandleObject self, + JS::HandleValue opts_val) { + MOZ_ASSERT(is_instance(self)); + + auto opts = image_optimizer::ImageOptimizerOptions::create(cx, opts_val); + if (!opts) { + return false; + } + JS::SetReservedSlot(self, static_cast(Request::Slots::ImageOptimizerOptions), + JS::PrivateValue(opts.release())); + return true; +} + bool Request::set_cache_override(JSContext *cx, JS::HandleObject self, JS::HandleValue cache_override) { MOZ_ASSERT(is_instance(self)); @@ -2331,6 +2345,17 @@ bool Request::clone(JSContext *cx, unsigned argc, JS::Value *vp) { cache_override); } + JS::RootedValue image_optimizer_options( + cx, JS::GetReservedSlot(self, static_cast(Slots::ImageOptimizerOptions))); + if (!image_optimizer_options.isNullOrUndefined()) { + if (!set_image_optimizer_options(cx, requestInstance, image_optimizer_options)) { + return false; + } + } else { + JS::SetReservedSlot(requestInstance, static_cast(Slots::ImageOptimizerOptions), + image_optimizer_options); + } + args.rval().setObject(*requestInstance); return true; } @@ -2399,6 +2424,8 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::NullValue()); JS::SetReservedSlot(requestInstance, static_cast(Slots::CacheOverride), JS::NullValue()); + JS::SetReservedSlot(requestInstance, static_cast(Slots::ImageOptimizerOptions), + JS::NullValue()); JS::SetReservedSlot(requestInstance, static_cast(Slots::IsDownstream), JS::BooleanValue(is_downstream)); return requestInstance; @@ -2568,6 +2595,7 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::H JS::RootedValue cache_override(cx); JS::RootedValue cache_key(cx); JS::RootedValue fastly_val(cx); + JS::RootedValue image_optimizer_options(cx); bool hasManualFramingHeaders = false; bool setManualFramingHeaders = false; if (init_val.isObject()) { @@ -2581,7 +2609,8 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::H !JS_GetProperty(cx, init, "cacheKey", &cache_key) || !JS_GetProperty(cx, init, "fastly", &fastly_val) || !JS_HasOwnProperty(cx, init, "manualFramingHeaders", &hasManualFramingHeaders) || - !JS_GetProperty(cx, init, "manualFramingHeaders", &manualFramingHeaders)) { + !JS_GetProperty(cx, init, "manualFramingHeaders", &manualFramingHeaders) || + !JS_GetProperty(cx, init, "imageOptimizerOptions", &image_optimizer_options)) { return nullptr; } setManualFramingHeaders = manualFramingHeaders.isBoolean() && manualFramingHeaders.toBoolean(); @@ -2871,6 +2900,13 @@ JSObject *Request::create(JSContext *cx, JS::HandleObject requestInstance, JS::H } } + // Apply the Fastly Compute-proprietary `imageOptimizerOptions` property. + if (!image_optimizer_options.isUndefined()) { + if (!set_image_optimizer_options(cx, request, image_optimizer_options)) { + return nullptr; + } + } + if (fastly_val.isObject()) { JS::RootedValue decompress_response_val(cx); JS::RootedObject fastly(cx, fastly_val.toObjectOrNull()); diff --git a/runtime/fastly/builtins/fetch/request-response.h b/runtime/fastly/builtins/fetch/request-response.h index f4da11e657..9e30b2f9f4 100644 --- a/runtime/fastly/builtins/fetch/request-response.h +++ b/runtime/fastly/builtins/fetch/request-response.h @@ -179,6 +179,7 @@ class Request final : public builtins::BuiltinImpl { ResponsePromise, IsDownstream, AutoDecompressGzip, + ImageOptimizerOptions, Count, }; @@ -195,6 +196,8 @@ class Request final : public builtins::BuiltinImpl { JS::HandleValue cache_override_val); static bool apply_cache_override(JSContext *cx, JS::HandleObject self); static bool apply_auto_decompress_gzip(JSContext *cx, JS::HandleObject self); + static bool set_image_optimizer_options(JSContext *cx, JS::HandleObject self, + JS::HandleValue image_optimizer_options); static bool isCacheable_get(JSContext *cx, unsigned argc, JS::Value *vp); static host_api::HttpReq request_handle(JSObject *obj); diff --git a/runtime/fastly/builtins/image-optimizer-options.inc b/runtime/fastly/builtins/image-optimizer-options.inc new file mode 100644 index 0000000000..3409f76c1e --- /dev/null +++ b/runtime/fastly/builtins/image-optimizer-options.inc @@ -0,0 +1,51 @@ +#ifndef FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION +#error "Must define the FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION macro" +#endif +#ifndef FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE +#error "Must define the FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE macro" +#endif +#ifndef FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE +#error "Must define the FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE macro" +#endif + +FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(Region, region) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(UsEast, "us_east", 1) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(UsCentral, "us_central", 2) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(UsWest, "us_west", 3) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(EuCentral, "eu_central", 4) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(EuWest, "eu_west", 5) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Asia, "asia", 6) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Australia, "australia", 7) +FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(Region) + +FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(Auto, auto) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(AVIF, "avif", 1) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(WEBP, "webp", 2) +FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(Auto) + +FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(Format, format) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Auto, "auto", 1) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(AVIF, "avif", 2) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(BJPG, "bjpg", 3) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(GIF, "gif", 4) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(JPG, "jpg", 5) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(JXL, "jxl", 6) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(MP4, "mp4", 7) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(PJPG, "pjpg", 8) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(PJXL, "pjxl", 9) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(PNG, "png", 10) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(PNG8, "png8", 11) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(SVG, "svg", 12) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(WEBP, "webp", 13) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(WEBPLL, "webpll", 14) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(WEBPLY, "webply", 15) +FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(Format) + +FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(BWAlgorithm, bw) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Threshold, "threshold", 1) +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Atkinson, "atkinson", 2) +FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(Auto) + +#undef FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION +#undef FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE +#undef FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE \ No newline at end of file diff --git a/runtime/fastly/builtins/image-optimizer.cpp b/runtime/fastly/builtins/image-optimizer.cpp new file mode 100644 index 0000000000..d748af639d --- /dev/null +++ b/runtime/fastly/builtins/image-optimizer.cpp @@ -0,0 +1,156 @@ +#include "image-optimizer.h" +#include +namespace fastly::image_optimizer { +const JSFunctionSpec EnumOption::static_methods[] = { + JS_FS_END, +}; +const JSFunctionSpec EnumOption::methods[] = { + JS_FS_END, +}; + +const JSPropertySpec EnumOption::properties[] = { + JS_PS_END, +}; + +#define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase) \ + const JSPropertySpec type::static_properties[] = { +#define FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(name, str, value) \ + JS_STRING_PS(#name, str, JSPROP_READONLY), +#define FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(type) \ + JS_PS_END, \ + } \ + ; +#include "image-optimizer-options.inc" + +bool install(api::Engine *engine) { + RootedObject image_optimizer_ns(engine->cx(), JS_NewObject(engine->cx(), nullptr)); + +#define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase) \ + if (!type::init_class_impl(engine->cx(), engine->global())) { \ + return false; \ + } \ + RootedObject lowercase##_obj( \ + engine->cx(), \ + JS_GetConstructor( \ + engine->cx(), \ + builtins::BuiltinNoConstructor::proto_obj)); \ + RootedValue lowercase##_val(engine->cx(), ObjectValue(*lowercase##_obj)); \ + if (!JS_SetProperty(engine->cx(), image_optimizer_ns, #type, lowercase##_val)) { \ + return false; \ + } +#define FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(name, str, value) +#define FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(type) +#include "image-optimizer-options.inc" + + RootedValue image_optimizer_ns_val(engine->cx(), JS::ObjectValue(*image_optimizer_ns)); + if (!engine->define_builtin_module("fastly:image-optimizer", image_optimizer_ns_val)) { + return false; + } + return true; +} // namespace fastly::image_optimizer + +std::unique_ptr ImageOptimizerOptions::create(JSContext *cx, + JS::HandleValue opts_val) { + // Extract properties. `region` is the only mandatory property + JS::RootedValue region_val(cx); + JS::RootedValue preserve_query_string_on_origin_request_val(cx); + JS::RootedValue auto_val(cx); + JS::RootedValue bg_color_val(cx); + JS::RootedValue blur_val(cx); + JS::RootedValue brightness_val(cx); + JS::RootedValue bw_val(cx); + JS::RootedValue canvas_val(cx); + JS::RootedValue contrast_val(cx); + JS::RootedValue crop_val(cx); + JS::RootedValue dpr_val(cx); + JS::RootedValue enable_val(cx); + JS::RootedValue fit_val(cx); + JS::RootedValue format_val(cx); + JS::RootedValue frame_val(cx); + JS::RootedValue height_val(cx); + JS::RootedValue level_val(cx); + JS::RootedValue metadata_val(cx); + JS::RootedValue optimize_val(cx); + JS::RootedValue orient_val(cx); + JS::RootedValue pad_val(cx); + JS::RootedValue precrop_val(cx); + JS::RootedValue profile_val(cx); + JS::RootedValue quality_val(cx); + JS::RootedValue resize_filter_val(cx); + JS::RootedValue saturation_val(cx); + JS::RootedValue sharpen_val(cx); + JS::RootedValue trim_val(cx); + JS::RootedValue trim_color_val(cx); + JS::RootedValue width_val(cx); + JS::RootedObject opts(cx, opts_val.toObjectOrNull()); + if (!JS_GetProperty(cx, opts, "region", ®ion_val) || + !JS_GetProperty(cx, opts, "preserve_query_string_on_origin_request", + &preserve_query_string_on_origin_request_val) || + !JS_GetProperty(cx, opts, "auto", &auto_val) || + !JS_GetProperty(cx, opts, "bgColor", &bg_color_val) || + !JS_GetProperty(cx, opts, "blur", &blur_val) || + !JS_GetProperty(cx, opts, "brightness", &brightness_val) || + !JS_GetProperty(cx, opts, "bw", &bw_val) || + !JS_GetProperty(cx, opts, "canvas", &canvas_val) || + !JS_GetProperty(cx, opts, "contrast", &contrast_val) || + !JS_GetProperty(cx, opts, "crop", &crop_val) || !JS_GetProperty(cx, opts, "dpr", &dpr_val) || + !JS_GetProperty(cx, opts, "enable", &enable_val) || + !JS_GetProperty(cx, opts, "fit", &fit_val) || + !JS_GetProperty(cx, opts, "format", &format_val) || + !JS_GetProperty(cx, opts, "frame", &frame_val) || + !JS_GetProperty(cx, opts, "height", &height_val) || + !JS_GetProperty(cx, opts, "level", &level_val) || + !JS_GetProperty(cx, opts, "metadata", &metadata_val) || + !JS_GetProperty(cx, opts, "optimize", &optimize_val) || + !JS_GetProperty(cx, opts, "orient", &orient_val) || + !JS_GetProperty(cx, opts, "pad", &pad_val) || + !JS_GetProperty(cx, opts, "precrop", &precrop_val) || + !JS_GetProperty(cx, opts, "profile", &profile_val) || + !JS_GetProperty(cx, opts, "quality", &quality_val) || + !JS_GetProperty(cx, opts, "resizeFilter", &resize_filter_val) || + !JS_GetProperty(cx, opts, "saturation", &saturation_val) || + !JS_GetProperty(cx, opts, "sharpen", &sharpen_val) || + !JS_GetProperty(cx, opts, "trim", &trim_val) || + !JS_GetProperty(cx, opts, "trimColor", &trim_color_val) || + !JS_GetProperty(cx, opts, "width", &width_val)) { + return nullptr; + } + + if (region_val.isUndefined()) { + api::throw_error(cx, api::Errors::TypeError, "Request", "imageOptimizerOptions", + "contain region"); + return nullptr; + } + auto region = to_region(cx, region_val); + if (!region) { + return nullptr; + } + + auto auto_ = to_auto(cx, auto_val); + auto format = to_format(cx, format_val); + + return std::unique_ptr{ + new ImageOptimizerOptions(region.value(), auto_, format)}; +} + +std::string ImageOptimizerOptions::to_string() const { + using image_optimizer::to_string; + std::string ret = to_string(region_); + auto append = [&ret](auto &&v) { + if (v) + ret += "&" + to_string(*v); + }; + append(auto_); + append(format_); + return ret; +} + +fastly_image_optimizer_transform_config ImageOptimizerOptions::to_config() { + fastly_image_optimizer_transform_config config; + auto str = this->to_string(); + host_api::HostString host_str(str); + config.sdk_claims_opts = host_str.ptr.release(); + config.sdk_claims_opts_len = host_str.len; + return config; +} +} // namespace fastly::image_optimizer \ No newline at end of file diff --git a/runtime/fastly/builtins/image-optimizer.h b/runtime/fastly/builtins/image-optimizer.h new file mode 100644 index 0000000000..190dfca265 --- /dev/null +++ b/runtime/fastly/builtins/image-optimizer.h @@ -0,0 +1,89 @@ +#ifndef FASTLY_IMAGE_OPTIMIZER_H +#define FASTLY_IMAGE_OPTIMIZER_H + +#include "../host-api/host_api_fastly.h" +#include "builtin.h" +#include "encode.h" +#include "extension-api.h" +#include + +#define HANDLE_IMAGE_OPTIMIZER_ERROR(cx, err) \ + ::host_api::handle_image_optimizer_error(cx, err, __LINE__, __func__) + +namespace fastly::image_optimizer { +class EnumOption { +public: + enum Slots { Count }; + static const JSFunctionSpec static_methods[]; + static const JSFunctionSpec methods[]; + static const JSPropertySpec properties[]; +}; +#define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase) \ + class type : public EnumOption, public builtins::BuiltinNoConstructor { \ + public: \ + static const JSPropertySpec static_properties[]; \ + static constexpr const char *class_name = #type; \ + }; +#define FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(name, str, value) +#define FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(type) +#include "image-optimizer-options.inc" + +class ImageOptimizerOptions { +public: + fastly_image_optimizer_transform_config to_config(); + static std::unique_ptr create(JSContext *cx, JS::HandleValue opts_val); + std::string to_string() const; + +#define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase) enum class type { +#define FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(name, str, value) name = value, +#define FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(type) \ + } \ + ; +#include "image-optimizer-options.inc" + +#define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase) \ + static std::optional to_##lowercase(JSContext *cx, JS::HandleValue val) { \ + if (val.isUndefined()) \ + return std::nullopt; \ + if (!val.isString()) { \ + api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", #type, \ + "must be a string"); \ + return std::nullopt; \ + } \ + JS::RootedString str_val(cx, val.toString()); \ + using enum type; +#define FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(name, str, value) \ + if (core::encode(cx, str_val) == std::string_view(str)) { \ + return name; \ + } +#define FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(type) \ + JS_ReportErrorUTF8(cx, #type " out of range"); \ + return std::nullopt; \ + } +#include "image-optimizer-options.inc" + +private: + ImageOptimizerOptions(Region region, std::optional auto_val, std::optional format) + : region_(region), auto_(auto_val), format_(format) {} + Region region_; + std::optional auto_; + std::optional format_; +}; + +inline std::string to_string(const ImageOptimizerOptions &opts) { return opts.to_string(); } + +#define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase) \ + inline std::string to_string(ImageOptimizerOptions::type val) { \ + using enum ImageOptimizerOptions::type; \ + std::string prefix = #lowercase; \ + switch (val) { +#define FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(name, str, value) \ + case name: \ + return prefix + '=' + str; +#define FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(type) \ + } \ + } +#include "image-optimizer-options.inc" + +} // namespace fastly::image_optimizer +#endif \ No newline at end of file diff --git a/runtime/fastly/host-api/fastly.h b/runtime/fastly/host-api/fastly.h index b0d0cb28c6..fe70720431 100644 --- a/runtime/fastly/host-api/fastly.h +++ b/runtime/fastly/host-api/fastly.h @@ -1103,6 +1103,32 @@ WASM_IMPORT("fastly_acl", "lookup") int acl_lookup(uint32_t acl_handle, const uint8_t *ip_octets, size_t ip_len, uint32_t *body_handle_out, fastly_acl_error *acl_error_out); +typedef struct __attribute__((aligned(4))) fastly_image_optimizer_transform_config { + const char *sdk_claims_opts; + size_t sdk_claims_opts_len; +} fastly_image_optimizer_transform_config; + +#define FASTLY_IMAGE_OPTIMIZER_RESERVED (1u << 0) +#define FASTLY_IMAGE_OPTIMIZER_SDK_CLAIMS_OPTS (1u << 1) + +#define FASTLY_IMAGE_OPTIMIZER_ERROR_TAG_UNINITIALIZED 0 +#define FASTLY_IMAGE_OPTIMIZER_ERROR_TAG_OK 1 +#define FASTLY_IMAGE_OPTIMIZER_ERROR_TAG_ERROR 2 +#define FASTLY_IMAGE_OPTIMIZER_ERROR_TAG_WARNING 3 + +typedef struct __attribute__((aligned(4))) fastly_image_optimizer_error_detail { + uint32_t tag; + const char *message; + size_t message_len; +} fastly_image_optimizer_error_detail; + +WASM_IMPORT("fastly_image_optimizer", "transform_image_optimizer_request") +int image_optimizer_transform_image_optimizer_request( + uint32_t req_handle, uint32_t body_handle, const char *backend, size_t backend_len, + int io_transform_config_mask, fastly_image_optimizer_transform_config *io_transform_config, + fastly_image_optimizer_error_detail *io_error_detail, uint32_t *resp_handle_out, + uint32_t *resp_body_handle_out); + #ifdef __cplusplus } // namespace fastly } // extern C diff --git a/runtime/fastly/host-api/host_api.cpp b/runtime/fastly/host-api/host_api.cpp index ab33543bfc..54fbda010e 100644 --- a/runtime/fastly/host-api/host_api.cpp +++ b/runtime/fastly/host-api/host_api.cpp @@ -896,6 +896,31 @@ FastlyKVError make_fastly_kv_error(fastly::fastly_kv_error kv_error, return err; } +FastlyImageOptimizerError +make_fastly_image_optimizer_error(fastly::fastly_image_optimizer_error_detail im_err) { + FastlyImageOptimizerError::detail det; + switch (im_err.tag) { + case FASTLY_IMAGE_OPTIMIZER_ERROR_TAG_UNINITIALIZED: + det = FastlyImageOptimizerError::uninitialized; + break; + case FASTLY_IMAGE_OPTIMIZER_ERROR_TAG_OK: + det = FastlyImageOptimizerError::ok; + break; + case FASTLY_IMAGE_OPTIMIZER_ERROR_TAG_ERROR: + det = FastlyImageOptimizerError::error; + break; + case FASTLY_IMAGE_OPTIMIZER_ERROR_TAG_WARNING: + det = FastlyImageOptimizerError::warning; + break; + } + + return {det, std::string(im_err.message, im_err.message_len)}; +} + +FastlyImageOptimizerError make_fastly_image_optimizer_error(fastly::fastly_host_error err) { + return {err}; +} + } // namespace Result HttpBody::make() { @@ -1461,6 +1486,36 @@ Result HttpReq::send_async_without_caching(HttpBody body, std::s return res; } +FastlyResult +HttpReq::send_image_optimizer(HttpBody body, std::string_view backend, + std::string_view config_str) { + TRACE_CALL() + FastlyResult res; + + fastly::fastly_host_error err = 0; + fastly::fastly_world_string backend_str = string_view_to_world_string(backend); + auto opts = FASTLY_IMAGE_OPTIMIZER_SDK_CLAIMS_OPTS; + fastly::fastly_image_optimizer_transform_config config{config_str.data(), config_str.size()}; + fastly::fastly_image_optimizer_error_detail io_err_out; + uint32_t resp_handle_out = 0, body_handle_out = 0; + auto host_call_success = + true || convert_result(fastly::image_optimizer_transform_image_optimizer_request( + this->handle, body.handle, + reinterpret_cast(backend_str.ptr), backend_str.len, opts, + &config, &io_err_out, &resp_handle_out, &body_handle_out), + &err); + if (host_call_success) { + res.emplace_err(make_fastly_image_optimizer_error(err)); + } else if (io_err_out.tag != FASTLY_IMAGE_OPTIMIZER_ERROR_TAG_OK) { + res.emplace_err(make_fastly_image_optimizer_error(io_err_out)); + } else { + res.emplace(HttpResp(resp_handle_out), HttpBody(body_handle_out)); + } + res.emplace_err(make_fastly_image_optimizer_error(0)); + + return res; +} + Result HttpReq::set_method(std::string_view method) { TRACE_CALL() Result res; @@ -3612,6 +3667,20 @@ const std::optional FastlySendError::message() const { return "NetworkError when attempting to fetch resource."; } +const std::optional FastlyImageOptimizerError::message() const { + switch (err) { + case ok: + return std::nullopt; + case uninitialized: + return "Uninitialized: " + msg; + case warning: + return "Warning: " + msg; + case error: + return "Error: " + msg; + } + return std::nullopt; +} + bool BackendHealth::is_unknown() const { return this->state & FASTLY_HOST_BACKEND_BACKEND_HEALTH_UNKNOWN; } diff --git a/runtime/fastly/host-api/host_api_fastly.h b/runtime/fastly/host-api/host_api_fastly.h index ad444c67d0..b7d8f93b08 100644 --- a/runtime/fastly/host-api/host_api_fastly.h +++ b/runtime/fastly/host-api/host_api_fastly.h @@ -374,7 +374,7 @@ struct TlsVersion { uint8_t value = 0; explicit TlsVersion(uint8_t raw); - explicit TlsVersion(){}; + explicit TlsVersion() {}; uint8_t get_version() const; double get_version_number() const; @@ -515,6 +515,26 @@ enum class FramingHeadersMode : uint8_t { ManuallyFromHeaders, }; +class FastlyImageOptimizerError final { +public: + enum detail { uninitialized, ok, error, warning }; + + FastlyImageOptimizerError(detail err, std::string msg) + : err(err), is_host_error(false), msg(std::move(msg)) {} + FastlyImageOptimizerError(APIError host_err) : host_err(host_err), is_host_error(true) {} + + union { + APIError host_err; + detail err; + }; + bool is_host_error; + + const std::optional message() const; + +private: + std::string msg; +}; + class HttpReq final : public HttpBase { public: using Handle = uint32_t; @@ -568,6 +588,10 @@ class HttpReq final : public HttpBase { Result send_async_without_caching(HttpBody body, std::string_view backend, bool streaming = false); + /// Send this request synchronously to the Image Optimizer and wait for the response. + api::FastlyResult + send_image_optimizer(HttpBody body, std::string_view backend, std::string_view config_str); + /// Get the http version used for this request. /// Set the request method. @@ -1233,6 +1257,8 @@ class Compute final { void handle_api_error(JSContext *cx, uint8_t err, int line, const char *func); void handle_kv_error(JSContext *cx, host_api::FastlyKVError err, const unsigned int err_type, int line, const char *func); +void handle_image_optimizer_error(JSContext *cx, const host_api::FastlyImageOptimizerError &err, + int line, const char *func); bool error_is_generic(APIError e); bool error_is_invalid_argument(APIError e); diff --git a/runtime/fastly/host-api/host_call.cpp b/runtime/fastly/host-api/host_call.cpp index 4e9a7dbabe..7c72ab1f60 100644 --- a/runtime/fastly/host-api/host_call.cpp +++ b/runtime/fastly/host-api/host_call.cpp @@ -46,6 +46,16 @@ void handle_kv_error(JSContext *cx, FastlyKVError err, const unsigned int err_ty JS_ReportErrorNumberASCII(cx, fastly::FastlyGetErrorMessage, nullptr, err_type, message.c_str()); } +void handle_image_optimizer_error(JSContext *cx, const FastlyImageOptimizerError &err, int line, + const char *func) { + if (err.host_err == FASTLY_HOST_ERROR_UNSUPPORTED) { + return handle_api_error(cx, err.host_err, line, func); + } + + std::string message = err.message().value(); + JS_ReportErrorUTF8(cx, "[Image Optimizer] %s", message.c_str()); +} + /* Returns false if an exception is set on `cx` and the caller should immediately return to propagate the exception. */ void handle_api_error(JSContext *cx, APIError err, int line, const char *func) { diff --git a/src/bundle.js b/src/bundle.js index 3050b17760..d39c1326a0 100644 --- a/src/bundle.js +++ b/src/bundle.js @@ -130,6 +130,14 @@ export const TransactionCacheEntry = globalThis.TransactionCacheEntry; contents: `export const HTMLRewritingStream = globalThis.HTMLRewritingStream;`, }; } + case 'image-optimizer': { + return { + contents: ` + export const Region = globalThis.Region; + export const Format = globalThis.Format; + export const Auto = globalThis.Auto;`, + }; + } } }); }, diff --git a/sy-test/.fastlyignore b/sy-test/.fastlyignore new file mode 100644 index 0000000000..d851121617 --- /dev/null +++ b/sy-test/.fastlyignore @@ -0,0 +1,3 @@ +/node_modules +/bin +/pkg diff --git a/sy-test/.gitignore b/sy-test/.gitignore new file mode 100644 index 0000000000..d851121617 --- /dev/null +++ b/sy-test/.gitignore @@ -0,0 +1,3 @@ +/node_modules +/bin +/pkg diff --git a/sy-test/README.md b/sy-test/README.md new file mode 100644 index 0000000000..71d306a5c8 --- /dev/null +++ b/sy-test/README.md @@ -0,0 +1,31 @@ +# Empty Starter Kit for JavaScript + +[![Deploy to Fastly](https://deploy.edgecompute.app/button)](https://deploy.edgecompute.app/deploy) + +An empty application template for the Fastly Compute environment which returns a 200 OK response. + +**For more details about other starter kits for Compute, see the [Fastly developer hub](https://developer.fastly.com/solutions/starters)** + +## Running the application + +To create an application using this starter kit, create a new directory for your application and switch to it, and then type the following command: + +```shell +npm create @fastly/compute@latest -- --language=javascript --starter-kit=empty +``` + +To build and run your new application in the local development environment, type the following command: + +```shell +npm run start +``` + +To build and deploy your application to your Fastly account, type the following command. The first time you deploy the application, you will be prompted to create a new service in your account. + +```shell +npm run deploy +``` + +## Security issues + +Please see our [SECURITY.md](SECURITY.md) for guidance on reporting security-related issues. diff --git a/sy-test/fastly.toml b/sy-test/fastly.toml new file mode 100644 index 0000000000..3ebf4f0ff2 --- /dev/null +++ b/sy-test/fastly.toml @@ -0,0 +1,22 @@ +# This file describes a Fastly Compute package. To learn more visit: +# https://www.fastly.com/documentation/reference/compute/fastly-toml + +authors = ["tartanllama@gmail.com"] +cloned_from = "https://github.com/fastly/compute-starter-kit-javascript-empty" +description = "" +language = "javascript" +manifest_version = 3 +name = "sy-test" +service_id = "" + +[local_server] + +[scripts] +build = "node ../js-compute-runtime-cli.js src/index.js" +post_init = "npm install" + +[setup] +[setup.backends] +[setup.backends.httpme] +address = "http-me.glitch.me" +port = 443 diff --git a/sy-test/src/index.js b/sy-test/src/index.js new file mode 100644 index 0000000000..0ac1dd0e45 --- /dev/null +++ b/sy-test/src/index.js @@ -0,0 +1,17 @@ +/// + +import { Region, Format } from 'fastly:image-optimizer'; + + + +addEventListener("fetch", (event) => event.respondWith(handleRequest(event))); + +async function handleRequest(event) { + return await fetch('https://http-me.glitch.me/image-jpeg', { + imageOptimizerOptions: { + region: Region.UsEast, + format: Format.PJPG, + }, + backend: 'httpme' + }); +} From d68598125dea393edd9cbf7a4e0836d5b7310e20 Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Tue, 4 Nov 2025 12:30:29 +0000 Subject: [PATCH 02/31] Why no work --- runtime/fastly/builtins/fetch/fetch.cpp | 7 ++++--- runtime/fastly/host-api/host_api.cpp | 24 +++++++++++++++--------- sy-test/fastly.toml | 5 ++++- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/runtime/fastly/builtins/fetch/fetch.cpp b/runtime/fastly/builtins/fetch/fetch.cpp index d1858ee477..8cb0433e1f 100644 --- a/runtime/fastly/builtins/fetch/fetch.cpp +++ b/runtime/fastly/builtins/fetch/fetch.cpp @@ -156,9 +156,9 @@ bool get_caching_mode(JSContext *cx, HandleObject request, CachingMode *caching_ // The WASM service uses cache_on_behalf to insert the result into // the service's cache. auto image_optimizer_opts = - JS::GetReservedSlot(request, static_cast(Request::Slots::ImageOptimizerOptions)) - .toPrivate(); - if (image_optimizer_opts) { + JS::GetReservedSlot(request, static_cast(Request::Slots::ImageOptimizerOptions)); + if (!image_optimizer_opts.isNullOrUndefined()) { + std::cerr << "CACHING MODE: IMAGE OPT" << std::endl; *caching_mode = CachingMode::ImageOptimizer; return true; } @@ -276,6 +276,7 @@ bool fetch_send_body(JSContext *cx, HandleObject request, JS::MutableHandleValue res = request_handle.send_async_without_caching(body, backend_chars, streaming); break; case CachingMode::ImageOptimizer: { + std::cerr << "about to break ho boy" << std::endl; auto config = reinterpret_cast( JS::GetReservedSlot(request, static_cast(Request::Slots::ImageOptimizerOptions)) .toPrivate()); diff --git a/runtime/fastly/host-api/host_api.cpp b/runtime/fastly/host-api/host_api.cpp index 54fbda010e..692feb27c1 100644 --- a/runtime/fastly/host-api/host_api.cpp +++ b/runtime/fastly/host-api/host_api.cpp @@ -48,8 +48,9 @@ static void log_hostcall(const char *func_name, ...) { #define MILLISECS_IN_NANOSECS 1000000 #define SECS_IN_NANOSECS 1000000000 - +#include static bool convert_result(int res, fastly::fastly_host_error *err) { + std::cerr << "CONVENRT_RESULT: " << res << std::endl; if (res == 0) return true; switch (res) { @@ -918,6 +919,7 @@ make_fastly_image_optimizer_error(fastly::fastly_image_optimizer_error_detail im } FastlyImageOptimizerError make_fastly_image_optimizer_error(fastly::fastly_host_error err) { + std::cerr << "MAKING IT " << static_cast(err) << std::endl; return {err}; } @@ -1498,20 +1500,22 @@ HttpReq::send_image_optimizer(HttpBody body, std::string_view backend, fastly::fastly_image_optimizer_transform_config config{config_str.data(), config_str.size()}; fastly::fastly_image_optimizer_error_detail io_err_out; uint32_t resp_handle_out = 0, body_handle_out = 0; - auto host_call_success = - true || convert_result(fastly::image_optimizer_transform_image_optimizer_request( - this->handle, body.handle, - reinterpret_cast(backend_str.ptr), backend_str.len, opts, - &config, &io_err_out, &resp_handle_out, &body_handle_out), - &err); - if (host_call_success) { + std::cerr << "DOING IMAGE TRANSFORM: handle: " << this->handle << " body handle: " << body.handle + << " backend_str: " << backend_str.ptr << " backend_len: " << backend_str.len + << " opts: " << opts << " config: " << config.sdk_claims_opts + << " config_len: " << config.sdk_claims_opts_len << std::endl; + auto host_call_success = convert_result( + fastly::image_optimizer_transform_image_optimizer_request( + this->handle, body.handle, reinterpret_cast(backend_str.ptr), backend_str.len, + opts, &config, &io_err_out, &resp_handle_out, &body_handle_out), + &err); + if (!host_call_success) { res.emplace_err(make_fastly_image_optimizer_error(err)); } else if (io_err_out.tag != FASTLY_IMAGE_OPTIMIZER_ERROR_TAG_OK) { res.emplace_err(make_fastly_image_optimizer_error(io_err_out)); } else { res.emplace(HttpResp(resp_handle_out), HttpBody(body_handle_out)); } - res.emplace_err(make_fastly_image_optimizer_error(0)); return res; } @@ -3668,6 +3672,8 @@ const std::optional FastlySendError::message() const { } const std::optional FastlyImageOptimizerError::message() const { + if (is_host_error) + return std::nullopt; switch (err) { case ok: return std::nullopt; diff --git a/sy-test/fastly.toml b/sy-test/fastly.toml index 3ebf4f0ff2..79b38e36de 100644 --- a/sy-test/fastly.toml +++ b/sy-test/fastly.toml @@ -10,9 +10,12 @@ name = "sy-test" service_id = "" [local_server] +[local_server.backends] +[local_server.backends.httpme] +url = "https://http-me.glitch.me" [scripts] -build = "node ../js-compute-runtime-cli.js src/index.js" +build = "node ../js-compute-runtime-cli.js src/index.js --debug-build" post_init = "npm install" [setup] From e858fc0c9a71b79f2cc91af6d0040a617d1c380e Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Wed, 5 Nov 2025 14:16:38 +0000 Subject: [PATCH 03/31] Correct alignment --- runtime/fastly/host-api/fastly.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/fastly/host-api/fastly.h b/runtime/fastly/host-api/fastly.h index fe70720431..d1e2e86d8c 100644 --- a/runtime/fastly/host-api/fastly.h +++ b/runtime/fastly/host-api/fastly.h @@ -1103,7 +1103,7 @@ WASM_IMPORT("fastly_acl", "lookup") int acl_lookup(uint32_t acl_handle, const uint8_t *ip_octets, size_t ip_len, uint32_t *body_handle_out, fastly_acl_error *acl_error_out); -typedef struct __attribute__((aligned(4))) fastly_image_optimizer_transform_config { +typedef struct __attribute__((aligned(8))) fastly_image_optimizer_transform_config { const char *sdk_claims_opts; size_t sdk_claims_opts_len; } fastly_image_optimizer_transform_config; @@ -1116,7 +1116,7 @@ typedef struct __attribute__((aligned(4))) fastly_image_optimizer_transform_conf #define FASTLY_IMAGE_OPTIMIZER_ERROR_TAG_ERROR 2 #define FASTLY_IMAGE_OPTIMIZER_ERROR_TAG_WARNING 3 -typedef struct __attribute__((aligned(4))) fastly_image_optimizer_error_detail { +typedef struct __attribute__((aligned(8))) fastly_image_optimizer_error_detail { uint32_t tag; const char *message; size_t message_len; From 75500f6a8f43ead6de244311302992c1def0baaa Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Wed, 5 Nov 2025 14:16:53 +0000 Subject: [PATCH 04/31] Better error handling --- runtime/fastly/host-api/host_call.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/fastly/host-api/host_call.cpp b/runtime/fastly/host-api/host_call.cpp index 7c72ab1f60..22b1ebee1b 100644 --- a/runtime/fastly/host-api/host_call.cpp +++ b/runtime/fastly/host-api/host_call.cpp @@ -48,7 +48,7 @@ void handle_kv_error(JSContext *cx, FastlyKVError err, const unsigned int err_ty void handle_image_optimizer_error(JSContext *cx, const FastlyImageOptimizerError &err, int line, const char *func) { - if (err.host_err == FASTLY_HOST_ERROR_UNSUPPORTED) { + if (err.is_host_error) { return handle_api_error(cx, err.host_err, line, func); } From 6c77c06e263e127def873e8d6af627d640fc48b0 Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Wed, 5 Nov 2025 14:18:12 +0000 Subject: [PATCH 05/31] Change debug output --- runtime/fastly/builtins/fetch/fetch.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/runtime/fastly/builtins/fetch/fetch.cpp b/runtime/fastly/builtins/fetch/fetch.cpp index 8cb0433e1f..3fd106d9a2 100644 --- a/runtime/fastly/builtins/fetch/fetch.cpp +++ b/runtime/fastly/builtins/fetch/fetch.cpp @@ -158,7 +158,6 @@ bool get_caching_mode(JSContext *cx, HandleObject request, CachingMode *caching_ auto image_optimizer_opts = JS::GetReservedSlot(request, static_cast(Request::Slots::ImageOptimizerOptions)); if (!image_optimizer_opts.isNullOrUndefined()) { - std::cerr << "CACHING MODE: IMAGE OPT" << std::endl; *caching_mode = CachingMode::ImageOptimizer; return true; } @@ -276,14 +275,12 @@ bool fetch_send_body(JSContext *cx, HandleObject request, JS::MutableHandleValue res = request_handle.send_async_without_caching(body, backend_chars, streaming); break; case CachingMode::ImageOptimizer: { - std::cerr << "about to break ho boy" << std::endl; auto config = reinterpret_cast( JS::GetReservedSlot(request, static_cast(Request::Slots::ImageOptimizerOptions)) .toPrivate()); auto config_str = config->to_string(); std::cerr << "sending config to image optimizer: " << config_str << std::endl; auto res = request_handle.send_image_optimizer(body, backend_chars, config_str); - if (auto *err = res.to_err()) { HANDLE_IMAGE_OPTIMIZER_ERROR(cx, *err); ret.setObject(*PromiseRejectedWithPendingError(cx)); From a219e1b4576c0040e84aca8d667876e27971f7dc Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Wed, 5 Nov 2025 14:18:40 +0000 Subject: [PATCH 06/31] Change test file --- sy-test/src/index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sy-test/src/index.js b/sy-test/src/index.js index 0ac1dd0e45..ad8aea56e1 100644 --- a/sy-test/src/index.js +++ b/sy-test/src/index.js @@ -2,15 +2,13 @@ import { Region, Format } from 'fastly:image-optimizer'; - - addEventListener("fetch", (event) => event.respondWith(handleRequest(event))); async function handleRequest(event) { return await fetch('https://http-me.glitch.me/image-jpeg', { imageOptimizerOptions: { region: Region.UsEast, - format: Format.PJPG, + format: Format.PNG }, backend: 'httpme' }); From 0e02de978a6c60569ef9fbf0dc71cecd9ee68ef6 Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Wed, 5 Nov 2025 14:58:44 +0000 Subject: [PATCH 07/31] WORKING --- runtime/fastly/host-api/host_api.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/runtime/fastly/host-api/host_api.cpp b/runtime/fastly/host-api/host_api.cpp index 692feb27c1..78c9efa77f 100644 --- a/runtime/fastly/host-api/host_api.cpp +++ b/runtime/fastly/host-api/host_api.cpp @@ -1494,24 +1494,26 @@ HttpReq::send_image_optimizer(HttpBody body, std::string_view backend, TRACE_CALL() FastlyResult res; - fastly::fastly_host_error err = 0; + fastly::fastly_host_error err; + HttpReq::Handle orig_req_body_handle = INVALID_HANDLE; fastly::fastly_world_string backend_str = string_view_to_world_string(backend); auto opts = FASTLY_IMAGE_OPTIMIZER_SDK_CLAIMS_OPTS; fastly::fastly_image_optimizer_transform_config config{config_str.data(), config_str.size()}; - fastly::fastly_image_optimizer_error_detail io_err_out; - uint32_t resp_handle_out = 0, body_handle_out = 0; - std::cerr << "DOING IMAGE TRANSFORM: handle: " << this->handle << " body handle: " << body.handle - << " backend_str: " << backend_str.ptr << " backend_len: " << backend_str.len - << " opts: " << opts << " config: " << config.sdk_claims_opts + fastly::fastly_image_optimizer_error_detail io_err_out{}; + uint32_t resp_handle_out = INVALID_HANDLE, body_handle_out = INVALID_HANDLE; + std::cerr << "DOING IMAGE TRANSFORM: handle: " << this->handle + << " body handle: " << INVALID_HANDLE << " backend_str: " << backend_str.ptr + << " backend_len: " << backend_str.len << " opts: " << opts + << " config: " << config.sdk_claims_opts << " config_len: " << config.sdk_claims_opts_len << std::endl; auto host_call_success = convert_result( fastly::image_optimizer_transform_image_optimizer_request( - this->handle, body.handle, reinterpret_cast(backend_str.ptr), backend_str.len, - opts, &config, &io_err_out, &resp_handle_out, &body_handle_out), + this->handle, orig_req_body_handle, reinterpret_cast(backend_str.ptr), + backend_str.len, opts, &config, &io_err_out, &resp_handle_out, &body_handle_out), &err); if (!host_call_success) { res.emplace_err(make_fastly_image_optimizer_error(err)); - } else if (io_err_out.tag != FASTLY_IMAGE_OPTIMIZER_ERROR_TAG_OK) { + } else if (false && io_err_out.tag != FASTLY_IMAGE_OPTIMIZER_ERROR_TAG_OK) { res.emplace_err(make_fastly_image_optimizer_error(io_err_out)); } else { res.emplace(HttpResp(resp_handle_out), HttpBody(body_handle_out)); From 6aa7bc3aea75ca4346a69431fdbd10bc9ea1b8f2 Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Fri, 7 Nov 2025 11:26:42 +0000 Subject: [PATCH 08/31] bgColor working --- package.json | 2 +- .../builtins/image-optimizer-options.inc | 119 +++++++++++----- runtime/fastly/builtins/image-optimizer.cpp | 132 ++++++++++++++---- runtime/fastly/builtins/image-optimizer.h | 77 ++++++---- 4 files changed, 241 insertions(+), 89 deletions(-) diff --git a/package.json b/package.json index b9dce9a044..7d1e904435 100644 --- a/package.json +++ b/package.json @@ -65,4 +65,4 @@ "magic-string": "^0.30.12", "regexpu-core": "^6.1.1" } -} +} \ No newline at end of file diff --git a/runtime/fastly/builtins/image-optimizer-options.inc b/runtime/fastly/builtins/image-optimizer-options.inc index 3409f76c1e..24a1d90ee7 100644 --- a/runtime/fastly/builtins/image-optimizer-options.inc +++ b/runtime/fastly/builtins/image-optimizer-options.inc @@ -1,50 +1,99 @@ -#ifndef FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION -#error "Must define the FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION macro" +#if !defined(FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION) && \ + !defined(FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE) && \ + !defined(FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE) +#error "Must define at least one of the FASTLY_DEFINE_IMAGE_OPTIMIZER_*** macros" #endif + #ifndef FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE -#error "Must define the FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE macro" +#define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase, str) +#endif +#ifndef FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION +#define FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(name, str) #endif #ifndef FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE -#error "Must define the FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE macro" +#define FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(type) #endif -FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(Region, region) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(UsEast, "us_east", 1) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(UsCentral, "us_central", 2) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(UsWest, "us_west", 3) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(EuCentral, "eu_central", 4) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(EuWest, "eu_west", 5) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Asia, "asia", 6) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Australia, "australia", 7) +FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(Region, region, "region") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(UsEast, "us_east") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(UsCentral, "us_central") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(UsWest, "us_west") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(EuCentral, "eu_central") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(EuWest, "eu_west") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Asia, "asia") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Australia, "australia") FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(Region) -FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(Auto, auto) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(AVIF, "avif", 1) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(WEBP, "webp", 2) +FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(Auto, auto, "auto") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(AVIF, "avif") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(WEBP, "webp") FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(Auto) -FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(Format, format) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Auto, "auto", 1) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(AVIF, "avif", 2) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(BJPG, "bjpg", 3) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(GIF, "gif", 4) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(JPG, "jpg", 5) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(JXL, "jxl", 6) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(MP4, "mp4", 7) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(PJPG, "pjpg", 8) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(PJXL, "pjxl", 9) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(PNG, "png", 10) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(PNG8, "png8", 11) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(SVG, "svg", 12) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(WEBP, "webp", 13) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(WEBPLL, "webpll", 14) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(WEBPLY, "webply", 15) +FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(Format, format, "format") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Auto, "auto") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(AVIF, "avif") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(BJPG, "bjpg") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(GIF, "gif") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(JPG, "jpg") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(JXL, "jxl") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(MP4, "mp4") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(PJPG, "pjpg") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(PJXL, "pjxl") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(PNG, "png") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(PNG8, "png8") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(SVG, "svg") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(WEBP, "webp") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(WEBPLL, "webpll") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(WEBPLY, "webply") FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(Format) -FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(BWAlgorithm, bw) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Threshold, "threshold", 1) -FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Atkinson, "atkinson", 2) -FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(Auto) +FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(BWAlgorithm, bw, "bw") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Threshold, "threshold") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Atkinson, "atkinson") +FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(BWAlgorithm) + +FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(Disable, disable, "disable") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Upscale, "upscale") +FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(Disable) + +FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(Enable, enable, "enable") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Upscale, "upscale") +FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(Enable) + +FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(Fit, fit, "fit") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Bounds, "bounds") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Cover, "cover") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Crop, "crop") +FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(Fit) + +FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(Metadata, metadata, "metadata") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Copyright, "copyright") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(C2PA, "c2pa") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(CopyrightAndC2PA, "copyright,c2pa") +FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(Metadata) + +FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(Optimize, optimize, "optimize") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Low, "low") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Medium, "medium") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(High, "high") +FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(Optimize) + +FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(Profile, profile, "profile") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Baseline, "baseline") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Main, "main") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(High, "high") +FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(Optimize) + +FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(ResizeFilter, resize_filter, "resize-filter") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Nearest, "nearest") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Bilinear, "bilinear") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Linear, "linear") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Bicubic, "bicubic") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Cubic, "cubic") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Lanczos2, "lanczos2") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Lanczos3, "lanczos3") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Lanczos, "lanczos") +FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(ResizeFilter) #undef FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION #undef FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE diff --git a/runtime/fastly/builtins/image-optimizer.cpp b/runtime/fastly/builtins/image-optimizer.cpp index d748af639d..19bd2fcee4 100644 --- a/runtime/fastly/builtins/image-optimizer.cpp +++ b/runtime/fastly/builtins/image-optimizer.cpp @@ -12,10 +12,9 @@ const JSPropertySpec EnumOption::properties[] = { JS_PS_END, }; -#define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase) \ +#define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase, str) \ const JSPropertySpec type::static_properties[] = { -#define FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(name, str, value) \ - JS_STRING_PS(#name, str, JSPROP_READONLY), +#define FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(name, str) JS_STRING_PS(#name, str, JSPROP_READONLY), #define FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(type) \ JS_PS_END, \ } \ @@ -25,7 +24,7 @@ const JSPropertySpec EnumOption::properties[] = { bool install(api::Engine *engine) { RootedObject image_optimizer_ns(engine->cx(), JS_NewObject(engine->cx(), nullptr)); -#define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase) \ +#define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase, str) \ if (!type::init_class_impl(engine->cx(), engine->global())) { \ return false; \ } \ @@ -38,7 +37,7 @@ bool install(api::Engine *engine) { if (!JS_SetProperty(engine->cx(), image_optimizer_ns, #type, lowercase##_val)) { \ return false; \ } -#define FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(name, str, value) +#define FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(name, str) #define FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(type) #include "image-optimizer-options.inc" @@ -51,32 +50,25 @@ bool install(api::Engine *engine) { std::unique_ptr ImageOptimizerOptions::create(JSContext *cx, JS::HandleValue opts_val) { - // Extract properties. `region` is the only mandatory property - JS::RootedValue region_val(cx); + // Extract properties. +#define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase, str) \ + JS::RootedValue lowercase##_val(cx); +#include "image-optimizer-options.inc" JS::RootedValue preserve_query_string_on_origin_request_val(cx); - JS::RootedValue auto_val(cx); JS::RootedValue bg_color_val(cx); JS::RootedValue blur_val(cx); JS::RootedValue brightness_val(cx); - JS::RootedValue bw_val(cx); JS::RootedValue canvas_val(cx); JS::RootedValue contrast_val(cx); JS::RootedValue crop_val(cx); JS::RootedValue dpr_val(cx); - JS::RootedValue enable_val(cx); - JS::RootedValue fit_val(cx); - JS::RootedValue format_val(cx); JS::RootedValue frame_val(cx); JS::RootedValue height_val(cx); JS::RootedValue level_val(cx); - JS::RootedValue metadata_val(cx); - JS::RootedValue optimize_val(cx); JS::RootedValue orient_val(cx); JS::RootedValue pad_val(cx); JS::RootedValue precrop_val(cx); - JS::RootedValue profile_val(cx); JS::RootedValue quality_val(cx); - JS::RootedValue resize_filter_val(cx); JS::RootedValue saturation_val(cx); JS::RootedValue sharpen_val(cx); JS::RootedValue trim_val(cx); @@ -93,7 +85,9 @@ std::unique_ptr ImageOptimizerOptions::create(JSContext * !JS_GetProperty(cx, opts, "bw", &bw_val) || !JS_GetProperty(cx, opts, "canvas", &canvas_val) || !JS_GetProperty(cx, opts, "contrast", &contrast_val) || - !JS_GetProperty(cx, opts, "crop", &crop_val) || !JS_GetProperty(cx, opts, "dpr", &dpr_val) || + !JS_GetProperty(cx, opts, "crop", &crop_val) || + !JS_GetProperty(cx, opts, "disable", &disable_val) || + !JS_GetProperty(cx, opts, "dpr", &dpr_val) || !JS_GetProperty(cx, opts, "enable", &enable_val) || !JS_GetProperty(cx, opts, "fit", &fit_val) || !JS_GetProperty(cx, opts, "format", &format_val) || @@ -121,16 +115,24 @@ std::unique_ptr ImageOptimizerOptions::create(JSContext * "contain region"); return nullptr; } - auto region = to_region(cx, region_val); - if (!region) { - return nullptr; + +#define TRY_CONVERT(name) \ + decltype(to_##name(cx, name##_val)) name##_opt; \ + if (!name##_val.isUndefined()) { \ + name##_opt = to_##name(cx, name##_val); \ + if (!name##_opt) { \ + return nullptr; \ + } \ } - auto auto_ = to_auto(cx, auto_val); - auto format = to_format(cx, format_val); +#define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase, str) TRY_CONVERT(lowercase); +#include "image-optimizer-options.inc" + + TRY_CONVERT(bg_color); - return std::unique_ptr{ - new ImageOptimizerOptions(region.value(), auto_, format)}; + return std::unique_ptr{new ImageOptimizerOptions( + *region_opt, auto_opt, std::move(bg_color_opt), bw_opt, disable_opt, enable_opt, fit_opt, + format_opt, metadata_opt, optimize_opt, profile_opt, resize_filter_opt)}; } std::string ImageOptimizerOptions::to_string() const { @@ -141,7 +143,16 @@ std::string ImageOptimizerOptions::to_string() const { ret += "&" + to_string(*v); }; append(auto_); + append(bg_color_); + append(bw_); + append(disable_); + append(enable_); + append(fit_); append(format_); + append(metadata_); + append(optimize_); + append(profile_); + append(resizeFilter_); return ret; } @@ -153,4 +164,77 @@ fastly_image_optimizer_transform_config ImageOptimizerOptions::to_config() { config.sdk_claims_opts_len = host_str.len; return config; } + +std::optional +ImageOptimizerOptions::to_color(JSContext *cx, JS::HandleValue val, const std::string &field_name) { + // Hex strings of size 3 or 6 are allowed + if (val.isString()) { + JS::RootedString str_val(cx, val.toString()); + auto str = core::encode(cx, str_val); + if ((str.len == 3 || str.len == 6) && + (std::all_of(str.begin(), str.end(), [](char c) { return std::isxdigit(c); }))) { + return Color{std::move(str)}; + } + } + + auto throw_error = [&]() { + api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", field_name.data(), + "must be a 3/6 character hex string or RGB(A) object"); + }; + + // Otherwise, it should be an rgb(a) object + if (!val.isObject()) { + throw_error(); + return std::nullopt; + } + + JS::RootedValue r(cx); + JS::RootedValue g(cx); + JS::RootedValue b(cx); + JS::RootedValue a(cx); + JS::RootedObject obj(cx, &val.toObject()); + if (!JS_GetProperty(cx, obj, "r", &r) || !JS_GetProperty(cx, obj, "g", &g) || + !JS_GetProperty(cx, obj, "b", &b) || !JS_GetProperty(cx, obj, "a", &a)) { + return std::nullopt; + } + + auto is_valid_color_component = [](const auto &c) { + return c.isInt32() && c.toInt32() >= 0 && c.toInt32() < 256; + }; + auto alpha_valid = + a.isUndefined() || (a.isNumber() && a.toNumber() >= 0.0 && a.toNumber() <= 1.0); + if (!is_valid_color_component(r) || !is_valid_color_component(g) || + !is_valid_color_component(b) || !alpha_valid) { + throw_error(); + return std::nullopt; + } + + std::string rep; + rep += std::to_string(r.toInt32()) + ',' + std::to_string(g.toInt32()) + ',' + + std::to_string(b.toInt32()); + if (!a.isUndefined()) { + rep += ',' + std::to_string(a.toNumber()); + } + return Color{host_api::HostString(rep)}; +} + +#define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase, str) \ + std::optional ImageOptimizerOptions::to_##lowercase( \ + JSContext *cx, JS::HandleValue val) { \ + if (!val.isString()) { \ + api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", #type, \ + "must be a string"); \ + return std::nullopt; \ + } \ + JS::RootedString str_val(cx, val.toString()); \ + using enum type; +#define FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(name, str) \ + if (core::encode(cx, str_val) == std::string_view(str)) { \ + return name; \ + } +#define FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(type) \ + JS_ReportErrorUTF8(cx, #type " out of range"); \ + return std::nullopt; \ + } +#include "image-optimizer-options.inc" } // namespace fastly::image_optimizer \ No newline at end of file diff --git a/runtime/fastly/builtins/image-optimizer.h b/runtime/fastly/builtins/image-optimizer.h index 190dfca265..a2d3b7e750 100644 --- a/runtime/fastly/builtins/image-optimizer.h +++ b/runtime/fastly/builtins/image-optimizer.h @@ -18,14 +18,12 @@ class EnumOption { static const JSFunctionSpec methods[]; static const JSPropertySpec properties[]; }; -#define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase) \ +#define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase, str) \ class type : public EnumOption, public builtins::BuiltinNoConstructor { \ public: \ static const JSPropertySpec static_properties[]; \ static constexpr const char *class_name = #type; \ }; -#define FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(name, str, value) -#define FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(type) #include "image-optimizer-options.inc" class ImageOptimizerOptions { @@ -34,50 +32,65 @@ class ImageOptimizerOptions { static std::unique_ptr create(JSContext *cx, JS::HandleValue opts_val); std::string to_string() const; -#define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase) enum class type { -#define FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(name, str, value) name = value, +#define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase, str) enum class type { +#define FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(name, str) name, #define FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(type) \ } \ ; #include "image-optimizer-options.inc" -#define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase) \ - static std::optional to_##lowercase(JSContext *cx, JS::HandleValue val) { \ - if (val.isUndefined()) \ - return std::nullopt; \ - if (!val.isString()) { \ - api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", #type, \ - "must be a string"); \ - return std::nullopt; \ - } \ - JS::RootedString str_val(cx, val.toString()); \ - using enum type; -#define FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(name, str, value) \ - if (core::encode(cx, str_val) == std::string_view(str)) { \ - return name; \ - } -#define FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(type) \ - JS_ReportErrorUTF8(cx, #type " out of range"); \ - return std::nullopt; \ - } +#define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase, str) \ + static std::optional to_##lowercase(JSContext *cx, JS::HandleValue val); #include "image-optimizer-options.inc" + struct Color { + host_api::HostString val; + }; + static std::optional to_color(JSContext *cx, JS::HandleValue val, + const std::string &field_name); + + struct BGColor { + Color color; + }; + static std::optional to_bg_color(JSContext *cx, JS::HandleValue val) { + auto color = to_color(cx, val, "bgColor"); + if (!color) + return std::nullopt; + return BGColor{std::move(*color)}; + } + private: - ImageOptimizerOptions(Region region, std::optional auto_val, std::optional format) - : region_(region), auto_(auto_val), format_(format) {} + ImageOptimizerOptions(Region region, std::optional auto_val, + std::optional bg_color, std::optional bw, + std::optional disable, std::optional enable, + std::optional fit, std::optional format, + std::optional metadata, std::optional optimize, + std::optional profile, std::optional resize_filter) + : region_(region), auto_(auto_val), bg_color_(std::move(bg_color)), bw_(bw), + disable_(disable), enable_(enable), fit_(fit), format_(format), metadata_(metadata), + optimize_(optimize), profile_(profile), resizeFilter_(resize_filter) {} Region region_; std::optional auto_; + std::optional bg_color_; + std::optional bw_; + std::optional disable_; + std::optional enable_; + std::optional fit_; std::optional format_; + std::optional metadata_; + std::optional optimize_; + std::optional profile_; + std::optional resizeFilter_; }; inline std::string to_string(const ImageOptimizerOptions &opts) { return opts.to_string(); } -#define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase) \ +#define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase, str) \ inline std::string to_string(ImageOptimizerOptions::type val) { \ using enum ImageOptimizerOptions::type; \ - std::string prefix = #lowercase; \ + std::string prefix = str; \ switch (val) { -#define FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(name, str, value) \ +#define FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(name, str) \ case name: \ return prefix + '=' + str; #define FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(type) \ @@ -85,5 +98,11 @@ inline std::string to_string(const ImageOptimizerOptions &opts) { return opts.to } #include "image-optimizer-options.inc" +inline std::string to_string(const ImageOptimizerOptions::Color &c) { + return std::string{std::string_view(c.val)}; +} +inline std::string to_string(const ImageOptimizerOptions::BGColor &bg) { + return "bg-color=" + to_string(bg.color); +} } // namespace fastly::image_optimizer #endif \ No newline at end of file From d848b130a5fe9e6d5a02052a80a944375ea580a0 Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Fri, 7 Nov 2025 13:59:13 +0000 Subject: [PATCH 09/31] ALmost there --- .../builtins/image-optimizer-options.inc | 11 + runtime/fastly/builtins/image-optimizer.cpp | 227 +++++++++++++++++- runtime/fastly/builtins/image-optimizer.h | 157 +++++++++++- runtime/fastly/host-api/host_api.cpp | 1 - src/bundle.js | 1 + 5 files changed, 386 insertions(+), 11 deletions(-) diff --git a/runtime/fastly/builtins/image-optimizer-options.inc b/runtime/fastly/builtins/image-optimizer-options.inc index 24a1d90ee7..97b0601faf 100644 --- a/runtime/fastly/builtins/image-optimizer-options.inc +++ b/runtime/fastly/builtins/image-optimizer-options.inc @@ -78,6 +78,17 @@ FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Medium, "medium") FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(High, "high") FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(Optimize) +FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(Orient, orient, "orient") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Default, "1") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(FlipHorizontal, "2") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(FlipHorizontalAndVertical, "3") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(FlipVertical, "4") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(FlipHorizontalOrientLeft, "5") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(OrientRight, "6") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(FlipHorizontalOrientRight, "7") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(OrientLeft, "8") +FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(Orient) + FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(Profile, profile, "profile") FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Baseline, "baseline") FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Main, "main") diff --git a/runtime/fastly/builtins/image-optimizer.cpp b/runtime/fastly/builtins/image-optimizer.cpp index 19bd2fcee4..7e948f55c6 100644 --- a/runtime/fastly/builtins/image-optimizer.cpp +++ b/runtime/fastly/builtins/image-optimizer.cpp @@ -1,5 +1,31 @@ #include "image-optimizer.h" #include + +namespace { +std::optional from_percentage(std::string_view sv) { + if (!sv.ends_with('%')) { + return std::nullopt; + } + char *end; + auto percentage = std::strtod(sv.data(), &end); + if (end != sv.data() + sv.size() - 1 || percentage == HUGE_VAL) { + return std::nullopt; + } + return percentage; +} + +std::optional to_number_between_inclusive(JS::HandleValue val, double from, double to) { + if (!val.isNumber()) { + return std::nullopt; + } + auto num = val.toNumber(); + if (num < from || num > to) { + return std::nullopt; + } + return num; +} +} // namespace + namespace fastly::image_optimizer { const JSFunctionSpec EnumOption::static_methods[] = { JS_FS_END, @@ -65,7 +91,6 @@ std::unique_ptr ImageOptimizerOptions::create(JSContext * JS::RootedValue frame_val(cx); JS::RootedValue height_val(cx); JS::RootedValue level_val(cx); - JS::RootedValue orient_val(cx); JS::RootedValue pad_val(cx); JS::RootedValue precrop_val(cx); JS::RootedValue quality_val(cx); @@ -73,6 +98,7 @@ std::unique_ptr ImageOptimizerOptions::create(JSContext * JS::RootedValue sharpen_val(cx); JS::RootedValue trim_val(cx); JS::RootedValue trim_color_val(cx); + JS::RootedValue viewbox_val(cx); JS::RootedValue width_val(cx); JS::RootedObject opts(cx, opts_val.toObjectOrNull()); if (!JS_GetProperty(cx, opts, "region", ®ion_val) || @@ -106,6 +132,7 @@ std::unique_ptr ImageOptimizerOptions::create(JSContext * !JS_GetProperty(cx, opts, "sharpen", &sharpen_val) || !JS_GetProperty(cx, opts, "trim", &trim_val) || !JS_GetProperty(cx, opts, "trimColor", &trim_color_val) || + !JS_GetProperty(cx, opts, "viewbox", &viewbox_val) || !JS_GetProperty(cx, opts, "width", &width_val)) { return nullptr; } @@ -129,10 +156,23 @@ std::unique_ptr ImageOptimizerOptions::create(JSContext * #include "image-optimizer-options.inc" TRY_CONVERT(bg_color); + TRY_CONVERT(blur); + TRY_CONVERT(brightness); + TRY_CONVERT(contrast); + TRY_CONVERT(dpr); + TRY_CONVERT(frame); + TRY_CONVERT(height); + TRY_CONVERT(level); + TRY_CONVERT(saturation); + TRY_CONVERT(sharpen); + TRY_CONVERT(viewbox); + TRY_CONVERT(width); return std::unique_ptr{new ImageOptimizerOptions( - *region_opt, auto_opt, std::move(bg_color_opt), bw_opt, disable_opt, enable_opt, fit_opt, - format_opt, metadata_opt, optimize_opt, profile_opt, resize_filter_opt)}; + *region_opt, auto_opt, std::move(bg_color_opt), blur_opt, brightness_opt, bw_opt, + contrast_opt, disable_opt, dpr_opt, enable_opt, fit_opt, format_opt, frame_opt, height_opt, + std::move(level_opt), metadata_opt, optimize_opt, orient_opt, profile_opt, resize_filter_opt, + saturation_opt, sharpen_opt, viewbox_opt, width_opt)}; } std::string ImageOptimizerOptions::to_string() const { @@ -144,18 +184,39 @@ std::string ImageOptimizerOptions::to_string() const { }; append(auto_); append(bg_color_); + append(blur_); + append(brightness_); append(bw_); + append(contrast_); append(disable_); + append(dpr_); append(enable_); append(fit_); append(format_); + append(frame_); + append(height_); + append(level_); append(metadata_); append(optimize_); + append(orient_); append(profile_); append(resizeFilter_); + append(saturation_); + append(sharpen_); + append(viewbox_); + append(width_); return ret; } +// TODO +// canvas +// crop +// pad +// precrop +// quality +// trim +// trimcolor + fastly_image_optimizer_transform_config ImageOptimizerOptions::to_config() { fastly_image_optimizer_transform_config config; auto str = this->to_string(); @@ -165,6 +226,166 @@ fastly_image_optimizer_transform_config ImageOptimizerOptions::to_config() { return config; } +std::optional +ImageOptimizerOptions::to_brightness(JSContext *cx, JS::HandleValue val) { + auto num = to_number_between_inclusive(val, -100, 100); + if (!num) { + api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", "brightness", + "must be a number between -100 and 100"); + return std::nullopt; + } + return Brightness{*num}; +} +std::optional +ImageOptimizerOptions::to_contrast(JSContext *cx, JS::HandleValue val) { + auto num = to_number_between_inclusive(val, -100, 100); + if (!num) { + api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", "contrast", + "must be a number between -100 and 100"); + return std::nullopt; + } + return Contrast{*num}; +} +std::optional +ImageOptimizerOptions::to_saturation(JSContext *cx, JS::HandleValue val) { + auto num = to_number_between_inclusive(val, -100, 100); + if (!num) { + api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", "saturation", + "must be a number between -100 and 100"); + return std::nullopt; + } + return Saturation{*num}; +} +std::optional ImageOptimizerOptions::to_dpr(JSContext *cx, + JS::HandleValue val) { + auto num = to_number_between_inclusive(val, 1, 10); + if (!num) { + api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", "dpr", + "must be a number between 1 and 10"); + return std::nullopt; + } + return Dpr{*num}; +} +std::optional ImageOptimizerOptions::to_level(JSContext *cx, + JS::HandleValue val) { + auto throw_error = [&]() { + api::throw_error( + cx, api::Errors::TypeError, "imageOptimizerOptions", "level", + "must be a string containing 1.0, 1.1, 1.2, 1.3, 2.0, 2.1, 2.2, 3.0, 3.1, 3.2, " + "4.0, 4.1, 4.2, 5.0, 5.1, 5.2, 6.0, 6.1, or 6.2"); + }; + if (!val.isString()) { + throw_error(); + return std::nullopt; + } + JS::RootedString js_str(cx, val.toString()); + auto str = core::encode(cx, js_str); + std::string_view sv = str; + if (sv != "1.0" && sv != "1.1" && sv != "1.2" && sv != "1.3" && sv != "2.0" && sv != "2.1" && + sv != "2.2" && sv != "3.0" && sv != "3.1" && sv != "3.2" && sv != "4.0" && sv != "4.1" && + sv != "4.2" && sv != "5.0" && sv != "5.1" && sv != "5.2" && sv != "6.0" && sv != "6.1" && + sv != "6.2") { + throw_error(); + return std::nullopt; + } + return Level{std::move(str)}; +} +std::optional ImageOptimizerOptions::to_frame(JSContext *cx, + JS::HandleValue val) { + if (!val.isInt32() || val.toInt32() != 1) { + api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", "frame", "must be 1"); + return std::nullopt; + } + return Frame{1}; +} +std::optional +ImageOptimizerOptions::to_viewbox(JSContext *cx, JS::HandleValue val) { + if (!val.isInt32() || val.toInt32() != 1) { + api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", "viewbox", "must be 1"); + return std::nullopt; + } + return Viewbox{1}; +} +std::optional ImageOptimizerOptions::to_blur(JSContext *cx, + JS::HandleValue val) { + auto throw_error = [&]() { + api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", "blur", + "must be a number between 0.5 and 1000 or a string percentage value"); + }; + if (val.isNumber()) { + auto num = to_number_between_inclusive(val, 0.5, 1000); + if (!num) { + throw_error(); + return std::nullopt; + } + return Blur{.value = *num, .is_percentage = false}; + } + if (!val.isString()) { + throw_error(); + return std::nullopt; + } + JS::RootedString js_str(cx, val.toString()); + auto str = core::encode(cx, js_str); + auto percentage = from_percentage(str); + if (!percentage) { + throw_error(); + return std::nullopt; + } + return Blur{.value = *percentage, .is_percentage = true}; +} +std::optional +ImageOptimizerOptions::to_sharpen(JSContext *cx, JS::HandleValue val) { + auto throw_error = [&]() { + api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", "sharpen", + "must be an object with keys `amount` (0.0-10.0), `radius` (0.5-1000.0), and " + "`threshold` (0-255)"); + }; + if (!val.isObject()) { + throw_error(); + return std::nullopt; + } + JS::RootedValue amount_val(cx); + JS::RootedValue radius_val(cx); + JS::RootedValue threshold_val(cx); + JS::RootedObject opts(cx, &val.toObject()); + if (!JS_GetProperty(cx, opts, "amount", &amount_val) || + !JS_GetProperty(cx, opts, "radius", &radius_val) || + !JS_GetProperty(cx, opts, "threshold", &threshold_val)) { + return std::nullopt; + } + auto amount = to_number_between_inclusive(amount_val, 0, 10); + auto radius = to_number_between_inclusive(radius_val, 0.5, 1000); + if (!amount || !radius || !threshold_val.isInt32() || threshold_val.toInt32() < 0 || + threshold_val.toInt32() > 255) { + throw_error(); + return std::nullopt; + } + return Sharpen{*amount, *radius, threshold_val.toInt32()}; +} +std::optional +ImageOptimizerOptions::to_pixels_or_percentage(JSContext *cx, JS::HandleValue val, + const std::string &field_name) { + auto throw_error = [&]() { + api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", field_name.data(), + "must be an integer pixel value or a string percentage value"); + }; + if (val.isInt32()) { + return PixelsOrPercentage{.pixels = val.toInt32(), .is_percentage = false}; + } + if (!val.isString()) { + throw_error(); + return std::nullopt; + } + JS::RootedString js_str(cx, val.toString()); + auto str = core::encode(cx, js_str); + auto percentage = from_percentage(str); + if (!percentage) { + throw_error(); + return std::nullopt; + } + return PixelsOrPercentage{.percentage = *percentage, .is_percentage = true}; +} + std::optional ImageOptimizerOptions::to_color(JSContext *cx, JS::HandleValue val, const std::string &field_name) { // Hex strings of size 3 or 6 are allowed diff --git a/runtime/fastly/builtins/image-optimizer.h b/runtime/fastly/builtins/image-optimizer.h index a2d3b7e750..90309dae34 100644 --- a/runtime/fastly/builtins/image-optimizer.h +++ b/runtime/fastly/builtins/image-optimizer.h @@ -43,12 +43,90 @@ class ImageOptimizerOptions { static std::optional to_##lowercase(JSContext *cx, JS::HandleValue val); #include "image-optimizer-options.inc" + struct Blur { + double value; + bool is_percentage; + }; + static std::optional to_blur(JSContext *cx, JS::HandleValue val); + + struct Brightness { + double value; + }; + static std::optional to_brightness(JSContext *cx, JS::HandleValue val); + struct Color { host_api::HostString val; }; static std::optional to_color(JSContext *cx, JS::HandleValue val, const std::string &field_name); + struct Contrast { + double value; + }; + static std::optional to_contrast(JSContext *cx, JS::HandleValue val); + + struct Dpr { + double value; + }; + static std::optional to_dpr(JSContext *cx, JS::HandleValue val); + + struct Frame { + int32_t value; + }; + static std::optional to_frame(JSContext *cx, JS::HandleValue val); + + struct PixelsOrPercentage { + union { + int pixels; + double percentage; + }; + bool is_percentage; + }; + static std::optional + to_pixels_or_percentage(JSContext *cx, JS::HandleValue val, const std::string &field_name); + + struct Height { + PixelsOrPercentage value; + }; + static std::optional to_height(JSContext *cx, JS::HandleValue val) { + auto ret = to_pixels_or_percentage(cx, val, "height"); + if (!ret) + return std::nullopt; + return Height{*ret}; + } + + struct Level { + host_api::HostString value; + }; + static std::optional to_level(JSContext *cx, JS::HandleValue val); + + struct Saturation { + double value; + }; + static std::optional to_saturation(JSContext *cx, JS::HandleValue val); + + struct Sharpen { + double amount; + double radius; + int32_t threshold; + }; + static std::optional to_sharpen(JSContext *cx, JS::HandleValue val); + + struct Viewbox { + int32_t value; + }; + static std::optional to_viewbox(JSContext *cx, JS::HandleValue val); + + struct Width { + PixelsOrPercentage value; + }; + static std::optional to_width(JSContext *cx, JS::HandleValue val) { + auto ret = to_pixels_or_percentage(cx, val, "width"); + if (!ret) + return std::nullopt; + return Width{*ret}; + } + struct BGColor { Color color; }; @@ -61,26 +139,47 @@ class ImageOptimizerOptions { private: ImageOptimizerOptions(Region region, std::optional auto_val, - std::optional bg_color, std::optional bw, - std::optional disable, std::optional enable, + std::optional bg_color, std::optional blur, + std::optional brightness, std::optional bw, + std::optional contrast, std::optional disable, + std::optional dpr, std::optional enable, std::optional fit, std::optional format, - std::optional metadata, std::optional optimize, - std::optional profile, std::optional resize_filter) - : region_(region), auto_(auto_val), bg_color_(std::move(bg_color)), bw_(bw), - disable_(disable), enable_(enable), fit_(fit), format_(format), metadata_(metadata), - optimize_(optimize), profile_(profile), resizeFilter_(resize_filter) {} + std::optional frame, std::optional height, + std::optional level, std::optional metadata, + std::optional optimize, std::optional orient, + std::optional profile, std::optional resize_filter, + std::optional saturation, std::optional sharpen, + std::optional viewbox, std::optional width) + : region_(region), auto_(auto_val), bg_color_(std::move(bg_color)), blur_(blur), bw_(bw), + contrast_(contrast), disable_(disable), dpr_(dpr), enable_(enable), fit_(fit), + format_(format), frame_(frame), height_(height), level_(std::move(level)), + metadata_(metadata), optimize_(optimize), orient_(orient), profile_(profile), + resizeFilter_(resize_filter), saturation_(saturation), sharpen_(sharpen), viewbox_(viewbox), + width_(width) {} Region region_; std::optional auto_; std::optional bg_color_; + std::optional blur_; + std::optional brightness_; std::optional bw_; + std::optional contrast_; std::optional disable_; + std::optional dpr_; std::optional enable_; std::optional fit_; std::optional format_; + std::optional frame_; + std::optional height_; + std::optional level_; std::optional metadata_; std::optional optimize_; + std::optional orient_; std::optional profile_; std::optional resizeFilter_; + std::optional saturation_; + std::optional sharpen_; + std::optional viewbox_; + std::optional width_; }; inline std::string to_string(const ImageOptimizerOptions &opts) { return opts.to_string(); } @@ -104,5 +203,49 @@ inline std::string to_string(const ImageOptimizerOptions::Color &c) { inline std::string to_string(const ImageOptimizerOptions::BGColor &bg) { return "bg-color=" + to_string(bg.color); } +inline std::string to_string(const ImageOptimizerOptions::Blur &blur) { + auto ret = "blur=" + std::to_string(blur.value); + if (blur.is_percentage) { + ret += 'p'; + } + return ret; +} +inline std::string to_string(const ImageOptimizerOptions::Brightness &brightness) { + return "brightness=" + std::to_string(brightness.value); +} +inline std::string to_string(const ImageOptimizerOptions::Contrast &contrast) { + return "contrast=" + std::to_string(contrast.value); +} +inline std::string to_string(const ImageOptimizerOptions::Dpr &dpr) { + return "dpr=" + std::to_string(dpr.value); +} +inline std::string to_string(const ImageOptimizerOptions::Frame &frame) { + return "frame=" + std::to_string(frame.value); +} +inline std::string to_string(const ImageOptimizerOptions::PixelsOrPercentage &value) { + if (value.is_percentage) { + return std::to_string(value.percentage) + 'p'; + } + return std::to_string(value.pixels); +} +inline std::string to_string(const ImageOptimizerOptions::Height &height) { + return "height=" + to_string(height.value); +} +inline std::string to_string(const ImageOptimizerOptions::Level &level) { + return "level=" + std::string(std::string_view(level.value)); +} +inline std::string to_string(const ImageOptimizerOptions::Saturation &saturation) { + return "saturation=" + std::to_string(saturation.value); +} +inline std::string to_string(const ImageOptimizerOptions::Sharpen &sharpen) { + return "sharpen=a" + std::to_string(sharpen.amount) + ",r" + std::to_string(sharpen.radius) + + ",t" + std::to_string(sharpen.threshold); +} +inline std::string to_string(const ImageOptimizerOptions::Viewbox &viewbox) { + return "viewbox=" + std::to_string(viewbox.value); +} +inline std::string to_string(const ImageOptimizerOptions::Width &width) { + return "width=" + to_string(width.value); +} } // namespace fastly::image_optimizer #endif \ No newline at end of file diff --git a/runtime/fastly/host-api/host_api.cpp b/runtime/fastly/host-api/host_api.cpp index 78c9efa77f..f551464b60 100644 --- a/runtime/fastly/host-api/host_api.cpp +++ b/runtime/fastly/host-api/host_api.cpp @@ -50,7 +50,6 @@ static void log_hostcall(const char *func_name, ...) { #define SECS_IN_NANOSECS 1000000000 #include static bool convert_result(int res, fastly::fastly_host_error *err) { - std::cerr << "CONVENRT_RESULT: " << res << std::endl; if (res == 0) return true; switch (res) { diff --git a/src/bundle.js b/src/bundle.js index d39c1326a0..88037c16d2 100644 --- a/src/bundle.js +++ b/src/bundle.js @@ -135,6 +135,7 @@ export const TransactionCacheEntry = globalThis.TransactionCacheEntry; contents: ` export const Region = globalThis.Region; export const Format = globalThis.Format; + export const Orient = globalThis.Orient; export const Auto = globalThis.Auto;`, }; } From 070a80b6f874bbe1812f6657a7f5892beb52d15d Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Fri, 7 Nov 2025 15:50:01 +0000 Subject: [PATCH 10/31] Canvas implemented --- runtime/fastly/builtins/image-optimizer.cpp | 183 +++++++++++++++++--- runtime/fastly/builtins/image-optimizer.h | 86 ++++++--- 2 files changed, 220 insertions(+), 49 deletions(-) diff --git a/runtime/fastly/builtins/image-optimizer.cpp b/runtime/fastly/builtins/image-optimizer.cpp index 7e948f55c6..c8a96a51da 100644 --- a/runtime/fastly/builtins/image-optimizer.cpp +++ b/runtime/fastly/builtins/image-optimizer.cpp @@ -24,6 +24,10 @@ std::optional to_number_between_inclusive(JS::HandleValue val, double fr } return num; } + +bool exactly_one_defined(JS::HandleValue a, JS::HandleValue b) { + return (!a.isUndefined() || !b.isUndefined()) && (a.isUndefined() != b.isUndefined()); +} } // namespace namespace fastly::image_optimizer { @@ -158,6 +162,7 @@ std::unique_ptr ImageOptimizerOptions::create(JSContext * TRY_CONVERT(bg_color); TRY_CONVERT(blur); TRY_CONVERT(brightness); + TRY_CONVERT(canvas); TRY_CONVERT(contrast); TRY_CONVERT(dpr); TRY_CONVERT(frame); @@ -170,9 +175,9 @@ std::unique_ptr ImageOptimizerOptions::create(JSContext * return std::unique_ptr{new ImageOptimizerOptions( *region_opt, auto_opt, std::move(bg_color_opt), blur_opt, brightness_opt, bw_opt, - contrast_opt, disable_opt, dpr_opt, enable_opt, fit_opt, format_opt, frame_opt, height_opt, - std::move(level_opt), metadata_opt, optimize_opt, orient_opt, profile_opt, resize_filter_opt, - saturation_opt, sharpen_opt, viewbox_opt, width_opt)}; + std::move(canvas_opt), contrast_opt, disable_opt, dpr_opt, enable_opt, fit_opt, format_opt, + frame_opt, height_opt, std::move(level_opt), metadata_opt, optimize_opt, orient_opt, + profile_opt, resize_filter_opt, saturation_opt, sharpen_opt, viewbox_opt, width_opt)}; } std::string ImageOptimizerOptions::to_string() const { @@ -187,6 +192,7 @@ std::string ImageOptimizerOptions::to_string() const { append(blur_); append(brightness_); append(bw_); + append(canvas_); append(contrast_); append(disable_); append(dpr_); @@ -209,7 +215,6 @@ std::string ImageOptimizerOptions::to_string() const { } // TODO -// canvas // crop // pad // precrop @@ -226,6 +231,118 @@ fastly_image_optimizer_transform_config ImageOptimizerOptions::to_config() { return config; } +std::optional ImageOptimizerOptions::to_canvas(JSContext *cx, + JS::HandleValue val) { + auto throw_error = [&](const char *object, const char *message) { + api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", object, message); + return std::nullopt; + }; + if (!val.isObject()) { + return throw_error("canvas", "be an object"); + } + JS::RootedValue size_val(cx); + JS::RootedValue position_val(cx); + JS::RootedObject canvas_obj(cx, &val.toObject()); + if (!JS_GetProperty(cx, canvas_obj, "size", &size_val) || + !JS_GetProperty(cx, canvas_obj, "position", &position_val)) { + return std::nullopt; + } + if (size_val.isUndefined() || !size_val.isObject()) { + return throw_error("canvas.size", "be an object"); + } + + JS::RootedValue absolute_val(cx); + JS::RootedValue ratio_val(cx); + JS::RootedObject size_obj(cx, &size_val.toObject()); + if (!JS_GetProperty(cx, size_obj, "absolute", &absolute_val) || + !JS_GetProperty(cx, size_obj, "ratio", &ratio_val)) { + return std::nullopt; + } + if (!exactly_one_defined(absolute_val, ratio_val)) { + return throw_error("canvas.size", "have exactly one of the keys absolute or ratio"); + } + + auto size = [&]() -> std::optional> { + JS::RootedValue width_val(cx); + JS::RootedValue height_val(cx); + if (!absolute_val.isUndefined()) { + if (!absolute_val.isObject()) { + return throw_error("canvas.size.absolute", "be an object"); + } + JS::RootedObject absolute_obj(cx, &absolute_val.toObject()); + if (!JS_GetProperty(cx, absolute_obj, "width", &width_val) || + !JS_GetProperty(cx, absolute_obj, "height", &height_val)) { + return std::nullopt; + } + auto width = to_pixels_or_percentage(cx, width_val); + auto height = to_pixels_or_percentage(cx, height_val); + if (!width || !height) { + return throw_error( + "canvas.size.absolute", + "have width and height values who are absolute pixel integers or percentage strings"); + } + return ImageOptimizerOptions::Canvas::Absolute{*width, *height}; + } else { + if (!ratio_val.isObject()) { + return throw_error("canvas.size.ratio", "be an object"); + } + JS::RootedObject ratio_obj(cx, &ratio_val.toObject()); + if (!JS_GetProperty(cx, ratio_obj, "width", &width_val) || + !JS_GetProperty(cx, ratio_obj, "height", &height_val)) { + return std::nullopt; + } + if (!width_val.isNumber() || !height_val.isNumber()) { + return throw_error("canvas.size.ratio", "have width and height number values"); + } + return ImageOptimizerOptions::Canvas::Ratio{width_val.toNumber(), height_val.toNumber()}; + } + }(); + if (!size) { + return std::nullopt; + } + + if (position_val.isUndefined()) { + return ImageOptimizerOptions::Canvas{*size, std::monostate{}}; + } + + JS::RootedValue x_val(cx); + JS::RootedValue y_val(cx); + JS::RootedValue offset_x_val(cx); + JS::RootedValue offset_y_val(cx); + + JS::RootedObject position_obj(cx, &position_val.toObject()); + if (!JS_GetProperty(cx, position_obj, "x", &x_val) || + !JS_GetProperty(cx, position_obj, "y", &y_val) || + !JS_GetProperty(cx, position_obj, "offsetX", &offset_x_val) || + !JS_GetProperty(cx, position_obj, "offsetY", &offset_y_val)) { + return std::nullopt; + } + if (!exactly_one_defined(x_val, offset_x_val) || !exactly_one_defined(y_val, offset_y_val)) { + return throw_error("canvas.position", + "have exactly one of x/offsetX and exactly one of y/offsetY"); + } + + auto absolute_or_offset = + [&](JS::HandleValue absolute, + JS::HandleValue offset) -> std::optional> { + if (!absolute.isUndefined()) { + return to_pixels_or_percentage(cx, absolute); + } + if (offset.isNumber()) { + return offset.toNumber(); + } + return std::nullopt; + }; + auto x = absolute_or_offset(x_val, offset_x_val); + auto y = absolute_or_offset(y_val, offset_y_val); + if (!x || !y) { + return throw_error("canvas.position", "have valid x and y components"); + } + + return ImageOptimizerOptions::Canvas{*size, ImageOptimizerOptions::Canvas::Position{*x, *y}}; +} + std::optional ImageOptimizerOptions::to_brightness(JSContext *cx, JS::HandleValue val) { auto num = to_number_between_inclusive(val, -100, 100); @@ -363,31 +480,24 @@ ImageOptimizerOptions::to_sharpen(JSContext *cx, JS::HandleValue val) { return Sharpen{*amount, *radius, threshold_val.toInt32()}; } std::optional -ImageOptimizerOptions::to_pixels_or_percentage(JSContext *cx, JS::HandleValue val, - const std::string &field_name) { - auto throw_error = [&]() { - api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", field_name.data(), - "must be an integer pixel value or a string percentage value"); - }; +ImageOptimizerOptions::to_pixels_or_percentage(JSContext *cx, JS::HandleValue val) { if (val.isInt32()) { return PixelsOrPercentage{.pixels = val.toInt32(), .is_percentage = false}; } if (!val.isString()) { - throw_error(); return std::nullopt; } JS::RootedString js_str(cx, val.toString()); auto str = core::encode(cx, js_str); auto percentage = from_percentage(str); if (!percentage) { - throw_error(); return std::nullopt; } return PixelsOrPercentage{.percentage = *percentage, .is_percentage = true}; } -std::optional -ImageOptimizerOptions::to_color(JSContext *cx, JS::HandleValue val, const std::string &field_name) { +std::optional ImageOptimizerOptions::to_color(JSContext *cx, + JS::HandleValue val) { // Hex strings of size 3 or 6 are allowed if (val.isString()) { JS::RootedString str_val(cx, val.toString()); @@ -397,15 +507,8 @@ ImageOptimizerOptions::to_color(JSContext *cx, JS::HandleValue val, const std::s return Color{std::move(str)}; } } - - auto throw_error = [&]() { - api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", field_name.data(), - "must be a 3/6 character hex string or RGB(A) object"); - }; - // Otherwise, it should be an rgb(a) object if (!val.isObject()) { - throw_error(); return std::nullopt; } @@ -426,7 +529,6 @@ ImageOptimizerOptions::to_color(JSContext *cx, JS::HandleValue val, const std::s a.isUndefined() || (a.isNumber() && a.toNumber() >= 0.0 && a.toNumber() <= 1.0); if (!is_valid_color_component(r) || !is_valid_color_component(g) || !is_valid_color_component(b) || !alpha_valid) { - throw_error(); return std::nullopt; } @@ -439,6 +541,43 @@ ImageOptimizerOptions::to_color(JSContext *cx, JS::HandleValue val, const std::s return Color{host_api::HostString(rep)}; } +template struct overload : Ts... { + using Ts::operator()...; +}; +template overload(Ts...) -> overload; + +std::string to_string(const ImageOptimizerOptions::Canvas &canvas) { + std::string ret = "canvas="; + std::visit(overload( + [&](ImageOptimizerOptions::Canvas::Absolute abs) { + ret += to_string(abs.width) + ',' + to_string(abs.height); + }, + [&](ImageOptimizerOptions::Canvas::Ratio ratio) { + ret += ratio.width_ratio + ':' + ratio.height_ratio; + }), + canvas.size); + std::visit(overload( + [&](const ImageOptimizerOptions::Canvas::Position &pos) { + ret += ','; + std::visit(overload( + [&](ImageOptimizerOptions::PixelsOrPercentage x) { + ret += 'x' + to_string(x); + }, + [&](double x) { ret += "offset-x" + std::to_string(x); }), + pos.x); + std::visit(overload( + [&](ImageOptimizerOptions::PixelsOrPercentage y) { + ret += 'y' + to_string(y); + }, + [&](double y) { ret += "offset-y" + std::to_string(y); }), + pos.y); + }, + [](std::monostate) {}), + canvas.position); + + return ret; +} + #define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase, str) \ std::optional ImageOptimizerOptions::to_##lowercase( \ JSContext *cx, JS::HandleValue val) { \ diff --git a/runtime/fastly/builtins/image-optimizer.h b/runtime/fastly/builtins/image-optimizer.h index 90309dae34..3001cf90d0 100644 --- a/runtime/fastly/builtins/image-optimizer.h +++ b/runtime/fastly/builtins/image-optimizer.h @@ -6,6 +6,7 @@ #include "encode.h" #include "extension-api.h" #include +#include #define HANDLE_IMAGE_OPTIMIZER_ERROR(cx, err) \ ::host_api::handle_image_optimizer_error(cx, err, __LINE__, __func__) @@ -54,11 +55,40 @@ class ImageOptimizerOptions { }; static std::optional to_brightness(JSContext *cx, JS::HandleValue val); + struct PixelsOrPercentage { + union { + int pixels; + double percentage; + }; + bool is_percentage; + }; + static std::optional to_pixels_or_percentage(JSContext *cx, + JS::HandleValue val); + + struct Canvas { + struct Absolute { + PixelsOrPercentage width; + PixelsOrPercentage height; + }; + struct Ratio { + double width_ratio; + double height_ratio; + }; + struct Position { + // Absolute or offset + std::variant x; + std::variant y; + }; + + std::variant size; + std::variant position; + }; + static std::optional to_canvas(JSContext *cx, JS::HandleValue val); + struct Color { host_api::HostString val; }; - static std::optional to_color(JSContext *cx, JS::HandleValue val, - const std::string &field_name); + static std::optional to_color(JSContext *cx, JS::HandleValue val); struct Contrast { double value; @@ -75,23 +105,16 @@ class ImageOptimizerOptions { }; static std::optional to_frame(JSContext *cx, JS::HandleValue val); - struct PixelsOrPercentage { - union { - int pixels; - double percentage; - }; - bool is_percentage; - }; - static std::optional - to_pixels_or_percentage(JSContext *cx, JS::HandleValue val, const std::string &field_name); - struct Height { PixelsOrPercentage value; }; static std::optional to_height(JSContext *cx, JS::HandleValue val) { - auto ret = to_pixels_or_percentage(cx, val, "height"); - if (!ret) + auto ret = to_pixels_or_percentage(cx, val); + if (!ret) { + api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", "height", + "must be an integer pixel value or a string percentage value"); return std::nullopt; + } return Height{*ret}; } @@ -121,9 +144,12 @@ class ImageOptimizerOptions { PixelsOrPercentage value; }; static std::optional to_width(JSContext *cx, JS::HandleValue val) { - auto ret = to_pixels_or_percentage(cx, val, "width"); - if (!ret) + auto ret = to_pixels_or_percentage(cx, val); + if (!ret) { + api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", "width", + "must be an integer pixel value or a string percentage value"); return std::nullopt; + } return Width{*ret}; } @@ -131,9 +157,12 @@ class ImageOptimizerOptions { Color color; }; static std::optional to_bg_color(JSContext *cx, JS::HandleValue val) { - auto color = to_color(cx, val, "bgColor"); - if (!color) + auto color = to_color(cx, val); + if (!color) { + api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", "bgColor", + "must be a 3/6 character hex string or RGB(A) object"); return std::nullopt; + } return BGColor{std::move(*color)}; } @@ -141,18 +170,19 @@ class ImageOptimizerOptions { ImageOptimizerOptions(Region region, std::optional auto_val, std::optional bg_color, std::optional blur, std::optional brightness, std::optional bw, - std::optional contrast, std::optional disable, - std::optional dpr, std::optional enable, - std::optional fit, std::optional format, - std::optional frame, std::optional height, - std::optional level, std::optional metadata, - std::optional optimize, std::optional orient, - std::optional profile, std::optional resize_filter, + std::optional canvas, std::optional contrast, + std::optional disable, std::optional dpr, + std::optional enable, std::optional fit, + std::optional format, std::optional frame, + std::optional height, std::optional level, + std::optional metadata, std::optional optimize, + std::optional orient, std::optional profile, + std::optional resize_filter, std::optional saturation, std::optional sharpen, std::optional viewbox, std::optional width) : region_(region), auto_(auto_val), bg_color_(std::move(bg_color)), blur_(blur), bw_(bw), - contrast_(contrast), disable_(disable), dpr_(dpr), enable_(enable), fit_(fit), - format_(format), frame_(frame), height_(height), level_(std::move(level)), + canvas_(canvas), contrast_(contrast), disable_(disable), dpr_(dpr), enable_(enable), + fit_(fit), format_(format), frame_(frame), height_(height), level_(std::move(level)), metadata_(metadata), optimize_(optimize), orient_(orient), profile_(profile), resizeFilter_(resize_filter), saturation_(saturation), sharpen_(sharpen), viewbox_(viewbox), width_(width) {} @@ -162,6 +192,7 @@ class ImageOptimizerOptions { std::optional blur_; std::optional brightness_; std::optional bw_; + std::optional canvas_; std::optional contrast_; std::optional disable_; std::optional dpr_; @@ -213,6 +244,7 @@ inline std::string to_string(const ImageOptimizerOptions::Blur &blur) { inline std::string to_string(const ImageOptimizerOptions::Brightness &brightness) { return "brightness=" + std::to_string(brightness.value); } +std::string to_string(const ImageOptimizerOptions::Canvas &canvas); inline std::string to_string(const ImageOptimizerOptions::Contrast &contrast) { return "contrast=" + std::to_string(contrast.value); } From a41bb51760e3498e8ecc9456bce966974b4b6dc6 Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Tue, 11 Nov 2025 16:26:20 +0000 Subject: [PATCH 11/31] Options all implemented --- .../builtins/image-optimizer-options.inc | 5 + runtime/fastly/builtins/image-optimizer.cpp | 350 ++++++++++++------ runtime/fastly/builtins/image-optimizer.h | 217 +++++++++-- src/bundle.js | 1 + 4 files changed, 434 insertions(+), 139 deletions(-) diff --git a/runtime/fastly/builtins/image-optimizer-options.inc b/runtime/fastly/builtins/image-optimizer-options.inc index 97b0601faf..50f09377e7 100644 --- a/runtime/fastly/builtins/image-optimizer-options.inc +++ b/runtime/fastly/builtins/image-optimizer-options.inc @@ -106,6 +106,11 @@ FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Lanczos3, "lanczos3") FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Lanczos, "lanczos") FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(ResizeFilter) +FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(CropMode, crop_mode, "N/A") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Smart, "smart") +FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Safe, "safe") +FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(Optimize) + #undef FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION #undef FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE #undef FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE \ No newline at end of file diff --git a/runtime/fastly/builtins/image-optimizer.cpp b/runtime/fastly/builtins/image-optimizer.cpp index c8a96a51da..b66f19f1ce 100644 --- a/runtime/fastly/builtins/image-optimizer.cpp +++ b/runtime/fastly/builtins/image-optimizer.cpp @@ -164,20 +164,27 @@ std::unique_ptr ImageOptimizerOptions::create(JSContext * TRY_CONVERT(brightness); TRY_CONVERT(canvas); TRY_CONVERT(contrast); + TRY_CONVERT(crop); TRY_CONVERT(dpr); TRY_CONVERT(frame); TRY_CONVERT(height); TRY_CONVERT(level); + TRY_CONVERT(pad); + TRY_CONVERT(precrop); + TRY_CONVERT(quality); TRY_CONVERT(saturation); TRY_CONVERT(sharpen); + TRY_CONVERT(trim); + TRY_CONVERT(trim_color); TRY_CONVERT(viewbox); TRY_CONVERT(width); return std::unique_ptr{new ImageOptimizerOptions( - *region_opt, auto_opt, std::move(bg_color_opt), blur_opt, brightness_opt, bw_opt, - std::move(canvas_opt), contrast_opt, disable_opt, dpr_opt, enable_opt, fit_opt, format_opt, - frame_opt, height_opt, std::move(level_opt), metadata_opt, optimize_opt, orient_opt, - profile_opt, resize_filter_opt, saturation_opt, sharpen_opt, viewbox_opt, width_opt)}; + *region_opt, auto_opt, std::move(bg_color_opt), blur_opt, brightness_opt, bw_opt, canvas_opt, + contrast_opt, crop_opt, disable_opt, dpr_opt, enable_opt, fit_opt, format_opt, frame_opt, + height_opt, std::move(level_opt), metadata_opt, optimize_opt, orient_opt, pad_opt, + precrop_opt, profile_opt, quality_opt, resize_filter_opt, saturation_opt, sharpen_opt, + trim_opt, std::move(trim_color_opt), viewbox_opt, width_opt)}; } std::string ImageOptimizerOptions::to_string() const { @@ -193,6 +200,7 @@ std::string ImageOptimizerOptions::to_string() const { append(brightness_); append(bw_); append(canvas_); + append(crop_); append(contrast_); append(disable_); append(dpr_); @@ -205,22 +213,22 @@ std::string ImageOptimizerOptions::to_string() const { append(metadata_); append(optimize_); append(orient_); + append(pad_); + append(precrop_); append(profile_); + append(quality_); append(resizeFilter_); append(saturation_); append(sharpen_); + append(trim_); + append(trim_color_); append(viewbox_); append(width_); return ret; } // TODO -// crop // pad -// precrop -// quality -// trim -// trimcolor fastly_image_optimizer_transform_config ImageOptimizerOptions::to_config() { fastly_image_optimizer_transform_config config; @@ -231,87 +239,75 @@ fastly_image_optimizer_transform_config ImageOptimizerOptions::to_config() { return config; } -std::optional ImageOptimizerOptions::to_canvas(JSContext *cx, - JS::HandleValue val) { +std::optional ImageOptimizerOptions::to_size(JSContext *cx, + JS::HandleValue val) { auto throw_error = [&](const char *object, const char *message) { api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", object, message); return std::nullopt; }; - if (!val.isObject()) { - return throw_error("canvas", "be an object"); - } - JS::RootedValue size_val(cx); - JS::RootedValue position_val(cx); - JS::RootedObject canvas_obj(cx, &val.toObject()); - if (!JS_GetProperty(cx, canvas_obj, "size", &size_val) || - !JS_GetProperty(cx, canvas_obj, "position", &position_val)) { - return std::nullopt; - } - if (size_val.isUndefined() || !size_val.isObject()) { - return throw_error("canvas.size", "be an object"); + + if (val.isUndefined() || !val.isObject()) { + return throw_error("size", "be an object"); } JS::RootedValue absolute_val(cx); JS::RootedValue ratio_val(cx); - JS::RootedObject size_obj(cx, &size_val.toObject()); + JS::RootedObject size_obj(cx, &val.toObject()); if (!JS_GetProperty(cx, size_obj, "absolute", &absolute_val) || !JS_GetProperty(cx, size_obj, "ratio", &ratio_val)) { return std::nullopt; } if (!exactly_one_defined(absolute_val, ratio_val)) { - return throw_error("canvas.size", "have exactly one of the keys absolute or ratio"); - } - - auto size = [&]() -> std::optional> { - JS::RootedValue width_val(cx); - JS::RootedValue height_val(cx); - if (!absolute_val.isUndefined()) { - if (!absolute_val.isObject()) { - return throw_error("canvas.size.absolute", "be an object"); - } - JS::RootedObject absolute_obj(cx, &absolute_val.toObject()); - if (!JS_GetProperty(cx, absolute_obj, "width", &width_val) || - !JS_GetProperty(cx, absolute_obj, "height", &height_val)) { - return std::nullopt; - } - auto width = to_pixels_or_percentage(cx, width_val); - auto height = to_pixels_or_percentage(cx, height_val); - if (!width || !height) { - return throw_error( - "canvas.size.absolute", - "have width and height values who are absolute pixel integers or percentage strings"); - } - return ImageOptimizerOptions::Canvas::Absolute{*width, *height}; - } else { - if (!ratio_val.isObject()) { - return throw_error("canvas.size.ratio", "be an object"); - } - JS::RootedObject ratio_obj(cx, &ratio_val.toObject()); - if (!JS_GetProperty(cx, ratio_obj, "width", &width_val) || - !JS_GetProperty(cx, ratio_obj, "height", &height_val)) { - return std::nullopt; - } - if (!width_val.isNumber() || !height_val.isNumber()) { - return throw_error("canvas.size.ratio", "have width and height number values"); - } - return ImageOptimizerOptions::Canvas::Ratio{width_val.toNumber(), height_val.toNumber()}; - } - }(); - if (!size) { - return std::nullopt; + return throw_error("size", "have exactly one of the keys absolute or ratio"); } - if (position_val.isUndefined()) { - return ImageOptimizerOptions::Canvas{*size, std::monostate{}}; + JS::RootedValue width_val(cx); + JS::RootedValue height_val(cx); + if (!absolute_val.isUndefined()) { + if (!absolute_val.isObject()) { + return throw_error("size.absolute", "be an object"); + } + JS::RootedObject absolute_obj(cx, &absolute_val.toObject()); + if (!JS_GetProperty(cx, absolute_obj, "width", &width_val) || + !JS_GetProperty(cx, absolute_obj, "height", &height_val)) { + return std::nullopt; + } + auto width = to_pixels_or_percentage(cx, width_val); + auto height = to_pixels_or_percentage(cx, height_val); + if (!width || !height) { + return throw_error( + "size.absolute", + "have width and height values who are absolute pixel integers or percentage strings"); + } + return Size{Size::Absolute{*width, *height}}; + } else { + if (!ratio_val.isObject()) { + return throw_error("size.ratio", "be an object"); + } + JS::RootedObject ratio_obj(cx, &ratio_val.toObject()); + if (!JS_GetProperty(cx, ratio_obj, "width", &width_val) || + !JS_GetProperty(cx, ratio_obj, "height", &height_val)) { + return std::nullopt; + } + if (!width_val.isNumber() || !height_val.isNumber()) { + return throw_error("size.ratio", "have width and height number values"); + } + return Size{Size::Ratio{width_val.toNumber(), height_val.toNumber()}}; } +} +std::optional +ImageOptimizerOptions::to_position(JSContext *cx, JS::HandleValue val) { + auto throw_error = [&](const char *object, const char *message) { + api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", object, message); + return std::nullopt; + }; JS::RootedValue x_val(cx); JS::RootedValue y_val(cx); JS::RootedValue offset_x_val(cx); JS::RootedValue offset_y_val(cx); - JS::RootedObject position_obj(cx, &position_val.toObject()); + JS::RootedObject position_obj(cx, &val.toObject()); if (!JS_GetProperty(cx, position_obj, "x", &x_val) || !JS_GetProperty(cx, position_obj, "y", &y_val) || !JS_GetProperty(cx, position_obj, "offsetX", &offset_x_val) || @@ -319,8 +315,7 @@ std::optional ImageOptimizerOptions::to_canvas(JS return std::nullopt; } if (!exactly_one_defined(x_val, offset_x_val) || !exactly_one_defined(y_val, offset_y_val)) { - return throw_error("canvas.position", - "have exactly one of x/offsetX and exactly one of y/offsetY"); + return throw_error("position", "have exactly one of x/offsetX and exactly one of y/offsetY"); } auto absolute_or_offset = @@ -337,10 +332,82 @@ std::optional ImageOptimizerOptions::to_canvas(JS auto x = absolute_or_offset(x_val, offset_x_val); auto y = absolute_or_offset(y_val, offset_y_val); if (!x || !y) { - return throw_error("canvas.position", "have valid x and y components"); + return throw_error("position", "have valid x and y components"); + } + + return ImageOptimizerOptions::Position{*x, *y}; +} + +std::optional ImageOptimizerOptions::to_canvas(JSContext *cx, + JS::HandleValue val) { + auto throw_error = [&](const char *object, const char *message) { + api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", object, message); + return std::nullopt; + }; + if (!val.isObject()) { + return throw_error("canvas", "be an object"); + } + JS::RootedValue size_val(cx); + JS::RootedValue position_val(cx); + JS::RootedObject canvas_obj(cx, &val.toObject()); + if (!JS_GetProperty(cx, canvas_obj, "size", &size_val) || + !JS_GetProperty(cx, canvas_obj, "position", &position_val)) { + return std::nullopt; + } + auto size = to_size(cx, size_val); + if (!size) { + return std::nullopt; + } + + if (position_val.isUndefined()) { + return ImageOptimizerOptions::Canvas{*size, std::nullopt}; + } + auto position = to_position(cx, position_val); + if (!position) { + return std::nullopt; + } + return ImageOptimizerOptions::Canvas{*size, *position}; +} + +std::optional +ImageOptimizerOptions::to_crop_spec(JSContext *cx, JS::HandleValue val) { + auto throw_error = [&](const char *object, const char *message) { + api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", object, message); + return std::nullopt; + }; + if (!val.isObject()) { + return throw_error("crop", "be an object"); + } + JS::RootedValue size_val(cx); + JS::RootedValue position_val(cx); + JS::RootedValue mode_val(cx); + JS::RootedObject crop_obj(cx, &val.toObject()); + if (!JS_GetProperty(cx, crop_obj, "size", &size_val) || + !JS_GetProperty(cx, crop_obj, "position", &position_val) || + !JS_GetProperty(cx, crop_obj, "mode", &mode_val)) { + return std::nullopt; + } + auto size = to_size(cx, size_val); + if (!size) { + return std::nullopt; + } + + std::optional position = std::nullopt; + if (!position_val.isUndefined()) { + position = to_position(cx, position_val); + if (!position) { + return std::nullopt; + } } - return ImageOptimizerOptions::Canvas{*size, ImageOptimizerOptions::Canvas::Position{*x, *y}}; + std::optional mode = std::nullopt; + if (!mode_val.isUndefined()) { + mode = to_crop_mode(cx, mode_val); + if (!mode) { + return std::nullopt; + } + } + return ImageOptimizerOptions::CropSpec{*size, position, mode}; } std::optional @@ -363,6 +430,15 @@ ImageOptimizerOptions::to_contrast(JSContext *cx, JS::HandleValue val) { } return Contrast{*num}; } +std::optional +ImageOptimizerOptions::to_quality(JSContext *cx, JS::HandleValue val) { + if (!val.isInt32() || val.toInt32() < 0 || val.toInt32() > 100) { + api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", "quality", + "must be n integer between 0 and 100"); + return std::nullopt; + } + return Quality(val.toInt32()); +} std::optional ImageOptimizerOptions::to_saturation(JSContext *cx, JS::HandleValue val) { auto num = to_number_between_inclusive(val, -100, 100); @@ -541,41 +617,103 @@ std::optional ImageOptimizerOptions::to_color(JSCo return Color{host_api::HostString(rep)}; } -template struct overload : Ts... { - using Ts::operator()...; -}; -template overload(Ts...) -> overload; - -std::string to_string(const ImageOptimizerOptions::Canvas &canvas) { - std::string ret = "canvas="; - std::visit(overload( - [&](ImageOptimizerOptions::Canvas::Absolute abs) { - ret += to_string(abs.width) + ',' + to_string(abs.height); - }, - [&](ImageOptimizerOptions::Canvas::Ratio ratio) { - ret += ratio.width_ratio + ':' + ratio.height_ratio; - }), - canvas.size); - std::visit(overload( - [&](const ImageOptimizerOptions::Canvas::Position &pos) { - ret += ','; - std::visit(overload( - [&](ImageOptimizerOptions::PixelsOrPercentage x) { - ret += 'x' + to_string(x); - }, - [&](double x) { ret += "offset-x" + std::to_string(x); }), - pos.x); - std::visit(overload( - [&](ImageOptimizerOptions::PixelsOrPercentage y) { - ret += 'y' + to_string(y); - }, - [&](double y) { ret += "offset-y" + std::to_string(y); }), - pos.y); - }, - [](std::monostate) {}), - canvas.position); +std::optional ImageOptimizerOptions::to_sides(JSContext *cx, + JS::HandleValue val) { + if (!val.isObject()) { + return std::nullopt; + } - return ret; + JS::RootedValue top_val(cx); + JS::RootedValue bottom_val(cx); + JS::RootedValue left_val(cx); + JS::RootedValue right_val(cx); + JS::RootedObject sides_obj(cx, &val.toObject()); + if (!JS_GetProperty(cx, sides_obj, "top", &top_val) || + !JS_GetProperty(cx, sides_obj, "bottom", &bottom_val) || + !JS_GetProperty(cx, sides_obj, "left", &left_val) || + !JS_GetProperty(cx, sides_obj, "right", &right_val)) { + return std::nullopt; + } + + if (top_val.isUndefined() || bottom_val.isUndefined() || left_val.isUndefined() || + right_val.isUndefined()) { + return std::nullopt; + } + + auto top = to_pixels_or_percentage(cx, top_val); + auto bottom = to_pixels_or_percentage(cx, bottom_val); + auto left = to_pixels_or_percentage(cx, left_val); + auto right = to_pixels_or_percentage(cx, right_val); + + if (!top || !bottom || !left || !right) { + return std::nullopt; + } + + return Sides{ + .top = *top, + .right = *right, + .bottom = *bottom, + .left = *left, + }; +} + +std::optional +ImageOptimizerOptions::to_trim_color(JSContext *cx, JS::HandleValue val) { + auto throw_error = [&]() { + api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", "trimColor", + "must be either a color, or an object with color and threshold elements, " + "where threshold is a number between 0 and 1"); + return std::nullopt; + }; + + if (val.isString()) { + auto str = core::encode(cx, val); + // We also support "auto" as a color + if (str == std::string_view("auto")) { + return TrimColor{Color{std::move(str)}}; + } + + auto color = to_color(cx, val); + if (!color) { + return throw_error(); + } + return TrimColor{*std::move(color), std::nullopt}; + } + + if (!val.isObject()) { + return throw_error(); + } + + JS::RootedValue color_val(cx); + JS::RootedValue threshold_val(cx); + JS::RootedObject trim_color_obj(cx, &val.toObject()); + if (!JS_GetProperty(cx, trim_color_obj, "color", &color_val) || + !JS_GetProperty(cx, trim_color_obj, "threshold", &threshold_val)) { + return std::nullopt; + } + + // Could be an rgb(a) object + if (color_val.isUndefined()) { + auto color = to_color(cx, val); + if (!color) { + return throw_error(); + } + return TrimColor{*std::move(color), std::nullopt}; + } + + auto color = to_color(cx, color_val); + if (!color) { + return throw_error(); + } + + std::optional threshold = std::nullopt; + if (threshold_val.isNumber()) { + threshold = to_number_between_inclusive(threshold_val, 0, 1); + if (!threshold) { + return throw_error(); + } + } + return TrimColor{*std::move(color), threshold}; } #define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase, str) \ diff --git a/runtime/fastly/builtins/image-optimizer.h b/runtime/fastly/builtins/image-optimizer.h index 3001cf90d0..9cec7867cb 100644 --- a/runtime/fastly/builtins/image-optimizer.h +++ b/runtime/fastly/builtins/image-optimizer.h @@ -65,7 +65,7 @@ class ImageOptimizerOptions { static std::optional to_pixels_or_percentage(JSContext *cx, JS::HandleValue val); - struct Canvas { + struct Size { struct Absolute { PixelsOrPercentage width; PixelsOrPercentage height; @@ -74,14 +74,19 @@ class ImageOptimizerOptions { double width_ratio; double height_ratio; }; - struct Position { - // Absolute or offset - std::variant x; - std::variant y; - }; + std::variant value; + }; + static std::optional to_size(JSContext *cx, JS::HandleValue val); - std::variant size; - std::variant position; + struct Position { + // Absolute or offset + std::variant x; + std::variant y; + }; + static std::optional to_position(JSContext *cx, JS::HandleValue val); + struct Canvas { + Size size; + std::optional position; }; static std::optional to_canvas(JSContext *cx, JS::HandleValue val); @@ -95,6 +100,24 @@ class ImageOptimizerOptions { }; static std::optional to_contrast(JSContext *cx, JS::HandleValue val); + // Deduplicates between Crop and Precrop + struct CropSpec { + Size size; + std::optional position; + std::optional mode; + }; + static std::optional to_crop_spec(JSContext *cx, JS::HandleValue val); + + struct Crop { + CropSpec value; + }; + static std::optional to_crop(JSContext *cx, JS::HandleValue val) { + auto value = to_crop_spec(cx, val); + if (!value) + return std::nullopt; + return Crop{*value}; + } + struct Dpr { double value; }; @@ -123,6 +146,41 @@ class ImageOptimizerOptions { }; static std::optional to_level(JSContext *cx, JS::HandleValue val); + struct Sides { + PixelsOrPercentage top, right, bottom, left; + }; + static std::optional to_sides(JSContext *cx, JS::HandleValue val); + + struct Pad { + Sides value; + }; + static std::optional to_pad(JSContext *cx, JS::HandleValue val) { + auto sides = to_sides(cx, val); + if (!sides) { + api::throw_error( + cx, api::Errors::TypeError, "imageOptimizerOptions", "trim", + "must be an object with top, right, bottom, and left elements, each being an " + "integer or a string percentage value"); + return std::nullopt; + } + return Pad{*sides}; + } + + struct Precrop { + CropSpec value; + }; + static std::optional to_precrop(JSContext *cx, JS::HandleValue val) { + auto value = to_crop_spec(cx, val); + if (!value) + return std::nullopt; + return Precrop{*value}; + } + + struct Quality { + uint32_t value; + }; + static std::optional to_quality(JSContext *cx, JS::HandleValue val); + struct Saturation { double value; }; @@ -135,6 +193,27 @@ class ImageOptimizerOptions { }; static std::optional to_sharpen(JSContext *cx, JS::HandleValue val); + struct Trim { + Sides value; + }; + static std::optional to_trim(JSContext *cx, JS::HandleValue val) { + auto sides = to_sides(cx, val); + if (!sides) { + api::throw_error( + cx, api::Errors::TypeError, "imageOptimizerOptions", "trim", + "must be an object with top, right, bottom, and left elements, each being an " + "integer or a string percentage value"); + return std::nullopt; + } + return Trim{*sides}; + } + + struct TrimColor { + Color color; + std::optional threshold; + }; + static std::optional to_trim_color(JSContext *cx, JS::HandleValue val); + struct Viewbox { int32_t value; }; @@ -167,25 +246,26 @@ class ImageOptimizerOptions { } private: - ImageOptimizerOptions(Region region, std::optional auto_val, - std::optional bg_color, std::optional blur, - std::optional brightness, std::optional bw, - std::optional canvas, std::optional contrast, - std::optional disable, std::optional dpr, - std::optional enable, std::optional fit, - std::optional format, std::optional frame, - std::optional height, std::optional level, - std::optional metadata, std::optional optimize, - std::optional orient, std::optional profile, - std::optional resize_filter, - std::optional saturation, std::optional sharpen, - std::optional viewbox, std::optional width) + ImageOptimizerOptions( + Region region, std::optional auto_val, std::optional bg_color, + std::optional blur, std::optional brightness, std::optional bw, + std::optional canvas, std::optional contrast, std::optional crop, + std::optional disable, std::optional dpr, std::optional enable, + std::optional fit, std::optional format, std::optional frame, + std::optional height, std::optional level, std::optional metadata, + std::optional optimize, std::optional orient, std::optional pad, + std::optional precrop, std::optional profile, + std::optional quality, std::optional resize_filter, + std::optional saturation, std::optional sharpen, + std::optional trim, std::optional trim_color, std::optional viewbox, + std::optional width) : region_(region), auto_(auto_val), bg_color_(std::move(bg_color)), blur_(blur), bw_(bw), - canvas_(canvas), contrast_(contrast), disable_(disable), dpr_(dpr), enable_(enable), - fit_(fit), format_(format), frame_(frame), height_(height), level_(std::move(level)), - metadata_(metadata), optimize_(optimize), orient_(orient), profile_(profile), - resizeFilter_(resize_filter), saturation_(saturation), sharpen_(sharpen), viewbox_(viewbox), - width_(width) {} + canvas_(canvas), contrast_(contrast), crop_(crop), disable_(disable), dpr_(dpr), + enable_(enable), fit_(fit), format_(format), frame_(frame), height_(height), + level_(std::move(level)), metadata_(metadata), optimize_(optimize), orient_(orient), + pad_(pad), precrop_(precrop), profile_(profile), quality_(quality), + resizeFilter_(resize_filter), saturation_(saturation), sharpen_(sharpen), trim_(trim), + trim_color_(std::move(trim_color)), viewbox_(viewbox), width_(width) {} Region region_; std::optional auto_; std::optional bg_color_; @@ -194,6 +274,7 @@ class ImageOptimizerOptions { std::optional bw_; std::optional canvas_; std::optional contrast_; + std::optional crop_; std::optional disable_; std::optional dpr_; std::optional enable_; @@ -205,10 +286,15 @@ class ImageOptimizerOptions { std::optional metadata_; std::optional optimize_; std::optional orient_; + std::optional pad_; + std::optional precrop_; std::optional profile_; + std::optional quality_; std::optional resizeFilter_; std::optional saturation_; std::optional sharpen_; + std::optional trim_; + std::optional trim_color_; std::optional viewbox_; std::optional width_; }; @@ -244,31 +330,96 @@ inline std::string to_string(const ImageOptimizerOptions::Blur &blur) { inline std::string to_string(const ImageOptimizerOptions::Brightness &brightness) { return "brightness=" + std::to_string(brightness.value); } -std::string to_string(const ImageOptimizerOptions::Canvas &canvas); +inline std::string to_string(const ImageOptimizerOptions::PixelsOrPercentage &value) { + if (value.is_percentage) { + return std::to_string(value.percentage) + 'p'; + } + return std::to_string(value.pixels); +} +inline std::string to_string(const ImageOptimizerOptions::Size &size) { + if (auto abs = std::get_if(&size.value)) { + return to_string(abs->width) + ',' + to_string(abs->height); + } + auto ratio = std::get(size.value); + return std::to_string(ratio.width_ratio) + ':' + std::to_string(ratio.height_ratio); +} +inline std::string to_string(const ImageOptimizerOptions::Position &position) { + std::string ret; + if (auto value = std::get_if(&position.x)) { + ret += 'x' + to_string(*value); + } else { + auto dbl = std::get(position.x); + ret += "offset-x" + std::to_string(dbl); + } + if (auto value = std::get_if(&position.y)) { + ret += 'y' + to_string(*value); + } else { + auto dbl = std::get(position.y); + ret += "offset-y" + std::to_string(dbl); + } + return ret; +} +inline std::string to_string(const ImageOptimizerOptions::Canvas &canvas) { + std::string ret = "canvas=" + to_string(canvas.size); + if (canvas.position) { + ret += ',' + to_string(*canvas.position); + } + return ret; +} inline std::string to_string(const ImageOptimizerOptions::Contrast &contrast) { return "contrast=" + std::to_string(contrast.value); } +inline std::string to_string(const ImageOptimizerOptions::CropSpec &crop) { + std::string ret = to_string(crop.size); + if (crop.position) { + ret += ',' + to_string(*crop.position); + } + if (crop.mode) { + ret += ',' + to_string(*crop.mode); + } + return ret; +} +inline std::string to_string(const ImageOptimizerOptions::Crop &crop) { + return "crop=" + to_string(crop.value); +} inline std::string to_string(const ImageOptimizerOptions::Dpr &dpr) { return "dpr=" + std::to_string(dpr.value); } inline std::string to_string(const ImageOptimizerOptions::Frame &frame) { return "frame=" + std::to_string(frame.value); } -inline std::string to_string(const ImageOptimizerOptions::PixelsOrPercentage &value) { - if (value.is_percentage) { - return std::to_string(value.percentage) + 'p'; - } - return std::to_string(value.pixels); -} inline std::string to_string(const ImageOptimizerOptions::Height &height) { return "height=" + to_string(height.value); } inline std::string to_string(const ImageOptimizerOptions::Level &level) { return "level=" + std::string(std::string_view(level.value)); } +inline std::string to_string(const ImageOptimizerOptions::Sides &sides) { + return to_string(sides.top) + ',' + to_string(sides.right) + ',' + to_string(sides.bottom) + ',' + + to_string(sides.left); +} +inline std::string to_string(const ImageOptimizerOptions::Pad &pad) { + return "pad=" + to_string(pad.value); +} +inline std::string to_string(const ImageOptimizerOptions::Precrop &precrop) { + return "precrop=" + to_string(precrop.value); +} +inline std::string to_string(const ImageOptimizerOptions::Quality &quality) { + return "quality=" + std::to_string(quality.value); +} inline std::string to_string(const ImageOptimizerOptions::Saturation &saturation) { return "saturation=" + std::to_string(saturation.value); } +inline std::string to_string(const ImageOptimizerOptions::Trim &trim) { + return "trim=" + to_string(trim.value); +} +inline std::string to_string(const ImageOptimizerOptions::TrimColor &trim_color) { + std::string ret = "trim-color=" + to_string(trim_color.color); + if (trim_color.threshold) { + ret += ",t" + std::to_string(*trim_color.threshold); + } + return ret; +} inline std::string to_string(const ImageOptimizerOptions::Sharpen &sharpen) { return "sharpen=a" + std::to_string(sharpen.amount) + ",r" + std::to_string(sharpen.radius) + ",t" + std::to_string(sharpen.threshold); diff --git a/src/bundle.js b/src/bundle.js index 88037c16d2..b5ecb456f2 100644 --- a/src/bundle.js +++ b/src/bundle.js @@ -136,6 +136,7 @@ export const TransactionCacheEntry = globalThis.TransactionCacheEntry; export const Region = globalThis.Region; export const Format = globalThis.Format; export const Orient = globalThis.Orient; + export const CropMode = globalThis.CropMode; export const Auto = globalThis.Auto;`, }; } From 2d53f756a3665c9c525c00d072fc7dc3c5e0cf94 Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Wed, 12 Nov 2025 12:02:53 +0000 Subject: [PATCH 12/31] Put builtins in fastly object --- runtime/fastly/builtins/fastly.cpp | 7 ++----- runtime/fastly/builtins/fastly.h | 20 ++++++++++++++++++++ runtime/fastly/builtins/image-optimizer.cpp | 13 +++++++++++-- src/bundle.js | 10 ++++------ 4 files changed, 37 insertions(+), 13 deletions(-) diff --git a/runtime/fastly/builtins/fastly.cpp b/runtime/fastly/builtins/fastly.cpp index 9e0ce57f79..125beed396 100644 --- a/runtime/fastly/builtins/fastly.cpp +++ b/runtime/fastly/builtins/fastly.cpp @@ -585,7 +585,8 @@ bool install(api::Engine *engine) { JS::SetOutOfMemoryCallback(engine->cx(), oom_callback, nullptr); - JS::RootedObject fastly(engine->cx(), JS_NewPlainObject(engine->cx())); + JS::RootedObject fastly(engine->cx()); + get_fastly_object(engine, &fastly); if (!fastly) { return false; } @@ -598,10 +599,6 @@ bool install(api::Engine *engine) { Fastly::baseURL.init(engine->cx()); Fastly::defaultBackend.init(engine->cx()); - if (!JS_DefineProperty(engine->cx(), engine->global(), "fastly", fastly, 0)) { - return false; - } - JSFunctionSpec nowfn = JS_FN("now", Fastly::now, 0, JSPROP_ENUMERATE); JSFunctionSpec end = JS_FS_END; diff --git a/runtime/fastly/builtins/fastly.h b/runtime/fastly/builtins/fastly.h index 93ece02108..e44c88c145 100644 --- a/runtime/fastly/builtins/fastly.h +++ b/runtime/fastly/builtins/fastly.h @@ -65,6 +65,26 @@ class Fastly : public builtins::BuiltinNoConstructor { JS::Result> convertBodyInit(JSContext *cx, JS::HandleValue bodyInit); +inline bool get_fastly_object(api::Engine *engine, JS::MutableHandleObject out) { + JS::RootedValue fastly_val(engine->cx()); + if (!JS_GetProperty(engine->cx(), engine->global(), "fastly", &fastly_val)) { + return false; + } + if (fastly_val.isObject()) { + out.set(&fastly_val.toObject()); + return true; + } + JS::RootedObject fastly_obj(engine->cx(), JS_NewPlainObject(engine->cx())); + if (!fastly_obj) { + return false; + } + if (!JS_DefineProperty(engine->cx(), engine->global(), "fastly", fastly_obj, 0)) { + return false; + } + out.set(fastly_obj); + return true; +} + /** * Debug only logging system, adding messages to `fastly.debugMessages` * diff --git a/runtime/fastly/builtins/image-optimizer.cpp b/runtime/fastly/builtins/image-optimizer.cpp index b66f19f1ce..4fdda96389 100644 --- a/runtime/fastly/builtins/image-optimizer.cpp +++ b/runtime/fastly/builtins/image-optimizer.cpp @@ -1,4 +1,5 @@ #include "image-optimizer.h" +#include "fastly.h" #include namespace { @@ -55,14 +56,14 @@ bool install(api::Engine *engine) { RootedObject image_optimizer_ns(engine->cx(), JS_NewObject(engine->cx(), nullptr)); #define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase, str) \ - if (!type::init_class_impl(engine->cx(), engine->global())) { \ + if (!type::init_class_impl(engine->cx(), image_optimizer_ns)) { \ return false; \ } \ RootedObject lowercase##_obj( \ engine->cx(), \ JS_GetConstructor( \ engine->cx(), \ - builtins::BuiltinNoConstructor::proto_obj)); \ + builtins::BuiltinNoConstructor<::fastly::image_optimizer::type>::proto_obj)); \ RootedValue lowercase##_val(engine->cx(), ObjectValue(*lowercase##_obj)); \ if (!JS_SetProperty(engine->cx(), image_optimizer_ns, #type, lowercase##_val)) { \ return false; \ @@ -75,6 +76,14 @@ bool install(api::Engine *engine) { if (!engine->define_builtin_module("fastly:image-optimizer", image_optimizer_ns_val)) { return false; } + + RootedObject fastly(engine->cx()); + if (!fastly::get_fastly_object(engine, &fastly)) { + return false; + } + if (!JS_SetProperty(engine->cx(), fastly, "imageOptimizer", image_optimizer_ns_val)) { + return false; + } return true; } // namespace fastly::image_optimizer diff --git a/src/bundle.js b/src/bundle.js index b5ecb456f2..8d5d46a48e 100644 --- a/src/bundle.js +++ b/src/bundle.js @@ -132,12 +132,10 @@ export const TransactionCacheEntry = globalThis.TransactionCacheEntry; } case 'image-optimizer': { return { - contents: ` - export const Region = globalThis.Region; - export const Format = globalThis.Format; - export const Orient = globalThis.Orient; - export const CropMode = globalThis.CropMode; - export const Auto = globalThis.Auto;`, + contents: `export const { + Region, Auto, Format, BWAlgorithm, Disable, Enable, Fit, Metadata, + Optimize, Orient, Profile, ResizeFilter, CropMode + } = globalThis.fastly.imageOptimizer;`, }; } } From b6d1bb06d9bb32f7be3c6660b0b8b9fdc96ffa41 Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Wed, 12 Nov 2025 14:43:27 +0000 Subject: [PATCH 13/31] Docs --- .../docs/globals/Request/Request.mdx | 1 + documentation/docs/image-optimizer/Auto.mdx | 28 ++++ .../docs/image-optimizer/BWAlgorithm.mdx | 29 ++++ .../docs/image-optimizer/CropMode.mdx | 31 +++++ .../docs/image-optimizer/Disable.mdx | 28 ++++ documentation/docs/image-optimizer/Enable.mdx | 28 ++++ documentation/docs/image-optimizer/Fit.mdx | 31 +++++ documentation/docs/image-optimizer/Format.mdx | 41 ++++++ .../docs/image-optimizer/Metadata.mdx | 29 ++++ .../docs/image-optimizer/Optimize.mdx | 29 ++++ documentation/docs/image-optimizer/Orient.mdx | 34 +++++ .../docs/image-optimizer/Profile.mdx | 29 ++++ documentation/docs/image-optimizer/Region.mdx | 33 +++++ .../docs/image-optimizer/ResizeFilter.mdx | 34 +++++ .../image-optimizer/imageOptimizerOptions.mdx | 130 ++++++++++++++++++ documentation/rename-docs.mjs | 3 +- 16 files changed, 537 insertions(+), 1 deletion(-) create mode 100644 documentation/docs/image-optimizer/Auto.mdx create mode 100644 documentation/docs/image-optimizer/BWAlgorithm.mdx create mode 100644 documentation/docs/image-optimizer/CropMode.mdx create mode 100644 documentation/docs/image-optimizer/Disable.mdx create mode 100644 documentation/docs/image-optimizer/Enable.mdx create mode 100644 documentation/docs/image-optimizer/Fit.mdx create mode 100644 documentation/docs/image-optimizer/Format.mdx create mode 100644 documentation/docs/image-optimizer/Metadata.mdx create mode 100644 documentation/docs/image-optimizer/Optimize.mdx create mode 100644 documentation/docs/image-optimizer/Orient.mdx create mode 100644 documentation/docs/image-optimizer/Profile.mdx create mode 100644 documentation/docs/image-optimizer/Region.mdx create mode 100644 documentation/docs/image-optimizer/ResizeFilter.mdx create mode 100644 documentation/docs/image-optimizer/imageOptimizerOptions.mdx diff --git a/documentation/docs/globals/Request/Request.mdx b/documentation/docs/globals/Request/Request.mdx index 2d450ee1fb..ba264db791 100644 --- a/documentation/docs/globals/Request/Request.mdx +++ b/documentation/docs/globals/Request/Request.mdx @@ -41,6 +41,7 @@ new Request(input, options) - `backend` _**Fastly-specific**_ - `cacheOverride` _**Fastly-specific**_, see [`CacheOverride`](../../fastly:cache-override/CacheOverride/CacheOverride.mdx). - `cacheKey` _**Fastly-specific**_ + - `imageOptimizerOptions` _**Fastly-specific**_, see [`imageOptimizerOptions`](../../fastly:image-optimizer/imageOptimizerOptions.mdx). - `manualFramingHeaders`_: boolean_ _**optional**_ _**Fastly-specific**_ - : The default value is `false`, which means that the framing headers are automatically created based on the message body. In "automatic" mode, a `Content-Length` is used when the size of the body can be determined before it is sent. diff --git a/documentation/docs/image-optimizer/Auto.mdx b/documentation/docs/image-optimizer/Auto.mdx new file mode 100644 index 0000000000..423ad3a90a --- /dev/null +++ b/documentation/docs/image-optimizer/Auto.mdx @@ -0,0 +1,28 @@ +--- +hide_title: false +hide_table_of_contents: false +pagination_next: null +pagination_prev: null +--- + +# `Auto` + +Enumerator options for [`imageOptimizerOptions.auto`](./imageOptimizerOptions.mdx). + +## Constants + +- `AVIF` (`"avif"`) If the browser's Accept header indicates compatibility, deliver an AVIF image. +- `WEBP` (`"webp"`) If the browser's Accept header indicates compatibility, deliver a WebP image. + +## Examples + +```js +import { Auto, Region } from 'fastly:image-optimizer'; +new Request( + "https://my.image.host/image.png", + imageOptimizerOptions: { + region: Region.UsEast, + auto: Auto.AVIF + } +) +``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/BWAlgorithm.mdx b/documentation/docs/image-optimizer/BWAlgorithm.mdx new file mode 100644 index 0000000000..2aeb580fb6 --- /dev/null +++ b/documentation/docs/image-optimizer/BWAlgorithm.mdx @@ -0,0 +1,29 @@ +--- +hide_title: false +hide_table_of_contents: false +pagination_next: null +pagination_prev: null +--- + +# `BWAlgorithm` + +Enumerator options for [`imageOptimizerOptions.bw`](./imageOptimizerOptions.mdx). + +## Constants + +- `Threshold` (`"threshold"`) Uses a luminance threshold to convert the image to black and white. +- `Atkinson` (`"atkinson"`) Uses [Atkinson dithering](https://en.wikipedia.org/wiki/Atkinson_dithering) to convert the image to black and white. + + +## Examples + +```js +import { Region, BWAlgorithm } from 'fastly:image-optimizer'; +new Request( + "https://my.image.host/image.png", + imageOptimizerOptions: { + region: Region.UsEast, + bw: BWAlgorithm.Threshold + } +) +``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/CropMode.mdx b/documentation/docs/image-optimizer/CropMode.mdx new file mode 100644 index 0000000000..4ad4e1c215 --- /dev/null +++ b/documentation/docs/image-optimizer/CropMode.mdx @@ -0,0 +1,31 @@ +--- +hide_title: false +hide_table_of_contents: false +pagination_next: null +pagination_prev: null +--- + +# `CropMode` + +Enumerator options for [`imageOptimizerOptions.crop.mode`](./imageOptimizerOptions.mdx) and `imageOptimizerOptions.precrop.mode`. + +## Constants + +- `Smart` (`"smart"`) Enables content-aware algorithms to attempt to crop the image to the desired aspect ratio while intelligently focusing on the most important visual content, including the detection of faces. +- `Safe` (`"safe"`) Allow cropping out-of-bounds regions. + +## Examples + +```js +import { CropMode, Region } from 'fastly:image-optimizer'; +new Request( + "https://my.image.host/image.png", + imageOptimizerOptions: { + region: Region.UsEast, + crop: { + size: { ratio: { width: 4, height: 3 } }, + mode: CropMode.Smart, + } + } +) +``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/Disable.mdx b/documentation/docs/image-optimizer/Disable.mdx new file mode 100644 index 0000000000..5076b3bd68 --- /dev/null +++ b/documentation/docs/image-optimizer/Disable.mdx @@ -0,0 +1,28 @@ +--- +hide_title: false +hide_table_of_contents: false +pagination_next: null +pagination_prev: null +--- + +# `Disable` + +Enumerator options for [`imageOptimizerOptions.disable`](./imageOptimizerOptions.mdx). + +## Constants + +- `Upscale` (`"upscale"`) Prevent images being resized such that the output image's dimensions are larger than the source image. + +## Examples + +```js +import { Disable, Region } from 'fastly:image-optimizer'; +new Request( + "https://my.image.host/image.png", + imageOptimizerOptions: { + region: Region.UsEast, + width: 2560, + disable: Disable.Upscale, + } +) +``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/Enable.mdx b/documentation/docs/image-optimizer/Enable.mdx new file mode 100644 index 0000000000..9ebeb6946d --- /dev/null +++ b/documentation/docs/image-optimizer/Enable.mdx @@ -0,0 +1,28 @@ +--- +hide_title: false +hide_table_of_contents: false +pagination_next: null +pagination_prev: null +--- + +# `Enable` + +Enumerator options for [`imageOptimizerOptions.enable`](./imageOptimizerOptions.mdx). + +## Constants + +- `Upscale` (`"upscale"`) Allow images to be resized such that the output image's dimensions are larger than the source image. + +## Examples + +```js +import { Enable, Region } from 'fastly:image-optimizer'; +new Request( + "https://my.image.host/image.png", + imageOptimizerOptions: { + region: Region.UsEast, + width: 2560, + enable: Enable.Upscale, + } +) +``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/Fit.mdx b/documentation/docs/image-optimizer/Fit.mdx new file mode 100644 index 0000000000..1b4b96ffb3 --- /dev/null +++ b/documentation/docs/image-optimizer/Fit.mdx @@ -0,0 +1,31 @@ +--- +hide_title: false +hide_table_of_contents: false +pagination_next: null +pagination_prev: null +--- + +# `Fit` + +Enumerator options for [`imageOptimizerOptions.fit`](./imageOptimizerOptions.mdx). + +## Constants + +- `Bounds` (`"bounds"`) Resize the image to fit entirely within the specified region, making one dimension smaller if needed. +- `Cover` (`"cover"`) Resize the image to entirely cover the specified region, making one dimension larger if needed. +- `Crop` (`"crop"`) Resize and crop the image centrally to exactly fit the specified region. + +## Examples + +```js +import { Fit, Region } from 'fastly:image-optimizer'; +new Request( + "https://my.image.host/image.png", + imageOptimizerOptions: { + region: Region.UsEast, + width: 150, + height: 150, + fit: Fit.Bounds, + } +) +``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/Format.mdx b/documentation/docs/image-optimizer/Format.mdx new file mode 100644 index 0000000000..6baba499d8 --- /dev/null +++ b/documentation/docs/image-optimizer/Format.mdx @@ -0,0 +1,41 @@ +--- +hide_title: false +hide_table_of_contents: false +pagination_next: null +pagination_prev: null +--- + +# `Format` + +Enumerator options for [`imageOptimizerOptions.format`](./imageOptimizerOptions.mdx). + +## Constants + +- `Auto` (`"auto"`) Automatically use the best format based on browser support and image/transform characteristics +- `AVIF` (`"avif"`) AVIF +- `BJPG` (`"bjpg"`) Baseline JPEG +- `GIF` (`"gif"`) Graphics Interchange Format +- `JPG` (`"jpg"`) JPEG +- `JXL` (`"jxl"`) JPEGXL +- `MP4` (`"mp4"`) MP4 (H.264) +- `PJPG` (`"pjpg"`) Progressive JPEG +- `PJXL` (`"pjxl"`) Progressive JPEGXL +- `PNG` (`"png"`) Portable Network Graphics +- `PNG8` (`"png8"`) Portable Network Graphics palette image with 256 colors and 8-bit transparency +- `SVG` (`"svg"`) Scalable Vector Graphics +- `WEBP` (`"webp"`) WebP +- `WEBPLL` (`"webpll"`) WebP (Lossless) +- `WEBPLY` (`"webply"`) WebP (Lossy) + +## Examples + +```js +import { Format, Region } from 'fastly:image-optimizer'; +new Request( + "https://my.image.host/image.png", + imageOptimizerOptions: { + region: Region.UsEast, + format: Format.PNG, + } +) +``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/Metadata.mdx b/documentation/docs/image-optimizer/Metadata.mdx new file mode 100644 index 0000000000..356ab23cbd --- /dev/null +++ b/documentation/docs/image-optimizer/Metadata.mdx @@ -0,0 +1,29 @@ +--- +hide_title: false +hide_table_of_contents: false +pagination_next: null +pagination_prev: null +--- + +# `Metadata` + +Enumerator options for [`imageOptimizerOptions.metadata`](./imageOptimizerOptions.mdx). + +## Constants + +- `Copyright` (`"copyright"`) Preserve [copyright notice](https://www.iptc.org/std/photometadata/specification/IPTC-PhotoMetadata#copyright-notice), creator, credit line, licensor, and web statement of rights fields. +- `C2PA` (`"c2pa"`) Preserve the [C2PA manifest](https://c2pa.org/) and add any transformations performed by Fastly Image Optimizer. +- `CopyRightAndC2PA` (`"copyright,c2pa"`) Resize and crop the image centrally to exactly fit the specified region. + +## Examples + +```js +import { Metadata, Region } from 'fastly:image-optimizer'; +new Request( + "https://my.image.host/image.png", + imageOptimizerOptions: { + region: Region.UsEast, + metadata: Metadata.Copyright + } +) +``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/Optimize.mdx b/documentation/docs/image-optimizer/Optimize.mdx new file mode 100644 index 0000000000..c6a36094b5 --- /dev/null +++ b/documentation/docs/image-optimizer/Optimize.mdx @@ -0,0 +1,29 @@ +--- +hide_title: false +hide_table_of_contents: false +pagination_next: null +pagination_prev: null +--- + +# `Optimize` + +Enumerator options for [`imageOptimizerOptions.optimize`](./imageOptimizerOptions.mdx). + +## Constants + +- `Low` (`"low"`) Output image quality will be similar to the input image quality. +- `Medium` (`"medium"`) More optimization is allowed. We attempt to preserve the visual quality of the input image. +- `High` (`"high"`) Minor visual artifacts may be visible. This produces the smallest file. + +## Examples + +```js +import { Optimize, Region } from 'fastly:image-optimizer'; +new Request( + "https://my.image.host/image.png", + imageOptimizerOptions: { + region: Region.UsEast, + optimize: Optimize.High + } +) +``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/Orient.mdx b/documentation/docs/image-optimizer/Orient.mdx new file mode 100644 index 0000000000..8d94675330 --- /dev/null +++ b/documentation/docs/image-optimizer/Orient.mdx @@ -0,0 +1,34 @@ +--- +hide_title: false +hide_table_of_contents: false +pagination_next: null +pagination_prev: null +--- + +# `Orient` + +Enumerator options for [`imageOptimizerOptions.orient`](./imageOptimizerOptions.mdx). + +## Constants + +- `Default` (`"1"`) +- `FlipHorizontal` (`"2"`) +- `FlipHorizontalAndVertical` (`"3"`) +- `FlipVertical` (`"4"`) +- `FlipHorizontalOrientLeft` (`"5"`) +- `OrientRight` (`"6"`) +- `FlipHorizontalOrientRight` (`"7"`) +- `OrientLeft` (`"8"`) + +## Examples + +```js +import { Orient, Region } from 'fastly:image-optimizer'; +new Request( + "https://my.image.host/image.png", + imageOptimizerOptions: { + region: Region.UsEast, + orient: Orient.FlipHorizontal + } +) +``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/Profile.mdx b/documentation/docs/image-optimizer/Profile.mdx new file mode 100644 index 0000000000..261a07a084 --- /dev/null +++ b/documentation/docs/image-optimizer/Profile.mdx @@ -0,0 +1,29 @@ +--- +hide_title: false +hide_table_of_contents: false +pagination_next: null +pagination_prev: null +--- + +# `Profile` + +Enumerator options for [`imageOptimizerOptions.profile`](./imageOptimizerOptions.mdx). + +## Constants + +- `Baseline` (`"baseline"`) The profile recommended for video conferencing and mobile applications. (Default) +- `Main` (`"main"`) The profile recommended for standard-definition broadcasts. +- `High` (`"high"`) The profile recommended for high-definition broadcasts. + +## Examples + +```js +import { Profile, Region } from 'fastly:image-optimizer'; +new Request( + "https://my.image.host/image.png", + imageOptimizerOptions: { + region: Region.UsEast, + orient: Orient.FlipHorizontal + } +) +``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/Region.mdx b/documentation/docs/image-optimizer/Region.mdx new file mode 100644 index 0000000000..0c7a24f13f --- /dev/null +++ b/documentation/docs/image-optimizer/Region.mdx @@ -0,0 +1,33 @@ +--- +hide_title: false +hide_table_of_contents: false +pagination_next: null +pagination_prev: null +--- + +# `Region` + +Enumerator options for [`imageOptimizerOptions.region`](./imageOptimizerOptions.mdx). + +## Constants + +- `UsEast` (`"us_east"`) +- `UsCentral` (`"us_central"`) +- `UsWest` (`"us_west"`) +- `EuCentral` (`"eu_central"`) +- `EuWest` (`"eu_west"`) +- `Asia` (`"asia"`) +- `Australia` (`"australia"`) + + +## Examples + +```js +import { Region } from 'fastly:image-optimizer'; +new Request( + "https://my.image.host/image.png", + imageOptimizerOptions: { + region: Region.UsEast + } +) +``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/ResizeFilter.mdx b/documentation/docs/image-optimizer/ResizeFilter.mdx new file mode 100644 index 0000000000..c0a7860155 --- /dev/null +++ b/documentation/docs/image-optimizer/ResizeFilter.mdx @@ -0,0 +1,34 @@ +--- +hide_title: false +hide_table_of_contents: false +pagination_next: null +pagination_prev: null +--- + +# `ResizeFilter` + +Enumerator options for [`imageOptimizerOptions.resizeFilter`](./imageOptimizerOptions.mdx). + +## Constants + +- `Nearest` (`"nearest"`) Uses the value of nearby translated pixel values. +- `Bilinear` (`"bilinear"`) Uses an average of a 2x2 environment of pixels. +- `Linear` (`"linear"`) Same as `Bilenear`. +- `Bicubic` (`"bicubic"`) Uses an average of a 4x4 environment of pixels, weighing the innermost pixels higher. +- `Cubic` (`"cubic"`) Same as `Bicubic`. +- `Lanczos2` (`"lanczos2"`) Uses the Lanczos filter to increase the ability to detect edges and linear features within an image and uses sinc resampling to provide the best possible reconstruction. +- `Lanczos3` (`"lanczos3"`) Lanczos3 uses a better approximation of the sinc resampling function. (Default) +- `Lanczos` (`"lanczos"`) Same as `Lanczos3`. + +## Examples + +```js +import { Profile, Region } from 'fastly:image-optimizer'; +new Request( + "https://my.image.host/image.png", + imageOptimizerOptions: { + region: Region.UsEast, + resizeFilter: ResizeFilter.Linear + } +) +``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/imageOptimizerOptions.mdx b/documentation/docs/image-optimizer/imageOptimizerOptions.mdx new file mode 100644 index 0000000000..c9e84f536d --- /dev/null +++ b/documentation/docs/image-optimizer/imageOptimizerOptions.mdx @@ -0,0 +1,130 @@ +--- +hide_title: false +hide_table_of_contents: false +pagination_next: null +pagination_prev: null +--- + +# `imageOptimizerOptions` + +Options specified in the [`Request`](../globals/Request/Request.mdx) constructor for running the [Fastly Image Optimizer](https://docs.fastly.com/products/image-optimizer). More detailed documentation on all Image Optimizer options is available in the [Image Optimizer reference docs](https://www.fastly.com/documentation/reference/io/). + +## Parameters + +All parameters other than `region` are optional. + +- `region`: _[`Region`](./Region.mdx)_ Where image optimizations should occur. +- `auto`: _[`Auto`](./Auto.mdx)_ Enable optimization features automatically. +- `bgColor`: _[`Color`](#color)_ Set the background color of an image. +- `blur`: _`number` (0.5-1000) or [`Percentage`](#percentage)_ Set the blurriness of the output image. +- `brightness`: _`number` (-100-100)_ Set the brightness of the output image. +- `bw`: _[`BWAlgorithm`](./BWAlgorithm.mdx)_ Convert an image to black and white. +- `canvas`: _`Object`_ Increase the size of the canvas around an image. + - `size`: _[`Size`](#size)_ + - `position`: _[`Position`](#position)_ +- `contrast`: _`number` (-100-100)_ Set the contrast of the output image. +- `crop`: _`Object`_ Remove pixels from an image. + - `size`: _[`Size`](#size)_ + - `position`: _[`Position`](#position)_ + - `mode`: _[`CropMode`](./CropMode.mdx)_ +- `disable`: _[`Disable`](./Disable.mdx)_ Disable functionality that is enabled by default. +- `dpr`: `number` Ratio between physical pixels and logical pixels. +- `enable`: _[`Enable`](./Enable.mdx)_ Enable functionality that is disabled by default. +- `fit`: _[`Fit`](./Fit.mdx)_ Set how the image will fit within the size bounds provided. +- `format`: _[`Format`](./Format.mdx)_ Specify the output format to convert the image to. +- `frame`: _`number` (must have the value 1)_ Extract the first frame from an animated image sequence. +- `height`: _`integer` (number of pixels) or [`Percentage`](#percentage)_ Resize the height of the image. +- `level`: _`String` containing one of the [allowed values](https://www.fastly.com/documentation/reference/io/level/#allowed-values)_ Specify the level constraints when converting to video. +- `metadata`: _[`Metadata`](./Metadata.mdx)_ Control which metadata fields are preserved during transformation. +- `optimize`: _[`Optimize`](./Optimize.mdx)_ Automatically apply optimal quality compression. +- `orient`: _[`Orient`](./Orient.mdx)_ Change the cardinal orientation of the image. +- `pad`: _[`Sides`](#sides)_ Add pixels to the edge of an image. +- `precrop`: _`Object`_ Remove pixels from an image before any other transformations occur. + - `size`: _[`Size`](#size)_ + - `position`: _[`Position`](#position)_ + - `mode`: _[`CropMode`](./CropMode.mdx)_ +- `profile`: _[`Profile`](./Profile.mdx)_ Specify the profile class of application when converting to video. +- `quality`: _`integer` (1-100)_ Optimize the image to the given compression level for lossy file formatted images. +- `resizeFilter`: _[`ResizeFilter`](./ResizeFilter.mdx)_ Specify the resize filter used when resizing images. +- `saturation`: _`number` (-100-100)_ Set the saturation of the output image. +- `sharpen`: _`Object`_ Set the sharpness of the output image. + - `amount`: _`number` (0-10)_ + - `radius`: _`number` (0.5-1000)_ + - `threshold`: _`integer` (0-255)_ +- `trim`: _[`Sides`](#sides)_ Remove pixels from the edge of an image. +- `viewbox`: _`number` (must have the value 1)_ Remove explicit width and height properties in SVG output. +- `width`: _`integer` (number of pixels) or [`Percentage`](#percentage)_ Resize the width of the image. + +## Types + +### Color + +Either: + +- a 3 or 6 character hexadecimal string +- an `Object` containing: + - `r`: _`integer` (0-255)_ Red component + - `g`: _`integer` (0-255)_ Green component + - `b`: _`integer` (0-255)_ Blue component + - `a` (optional): _`number` (0.0-1.0)_ Alpha component + +### Percentage + +A `String` containing a number suffixed with a percent sign (%). + +### Position + +An `Object` containing: + +- Exactly one of: + - `x`: _`integer` (number of pixels) or [`Percentage`](#percentage)_ + - `offsetX`: _`number` (interpreted as a percentage)_ +- Exactly one of: + - `y`: _`integer` (number of pixels) or [`Percentage`](#percentage)_ + - `offsetY`: _`number` (interpreted as a percentage)_ + +### Sides + +An `Object` containing `top`, `bottom`, `left`, and `right`, all of which are either an `integer` or [`Percentage`](#percentage). + +### Size + +An `Object` containing either: + +- `absolute`: _`Object`_ + - `width`: _`integer` (number of pixels) or [`Percentage`](#percentage)_ + - `height`: _`integer` (number of pixels) or [`Percentage`](#percentage)_ +- `ratio`: _`Object`_ + - `width`: _`number`_ + - `height`: _`number`_ + +## Examples + +```js +import { Format, Orient, CropMode, Region } from 'fastly:image-optimizer'; +new Request( + "https://my.image.host/image.png", + imageOptimizerOptions: { + region: Region.UsEast, + format: Format.PNG, + bgColor: { + 'r': 100, + 'g': 255, + 'b': 9, + 'a': 0.5 + }, + blur: '1%', + brightness: -20, + contrast: -20, + height: 600, + level: '4.0', + orient: Orient.FlipVertical, + saturation: 80, + sharpen: { 'amount': 5, 'radius': 6, 'threshold': 44 }, + canvas: { 'size': { 'absolute': { 'width': 400, 'height': 400 } } }, + crop: { size: { absolute: { width: 200, height: 200 }, mode: CropMode.Safe } }, + trim: { top: 10, left: 10, right: 10, bottom: 10 }, + pad: { top: 30, left: 30, right: "1%", bottom: 30 } + }, +); +``` \ No newline at end of file diff --git a/documentation/rename-docs.mjs b/documentation/rename-docs.mjs index ed2951a3d0..19da926285 100644 --- a/documentation/rename-docs.mjs +++ b/documentation/rename-docs.mjs @@ -26,7 +26,8 @@ const subsystems = [ 'logger', 'object-store', 'secret-store', - 'html-rewriter' + 'html-rewriter', + 'image-optimizer' ]; const files = readdirSync('docs'); From 6d131541a5453db0935f6dba5008f04a90fd72f7 Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Wed, 12 Nov 2025 14:43:50 +0000 Subject: [PATCH 14/31] Cleanup --- runtime/fastly/builtins/image-optimizer-options.inc | 4 +++- runtime/fastly/builtins/image-optimizer.cpp | 9 --------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/runtime/fastly/builtins/image-optimizer-options.inc b/runtime/fastly/builtins/image-optimizer-options.inc index 50f09377e7..18b3b8ebe6 100644 --- a/runtime/fastly/builtins/image-optimizer-options.inc +++ b/runtime/fastly/builtins/image-optimizer-options.inc @@ -1,3 +1,5 @@ +// Defines X-Macros (https://en.wikipedia.org/wiki/X_macro) for enumerator image optimizer options + #if !defined(FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION) && \ !defined(FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE) && \ !defined(FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE) @@ -93,7 +95,7 @@ FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(Profile, profile, "profile") FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Baseline, "baseline") FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Main, "main") FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(High, "high") -FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(Optimize) +FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(Profile) FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(ResizeFilter, resize_filter, "resize-filter") FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Nearest, "nearest") diff --git a/runtime/fastly/builtins/image-optimizer.cpp b/runtime/fastly/builtins/image-optimizer.cpp index 4fdda96389..b8ac98d590 100644 --- a/runtime/fastly/builtins/image-optimizer.cpp +++ b/runtime/fastly/builtins/image-optimizer.cpp @@ -236,9 +236,6 @@ std::string ImageOptimizerOptions::to_string() const { return ret; } -// TODO -// pad - fastly_image_optimizer_transform_config ImageOptimizerOptions::to_config() { fastly_image_optimizer_transform_config config; auto str = this->to_string(); @@ -676,12 +673,6 @@ ImageOptimizerOptions::to_trim_color(JSContext *cx, JS::HandleValue val) { }; if (val.isString()) { - auto str = core::encode(cx, val); - // We also support "auto" as a color - if (str == std::string_view("auto")) { - return TrimColor{Color{std::move(str)}}; - } - auto color = to_color(cx, val); if (!color) { return throw_error(); From 315e81899aab0136687fd6b3c2b11c3c6caf2490 Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Wed, 12 Nov 2025 15:31:17 +0000 Subject: [PATCH 15/31] Types --- .../docs/globals/Request/Request.mdx | 1 - documentation/docs/globals/fetch.mdx | 1 + .../image-optimizer/imageOptimizerOptions.mdx | 6 +- types/globals.d.ts | 71 ++--- types/image-optimizer.d.ts | 264 ++++++++++++++++++ types/index.d.ts | 1 + 6 files changed, 305 insertions(+), 39 deletions(-) create mode 100644 types/image-optimizer.d.ts diff --git a/documentation/docs/globals/Request/Request.mdx b/documentation/docs/globals/Request/Request.mdx index ba264db791..2d450ee1fb 100644 --- a/documentation/docs/globals/Request/Request.mdx +++ b/documentation/docs/globals/Request/Request.mdx @@ -41,7 +41,6 @@ new Request(input, options) - `backend` _**Fastly-specific**_ - `cacheOverride` _**Fastly-specific**_, see [`CacheOverride`](../../fastly:cache-override/CacheOverride/CacheOverride.mdx). - `cacheKey` _**Fastly-specific**_ - - `imageOptimizerOptions` _**Fastly-specific**_, see [`imageOptimizerOptions`](../../fastly:image-optimizer/imageOptimizerOptions.mdx). - `manualFramingHeaders`_: boolean_ _**optional**_ _**Fastly-specific**_ - : The default value is `false`, which means that the framing headers are automatically created based on the message body. In "automatic" mode, a `Content-Length` is used when the size of the body can be determined before it is sent. diff --git a/documentation/docs/globals/fetch.mdx b/documentation/docs/globals/fetch.mdx index 0fc2614628..43726b5445 100644 --- a/documentation/docs/globals/fetch.mdx +++ b/documentation/docs/globals/fetch.mdx @@ -76,6 +76,7 @@ fetch(resource, options) - *Fastly-specific* - `cacheOverride` _**Fastly-specific**_ - `cacheKey` _**Fastly-specific**_ + - `imageOptimizerOptions` _**Fastly-specific**_, see [`imageOptimizerOptions`](../../fastly:image-optimizer/imageOptimizerOptions.mdx). - `fastly` _**Fastly-specific**_ - `decompressGzip`_: boolean_ _**optional**_ - Whether to automatically gzip decompress the Response or not. diff --git a/documentation/docs/image-optimizer/imageOptimizerOptions.mdx b/documentation/docs/image-optimizer/imageOptimizerOptions.mdx index c9e84f536d..78fb2ced0d 100644 --- a/documentation/docs/image-optimizer/imageOptimizerOptions.mdx +++ b/documentation/docs/image-optimizer/imageOptimizerOptions.mdx @@ -21,12 +21,12 @@ All parameters other than `region` are optional. - `bw`: _[`BWAlgorithm`](./BWAlgorithm.mdx)_ Convert an image to black and white. - `canvas`: _`Object`_ Increase the size of the canvas around an image. - `size`: _[`Size`](#size)_ - - `position`: _[`Position`](#position)_ + - `position` (optional): _[`Position`](#position)_ - `contrast`: _`number` (-100-100)_ Set the contrast of the output image. - `crop`: _`Object`_ Remove pixels from an image. - `size`: _[`Size`](#size)_ - - `position`: _[`Position`](#position)_ - - `mode`: _[`CropMode`](./CropMode.mdx)_ + - `position` (optional): _[`Position`](#position)_ + - `mode` (optional): _[`CropMode`](./CropMode.mdx)_ - `disable`: _[`Disable`](./Disable.mdx)_ Disable functionality that is enabled by default. - `dpr`: `number` Ratio between physical pixels and logical pixels. - `enable`: _[`Enable`](./Enable.mdx)_ Enable functionality that is disabled by default. diff --git a/types/globals.d.ts b/types/globals.d.ts index ad9ac908ab..dac204b655 100644 --- a/types/globals.d.ts +++ b/types/globals.d.ts @@ -136,24 +136,24 @@ declare interface BackendConfiguration { * Setting to boolean true enables keepalive with the default options. */ tcpKeepalive?: - | boolean - | { - /** - * Configure how long to wait after the last sent data over the TCP connection before - * starting to send TCP keepalive probes. - */ - timeSecs?: number; + | boolean + | { + /** + * Configure how long to wait after the last sent data over the TCP connection before + * starting to send TCP keepalive probes. + */ + timeSecs?: number; - /** - * Configure how long to wait between each TCP keepalive probe sent to the backend to determine if it is still active. - */ - intervalSecs?: number; + /** + * Configure how long to wait between each TCP keepalive probe sent to the backend to determine if it is still active. + */ + intervalSecs?: number; - /** - * Number of probes to send to the backend before it is considered dead. - */ - probes?: number; - }; + /** + * Number of probes to send to the backend before it is considered dead. + */ + probes?: number; + }; } /** @@ -413,7 +413,7 @@ declare interface CacheOverride extends CacheOverrideInit { */ declare var CacheOverride: { prototype: CacheOverride; - new (mode: CacheOverrideMode, init?: CacheOverrideInit): CacheOverride; + new(mode: CacheOverrideMode, init?: CacheOverrideInit): CacheOverride; }; /** @@ -1182,7 +1182,7 @@ interface Blob { declare var Blob: { prototype: Blob; - new (blobParts?: BlobPart[], options?: BlobPropertyBag): Blob; + new(blobParts?: BlobPart[], options?: BlobPropertyBag): Blob; }; /** @@ -1201,7 +1201,7 @@ interface File extends Blob { declare var File: { prototype: File; - new (fileBits: BlobPart[], fileName: string, options?: FilePropertyBag): File; + new(fileBits: BlobPart[], fileName: string, options?: FilePropertyBag): File; }; /** @@ -1238,7 +1238,7 @@ interface FormData { declare var FormData: { prototype: FormData; - new ( + new( form?: any /*form?: HTMLFormElement, submitter?: HTMLElement | null*/, ): FormData; }; @@ -1322,14 +1322,15 @@ declare interface RequestInit { /** The Fastly configured backend name or instance the request should be sent to. */ backend?: string | import('fastly:backend').Backend; cacheOverride?: - | import('fastly:cache-override').CacheOverride - | import('fastly:cache-override').ICacheOverride - | Exclude; + | import('fastly:cache-override').CacheOverride + | import('fastly:cache-override').ICacheOverride + | Exclude; cacheKey?: string; fastly?: { decompressGzip?: boolean; }; manualFramingHeaders?: boolean; + imageOptimizerOptions?: import('fastly:image-optimizer').ImageOptimizerOptions; } /** @@ -1398,7 +1399,7 @@ interface Request extends Body { */ declare var Request: { prototype: Request; - new (input: RequestInfo | URL, init?: RequestInit): Request; + new(input: RequestInfo | URL, init?: RequestInit): Request; }; /** @@ -1520,7 +1521,7 @@ interface Response extends Body { */ declare var Response: { prototype: Response; - new (body?: BodyInit | null, init?: ResponseInit): Response; + new(body?: BodyInit | null, init?: ResponseInit): Response; // error(): Response; redirect(url: string | URL, status?: number): Response; json(data: any, init?: ResponseInit): Response; @@ -1751,7 +1752,7 @@ interface ReadableStreamDefaultController { */ declare var ReadableStreamDefaultController: { prototype: ReadableStreamDefaultController; - new (): ReadableStreamDefaultController; + new(): ReadableStreamDefaultController; }; /** @@ -1816,7 +1817,7 @@ interface WritableStreamDefaultController { */ declare var WritableStreamDefaultController: { prototype: WritableStreamDefaultController; - new (): WritableStreamDefaultController; + new(): WritableStreamDefaultController; }; /** @@ -1880,7 +1881,7 @@ interface TransformStreamDefaultController { */ declare var TransformStreamDefaultController: { prototype: TransformStreamDefaultController; - new (): TransformStreamDefaultController; + new(): TransformStreamDefaultController; }; /** @@ -1952,7 +1953,7 @@ interface Headers { */ declare var Headers: { prototype: Headers; - new (init?: HeadersInit): Headers; + new(init?: HeadersInit): Headers; }; /** @@ -2106,7 +2107,7 @@ interface WorkerLocation { */ declare var WorkerLocation: { prototype: WorkerLocation; - new (): WorkerLocation; + new(): WorkerLocation; }; /** @@ -2233,7 +2234,7 @@ interface Crypto { */ declare var Crypto: { prototype: Crypto; - new (): Crypto; + new(): Crypto; }; /** @@ -2260,7 +2261,7 @@ interface CryptoKey { declare var CryptoKey: { prototype: CryptoKey; - new (): CryptoKey; + new(): CryptoKey; }; interface KeyAlgorithm { @@ -2420,7 +2421,7 @@ interface Event { declare var Event: { prototype: Event; - new (type: string, eventInitDict?: EventInit): Event; + new(type: string, eventInitDict?: EventInit): Event; readonly NONE: 0; readonly CAPTURING_PHASE: 1; readonly AT_TARGET: 2; @@ -2484,7 +2485,7 @@ interface EventTarget { declare var EventTarget: { prototype: EventTarget; - new (): EventTarget; + new(): EventTarget; }; /** @@ -2505,7 +2506,7 @@ interface Performance extends EventTarget { */ declare var Performance: { prototype: Performance; - new (): Performance; + new(): Performance; }; /** diff --git a/types/image-optimizer.d.ts b/types/image-optimizer.d.ts new file mode 100644 index 0000000000..ac1b006855 --- /dev/null +++ b/types/image-optimizer.d.ts @@ -0,0 +1,264 @@ +declare module 'fastly:image-optimizer' { + /** + * A color, either a 3/6 character hex string or an rgb(a) object. + */ + type Color = string | { + r: number; + g: number; + b: number; + a?: number; + } + /** + * A percentage, expressed as a string such as '100%' + */ + type Percentage = string; + /** + * The size of a region, either expressed as absolute values (either integer pixel values or percentage strings), or as a ratio of integers. + */ + interface Size { + absolute?: { + width: number | Percentage; + height: number | Percentage; + }; + ratio?: { + width: number; + height: number; + } + } + /** + * The position of a region, with x and y components expressed as either integer pixel values/percentage strings, or offset percentages. + */ + interface Position { + x?: number | Percentage; + offsetX?: number; + y?: number | Percentage; + offsetY?: number; + } + + interface Sides { + top: number | Percentage; + bottom: number | Percentage; + left: number | Percentage; + right: number | Percentage; + } + + var Region: { + UsEast: 'us_east'; + UsCentral: 'us_central'; + UsWest: 'us_west'; + EuCentral: 'eu_central'; + Asia: 'asia'; + Australia: 'australia'; + } + var Auto: { + AVIF: 'avif'; + WEBP: 'webp'; + } + var BWAlgorithm: { + Threshold: 'threshold'; + Atkinson: 'atkinson'; + } + var CropMode: { + Smart: 'smart'; + Safe: 'safe'; + } + var Disable: { + Upscale: 'upscale'; + } + var Enable: { + Upscale: 'upscale'; + } + var Fit: { + Bounds: "bounds"; + Cover: "cover"; + Crop: "crop"; + } + var Metadata: { + Copyright: "copyright"; + C2PA: "c2pa"; + CopyrightAndC2PA: "copyright,c2pa"; + } + var Optimize: { + Low: "low"; + Medium: "medium"; + High: "high"; + } + var Orient: { + Default: "1"; + FlipHorizontal: "2"; + FlipHorizontalAndVertical: "3"; + FlipVertical: "4"; + FlipHorizontalOrientLeft: "5"; + OrientRight: "6"; + FlipHorizontalOrientRight: "7"; + OrientLeft: "8"; + } + var Profile: { + Baseline: "baseline"; + Main: "main"; + High: "high"; + } + var ResizeFilter: { + Nearest: "nearest"; + Bilinear: "bilinear"; + Linear: "linear"; + Bicubic: "bicubic"; + Cubic: "cubic"; + Lanczos2: "lanczos2"; + Lanczos3: "lanczos3"; + Lanczos: "lanczos"; + } + + interface ImageOptimizerOptions { + /** + * + */ + region: 'us_east' | 'us_central' | 'us_west' | 'eu_central' | 'asia' | 'australia'; + /** + * Enable optimization features automatically. + */ + auto?: 'avif' | 'webp'; + /** + * Set the background color of an image. + */ + bgColor?: Color; + /** + * Set the blurriness of the output image (0.5-1000). + */ + blur?: number | string; + /** + * Set the brightness of the output image. + */ + brightness?: number | string; + /** + * Convert an image to black and white using a given algorithm. + */ + bw?: 'threshold' | 'atkinson'; + /** + * Increase the size of the canvas around an image. + */ + canvas?: { + size: Size; + position?: Position; + } + /** + * Set the contrast of the output image (-100-100). + */ + contrast?: number; + /** + * Remove pixels from an image. + */ + crop?: { + size: Size; + position?: Position; + mode?: 'smart' | 'safe'; + }; + /** + * Disable functionality that is enabled by default. + */ + disable?: 'upscale'; + /** + * Ratio between physical pixels and logical pixels. + */ + dpr?: number; + /** + * Enable functionality that is disabled by default. + */ + enable?: 'upscale'; + /** + * Set how the image will fit within the size bounds provided. + */ + fit?: 'bounds' | 'cover' | 'crop'; + /** + * Specify the output format to convert the image to. + */ + format?: + 'auto' | + 'avif' | + 'bjpg' | + 'gif' | + 'jpg' | + 'jxl' | + 'mp4' | + 'pjpg' | + 'pjxl' | + 'png' | + 'png8' | + 'svg' | + 'webp' | + 'webpll' | + 'webply'; + /** + * Extract the first frame from an animated image. + */ + frame?: 1; + /** + * Resize the height of the image. + */ + height?: number | Percentage; + /** + * Specify the level constraints when converting to video. + */ + level?: string; + /** + * Control which metadata fields are preserved during transformation. + */ + metadata?: 'copyright' | 'c2pa' | 'copyright,c2pa'; + /** + * Automatically apply optimal quality compression. + */ + optimize?: 'low' | 'medium' | 'high'; + /** + * Change the cardinal orientation of the image. + */ + orient?: '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8'; + /** + * Add pixels to the edge of an image + */ + pad?: Sides, + /** + * Remove pixels from an image before any other transformations occur. + */ + precrop?: { + size: Size; + position?: Position; + mode?: 'smart' | 'safe'; + }; + /** + * Specify the profile class of application when converting to video. + */ + profile?: 'baseline' | 'main' | 'high'; + /** + * Optimize the image to the given compresion level for lossy file formatted images (1-100). + */ + quality?: number; + /** + * Specify the resize filter used when resizing images. + */ + resizeFilter?: 'nearest' | 'bilinear' | 'linear' | 'bicubic' | 'cubic' | 'lanczos2' | 'lanczos3' | 'lanczos'; + /** + * Set the saturation of the output image (-100-100). + */ + saturation?: number; + /** + * Set the sharpness of the output image. + */ + sharpen?: { + amount: number; + radius: number; + threshold: number; + }; + /** + * Remove pixels from the edge of an image. + */ + trim?: Sides; + /** + * Remove explicit width and height properties in SVG output. + */ + viewbox?: 1; + /** + * Resize the width of the image. + */ + width?: number | Percentage; + } +} \ No newline at end of file diff --git a/types/index.d.ts b/types/index.d.ts index 4bdd2bc169..39b052dfca 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -18,3 +18,4 @@ /// /// /// +/// From f5df4fe3df87b1a2b2c1ebade24e5792c705226f Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Wed, 12 Nov 2025 17:01:57 +0000 Subject: [PATCH 16/31] Testing --- .../fixtures/app/src/image-optimizer.js | 192 ++++++++++++++++-- .../js-compute/fixtures/app/tests.json | 27 ++- runtime/fastly/builtins/image-optimizer.cpp | 28 ++- runtime/fastly/builtins/image-optimizer.h | 11 +- src/bundle.js | 2 +- types/image-optimizer.d.ts | 10 +- 6 files changed, 247 insertions(+), 23 deletions(-) diff --git a/integration-tests/js-compute/fixtures/app/src/image-optimizer.js b/integration-tests/js-compute/fixtures/app/src/image-optimizer.js index 217cdc3351..3c368a284c 100644 --- a/integration-tests/js-compute/fixtures/app/src/image-optimizer.js +++ b/integration-tests/js-compute/fixtures/app/src/image-optimizer.js @@ -1,20 +1,188 @@ /* eslint-env serviceworker */ import { routes } from './routes.js'; -import { Region, Auto } from 'fastly:image-optimizer'; import { - assert, - assertThrows, - assertRejects, - strictEqual, -} from './assertions.js'; + Region, + Auto, + BWAlgorithm, + CropMode, + Disable, + Enable, + Fit, + Metadata, + Optimize, + Orient, + Profile, + ResizeFilter, + optionsToQueryString, +} from 'fastly:image-optimizer'; +import { assert, assertThrows } from './assertions.js'; -routes.set('/image-optimizer/test', async () => { - const response = await fetch('https://http-me.glitch.me/image-jpeg', { - imageOptimizerOptions: { - region: Region.UsEast, - auto: Auto.AVIF +// Enums +routes.set('/image-optimizer/options/region', () => { + assert(optionsToQueryString({ region: Region.UsEast }), 'region=us_east'); + assert(optionsToQueryString({ region: Region.Asia }), 'region=asia'); + assertThrows(() => optionsToQueryString({ region: 'invalid' }), TypeError); + assertThrows(() => optionsToQueryString({}), TypeError); +}); +routes.set('/image-optimizer/options/auto', () => { + assert(optionsToQueryString({ region: Region.Asia, auto: Auto.AVIF }), 'region=asia&auto=avif'); + assert(optionsToQueryString({ region: Region.Asia, auto: Auto.WEBP }), 'region=asia&auto=webp'); + assertThrows(() => optionsToQueryString({ region: Region.Asia, auto: 'invalid' }), TypeError); +}); +routes.set('/image-optimizer/options/bw', () => { + assert(optionsToQueryString({ region: Region.EuCentral, bw: BWAlgorithm.Threshold }), 'region=eu_central&bw=threshold'); + assert(optionsToQueryString({ region: Region.EuCentral, bw: BWAlgorithm.Atkinson }), 'region=eu_central&bw=atkinson'); + assertThrows(() => optionsToQueryString({ region: Region.EuCentral, bw: 'invalid' }), TypeError); +}); +routes.set('/image-optimizer/options/crop-mode', () => { + const qs = optionsToQueryString({ region: Region.UsWest, crop: { size: { absolute: { width: 100, height: 100 } }, mode: CropMode.Smart } }); + assert(qs.includes('smart'), true); + assertThrows(() => optionsToQueryString({ region: Region.UsWest, crop: { size: { absolute: { width: 100, height: 100 } }, mode: 'bad' } }), TypeError); +}); +routes.set('/image-optimizer/options/disable', () => { + assert(optionsToQueryString({ region: Region.Australia, disable: Disable.Upscale }), 'region=australia&disable=upscale'); + assertThrows(() => optionsToQueryString({ region: Region.Australia, disable: 'invalid' })); +}); +routes.set('/image-optimizer/options/enable', () => { + assert(optionsToQueryString({ region: Region.Australia, enable: Enable.Upscale }), 'region=australia&enable=upscale'); + assertThrows(() => optionsToQueryString({ region: Region.Australia, enable: 'invalid' })); +}); +routes.set('/image-optimizer/options/fit', () => { + assert(optionsToQueryString({ region: Region.Australia, fit: Fit.Crop }), 'region=australia&fit=crop'); + assert(optionsToQueryString({ region: Region.Australia, fit: Fit.Cover }), 'region=australia&fit=cover'); + assertThrows(() => optionsToQueryString({ region: Region.Australia, fit: 'invalid' })); +}); +routes.set('/image-optimizer/options/metadata', () => { + assert(optionsToQueryString({ region: Region.Australia, metadata: Metadata.C2PA }), 'region=australia&metadata=c2pa'); + assert(optionsToQueryString({ region: Region.Australia, metadata: Metadata.CopyrightAndC2PA }), 'region=australia&metadata=copyright,c2pa'); + assertThrows(() => optionsToQueryString({ region: Region.Australia, metadata: 'invalid' })); +}); +routes.set('/image-optimizer/options/orient', () => { + assert(optionsToQueryString({ region: Region.Australia, orient: Orient.Default }), 'region=australia&orient=1'); + assert(optionsToQueryString({ region: Region.Australia, orient: Orient.FlipHorizontalOrientRight }), 'region=australia&orient=7'); + assertThrows(() => optionsToQueryString({ region: Region.Australia, orient: 'invalid' })); +}); +routes.set('/image-optimizer/options/profile', () => { + assert(optionsToQueryString({ region: Region.Australia, profile: Profile.Baseline }), 'region=australia&profile=baseline'); + assert(optionsToQueryString({ region: Region.Australia, profile: Profile.Main }), 'region=australia&profile=main'); + assertThrows(() => optionsToQueryString({ region: Region.Australia, profile: 'invalid' })); +}); +routes.set('/image-optimizer/options/resizeFilter', () => { + assert(optionsToQueryString({ region: Region.Australia, resizeFilter: ResizeFilter.Bicubic }), 'region=australia&resize-filter=bicubic'); + assert(optionsToQueryString({ region: Region.Australia, resizeFilter: ResizeFilter.Lanczos2 }), 'region=australia&resize-filter=lanczos2'); + assertThrows(() => optionsToQueryString({ region: Region.Australia, resizeFilter: 'invalid' })); +}); + +// Other options +routes.set('/image-optimizer/options/bgColor', () => { + // Hex strings + assert(optionsToQueryString({ + region: Region.Asia, + bgColor: '123456' + }), 'region=asia&bg-color=123456'); + assert(optionsToQueryString({ + region: Region.Asia, + bgColor: 'a2345e' + }), 'region=asia&bg-color=a2345e'); + assert(optionsToQueryString({ + region: Region.Asia, + bgColor: '123' + }), 'region=asia&bg-color=123'); + assert(optionsToQueryString({ + region: Region.Asia, + bgColor: 'a2e' + }), 'region=asia&bg-color=a2e'); + assertThrows(() => optionsToQueryString({ + region: Region.Asia, + bgColor: '12' + }), TypeError); + assertThrows(() => optionsToQueryString({ + region: Region.Asia, + bgColor: '12j' + }), TypeError); + + // RGB(A) + assert(optionsToQueryString({ + region: Region.Asia, + bgColor: { + r: 255, + g: 0, + b: 128, } - }); + }), 'region=asia&bg-color=255,0,128'); + assert(optionsToQueryString({ + region: Region.Asia, + bgColor: { + r: 255, + g: 0, + b: 128, + a: 0.5 + } + }), 'region=asia&bg-color=255,0,128,0.500000'); + assertThrows(() => optionsToQueryString({ + region: Region.Asia, + bgColor: { + r: 12, + b: 12 + } + }), TypeError); + assertThrows(() => optionsToQueryString({ + region: Region.Asia, + bgColor: { + r: 12, + b: 1212, + g: 12, + } + }), TypeError); + assertThrows(() => optionsToQueryString({ + region: Region.Asia, + bgColor: 123123 + }), TypeError); +}); + +routes.set('/image-optimizer/options/blur', () => { + assert(optionsToQueryString({ region: Region.Asia, blur: 0.5 }), 'region=asia&blur=0.500000'); + assert(optionsToQueryString({ region: Region.Asia, blur: 1000 }), 'region=asia&blur=1000.000000'); + assertThrows(() => optionsToQueryString({ region: Region.Asia, blur: 1001 }), TypeError); + assertThrows(() => optionsToQueryString({ region: Region.Asia, blur: 0.4 }), TypeError); + + assert(optionsToQueryString({ region: Region.Asia, blur: "10%" }), 'region=asia&blur=10.000000p'); + assert(optionsToQueryString({ region: Region.Asia, blur: "0.5%" }), 'region=asia&blur=0.500000p'); +}); +routes.set('/image-optimizer/options/brightness', () => { + assert(optionsToQueryString({ region: Region.Asia, brightness: 0.5 }), 'region=asia&brightness=0.500000'); + assert(optionsToQueryString({ region: Region.Asia, brightness: 100 }), 'region=asia&brightness=100.000000'); + assert(optionsToQueryString({ region: Region.Asia, brightness: -100 }), 'region=asia&brightness=-100.000000'); + assertThrows(() => optionsToQueryString({ region: Region.Asia, brightness: 1001 }), TypeError); + assertThrows(() => optionsToQueryString({ region: Region.Asia, brightness: -101 }), TypeError); +}); +routes.set('/image-optimizer/options/canvas', () => { +}); +routes.set('/image-optimizer/options/crop', () => { +}); +routes.set('/image-optimizer/options/dpr', () => { +}); +routes.set('/image-optimizer/options/blur', () => { +}); +routes.set('/image-optimizer/options/frame', () => { +}); +routes.set('/image-optimizer/options/height', () => { +}); +routes.set('/image-optimizer/options/level', () => { +}); +routes.set('/image-optimizer/options/pad', () => { +}); +routes.set('/image-optimizer/options/quality', () => { +}); +routes.set('/image-optimizer/options/saturation', () => { +}); +routes.set('/image-optimizer/options/sharpen', () => { +}); +routes.set('/image-optimizer/options/trim', () => { +}); +routes.set('/image-optimizer/options/viewbox', () => { }); +routes.set('/image-optimizer/options/width', () => { +}); \ No newline at end of file diff --git a/integration-tests/js-compute/fixtures/app/tests.json b/integration-tests/js-compute/fixtures/app/tests.json index f2690acde0..b146774014 100644 --- a/integration-tests/js-compute/fixtures/app/tests.json +++ b/integration-tests/js-compute/fixtures/app/tests.json @@ -4150,5 +4150,30 @@ "GET /html-rewriter/invalid-html": {}, "GET /html-rewriter/insertion-order": {}, "GET /html-rewriter/escape-html": {}, - "GET /image-optimizer/test": {} + "GET /image-optimizer/options/region": {}, + "GET /image-optimizer/options/auto": {}, + "GET /image-optimizer/options/bw": {}, + "GET /image-optimizer/options/crop-mode": {}, + "GET /image-optimizer/options/disable": {}, + "GET /image-optimizer/options/enable": {}, + "GET /image-optimizer/options/fit": {}, + "GET /image-optimizer/options/metadata": {}, + "GET /image-optimizer/options/orient": {}, + "GET /image-optimizer/options/profile": {}, + "GET /image-optimizer/options/resizeFilter": {}, + "GET /image-optimizer/options/bgColor": {}, + "GET /image-optimizer/options/blur": {}, + "GET /image-optimizer/options/brightness": {}, + "GET /image-optimizer/options/canvas": {}, + "GET /image-optimizer/options/crop": {}, + "GET /image-optimizer/options/dpr": {}, + "GET /image-optimizer/options/frame": {}, + "GET /image-optimizer/options/height": {}, + "GET /image-optimizer/options/level": {}, + "GET /image-optimizer/options/pad": {}, + "GET /image-optimizer/options/quality": {}, + "GET /image-optimizer/options/saturation": {}, + "GET /image-optimizer/options/sharpen": {}, + "GET /image-optimizer/options/trim": {}, + "GET /image-optimizer/options/viewbox": {} } \ No newline at end of file diff --git a/runtime/fastly/builtins/image-optimizer.cpp b/runtime/fastly/builtins/image-optimizer.cpp index b8ac98d590..f1bee6b7be 100644 --- a/runtime/fastly/builtins/image-optimizer.cpp +++ b/runtime/fastly/builtins/image-optimizer.cpp @@ -72,6 +72,17 @@ bool install(api::Engine *engine) { #define FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(type) #include "image-optimizer-options.inc" + auto options_to_query_string_fn = JS_NewFunction( + engine->cx(), &ImageOptimizerOptions::optionsToQueryString, 1, 0, "optionsToQueryString"); + RootedObject options_to_query_string_obj(engine->cx(), + JS_GetFunctionObject(options_to_query_string_fn)); + RootedValue options_to_query_string_val(engine->cx(), + JS::ObjectValue(*options_to_query_string_obj)); + if (!JS_SetProperty(engine->cx(), image_optimizer_ns, "optionsToQueryString", + options_to_query_string_val)) { + return false; + } + RootedValue image_optimizer_ns_val(engine->cx(), JS::ObjectValue(*image_optimizer_ns)); if (!engine->define_builtin_module("fastly:image-optimizer", image_optimizer_ns_val)) { return false; @@ -196,6 +207,20 @@ std::unique_ptr ImageOptimizerOptions::create(JSContext * trim_opt, std::move(trim_color_opt), viewbox_opt, width_opt)}; } +bool ImageOptimizerOptions::optionsToQueryString(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = CallArgsFromVp(argc, vp); + if (!args.requireAtLeast(cx, "optionsToQueryString", 1)) { + return false; + } + auto options = create(cx, args.get(0)); + if (!options) { + return false; + } + auto query = options->to_string(); + args.rval().setString(JS_NewStringCopyZ(cx, query.data())); + return true; +} + std::string ImageOptimizerOptions::to_string() const { using image_optimizer::to_string; std::string ret = to_string(region_); @@ -731,7 +756,8 @@ ImageOptimizerOptions::to_trim_color(JSContext *cx, JS::HandleValue val) { return name; \ } #define FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(type) \ - JS_ReportErrorUTF8(cx, #type " out of range"); \ + api::throw_error(cx, api::Errors::TypeError, "imageOptimizerOptions", #type, \ + "be one of the allowed string values"); \ return std::nullopt; \ } #include "image-optimizer-options.inc" diff --git a/runtime/fastly/builtins/image-optimizer.h b/runtime/fastly/builtins/image-optimizer.h index 9cec7867cb..2588196aab 100644 --- a/runtime/fastly/builtins/image-optimizer.h +++ b/runtime/fastly/builtins/image-optimizer.h @@ -31,6 +31,7 @@ class ImageOptimizerOptions { public: fastly_image_optimizer_transform_config to_config(); static std::unique_ptr create(JSContext *cx, JS::HandleValue opts_val); + static bool optionsToQueryString(JSContext *cx, unsigned argc, JS::Value *vp); std::string to_string() const; #define FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(type, lowercase, str) enum class type { @@ -259,11 +260,11 @@ class ImageOptimizerOptions { std::optional saturation, std::optional sharpen, std::optional trim, std::optional trim_color, std::optional viewbox, std::optional width) - : region_(region), auto_(auto_val), bg_color_(std::move(bg_color)), blur_(blur), bw_(bw), - canvas_(canvas), contrast_(contrast), crop_(crop), disable_(disable), dpr_(dpr), - enable_(enable), fit_(fit), format_(format), frame_(frame), height_(height), - level_(std::move(level)), metadata_(metadata), optimize_(optimize), orient_(orient), - pad_(pad), precrop_(precrop), profile_(profile), quality_(quality), + : region_(region), auto_(auto_val), bg_color_(std::move(bg_color)), blur_(blur), + brightness_(brightness), bw_(bw), canvas_(canvas), contrast_(contrast), crop_(crop), + disable_(disable), dpr_(dpr), enable_(enable), fit_(fit), format_(format), frame_(frame), + height_(height), level_(std::move(level)), metadata_(metadata), optimize_(optimize), + orient_(orient), pad_(pad), precrop_(precrop), profile_(profile), quality_(quality), resizeFilter_(resize_filter), saturation_(saturation), sharpen_(sharpen), trim_(trim), trim_color_(std::move(trim_color)), viewbox_(viewbox), width_(width) {} Region region_; diff --git a/src/bundle.js b/src/bundle.js index 8d5d46a48e..3b9ee4bd09 100644 --- a/src/bundle.js +++ b/src/bundle.js @@ -134,7 +134,7 @@ export const TransactionCacheEntry = globalThis.TransactionCacheEntry; return { contents: `export const { Region, Auto, Format, BWAlgorithm, Disable, Enable, Fit, Metadata, - Optimize, Orient, Profile, ResizeFilter, CropMode + Optimize, Orient, Profile, ResizeFilter, CropMode, optionsToQueryString } = globalThis.fastly.imageOptimizer;`, }; } diff --git a/types/image-optimizer.d.ts b/types/image-optimizer.d.ts index ac1b006855..f13093b6c0 100644 --- a/types/image-optimizer.d.ts +++ b/types/image-optimizer.d.ts @@ -125,11 +125,11 @@ declare module 'fastly:image-optimizer' { /** * Set the blurriness of the output image (0.5-1000). */ - blur?: number | string; + blur?: number | Percentage; /** - * Set the brightness of the output image. + * Set the brightness of the output image (-100,100). */ - brightness?: number | string; + brightness?: number; /** * Convert an image to black and white using a given algorithm. */ @@ -261,4 +261,8 @@ declare module 'fastly:image-optimizer' { */ width?: number | Percentage; } + /** + * Convert image optimizer options into the query string that is sent to the image optimizer, for logging and debugging purposes. + */ + function optionsToQueryString(options: ImageOptimizerOptions): string; } \ No newline at end of file From 2b7204f751e673f9bc5da0fd7f5a1a52a4857cb4 Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Wed, 12 Nov 2025 17:45:00 +0000 Subject: [PATCH 17/31] Finish tests --- .../fixtures/app/src/image-optimizer.js | 529 +++++++++++++++++- .../js-compute/fixtures/app/tests.json | 2 + .../builtins/image-optimizer-options.inc | 2 + runtime/fastly/builtins/image-optimizer.h | 11 +- types/image-optimizer.d.ts | 2 +- 5 files changed, 542 insertions(+), 4 deletions(-) diff --git a/integration-tests/js-compute/fixtures/app/src/image-optimizer.js b/integration-tests/js-compute/fixtures/app/src/image-optimizer.js index 3c368a284c..45e203e1ed 100644 --- a/integration-tests/js-compute/fixtures/app/src/image-optimizer.js +++ b/integration-tests/js-compute/fixtures/app/src/image-optimizer.js @@ -58,6 +58,11 @@ routes.set('/image-optimizer/options/metadata', () => { assert(optionsToQueryString({ region: Region.Australia, metadata: Metadata.CopyrightAndC2PA }), 'region=australia&metadata=copyright,c2pa'); assertThrows(() => optionsToQueryString({ region: Region.Australia, metadata: 'invalid' })); }); +routes.set('/image-optimizer/options/optimize', () => { + assert(optionsToQueryString({ region: Region.Australia, optimize: Optimize.Low }), 'region=australia&optimize=low'); + assert(optionsToQueryString({ region: Region.Australia, optimize: Optimize.High }), 'region=australia&optimize=high'); + assertThrows(() => optionsToQueryString({ region: Region.Australia, optimize: 'invalid' })); +}); routes.set('/image-optimizer/options/orient', () => { assert(optionsToQueryString({ region: Region.Australia, orient: Orient.Default }), 'region=australia&orient=1'); assert(optionsToQueryString({ region: Region.Australia, orient: Orient.FlipHorizontalOrientRight }), 'region=australia&orient=7'); @@ -159,30 +164,550 @@ routes.set('/image-optimizer/options/brightness', () => { assertThrows(() => optionsToQueryString({ region: Region.Asia, brightness: -101 }), TypeError); }); routes.set('/image-optimizer/options/canvas', () => { + assert(optionsToQueryString({ + region: Region.Asia, + canvas: { + size: { + absolute: { + width: 100, + height: '10%' + } + } + } + }), 'region=asia&canvas=100,10.000000p'); + assert(optionsToQueryString({ + region: Region.Asia, + canvas: { + size: { + ratio: { + width: 4, + height: 3 + } + } + } + }), 'region=asia&canvas=4.000000:3.000000'); + assert(optionsToQueryString({ + region: Region.Asia, + canvas: { + size: { + absolute: { + width: 100, + height: '10%' + } + }, + position: { + x: 10, + offsetY: 10 + } + } + }), 'region=asia&canvas=100,10.000000p,x10,offset-y10.000000'); + assert(optionsToQueryString({ + region: Region.Asia, + canvas: { + size: { + ratio: { + width: 4, + height: 3 + } + }, + position: { + offsetX: 10, + y: '10%' + } + } + }), 'region=asia&canvas=4.000000:3.000000,offset-x10.000000,y10.000000p'); + assertThrows(() => optionsToQueryString({ + region: Region.Asia, + canvas: { + position: { + offsetX: 10, + y: '10%' + } + } + }), TypeError); + assertThrows(() => optionsToQueryString({ + region: Region.Asia, + canvas: { + size: { + ratio: { + width: '4%', + height: 3 + } + }, + position: { + offsetX: 10, + y: '10%' + } + } + }), TypeError); + assertThrows(() => optionsToQueryString({ + region: Region.Asia, + canvas: { + size: { + ratio: { + width: '4%', + height: 3 + } + }, + position: { + offsetX: 10, + x: 100, + y: '10%' + } + } + }), TypeError); + assertThrows(() => optionsToQueryString({ + region: Region.Asia, + canvas: { + size: { + ratio: { + width: '4%', + height: 3 + } + }, + position: { + y: '10%' + } + } + }), TypeError); }); routes.set('/image-optimizer/options/crop', () => { + assert(optionsToQueryString({ + region: Region.Asia, + crop: { + size: { + absolute: { + width: 100, + height: '10%' + } + } + } + }), 'region=asia&crop=100,10.000000p'); + assert(optionsToQueryString({ + region: Region.Asia, + crop: { + size: { + ratio: { + width: 4, + height: 3 + } + } + } + }), 'region=asia&crop=4.000000:3.000000'); + assert(optionsToQueryString({ + region: Region.Asia, + crop: { + size: { + absolute: { + width: 100, + height: '10%' + } + }, + position: { + x: 10, + offsetY: 10 + } + } + }), 'region=asia&crop=100,10.000000p,x10,offset-y10.000000'); + assert(optionsToQueryString({ + region: Region.Asia, + crop: { + size: { + ratio: { + width: 4, + height: 3 + } + }, + position: { + offsetX: 10, + y: '10%' + } + } + }), 'region=asia&crop=4.000000:3.000000,offset-x10.000000,y10.000000p'); + assert(optionsToQueryString({ + region: Region.Asia, + crop: { + size: { + ratio: { + width: 4, + height: 3 + } + }, + position: { + offsetX: 10, + y: '10%' + }, + mode: CropMode.Safe + } + }), 'region=asia&crop=4.000000:3.000000,offset-x10.000000,y10.000000p,safe'); + assertThrows(() => optionsToQueryString({ + region: Region.Asia, + crop: { + position: { + offsetX: 10, + y: '10%' + } + } + }), TypeError); + assertThrows(() => optionsToQueryString({ + region: Region.Asia, + crop: { + size: { + ratio: { + width: '4%', + height: 3 + } + }, + position: { + offsetX: 10, + y: '10%' + } + } + }), TypeError); + assertThrows(() => optionsToQueryString({ + region: Region.Asia, + crop: { + size: { + ratio: { + width: '4%', + height: 3 + } + }, + position: { + offsetX: 10, + x: 100, + y: '10%' + } + } + }), TypeError); + assertThrows(() => optionsToQueryString({ + region: Region.Asia, + crop: { + size: { + ratio: { + width: '4%', + height: 3 + } + }, + position: { + y: '10%' + } + } + }), TypeError); + assertThrows(() => optionsToQueryString({ + region: Region.Asia, + crop: { + size: { + ratio: { + width: 4, + height: 3 + } + }, + position: { + offsetX: 10, + y: '10%' + }, + mode: 'invalid' + } + }), TypeError); }); -routes.set('/image-optimizer/options/dpr', () => { +routes.set('/image-optimizer/options/precrop', () => { + assert(optionsToQueryString({ + region: Region.Asia, + precrop: { + size: { + absolute: { + width: 100, + height: '10%' + } + } + } + }), 'region=asia&precrop=100,10.000000p'); + assert(optionsToQueryString({ + region: Region.Asia, + precrop: { + size: { + ratio: { + width: 4, + height: 3 + } + } + } + }), 'region=asia&precrop=4.000000:3.000000'); + assert(optionsToQueryString({ + region: Region.Asia, + precrop: { + size: { + absolute: { + width: 100, + height: '10%' + } + }, + position: { + x: 10, + offsetY: 10 + } + } + }), 'region=asia&precrop=100,10.000000p,x10,offset-y10.000000'); + assert(optionsToQueryString({ + region: Region.Asia, + precrop: { + size: { + ratio: { + width: 4, + height: 3 + } + }, + position: { + offsetX: 10, + y: '10%' + } + } + }), 'region=asia&precrop=4.000000:3.000000,offset-x10.000000,y10.000000p'); + assert(optionsToQueryString({ + region: Region.Asia, + precrop: { + size: { + ratio: { + width: 4, + height: 3 + } + }, + position: { + offsetX: 10, + y: '10%' + }, + mode: CropMode.Safe + } + }), 'region=asia&precrop=4.000000:3.000000,offset-x10.000000,y10.000000p,safe'); + assertThrows(() => optionsToQueryString({ + region: Region.Asia, + precrop: { + position: { + offsetX: 10, + y: '10%' + } + } + }), TypeError); + assertThrows(() => optionsToQueryString({ + region: Region.Asia, + precrop: { + size: { + ratio: { + width: '4%', + height: 3 + } + }, + position: { + offsetX: 10, + y: '10%' + } + } + }), TypeError); + assertThrows(() => optionsToQueryString({ + region: Region.Asia, + precrop: { + size: { + ratio: { + width: '4%', + height: 3 + } + }, + position: { + offsetX: 10, + x: 100, + y: '10%' + } + } + }), TypeError); + assertThrows(() => optionsToQueryString({ + region: Region.Asia, + precrop: { + size: { + ratio: { + width: '4%', + height: 3 + } + }, + position: { + y: '10%' + } + } + }), TypeError); + assertThrows(() => optionsToQueryString({ + region: Region.Asia, + precrop: { + size: { + ratio: { + width: 4, + height: 3 + } + }, + position: { + offsetX: 10, + y: '10%' + }, + mode: 'invalid' + } + }), TypeError); }); -routes.set('/image-optimizer/options/blur', () => { +routes.set('/image-optimizer/options/dpr', () => { + assert(optionsToQueryString({ region: Region.Asia, dpr: 1.5 }), 'region=asia&dpr=1.500000'); + assert(optionsToQueryString({ region: Region.Asia, dpr: 10 }), 'region=asia&dpr=10.000000'); + assertThrows(() => optionsToQueryString({ region: Region.Asia, dpr: '1001' }), TypeError); + assertThrows(() => optionsToQueryString({ region: Region.Asia, dpr: 1001 }), TypeError); + assertThrows(() => optionsToQueryString({ region: Region.Asia, dpr: 0.5 }), TypeError); }); routes.set('/image-optimizer/options/frame', () => { + assert(optionsToQueryString({ region: Region.Asia, frame: 1 }), 'region=asia&frame=1'); + assertThrows(() => optionsToQueryString({ region: Region.Asia, frame: 2 }), TypeError); }); routes.set('/image-optimizer/options/height', () => { + assert(optionsToQueryString({ region: Region.Asia, height: 1000 }), 'region=asia&height=1000'); + assert(optionsToQueryString({ region: Region.Asia, height: '10%' }), 'region=asia&height=10.000000p'); + assertThrows(() => optionsToQueryString({ region: Region.Asia, height: '1001' }), TypeError); + assertThrows(() => optionsToQueryString({ region: Region.Asia, height: 100.5 }), TypeError); }); routes.set('/image-optimizer/options/level', () => { + assert(optionsToQueryString({ region: Region.Asia, level: '1.1' }), 'region=asia&level=1.1'); + assert(optionsToQueryString({ region: Region.Asia, level: '5.1' }), 'region=asia&level=5.1'); + assertThrows(() => optionsToQueryString({ region: Region.Asia, level: 5.1 }), TypeError); + assertThrows(() => optionsToQueryString({ region: Region.Asia, level: '7.1' }), TypeError); }); routes.set('/image-optimizer/options/pad', () => { + assert(optionsToQueryString({ + region: Region.Asia, + pad: { + top: 10, + bottom: 20, + left: '10%', + right: '20%' + } + }), 'region=asia&pad=10,20.000000p,20,10.000000p'); + assertThrows(() => optionsToQueryString({ + region: Region.Asia, + pad: { + top: 10, + bottom: 20, + left: '10', + right: '20%' + } + }), TypeError); + assertThrows(() => optionsToQueryString({ + region: Region.Asia, + pad: { + top: 10, + left: '10', + right: '20%' + } + }), TypeError); }); routes.set('/image-optimizer/options/quality', () => { + assert(optionsToQueryString({ region: Region.Asia, quality: 1 }), 'region=asia&quality=1'); + assert(optionsToQueryString({ region: Region.Asia, quality: 100 }), 'region=asia&quality=100'); + assertThrows(() => optionsToQueryString({ region: Region.Asia, quality: 1001 }), TypeError); + assertThrows(() => optionsToQueryString({ region: Region.Asia, quality: 1.5 }), TypeError); + assertThrows(() => optionsToQueryString({ region: Region.Asia, quality: 0.4 }), TypeError); }); routes.set('/image-optimizer/options/saturation', () => { + assert(optionsToQueryString({ region: Region.Asia, saturation: 1 }), 'region=asia&saturation=1.000000'); + assert(optionsToQueryString({ region: Region.Asia, saturation: 100 }), 'region=asia&saturation=100.000000'); + assert(optionsToQueryString({ region: Region.Asia, saturation: -100 }), 'region=asia&saturation=-100.000000'); + assertThrows(() => optionsToQueryString({ region: Region.Asia, saturation: 1001 }), TypeError); + assertThrows(() => optionsToQueryString({ region: Region.Asia, saturation: -101 }), TypeError); }); routes.set('/image-optimizer/options/sharpen', () => { + assert(optionsToQueryString({ + region: Region.Asia, + sharpen: { + amount: 10, + radius: 10, + threshold: 1, + } + }), 'region=asia&sharpen=a10.000000,r10.000000,t1'); + assert(optionsToQueryString({ + region: Region.Asia, + sharpen: { + amount: 0.1, + radius: 0.5, + threshold: 255, + } + }), 'region=asia&sharpen=a0.100000,r0.500000,t255'); + assertThrows(() => { + optionsToQueryString({ + region: Region.Asia, + sharpen: { + amount: 0.1, + radius: 0.5, + threshold: 256, + } + }) + }, TypeError); + assertThrows(() => { + optionsToQueryString({ + region: Region.Asia, + sharpen: { + amount: 0.1, + radius: 0.4, + threshold: 255, + } + }) + }, TypeError); + assertThrows(() => { + optionsToQueryString({ + region: Region.Asia, + sharpen: { + amount: -1, + radius: 0.5, + threshold: 255, + } + }) + }, TypeError); + assertThrows(() => { + optionsToQueryString({ + region: Region.Asia, + sharpen: { + amount: 1, + radius: 1, + } + }) + }, TypeError); }); routes.set('/image-optimizer/options/trim', () => { + assert(optionsToQueryString({ + region: Region.Asia, + trim: { + top: 10, + bottom: 20, + left: '10%', + right: '20%' + } + }), 'region=asia&trim=10,20.000000p,20,10.000000p'); + assertThrows(() => optionsToQueryString({ + region: Region.Asia, + trim: { + top: 10, + bottom: 20, + left: '10', + right: '20%' + } + }), TypeError); + assertThrows(() => optionsToQueryString({ + region: Region.Asia, + trim: { + top: 10, + left: '10', + right: '20%' + } + }), TypeError); }); routes.set('/image-optimizer/options/viewbox', () => { + assert(optionsToQueryString({ region: Region.Asia, viewbox: 1 }), 'region=asia&viewbox=1'); + assertThrows(() => optionsToQueryString({ region: Region.Asia, viewbox: 2 }), TypeError); }); routes.set('/image-optimizer/options/width', () => { + assert(optionsToQueryString({ region: Region.Asia, width: 1000 }), 'region=asia&width=1000'); + assert(optionsToQueryString({ region: Region.Asia, width: '10%' }), 'region=asia&width=10.000000p'); + assertThrows(() => optionsToQueryString({ region: Region.Asia, width: '1001' }), TypeError); + assertThrows(() => optionsToQueryString({ region: Region.Asia, width: 100.5 }), TypeError); }); \ No newline at end of file diff --git a/integration-tests/js-compute/fixtures/app/tests.json b/integration-tests/js-compute/fixtures/app/tests.json index b146774014..0c34410271 100644 --- a/integration-tests/js-compute/fixtures/app/tests.json +++ b/integration-tests/js-compute/fixtures/app/tests.json @@ -4158,6 +4158,7 @@ "GET /image-optimizer/options/enable": {}, "GET /image-optimizer/options/fit": {}, "GET /image-optimizer/options/metadata": {}, + "GET /image-optimizer/options/optimize": {}, "GET /image-optimizer/options/orient": {}, "GET /image-optimizer/options/profile": {}, "GET /image-optimizer/options/resizeFilter": {}, @@ -4166,6 +4167,7 @@ "GET /image-optimizer/options/brightness": {}, "GET /image-optimizer/options/canvas": {}, "GET /image-optimizer/options/crop": {}, + "GET /image-optimizer/options/precrop": {}, "GET /image-optimizer/options/dpr": {}, "GET /image-optimizer/options/frame": {}, "GET /image-optimizer/options/height": {}, diff --git a/runtime/fastly/builtins/image-optimizer-options.inc b/runtime/fastly/builtins/image-optimizer-options.inc index 18b3b8ebe6..e685960370 100644 --- a/runtime/fastly/builtins/image-optimizer-options.inc +++ b/runtime/fastly/builtins/image-optimizer-options.inc @@ -108,6 +108,8 @@ FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Lanczos3, "lanczos3") FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Lanczos, "lanczos") FASTLY_END_IMAGE_OPTIMIZER_OPTION_TYPE(ResizeFilter) +// CropMode is not a "true" option, but we reuse some of this machinery to easily define the +// constants FASTLY_BEGIN_IMAGE_OPTIMIZER_OPTION_TYPE(CropMode, crop_mode, "N/A") FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Smart, "smart") FASTLY_DEFINE_IMAGE_OPTIMIZER_OPTION(Safe, "safe") diff --git a/runtime/fastly/builtins/image-optimizer.h b/runtime/fastly/builtins/image-optimizer.h index 2588196aab..2cebb07763 100644 --- a/runtime/fastly/builtins/image-optimizer.h +++ b/runtime/fastly/builtins/image-optimizer.h @@ -352,6 +352,7 @@ inline std::string to_string(const ImageOptimizerOptions::Position &position) { auto dbl = std::get(position.x); ret += "offset-x" + std::to_string(dbl); } + ret += ','; if (auto value = std::get_if(&position.y)) { ret += 'y' + to_string(*value); } else { @@ -376,7 +377,15 @@ inline std::string to_string(const ImageOptimizerOptions::CropSpec &crop) { ret += ',' + to_string(*crop.position); } if (crop.mode) { - ret += ',' + to_string(*crop.mode); + ret += ','; + switch (*crop.mode) { + case ImageOptimizerOptions::CropMode::Safe: + ret += "safe"; + break; + case ImageOptimizerOptions::CropMode::Smart: + ret += "smart"; + break; + } } return ret; } diff --git a/types/image-optimizer.d.ts b/types/image-optimizer.d.ts index f13093b6c0..b4d39c9226 100644 --- a/types/image-optimizer.d.ts +++ b/types/image-optimizer.d.ts @@ -158,7 +158,7 @@ declare module 'fastly:image-optimizer' { */ disable?: 'upscale'; /** - * Ratio between physical pixels and logical pixels. + * Ratio between physical pixels and logical pixels (1-10). */ dpr?: number; /** From 79f8024e6f3f27419587b5fc20853ab909728f08 Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Wed, 12 Nov 2025 17:46:07 +0000 Subject: [PATCH 18/31] Revert bad changes --- .../fixtures/app/src/request-auto-decompress.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/integration-tests/js-compute/fixtures/app/src/request-auto-decompress.js b/integration-tests/js-compute/fixtures/app/src/request-auto-decompress.js index 439fe53e55..898e8a4946 100644 --- a/integration-tests/js-compute/fixtures/app/src/request-auto-decompress.js +++ b/integration-tests/js-compute/fixtures/app/src/request-auto-decompress.js @@ -4,7 +4,7 @@ import { routes } from './routes.js'; // Request.fastly.decompressGzip option -- automatic gzip decompression of responses routes.set('/request/constructor/fastly/decompressGzip/true', async () => { - const request = new Request('https://localhost:7676/gzip', { + const request = new Request('https://httpbin.org/gzip', { headers: { accept: 'application/json', }, @@ -20,7 +20,7 @@ routes.set('/request/constructor/fastly/decompressGzip/true', async () => { }); routes.set('/request/constructor/fastly/decompressGzip/false', async () => { - const request = new Request('https://localhost:7676/gzip', { + const request = new Request('https://httpbin.org/gzip', { headers: { accept: 'application/json', }, @@ -38,7 +38,7 @@ routes.set('/request/constructor/fastly/decompressGzip/false', async () => { }); routes.set('/fetch/requestinit/fastly/decompressGzip/true', async () => { - const response = await fetch('https://localhost:7676/gzip', { + const response = await fetch('https://httpbin.org/gzip', { headers: { accept: 'application/json', }, @@ -53,7 +53,7 @@ routes.set('/fetch/requestinit/fastly/decompressGzip/true', async () => { }); routes.set('/fetch/requestinit/fastly/decompressGzip/false', async () => { - const response = await fetch('https://localhost:7676/gzip', { + const response = await fetch('https://httpbin.org/gzip', { headers: { accept: 'application/json', }, From 5181cb572c5e0e54dd0c7ee95b74b9c0869ac239 Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Wed, 12 Nov 2025 17:51:35 +0000 Subject: [PATCH 19/31] Remove added files --- sy-test/.fastlyignore | 3 --- sy-test/.gitignore | 3 --- sy-test/README.md | 31 ------------------------------- sy-test/fastly.toml | 25 ------------------------- sy-test/src/index.js | 15 --------------- 5 files changed, 77 deletions(-) delete mode 100644 sy-test/.fastlyignore delete mode 100644 sy-test/.gitignore delete mode 100644 sy-test/README.md delete mode 100644 sy-test/fastly.toml delete mode 100644 sy-test/src/index.js diff --git a/sy-test/.fastlyignore b/sy-test/.fastlyignore deleted file mode 100644 index d851121617..0000000000 --- a/sy-test/.fastlyignore +++ /dev/null @@ -1,3 +0,0 @@ -/node_modules -/bin -/pkg diff --git a/sy-test/.gitignore b/sy-test/.gitignore deleted file mode 100644 index d851121617..0000000000 --- a/sy-test/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/node_modules -/bin -/pkg diff --git a/sy-test/README.md b/sy-test/README.md deleted file mode 100644 index 71d306a5c8..0000000000 --- a/sy-test/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# Empty Starter Kit for JavaScript - -[![Deploy to Fastly](https://deploy.edgecompute.app/button)](https://deploy.edgecompute.app/deploy) - -An empty application template for the Fastly Compute environment which returns a 200 OK response. - -**For more details about other starter kits for Compute, see the [Fastly developer hub](https://developer.fastly.com/solutions/starters)** - -## Running the application - -To create an application using this starter kit, create a new directory for your application and switch to it, and then type the following command: - -```shell -npm create @fastly/compute@latest -- --language=javascript --starter-kit=empty -``` - -To build and run your new application in the local development environment, type the following command: - -```shell -npm run start -``` - -To build and deploy your application to your Fastly account, type the following command. The first time you deploy the application, you will be prompted to create a new service in your account. - -```shell -npm run deploy -``` - -## Security issues - -Please see our [SECURITY.md](SECURITY.md) for guidance on reporting security-related issues. diff --git a/sy-test/fastly.toml b/sy-test/fastly.toml deleted file mode 100644 index 79b38e36de..0000000000 --- a/sy-test/fastly.toml +++ /dev/null @@ -1,25 +0,0 @@ -# This file describes a Fastly Compute package. To learn more visit: -# https://www.fastly.com/documentation/reference/compute/fastly-toml - -authors = ["tartanllama@gmail.com"] -cloned_from = "https://github.com/fastly/compute-starter-kit-javascript-empty" -description = "" -language = "javascript" -manifest_version = 3 -name = "sy-test" -service_id = "" - -[local_server] -[local_server.backends] -[local_server.backends.httpme] -url = "https://http-me.glitch.me" - -[scripts] -build = "node ../js-compute-runtime-cli.js src/index.js --debug-build" -post_init = "npm install" - -[setup] -[setup.backends] -[setup.backends.httpme] -address = "http-me.glitch.me" -port = 443 diff --git a/sy-test/src/index.js b/sy-test/src/index.js deleted file mode 100644 index ad8aea56e1..0000000000 --- a/sy-test/src/index.js +++ /dev/null @@ -1,15 +0,0 @@ -/// - -import { Region, Format } from 'fastly:image-optimizer'; - -addEventListener("fetch", (event) => event.respondWith(handleRequest(event))); - -async function handleRequest(event) { - return await fetch('https://http-me.glitch.me/image-jpeg', { - imageOptimizerOptions: { - region: Region.UsEast, - format: Format.PNG - }, - backend: 'httpme' - }); -} From 9cee306bb36c4a2f151e8f0b60255f3f611be0f4 Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Wed, 12 Nov 2025 17:51:46 +0000 Subject: [PATCH 20/31] Remove stale code --- runtime/fastly/builtins/fetch/fetch.cpp | 2 -- runtime/fastly/builtins/image-optimizer.cpp | 1 - runtime/fastly/host-api/host_api.cpp | 8 +------- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/runtime/fastly/builtins/fetch/fetch.cpp b/runtime/fastly/builtins/fetch/fetch.cpp index 3fd106d9a2..554b8e9daf 100644 --- a/runtime/fastly/builtins/fetch/fetch.cpp +++ b/runtime/fastly/builtins/fetch/fetch.cpp @@ -12,7 +12,6 @@ #include "builtin.h" #include "encode.h" #include "extension-api.h" -#include using builtins::web::streams::NativeStreamSink; using builtins::web::streams::NativeStreamSource; @@ -279,7 +278,6 @@ bool fetch_send_body(JSContext *cx, HandleObject request, JS::MutableHandleValue JS::GetReservedSlot(request, static_cast(Request::Slots::ImageOptimizerOptions)) .toPrivate()); auto config_str = config->to_string(); - std::cerr << "sending config to image optimizer: " << config_str << std::endl; auto res = request_handle.send_image_optimizer(body, backend_chars, config_str); if (auto *err = res.to_err()) { HANDLE_IMAGE_OPTIMIZER_ERROR(cx, *err); diff --git a/runtime/fastly/builtins/image-optimizer.cpp b/runtime/fastly/builtins/image-optimizer.cpp index f1bee6b7be..bd0a0b39c5 100644 --- a/runtime/fastly/builtins/image-optimizer.cpp +++ b/runtime/fastly/builtins/image-optimizer.cpp @@ -1,6 +1,5 @@ #include "image-optimizer.h" #include "fastly.h" -#include namespace { std::optional from_percentage(std::string_view sv) { diff --git a/runtime/fastly/host-api/host_api.cpp b/runtime/fastly/host-api/host_api.cpp index f551464b60..4d60996e0f 100644 --- a/runtime/fastly/host-api/host_api.cpp +++ b/runtime/fastly/host-api/host_api.cpp @@ -48,7 +48,7 @@ static void log_hostcall(const char *func_name, ...) { #define MILLISECS_IN_NANOSECS 1000000 #define SECS_IN_NANOSECS 1000000000 -#include + static bool convert_result(int res, fastly::fastly_host_error *err) { if (res == 0) return true; @@ -918,7 +918,6 @@ make_fastly_image_optimizer_error(fastly::fastly_image_optimizer_error_detail im } FastlyImageOptimizerError make_fastly_image_optimizer_error(fastly::fastly_host_error err) { - std::cerr << "MAKING IT " << static_cast(err) << std::endl; return {err}; } @@ -1500,11 +1499,6 @@ HttpReq::send_image_optimizer(HttpBody body, std::string_view backend, fastly::fastly_image_optimizer_transform_config config{config_str.data(), config_str.size()}; fastly::fastly_image_optimizer_error_detail io_err_out{}; uint32_t resp_handle_out = INVALID_HANDLE, body_handle_out = INVALID_HANDLE; - std::cerr << "DOING IMAGE TRANSFORM: handle: " << this->handle - << " body handle: " << INVALID_HANDLE << " backend_str: " << backend_str.ptr - << " backend_len: " << backend_str.len << " opts: " << opts - << " config: " << config.sdk_claims_opts - << " config_len: " << config.sdk_claims_opts_len << std::endl; auto host_call_success = convert_result( fastly::image_optimizer_transform_image_optimizer_request( this->handle, orig_req_body_handle, reinterpret_cast(backend_str.ptr), From f2ddb99ec535399a53c06bc1b5f9cb1520e21cb5 Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Thu, 13 Nov 2025 09:42:40 +0000 Subject: [PATCH 21/31] Correct doc style --- documentation/docs/image-optimizer/Auto.mdx | 17 +++++++++----- .../docs/image-optimizer/BWAlgorithm.mdx | 17 +++++++++----- .../docs/image-optimizer/CropMode.mdx | 23 +++++++++++-------- .../docs/image-optimizer/Disable.mdx | 19 +++++++++------ documentation/docs/image-optimizer/Enable.mdx | 19 +++++++++------ documentation/docs/image-optimizer/Fit.mdx | 21 ++++++++++------- documentation/docs/image-optimizer/Format.mdx | 17 +++++++++----- .../docs/image-optimizer/Metadata.mdx | 17 +++++++++----- .../docs/image-optimizer/Optimize.mdx | 17 +++++++++----- documentation/docs/image-optimizer/Orient.mdx | 17 +++++++++----- .../docs/image-optimizer/Profile.mdx | 17 +++++++++----- documentation/docs/image-optimizer/Region.mdx | 15 ++++++++---- .../docs/image-optimizer/ResizeFilter.mdx | 20 ++++++++++------ .../image-optimizer/imageOptimizerOptions.mdx | 11 ++++++--- .../version-3.36.0-sidebars.json | 8 +++++++ 15 files changed, 167 insertions(+), 88 deletions(-) create mode 100644 documentation/versioned_sidebars/version-3.36.0-sidebars.json diff --git a/documentation/docs/image-optimizer/Auto.mdx b/documentation/docs/image-optimizer/Auto.mdx index 423ad3a90a..ee14fd4912 100644 --- a/documentation/docs/image-optimizer/Auto.mdx +++ b/documentation/docs/image-optimizer/Auto.mdx @@ -18,11 +18,16 @@ Enumerator options for [`imageOptimizerOptions.auto`](./imageOptimizerOptions.md ```js import { Auto, Region } from 'fastly:image-optimizer'; -new Request( - "https://my.image.host/image.png", + +addEventListener("fetch", (event) => event.respondWith(handleRequest(event))); + +async function handleRequest(event) { + return await fetch('https://www.w3.org/Graphics/PNG/text2.png', { imageOptimizerOptions: { - region: Region.UsEast, - auto: Auto.AVIF - } -) + region: Region.UsEast, + auto: Auto.AVIF + }, + backend: 'w3' + }); +} ``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/BWAlgorithm.mdx b/documentation/docs/image-optimizer/BWAlgorithm.mdx index 2aeb580fb6..ecd80224ba 100644 --- a/documentation/docs/image-optimizer/BWAlgorithm.mdx +++ b/documentation/docs/image-optimizer/BWAlgorithm.mdx @@ -19,11 +19,16 @@ Enumerator options for [`imageOptimizerOptions.bw`](./imageOptimizerOptions.mdx) ```js import { Region, BWAlgorithm } from 'fastly:image-optimizer'; -new Request( - "https://my.image.host/image.png", + +addEventListener("fetch", (event) => event.respondWith(handleRequest(event))); + +async function handleRequest(event) { + return await fetch('https://www.w3.org/Graphics/PNG/text2.png', { imageOptimizerOptions: { - region: Region.UsEast, - bw: BWAlgorithm.Threshold - } -) + region: Region.UsEast, + bw: BWAlgorithm.Threshold + }, + backend: 'w3' + }); +} ``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/CropMode.mdx b/documentation/docs/image-optimizer/CropMode.mdx index 4ad4e1c215..dc9da1c02c 100644 --- a/documentation/docs/image-optimizer/CropMode.mdx +++ b/documentation/docs/image-optimizer/CropMode.mdx @@ -18,14 +18,19 @@ Enumerator options for [`imageOptimizerOptions.crop.mode`](./imageOptimizerOptio ```js import { CropMode, Region } from 'fastly:image-optimizer'; -new Request( - "https://my.image.host/image.png", + +addEventListener("fetch", (event) => event.respondWith(handleRequest(event))); + +async function handleRequest(event) { + return await fetch('https://www.w3.org/Graphics/PNG/text2.png', { imageOptimizerOptions: { - region: Region.UsEast, - crop: { - size: { ratio: { width: 4, height: 3 } }, - mode: CropMode.Smart, - } - } -) + region: Region.UsEast, + crop: { + size: { ratio: { width: 4, height: 3 } }, + mode: CropMode.Smart, + } + }, + backend: 'w3' + }); +} ``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/Disable.mdx b/documentation/docs/image-optimizer/Disable.mdx index 5076b3bd68..1c6efad7d9 100644 --- a/documentation/docs/image-optimizer/Disable.mdx +++ b/documentation/docs/image-optimizer/Disable.mdx @@ -17,12 +17,17 @@ Enumerator options for [`imageOptimizerOptions.disable`](./imageOptimizerOptions ```js import { Disable, Region } from 'fastly:image-optimizer'; -new Request( - "https://my.image.host/image.png", + +addEventListener("fetch", (event) => event.respondWith(handleRequest(event))); + +async function handleRequest(event) { + return await fetch('https://www.w3.org/Graphics/PNG/text2.png', { imageOptimizerOptions: { - region: Region.UsEast, - width: 2560, - disable: Disable.Upscale, - } -) + region: Region.UsEast, + width: 2560, + disable: Disable.Upscale + }, + backend: 'w3' + }); +} ``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/Enable.mdx b/documentation/docs/image-optimizer/Enable.mdx index 9ebeb6946d..52ce4f510f 100644 --- a/documentation/docs/image-optimizer/Enable.mdx +++ b/documentation/docs/image-optimizer/Enable.mdx @@ -17,12 +17,17 @@ Enumerator options for [`imageOptimizerOptions.enable`](./imageOptimizerOptions. ```js import { Enable, Region } from 'fastly:image-optimizer'; -new Request( - "https://my.image.host/image.png", + +addEventListener("fetch", (event) => event.respondWith(handleRequest(event))); + +async function handleRequest(event) { + return await fetch('https://www.w3.org/Graphics/PNG/text2.png', { imageOptimizerOptions: { - region: Region.UsEast, - width: 2560, - enable: Enable.Upscale, - } -) + region: Region.UsEast, + width: 2560, + enable: Enable.Upscale + }, + backend: 'w3' + }); +} ``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/Fit.mdx b/documentation/docs/image-optimizer/Fit.mdx index 1b4b96ffb3..f1e8fd7174 100644 --- a/documentation/docs/image-optimizer/Fit.mdx +++ b/documentation/docs/image-optimizer/Fit.mdx @@ -19,13 +19,18 @@ Enumerator options for [`imageOptimizerOptions.fit`](./imageOptimizerOptions.mdx ```js import { Fit, Region } from 'fastly:image-optimizer'; -new Request( - "https://my.image.host/image.png", + +addEventListener("fetch", (event) => event.respondWith(handleRequest(event))); + +async function handleRequest(event) { + return await fetch('https://www.w3.org/Graphics/PNG/text2.png', { imageOptimizerOptions: { - region: Region.UsEast, - width: 150, - height: 150, - fit: Fit.Bounds, - } -) + region: Region.UsEast, + width: 150, + height; 150, + fit: Fit.Bounds + }, + backend: 'w3' + }); +} ``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/Format.mdx b/documentation/docs/image-optimizer/Format.mdx index 6baba499d8..4419da1b0a 100644 --- a/documentation/docs/image-optimizer/Format.mdx +++ b/documentation/docs/image-optimizer/Format.mdx @@ -31,11 +31,16 @@ Enumerator options for [`imageOptimizerOptions.format`](./imageOptimizerOptions. ```js import { Format, Region } from 'fastly:image-optimizer'; -new Request( - "https://my.image.host/image.png", + +addEventListener("fetch", (event) => event.respondWith(handleRequest(event))); + +async function handleRequest(event) { + return await fetch('https://www.w3.org/Graphics/PNG/text2.png', { imageOptimizerOptions: { - region: Region.UsEast, - format: Format.PNG, - } -) + region: Region.UsEast, + format: Format.PNG + }, + backend: 'w3' + }); +} ``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/Metadata.mdx b/documentation/docs/image-optimizer/Metadata.mdx index 356ab23cbd..402e6ea92b 100644 --- a/documentation/docs/image-optimizer/Metadata.mdx +++ b/documentation/docs/image-optimizer/Metadata.mdx @@ -19,11 +19,16 @@ Enumerator options for [`imageOptimizerOptions.metadata`](./imageOptimizerOption ```js import { Metadata, Region } from 'fastly:image-optimizer'; -new Request( - "https://my.image.host/image.png", + +addEventListener("fetch", (event) => event.respondWith(handleRequest(event))); + +async function handleRequest(event) { + return await fetch('https://www.w3.org/Graphics/PNG/text2.png', { imageOptimizerOptions: { - region: Region.UsEast, - metadata: Metadata.Copyright - } -) + region: Region.UsEast, + metadata: Metadata.Copyright + }, + backend: 'w3' + }); +} ``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/Optimize.mdx b/documentation/docs/image-optimizer/Optimize.mdx index c6a36094b5..4271e8190e 100644 --- a/documentation/docs/image-optimizer/Optimize.mdx +++ b/documentation/docs/image-optimizer/Optimize.mdx @@ -19,11 +19,16 @@ Enumerator options for [`imageOptimizerOptions.optimize`](./imageOptimizerOption ```js import { Optimize, Region } from 'fastly:image-optimizer'; -new Request( - "https://my.image.host/image.png", + +addEventListener("fetch", (event) => event.respondWith(handleRequest(event))); + +async function handleRequest(event) { + return await fetch('https://www.w3.org/Graphics/PNG/text2.png', { imageOptimizerOptions: { - region: Region.UsEast, - optimize: Optimize.High - } -) + region: Region.UsEast, + optimize: Optimize.High + }, + backend: 'w3' + }); +} ``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/Orient.mdx b/documentation/docs/image-optimizer/Orient.mdx index 8d94675330..642154c6db 100644 --- a/documentation/docs/image-optimizer/Orient.mdx +++ b/documentation/docs/image-optimizer/Orient.mdx @@ -24,11 +24,16 @@ Enumerator options for [`imageOptimizerOptions.orient`](./imageOptimizerOptions. ```js import { Orient, Region } from 'fastly:image-optimizer'; -new Request( - "https://my.image.host/image.png", + +addEventListener("fetch", (event) => event.respondWith(handleRequest(event))); + +async function handleRequest(event) { + return await fetch('https://www.w3.org/Graphics/PNG/text2.png', { imageOptimizerOptions: { - region: Region.UsEast, - orient: Orient.FlipHorizontal - } -) + region: Region.UsEast, + orient: Orient.FlipHorizontal + }, + backend: 'w3' + }); +} ``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/Profile.mdx b/documentation/docs/image-optimizer/Profile.mdx index 261a07a084..193382ae54 100644 --- a/documentation/docs/image-optimizer/Profile.mdx +++ b/documentation/docs/image-optimizer/Profile.mdx @@ -19,11 +19,16 @@ Enumerator options for [`imageOptimizerOptions.profile`](./imageOptimizerOptions ```js import { Profile, Region } from 'fastly:image-optimizer'; -new Request( - "https://my.image.host/image.png", + +addEventListener("fetch", (event) => event.respondWith(handleRequest(event))); + +async function handleRequest(event) { + return await fetch('https://www.w3.org/Graphics/PNG/text2.png', { imageOptimizerOptions: { - region: Region.UsEast, - orient: Orient.FlipHorizontal - } -) + region: Region.UsEast, + profile: Profile.Main + }, + backend: 'w3' + }); +} ``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/Region.mdx b/documentation/docs/image-optimizer/Region.mdx index 0c7a24f13f..3d046e9607 100644 --- a/documentation/docs/image-optimizer/Region.mdx +++ b/documentation/docs/image-optimizer/Region.mdx @@ -24,10 +24,15 @@ Enumerator options for [`imageOptimizerOptions.region`](./imageOptimizerOptions. ```js import { Region } from 'fastly:image-optimizer'; -new Request( - "https://my.image.host/image.png", + +addEventListener("fetch", (event) => event.respondWith(handleRequest(event))); + +async function handleRequest(event) { + return await fetch('https://www.w3.org/Graphics/PNG/text2.png', { imageOptimizerOptions: { - region: Region.UsEast - } -) + region: Region.UsEast + }, + backend: 'w3' + }); +} ``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/ResizeFilter.mdx b/documentation/docs/image-optimizer/ResizeFilter.mdx index c0a7860155..cd04a4b287 100644 --- a/documentation/docs/image-optimizer/ResizeFilter.mdx +++ b/documentation/docs/image-optimizer/ResizeFilter.mdx @@ -23,12 +23,18 @@ Enumerator options for [`imageOptimizerOptions.resizeFilter`](./imageOptimizerOp ## Examples ```js -import { Profile, Region } from 'fastly:image-optimizer'; -new Request( - "https://my.image.host/image.png", +import { ResizeFilter, Region } from 'fastly:image-optimizer'; + +addEventListener("fetch", (event) => event.respondWith(handleRequest(event))); + +async function handleRequest(event) { + return await fetch('https://www.w3.org/Graphics/PNG/text2.png', { imageOptimizerOptions: { - region: Region.UsEast, - resizeFilter: ResizeFilter.Linear - } -) + region: Region.UsEast, + width: 2560, + resizeFilter: ResizeFilter.Linear + }, + backend: 'w3' + }); +} ``` \ No newline at end of file diff --git a/documentation/docs/image-optimizer/imageOptimizerOptions.mdx b/documentation/docs/image-optimizer/imageOptimizerOptions.mdx index 78fb2ced0d..8675d5076a 100644 --- a/documentation/docs/image-optimizer/imageOptimizerOptions.mdx +++ b/documentation/docs/image-optimizer/imageOptimizerOptions.mdx @@ -102,8 +102,11 @@ An `Object` containing either: ```js import { Format, Orient, CropMode, Region } from 'fastly:image-optimizer'; -new Request( - "https://my.image.host/image.png", + +addEventListener("fetch", (event) => event.respondWith(handleRequest(event))); + +async function handleRequest(event) { + return await fetch('https://www.w3.org/Graphics/PNG/text2.png', { imageOptimizerOptions: { region: Region.UsEast, format: Format.PNG, @@ -126,5 +129,7 @@ new Request( trim: { top: 10, left: 10, right: 10, bottom: 10 }, pad: { top: 30, left: 30, right: "1%", bottom: 30 } }, -); + backend: 'w3' + }); +} ``` \ No newline at end of file diff --git a/documentation/versioned_sidebars/version-3.36.0-sidebars.json b/documentation/versioned_sidebars/version-3.36.0-sidebars.json new file mode 100644 index 0000000000..cff0c94e17 --- /dev/null +++ b/documentation/versioned_sidebars/version-3.36.0-sidebars.json @@ -0,0 +1,8 @@ +{ + "defaultSidebar": [ + { + "type": "autogenerated", + "dirName": "." + } + ] +} From 961368faf7825ba2c30bada122a19ba0ae2144e6 Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Thu, 13 Nov 2025 09:43:02 +0000 Subject: [PATCH 22/31] Remove file --- .../versioned_sidebars/version-3.36.0-sidebars.json | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 documentation/versioned_sidebars/version-3.36.0-sidebars.json diff --git a/documentation/versioned_sidebars/version-3.36.0-sidebars.json b/documentation/versioned_sidebars/version-3.36.0-sidebars.json deleted file mode 100644 index cff0c94e17..0000000000 --- a/documentation/versioned_sidebars/version-3.36.0-sidebars.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "defaultSidebar": [ - { - "type": "autogenerated", - "dirName": "." - } - ] -} From a7a9306a1cc898c83d56c33335d244e1682f6b39 Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Fri, 14 Nov 2025 11:59:28 +0000 Subject: [PATCH 23/31] Correct caching --- runtime/fastly/builtins/fetch/fetch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/fastly/builtins/fetch/fetch.cpp b/runtime/fastly/builtins/fetch/fetch.cpp index 554b8e9daf..61691cb2fb 100644 --- a/runtime/fastly/builtins/fetch/fetch.cpp +++ b/runtime/fastly/builtins/fetch/fetch.cpp @@ -1151,7 +1151,7 @@ bool fetch(JSContext *cx, unsigned argc, Value *vp) { // If not cacheable, fallback to non-caching path if (!is_cacheable) { DEBUG_LOG("HTTP Cache: Request not cacheable, using non-caching fetch") - return fetch_send_body(cx, request, args.rval()); + return fetch_send_body(cx, request, args.rval()); } // Lookup in cache From 24bfddc3998369d702223339a9833d0a57947ae1 Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Fri, 14 Nov 2025 12:39:12 +0000 Subject: [PATCH 24/31] Correct caching --- runtime/fastly/builtins/fetch/fetch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/fastly/builtins/fetch/fetch.cpp b/runtime/fastly/builtins/fetch/fetch.cpp index 61691cb2fb..23df03b20e 100644 --- a/runtime/fastly/builtins/fetch/fetch.cpp +++ b/runtime/fastly/builtins/fetch/fetch.cpp @@ -229,7 +229,7 @@ bool fetch_send_body(JSContext *cx, HandleObject request, JS::MutableHandleValue } // cache override only applies to requests with caching - if (caching_mode == CachingMode::Guest) { + if (caching_mode == CachingMode::Host) { if (!Request::apply_cache_override(cx, request)) { return false; } From 9bf896655ce161a48d07a3d385c3d0df20c1d0eb Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Fri, 14 Nov 2025 12:52:24 +0000 Subject: [PATCH 25/31] Upgrade Viceroy --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 939a5b6868..fa6c19bd04 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,7 +11,7 @@ defaults: shell: bash env: # Note: when updated, also update version in ensure-cargo-installs - viceroy_version: 0.12.2 + viceroy_version: 0.15.0 # Note: when updated, also update version in ensure-cargo-installs ! AND ! release-please.yml wasm-tools_version: 1.216.0 fastly-cli_version: 10.19.0 @@ -46,7 +46,7 @@ jobs: matrix: include: - crate: viceroy - version: 0.12.2 # Note: workflow-level env vars can't be used in matrix definitions + version: 0.15.0 # Note: workflow-level env vars can't be used in matrix definitions options: "" - crate: wasm-tools version: 1.216.0 # Note: workflow-level env vars can't be used in matrix definitions From 1df3bf63b70c53ea1bba21528fae75438593667e Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Fri, 14 Nov 2025 12:52:56 +0000 Subject: [PATCH 26/31] Format --- .../fixtures/app/src/image-optimizer.js | 1579 +++++++++----- .../js-compute/fixtures/app/src/index.js | 12 +- .../js-compute/fixtures/app/tests.json | 1916 ++++------------- types/globals.d.ts | 70 +- types/image-optimizer.d.ts | 526 ++--- 5 files changed, 1732 insertions(+), 2371 deletions(-) diff --git a/integration-tests/js-compute/fixtures/app/src/image-optimizer.js b/integration-tests/js-compute/fixtures/app/src/image-optimizer.js index 45e203e1ed..b8e611ecda 100644 --- a/integration-tests/js-compute/fixtures/app/src/image-optimizer.js +++ b/integration-tests/js-compute/fixtures/app/src/image-optimizer.js @@ -2,712 +2,1115 @@ import { routes } from './routes.js'; import { - Region, - Auto, - BWAlgorithm, - CropMode, - Disable, - Enable, - Fit, - Metadata, - Optimize, - Orient, - Profile, - ResizeFilter, - optionsToQueryString, + Region, + Auto, + BWAlgorithm, + CropMode, + Disable, + Enable, + Fit, + Metadata, + Optimize, + Orient, + Profile, + ResizeFilter, + optionsToQueryString, } from 'fastly:image-optimizer'; import { assert, assertThrows } from './assertions.js'; // Enums routes.set('/image-optimizer/options/region', () => { - assert(optionsToQueryString({ region: Region.UsEast }), 'region=us_east'); - assert(optionsToQueryString({ region: Region.Asia }), 'region=asia'); - assertThrows(() => optionsToQueryString({ region: 'invalid' }), TypeError); - assertThrows(() => optionsToQueryString({}), TypeError); + assert(optionsToQueryString({ region: Region.UsEast }), 'region=us_east'); + assert(optionsToQueryString({ region: Region.Asia }), 'region=asia'); + assertThrows(() => optionsToQueryString({ region: 'invalid' }), TypeError); + assertThrows(() => optionsToQueryString({}), TypeError); }); routes.set('/image-optimizer/options/auto', () => { - assert(optionsToQueryString({ region: Region.Asia, auto: Auto.AVIF }), 'region=asia&auto=avif'); - assert(optionsToQueryString({ region: Region.Asia, auto: Auto.WEBP }), 'region=asia&auto=webp'); - assertThrows(() => optionsToQueryString({ region: Region.Asia, auto: 'invalid' }), TypeError); + assert( + optionsToQueryString({ region: Region.Asia, auto: Auto.AVIF }), + 'region=asia&auto=avif', + ); + assert( + optionsToQueryString({ region: Region.Asia, auto: Auto.WEBP }), + 'region=asia&auto=webp', + ); + assertThrows( + () => optionsToQueryString({ region: Region.Asia, auto: 'invalid' }), + TypeError, + ); }); routes.set('/image-optimizer/options/bw', () => { - assert(optionsToQueryString({ region: Region.EuCentral, bw: BWAlgorithm.Threshold }), 'region=eu_central&bw=threshold'); - assert(optionsToQueryString({ region: Region.EuCentral, bw: BWAlgorithm.Atkinson }), 'region=eu_central&bw=atkinson'); - assertThrows(() => optionsToQueryString({ region: Region.EuCentral, bw: 'invalid' }), TypeError); + assert( + optionsToQueryString({ + region: Region.EuCentral, + bw: BWAlgorithm.Threshold, + }), + 'region=eu_central&bw=threshold', + ); + assert( + optionsToQueryString({ + region: Region.EuCentral, + bw: BWAlgorithm.Atkinson, + }), + 'region=eu_central&bw=atkinson', + ); + assertThrows( + () => optionsToQueryString({ region: Region.EuCentral, bw: 'invalid' }), + TypeError, + ); }); routes.set('/image-optimizer/options/crop-mode', () => { - const qs = optionsToQueryString({ region: Region.UsWest, crop: { size: { absolute: { width: 100, height: 100 } }, mode: CropMode.Smart } }); - assert(qs.includes('smart'), true); - assertThrows(() => optionsToQueryString({ region: Region.UsWest, crop: { size: { absolute: { width: 100, height: 100 } }, mode: 'bad' } }), TypeError); + const qs = optionsToQueryString({ + region: Region.UsWest, + crop: { + size: { absolute: { width: 100, height: 100 } }, + mode: CropMode.Smart, + }, + }); + assert(qs.includes('smart'), true); + assertThrows( + () => + optionsToQueryString({ + region: Region.UsWest, + crop: { size: { absolute: { width: 100, height: 100 } }, mode: 'bad' }, + }), + TypeError, + ); }); routes.set('/image-optimizer/options/disable', () => { - assert(optionsToQueryString({ region: Region.Australia, disable: Disable.Upscale }), 'region=australia&disable=upscale'); - assertThrows(() => optionsToQueryString({ region: Region.Australia, disable: 'invalid' })); + assert( + optionsToQueryString({ + region: Region.Australia, + disable: Disable.Upscale, + }), + 'region=australia&disable=upscale', + ); + assertThrows(() => + optionsToQueryString({ region: Region.Australia, disable: 'invalid' }), + ); }); routes.set('/image-optimizer/options/enable', () => { - assert(optionsToQueryString({ region: Region.Australia, enable: Enable.Upscale }), 'region=australia&enable=upscale'); - assertThrows(() => optionsToQueryString({ region: Region.Australia, enable: 'invalid' })); + assert( + optionsToQueryString({ region: Region.Australia, enable: Enable.Upscale }), + 'region=australia&enable=upscale', + ); + assertThrows(() => + optionsToQueryString({ region: Region.Australia, enable: 'invalid' }), + ); }); routes.set('/image-optimizer/options/fit', () => { - assert(optionsToQueryString({ region: Region.Australia, fit: Fit.Crop }), 'region=australia&fit=crop'); - assert(optionsToQueryString({ region: Region.Australia, fit: Fit.Cover }), 'region=australia&fit=cover'); - assertThrows(() => optionsToQueryString({ region: Region.Australia, fit: 'invalid' })); + assert( + optionsToQueryString({ region: Region.Australia, fit: Fit.Crop }), + 'region=australia&fit=crop', + ); + assert( + optionsToQueryString({ region: Region.Australia, fit: Fit.Cover }), + 'region=australia&fit=cover', + ); + assertThrows(() => + optionsToQueryString({ region: Region.Australia, fit: 'invalid' }), + ); }); routes.set('/image-optimizer/options/metadata', () => { - assert(optionsToQueryString({ region: Region.Australia, metadata: Metadata.C2PA }), 'region=australia&metadata=c2pa'); - assert(optionsToQueryString({ region: Region.Australia, metadata: Metadata.CopyrightAndC2PA }), 'region=australia&metadata=copyright,c2pa'); - assertThrows(() => optionsToQueryString({ region: Region.Australia, metadata: 'invalid' })); + assert( + optionsToQueryString({ region: Region.Australia, metadata: Metadata.C2PA }), + 'region=australia&metadata=c2pa', + ); + assert( + optionsToQueryString({ + region: Region.Australia, + metadata: Metadata.CopyrightAndC2PA, + }), + 'region=australia&metadata=copyright,c2pa', + ); + assertThrows(() => + optionsToQueryString({ region: Region.Australia, metadata: 'invalid' }), + ); }); routes.set('/image-optimizer/options/optimize', () => { - assert(optionsToQueryString({ region: Region.Australia, optimize: Optimize.Low }), 'region=australia&optimize=low'); - assert(optionsToQueryString({ region: Region.Australia, optimize: Optimize.High }), 'region=australia&optimize=high'); - assertThrows(() => optionsToQueryString({ region: Region.Australia, optimize: 'invalid' })); + assert( + optionsToQueryString({ region: Region.Australia, optimize: Optimize.Low }), + 'region=australia&optimize=low', + ); + assert( + optionsToQueryString({ region: Region.Australia, optimize: Optimize.High }), + 'region=australia&optimize=high', + ); + assertThrows(() => + optionsToQueryString({ region: Region.Australia, optimize: 'invalid' }), + ); }); routes.set('/image-optimizer/options/orient', () => { - assert(optionsToQueryString({ region: Region.Australia, orient: Orient.Default }), 'region=australia&orient=1'); - assert(optionsToQueryString({ region: Region.Australia, orient: Orient.FlipHorizontalOrientRight }), 'region=australia&orient=7'); - assertThrows(() => optionsToQueryString({ region: Region.Australia, orient: 'invalid' })); + assert( + optionsToQueryString({ region: Region.Australia, orient: Orient.Default }), + 'region=australia&orient=1', + ); + assert( + optionsToQueryString({ + region: Region.Australia, + orient: Orient.FlipHorizontalOrientRight, + }), + 'region=australia&orient=7', + ); + assertThrows(() => + optionsToQueryString({ region: Region.Australia, orient: 'invalid' }), + ); }); routes.set('/image-optimizer/options/profile', () => { - assert(optionsToQueryString({ region: Region.Australia, profile: Profile.Baseline }), 'region=australia&profile=baseline'); - assert(optionsToQueryString({ region: Region.Australia, profile: Profile.Main }), 'region=australia&profile=main'); - assertThrows(() => optionsToQueryString({ region: Region.Australia, profile: 'invalid' })); + assert( + optionsToQueryString({ + region: Region.Australia, + profile: Profile.Baseline, + }), + 'region=australia&profile=baseline', + ); + assert( + optionsToQueryString({ region: Region.Australia, profile: Profile.Main }), + 'region=australia&profile=main', + ); + assertThrows(() => + optionsToQueryString({ region: Region.Australia, profile: 'invalid' }), + ); }); routes.set('/image-optimizer/options/resizeFilter', () => { - assert(optionsToQueryString({ region: Region.Australia, resizeFilter: ResizeFilter.Bicubic }), 'region=australia&resize-filter=bicubic'); - assert(optionsToQueryString({ region: Region.Australia, resizeFilter: ResizeFilter.Lanczos2 }), 'region=australia&resize-filter=lanczos2'); - assertThrows(() => optionsToQueryString({ region: Region.Australia, resizeFilter: 'invalid' })); + assert( + optionsToQueryString({ + region: Region.Australia, + resizeFilter: ResizeFilter.Bicubic, + }), + 'region=australia&resize-filter=bicubic', + ); + assert( + optionsToQueryString({ + region: Region.Australia, + resizeFilter: ResizeFilter.Lanczos2, + }), + 'region=australia&resize-filter=lanczos2', + ); + assertThrows(() => + optionsToQueryString({ region: Region.Australia, resizeFilter: 'invalid' }), + ); }); // Other options routes.set('/image-optimizer/options/bgColor', () => { - // Hex strings - assert(optionsToQueryString({ + // Hex strings + assert( + optionsToQueryString({ + region: Region.Asia, + bgColor: '123456', + }), + 'region=asia&bg-color=123456', + ); + assert( + optionsToQueryString({ + region: Region.Asia, + bgColor: 'a2345e', + }), + 'region=asia&bg-color=a2345e', + ); + assert( + optionsToQueryString({ + region: Region.Asia, + bgColor: '123', + }), + 'region=asia&bg-color=123', + ); + assert( + optionsToQueryString({ + region: Region.Asia, + bgColor: 'a2e', + }), + 'region=asia&bg-color=a2e', + ); + assertThrows( + () => + optionsToQueryString({ region: Region.Asia, - bgColor: '123456' - }), 'region=asia&bg-color=123456'); - assert(optionsToQueryString({ + bgColor: '12', + }), + TypeError, + ); + assertThrows( + () => + optionsToQueryString({ region: Region.Asia, - bgColor: 'a2345e' - }), 'region=asia&bg-color=a2345e'); - assert(optionsToQueryString({ - region: Region.Asia, - bgColor: '123' - }), 'region=asia&bg-color=123'); - assert(optionsToQueryString({ - region: Region.Asia, - bgColor: 'a2e' - }), 'region=asia&bg-color=a2e'); - assertThrows(() => optionsToQueryString({ - region: Region.Asia, - bgColor: '12' - }), TypeError); - assertThrows(() => optionsToQueryString({ - region: Region.Asia, - bgColor: '12j' - }), TypeError); + bgColor: '12j', + }), + TypeError, + ); - // RGB(A) - assert(optionsToQueryString({ - region: Region.Asia, - bgColor: { - r: 255, - g: 0, - b: 128, - } - }), 'region=asia&bg-color=255,0,128'); - assert(optionsToQueryString({ - region: Region.Asia, - bgColor: { - r: 255, - g: 0, - b: 128, - a: 0.5 - } - }), 'region=asia&bg-color=255,0,128,0.500000'); - assertThrows(() => optionsToQueryString({ + // RGB(A) + assert( + optionsToQueryString({ + region: Region.Asia, + bgColor: { + r: 255, + g: 0, + b: 128, + }, + }), + 'region=asia&bg-color=255,0,128', + ); + assert( + optionsToQueryString({ + region: Region.Asia, + bgColor: { + r: 255, + g: 0, + b: 128, + a: 0.5, + }, + }), + 'region=asia&bg-color=255,0,128,0.500000', + ); + assertThrows( + () => + optionsToQueryString({ region: Region.Asia, bgColor: { - r: 12, - b: 12 - } - }), TypeError); - assertThrows(() => optionsToQueryString({ + r: 12, + b: 12, + }, + }), + TypeError, + ); + assertThrows( + () => + optionsToQueryString({ region: Region.Asia, bgColor: { - r: 12, - b: 1212, - g: 12, - } - }), TypeError); + r: 12, + b: 1212, + g: 12, + }, + }), + TypeError, + ); - assertThrows(() => optionsToQueryString({ + assertThrows( + () => + optionsToQueryString({ region: Region.Asia, - bgColor: 123123 - }), TypeError); + bgColor: 123123, + }), + TypeError, + ); }); routes.set('/image-optimizer/options/blur', () => { - assert(optionsToQueryString({ region: Region.Asia, blur: 0.5 }), 'region=asia&blur=0.500000'); - assert(optionsToQueryString({ region: Region.Asia, blur: 1000 }), 'region=asia&blur=1000.000000'); - assertThrows(() => optionsToQueryString({ region: Region.Asia, blur: 1001 }), TypeError); - assertThrows(() => optionsToQueryString({ region: Region.Asia, blur: 0.4 }), TypeError); + assert( + optionsToQueryString({ region: Region.Asia, blur: 0.5 }), + 'region=asia&blur=0.500000', + ); + assert( + optionsToQueryString({ region: Region.Asia, blur: 1000 }), + 'region=asia&blur=1000.000000', + ); + assertThrows( + () => optionsToQueryString({ region: Region.Asia, blur: 1001 }), + TypeError, + ); + assertThrows( + () => optionsToQueryString({ region: Region.Asia, blur: 0.4 }), + TypeError, + ); - assert(optionsToQueryString({ region: Region.Asia, blur: "10%" }), 'region=asia&blur=10.000000p'); - assert(optionsToQueryString({ region: Region.Asia, blur: "0.5%" }), 'region=asia&blur=0.500000p'); + assert( + optionsToQueryString({ region: Region.Asia, blur: '10%' }), + 'region=asia&blur=10.000000p', + ); + assert( + optionsToQueryString({ region: Region.Asia, blur: '0.5%' }), + 'region=asia&blur=0.500000p', + ); }); routes.set('/image-optimizer/options/brightness', () => { - assert(optionsToQueryString({ region: Region.Asia, brightness: 0.5 }), 'region=asia&brightness=0.500000'); - assert(optionsToQueryString({ region: Region.Asia, brightness: 100 }), 'region=asia&brightness=100.000000'); - assert(optionsToQueryString({ region: Region.Asia, brightness: -100 }), 'region=asia&brightness=-100.000000'); - assertThrows(() => optionsToQueryString({ region: Region.Asia, brightness: 1001 }), TypeError); - assertThrows(() => optionsToQueryString({ region: Region.Asia, brightness: -101 }), TypeError); + assert( + optionsToQueryString({ region: Region.Asia, brightness: 0.5 }), + 'region=asia&brightness=0.500000', + ); + assert( + optionsToQueryString({ region: Region.Asia, brightness: 100 }), + 'region=asia&brightness=100.000000', + ); + assert( + optionsToQueryString({ region: Region.Asia, brightness: -100 }), + 'region=asia&brightness=-100.000000', + ); + assertThrows( + () => optionsToQueryString({ region: Region.Asia, brightness: 1001 }), + TypeError, + ); + assertThrows( + () => optionsToQueryString({ region: Region.Asia, brightness: -101 }), + TypeError, + ); }); routes.set('/image-optimizer/options/canvas', () => { - assert(optionsToQueryString({ - region: Region.Asia, - canvas: { - size: { - absolute: { - width: 100, - height: '10%' - } - } - } - }), 'region=asia&canvas=100,10.000000p'); - assert(optionsToQueryString({ - region: Region.Asia, - canvas: { - size: { - ratio: { - width: 4, - height: 3 - } - } - } - }), 'region=asia&canvas=4.000000:3.000000'); - assert(optionsToQueryString({ - region: Region.Asia, - canvas: { - size: { - absolute: { - width: 100, - height: '10%' - } - }, - position: { - x: 10, - offsetY: 10 - } - } - }), 'region=asia&canvas=100,10.000000p,x10,offset-y10.000000'); - assert(optionsToQueryString({ - region: Region.Asia, - canvas: { - size: { - ratio: { - width: 4, - height: 3 - } - }, - position: { - offsetX: 10, - y: '10%' - } - } - }), 'region=asia&canvas=4.000000:3.000000,offset-x10.000000,y10.000000p'); - assertThrows(() => optionsToQueryString({ + assert( + optionsToQueryString({ + region: Region.Asia, + canvas: { + size: { + absolute: { + width: 100, + height: '10%', + }, + }, + }, + }), + 'region=asia&canvas=100,10.000000p', + ); + assert( + optionsToQueryString({ + region: Region.Asia, + canvas: { + size: { + ratio: { + width: 4, + height: 3, + }, + }, + }, + }), + 'region=asia&canvas=4.000000:3.000000', + ); + assert( + optionsToQueryString({ + region: Region.Asia, + canvas: { + size: { + absolute: { + width: 100, + height: '10%', + }, + }, + position: { + x: 10, + offsetY: 10, + }, + }, + }), + 'region=asia&canvas=100,10.000000p,x10,offset-y10.000000', + ); + assert( + optionsToQueryString({ + region: Region.Asia, + canvas: { + size: { + ratio: { + width: 4, + height: 3, + }, + }, + position: { + offsetX: 10, + y: '10%', + }, + }, + }), + 'region=asia&canvas=4.000000:3.000000,offset-x10.000000,y10.000000p', + ); + assertThrows( + () => + optionsToQueryString({ region: Region.Asia, canvas: { - position: { - offsetX: 10, - y: '10%' - } - } - }), TypeError); - assertThrows(() => optionsToQueryString({ + position: { + offsetX: 10, + y: '10%', + }, + }, + }), + TypeError, + ); + assertThrows( + () => + optionsToQueryString({ region: Region.Asia, canvas: { - size: { - ratio: { - width: '4%', - height: 3 - } + size: { + ratio: { + width: '4%', + height: 3, }, - position: { - offsetX: 10, - y: '10%' - } - } - }), TypeError); - assertThrows(() => optionsToQueryString({ + }, + position: { + offsetX: 10, + y: '10%', + }, + }, + }), + TypeError, + ); + assertThrows( + () => + optionsToQueryString({ region: Region.Asia, canvas: { - size: { - ratio: { - width: '4%', - height: 3 - } + size: { + ratio: { + width: '4%', + height: 3, }, - position: { - offsetX: 10, - x: 100, - y: '10%' - } - } - }), TypeError); - assertThrows(() => optionsToQueryString({ + }, + position: { + offsetX: 10, + x: 100, + y: '10%', + }, + }, + }), + TypeError, + ); + assertThrows( + () => + optionsToQueryString({ region: Region.Asia, canvas: { - size: { - ratio: { - width: '4%', - height: 3 - } + size: { + ratio: { + width: '4%', + height: 3, }, - position: { - y: '10%' - } - } - }), TypeError); + }, + position: { + y: '10%', + }, + }, + }), + TypeError, + ); }); routes.set('/image-optimizer/options/crop', () => { - assert(optionsToQueryString({ - region: Region.Asia, - crop: { - size: { - absolute: { - width: 100, - height: '10%' - } - } - } - }), 'region=asia&crop=100,10.000000p'); - assert(optionsToQueryString({ - region: Region.Asia, - crop: { - size: { - ratio: { - width: 4, - height: 3 - } - } - } - }), 'region=asia&crop=4.000000:3.000000'); - assert(optionsToQueryString({ + assert( + optionsToQueryString({ + region: Region.Asia, + crop: { + size: { + absolute: { + width: 100, + height: '10%', + }, + }, + }, + }), + 'region=asia&crop=100,10.000000p', + ); + assert( + optionsToQueryString({ + region: Region.Asia, + crop: { + size: { + ratio: { + width: 4, + height: 3, + }, + }, + }, + }), + 'region=asia&crop=4.000000:3.000000', + ); + assert( + optionsToQueryString({ + region: Region.Asia, + crop: { + size: { + absolute: { + width: 100, + height: '10%', + }, + }, + position: { + x: 10, + offsetY: 10, + }, + }, + }), + 'region=asia&crop=100,10.000000p,x10,offset-y10.000000', + ); + assert( + optionsToQueryString({ + region: Region.Asia, + crop: { + size: { + ratio: { + width: 4, + height: 3, + }, + }, + position: { + offsetX: 10, + y: '10%', + }, + }, + }), + 'region=asia&crop=4.000000:3.000000,offset-x10.000000,y10.000000p', + ); + assert( + optionsToQueryString({ + region: Region.Asia, + crop: { + size: { + ratio: { + width: 4, + height: 3, + }, + }, + position: { + offsetX: 10, + y: '10%', + }, + mode: CropMode.Safe, + }, + }), + 'region=asia&crop=4.000000:3.000000,offset-x10.000000,y10.000000p,safe', + ); + assertThrows( + () => + optionsToQueryString({ region: Region.Asia, crop: { - size: { - absolute: { - width: 100, - height: '10%' - } - }, - position: { - x: 10, - offsetY: 10 - } - } - }), 'region=asia&crop=100,10.000000p,x10,offset-y10.000000'); - assert(optionsToQueryString({ + position: { + offsetX: 10, + y: '10%', + }, + }, + }), + TypeError, + ); + assertThrows( + () => + optionsToQueryString({ region: Region.Asia, crop: { - size: { - ratio: { - width: 4, - height: 3 - } + size: { + ratio: { + width: '4%', + height: 3, }, - position: { - offsetX: 10, - y: '10%' - } - } - }), 'region=asia&crop=4.000000:3.000000,offset-x10.000000,y10.000000p'); - assert(optionsToQueryString({ + }, + position: { + offsetX: 10, + y: '10%', + }, + }, + }), + TypeError, + ); + assertThrows( + () => + optionsToQueryString({ region: Region.Asia, crop: { - size: { - ratio: { - width: 4, - height: 3 - } + size: { + ratio: { + width: '4%', + height: 3, }, - position: { - offsetX: 10, - y: '10%' - }, - mode: CropMode.Safe - } - }), 'region=asia&crop=4.000000:3.000000,offset-x10.000000,y10.000000p,safe'); - assertThrows(() => optionsToQueryString({ + }, + position: { + offsetX: 10, + x: 100, + y: '10%', + }, + }, + }), + TypeError, + ); + assertThrows( + () => + optionsToQueryString({ region: Region.Asia, crop: { - position: { - offsetX: 10, - y: '10%' - } - } - }), TypeError); - assertThrows(() => optionsToQueryString({ - region: Region.Asia, - crop: { - size: { - ratio: { - width: '4%', - height: 3 - } + size: { + ratio: { + width: '4%', + height: 3, }, - position: { - offsetX: 10, - y: '10%' - } - } - }), TypeError); - assertThrows(() => optionsToQueryString({ + }, + position: { + y: '10%', + }, + }, + }), + TypeError, + ); + assertThrows( + () => + optionsToQueryString({ region: Region.Asia, crop: { - size: { - ratio: { - width: '4%', - height: 3 - } + size: { + ratio: { + width: 4, + height: 3, }, - position: { - offsetX: 10, - x: 100, - y: '10%' - } - } - }), TypeError); - assertThrows(() => optionsToQueryString({ - region: Region.Asia, - crop: { - size: { - ratio: { - width: '4%', - height: 3 - } - }, - position: { - y: '10%' - } - } - }), TypeError); - assertThrows(() => optionsToQueryString({ - region: Region.Asia, - crop: { - size: { - ratio: { - width: 4, - height: 3 - } - }, - position: { - offsetX: 10, - y: '10%' - }, - mode: 'invalid' - } - }), TypeError); + }, + position: { + offsetX: 10, + y: '10%', + }, + mode: 'invalid', + }, + }), + TypeError, + ); }); routes.set('/image-optimizer/options/precrop', () => { - assert(optionsToQueryString({ - region: Region.Asia, - precrop: { - size: { - absolute: { - width: 100, - height: '10%' - } - } - } - }), 'region=asia&precrop=100,10.000000p'); - assert(optionsToQueryString({ + assert( + optionsToQueryString({ + region: Region.Asia, + precrop: { + size: { + absolute: { + width: 100, + height: '10%', + }, + }, + }, + }), + 'region=asia&precrop=100,10.000000p', + ); + assert( + optionsToQueryString({ + region: Region.Asia, + precrop: { + size: { + ratio: { + width: 4, + height: 3, + }, + }, + }, + }), + 'region=asia&precrop=4.000000:3.000000', + ); + assert( + optionsToQueryString({ + region: Region.Asia, + precrop: { + size: { + absolute: { + width: 100, + height: '10%', + }, + }, + position: { + x: 10, + offsetY: 10, + }, + }, + }), + 'region=asia&precrop=100,10.000000p,x10,offset-y10.000000', + ); + assert( + optionsToQueryString({ + region: Region.Asia, + precrop: { + size: { + ratio: { + width: 4, + height: 3, + }, + }, + position: { + offsetX: 10, + y: '10%', + }, + }, + }), + 'region=asia&precrop=4.000000:3.000000,offset-x10.000000,y10.000000p', + ); + assert( + optionsToQueryString({ + region: Region.Asia, + precrop: { + size: { + ratio: { + width: 4, + height: 3, + }, + }, + position: { + offsetX: 10, + y: '10%', + }, + mode: CropMode.Safe, + }, + }), + 'region=asia&precrop=4.000000:3.000000,offset-x10.000000,y10.000000p,safe', + ); + assertThrows( + () => + optionsToQueryString({ region: Region.Asia, precrop: { - size: { - ratio: { - width: 4, - height: 3 - } - } - } - }), 'region=asia&precrop=4.000000:3.000000'); - assert(optionsToQueryString({ + position: { + offsetX: 10, + y: '10%', + }, + }, + }), + TypeError, + ); + assertThrows( + () => + optionsToQueryString({ region: Region.Asia, precrop: { - size: { - absolute: { - width: 100, - height: '10%' - } + size: { + ratio: { + width: '4%', + height: 3, }, - position: { - x: 10, - offsetY: 10 - } - } - }), 'region=asia&precrop=100,10.000000p,x10,offset-y10.000000'); - assert(optionsToQueryString({ + }, + position: { + offsetX: 10, + y: '10%', + }, + }, + }), + TypeError, + ); + assertThrows( + () => + optionsToQueryString({ region: Region.Asia, precrop: { - size: { - ratio: { - width: 4, - height: 3 - } + size: { + ratio: { + width: '4%', + height: 3, }, - position: { - offsetX: 10, - y: '10%' - } - } - }), 'region=asia&precrop=4.000000:3.000000,offset-x10.000000,y10.000000p'); - assert(optionsToQueryString({ + }, + position: { + offsetX: 10, + x: 100, + y: '10%', + }, + }, + }), + TypeError, + ); + assertThrows( + () => + optionsToQueryString({ region: Region.Asia, precrop: { - size: { - ratio: { - width: 4, - height: 3 - } + size: { + ratio: { + width: '4%', + height: 3, }, - position: { - offsetX: 10, - y: '10%' - }, - mode: CropMode.Safe - } - }), 'region=asia&precrop=4.000000:3.000000,offset-x10.000000,y10.000000p,safe'); - assertThrows(() => optionsToQueryString({ - region: Region.Asia, - precrop: { - position: { - offsetX: 10, - y: '10%' - } - } - }), TypeError); - assertThrows(() => optionsToQueryString({ + }, + position: { + y: '10%', + }, + }, + }), + TypeError, + ); + assertThrows( + () => + optionsToQueryString({ region: Region.Asia, precrop: { - size: { - ratio: { - width: '4%', - height: 3 - } + size: { + ratio: { + width: 4, + height: 3, }, - position: { - offsetX: 10, - y: '10%' - } - } - }), TypeError); - assertThrows(() => optionsToQueryString({ - region: Region.Asia, - precrop: { - size: { - ratio: { - width: '4%', - height: 3 - } - }, - position: { - offsetX: 10, - x: 100, - y: '10%' - } - } - }), TypeError); - assertThrows(() => optionsToQueryString({ - region: Region.Asia, - precrop: { - size: { - ratio: { - width: '4%', - height: 3 - } - }, - position: { - y: '10%' - } - } - }), TypeError); - assertThrows(() => optionsToQueryString({ - region: Region.Asia, - precrop: { - size: { - ratio: { - width: 4, - height: 3 - } - }, - position: { - offsetX: 10, - y: '10%' - }, - mode: 'invalid' - } - }), TypeError); + }, + position: { + offsetX: 10, + y: '10%', + }, + mode: 'invalid', + }, + }), + TypeError, + ); }); routes.set('/image-optimizer/options/dpr', () => { - assert(optionsToQueryString({ region: Region.Asia, dpr: 1.5 }), 'region=asia&dpr=1.500000'); - assert(optionsToQueryString({ region: Region.Asia, dpr: 10 }), 'region=asia&dpr=10.000000'); - assertThrows(() => optionsToQueryString({ region: Region.Asia, dpr: '1001' }), TypeError); - assertThrows(() => optionsToQueryString({ region: Region.Asia, dpr: 1001 }), TypeError); - assertThrows(() => optionsToQueryString({ region: Region.Asia, dpr: 0.5 }), TypeError); + assert( + optionsToQueryString({ region: Region.Asia, dpr: 1.5 }), + 'region=asia&dpr=1.500000', + ); + assert( + optionsToQueryString({ region: Region.Asia, dpr: 10 }), + 'region=asia&dpr=10.000000', + ); + assertThrows( + () => optionsToQueryString({ region: Region.Asia, dpr: '1001' }), + TypeError, + ); + assertThrows( + () => optionsToQueryString({ region: Region.Asia, dpr: 1001 }), + TypeError, + ); + assertThrows( + () => optionsToQueryString({ region: Region.Asia, dpr: 0.5 }), + TypeError, + ); }); routes.set('/image-optimizer/options/frame', () => { - assert(optionsToQueryString({ region: Region.Asia, frame: 1 }), 'region=asia&frame=1'); - assertThrows(() => optionsToQueryString({ region: Region.Asia, frame: 2 }), TypeError); + assert( + optionsToQueryString({ region: Region.Asia, frame: 1 }), + 'region=asia&frame=1', + ); + assertThrows( + () => optionsToQueryString({ region: Region.Asia, frame: 2 }), + TypeError, + ); }); routes.set('/image-optimizer/options/height', () => { - assert(optionsToQueryString({ region: Region.Asia, height: 1000 }), 'region=asia&height=1000'); - assert(optionsToQueryString({ region: Region.Asia, height: '10%' }), 'region=asia&height=10.000000p'); - assertThrows(() => optionsToQueryString({ region: Region.Asia, height: '1001' }), TypeError); - assertThrows(() => optionsToQueryString({ region: Region.Asia, height: 100.5 }), TypeError); + assert( + optionsToQueryString({ region: Region.Asia, height: 1000 }), + 'region=asia&height=1000', + ); + assert( + optionsToQueryString({ region: Region.Asia, height: '10%' }), + 'region=asia&height=10.000000p', + ); + assertThrows( + () => optionsToQueryString({ region: Region.Asia, height: '1001' }), + TypeError, + ); + assertThrows( + () => optionsToQueryString({ region: Region.Asia, height: 100.5 }), + TypeError, + ); }); routes.set('/image-optimizer/options/level', () => { - assert(optionsToQueryString({ region: Region.Asia, level: '1.1' }), 'region=asia&level=1.1'); - assert(optionsToQueryString({ region: Region.Asia, level: '5.1' }), 'region=asia&level=5.1'); - assertThrows(() => optionsToQueryString({ region: Region.Asia, level: 5.1 }), TypeError); - assertThrows(() => optionsToQueryString({ region: Region.Asia, level: '7.1' }), TypeError); + assert( + optionsToQueryString({ region: Region.Asia, level: '1.1' }), + 'region=asia&level=1.1', + ); + assert( + optionsToQueryString({ region: Region.Asia, level: '5.1' }), + 'region=asia&level=5.1', + ); + assertThrows( + () => optionsToQueryString({ region: Region.Asia, level: 5.1 }), + TypeError, + ); + assertThrows( + () => optionsToQueryString({ region: Region.Asia, level: '7.1' }), + TypeError, + ); }); routes.set('/image-optimizer/options/pad', () => { - assert(optionsToQueryString({ + assert( + optionsToQueryString({ + region: Region.Asia, + pad: { + top: 10, + bottom: 20, + left: '10%', + right: '20%', + }, + }), + 'region=asia&pad=10,20.000000p,20,10.000000p', + ); + assertThrows( + () => + optionsToQueryString({ region: Region.Asia, pad: { - top: 10, - bottom: 20, - left: '10%', - right: '20%' - } - }), 'region=asia&pad=10,20.000000p,20,10.000000p'); - assertThrows(() => optionsToQueryString({ + top: 10, + bottom: 20, + left: '10', + right: '20%', + }, + }), + TypeError, + ); + assertThrows( + () => + optionsToQueryString({ region: Region.Asia, pad: { - top: 10, - bottom: 20, - left: '10', - right: '20%' - } - }), TypeError); - assertThrows(() => optionsToQueryString({ - region: Region.Asia, - pad: { - top: 10, - left: '10', - right: '20%' - } - }), TypeError); + top: 10, + left: '10', + right: '20%', + }, + }), + TypeError, + ); }); routes.set('/image-optimizer/options/quality', () => { - assert(optionsToQueryString({ region: Region.Asia, quality: 1 }), 'region=asia&quality=1'); - assert(optionsToQueryString({ region: Region.Asia, quality: 100 }), 'region=asia&quality=100'); - assertThrows(() => optionsToQueryString({ region: Region.Asia, quality: 1001 }), TypeError); - assertThrows(() => optionsToQueryString({ region: Region.Asia, quality: 1.5 }), TypeError); - assertThrows(() => optionsToQueryString({ region: Region.Asia, quality: 0.4 }), TypeError); + assert( + optionsToQueryString({ region: Region.Asia, quality: 1 }), + 'region=asia&quality=1', + ); + assert( + optionsToQueryString({ region: Region.Asia, quality: 100 }), + 'region=asia&quality=100', + ); + assertThrows( + () => optionsToQueryString({ region: Region.Asia, quality: 1001 }), + TypeError, + ); + assertThrows( + () => optionsToQueryString({ region: Region.Asia, quality: 1.5 }), + TypeError, + ); + assertThrows( + () => optionsToQueryString({ region: Region.Asia, quality: 0.4 }), + TypeError, + ); }); routes.set('/image-optimizer/options/saturation', () => { - assert(optionsToQueryString({ region: Region.Asia, saturation: 1 }), 'region=asia&saturation=1.000000'); - assert(optionsToQueryString({ region: Region.Asia, saturation: 100 }), 'region=asia&saturation=100.000000'); - assert(optionsToQueryString({ region: Region.Asia, saturation: -100 }), 'region=asia&saturation=-100.000000'); - assertThrows(() => optionsToQueryString({ region: Region.Asia, saturation: 1001 }), TypeError); - assertThrows(() => optionsToQueryString({ region: Region.Asia, saturation: -101 }), TypeError); + assert( + optionsToQueryString({ region: Region.Asia, saturation: 1 }), + 'region=asia&saturation=1.000000', + ); + assert( + optionsToQueryString({ region: Region.Asia, saturation: 100 }), + 'region=asia&saturation=100.000000', + ); + assert( + optionsToQueryString({ region: Region.Asia, saturation: -100 }), + 'region=asia&saturation=-100.000000', + ); + assertThrows( + () => optionsToQueryString({ region: Region.Asia, saturation: 1001 }), + TypeError, + ); + assertThrows( + () => optionsToQueryString({ region: Region.Asia, saturation: -101 }), + TypeError, + ); }); routes.set('/image-optimizer/options/sharpen', () => { - assert(optionsToQueryString({ - region: Region.Asia, - sharpen: { - amount: 10, - radius: 10, - threshold: 1, - } - }), 'region=asia&sharpen=a10.000000,r10.000000,t1'); - assert(optionsToQueryString({ - region: Region.Asia, - sharpen: { - amount: 0.1, - radius: 0.5, - threshold: 255, - } - }), 'region=asia&sharpen=a0.100000,r0.500000,t255'); - assertThrows(() => { - optionsToQueryString({ - region: Region.Asia, - sharpen: { - amount: 0.1, - radius: 0.5, - threshold: 256, - } - }) - }, TypeError); - assertThrows(() => { - optionsToQueryString({ - region: Region.Asia, - sharpen: { - amount: 0.1, - radius: 0.4, - threshold: 255, - } - }) - }, TypeError); - assertThrows(() => { - optionsToQueryString({ - region: Region.Asia, - sharpen: { - amount: -1, - radius: 0.5, - threshold: 255, - } - }) - }, TypeError); - assertThrows(() => { - optionsToQueryString({ - region: Region.Asia, - sharpen: { - amount: 1, - radius: 1, - } - }) - }, TypeError); + assert( + optionsToQueryString({ + region: Region.Asia, + sharpen: { + amount: 10, + radius: 10, + threshold: 1, + }, + }), + 'region=asia&sharpen=a10.000000,r10.000000,t1', + ); + assert( + optionsToQueryString({ + region: Region.Asia, + sharpen: { + amount: 0.1, + radius: 0.5, + threshold: 255, + }, + }), + 'region=asia&sharpen=a0.100000,r0.500000,t255', + ); + assertThrows(() => { + optionsToQueryString({ + region: Region.Asia, + sharpen: { + amount: 0.1, + radius: 0.5, + threshold: 256, + }, + }); + }, TypeError); + assertThrows(() => { + optionsToQueryString({ + region: Region.Asia, + sharpen: { + amount: 0.1, + radius: 0.4, + threshold: 255, + }, + }); + }, TypeError); + assertThrows(() => { + optionsToQueryString({ + region: Region.Asia, + sharpen: { + amount: -1, + radius: 0.5, + threshold: 255, + }, + }); + }, TypeError); + assertThrows(() => { + optionsToQueryString({ + region: Region.Asia, + sharpen: { + amount: 1, + radius: 1, + }, + }); + }, TypeError); }); routes.set('/image-optimizer/options/trim', () => { - assert(optionsToQueryString({ + assert( + optionsToQueryString({ + region: Region.Asia, + trim: { + top: 10, + bottom: 20, + left: '10%', + right: '20%', + }, + }), + 'region=asia&trim=10,20.000000p,20,10.000000p', + ); + assertThrows( + () => + optionsToQueryString({ region: Region.Asia, trim: { - top: 10, - bottom: 20, - left: '10%', - right: '20%' - } - }), 'region=asia&trim=10,20.000000p,20,10.000000p'); - assertThrows(() => optionsToQueryString({ + top: 10, + bottom: 20, + left: '10', + right: '20%', + }, + }), + TypeError, + ); + assertThrows( + () => + optionsToQueryString({ region: Region.Asia, trim: { - top: 10, - bottom: 20, - left: '10', - right: '20%' - } - }), TypeError); - assertThrows(() => optionsToQueryString({ - region: Region.Asia, - trim: { - top: 10, - left: '10', - right: '20%' - } - }), TypeError); + top: 10, + left: '10', + right: '20%', + }, + }), + TypeError, + ); }); routes.set('/image-optimizer/options/viewbox', () => { - assert(optionsToQueryString({ region: Region.Asia, viewbox: 1 }), 'region=asia&viewbox=1'); - assertThrows(() => optionsToQueryString({ region: Region.Asia, viewbox: 2 }), TypeError); + assert( + optionsToQueryString({ region: Region.Asia, viewbox: 1 }), + 'region=asia&viewbox=1', + ); + assertThrows( + () => optionsToQueryString({ region: Region.Asia, viewbox: 2 }), + TypeError, + ); }); routes.set('/image-optimizer/options/width', () => { - assert(optionsToQueryString({ region: Region.Asia, width: 1000 }), 'region=asia&width=1000'); - assert(optionsToQueryString({ region: Region.Asia, width: '10%' }), 'region=asia&width=10.000000p'); - assertThrows(() => optionsToQueryString({ region: Region.Asia, width: '1001' }), TypeError); - assertThrows(() => optionsToQueryString({ region: Region.Asia, width: 100.5 }), TypeError); -}); \ No newline at end of file + assert( + optionsToQueryString({ region: Region.Asia, width: 1000 }), + 'region=asia&width=1000', + ); + assert( + optionsToQueryString({ region: Region.Asia, width: '10%' }), + 'region=asia&width=10.000000p', + ); + assertThrows( + () => optionsToQueryString({ region: Region.Asia, width: '1001' }), + TypeError, + ); + assertThrows( + () => optionsToQueryString({ region: Region.Asia, width: 100.5 }), + TypeError, + ); +}); diff --git a/integration-tests/js-compute/fixtures/app/src/index.js b/integration-tests/js-compute/fixtures/app/src/index.js index a8a23d8569..ee02fe1e58 100644 --- a/integration-tests/js-compute/fixtures/app/src/index.js +++ b/integration-tests/js-compute/fixtures/app/src/index.js @@ -95,12 +95,12 @@ async function app(event) { try { return (res = new Response( `The routeHandler for ${path} threw a [${error.constructor?.name ?? error.name}] error: ${error.message || error}` + - '\n' + - error.stack + - (fastly.debugMessages - ? '\n[DEBUG BUILD MESSAGES]:\n\n - ' + - fastly.debugMessages.join('\n - ') - : ''), + '\n' + + error.stack + + (fastly.debugMessages + ? '\n[DEBUG BUILD MESSAGES]:\n\n - ' + + fastly.debugMessages.join('\n - ') + : ''), { status: 500 }, )); } catch (errRes) { diff --git a/integration-tests/js-compute/fixtures/app/tests.json b/integration-tests/js-compute/fixtures/app/tests.json index 0c34410271..c44f76aa48 100644 --- a/integration-tests/js-compute/fixtures/app/tests.json +++ b/integration-tests/js-compute/fixtures/app/tests.json @@ -3,22 +3,14 @@ "downstream_response": { "status": 200, "headers": [ - [ - "FooName", - "FooValue" - ], - [ - "BarName", - "BarValue" - ] + ["FooName", "FooValue"], + ["BarName", "BarValue"] ], "body": "pong" } }, "GET /btoa": { - "environments": [ - "viceroy" - ], + "environments": ["viceroy"], "downstream_response": { "status": 200, "body": "ok" @@ -27,18 +19,13 @@ "GET /byob": { "downstream_response": { "status": 200, - "body": [ - "hey3" - ] + "body": ["hey3"] } }, "GET /byte-repeater": { "downstream_response": { "status": 200, - "body": [ - "11223344", - "5566778899001122\n\n" - ] + "body": ["11223344", "5566778899001122\n\n"] } }, "GET /cache-override/constructor/called-as-regular-function": {}, @@ -122,508 +109,340 @@ "GET /simple-cache/interface": {}, "GET /simple-store/constructor/called-as-regular-function": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/constructor/throws": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/purge/called-as-constructor": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/purge/key-parameter-calls-7.1.17-ToString": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/purge/key-parameter-not-supplied": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/purge/key-parameter-empty-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/purge/key-parameter-8135-character-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/purge/key-parameter-8136-character-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/purge/options-parameter": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/purge/returns-undefined": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/called-as-constructor": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/key-parameter-calls-7.1.17-ToString": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/tll-parameter-7.1.4-ToNumber": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/no-parameters-supplied": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/key-parameter-empty-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/key-parameter-8135-character-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/key-parameter-8136-character-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/ttl-parameter-negative-number": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/ttl-parameter-NaN": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/ttl-parameter-Infinity": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-as-undefined": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-readablestream-missing-length-parameter": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-readablestream-negative-length-parameter": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-readablestream-nan-length-parameter": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-readablestream-negative-infinity-length-parameter": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-readablestream-positive-infinity-length-parameter": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/length-parameter-7.1.4-ToNumber": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-readablestream-empty": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-readablestream-locked": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-readablestream": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-URLSearchParams": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-strings": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-calls-7.1.17-ToString": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-buffer": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-arraybuffer": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-typed-arrays": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-dataview": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/returns-undefined": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/get/called-as-constructor": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/get/key-parameter-calls-7.1.17-ToString": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/get/key-parameter-not-supplied": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/get/key-parameter-empty-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/get/key-parameter-8135-character-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/get/key-parameter-8136-character-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/get/key-does-not-exist-returns-null": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/get/key-exists": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache-entry/interface": {}, "GET /simple-cache-entry/text/valid": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache-entry/json/valid": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache-entry/json/invalid": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache-entry/arrayBuffer/valid": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache-entry/body": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache-entry/bodyUsed": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache-entry/readablestream": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/rejection-rejects-outer": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/called-as-constructor": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/no-parameters-supplied": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/key-parameter-calls-7.1.17-ToString": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/key-parameter-empty-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/key-parameter-8135-character-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/key-parameter-8136-character-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/ttl-field-7.1.4-ToNumber": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/ttl-field-negative-number": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/ttl-field-NaN": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/ttl-field-Infinity": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-as-undefined": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-readablestream-missing-length-field": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-readablestream-negative-length-field": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-readablestream-nan-length-field": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-readablestream-negative-infinity-length-field": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-readablestream-positive-infinity-length-field": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/length-field-7.1.4-ToNumber": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-readablestream-empty": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-readablestream-locked": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-readablestream": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-URLSearchParams": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-strings": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-calls-7.1.17-ToString": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-buffer": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-typed-arrays": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-dataview": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/returns-SimpleCacheEntry": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/executes-the-set-method-when-key-not-in-cache": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/does-not-execute-the-set-method-when-key-is-in-cache": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/does-not-freeze-when-called-after-a-get": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /client/tlsJA3MD5": {}, "GET /client/tlsClientHello": {}, @@ -1252,74 +1071,48 @@ "flake": true }, "GET /env": { - "environments": [ - "viceroy" - ] + "environments": ["viceroy"] }, "GET /createFanoutHandoff": {}, "GET /createWebsocketHandoff": {}, "GET /fastly/now": {}, "GET /fastly/version": {}, "GET /fastly/getgeolocationforipaddress/interface": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /fastly/getgeolocationforipaddress/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /fastly/getgeolocationforipaddress/parameter-calls-7.1.17-ToString": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /fastly/getgeolocationforipaddress/parameter-not-supplied": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /fastly/getgeolocationforipaddress/parameter-empty-string": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /fastly/getgeolocationforipaddress/bad-ip": {}, "GET /fastly/getgeolocationforipaddress/parameter-ipv4-string": {}, "GET /fastly/getgeolocationforipaddress/parameter-compressed-ipv6-string": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /fastly/getgeolocationforipaddress/parameter-shortened-ipv6-string": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /fastly/getgeolocationforipaddress/parameter-expanded-ipv6-string": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /fastly/getgeolocationforipaddress/called-unbound": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /fastly:geolocation": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /includeBytes": {}, "GET /logger": { - "environments": [ - "viceroy" - ], - "logs": [ - "ComputeLog :: Hello!" - ] + "environments": ["viceroy"], + "logs": ["ComputeLog :: Hello!"] }, "GET /missing-backend": {}, "GET /multiple-set-cookie/response-init": { @@ -1330,46 +1123,16 @@ "Set-Cookie", "test=1; expires=Tue, 06-Dec-2022 12:34:56 GMT; Max-Age=60; Path=/; HttpOnly; Secure, test_id=1; Max-Age=60; Path=/; expires=Tue, 06-Dec-2022 12:34:56 GMT, test_id=1; Max-Age=60; Path=/; expires=Tue, 06-Dec-2022 12:34:56 GMT" ], - [ - "Set-Cookie", - "test2=2" - ], - [ - "Set-Cookie", - "test3=3" - ], - [ - "Set-Cookie", - "test4=4" - ], - [ - "Set-Cookie", - "test5=5" - ], - [ - "Set-Cookie", - "test6=6" - ], - [ - "Set-Cookie", - "test7=7" - ], - [ - "Set-Cookie", - "test8=8" - ], - [ - "Set-Cookie", - "test9=9" - ], - [ - "Set-Cookie", - "test10=10" - ], - [ - "Set-Cookie", - "test11=11" - ] + ["Set-Cookie", "test2=2"], + ["Set-Cookie", "test3=3"], + ["Set-Cookie", "test4=4"], + ["Set-Cookie", "test5=5"], + ["Set-Cookie", "test6=6"], + ["Set-Cookie", "test7=7"], + ["Set-Cookie", "test8=8"], + ["Set-Cookie", "test9=9"], + ["Set-Cookie", "test10=10"], + ["Set-Cookie", "test11=11"] ] } }, @@ -1381,46 +1144,16 @@ "Set-Cookie", "test=1; expires=Tue, 06-Dec-2022 12:34:56 GMT; Max-Age=60; Path=/; HttpOnly; Secure, test_id=1; Max-Age=60; Path=/; expires=Tue, 06-Dec-2022 12:34:56 GMT, test_id=1; Max-Age=60; Path=/; expires=Tue, 06-Dec-2022 12:34:56 GMT" ], - [ - "Set-Cookie", - "test2=2" - ], - [ - "Set-Cookie", - "test3=3" - ], - [ - "Set-Cookie", - "test4=4" - ], - [ - "Set-Cookie", - "test5=5" - ], - [ - "Set-Cookie", - "test6=6" - ], - [ - "Set-Cookie", - "test7=7" - ], - [ - "Set-Cookie", - "test8=8" - ], - [ - "Set-Cookie", - "test9=9" - ], - [ - "Set-Cookie", - "test10=10" - ], - [ - "Set-Cookie", - "test11=11" - ] + ["Set-Cookie", "test2=2"], + ["Set-Cookie", "test3=3"], + ["Set-Cookie", "test4=4"], + ["Set-Cookie", "test5=5"], + ["Set-Cookie", "test6=6"], + ["Set-Cookie", "test7=7"], + ["Set-Cookie", "test8=8"], + ["Set-Cookie", "test9=9"], + ["Set-Cookie", "test10=10"], + ["Set-Cookie", "test11=11"] ] } }, @@ -1428,84 +1161,43 @@ "downstream_response": { "status": 302, "headers": [ - [ - "Set-Cookie", - "1=1; Path=/" - ], - [ - "Set-Cookie", - "2=2; Path=/" - ], - [ - "Set-Cookie", - "3=3; Path=/" - ], - [ - "Set-Cookie", - "4=4; Path=/" - ], - [ - "Set-Cookie", - "5=5; Path=/" - ], - [ - "Set-Cookie", - "6=6; Path=/" - ], - [ - "Set-Cookie", - "7=7; Path=/" - ], - [ - "Set-Cookie", - "8=8; Path=/" - ], - [ - "Set-Cookie", - "9=9; Path=/" - ], - [ - "Set-Cookie", - "10=10; Path=/" - ], - [ - "Set-Cookie", - "11=11; Path=/" - ] + ["Set-Cookie", "1=1; Path=/"], + ["Set-Cookie", "2=2; Path=/"], + ["Set-Cookie", "3=3; Path=/"], + ["Set-Cookie", "4=4; Path=/"], + ["Set-Cookie", "5=5; Path=/"], + ["Set-Cookie", "6=6; Path=/"], + ["Set-Cookie", "7=7; Path=/"], + ["Set-Cookie", "8=8; Path=/"], + ["Set-Cookie", "9=9; Path=/"], + ["Set-Cookie", "10=10; Path=/"], + ["Set-Cookie", "11=11; Path=/"] ] } }, "GET /Performance/interface": { - "environments": [ - "viceroy" - ], + "environments": ["viceroy"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /globalThis.performance": { - "environments": [ - "viceroy" - ], + "environments": ["viceroy"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /globalThis.performance/now": { - "environments": [ - "viceroy" - ], + "environments": ["viceroy"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /globalThis.performance/timeOrigin": { - "environments": [ - "viceroy" - ], + "environments": ["viceroy"], "downstream_response": { "status": 200, "body": "ok" @@ -1531,9 +1223,7 @@ "headers": { "content-type": "text/html" }, - "body": [ - "

blob

" - ] + "body": ["

blob

"] } }, "GET /response/stall": { @@ -1549,120 +1239,14 @@ "downstream_response": { "status": 200, "body_prefix": [ - 123, - 10, - 32, - 32, - 34, - 97, - 114, - 103, - 115, - 34, - 58, - 32, - 123, - 125, - 44, - 32, - 10, - 32, - 32, - 34, - 100, - 97, - 116, - 97, - 34, - 58, - 32, - 34, - 100, - 97, - 116, - 97, - 58, - 97, - 112, - 112, - 108, - 105, - 99, - 97, - 116, - 105, - 111, - 110, - 47, - 111, - 99, - 116, - 101, - 116, - 45, - 115, - 116, - 114, - 101, - 97, - 109, - 59, - 98, - 97, - 115, - 101, - 54, - 52, - 44, - 85, - 107, - 108, - 71, - 82, - 107, - 65, - 112, - 65, - 65, - 66, - 88, - 82, - 85, - 74, - 81, - 86, - 108, - 65, - 52, - 87, - 65, - 111, - 65, - 65, - 65, - 65, - 69, - 65, - 65, - 65, - 65, - 69, - 81 + 123, 10, 32, 32, 34, 97, 114, 103, 115, 34, 58, 32, 123, 125, 44, 32, + 10, 32, 32, 34, 100, 97, 116, 97, 34, 58, 32, 34, 100, 97, 116, 97, 58, + 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 111, 99, 116, + 101, 116, 45, 115, 116, 114, 101, 97, 109, 59, 98, 97, 115, 101, 54, 52, + 44, 85, 107, 108, 71, 82, 107, 65, 112, 65, 65, 66, 88, 82, 85, 74, 81, + 86, 108, 65, 52, 87, 65, 111, 65, 65, 65, 65, 69, 65, 65, 65, 65, 69, 81 ], - "body_suffix": [ - 123, - 10, - 32, - 32, - 34, - 97, - 114, - 103, - 115, - 34, - 58, - 32 - ] + "body_suffix": [123, 10, 32, 32, 34, 97, 114, 103, 115, 34, 58, 32] } }, "GET /response/ip-port-undefined": {}, @@ -1693,9 +1277,7 @@ "GET /setTimeout/200-ms": { "downstream_response": { "status": 200, - "body": [ - "START\nEND\n" - ] + "body": ["START\nEND\n"] } }, "GET /setTimeout/fetch-timeout": { @@ -1738,10 +1320,7 @@ "downstream_request": { "method": "POST", "pathname": "/tee", - "headers": [ - "Content-Type", - "application/json" - ], + "headers": ["Content-Type", "application/json"], "body": "hello world!" }, "downstream_response": { @@ -1753,10 +1332,7 @@ "downstream_request": { "method": "GET", "pathname": "/tee/error", - "headers": [ - "Content-Type", - "application/json" - ] + "headers": ["Content-Type", "application/json"] }, "downstream_response": { "status": 200, @@ -1764,54 +1340,42 @@ } }, "GET /override-content-length/request/init/object-literal/true": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /override-content-length/request/init/object-literal/false": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /override-content-length/request/clone/true": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /override-content-length/request/clone/false": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /override-content-length/fetch/init/object-literal/true": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /override-content-length/fetch/init/object-literal/false": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "ok" @@ -1819,20 +1383,14 @@ }, "/override-content-length/response/clone/true": { "skip": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "/override-content-length/response/clone/false": { "skip": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /override-content-length/response/init/object-literal/true": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "meow", @@ -1842,18 +1400,14 @@ } }, "GET /override-content-length/response/init/object-literal/false": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "meow" } }, "GET /override-content-length/response/init/response-instance/true": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "meow", @@ -1863,23 +1417,17 @@ } }, "GET /override-content-length/response/init/response-instance/false": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "meow" } }, "GET /override-content-length/response/method/false": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /override-content-length/response/method/true": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "headers": { @@ -1890,14 +1438,7 @@ "GET /headers/getsetcookie": { "downstream_response": { "status": 200, - "headers": [ - [ - "name1=value1" - ], - [ - "name2=value2" - ] - ] + "headers": [["name1=value1"], ["name2=value2"]] } }, "GET /headers/construct": { @@ -1912,12 +1453,7 @@ "flake": true, "downstream_response": { "status": 200, - "headers": [ - [ - "cuStom", - "test" - ] - ] + "headers": [["cuStom", "test"]] } }, "GET /headers/from-response/delete-invalid": { @@ -1927,1411 +1463,940 @@ "flake": true, "downstream_response": { "status": 200, - "headers": [ - [ - "cuStom", - "test" - ] - ] + "headers": [["cuStom", "test"]] } }, "GET /FastlyBody/interface": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/constructor/called-as-regular-function": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/constructor/called-as-constructor": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/called-as-constructor": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-not-supplied": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-wrong-type": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-readablestream-guest-backed": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-readablestream-host-backed": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-URLSearchParams": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-strings": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-calls-7.1.17-ToString": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-buffer": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-arraybuffer": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-typed-arrays": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-dataview": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/called-as-constructor": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-not-supplied": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-wrong-type": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-readablestream-guest-backed": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-readablestream-host-backed": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-readablestream-locked": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-URLSearchParams": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-strings": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-calls-7.1.17-ToString": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-buffer": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-arraybuffer": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-typed-arrays": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-dataview": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/concat/called-as-constructor": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/concat/dest-parameter-not-supplied": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/concat/dest-parameter-wrong-type": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/concat/concat-same-fastlybody-twice": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/concat/happy-path": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/called-as-constructor": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/chunkSize-parameter-not-supplied": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/chunkSize-parameter-wrong-type": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/chunkSize-parameter-negative": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/chunkSize-parameter-infinity": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/chunkSize-parameter-NaN": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/happy-path": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/close/called-as-constructor": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/close/called-once": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/close/called-twice": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/interface": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/constructor/called-as-regular-function": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/constructor/throws": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/called-as-constructor": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-parameter-calls-7.1.17-ToString": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-parameter-not-supplied": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-parameter-empty-string": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-parameter-8135-character-string": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-parameter-8136-character-string": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-does-not-exist-returns-null": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-exists": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/options-parameter-wrong-type": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/options-parameter-none": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/lookup/options-parameter-headers-field-wrong-type": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/lookup/options-parameter-headers-field-undefined": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/lookup/options-parameter-headers-field-valid-sequence": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/lookup/options-parameter-headers-field-valid-record": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/lookup/options-parameter-headers-field-valid-Headers-instance": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/key-parameter-calls-7.1.17-ToString": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/key-parameter-not-supplied": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/key-parameter-empty-string": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/key-parameter-8135-character-string": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/key-parameter-8136-character-string": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-wrong-type": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-headers-field-wrong-type": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-headers-field-undefined": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-headers-field-valid-sequence": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-headers-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-headers-field-valid-Headers-instance": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-maxAge-field-valid-record": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-maxAge-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-maxAge-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-maxAge-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-maxAge-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-initialAge-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-initialAge-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-initialAge-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-initialAge-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-initialAge-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-staleWhileRevalidate-field-valid-record": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-staleWhileRevalidate-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-staleWhileRevalidate-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-staleWhileRevalidate-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-staleWhileRevalidate-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-length-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-length-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-length-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-length-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-length-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-sensitive-field": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-vary-field": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-userMetadata-field/arraybuffer/empty": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-userMetadata-field/arraybuffer/not-empty": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-userMetadata-field/URLSearchParams": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-userMetadata-field/string": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/key-parameter-calls-7.1.17-ToString": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/key-parameter-not-supplied": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/key-parameter-empty-string": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/key-parameter-8135-character-string": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/key-parameter-8136-character-string": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/key-does-not-exist": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/key-exists": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/options-parameter-wrong-type": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/options-parameter-headers-field-wrong-type": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/options-parameter-headers-field-undefined": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/options-parameter-headers-field-valid-sequence": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/options-parameter-headers-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/options-parameter-headers-field-valid-Headers-instance": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/interface": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/constructor/called-as-regular-function": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/constructor/throws": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/close/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/close/called-unbound": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/close/called-on-instance": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/state/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/state/called-unbound": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/state/called-on-instance": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/userMetadata/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/userMetadata/called-unbound": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/userMetadata/called-on-instance": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/userMetadata/basic": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/called-unbound": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/called-on-instance": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/options-start-negative": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/options-start-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/options-start-Infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/options-start-valid": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/options-start-longer-than-body": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/options-end-negative": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/options-end-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/options-end-Infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/options-end-valid": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/options-end-zero": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/length/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/length/called-unbound": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/length/called-on-instance": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/maxAge/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/maxAge/called-unbound": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/maxAge/called-on-instance": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/staleWhileRevalidate/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/staleWhileRevalidate/called-unbound": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/staleWhileRevalidate/called-on-instance": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/age/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/age/called-unbound": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/age/called-on-instance": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/hits/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/hits/called-unbound": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/hits/called-on-instance": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/interface": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/entry-parameter-not-supplied": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-maxAge-field-valid-record": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-maxAge-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-maxAge-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-maxAge-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-maxAge-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-initialAge-field-valid-record": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-initialAge-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-initialAge-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-initialAge-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-initialAge-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-staleWhileRevalidate-field-valid-record": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-staleWhileRevalidate-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-staleWhileRevalidate-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-staleWhileRevalidate-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-staleWhileRevalidate-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-length-field-valid-record": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-length-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-length-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-length-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-length-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-sensitive-field": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-vary-field": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/entry-parameter-not-supplied": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-maxAge-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-maxAge-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-maxAge-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-maxAge-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-maxAge-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-initialAge-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-initialAge-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-initialAge-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-initialAge-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-initialAge-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-staleWhileRevalidate-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-staleWhileRevalidate-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-staleWhileRevalidate-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-staleWhileRevalidate-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-staleWhileRevalidate-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-length-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-length-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-length-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-length-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-length-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-sensitive-field": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/write-to-writer-and-read-from-reader": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-vary-field": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/entry-parameter-not-supplied": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-maxAge-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-maxAge-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-maxAge-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-maxAge-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-maxAge-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-initialAge-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-initialAge-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-initialAge-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-initialAge-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-initialAge-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-staleWhileRevalidate-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-staleWhileRevalidate-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-staleWhileRevalidate-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-staleWhileRevalidate-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-staleWhileRevalidate-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-length-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-length-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-length-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-length-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-length-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/write-to-writer-and-read-from-reader": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-vary-field": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-userMetadata-field": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/cancel/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/cancel/called-once": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/cancel/makes-entry-cancelled": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/cancel/called-twice-throws": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /rate-counter/interface": { "downstream_response": { @@ -3784,36 +2849,28 @@ } }, "GET /device/lookup/useragent-does-not-exist-returns-null": { - "environments": [ - "viceroy" - ], + "environments": ["viceroy"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /device/lookup/useragent-exists-all-fields-identified": { - "environments": [ - "viceroy" - ], + "environments": ["viceroy"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /device/lookup/useragent-exists-all-fields-identified-tojson": { - "environments": [ - "viceroy" - ], + "environments": ["viceroy"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /device/lookup/useragent-exists-some-fields-identified": { - "environments": [ - "viceroy" - ], + "environments": ["viceroy"], "downstream_response": { "status": 200, "body": "ok" @@ -3827,18 +2884,14 @@ }, "GET /core-cache/transaction-lookup-transaction-insert-vary-works": { "flake": true, - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /core-cache/lookup-insert-vary-works": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "ok" @@ -3848,252 +2901,147 @@ "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "GET" - ] - ] + "headers": [["result", "GET"]] } }, "get /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "GET" - ] - ] + "headers": [["result", "GET"]] } }, "GeT /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "GET" - ] - ] + "headers": [["result", "GET"]] } }, "HEAD /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "HEAD" - ] - ] + "headers": [["result", "HEAD"]] } }, "head /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "HEAD" - ] - ] + "headers": [["result", "HEAD"]] } }, "HeAd /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "HEAD" - ] - ] + "headers": [["result", "HEAD"]] } }, "OPTIONS /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "OPTIONS" - ] - ] + "headers": [["result", "OPTIONS"]] } }, "OPTioNS /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "OPTIONS" - ] - ] + "headers": [["result", "OPTIONS"]] } }, "options /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "OPTIONS" - ] - ] + "headers": [["result", "OPTIONS"]] } }, "POST /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "POST" - ] - ] + "headers": [["result", "POST"]] } }, "post /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "POST" - ] - ] + "headers": [["result", "POST"]] } }, "PosT /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "POST" - ] - ] + "headers": [["result", "POST"]] } }, "PUT /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "PUT" - ] - ] + "headers": [["result", "PUT"]] } }, "put /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "PUT" - ] - ] + "headers": [["result", "PUT"]] } }, "Put /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "PUT" - ] - ] + "headers": [["result", "PUT"]] } }, "DELETE /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "DELETE" - ] - ] + "headers": [["result", "DELETE"]] } }, "DeLeTe /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "DELETE" - ] - ] + "headers": [["result", "DELETE"]] } }, "delete /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "DELETE" - ] - ] + "headers": [["result", "DELETE"]] } }, "HELLO /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "HELLO" - ] - ] + "headers": [["result", "HELLO"]] } }, "hello /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "hello" - ] - ] + "headers": [["result", "hello"]] } }, "heLLo /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "heLLo" - ] - ] + "headers": [["result", "heLLo"]] } }, "!*+-.^_`|~1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ /request/method/host": { @@ -4116,9 +3064,7 @@ }, "GET /compute/get-vcpu-ms": { "flake": true, - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 @@ -4126,14 +3072,10 @@ }, "GET /compute/purge-surrogate-key-invalid": {}, "GET /compute/purge-surrogate-key-hard": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /compute/purge-surrogate-key-soft": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /html-rewriter/set-attribute": {}, "GET /html-rewriter/get-attribute": {}, @@ -4178,4 +3120,4 @@ "GET /image-optimizer/options/sharpen": {}, "GET /image-optimizer/options/trim": {}, "GET /image-optimizer/options/viewbox": {} -} \ No newline at end of file +} diff --git a/types/globals.d.ts b/types/globals.d.ts index 6a26e8af48..7a55cd56b7 100644 --- a/types/globals.d.ts +++ b/types/globals.d.ts @@ -136,24 +136,24 @@ declare interface BackendConfiguration { * Setting to boolean true enables keepalive with the default options. */ tcpKeepalive?: - | boolean - | { - /** - * Configure how long to wait after the last sent data over the TCP connection before - * starting to send TCP keepalive probes. - */ - timeSecs?: number; + | boolean + | { + /** + * Configure how long to wait after the last sent data over the TCP connection before + * starting to send TCP keepalive probes. + */ + timeSecs?: number; - /** - * Configure how long to wait between each TCP keepalive probe sent to the backend to determine if it is still active. - */ - intervalSecs?: number; + /** + * Configure how long to wait between each TCP keepalive probe sent to the backend to determine if it is still active. + */ + intervalSecs?: number; - /** - * Number of probes to send to the backend before it is considered dead. - */ - probes?: number; - }; + /** + * Number of probes to send to the backend before it is considered dead. + */ + probes?: number; + }; } /** @@ -413,7 +413,7 @@ declare interface CacheOverride extends CacheOverrideInit { */ declare var CacheOverride: { prototype: CacheOverride; - new(mode: CacheOverrideMode, init?: CacheOverrideInit): CacheOverride; + new (mode: CacheOverrideMode, init?: CacheOverrideInit): CacheOverride; }; /** @@ -1182,7 +1182,7 @@ interface Blob { declare var Blob: { prototype: Blob; - new(blobParts?: BlobPart[], options?: BlobPropertyBag): Blob; + new (blobParts?: BlobPart[], options?: BlobPropertyBag): Blob; }; /** @@ -1201,7 +1201,7 @@ interface File extends Blob { declare var File: { prototype: File; - new(fileBits: BlobPart[], fileName: string, options?: FilePropertyBag): File; + new (fileBits: BlobPart[], fileName: string, options?: FilePropertyBag): File; }; /** @@ -1238,7 +1238,7 @@ interface FormData { declare var FormData: { prototype: FormData; - new( + new ( form?: any /*form?: HTMLFormElement, submitter?: HTMLElement | null*/, ): FormData; }; @@ -1322,9 +1322,9 @@ declare interface RequestInit { /** The Fastly configured backend name or instance the request should be sent to. */ backend?: string | import('fastly:backend').Backend; cacheOverride?: - | import('fastly:cache-override').CacheOverride - | import('fastly:cache-override').ICacheOverride - | Exclude; + | import('fastly:cache-override').CacheOverride + | import('fastly:cache-override').ICacheOverride + | Exclude; cacheKey?: string; fastly?: { decompressGzip?: boolean; @@ -1399,7 +1399,7 @@ interface Request extends Body { */ declare var Request: { prototype: Request; - new(input: RequestInfo | URL, init?: RequestInit): Request; + new (input: RequestInfo | URL, init?: RequestInit): Request; }; /** @@ -1521,7 +1521,7 @@ interface Response extends Body { */ declare var Response: { prototype: Response; - new(body?: BodyInit | null, init?: ResponseInit): Response; + new (body?: BodyInit | null, init?: ResponseInit): Response; // error(): Response; redirect(url: string | URL, status?: number): Response; json(data: any, init?: ResponseInit): Response; @@ -1752,7 +1752,7 @@ interface ReadableStreamDefaultController { */ declare var ReadableStreamDefaultController: { prototype: ReadableStreamDefaultController; - new(): ReadableStreamDefaultController; + new (): ReadableStreamDefaultController; }; /** @@ -1817,7 +1817,7 @@ interface WritableStreamDefaultController { */ declare var WritableStreamDefaultController: { prototype: WritableStreamDefaultController; - new(): WritableStreamDefaultController; + new (): WritableStreamDefaultController; }; /** @@ -1881,7 +1881,7 @@ interface TransformStreamDefaultController { */ declare var TransformStreamDefaultController: { prototype: TransformStreamDefaultController; - new(): TransformStreamDefaultController; + new (): TransformStreamDefaultController; }; /** @@ -1953,7 +1953,7 @@ interface Headers { */ declare var Headers: { prototype: Headers; - new(init?: HeadersInit): Headers; + new (init?: HeadersInit): Headers; }; /** @@ -2107,7 +2107,7 @@ interface WorkerLocation { */ declare var WorkerLocation: { prototype: WorkerLocation; - new(): WorkerLocation; + new (): WorkerLocation; }; /** @@ -2234,7 +2234,7 @@ interface Crypto { */ declare var Crypto: { prototype: Crypto; - new(): Crypto; + new (): Crypto; }; /** @@ -2261,7 +2261,7 @@ interface CryptoKey { declare var CryptoKey: { prototype: CryptoKey; - new(): CryptoKey; + new (): CryptoKey; }; interface KeyAlgorithm { @@ -2421,7 +2421,7 @@ interface Event { declare var Event: { prototype: Event; - new(type: string, eventInitDict?: EventInit): Event; + new (type: string, eventInitDict?: EventInit): Event; readonly NONE: 0; readonly CAPTURING_PHASE: 1; readonly AT_TARGET: 2; @@ -2485,7 +2485,7 @@ interface EventTarget { declare var EventTarget: { prototype: EventTarget; - new(): EventTarget; + new (): EventTarget; }; /** @@ -2506,7 +2506,7 @@ interface Performance extends EventTarget { */ declare var Performance: { prototype: Performance; - new(): Performance; + new (): Performance; }; /** diff --git a/types/image-optimizer.d.ts b/types/image-optimizer.d.ts index b4d39c9226..9090d4d776 100644 --- a/types/image-optimizer.d.ts +++ b/types/image-optimizer.d.ts @@ -1,268 +1,284 @@ declare module 'fastly:image-optimizer' { - /** - * A color, either a 3/6 character hex string or an rgb(a) object. - */ - type Color = string | { + /** + * A color, either a 3/6 character hex string or an rgb(a) object. + */ + type Color = + | string + | { r: number; g: number; b: number; a?: number; - } + }; + /** + * A percentage, expressed as a string such as '100%' + */ + type Percentage = string; + /** + * The size of a region, either expressed as absolute values (either integer pixel values or percentage strings), or as a ratio of integers. + */ + interface Size { + absolute?: { + width: number | Percentage; + height: number | Percentage; + }; + ratio?: { + width: number; + height: number; + }; + } + /** + * The position of a region, with x and y components expressed as either integer pixel values/percentage strings, or offset percentages. + */ + interface Position { + x?: number | Percentage; + offsetX?: number; + y?: number | Percentage; + offsetY?: number; + } + + interface Sides { + top: number | Percentage; + bottom: number | Percentage; + left: number | Percentage; + right: number | Percentage; + } + + var Region: { + UsEast: 'us_east'; + UsCentral: 'us_central'; + UsWest: 'us_west'; + EuCentral: 'eu_central'; + Asia: 'asia'; + Australia: 'australia'; + }; + var Auto: { + AVIF: 'avif'; + WEBP: 'webp'; + }; + var BWAlgorithm: { + Threshold: 'threshold'; + Atkinson: 'atkinson'; + }; + var CropMode: { + Smart: 'smart'; + Safe: 'safe'; + }; + var Disable: { + Upscale: 'upscale'; + }; + var Enable: { + Upscale: 'upscale'; + }; + var Fit: { + Bounds: 'bounds'; + Cover: 'cover'; + Crop: 'crop'; + }; + var Metadata: { + Copyright: 'copyright'; + C2PA: 'c2pa'; + CopyrightAndC2PA: 'copyright,c2pa'; + }; + var Optimize: { + Low: 'low'; + Medium: 'medium'; + High: 'high'; + }; + var Orient: { + Default: '1'; + FlipHorizontal: '2'; + FlipHorizontalAndVertical: '3'; + FlipVertical: '4'; + FlipHorizontalOrientLeft: '5'; + OrientRight: '6'; + FlipHorizontalOrientRight: '7'; + OrientLeft: '8'; + }; + var Profile: { + Baseline: 'baseline'; + Main: 'main'; + High: 'high'; + }; + var ResizeFilter: { + Nearest: 'nearest'; + Bilinear: 'bilinear'; + Linear: 'linear'; + Bicubic: 'bicubic'; + Cubic: 'cubic'; + Lanczos2: 'lanczos2'; + Lanczos3: 'lanczos3'; + Lanczos: 'lanczos'; + }; + + interface ImageOptimizerOptions { /** - * A percentage, expressed as a string such as '100%' + * */ - type Percentage = string; + region: + | 'us_east' + | 'us_central' + | 'us_west' + | 'eu_central' + | 'asia' + | 'australia'; /** - * The size of a region, either expressed as absolute values (either integer pixel values or percentage strings), or as a ratio of integers. + * Enable optimization features automatically. */ - interface Size { - absolute?: { - width: number | Percentage; - height: number | Percentage; - }; - ratio?: { - width: number; - height: number; - } - } + auto?: 'avif' | 'webp'; /** - * The position of a region, with x and y components expressed as either integer pixel values/percentage strings, or offset percentages. + * Set the background color of an image. */ - interface Position { - x?: number | Percentage; - offsetX?: number; - y?: number | Percentage; - offsetY?: number; - } - - interface Sides { - top: number | Percentage; - bottom: number | Percentage; - left: number | Percentage; - right: number | Percentage; - } - - var Region: { - UsEast: 'us_east'; - UsCentral: 'us_central'; - UsWest: 'us_west'; - EuCentral: 'eu_central'; - Asia: 'asia'; - Australia: 'australia'; - } - var Auto: { - AVIF: 'avif'; - WEBP: 'webp'; - } - var BWAlgorithm: { - Threshold: 'threshold'; - Atkinson: 'atkinson'; - } - var CropMode: { - Smart: 'smart'; - Safe: 'safe'; - } - var Disable: { - Upscale: 'upscale'; - } - var Enable: { - Upscale: 'upscale'; - } - var Fit: { - Bounds: "bounds"; - Cover: "cover"; - Crop: "crop"; - } - var Metadata: { - Copyright: "copyright"; - C2PA: "c2pa"; - CopyrightAndC2PA: "copyright,c2pa"; - } - var Optimize: { - Low: "low"; - Medium: "medium"; - High: "high"; - } - var Orient: { - Default: "1"; - FlipHorizontal: "2"; - FlipHorizontalAndVertical: "3"; - FlipVertical: "4"; - FlipHorizontalOrientLeft: "5"; - OrientRight: "6"; - FlipHorizontalOrientRight: "7"; - OrientLeft: "8"; - } - var Profile: { - Baseline: "baseline"; - Main: "main"; - High: "high"; - } - var ResizeFilter: { - Nearest: "nearest"; - Bilinear: "bilinear"; - Linear: "linear"; - Bicubic: "bicubic"; - Cubic: "cubic"; - Lanczos2: "lanczos2"; - Lanczos3: "lanczos3"; - Lanczos: "lanczos"; - } - - interface ImageOptimizerOptions { - /** - * - */ - region: 'us_east' | 'us_central' | 'us_west' | 'eu_central' | 'asia' | 'australia'; - /** - * Enable optimization features automatically. - */ - auto?: 'avif' | 'webp'; - /** - * Set the background color of an image. - */ - bgColor?: Color; - /** - * Set the blurriness of the output image (0.5-1000). - */ - blur?: number | Percentage; - /** - * Set the brightness of the output image (-100,100). - */ - brightness?: number; - /** - * Convert an image to black and white using a given algorithm. - */ - bw?: 'threshold' | 'atkinson'; - /** - * Increase the size of the canvas around an image. - */ - canvas?: { - size: Size; - position?: Position; - } - /** - * Set the contrast of the output image (-100-100). - */ - contrast?: number; - /** - * Remove pixels from an image. - */ - crop?: { - size: Size; - position?: Position; - mode?: 'smart' | 'safe'; - }; - /** - * Disable functionality that is enabled by default. - */ - disable?: 'upscale'; - /** - * Ratio between physical pixels and logical pixels (1-10). - */ - dpr?: number; - /** - * Enable functionality that is disabled by default. - */ - enable?: 'upscale'; - /** - * Set how the image will fit within the size bounds provided. - */ - fit?: 'bounds' | 'cover' | 'crop'; - /** - * Specify the output format to convert the image to. - */ - format?: - 'auto' | - 'avif' | - 'bjpg' | - 'gif' | - 'jpg' | - 'jxl' | - 'mp4' | - 'pjpg' | - 'pjxl' | - 'png' | - 'png8' | - 'svg' | - 'webp' | - 'webpll' | - 'webply'; - /** - * Extract the first frame from an animated image. - */ - frame?: 1; - /** - * Resize the height of the image. - */ - height?: number | Percentage; - /** - * Specify the level constraints when converting to video. - */ - level?: string; - /** - * Control which metadata fields are preserved during transformation. - */ - metadata?: 'copyright' | 'c2pa' | 'copyright,c2pa'; - /** - * Automatically apply optimal quality compression. - */ - optimize?: 'low' | 'medium' | 'high'; - /** - * Change the cardinal orientation of the image. - */ - orient?: '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8'; - /** - * Add pixels to the edge of an image - */ - pad?: Sides, - /** - * Remove pixels from an image before any other transformations occur. - */ - precrop?: { - size: Size; - position?: Position; - mode?: 'smart' | 'safe'; - }; - /** - * Specify the profile class of application when converting to video. - */ - profile?: 'baseline' | 'main' | 'high'; - /** - * Optimize the image to the given compresion level for lossy file formatted images (1-100). - */ - quality?: number; - /** - * Specify the resize filter used when resizing images. - */ - resizeFilter?: 'nearest' | 'bilinear' | 'linear' | 'bicubic' | 'cubic' | 'lanczos2' | 'lanczos3' | 'lanczos'; - /** - * Set the saturation of the output image (-100-100). - */ - saturation?: number; - /** - * Set the sharpness of the output image. - */ - sharpen?: { - amount: number; - radius: number; - threshold: number; - }; - /** - * Remove pixels from the edge of an image. - */ - trim?: Sides; - /** - * Remove explicit width and height properties in SVG output. - */ - viewbox?: 1; - /** - * Resize the width of the image. - */ - width?: number | Percentage; - } + bgColor?: Color; + /** + * Set the blurriness of the output image (0.5-1000). + */ + blur?: number | Percentage; + /** + * Set the brightness of the output image (-100,100). + */ + brightness?: number; + /** + * Convert an image to black and white using a given algorithm. + */ + bw?: 'threshold' | 'atkinson'; + /** + * Increase the size of the canvas around an image. + */ + canvas?: { + size: Size; + position?: Position; + }; + /** + * Set the contrast of the output image (-100-100). + */ + contrast?: number; + /** + * Remove pixels from an image. + */ + crop?: { + size: Size; + position?: Position; + mode?: 'smart' | 'safe'; + }; + /** + * Disable functionality that is enabled by default. + */ + disable?: 'upscale'; + /** + * Ratio between physical pixels and logical pixels (1-10). + */ + dpr?: number; + /** + * Enable functionality that is disabled by default. + */ + enable?: 'upscale'; + /** + * Set how the image will fit within the size bounds provided. + */ + fit?: 'bounds' | 'cover' | 'crop'; + /** + * Specify the output format to convert the image to. + */ + format?: + | 'auto' + | 'avif' + | 'bjpg' + | 'gif' + | 'jpg' + | 'jxl' + | 'mp4' + | 'pjpg' + | 'pjxl' + | 'png' + | 'png8' + | 'svg' + | 'webp' + | 'webpll' + | 'webply'; + /** + * Extract the first frame from an animated image. + */ + frame?: 1; + /** + * Resize the height of the image. + */ + height?: number | Percentage; + /** + * Specify the level constraints when converting to video. + */ + level?: string; + /** + * Control which metadata fields are preserved during transformation. + */ + metadata?: 'copyright' | 'c2pa' | 'copyright,c2pa'; + /** + * Automatically apply optimal quality compression. + */ + optimize?: 'low' | 'medium' | 'high'; + /** + * Change the cardinal orientation of the image. + */ + orient?: '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8'; + /** + * Add pixels to the edge of an image + */ + pad?: Sides; + /** + * Remove pixels from an image before any other transformations occur. + */ + precrop?: { + size: Size; + position?: Position; + mode?: 'smart' | 'safe'; + }; + /** + * Specify the profile class of application when converting to video. + */ + profile?: 'baseline' | 'main' | 'high'; + /** + * Optimize the image to the given compresion level for lossy file formatted images (1-100). + */ + quality?: number; + /** + * Specify the resize filter used when resizing images. + */ + resizeFilter?: + | 'nearest' + | 'bilinear' + | 'linear' + | 'bicubic' + | 'cubic' + | 'lanczos2' + | 'lanczos3' + | 'lanczos'; + /** + * Set the saturation of the output image (-100-100). + */ + saturation?: number; + /** + * Set the sharpness of the output image. + */ + sharpen?: { + amount: number; + radius: number; + threshold: number; + }; + /** + * Remove pixels from the edge of an image. + */ + trim?: Sides; + /** + * Remove explicit width and height properties in SVG output. + */ + viewbox?: 1; /** - * Convert image optimizer options into the query string that is sent to the image optimizer, for logging and debugging purposes. + * Resize the width of the image. */ - function optionsToQueryString(options: ImageOptimizerOptions): string; -} \ No newline at end of file + width?: number | Percentage; + } + /** + * Convert image optimizer options into the query string that is sent to the image optimizer, for logging and debugging purposes. + */ + function optionsToQueryString(options: ImageOptimizerOptions): string; +} From 983133bf30bc1a3a4f7bac120ce8e3085dc653ea Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Fri, 14 Nov 2025 13:12:32 +0000 Subject: [PATCH 27/31] Fix merge --- .../js-compute/fixtures/app/tests.json | 1940 +++++++++++++---- 1 file changed, 1505 insertions(+), 435 deletions(-) diff --git a/integration-tests/js-compute/fixtures/app/tests.json b/integration-tests/js-compute/fixtures/app/tests.json index 25ab0deec9..5e8096b86d 100644 --- a/integration-tests/js-compute/fixtures/app/tests.json +++ b/integration-tests/js-compute/fixtures/app/tests.json @@ -3,14 +3,22 @@ "downstream_response": { "status": 200, "headers": [ - ["FooName", "FooValue"], - ["BarName", "BarValue"] + [ + "FooName", + "FooValue" + ], + [ + "BarName", + "BarValue" + ] ], "body": "pong" } }, "GET /btoa": { - "environments": ["viceroy"], + "environments": [ + "viceroy" + ], "downstream_response": { "status": 200, "body": "ok" @@ -19,13 +27,18 @@ "GET /byob": { "downstream_response": { "status": 200, - "body": ["hey3"] + "body": [ + "hey3" + ] } }, "GET /byte-repeater": { "downstream_response": { "status": 200, - "body": ["11223344", "5566778899001122\n\n"] + "body": [ + "11223344", + "5566778899001122\n\n" + ] } }, "GET /cache-override/constructor/called-as-regular-function": {}, @@ -109,340 +122,508 @@ "GET /simple-cache/interface": {}, "GET /simple-store/constructor/called-as-regular-function": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/constructor/throws": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/purge/called-as-constructor": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/purge/key-parameter-calls-7.1.17-ToString": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/purge/key-parameter-not-supplied": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/purge/key-parameter-empty-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/purge/key-parameter-8135-character-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/purge/key-parameter-8136-character-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/purge/options-parameter": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/purge/returns-undefined": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/called-as-constructor": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/key-parameter-calls-7.1.17-ToString": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/tll-parameter-7.1.4-ToNumber": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/no-parameters-supplied": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/key-parameter-empty-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/key-parameter-8135-character-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/key-parameter-8136-character-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/ttl-parameter-negative-number": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/ttl-parameter-NaN": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/ttl-parameter-Infinity": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-as-undefined": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-readablestream-missing-length-parameter": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-readablestream-negative-length-parameter": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-readablestream-nan-length-parameter": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-readablestream-negative-infinity-length-parameter": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-readablestream-positive-infinity-length-parameter": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/length-parameter-7.1.4-ToNumber": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-readablestream-empty": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-readablestream-locked": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-readablestream": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-URLSearchParams": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-strings": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-calls-7.1.17-ToString": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-buffer": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-arraybuffer": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-typed-arrays": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/value-parameter-dataview": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/set/returns-undefined": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/get/called-as-constructor": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/get/key-parameter-calls-7.1.17-ToString": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/get/key-parameter-not-supplied": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/get/key-parameter-empty-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/get/key-parameter-8135-character-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/get/key-parameter-8136-character-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/get/key-does-not-exist-returns-null": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/get/key-exists": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache-entry/interface": {}, "GET /simple-cache-entry/text/valid": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache-entry/json/valid": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache-entry/json/invalid": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache-entry/arrayBuffer/valid": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache-entry/body": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache-entry/bodyUsed": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache-entry/readablestream": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/rejection-rejects-outer": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/called-as-constructor": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/no-parameters-supplied": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/key-parameter-calls-7.1.17-ToString": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/key-parameter-empty-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/key-parameter-8135-character-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/key-parameter-8136-character-string": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/ttl-field-7.1.4-ToNumber": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/ttl-field-negative-number": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/ttl-field-NaN": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/ttl-field-Infinity": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-as-undefined": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-readablestream-missing-length-field": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-readablestream-negative-length-field": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-readablestream-nan-length-field": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-readablestream-negative-infinity-length-field": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-readablestream-positive-infinity-length-field": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/length-field-7.1.4-ToNumber": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-readablestream-empty": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-readablestream-locked": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-readablestream": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-URLSearchParams": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-strings": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-calls-7.1.17-ToString": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-buffer": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-typed-arrays": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/value-field-dataview": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/returns-SimpleCacheEntry": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/executes-the-set-method-when-key-not-in-cache": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/does-not-execute-the-set-method-when-key-is-in-cache": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /simple-cache/getOrSet/does-not-freeze-when-called-after-a-get": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /client/tlsJA3MD5": {}, "GET /client/tlsClientHello": {}, @@ -1071,48 +1252,74 @@ "flake": true }, "GET /env": { - "environments": ["viceroy"] + "environments": [ + "viceroy" + ] }, "GET /createFanoutHandoff": {}, "GET /createWebsocketHandoff": {}, "GET /fastly/now": {}, "GET /fastly/version": {}, "GET /fastly/getgeolocationforipaddress/interface": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /fastly/getgeolocationforipaddress/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /fastly/getgeolocationforipaddress/parameter-calls-7.1.17-ToString": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /fastly/getgeolocationforipaddress/parameter-not-supplied": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /fastly/getgeolocationforipaddress/parameter-empty-string": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /fastly/getgeolocationforipaddress/bad-ip": {}, "GET /fastly/getgeolocationforipaddress/parameter-ipv4-string": {}, "GET /fastly/getgeolocationforipaddress/parameter-compressed-ipv6-string": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /fastly/getgeolocationforipaddress/parameter-shortened-ipv6-string": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /fastly/getgeolocationforipaddress/parameter-expanded-ipv6-string": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /fastly/getgeolocationforipaddress/called-unbound": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /fastly:geolocation": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /includeBytes": {}, "GET /logger": { - "environments": ["viceroy"], - "logs": ["ComputeLog :: Hello!"] + "environments": [ + "viceroy" + ], + "logs": [ + "ComputeLog :: Hello!" + ] }, "GET /missing-backend": {}, "GET /multiple-set-cookie/response-init": { @@ -1123,16 +1330,46 @@ "Set-Cookie", "test=1; expires=Tue, 06-Dec-2022 12:34:56 GMT; Max-Age=60; Path=/; HttpOnly; Secure, test_id=1; Max-Age=60; Path=/; expires=Tue, 06-Dec-2022 12:34:56 GMT, test_id=1; Max-Age=60; Path=/; expires=Tue, 06-Dec-2022 12:34:56 GMT" ], - ["Set-Cookie", "test2=2"], - ["Set-Cookie", "test3=3"], - ["Set-Cookie", "test4=4"], - ["Set-Cookie", "test5=5"], - ["Set-Cookie", "test6=6"], - ["Set-Cookie", "test7=7"], - ["Set-Cookie", "test8=8"], - ["Set-Cookie", "test9=9"], - ["Set-Cookie", "test10=10"], - ["Set-Cookie", "test11=11"] + [ + "Set-Cookie", + "test2=2" + ], + [ + "Set-Cookie", + "test3=3" + ], + [ + "Set-Cookie", + "test4=4" + ], + [ + "Set-Cookie", + "test5=5" + ], + [ + "Set-Cookie", + "test6=6" + ], + [ + "Set-Cookie", + "test7=7" + ], + [ + "Set-Cookie", + "test8=8" + ], + [ + "Set-Cookie", + "test9=9" + ], + [ + "Set-Cookie", + "test10=10" + ], + [ + "Set-Cookie", + "test11=11" + ] ] } }, @@ -1144,16 +1381,46 @@ "Set-Cookie", "test=1; expires=Tue, 06-Dec-2022 12:34:56 GMT; Max-Age=60; Path=/; HttpOnly; Secure, test_id=1; Max-Age=60; Path=/; expires=Tue, 06-Dec-2022 12:34:56 GMT, test_id=1; Max-Age=60; Path=/; expires=Tue, 06-Dec-2022 12:34:56 GMT" ], - ["Set-Cookie", "test2=2"], - ["Set-Cookie", "test3=3"], - ["Set-Cookie", "test4=4"], - ["Set-Cookie", "test5=5"], - ["Set-Cookie", "test6=6"], - ["Set-Cookie", "test7=7"], - ["Set-Cookie", "test8=8"], - ["Set-Cookie", "test9=9"], - ["Set-Cookie", "test10=10"], - ["Set-Cookie", "test11=11"] + [ + "Set-Cookie", + "test2=2" + ], + [ + "Set-Cookie", + "test3=3" + ], + [ + "Set-Cookie", + "test4=4" + ], + [ + "Set-Cookie", + "test5=5" + ], + [ + "Set-Cookie", + "test6=6" + ], + [ + "Set-Cookie", + "test7=7" + ], + [ + "Set-Cookie", + "test8=8" + ], + [ + "Set-Cookie", + "test9=9" + ], + [ + "Set-Cookie", + "test10=10" + ], + [ + "Set-Cookie", + "test11=11" + ] ] } }, @@ -1161,43 +1428,84 @@ "downstream_response": { "status": 302, "headers": [ - ["Set-Cookie", "1=1; Path=/"], - ["Set-Cookie", "2=2; Path=/"], - ["Set-Cookie", "3=3; Path=/"], - ["Set-Cookie", "4=4; Path=/"], - ["Set-Cookie", "5=5; Path=/"], - ["Set-Cookie", "6=6; Path=/"], - ["Set-Cookie", "7=7; Path=/"], - ["Set-Cookie", "8=8; Path=/"], - ["Set-Cookie", "9=9; Path=/"], - ["Set-Cookie", "10=10; Path=/"], - ["Set-Cookie", "11=11; Path=/"] + [ + "Set-Cookie", + "1=1; Path=/" + ], + [ + "Set-Cookie", + "2=2; Path=/" + ], + [ + "Set-Cookie", + "3=3; Path=/" + ], + [ + "Set-Cookie", + "4=4; Path=/" + ], + [ + "Set-Cookie", + "5=5; Path=/" + ], + [ + "Set-Cookie", + "6=6; Path=/" + ], + [ + "Set-Cookie", + "7=7; Path=/" + ], + [ + "Set-Cookie", + "8=8; Path=/" + ], + [ + "Set-Cookie", + "9=9; Path=/" + ], + [ + "Set-Cookie", + "10=10; Path=/" + ], + [ + "Set-Cookie", + "11=11; Path=/" + ] ] } }, "GET /Performance/interface": { - "environments": ["viceroy"], + "environments": [ + "viceroy" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /globalThis.performance": { - "environments": ["viceroy"], + "environments": [ + "viceroy" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /globalThis.performance/now": { - "environments": ["viceroy"], + "environments": [ + "viceroy" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /globalThis.performance/timeOrigin": { - "environments": ["viceroy"], + "environments": [ + "viceroy" + ], "downstream_response": { "status": 200, "body": "ok" @@ -1223,7 +1531,9 @@ "headers": { "content-type": "text/html" }, - "body": ["

blob

"] + "body": [ + "

blob

" + ] } }, "GET /response/stall": { @@ -1239,14 +1549,120 @@ "downstream_response": { "status": 200, "body_prefix": [ - 123, 10, 32, 32, 34, 97, 114, 103, 115, 34, 58, 32, 123, 125, 44, 32, - 10, 32, 32, 34, 100, 97, 116, 97, 34, 58, 32, 34, 100, 97, 116, 97, 58, - 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 111, 99, 116, - 101, 116, 45, 115, 116, 114, 101, 97, 109, 59, 98, 97, 115, 101, 54, 52, - 44, 85, 107, 108, 71, 82, 107, 65, 112, 65, 65, 66, 88, 82, 85, 74, 81, - 86, 108, 65, 52, 87, 65, 111, 65, 65, 65, 65, 69, 65, 65, 65, 65, 69, 81 + 123, + 10, + 32, + 32, + 34, + 97, + 114, + 103, + 115, + 34, + 58, + 32, + 123, + 125, + 44, + 32, + 10, + 32, + 32, + 34, + 100, + 97, + 116, + 97, + 34, + 58, + 32, + 34, + 100, + 97, + 116, + 97, + 58, + 97, + 112, + 112, + 108, + 105, + 99, + 97, + 116, + 105, + 111, + 110, + 47, + 111, + 99, + 116, + 101, + 116, + 45, + 115, + 116, + 114, + 101, + 97, + 109, + 59, + 98, + 97, + 115, + 101, + 54, + 52, + 44, + 85, + 107, + 108, + 71, + 82, + 107, + 65, + 112, + 65, + 65, + 66, + 88, + 82, + 85, + 74, + 81, + 86, + 108, + 65, + 52, + 87, + 65, + 111, + 65, + 65, + 65, + 65, + 69, + 65, + 65, + 65, + 65, + 69, + 81 ], - "body_suffix": [123, 10, 32, 32, 34, 97, 114, 103, 115, 34, 58, 32] + "body_suffix": [ + 123, + 10, + 32, + 32, + 34, + 97, + 114, + 103, + 115, + 34, + 58, + 32 + ] } }, "GET /response/ip-port-undefined": {}, @@ -1277,7 +1693,9 @@ "GET /setTimeout/200-ms": { "downstream_response": { "status": 200, - "body": ["START\nEND\n"] + "body": [ + "START\nEND\n" + ] } }, "GET /setTimeout/fetch-timeout": { @@ -1320,7 +1738,10 @@ "downstream_request": { "method": "POST", "pathname": "/tee", - "headers": ["Content-Type", "application/json"], + "headers": [ + "Content-Type", + "application/json" + ], "body": "hello world!" }, "downstream_response": { @@ -1332,7 +1753,10 @@ "downstream_request": { "method": "GET", "pathname": "/tee/error", - "headers": ["Content-Type", "application/json"] + "headers": [ + "Content-Type", + "application/json" + ] }, "downstream_response": { "status": 200, @@ -1340,42 +1764,54 @@ } }, "GET /override-content-length/request/init/object-literal/true": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /override-content-length/request/init/object-literal/false": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /override-content-length/request/clone/true": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /override-content-length/request/clone/false": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /override-content-length/fetch/init/object-literal/true": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /override-content-length/fetch/init/object-literal/false": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "ok" @@ -1383,14 +1819,20 @@ }, "/override-content-length/response/clone/true": { "skip": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "/override-content-length/response/clone/false": { "skip": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /override-content-length/response/init/object-literal/true": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "meow", @@ -1400,14 +1842,18 @@ } }, "GET /override-content-length/response/init/object-literal/false": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "meow" } }, "GET /override-content-length/response/init/response-instance/true": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "meow", @@ -1417,17 +1863,23 @@ } }, "GET /override-content-length/response/init/response-instance/false": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "meow" } }, "GET /override-content-length/response/method/false": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /override-content-length/response/method/true": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "headers": { @@ -1438,7 +1890,14 @@ "GET /headers/getsetcookie": { "downstream_response": { "status": 200, - "headers": [["name1=value1"], ["name2=value2"]] + "headers": [ + [ + "name1=value1" + ], + [ + "name2=value2" + ] + ] } }, "GET /headers/construct": { @@ -1453,7 +1912,12 @@ "flake": true, "downstream_response": { "status": 200, - "headers": [["cuStom", "test"]] + "headers": [ + [ + "cuStom", + "test" + ] + ] } }, "GET /headers/from-response/delete-invalid": { @@ -1463,940 +1927,1411 @@ "flake": true, "downstream_response": { "status": 200, - "headers": [["cuStom", "test"]] + "headers": [ + [ + "cuStom", + "test" + ] + ] } }, "GET /FastlyBody/interface": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/constructor/called-as-regular-function": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/constructor/called-as-constructor": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/called-as-constructor": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-not-supplied": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-wrong-type": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-readablestream-guest-backed": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-readablestream-host-backed": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-URLSearchParams": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-strings": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-calls-7.1.17-ToString": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-buffer": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-arraybuffer": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-typed-arrays": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-dataview": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/called-as-constructor": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-not-supplied": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-wrong-type": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-readablestream-guest-backed": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-readablestream-host-backed": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-readablestream-locked": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-URLSearchParams": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-strings": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-calls-7.1.17-ToString": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-buffer": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-arraybuffer": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-typed-arrays": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-dataview": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/concat/called-as-constructor": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/concat/dest-parameter-not-supplied": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/concat/dest-parameter-wrong-type": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/concat/concat-same-fastlybody-twice": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/concat/happy-path": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/called-as-constructor": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/chunkSize-parameter-not-supplied": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/chunkSize-parameter-wrong-type": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/chunkSize-parameter-negative": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/chunkSize-parameter-infinity": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/chunkSize-parameter-NaN": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/happy-path": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/close/called-as-constructor": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/close/called-once": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/close/called-twice": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/interface": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/constructor/called-as-regular-function": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/constructor/throws": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/called-as-constructor": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-parameter-calls-7.1.17-ToString": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-parameter-not-supplied": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-parameter-empty-string": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-parameter-8135-character-string": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-parameter-8136-character-string": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-does-not-exist-returns-null": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-exists": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/options-parameter-wrong-type": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/options-parameter-none": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/lookup/options-parameter-headers-field-wrong-type": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/lookup/options-parameter-headers-field-undefined": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/lookup/options-parameter-headers-field-valid-sequence": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/lookup/options-parameter-headers-field-valid-record": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/lookup/options-parameter-headers-field-valid-Headers-instance": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/key-parameter-calls-7.1.17-ToString": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/key-parameter-not-supplied": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/key-parameter-empty-string": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/key-parameter-8135-character-string": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/key-parameter-8136-character-string": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-wrong-type": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-headers-field-wrong-type": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-headers-field-undefined": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-headers-field-valid-sequence": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-headers-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-headers-field-valid-Headers-instance": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-maxAge-field-valid-record": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-maxAge-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-maxAge-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-maxAge-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-maxAge-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-initialAge-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-initialAge-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-initialAge-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-initialAge-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-initialAge-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-staleWhileRevalidate-field-valid-record": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-staleWhileRevalidate-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-staleWhileRevalidate-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-staleWhileRevalidate-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-staleWhileRevalidate-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-length-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-length-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-length-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-length-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-length-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-sensitive-field": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-vary-field": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-userMetadata-field/arraybuffer/empty": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-userMetadata-field/arraybuffer/not-empty": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-userMetadata-field/URLSearchParams": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/insert/options-parameter-userMetadata-field/string": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/key-parameter-calls-7.1.17-ToString": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/key-parameter-not-supplied": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/key-parameter-empty-string": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/key-parameter-8135-character-string": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/key-parameter-8136-character-string": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/key-does-not-exist": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/key-exists": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/options-parameter-wrong-type": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/options-parameter-headers-field-wrong-type": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/options-parameter-headers-field-undefined": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/options-parameter-headers-field-valid-sequence": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/options-parameter-headers-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /core-cache/transactionLookup/options-parameter-headers-field-valid-Headers-instance": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/interface": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/constructor/called-as-regular-function": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/constructor/throws": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/close/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/close/called-unbound": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/close/called-on-instance": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/state/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/state/called-unbound": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/state/called-on-instance": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/userMetadata/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/userMetadata/called-unbound": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/userMetadata/called-on-instance": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/userMetadata/basic": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/called-unbound": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/called-on-instance": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/options-start-negative": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/options-start-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/options-start-Infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/options-start-valid": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/options-start-longer-than-body": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/options-end-negative": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/options-end-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/options-end-Infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/options-end-valid": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/body/options-end-zero": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/length/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/length/called-unbound": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/length/called-on-instance": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/maxAge/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/maxAge/called-unbound": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/maxAge/called-on-instance": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/staleWhileRevalidate/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/staleWhileRevalidate/called-unbound": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/staleWhileRevalidate/called-on-instance": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/age/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/age/called-unbound": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/age/called-on-instance": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/hits/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/hits/called-unbound": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /cache-entry/hits/called-on-instance": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/interface": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/entry-parameter-not-supplied": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-maxAge-field-valid-record": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-maxAge-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-maxAge-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-maxAge-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-maxAge-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-initialAge-field-valid-record": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-initialAge-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-initialAge-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-initialAge-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-initialAge-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-staleWhileRevalidate-field-valid-record": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-staleWhileRevalidate-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-staleWhileRevalidate-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-staleWhileRevalidate-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-staleWhileRevalidate-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-length-field-valid-record": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-length-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-length-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-length-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-length-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-sensitive-field": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insert/options-parameter-vary-field": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/entry-parameter-not-supplied": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-maxAge-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-maxAge-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-maxAge-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-maxAge-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-maxAge-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-initialAge-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-initialAge-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-initialAge-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-initialAge-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-initialAge-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-staleWhileRevalidate-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-staleWhileRevalidate-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-staleWhileRevalidate-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-staleWhileRevalidate-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-staleWhileRevalidate-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-length-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-length-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-length-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-length-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-length-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-sensitive-field": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/write-to-writer-and-read-from-reader": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-vary-field": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/entry-parameter-not-supplied": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-maxAge-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-maxAge-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-maxAge-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-maxAge-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-maxAge-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-initialAge-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-initialAge-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-initialAge-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-initialAge-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-initialAge-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-staleWhileRevalidate-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-staleWhileRevalidate-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-staleWhileRevalidate-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-staleWhileRevalidate-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-staleWhileRevalidate-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-length-field-valid-record": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-length-field-NaN": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-length-field-postitive-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-length-field-negative-infinity": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-length-field-negative-number": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/write-to-writer-and-read-from-reader": { "flake": true, - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-vary-field": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/update/options-parameter-userMetadata-field": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/cancel/called-as-constructor": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/cancel/called-once": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/cancel/makes-entry-cancelled": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /transaction-cache-entry/cancel/called-twice-throws": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /rate-counter/interface": { "downstream_response": { @@ -2849,28 +3784,36 @@ } }, "GET /device/lookup/useragent-does-not-exist-returns-null": { - "environments": ["viceroy"], + "environments": [ + "viceroy" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /device/lookup/useragent-exists-all-fields-identified": { - "environments": ["viceroy"], + "environments": [ + "viceroy" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /device/lookup/useragent-exists-all-fields-identified-tojson": { - "environments": ["viceroy"], + "environments": [ + "viceroy" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /device/lookup/useragent-exists-some-fields-identified": { - "environments": ["viceroy"], + "environments": [ + "viceroy" + ], "downstream_response": { "status": 200, "body": "ok" @@ -2884,14 +3827,18 @@ }, "GET /core-cache/transaction-lookup-transaction-insert-vary-works": { "flake": true, - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "ok" } }, "GET /core-cache/lookup-insert-vary-works": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "status": 200, "body": "ok" @@ -2901,147 +3848,252 @@ "downstream_response": { "status": 200, "body": "", - "headers": [["result", "GET"]] + "headers": [ + [ + "result", + "GET" + ] + ] } }, "get /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "GET"]] + "headers": [ + [ + "result", + "GET" + ] + ] } }, "GeT /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "GET"]] + "headers": [ + [ + "result", + "GET" + ] + ] } }, "HEAD /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "HEAD"]] + "headers": [ + [ + "result", + "HEAD" + ] + ] } }, "head /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "HEAD"]] + "headers": [ + [ + "result", + "HEAD" + ] + ] } }, "HeAd /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "HEAD"]] + "headers": [ + [ + "result", + "HEAD" + ] + ] } }, "OPTIONS /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "OPTIONS"]] + "headers": [ + [ + "result", + "OPTIONS" + ] + ] } }, "OPTioNS /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "OPTIONS"]] + "headers": [ + [ + "result", + "OPTIONS" + ] + ] } }, "options /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "OPTIONS"]] + "headers": [ + [ + "result", + "OPTIONS" + ] + ] } }, "POST /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "POST"]] + "headers": [ + [ + "result", + "POST" + ] + ] } }, "post /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "POST"]] + "headers": [ + [ + "result", + "POST" + ] + ] } }, "PosT /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "POST"]] + "headers": [ + [ + "result", + "POST" + ] + ] } }, "PUT /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "PUT"]] + "headers": [ + [ + "result", + "PUT" + ] + ] } }, "put /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "PUT"]] + "headers": [ + [ + "result", + "PUT" + ] + ] } }, "Put /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "PUT"]] + "headers": [ + [ + "result", + "PUT" + ] + ] } }, "DELETE /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "DELETE"]] + "headers": [ + [ + "result", + "DELETE" + ] + ] } }, "DeLeTe /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "DELETE"]] + "headers": [ + [ + "result", + "DELETE" + ] + ] } }, "delete /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "DELETE"]] + "headers": [ + [ + "result", + "DELETE" + ] + ] } }, "HELLO /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "HELLO"]] + "headers": [ + [ + "result", + "HELLO" + ] + ] } }, "hello /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "hello"]] + "headers": [ + [ + "result", + "hello" + ] + ] } }, "heLLo /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [["result", "heLLo"]] + "headers": [ + [ + "result", + "heLLo" + ] + ] } }, "!*+-.^_`|~1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ /request/method/host": { @@ -3064,7 +4116,9 @@ }, "GET /compute/get-vcpu-ms": { "flake": true, - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 @@ -3072,10 +4126,14 @@ }, "GET /compute/purge-surrogate-key-invalid": {}, "GET /compute/purge-surrogate-key-hard": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /compute/purge-surrogate-key-soft": { - "environments": ["compute"] + "environments": [ + "compute" + ] }, "GET /html-rewriter/set-attribute": {}, "GET /html-rewriter/get-attribute": {}, @@ -3119,9 +4177,11 @@ "GET /image-optimizer/options/saturation": {}, "GET /image-optimizer/options/sharpen": {}, "GET /image-optimizer/options/trim": {}, - "GET /image-optimizer/options/viewbox": {} + "GET /image-optimizer/options/viewbox": {}, "GET /early-hints/manual-response": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 @@ -3134,7 +4194,9 @@ } }, "GET /early-hints/send-early-hints": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 @@ -3147,7 +4209,9 @@ } }, "GET /early-hints/send-early-hints-multiple-headers": { - "environments": ["compute"], + "environments": [ + "compute" + ], "downstream_response": { "body": "ok", "status": 200 @@ -3155,9 +4219,15 @@ "downstream_info": { "status": 103, "headers": [ - ["link", "; rel=preload; as=style"], - ["link", "; rel=preload; as=style"] + [ + "link", + "; rel=preload; as=style" + ], + [ + "link", + "; rel=preload; as=style" + ] ] } } -} +} \ No newline at end of file From 3de787b8ace48baf270764435053eb998ad263ec Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Fri, 14 Nov 2025 13:46:12 +0000 Subject: [PATCH 28/31] Type tests --- .../js-compute/fixtures/app/tests.json | 1938 ++++------------- test-d/image-optimizer.test-d.ts | 8 + 2 files changed, 442 insertions(+), 1504 deletions(-) create mode 100644 test-d/image-optimizer.test-d.ts diff --git a/integration-tests/js-compute/fixtures/app/tests.json b/integration-tests/js-compute/fixtures/app/tests.json index 5e8096b86d..6c4763e156 100644 --- a/integration-tests/js-compute/fixtures/app/tests.json +++ b/integration-tests/js-compute/fixtures/app/tests.json @@ -3,22 +3,14 @@ "downstream_response": { "status": 200, "headers": [ - [ - "FooName", - "FooValue" - ], - [ - "BarName", - "BarValue" - ] + ["FooName", "FooValue"], + ["BarName", "BarValue"] ], "body": "pong" } }, "GET /btoa": { - "environments": [ - "viceroy" - ], + "environments": ["viceroy"], "downstream_response": { "status": 200, "body": "ok" @@ -27,18 +19,13 @@ "GET /byob": { "downstream_response": { "status": 200, - "body": [ - "hey3" - ] + "body": ["hey3"] } }, "GET /byte-repeater": { "downstream_response": { "status": 200, - "body": [ - "11223344", - "5566778899001122\n\n" - ] + "body": ["11223344", "5566778899001122\n\n"] } }, "GET /cache-override/constructor/called-as-regular-function": {}, @@ -122,508 +109,340 @@ "GET /simple-cache/interface": {}, "GET /simple-store/constructor/called-as-regular-function": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/constructor/throws": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/purge/called-as-constructor": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/purge/key-parameter-calls-7.1.17-ToString": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/purge/key-parameter-not-supplied": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/purge/key-parameter-empty-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/purge/key-parameter-8135-character-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/purge/key-parameter-8136-character-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/purge/options-parameter": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/purge/returns-undefined": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/called-as-constructor": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/key-parameter-calls-7.1.17-ToString": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/tll-parameter-7.1.4-ToNumber": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/no-parameters-supplied": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/key-parameter-empty-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/key-parameter-8135-character-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/key-parameter-8136-character-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/ttl-parameter-negative-number": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/ttl-parameter-NaN": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/ttl-parameter-Infinity": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-as-undefined": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-readablestream-missing-length-parameter": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-readablestream-negative-length-parameter": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-readablestream-nan-length-parameter": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-readablestream-negative-infinity-length-parameter": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-readablestream-positive-infinity-length-parameter": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/length-parameter-7.1.4-ToNumber": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-readablestream-empty": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-readablestream-locked": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-readablestream": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-URLSearchParams": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-strings": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-calls-7.1.17-ToString": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-buffer": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-arraybuffer": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-typed-arrays": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/value-parameter-dataview": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/set/returns-undefined": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/get/called-as-constructor": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/get/key-parameter-calls-7.1.17-ToString": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/get/key-parameter-not-supplied": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/get/key-parameter-empty-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/get/key-parameter-8135-character-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/get/key-parameter-8136-character-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/get/key-does-not-exist-returns-null": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/get/key-exists": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache-entry/interface": {}, "GET /simple-cache-entry/text/valid": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache-entry/json/valid": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache-entry/json/invalid": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache-entry/arrayBuffer/valid": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache-entry/body": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache-entry/bodyUsed": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache-entry/readablestream": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/rejection-rejects-outer": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/called-as-constructor": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/no-parameters-supplied": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/key-parameter-calls-7.1.17-ToString": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/key-parameter-empty-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/key-parameter-8135-character-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/key-parameter-8136-character-string": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/ttl-field-7.1.4-ToNumber": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/ttl-field-negative-number": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/ttl-field-NaN": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/ttl-field-Infinity": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-as-undefined": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-readablestream-missing-length-field": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-readablestream-negative-length-field": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-readablestream-nan-length-field": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-readablestream-negative-infinity-length-field": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-readablestream-positive-infinity-length-field": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/length-field-7.1.4-ToNumber": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-readablestream-empty": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-readablestream-locked": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-readablestream": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-URLSearchParams": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-strings": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-calls-7.1.17-ToString": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-buffer": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-typed-arrays": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/value-field-dataview": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/returns-SimpleCacheEntry": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/executes-the-set-method-when-key-not-in-cache": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/does-not-execute-the-set-method-when-key-is-in-cache": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /simple-cache/getOrSet/does-not-freeze-when-called-after-a-get": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /client/tlsJA3MD5": {}, "GET /client/tlsClientHello": {}, @@ -1252,74 +1071,48 @@ "flake": true }, "GET /env": { - "environments": [ - "viceroy" - ] + "environments": ["viceroy"] }, "GET /createFanoutHandoff": {}, "GET /createWebsocketHandoff": {}, "GET /fastly/now": {}, "GET /fastly/version": {}, "GET /fastly/getgeolocationforipaddress/interface": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /fastly/getgeolocationforipaddress/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /fastly/getgeolocationforipaddress/parameter-calls-7.1.17-ToString": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /fastly/getgeolocationforipaddress/parameter-not-supplied": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /fastly/getgeolocationforipaddress/parameter-empty-string": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /fastly/getgeolocationforipaddress/bad-ip": {}, "GET /fastly/getgeolocationforipaddress/parameter-ipv4-string": {}, "GET /fastly/getgeolocationforipaddress/parameter-compressed-ipv6-string": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /fastly/getgeolocationforipaddress/parameter-shortened-ipv6-string": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /fastly/getgeolocationforipaddress/parameter-expanded-ipv6-string": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /fastly/getgeolocationforipaddress/called-unbound": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /fastly:geolocation": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /includeBytes": {}, "GET /logger": { - "environments": [ - "viceroy" - ], - "logs": [ - "ComputeLog :: Hello!" - ] + "environments": ["viceroy"], + "logs": ["ComputeLog :: Hello!"] }, "GET /missing-backend": {}, "GET /multiple-set-cookie/response-init": { @@ -1330,46 +1123,16 @@ "Set-Cookie", "test=1; expires=Tue, 06-Dec-2022 12:34:56 GMT; Max-Age=60; Path=/; HttpOnly; Secure, test_id=1; Max-Age=60; Path=/; expires=Tue, 06-Dec-2022 12:34:56 GMT, test_id=1; Max-Age=60; Path=/; expires=Tue, 06-Dec-2022 12:34:56 GMT" ], - [ - "Set-Cookie", - "test2=2" - ], - [ - "Set-Cookie", - "test3=3" - ], - [ - "Set-Cookie", - "test4=4" - ], - [ - "Set-Cookie", - "test5=5" - ], - [ - "Set-Cookie", - "test6=6" - ], - [ - "Set-Cookie", - "test7=7" - ], - [ - "Set-Cookie", - "test8=8" - ], - [ - "Set-Cookie", - "test9=9" - ], - [ - "Set-Cookie", - "test10=10" - ], - [ - "Set-Cookie", - "test11=11" - ] + ["Set-Cookie", "test2=2"], + ["Set-Cookie", "test3=3"], + ["Set-Cookie", "test4=4"], + ["Set-Cookie", "test5=5"], + ["Set-Cookie", "test6=6"], + ["Set-Cookie", "test7=7"], + ["Set-Cookie", "test8=8"], + ["Set-Cookie", "test9=9"], + ["Set-Cookie", "test10=10"], + ["Set-Cookie", "test11=11"] ] } }, @@ -1381,46 +1144,16 @@ "Set-Cookie", "test=1; expires=Tue, 06-Dec-2022 12:34:56 GMT; Max-Age=60; Path=/; HttpOnly; Secure, test_id=1; Max-Age=60; Path=/; expires=Tue, 06-Dec-2022 12:34:56 GMT, test_id=1; Max-Age=60; Path=/; expires=Tue, 06-Dec-2022 12:34:56 GMT" ], - [ - "Set-Cookie", - "test2=2" - ], - [ - "Set-Cookie", - "test3=3" - ], - [ - "Set-Cookie", - "test4=4" - ], - [ - "Set-Cookie", - "test5=5" - ], - [ - "Set-Cookie", - "test6=6" - ], - [ - "Set-Cookie", - "test7=7" - ], - [ - "Set-Cookie", - "test8=8" - ], - [ - "Set-Cookie", - "test9=9" - ], - [ - "Set-Cookie", - "test10=10" - ], - [ - "Set-Cookie", - "test11=11" - ] + ["Set-Cookie", "test2=2"], + ["Set-Cookie", "test3=3"], + ["Set-Cookie", "test4=4"], + ["Set-Cookie", "test5=5"], + ["Set-Cookie", "test6=6"], + ["Set-Cookie", "test7=7"], + ["Set-Cookie", "test8=8"], + ["Set-Cookie", "test9=9"], + ["Set-Cookie", "test10=10"], + ["Set-Cookie", "test11=11"] ] } }, @@ -1428,84 +1161,43 @@ "downstream_response": { "status": 302, "headers": [ - [ - "Set-Cookie", - "1=1; Path=/" - ], - [ - "Set-Cookie", - "2=2; Path=/" - ], - [ - "Set-Cookie", - "3=3; Path=/" - ], - [ - "Set-Cookie", - "4=4; Path=/" - ], - [ - "Set-Cookie", - "5=5; Path=/" - ], - [ - "Set-Cookie", - "6=6; Path=/" - ], - [ - "Set-Cookie", - "7=7; Path=/" - ], - [ - "Set-Cookie", - "8=8; Path=/" - ], - [ - "Set-Cookie", - "9=9; Path=/" - ], - [ - "Set-Cookie", - "10=10; Path=/" - ], - [ - "Set-Cookie", - "11=11; Path=/" - ] + ["Set-Cookie", "1=1; Path=/"], + ["Set-Cookie", "2=2; Path=/"], + ["Set-Cookie", "3=3; Path=/"], + ["Set-Cookie", "4=4; Path=/"], + ["Set-Cookie", "5=5; Path=/"], + ["Set-Cookie", "6=6; Path=/"], + ["Set-Cookie", "7=7; Path=/"], + ["Set-Cookie", "8=8; Path=/"], + ["Set-Cookie", "9=9; Path=/"], + ["Set-Cookie", "10=10; Path=/"], + ["Set-Cookie", "11=11; Path=/"] ] } }, "GET /Performance/interface": { - "environments": [ - "viceroy" - ], + "environments": ["viceroy"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /globalThis.performance": { - "environments": [ - "viceroy" - ], + "environments": ["viceroy"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /globalThis.performance/now": { - "environments": [ - "viceroy" - ], + "environments": ["viceroy"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /globalThis.performance/timeOrigin": { - "environments": [ - "viceroy" - ], + "environments": ["viceroy"], "downstream_response": { "status": 200, "body": "ok" @@ -1531,9 +1223,7 @@ "headers": { "content-type": "text/html" }, - "body": [ - "

blob

" - ] + "body": ["

blob

"] } }, "GET /response/stall": { @@ -1549,120 +1239,14 @@ "downstream_response": { "status": 200, "body_prefix": [ - 123, - 10, - 32, - 32, - 34, - 97, - 114, - 103, - 115, - 34, - 58, - 32, - 123, - 125, - 44, - 32, - 10, - 32, - 32, - 34, - 100, - 97, - 116, - 97, - 34, - 58, - 32, - 34, - 100, - 97, - 116, - 97, - 58, - 97, - 112, - 112, - 108, - 105, - 99, - 97, - 116, - 105, - 111, - 110, - 47, - 111, - 99, - 116, - 101, - 116, - 45, - 115, - 116, - 114, - 101, - 97, - 109, - 59, - 98, - 97, - 115, - 101, - 54, - 52, - 44, - 85, - 107, - 108, - 71, - 82, - 107, - 65, - 112, - 65, - 65, - 66, - 88, - 82, - 85, - 74, - 81, - 86, - 108, - 65, - 52, - 87, - 65, - 111, - 65, - 65, - 65, - 65, - 69, - 65, - 65, - 65, - 65, - 69, - 81 + 123, 10, 32, 32, 34, 97, 114, 103, 115, 34, 58, 32, 123, 125, 44, 32, + 10, 32, 32, 34, 100, 97, 116, 97, 34, 58, 32, 34, 100, 97, 116, 97, 58, + 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 111, 99, 116, + 101, 116, 45, 115, 116, 114, 101, 97, 109, 59, 98, 97, 115, 101, 54, 52, + 44, 85, 107, 108, 71, 82, 107, 65, 112, 65, 65, 66, 88, 82, 85, 74, 81, + 86, 108, 65, 52, 87, 65, 111, 65, 65, 65, 65, 69, 65, 65, 65, 65, 69, 81 ], - "body_suffix": [ - 123, - 10, - 32, - 32, - 34, - 97, - 114, - 103, - 115, - 34, - 58, - 32 - ] + "body_suffix": [123, 10, 32, 32, 34, 97, 114, 103, 115, 34, 58, 32] } }, "GET /response/ip-port-undefined": {}, @@ -1693,9 +1277,7 @@ "GET /setTimeout/200-ms": { "downstream_response": { "status": 200, - "body": [ - "START\nEND\n" - ] + "body": ["START\nEND\n"] } }, "GET /setTimeout/fetch-timeout": { @@ -1738,10 +1320,7 @@ "downstream_request": { "method": "POST", "pathname": "/tee", - "headers": [ - "Content-Type", - "application/json" - ], + "headers": ["Content-Type", "application/json"], "body": "hello world!" }, "downstream_response": { @@ -1753,10 +1332,7 @@ "downstream_request": { "method": "GET", "pathname": "/tee/error", - "headers": [ - "Content-Type", - "application/json" - ] + "headers": ["Content-Type", "application/json"] }, "downstream_response": { "status": 200, @@ -1764,54 +1340,42 @@ } }, "GET /override-content-length/request/init/object-literal/true": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /override-content-length/request/init/object-literal/false": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /override-content-length/request/clone/true": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /override-content-length/request/clone/false": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /override-content-length/fetch/init/object-literal/true": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /override-content-length/fetch/init/object-literal/false": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "ok" @@ -1819,20 +1383,14 @@ }, "/override-content-length/response/clone/true": { "skip": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "/override-content-length/response/clone/false": { "skip": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /override-content-length/response/init/object-literal/true": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "meow", @@ -1842,18 +1400,14 @@ } }, "GET /override-content-length/response/init/object-literal/false": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "meow" } }, "GET /override-content-length/response/init/response-instance/true": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "meow", @@ -1863,23 +1417,17 @@ } }, "GET /override-content-length/response/init/response-instance/false": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "meow" } }, "GET /override-content-length/response/method/false": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /override-content-length/response/method/true": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "headers": { @@ -1890,14 +1438,7 @@ "GET /headers/getsetcookie": { "downstream_response": { "status": 200, - "headers": [ - [ - "name1=value1" - ], - [ - "name2=value2" - ] - ] + "headers": [["name1=value1"], ["name2=value2"]] } }, "GET /headers/construct": { @@ -1912,12 +1453,7 @@ "flake": true, "downstream_response": { "status": 200, - "headers": [ - [ - "cuStom", - "test" - ] - ] + "headers": [["cuStom", "test"]] } }, "GET /headers/from-response/delete-invalid": { @@ -1927,1411 +1463,940 @@ "flake": true, "downstream_response": { "status": 200, - "headers": [ - [ - "cuStom", - "test" - ] - ] + "headers": [["cuStom", "test"]] } }, "GET /FastlyBody/interface": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/constructor/called-as-regular-function": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/constructor/called-as-constructor": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/called-as-constructor": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-not-supplied": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-wrong-type": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-readablestream-guest-backed": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-readablestream-host-backed": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-URLSearchParams": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-strings": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-calls-7.1.17-ToString": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-buffer": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-arraybuffer": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-typed-arrays": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/append/data-parameter-dataview": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/called-as-constructor": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-not-supplied": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-wrong-type": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-readablestream-guest-backed": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-readablestream-host-backed": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-readablestream-locked": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-URLSearchParams": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-strings": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-calls-7.1.17-ToString": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-buffer": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-arraybuffer": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-typed-arrays": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/prepend/data-parameter-dataview": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/concat/called-as-constructor": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/concat/dest-parameter-not-supplied": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/concat/dest-parameter-wrong-type": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/concat/concat-same-fastlybody-twice": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/concat/happy-path": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/called-as-constructor": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/chunkSize-parameter-not-supplied": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/chunkSize-parameter-wrong-type": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/chunkSize-parameter-negative": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/chunkSize-parameter-infinity": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/chunkSize-parameter-NaN": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/read/happy-path": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/close/called-as-constructor": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/close/called-once": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /FastlyBody/close/called-twice": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/interface": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/constructor/called-as-regular-function": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/constructor/throws": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/called-as-constructor": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-parameter-calls-7.1.17-ToString": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-parameter-not-supplied": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-parameter-empty-string": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-parameter-8135-character-string": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-parameter-8136-character-string": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-does-not-exist-returns-null": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/key-exists": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/options-parameter-wrong-type": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 } }, "GET /core-cache/lookup/options-parameter-none": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/lookup/options-parameter-headers-field-wrong-type": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/lookup/options-parameter-headers-field-undefined": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/lookup/options-parameter-headers-field-valid-sequence": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/lookup/options-parameter-headers-field-valid-record": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/lookup/options-parameter-headers-field-valid-Headers-instance": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/key-parameter-calls-7.1.17-ToString": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/key-parameter-not-supplied": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/key-parameter-empty-string": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/key-parameter-8135-character-string": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/key-parameter-8136-character-string": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-wrong-type": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-headers-field-wrong-type": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-headers-field-undefined": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-headers-field-valid-sequence": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-headers-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-headers-field-valid-Headers-instance": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-maxAge-field-valid-record": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-maxAge-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-maxAge-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-maxAge-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-maxAge-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-initialAge-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-initialAge-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-initialAge-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-initialAge-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-initialAge-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-staleWhileRevalidate-field-valid-record": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-staleWhileRevalidate-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-staleWhileRevalidate-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-staleWhileRevalidate-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-staleWhileRevalidate-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-length-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-length-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-length-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-length-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-length-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-sensitive-field": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-vary-field": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-userMetadata-field/arraybuffer/empty": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-userMetadata-field/arraybuffer/not-empty": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-userMetadata-field/URLSearchParams": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/insert/options-parameter-userMetadata-field/string": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/key-parameter-calls-7.1.17-ToString": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/key-parameter-not-supplied": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/key-parameter-empty-string": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/key-parameter-8135-character-string": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/key-parameter-8136-character-string": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/key-does-not-exist": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/key-exists": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/options-parameter-wrong-type": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/options-parameter-headers-field-wrong-type": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/options-parameter-headers-field-undefined": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/options-parameter-headers-field-valid-sequence": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/options-parameter-headers-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /core-cache/transactionLookup/options-parameter-headers-field-valid-Headers-instance": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/interface": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/constructor/called-as-regular-function": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/constructor/throws": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/close/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/close/called-unbound": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/close/called-on-instance": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/state/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/state/called-unbound": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/state/called-on-instance": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/userMetadata/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/userMetadata/called-unbound": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/userMetadata/called-on-instance": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/userMetadata/basic": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/called-unbound": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/called-on-instance": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/options-start-negative": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/options-start-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/options-start-Infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/options-start-valid": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/options-start-longer-than-body": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/options-end-negative": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/options-end-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/options-end-Infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/options-end-valid": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/body/options-end-zero": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/length/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/length/called-unbound": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/length/called-on-instance": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/maxAge/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/maxAge/called-unbound": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/maxAge/called-on-instance": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/staleWhileRevalidate/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/staleWhileRevalidate/called-unbound": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/staleWhileRevalidate/called-on-instance": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/age/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/age/called-unbound": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/age/called-on-instance": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/hits/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/hits/called-unbound": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /cache-entry/hits/called-on-instance": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/interface": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/entry-parameter-not-supplied": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-maxAge-field-valid-record": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-maxAge-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-maxAge-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-maxAge-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-maxAge-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-initialAge-field-valid-record": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-initialAge-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-initialAge-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-initialAge-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-initialAge-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-staleWhileRevalidate-field-valid-record": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-staleWhileRevalidate-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-staleWhileRevalidate-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-staleWhileRevalidate-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-staleWhileRevalidate-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-length-field-valid-record": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-length-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-length-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-length-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-length-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-sensitive-field": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insert/options-parameter-vary-field": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/entry-parameter-not-supplied": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-maxAge-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-maxAge-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-maxAge-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-maxAge-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-maxAge-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-initialAge-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-initialAge-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-initialAge-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-initialAge-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-initialAge-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-staleWhileRevalidate-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-staleWhileRevalidate-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-staleWhileRevalidate-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-staleWhileRevalidate-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-staleWhileRevalidate-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-length-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-length-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-length-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-length-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-length-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-sensitive-field": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/write-to-writer-and-read-from-reader": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/insertAndStreamBack/options-parameter-vary-field": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/entry-parameter-not-supplied": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-maxAge-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-maxAge-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-maxAge-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-maxAge-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-maxAge-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-initialAge-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-initialAge-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-initialAge-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-initialAge-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-initialAge-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-staleWhileRevalidate-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-staleWhileRevalidate-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-staleWhileRevalidate-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-staleWhileRevalidate-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-staleWhileRevalidate-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-length-field-valid-record": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-length-field-NaN": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-length-field-postitive-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-length-field-negative-infinity": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-length-field-negative-number": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/write-to-writer-and-read-from-reader": { "flake": true, - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-vary-field": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/update/options-parameter-userMetadata-field": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/cancel/called-as-constructor": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/cancel/called-once": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/cancel/makes-entry-cancelled": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /transaction-cache-entry/cancel/called-twice-throws": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /rate-counter/interface": { "downstream_response": { @@ -3784,36 +2849,28 @@ } }, "GET /device/lookup/useragent-does-not-exist-returns-null": { - "environments": [ - "viceroy" - ], + "environments": ["viceroy"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /device/lookup/useragent-exists-all-fields-identified": { - "environments": [ - "viceroy" - ], + "environments": ["viceroy"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /device/lookup/useragent-exists-all-fields-identified-tojson": { - "environments": [ - "viceroy" - ], + "environments": ["viceroy"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /device/lookup/useragent-exists-some-fields-identified": { - "environments": [ - "viceroy" - ], + "environments": ["viceroy"], "downstream_response": { "status": 200, "body": "ok" @@ -3827,18 +2884,14 @@ }, "GET /core-cache/transaction-lookup-transaction-insert-vary-works": { "flake": true, - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "ok" } }, "GET /core-cache/lookup-insert-vary-works": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "status": 200, "body": "ok" @@ -3848,252 +2901,147 @@ "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "GET" - ] - ] + "headers": [["result", "GET"]] } }, "get /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "GET" - ] - ] + "headers": [["result", "GET"]] } }, "GeT /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "GET" - ] - ] + "headers": [["result", "GET"]] } }, "HEAD /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "HEAD" - ] - ] + "headers": [["result", "HEAD"]] } }, "head /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "HEAD" - ] - ] + "headers": [["result", "HEAD"]] } }, "HeAd /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "HEAD" - ] - ] + "headers": [["result", "HEAD"]] } }, "OPTIONS /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "OPTIONS" - ] - ] + "headers": [["result", "OPTIONS"]] } }, "OPTioNS /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "OPTIONS" - ] - ] + "headers": [["result", "OPTIONS"]] } }, "options /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "OPTIONS" - ] - ] + "headers": [["result", "OPTIONS"]] } }, "POST /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "POST" - ] - ] + "headers": [["result", "POST"]] } }, "post /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "POST" - ] - ] + "headers": [["result", "POST"]] } }, "PosT /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "POST" - ] - ] + "headers": [["result", "POST"]] } }, "PUT /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "PUT" - ] - ] + "headers": [["result", "PUT"]] } }, "put /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "PUT" - ] - ] + "headers": [["result", "PUT"]] } }, "Put /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "PUT" - ] - ] + "headers": [["result", "PUT"]] } }, "DELETE /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "DELETE" - ] - ] + "headers": [["result", "DELETE"]] } }, "DeLeTe /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "DELETE" - ] - ] + "headers": [["result", "DELETE"]] } }, "delete /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "DELETE" - ] - ] + "headers": [["result", "DELETE"]] } }, "HELLO /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "HELLO" - ] - ] + "headers": [["result", "HELLO"]] } }, "hello /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "hello" - ] - ] + "headers": [["result", "hello"]] } }, "heLLo /request/method/host": { "downstream_response": { "status": 200, "body": "", - "headers": [ - [ - "result", - "heLLo" - ] - ] + "headers": [["result", "heLLo"]] } }, "!*+-.^_`|~1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ /request/method/host": { @@ -4116,9 +3064,7 @@ }, "GET /compute/get-vcpu-ms": { "flake": true, - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 @@ -4126,14 +3072,10 @@ }, "GET /compute/purge-surrogate-key-invalid": {}, "GET /compute/purge-surrogate-key-hard": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /compute/purge-surrogate-key-soft": { - "environments": [ - "compute" - ] + "environments": ["compute"] }, "GET /html-rewriter/set-attribute": {}, "GET /html-rewriter/get-attribute": {}, @@ -4179,9 +3121,7 @@ "GET /image-optimizer/options/trim": {}, "GET /image-optimizer/options/viewbox": {}, "GET /early-hints/manual-response": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 @@ -4194,9 +3134,7 @@ } }, "GET /early-hints/send-early-hints": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 @@ -4209,9 +3147,7 @@ } }, "GET /early-hints/send-early-hints-multiple-headers": { - "environments": [ - "compute" - ], + "environments": ["compute"], "downstream_response": { "body": "ok", "status": 200 @@ -4219,15 +3155,9 @@ "downstream_info": { "status": 103, "headers": [ - [ - "link", - "; rel=preload; as=style" - ], - [ - "link", - "; rel=preload; as=style" - ] + ["link", "; rel=preload; as=style"], + ["link", "; rel=preload; as=style"] ] } } -} \ No newline at end of file +} diff --git a/test-d/image-optimizer.test-d.ts b/test-d/image-optimizer.test-d.ts new file mode 100644 index 0000000000..5295d34b69 --- /dev/null +++ b/test-d/image-optimizer.test-d.ts @@ -0,0 +1,8 @@ +/// +import { + ImageOptimizerOptions, + optionsToQueryString, +} from 'fastly:image-optimizer'; +import { expectType } from 'tsd'; + +expectType<(options: ImageOptimizerOptions) => string>(optionsToQueryString); From 06040726cc530b34c57515225c71c83b473ad980 Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Fri, 14 Nov 2025 13:58:56 +0000 Subject: [PATCH 29/31] Format --- runtime/fastly/host-api/host_api_fastly.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/fastly/host-api/host_api_fastly.h b/runtime/fastly/host-api/host_api_fastly.h index b7d8f93b08..74afd94ae4 100644 --- a/runtime/fastly/host-api/host_api_fastly.h +++ b/runtime/fastly/host-api/host_api_fastly.h @@ -374,7 +374,7 @@ struct TlsVersion { uint8_t value = 0; explicit TlsVersion(uint8_t raw); - explicit TlsVersion() {}; + explicit TlsVersion(){}; uint8_t get_version() const; double get_version_number() const; From a239f4c027e33d425a61c59ebba680b5a58f83b3 Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Fri, 14 Nov 2025 14:01:43 +0000 Subject: [PATCH 30/31] Format --- integration-tests/js-compute/test.js | 50 ++++++++++++++-------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/integration-tests/js-compute/test.js b/integration-tests/js-compute/test.js index 5ad9ebee61..bf1aa7b4b5 100755 --- a/integration-tests/js-compute/test.js +++ b/integration-tests/js-compute/test.js @@ -136,7 +136,7 @@ if (!local) { core.startGroup('Delete service if already exists'); try { await zx`fastly service delete --quiet --service-name "${serviceName}" --force --token $FASTLY_API_TOKEN`; - } catch { } + } catch {} core.endGroup(); core.startGroup('Build and deploy service'); await zx`npm i`; @@ -170,13 +170,13 @@ await Promise.all([ 27, local ? [ - // we expect it to take ~10 seconds to deploy, so focus on that time - 6000, 3000, 1500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, - 500, 500, 500, 500, 500, 500, 500, 500, 500, - // after more than 20 seconds, means we have an unusually slow build, start backoff before timeout - 1500, - 3000, 6000, 12000, 24000, - ].values() + // we expect it to take ~10 seconds to deploy, so focus on that time + 6000, 3000, 1500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, + 500, 500, 500, 500, 500, 500, 500, 500, 500, + // after more than 20 seconds, means we have an unusually slow build, start backoff before timeout + 1500, + 3000, 6000, 12000, 24000, + ].values() : expBackoff('60s', '10s'), async () => { const response = await request(domain); @@ -275,12 +275,12 @@ for (const chunk of chunks(Object.entries(tests), 100)) { case 'first-chunk-only': for await (const chunk of response.body) { bodyChunks.push(chunk); - response.body.on('error', () => { }); + response.body.on('error', () => {}); break; } break; case 'none': - response.body.on('error', () => { }); + response.body.on('error', () => {}); break; case 'full': default: @@ -302,22 +302,22 @@ for (const chunk of chunks(Object.entries(tests), 100)) { } let onInfoHandler = test.downstream_info ? async (status, headers) => { - if ( - test.downstream_info.status !== undefined && - test.downstream_info.status != status - ) { - throw new Error( - `[DownstreamInfo: Status mismatch] Expected: ${configResponse.status} - Got: ${status}}`, - ); - } - if (headers) { - compareHeaders( - configResponse.headers, - headers, - configResponse.headersExhaustive, - ); + if ( + test.downstream_info.status !== undefined && + test.downstream_info.status != status + ) { + throw new Error( + `[DownstreamInfo: Status mismatch] Expected: ${configResponse.status} - Got: ${status}}`, + ); + } + if (headers) { + compareHeaders( + configResponse.headers, + headers, + configResponse.headersExhaustive, + ); + } } - } : undefined; if (local) { From 40c97028ac6611caeaaae128b78009745cadfc5f Mon Sep 17 00:00:00 2001 From: Sy Brand Date: Fri, 14 Nov 2025 14:39:02 +0000 Subject: [PATCH 31/31] Fix caching --- runtime/fastly/builtins/fetch/fetch.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/fastly/builtins/fetch/fetch.cpp b/runtime/fastly/builtins/fetch/fetch.cpp index 23df03b20e..f15468eaae 100644 --- a/runtime/fastly/builtins/fetch/fetch.cpp +++ b/runtime/fastly/builtins/fetch/fetch.cpp @@ -262,7 +262,7 @@ bool fetch_send_body(JSContext *cx, HandleObject request, JS::MutableHandleValue auto body = RequestOrResponse::body_handle(request); host_api::Result res; switch (caching_mode) { - case CachingMode::Guest: { + case CachingMode::Host: { if (streaming) { res = request_handle.send_async_streaming(body, backend_chars); } else { @@ -270,7 +270,7 @@ bool fetch_send_body(JSContext *cx, HandleObject request, JS::MutableHandleValue } break; } - case CachingMode::Host: + case CachingMode::Guest: res = request_handle.send_async_without_caching(body, backend_chars, streaming); break; case CachingMode::ImageOptimizer: {