diff --git a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-ab-testing-as.md b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-ab-testing-as.md
index c74421a..c70feb6 100644
--- a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-ab-testing-as.md
+++ b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-ab-testing-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
# A/B Testing — AssemblyScript (CDN)
diff --git a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-api-key-as.md b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-api-key-as.md
index 4807ec3..9a743b6 100644
--- a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-api-key-as.md
+++ b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-api-key-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
diff --git a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-auth-jwt-as.md b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-auth-jwt-as.md
index 8495978..68adc80 100644
--- a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-auth-jwt-as.md
+++ b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-auth-jwt-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
capabilities:
diff --git a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-body-as.md b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-body-as.md
index d98a1dc..f558a45 100644
--- a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-body-as.md
+++ b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-body-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
diff --git a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-cache-control-as.md b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-cache-control-as.md
index 41bcabf..ee2e585 100644
--- a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-cache-control-as.md
+++ b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-cache-control-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
diff --git a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-cors-as.md b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-cors-as.md
index 090a82c..a514711 100644
--- a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-cors-as.md
+++ b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-cors-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
# CORS — AssemblyScript (CDN)
diff --git a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-custom-error-pages-as.md b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-custom-error-pages-as.md
index bf58305..ce71e32 100644
--- a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-custom-error-pages-as.md
+++ b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-custom-error-pages-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
@@ -292,241 +292,3 @@ export * from "@gcoredev/proxy-wasm-sdk-as/assembly/proxy";
- CDN app platform overview
- host-services-rust reference (for Rust equivalent patterns)
- best-practices reference (response body buffering, hook sequencing)
-
-## Source Material
-
-### FILE: examples/customErrorPages/assembly/index.ts
-
-```ts
-export * from "@gcoredev/proxy-wasm-sdk-as/assembly/proxy";
-import {
- Context,
- FilterDataStatusValues,
- FilterHeadersStatusValues,
- get_property,
- log,
- LogLevelValues,
- registerRootContext,
- RootContext,
- set_buffer_bytes,
- BufferTypeValues,
- stream_context,
-} from "@gcoredev/proxy-wasm-sdk-as/assembly";
-import { setLogLevel } from "@gcoredev/proxy-wasm-sdk-as/assembly/fastedge";
-
-class ErrorPagesRoot extends RootContext {
- createContext(context_id: u32): Context {
- setLogLevel(LogLevelValues.info);
- return new ErrorPagesContext(context_id, this);
- }
-}
-
-class ErrorPagesContext extends Context {
- constructor(context_id: u32, root_context: ErrorPagesRoot) {
- super(context_id, root_context);
- }
-
- onResponseHeaders(a: u32, end_of_stream: bool): FilterHeadersStatusValues {
- const statusBuf = get_property("response.status");
- if (statusBuf.byteLength < 2) {
- return FilterHeadersStatusValues.Continue;
- }
-
- const bytes = Uint8Array.wrap(statusBuf);
- const code: u32 = (u32(bytes[0]) << 8) | u32(bytes[1]);
-
- if (code >= 400 && code < 600) {
- stream_context.headers.response.replace("Content-Type", "text/html");
- stream_context.headers.response.remove("Content-Length");
- stream_context.headers.response.replace("Transfer-Encoding", "Chunked");
-
- log(LogLevelValues.info, "Error response detected: " + code.toString());
- }
-
- return FilterHeadersStatusValues.Continue;
- }
-
- onResponseBody(
- body_buffer_length: usize,
- end_of_stream: bool,
- ): FilterDataStatusValues {
- // Read response status from property (no instance state between hooks)
- const statusBuf = get_property("response.status");
- if (statusBuf.byteLength < 2) {
- return FilterDataStatusValues.Continue;
- }
-
- const bytes = Uint8Array.wrap(statusBuf);
- const code: u32 = (u32(bytes[0]) << 8) | u32(bytes[1]);
-
- if (code < 400 || code >= 600) {
- return FilterDataStatusValues.Continue;
- }
-
- if (!end_of_stream) {
- return FilterDataStatusValues.StopIterationAndBuffer;
- }
- const title = this.getErrorTitle(code);
- const description = this.getErrorDescription(code);
- const category = code >= 500 ? "Server Error" : "Client Error";
-
- const html =
- '
' +
- '' +
- "" +
- code.toString() +
- " — " +
- title +
- "" +
- "" +
- "
" +
- code.toString() +
- "
" +
- "
" +
- title +
- "
" +
- "
" +
- description +
- "
" +
- "
" +
- category +
- "
" +
- "
";
-
- const body = String.UTF8.encode(html);
- // Replace the entire original body — pass body_buffer_length as the length
- // to replace, not body.byteLength. Otherwise any original bytes beyond the
- // new body's length survive at the tail of the response.
- set_buffer_bytes(
- BufferTypeValues.HttpResponseBody,
- 0,
- body_buffer_length as u32,
- body,
- );
-
- return FilterDataStatusValues.Continue;
- }
-
- private getErrorTitle(code: u32): string {
- if (code == 400) return "Bad Request";
- if (code == 401) return "Unauthorized";
- if (code == 403) return "Forbidden";
- if (code == 404) return "Not Found";
- if (code == 405) return "Method Not Allowed";
- if (code == 408) return "Request Timeout";
- if (code == 429) return "Too Many Requests";
- if (code == 500) return "Internal Server Error";
- if (code == 502) return "Bad Gateway";
- if (code == 503) return "Service Unavailable";
- if (code == 504) return "Gateway Timeout";
- if (code >= 500) return "Server Error";
- return "Error";
- }
-
- private getErrorDescription(code: u32): string {
- if (code == 400)
- return "The server could not understand the request due to invalid syntax.";
- if (code == 401)
- return "You need to authenticate to access this resource.";
- if (code == 403)
- return "You do not have permission to access this resource.";
- if (code == 404)
- return "The requested page could not be found. It may have been moved or deleted.";
- if (code == 405)
- return "The request method is not supported for this resource.";
- if (code == 408)
- return "The server timed out waiting for the request.";
- if (code == 429)
- return "You have sent too many requests. Please try again later.";
- if (code == 500)
- return "The server encountered an unexpected condition that prevented it from fulfilling the request.";
- if (code == 502)
- return "The server received an invalid response from the upstream server.";
- if (code == 503)
- return "The server is temporarily unavailable. Please try again later.";
- if (code == 504)
- return "The server did not receive a timely response from the upstream server.";
- if (code >= 500)
- return "The server encountered an error processing your request.";
- return "An error occurred processing your request.";
- }
-}
-
-registerRootContext((context_id: u32) => {
- return new ErrorPagesRoot(context_id);
-}, "customErrorPages");
-```
-
-
-### FILE: examples/customErrorPages/package.json
-
-```json
-{
- "name": "fastedge-as-example-custom-error-pages",
- "version": "1.0.0",
- "description": "FastEdge AssemblyScript example: Custom Error Pages — replace 4xx/5xx responses with branded HTML",
- "scripts": {
- "asbuild:debug": "asc assembly/index.ts --target debug",
- "asbuild:release": "asc assembly/index.ts --target release",
- "asbuild": "npm run asbuild:debug && npm run asbuild:release"
- },
- "dependencies": {
- "@gcoredev/proxy-wasm-sdk-as": "^1.2.3"
- },
- "devDependencies": {
- "@assemblyscript/wasi-shim": "^0.1.0",
- "assemblyscript": "^0.28.9"
- }
-}
-```
-
-
-### FILE: examples/customErrorPages/README.md
-
-```
-[← Back to examples](../README.md)
-
-# Custom Error Pages
-
-This application intercepts 4xx and 5xx error responses and replaces them with clean, branded HTML error pages.
-
-## What it does
-
-In `onResponseHeaders`, the app reads the `response.status` property. If it is in the 400-599 range, it sets the `Content-Type` to `text/html` and prepares for body replacement.
-
-In `onResponseBody`, the app buffers the full response, then replaces the body with a styled HTML page containing:
-
-- The numeric status code
-- A human-readable error title (e.g. "Not Found", "Bad Gateway")
-- A description explaining what went wrong
-- An error category label ("Client Error" or "Server Error")
-
-Covers all common HTTP error codes (400, 401, 403, 404, 405, 408, 429, 500, 502, 503, 504) with specific messages and falls back to generic messages for other codes.
-
-## Build
-
-```sh
-pnpm install
-pnpm run asbuild
-```
-
-Build output:
-
-| File | Description |
-|------|-------------|
-| `build/customErrorPages.wasm` | Optimised release binary — upload this to FastEdge |
-| `build/customErrorPages-debug.wasm` | Debug binary with source maps |
-
-## Deploy
-
-Upload `build/customErrorPages.wasm` to the FastEdge portal and attach it to your CDN application. No environment variables or secrets are required.
-```
diff --git a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-env-secrets-as.md b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-env-secrets-as.md
index 2039ea1..cee8f25 100644
--- a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-env-secrets-as.md
+++ b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-env-secrets-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
type: example
diff --git a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-geo-redirect-as.md b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-geo-redirect-as.md
index 03f0c0b..849560f 100644
--- a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-geo-redirect-as.md
+++ b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-geo-redirect-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
diff --git a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-geoblock-as.md b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-geoblock-as.md
index 462ec1f..0a9923a 100644
--- a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-geoblock-as.md
+++ b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-geoblock-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
diff --git a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-headers-as.md b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-headers-as.md
index 981cd2f..7a52ea5 100644
--- a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-headers-as.md
+++ b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-headers-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
@@ -66,14 +66,14 @@ registerRootContext((context_id: u32) => {
All header operations are accessed via `stream_context.headers.request` or `stream_context.headers.response`.
-| Method | Signature | Description |
-| ------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
-| `get(name)` | `(name: string) => string` | Returns the value of the named header, or empty string if not present |
-| `add(name, value)` | `(name: string, value: string) => void` | Adds a header; multiple calls with the same name produce multiple values |
-| `replace(name, value)` | `(name: string, value: string) => void` | Upserts the header value — creates the header if it does not exist (see Known Issues) |
-| `remove(name)` | `(name: string) => void` | Removes the header (see Known Issues) |
-| `get_headers()` | `() => Headers` (alias: `HeaderPair[]`) | Returns all headers as an array of `{ key: ArrayBuffer, value: ArrayBuffer }` |
-| `set_headers(headers)` | `(headers: Headers) => void` | Replaces the full header collection |
+| Method | Signature | Description |
+| -------------------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------- |
+| `get(name)` | `(name: string) => string` | Returns the value of the named header, or empty string if not present |
+| `add(name, value)` | `(name: string, value: string) => void` | Adds a header; multiple calls with the same name produce multiple values |
+| `replace(name, value)` | `(name: string, value: string) => void` | Upserts the header value — creates the header if it does not exist (see Known Issues) |
+| `remove(name)` | `(name: string) => void` | Removes the header (see Known Issues) |
+| `get_headers()` | `() => Headers` (alias: `HeaderPair[]`) | Returns all headers as an array of `{ key: ArrayBuffer, value: ArrayBuffer }` |
+| `set_headers(headers)` | `(headers: Headers) => void` | Replaces the full header collection |
### `Headers` / `HeaderPair` type
diff --git a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-http-call-as.md b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-http-call-as.md
index bc289c7..95ba36c 100644
--- a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-http-call-as.md
+++ b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-http-call-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
## Overview
@@ -321,202 +321,3 @@ Dependencies (from `package.json`):
- SDK API reference — full lifecycle hook signatures, `RootContext` and `Context` base class APIs, all enum values (`WasmResultValues`, `FilterHeadersStatusValues`, `BufferTypeValues`, `LogLevelValues`)
- Platform overview — FastEdge CDN execution model, context lifecycle, how the runtime re-enters hooks after async callbacks
- Best practices — timeout tuning, upstream cluster naming, error response patterns
-
-## Source Material
-
-### FILE: examples/httpCall/assembly/index.ts
-
-```ts
-export * from "@gcoredev/proxy-wasm-sdk-as/assembly/proxy"; // this exports the required functions for the proxy to interact with us.
-import {
- BaseContext,
- BufferTypeValues,
- Context,
- FilterHeadersStatusValues,
- get_buffer_bytes,
- HeaderPair,
- log,
- LogLevelValues,
- makeHeaderPair,
- registerRootContext,
- RootContext,
- send_http_response,
- stream_context,
- WasmResultValues,
-} from "@gcoredev/proxy-wasm-sdk-as/assembly";
-import { setLogLevel } from "@gcoredev/proxy-wasm-sdk-as/assembly/fastedge";
-
-const INTERNAL_SERVER_ERROR: u32 = 500;
-
-function handleHttpCallResponse(
- ctx: BaseContext,
- hdrs: u32,
- bodySize: usize,
- trls: u32,
-): void {
- if (hdrs == 0) {
- log(LogLevelValues.error, "HTTP call failed — no response received");
- return;
- }
-
- const userAgent = stream_context.headers.http_callback.get("user-agent");
- if (userAgent !== "") {
- log(LogLevelValues.info, "User-Agent: " + userAgent);
- }
-
- if (bodySize > 0) {
- const bodyBytes = get_buffer_bytes(
- BufferTypeValues.HttpCallResponseBody,
- 0,
- bodySize as u32,
- );
- const bodyStr = String.UTF8.decode(bodyBytes);
- log(
- LogLevelValues.info,
- "Response body (" + bodySize.toString() + " bytes): " + bodyStr,
- );
- } else {
- log(LogLevelValues.info, "Response body: empty");
- }
-}
-
-class HttpCallRoot extends RootContext {
- createContext(context_id: u32): Context {
- setLogLevel(LogLevelValues.info);
- return new HttpCallContext(context_id, this);
- }
-}
-
-class HttpCallContext extends Context {
- httpCallDispatched: bool = false;
-
- constructor(context_id: u32, root_context: HttpCallRoot) {
- super(context_id, root_context);
- }
-
- onRequestHeaders(a: u32, end_of_stream: bool): FilterHeadersStatusValues {
- // FastEdge re-invokes this hook after the httpCall response is processed.
- // The latch gates re-dispatch so the second invocation returns Continue
- // instead of firing another HTTP call. See SDK docs → Outbound HTTP.
- if (this.httpCallDispatched) {
- log(
- LogLevelValues.info,
- "HTTP call response received, resuming request.",
- );
- return FilterHeadersStatusValues.Continue;
- }
-
- log(LogLevelValues.info, "onRequestHeaders >> dispatching HTTP call");
-
- const headers = new Array();
- headers.push(makeHeaderPair(":scheme", "https"));
- headers.push(makeHeaderPair(":authority", "httpbin.org"));
- headers.push(makeHeaderPair(":path", "/ip"));
- headers.push(makeHeaderPair(":method", "GET"));
- headers.push(makeHeaderPair("User-Agent", "fastedge"));
-
- // 3000ms accommodates cold DNS + variable network conditions; tune per upstream in production.
- const result = (this.root_context as HttpCallRoot).httpCall(
- "httpbin.org",
- headers,
- new ArrayBuffer(0),
- new Array(),
- 3000,
- this,
- handleHttpCallResponse,
- );
-
- if (result != WasmResultValues.Ok) {
- log(
- LogLevelValues.error,
- "Failed to dispatch HTTP call: " + result.toString(),
- );
- send_http_response(
- INTERNAL_SERVER_ERROR,
- "internal server error",
- String.UTF8.encode("Failed to dispatch HTTP call"),
- [],
- );
- return FilterHeadersStatusValues.StopIteration;
- }
-
- this.httpCallDispatched = true;
- log(LogLevelValues.info, "HTTP call dispatched, pausing request");
-
- return FilterHeadersStatusValues.StopIteration;
- }
-}
-
-registerRootContext((context_id: u32) => {
- return new HttpCallRoot(context_id);
-}, "httpCall");
-```
-
-### FILE: examples/httpCall/package.json
-
-```json
-{
- "name": "fastedge-as-example-http-call",
- "version": "1.0.0",
- "description": "FastEdge AssemblyScript example: HTTP Call — async HTTP dispatch with callback",
- "scripts": {
- "asbuild:debug": "asc assembly/index.ts --target debug",
- "asbuild:release": "asc assembly/index.ts --target release",
- "asbuild": "npm run asbuild:debug && npm run asbuild:release"
- },
- "dependencies": {
- "@gcoredev/proxy-wasm-sdk-as": "^1.2.3"
- },
- "devDependencies": {
- "@assemblyscript/wasi-shim": "^0.1.0",
- "assemblyscript": "^0.28.9"
- }
-}
-```
-
-### FILE: examples/httpCall/README.md
-
-```
-[← Back to examples](../README.md)
-
-# HTTP Call
-
-This application makes an asynchronous HTTP call to an external service using the proxy-wasm HTTP dispatch API.
-
-## What it does
-
-In `onRequestHeaders`, the app dispatches an outbound HTTP GET request to `httpbin.org/ip` and pauses the hook (`StopIteration`) until the response arrives. Dispatch is latched on an instance field so the second invocation of the hook (after the response is processed) returns `Continue` instead of dispatching again.
-
-Inside the response callback passed to `httpCall`:
-
-1. Checks whether the call succeeded (a `headers` value of `0` indicates failure — timeout, DNS error, etc.).
-2. Reads and logs the `User-Agent` response header via `stream_context.headers.http_callback`.
-3. Reads and logs the response body via `get_buffer_bytes(BufferTypeValues.HttpCallResponseBody, ...)`.
-
-If the dispatch itself fails (e.g. invalid arguments), the app returns a `500` error to the client.
-
-## Key concepts
-
-- **`httpCall()`** on `RootContext` dispatches an async HTTP request. It accepts a cluster (host), headers, body, trailers, a timeout in milliseconds, the originating context, and a callback.
-- **FastEdge resume model.** Returning `FilterHeadersStatusValues.StopIteration` pauses the hook. The runtime processes the HTTP response, invokes the callback, then **re-invokes `onRequestHeaders` on the same `Context` instance**. The `httpCallDispatched` latch gates re-dispatch so the hook returns `Continue` the second time. This differs from canonical proxy-wasm — calling `continueRequest()` is not required (and has no effect on FastEdge).
-- **`stream_context.headers.http_callback`** provides access to the HTTP call response headers inside the callback. The SDK sets the effective context before the callback fires, so these lookups resolve against the originating request.
-- **`get_buffer_bytes(BufferTypeValues.HttpCallResponseBody, ...)`** reads the response body inside the callback.
-
-## Build
-
-```sh
-pnpm install
-pnpm run asbuild
-```
-
-Build output:
-
-| File | Description |
-|------|-------------|
-| `build/httpCall.wasm` | Optimised release binary — upload this to FastEdge |
-| `build/httpCall-debug.wasm` | Debug binary with source maps |
-
-## Deploy
-
-Upload `build/httpCall.wasm` to the FastEdge portal and attach it to your CDN application. No environment variables or secrets are required.
-```
diff --git a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-kv-store-as.md b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-kv-store-as.md
index 0a8fd7b..8b35bae 100644
--- a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-kv-store-as.md
+++ b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-kv-store-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
diff --git a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-large-dictionary-as.md b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-large-dictionary-as.md
index d1779cc..fe756b2 100644
--- a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-large-dictionary-as.md
+++ b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-large-dictionary-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
# Large Dictionary — AssemblyScript (CDN)
@@ -136,3 +136,129 @@ Upload `build/largeDictionary.wasm` to the FastEdge portal and attach it to your
- `getEnv` — standard WASI environment variable access (under 64 KB)
- proxy-wasm-sdk-as SDK reference — full list of imports from `@gcoredev/proxy-wasm-sdk-as/assembly` and `@gcoredev/proxy-wasm-sdk-as/assembly/fastedge`
- FastEdge CDN app examples (other concepts) — additional CDN app patterns in the fastedge-docs reference
+
+## Source Material
+
+### FILE: examples/largeDictionary/assembly/index.ts
+
+```ts
+export * from "@gcoredev/proxy-wasm-sdk-as/assembly/proxy";
+import {
+ Context,
+ FilterHeadersStatusValues,
+ log,
+ LogLevelValues,
+ registerRootContext,
+ RootContext,
+ stream_context,
+} from "@gcoredev/proxy-wasm-sdk-as/assembly";
+import {
+ getDictionary,
+ setLogLevel,
+} from "@gcoredev/proxy-wasm-sdk-as/assembly/fastedge";
+
+class LargeDictionaryRoot extends RootContext {
+ createContext(context_id: u32): Context {
+ setLogLevel(LogLevelValues.info);
+ return new LargeDictionaryContext(context_id, this);
+ }
+}
+
+class LargeDictionaryContext extends Context {
+ constructor(context_id: u32, root_context: LargeDictionaryRoot) {
+ super(context_id, root_context);
+ }
+
+ onRequestHeaders(a: u32, end_of_stream: bool): FilterHeadersStatusValues {
+ // Use getDictionary for environment variables that may exceed 64KB.
+ // For normal-sized env vars (< 64KB), use getEnv instead.
+ const config = getDictionary("LARGE_CONFIG");
+
+ const size = config.length;
+ log(LogLevelValues.info, "LARGE_CONFIG size: " + size.toString() + " bytes");
+
+ stream_context.headers.request.add("x-config-size", size.toString());
+
+ return FilterHeadersStatusValues.Continue;
+ }
+}
+
+registerRootContext((context_id: u32) => {
+ return new LargeDictionaryRoot(context_id);
+}, "largeDictionary");
+```
+
+
+### FILE: examples/largeDictionary/package.json
+
+```json
+{
+ "name": "fastedge-as-example-large-dictionary",
+ "version": "1.0.0",
+ "description": "FastEdge AssemblyScript example: Large Dictionary — read env vars exceeding the 64KB WASI limit",
+ "scripts": {
+ "asbuild:debug": "asc assembly/index.ts --target debug",
+ "asbuild:release": "asc assembly/index.ts --target release",
+ "asbuild": "npm run asbuild:debug && npm run asbuild:release"
+ },
+ "dependencies": {
+ "@gcoredev/proxy-wasm-sdk-as": "^1.2.3"
+ },
+ "devDependencies": {
+ "@assemblyscript/wasi-shim": "^0.1.0",
+ "assemblyscript": "^0.28.9"
+ }
+}
+```
+
+
+### FILE: examples/largeDictionary/README.md
+
+```
+[← Back to examples](../README.md)
+
+# Large Dictionary
+
+This application demonstrates how to read large environment variables (> 64 KB) using the proxy-wasm dictionary API.
+
+## When to use `getDictionary` vs `getEnv`
+
+| Function | Use when |
+|----------|----------|
+| `getEnv(name)` | Variable value is under 64 KB (most cases) |
+| `getDictionary(name)` | Variable value may exceed the 64 KB WASI env var size limit |
+
+The WASI environment variable interface has a **64 KB size limit** per variable. If your app needs to read larger values (e.g. large JSON configs, PEM certificates, policy documents), use `getDictionary` which calls `proxy_dictionary_get` and bypasses this limit.
+
+For all other environment variable access, prefer `getEnv` as it uses the standard WASI environment interface.
+
+## What it does
+
+In `onRequestHeaders`, the app reads the `LARGE_CONFIG` environment variable using `getDictionary`, logs its size, and adds it as an `x-config-size` request header for the upstream to see.
+
+## Configuration
+
+Set the following on your FastEdge application:
+
+| Name | Type | Description |
+|------|------|-------------|
+| `LARGE_CONFIG` | Environment variable | A large configuration payload (e.g. JSON, PEM certificate) |
+
+## Build
+
+```sh
+pnpm install
+pnpm run asbuild
+```
+
+Build output:
+
+| File | Description |
+|------|-------------|
+| `build/largeDictionary.wasm` | Optimised release binary — upload this to FastEdge |
+| `build/largeDictionary-debug.wasm` | Debug binary with source maps |
+
+## Deploy
+
+Upload `build/largeDictionary.wasm` to the FastEdge portal and attach it to your CDN application. Configure the `LARGE_CONFIG` environment variable in the application settings.
+```
diff --git a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-log-time-as.md b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-log-time-as.md
index c6fc860..1338661 100644
--- a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-log-time-as.md
+++ b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-log-time-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
diff --git a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-properties-as.md b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-properties-as.md
index 137fe6c..94c40a9 100644
--- a/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-properties-as.md
+++ b/plugins/gcore-fastedge/skills/fastedge-docs/reference/cdn/examples-properties-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
# CDN Runtime Properties — AssemblyScript
diff --git a/plugins/gcore-fastedge/skills/fastedge-docs/reference/quickstart-as.md b/plugins/gcore-fastedge/skills/fastedge-docs/reference/quickstart-as.md
index 9078785..fcee56a 100644
--- a/plugins/gcore-fastedge/skills/fastedge-docs/reference/quickstart-as.md
+++ b/plugins/gcore-fastedge/skills/fastedge-docs/reference/quickstart-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
# Quickstart: AssemblyScript CDN Apps on FastEdge
diff --git a/plugins/gcore-fastedge/skills/fastedge-docs/reference/sdk-reference-as.md b/plugins/gcore-fastedge/skills/fastedge-docs/reference/sdk-reference-as.md
index cad60ae..3a8d32e 100644
--- a/plugins/gcore-fastedge/skills/fastedge-docs/reference/sdk-reference-as.md
+++ b/plugins/gcore-fastedge/skills/fastedge-docs/reference/sdk-reference-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
# AssemblyScript Proxy-Wasm SDK Reference
diff --git a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/ab-testing-as.md b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/ab-testing-as.md
index 695920d..2393c2e 100644
--- a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/ab-testing-as.md
+++ b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/ab-testing-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
@@ -222,3 +222,233 @@ pnpm run asbuild
- proxy-wasm-sdk-as assembly API reference
- FastEdge environment variable configuration
- FastEdge CDN application deployment guide
+
+## Source Material
+
+### FILE: examples/abTesting/assembly/index.ts
+
+```ts
+export * from "@gcoredev/proxy-wasm-sdk-as/assembly/proxy";
+import {
+ Context,
+ FilterHeadersStatusValues,
+ get_property,
+ log,
+ LogLevelValues,
+ registerRootContext,
+ RootContext,
+ send_http_response,
+ set_property,
+ stream_context,
+} from "@gcoredev/proxy-wasm-sdk-as/assembly";
+import {
+ getEnv,
+ setLogLevel,
+} from "@gcoredev/proxy-wasm-sdk-as/assembly/fastedge";
+import { getCurrentTime } from "@gcoredev/proxy-wasm-sdk-as/assembly/fastedge/utils/runtime";
+
+class AbTestingRoot extends RootContext {
+ createContext(context_id: u32): Context {
+ setLogLevel(LogLevelValues.info);
+ return new AbTestingContext(context_id, this);
+ }
+}
+
+class AbTestingContext extends Context {
+ constructor(context_id: u32, root_context: AbTestingRoot) {
+ super(context_id, root_context);
+ }
+
+ onRequestHeaders(a: u32, end_of_stream: bool): FilterHeadersStatusValues {
+ const experimentName = getEnv("EXPERIMENT_NAME");
+ if (experimentName === "") {
+ send_http_response(
+ 500,
+ "internal server error",
+ String.UTF8.encode("App misconfigured - EXPERIMENT_NAME must be set"),
+ [],
+ );
+ return FilterHeadersStatusValues.StopIteration;
+ }
+
+ const variantAPath = getEnv("VARIANT_A_PATH");
+ const variantBPath = getEnv("VARIANT_B_PATH");
+ if (variantAPath === "" || variantBPath === "") {
+ send_http_response(
+ 500,
+ "internal server error",
+ String.UTF8.encode(
+ "App misconfigured - VARIANT_A_PATH and VARIANT_B_PATH must be set",
+ ),
+ [],
+ );
+ return FilterHeadersStatusValues.StopIteration;
+ }
+
+ // Check for existing experiment cookie
+ const cookieName = "fe_exp_" + experimentName;
+ const cookieHeader = stream_context.headers.request.get("Cookie");
+ let assignedVariant = this.getCookieValue(cookieHeader, cookieName);
+
+ // Assign variant if not already set
+ if (assignedVariant !== "A" && assignedVariant !== "B") {
+ // Use current time as a simple entropy source for 50/50 split
+ const now = getCurrentTime();
+ assignedVariant = now % 2 == 0 ? "A" : "B";
+ }
+
+ // Rewrite the request path to the variant path
+ const pathArrBuf = get_property("request.path");
+ if (pathArrBuf.byteLength === 0) {
+ return FilterHeadersStatusValues.Continue;
+ }
+ const originalPath = String.UTF8.decode(pathArrBuf);
+ const variantPath = assignedVariant === "A" ? variantAPath : variantBPath;
+ const newPath = variantPath + originalPath;
+
+ // Reconstruct request.url from its decomposed parts rather than splicing
+ // the path out of the full URL — splicing breaks when the path happens to
+ // appear inside the host, and it can silently lose the query string.
+ const schemeBuf = get_property("request.scheme");
+ const hostBuf = get_property("request.host");
+ if (schemeBuf.byteLength > 0 && hostBuf.byteLength > 0) {
+ const scheme = String.UTF8.decode(schemeBuf);
+ const host = String.UTF8.decode(hostBuf);
+ const queryBuf = get_property("request.query");
+ const query = queryBuf.byteLength > 0 ? String.UTF8.decode(queryBuf) : "";
+ const newUrl =
+ scheme + "://" + host + newPath + (query.length > 0 ? "?" + query : "");
+ log(LogLevelValues.info, `A/B routing: ${newUrl}`);
+ set_property("request.url", String.UTF8.encode(newUrl));
+ }
+
+ // Add variant header for upstream visibility
+ stream_context.headers.request.add("X-Experiment", experimentName);
+ stream_context.headers.request.add("X-Variant", assignedVariant);
+
+ log(
+ LogLevelValues.info,
+ `A/B test "${experimentName}": variant ${assignedVariant}, path ${newPath}`,
+ );
+
+ return FilterHeadersStatusValues.Continue;
+ }
+
+ onResponseHeaders(a: u32, end_of_stream: bool): FilterHeadersStatusValues {
+ // Recover the assigned variant from the request header set in onRequestHeaders.
+ // Instance state (this.variant) does not survive the nginx -> core-proxy hop.
+ const variant = stream_context.headers.request.get("X-Variant");
+ if (variant === "") {
+ return FilterHeadersStatusValues.Continue;
+ }
+
+ const experimentName = getEnv("EXPERIMENT_NAME");
+ const cookieName = "fe_exp_" + experimentName;
+
+ // Set the experiment cookie so subsequent requests stick to the same variant
+ stream_context.headers.response.add(
+ "Set-Cookie",
+ cookieName + "=" + variant + "; Path=/; Max-Age=86400; SameSite=Lax",
+ );
+
+ // Add variant as response header for observability
+ stream_context.headers.response.add("X-Variant", variant);
+
+ return FilterHeadersStatusValues.Continue;
+ }
+
+ private getCookieValue(cookieHeader: string, name: string): string {
+ if (cookieHeader === "") return "";
+ const pairs = cookieHeader.split(";");
+ const prefix = name + "=";
+ for (let i = 0; i < pairs.length; i++) {
+ const pair = pairs[i].trim();
+ if (pair.startsWith(prefix)) {
+ return pair.substring(prefix.length);
+ }
+ }
+ return "";
+ }
+}
+
+registerRootContext((context_id: u32) => {
+ return new AbTestingRoot(context_id);
+}, "abTesting");
+```
+
+
+### FILE: examples/abTesting/package.json
+
+```json
+{
+ "name": "fastedge-as-example-ab-testing",
+ "version": "1.0.0",
+ "description": "FastEdge AssemblyScript example: A/B Testing — cookie-based traffic splitting at the CDN layer",
+ "scripts": {
+ "asbuild:debug": "asc assembly/index.ts --target debug",
+ "asbuild:release": "asc assembly/index.ts --target release",
+ "asbuild": "npm run asbuild:debug && npm run asbuild:release"
+ },
+ "dependencies": {
+ "@gcoredev/proxy-wasm-sdk-as": "^1.2.3"
+ },
+ "devDependencies": {
+ "@assemblyscript/wasi-shim": "^0.1.0",
+ "assemblyscript": "^0.28.9"
+ }
+}
+```
+
+
+### FILE: examples/abTesting/README.md
+
+```
+[← Back to examples](../README.md)
+
+# A/B Testing
+
+This application performs cookie-based A/B traffic splitting at the CDN layer, routing requests to different origin paths based on variant assignment.
+
+## What it does
+
+In `onRequestHeaders`, the app:
+
+1. Checks for an existing experiment cookie (`fe_exp_`).
+2. If no cookie is found, assigns the user to variant **A** or **B** (50/50 split).
+3. Rewrites the request path by prepending the variant-specific path prefix (e.g. `/variant-a/original/path`).
+4. Adds `X-Experiment` and `X-Variant` request headers for upstream visibility.
+
+In `onResponseHeaders`, the app sets a `Set-Cookie` header to persist the variant assignment for subsequent requests (24-hour TTL).
+
+> **Note on variant assignment entropy:** New-visitor assignment uses `getCurrentTime() % 2` as a simple 50/50 source. This is illustrative — it is not sticky across two requests that arrive in the same millisecond and is not reproducible in tests. Production A/B implementations typically hash a stable visitor identifier (e.g. client IP or session token) for deterministic, sticky pre-cookie assignment.
+
+## Configuration
+
+Set the following environment variables on your FastEdge application:
+
+| Variable | Example | Description |
+|----------|---------|-------------|
+| `EXPERIMENT_NAME` | `homepage-redesign` | Name of the experiment (required) |
+| `VARIANT_A_PATH` | `/variant-a` | Path prefix for variant A (required) |
+| `VARIANT_B_PATH` | `/variant-b` | Path prefix for variant B (required) |
+
+Your origin server should serve different content at each variant path prefix.
+
+## Build
+
+```sh
+pnpm install
+pnpm run asbuild
+```
+
+Build output:
+
+| File | Description |
+|------|-------------|
+| `build/abTesting.wasm` | Optimised release binary — upload this to FastEdge |
+| `build/abTesting-debug.wasm` | Debug binary with source maps |
+
+## Deploy
+
+Upload `build/abTesting.wasm` to the FastEdge portal and attach it to your CDN application. Configure the experiment environment variables in the application settings.
+```
diff --git a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/api-key-as.md b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/api-key-as.md
index 68c55ab..8a3a3e4 100644
--- a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/api-key-as.md
+++ b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/api-key-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
diff --git a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/auth-jwt-as.md b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/auth-jwt-as.md
index 07cdb77..9da636d 100644
--- a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/auth-jwt-as.md
+++ b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/auth-jwt-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
@@ -267,3 +267,204 @@ pnpm run asbuild
- `@gcoredev/as-jwt` npm package
- proxy-wasm-sdk-as host services reference (getSecret, send_http_response, stream_context)
- cdn-base skeleton blueprint
+
+## Source Material
+
+### FILE: examples/jwt/assembly/index.ts
+
+```ts
+export * from "@gcoredev/proxy-wasm-sdk-as/assembly/proxy"; // this exports the required functions for the proxy to interact with us.
+import {
+ Context,
+ FilterHeadersStatusValues,
+ log,
+ LogLevelValues,
+ registerRootContext,
+ RootContext,
+ send_http_response,
+ stream_context,
+} from "@gcoredev/proxy-wasm-sdk-as/assembly";
+import {
+ getSecret,
+ setLogLevel,
+} from "@gcoredev/proxy-wasm-sdk-as/assembly/fastedge";
+
+import { jwtVerify, JwtValidation } from "@gcoredev/as-jwt/assembly";
+
+const UNAUTHORIZED: u32 = 401;
+const FORBIDDEN: u32 = 403;
+const INTERNAL_SERVER_ERROR: u32 = 500;
+
+class AuthRoot extends RootContext {
+ createContext(context_id: u32): Context {
+ setLogLevel(LogLevelValues.info); // Set log level to info is the default setting. This is purely here for demonstration purposes
+ return new Auth(context_id, this);
+ }
+}
+
+class Auth extends Context {
+ constructor(context_id: u32, root_context: AuthRoot) {
+ super(context_id, root_context);
+ }
+
+ onRequestHeaders(a: u32, end_of_stream: bool): FilterHeadersStatusValues {
+ const secret = getSecret("SECRET");
+ if (!secret) {
+ send_http_response(
+ INTERNAL_SERVER_ERROR,
+ "internal server error",
+ String.UTF8.encode("App misconfigured"),
+ [],
+ );
+ return FilterHeadersStatusValues.StopIteration;
+ }
+
+ const authHeader = stream_context.headers.request.get("Authorization");
+ if (!authHeader) {
+ send_http_response(
+ UNAUTHORIZED,
+ "unauthorized",
+ String.UTF8.encode("No Authorization header"),
+ [],
+ );
+ return FilterHeadersStatusValues.StopIteration;
+ }
+
+ if (!authHeader.startsWith("Bearer ")) {
+ send_http_response(
+ UNAUTHORIZED,
+ "unauthorized",
+ String.UTF8.encode("Authorization header must use Bearer scheme"),
+ [],
+ );
+ return FilterHeadersStatusValues.StopIteration;
+ }
+
+ const token = authHeader.slice(7); // strip "Bearer " prefix
+ if (!token) {
+ send_http_response(
+ UNAUTHORIZED,
+ "unauthorized",
+ String.UTF8.encode("Token not found"),
+ [],
+ );
+ return FilterHeadersStatusValues.StopIteration;
+ }
+
+ // Decode the JWT token
+ const jwtResult = jwtVerify(token, secret);
+ if (jwtResult !== JwtValidation.Ok) {
+ if (jwtResult === JwtValidation.Expired) {
+ log(LogLevelValues.info, "Token Expired");
+ send_http_response(
+ FORBIDDEN,
+ "forbidden",
+ String.UTF8.encode("Expired token"),
+ [],
+ );
+ } else {
+ log(LogLevelValues.info, "Bad Token");
+ send_http_response(
+ FORBIDDEN,
+ "forbidden",
+ String.UTF8.encode("Invalid token"),
+ [],
+ );
+ }
+ return FilterHeadersStatusValues.StopIteration;
+ }
+ return FilterHeadersStatusValues.Continue;
+ }
+}
+
+registerRootContext((context_id: u32) => {
+ return new AuthRoot(context_id);
+}, "auth");
+```
+
+### FILE: examples/jwt/package.json
+
+```json
+{
+ "name": "fastedge-as-example-jwt",
+ "version": "1.0.0",
+ "description": "FastEdge AssemblyScript example: JWT validation",
+ "scripts": {
+ "asbuild:debug": "asc assembly/index.ts --target debug",
+ "asbuild:release": "asc assembly/index.ts --target release",
+ "asbuild": "npm run asbuild:debug && npm run asbuild:release"
+ },
+ "dependencies": {
+ "@gcoredev/as-jwt": "^1.0.3",
+ "@gcoredev/proxy-wasm-sdk-as": "^1.2.3",
+ "assemblyscript-json": "^1.1.0"
+ },
+ "devDependencies": {
+ "@assemblyscript/wasi-shim": "^0.1.0",
+ "assemblyscript": "^0.28.9"
+ }
+}
+```
+
+### FILE: examples/jwt/README.md
+
+```
+[← Back to examples](../README.md)
+
+# JWT Validation
+
+This application validates a JWT Bearer token on every incoming request using the [`@gcoredev/as-jwt`](https://www.npmjs.com/package/@gcoredev/as-jwt) library.
+
+## What it does
+
+In `onRequestHeaders`, the app:
+
+1. Reads the HMAC secret from a FastEdge secret variable named `SECRET`.
+2. Checks that the `Authorization` header uses the `Bearer` scheme; rejects other schemes with `401`.
+3. Verifies the token signature and expiry using `jwtVerify()`.
+4. Allows the request through on a valid token, or returns `401`/`403` on missing, invalid scheme, expired, or invalid tokens.
+
+## Configuration
+
+Set the following secret variable on your FastEdge application:
+
+| Secret | Description |
+| -------- | ------------------------------------------------------------------ |
+| `SECRET` | The HMAC-SHA256 signing secret (at least 256 bits / 32 characters) |
+
+## Testing tokens
+
+**Expired token** (will return `403 Forbidden`):
+
+```
+eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjk3ODMxMDg2MX0.egSSDoDdAHz8Kqee7be9N168CDEwOiOej96Idm2c1yQ
+```
+
+**Valid token** (expiry: 2035-01-01, will return `200 OK`):
+
+```
+eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjIwNTEyMjYwNjF9.zn_pSdcBo8T3SvNgMVYzWc5CU_MKqOlms7TpZXhPtJU
+```
+
+Both tokens use the secret `a-string-secret-at-least-256-bits-long-thats-hard-to-break`.
+
+## Build
+
+```sh
+pnpm install
+pnpm run asbuild
+```
+
+Build output:
+
+| File | Description |
+| ---------------------- | -------------------------------------------------- |
+| `build/jwt.wasm` | Optimised release binary — upload this to FastEdge |
+| `build/jwt-debug.wasm` | Debug binary with source maps |
+
+## Deploy
+
+Upload `build/jwt.wasm` to the FastEdge portal and attach it to your CDN application. Configure the `SECRET` secret variable in the application settings.
+
+For more on secrets and secret rotation slots, see the FastEdge secrets documentation.
+```
diff --git a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/base-as.md b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/base-as.md
index 3f82231..6a43237 100644
--- a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/base-as.md
+++ b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/base-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
@@ -14,8 +14,8 @@ languages: [assemblyscript]
template_origin: cdn-base
source_example: proxy-wasm-sdk-as/examples/helloWorld
source_repo: proxy-wasm-sdk-as
-source_ref: 8e3bb621bc013a0aed7e52122066b417ad62a207
-updated: 2026-08-20
+source_ref: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+updated: 2026-09-22
---
# Base Skeleton: CDN AssemblyScript
@@ -263,118 +263,3 @@ registerRootContext((context_id: u32) => {
- platform-overview (CDN filter pipeline, hook execution order)
- examples-headers-cdn-assemblyscript (header manipulation feature blueprint)
- examples-body-cdn-assemblyscript (body transformation feature blueprint)
-
-## Source Material
-
-### FILE: examples/helloWorld/assembly/index.ts
-
-```ts
-export * from "@gcoredev/proxy-wasm-sdk-as/assembly/proxy"; // this exports the required functions for the proxy to interact with us.
-import {
- Context,
- FilterDataStatusValues,
- FilterHeadersStatusValues,
- log,
- LogLevelValues,
- registerRootContext,
- RootContext,
-} from "@gcoredev/proxy-wasm-sdk-as/assembly";
-
-class HelloWorldRoot extends RootContext {
- createContext(context_id: u32): Context {
- return new HelloWorld(context_id, this);
- }
-}
-
-class HelloWorld extends Context {
- constructor(context_id: u32, root_context: HelloWorldRoot) {
- super(context_id, root_context);
- }
-
- onRequestHeaders(
- headers: u32,
- end_of_stream: bool,
- ): FilterHeadersStatusValues {
- log(LogLevelValues.info, "onRequestHeaders >> Hello World!");
- return FilterHeadersStatusValues.Continue;
- }
-
- onRequestBody(
- body_buffer_length: usize,
- end_of_stream: bool,
- ): FilterDataStatusValues {
- log(LogLevelValues.info, "onRequestBody >> Hello World!");
- return FilterDataStatusValues.Continue;
- }
-
- onResponseHeaders(a: u32, end_of_stream: bool): FilterHeadersStatusValues {
- log(LogLevelValues.info, "onResponseHeaders >> Hello World!");
- return FilterHeadersStatusValues.Continue;
- }
-
- onResponseBody(
- body_buffer_length: usize,
- end_of_stream: bool,
- ): FilterDataStatusValues {
- log(LogLevelValues.info, "onResponseBody >> Hello World!");
- return FilterDataStatusValues.Continue;
- }
-}
-
-registerRootContext((context_id: u32) => {
- return new HelloWorldRoot(context_id);
-}, "helloWorld");
-```
-
-
-### FILE: examples/helloWorld/package.json
-
-```json
-{
- "name": "fastedge-as-example-hello-world",
- "version": "1.0.0",
- "description": "FastEdge AssemblyScript example: Hello World — minimal CDN app skeleton",
- "scripts": {
- "asbuild:debug": "asc assembly/index.ts --target debug",
- "asbuild:release": "asc assembly/index.ts --target release",
- "asbuild": "npm run asbuild:debug && npm run asbuild:release"
- },
- "dependencies": {
- "@gcoredev/proxy-wasm-sdk-as": "^1.2.3"
- },
- "devDependencies": {
- "@assemblyscript/wasi-shim": "^0.1.0",
- "assemblyscript": "^0.28.9"
- }
-}
-```
-
-
-### FILE: examples/helloWorld/asconfig.json
-
-```json
-{
- "extends": "./node_modules/@assemblyscript/wasi-shim/asconfig.json",
- "targets": {
- "debug": {
- "outFile": "build/helloWorld-debug.wasm",
- "textFile": "build/helloWorld-debug.wat",
- "sourceMap": true,
- "debug": true
- },
- "release": {
- "outFile": "build/helloWorld.wasm",
- "textFile": "build/helloWorld.wat",
- "sourceMap": true,
- "optimizeLevel": 3,
- "shrinkLevel": 0,
- "converge": false,
- "noAssert": false
- }
- },
- "options": {
- "bindings": "esm",
- "use": "abort=abort_proc_exit"
- }
-}
-```
diff --git a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/body-as.md b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/body-as.md
index 837648f..89784fd 100644
--- a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/body-as.md
+++ b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/body-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
@@ -360,3 +360,218 @@ Upload `build/body.wasm` via the FastEdge portal and attach it to your CDN appli
- cdn-base skeleton
- FastEdge portal deployment guide
- fastedge-test reference (for testing body manipulation logic)
+
+## Source Material
+
+### FILE: examples/body/assembly/index.ts
+
+```ts
+export * from "@gcoredev/proxy-wasm-sdk-as/assembly/proxy"; // this exports the required functions for the proxy to interact with us.
+import {
+ BufferTypeValues,
+ Context,
+ FilterDataStatusValues,
+ FilterHeadersStatusValues,
+ get_buffer_bytes,
+ get_property,
+ log,
+ LogLevelValues,
+ registerRootContext,
+ RootContext,
+ set_buffer_bytes,
+ set_property,
+ stream_context,
+} from "@gcoredev/proxy-wasm-sdk-as/assembly";
+import { setLogLevel } from "@gcoredev/proxy-wasm-sdk-as/assembly/fastedge";
+
+class HttpBodyRoot extends RootContext {
+ createContext(context_id: u32): Context {
+ setLogLevel(LogLevelValues.info); // Set the log level to info - for more logging reduce this to LogLevelValues.debug
+ return new HttpBody(context_id, this);
+ }
+}
+
+class HttpBody extends Context {
+ constructor(context_id: u32, root_context: HttpBodyRoot) {
+ super(context_id, root_context);
+ }
+
+ onRequestHeaders(
+ headers: u32,
+ end_of_stream: bool
+ ): FilterHeadersStatusValues {
+ log(LogLevelValues.debug, "onRequestHeaders >>");
+ // Remove the "content-length" header
+ stream_context.headers.request.remove("content-length");
+ return FilterHeadersStatusValues.Continue;
+ }
+
+ onRequestBody(
+ body_buffer_length: usize,
+ end_of_stream: bool
+ ): FilterDataStatusValues {
+ log(LogLevelValues.debug, "onRequestBody >>");
+ if (!end_of_stream) {
+ // Wait until the complete body is buffered
+ return FilterDataStatusValues.StopIterationAndBuffer;
+ }
+
+ // Retrieve the body from the HttpRequestBody buffer
+ const bodyBytes = get_buffer_bytes(
+ BufferTypeValues.HttpRequestBody,
+ 0,
+ body_buffer_length
+ );
+
+ if (bodyBytes.byteLength > 0) {
+ const bodyStr = String.UTF8.decode(bodyBytes);
+ log(LogLevelValues.debug, "onRequestBody >> bodyStr: " + bodyStr);
+ if (bodyStr.includes("Client")) {
+ const newBody = `Original message body (${body_buffer_length.toString()} bytes) redacted.\n`;
+ set_buffer_bytes(
+ BufferTypeValues.HttpRequestBody,
+ 0,
+ body_buffer_length,
+ String.UTF8.encode(newBody)
+ );
+ }
+ }
+ return FilterDataStatusValues.Continue;
+ }
+
+ onResponseHeaders(a: u32, end_of_stream: bool): FilterHeadersStatusValues {
+ log(LogLevelValues.debug, "onResponseHeaders >>");
+
+ // Remove "content-length" header as the body size will change
+ stream_context.headers.response.remove("content-length");
+
+ // Set "transfer-encoding" to "chunked"
+ stream_context.headers.response.replace("transfer-encoding", "Chunked");
+
+ const contentType = stream_context.headers.response.get("content-type");
+ if (contentType.length > 0) {
+ set_property("response.content_type", String.UTF8.encode(contentType));
+ }
+
+ return FilterHeadersStatusValues.Continue;
+ }
+
+ onResponseBody(
+ body_buffer_length: usize,
+ end_of_stream: bool
+ ): FilterDataStatusValues {
+ log(LogLevelValues.debug, "onResponseBody >>" + end_of_stream.toString());
+
+ if (!end_of_stream) {
+ // Wait until the complete body is buffered
+ return FilterDataStatusValues.StopIterationAndBuffer;
+ }
+
+ log(
+ LogLevelValues.debug,
+ "onResponseBody >> body_buffer_length: " + body_buffer_length.toString()
+ );
+
+ // Retrieve the request URL
+ const urlBytes = get_property("request.url");
+ const url = urlBytes.byteLength === 0 ? "" : String.UTF8.decode(urlBytes);
+ if (url !== "") {
+ log(LogLevelValues.info, `url=${url}`);
+ }
+
+ // Retrieve the response content type stored in onResponseHeaders
+ const contentTypeBytes = get_property("response.content_type");
+ const contentType =
+ contentTypeBytes.byteLength === 0
+ ? ""
+ : String.UTF8.decode(contentTypeBytes);
+ if (contentType !== "") {
+ log(LogLevelValues.info, `contentType=${contentType}`);
+ }
+
+ // Retrieve the body from the HttpRequestBody buffer
+ const bodyBytes = get_buffer_bytes(
+ BufferTypeValues.HttpResponseBody,
+ 0,
+ body_buffer_length
+ );
+
+ if (bodyBytes.byteLength > 0) {
+ const bodyStr = String.UTF8.decode(bodyBytes);
+ log(LogLevelValues.info, "onResponseBody >> bodyStr: " + bodyStr);
+ }
+ return FilterDataStatusValues.Continue;
+ }
+}
+
+registerRootContext((context_id: u32) => {
+ return new HttpBodyRoot(context_id);
+}, "httpbody");
+```
+
+
+### FILE: examples/body/package.json
+
+```json
+{
+ "name": "fastedge-as-example-body",
+ "version": "1.0.0",
+ "description": "FastEdge AssemblyScript example: Body manipulation",
+ "scripts": {
+ "asbuild:debug": "asc assembly/index.ts --target debug",
+ "asbuild:release": "asc assembly/index.ts --target release",
+ "asbuild": "npm run asbuild:debug && npm run asbuild:release"
+ },
+ "dependencies": {
+ "@gcoredev/proxy-wasm-sdk-as": "^1.2.3"
+ },
+ "devDependencies": {
+ "@assemblyscript/wasi-shim": "^0.1.0",
+ "assemblyscript": "^0.28.9"
+ }
+}
+```
+
+
+### FILE: examples/body/README.md
+
+```
+[← Back to examples](../README.md)
+
+# Body
+
+This application modifies the request and response body using the `onRequestBody` and `onResponseBody` lifecycle hooks.
+
+## What it does
+
+1. **`onRequestHeaders`** — removes the `content-length` header, required because the body content will be altered.
+
+2. **`onRequestBody`** — buffers the full request body then checks whether it contains the word `Client`. If found, the body is replaced with a redaction notice.
+
+3. **`onResponseHeaders`** — removes `content-length`, sets `transfer-encoding: Chunked`, and captures the `content-type` into a runtime property.
+
+4. **`onResponseBody`** — logs the request URL, content type, and full response body once the stream is complete.
+
+This demonstrates the basic flow for body manipulation across all lifecycle hooks. Key points:
+
+- Headers must be adjusted _before_ modifying the body (`content-length`, `transfer-encoding`).
+- The `end_of_stream` flag is checked before processing, allowing the body to be buffered across multiple invocations before acting on it.
+
+## Build
+
+```sh
+pnpm install
+pnpm run asbuild
+```
+
+Build output:
+
+| File | Description |
+| ----------------------- | -------------------------------------------------- |
+| `build/body.wasm` | Optimised release binary — upload this to FastEdge |
+| `build/body-debug.wasm` | Debug binary with source maps |
+
+## Deploy
+
+Upload `build/body.wasm` to the FastEdge portal and attach it to your CDN application.
+```
diff --git a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/cache-control-as.md b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/cache-control-as.md
index f49e70c..63a5c49 100644
--- a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/cache-control-as.md
+++ b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/cache-control-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
@@ -187,3 +187,191 @@ Scripts defined in `package.json`:
- cdn-base skeleton (base class structure and proxy-wasm lifecycle)
- sdk-reference assemblyscript (full API surface for stream_context, get_property, getEnv)
- platform-overview (CDN app deployment and environment variable configuration)
+
+## Source Material
+
+### FILE: examples/cacheControl/assembly/index.ts
+
+```ts
+export * from "@gcoredev/proxy-wasm-sdk-as/assembly/proxy";
+import {
+ Context,
+ FilterHeadersStatusValues,
+ get_property,
+ log,
+ LogLevelValues,
+ registerRootContext,
+ RootContext,
+ stream_context,
+} from "@gcoredev/proxy-wasm-sdk-as/assembly";
+import {
+ getEnv,
+ setLogLevel,
+} from "@gcoredev/proxy-wasm-sdk-as/assembly/fastedge";
+
+class CacheControlRoot extends RootContext {
+ createContext(context_id: u32): Context {
+ setLogLevel(LogLevelValues.info);
+ return new CacheControlContext(context_id, this);
+ }
+}
+
+class CacheControlContext extends Context {
+ constructor(context_id: u32, root_context: CacheControlRoot) {
+ super(context_id, root_context);
+ }
+
+ onResponseHeaders(a: u32, end_of_stream: bool): FilterHeadersStatusValues {
+ const statusBuf = get_property("response.status");
+ let statusCode: u32 = 200;
+ if (statusBuf.byteLength >= 2) {
+ const bytes = Uint8Array.wrap(statusBuf);
+ statusCode = (u32(bytes[0]) << 8) | u32(bytes[1]);
+ }
+
+ // Only cache successful responses
+ if (statusCode < 200 || statusCode >= 400) {
+ stream_context.headers.response.replace(
+ "Cache-Control",
+ "no-store",
+ );
+ return FilterHeadersStatusValues.Continue;
+ }
+
+ // Determine cache policy based on content type
+ const contentType = stream_context.headers.response.get("Content-Type");
+
+ const rawStaticMaxAge = getEnv("STATIC_MAX_AGE");
+ const staticMaxAge = rawStaticMaxAge === "" ? "31536000" : rawStaticMaxAge;
+ const rawHtmlMaxAge = getEnv("HTML_MAX_AGE");
+ const htmlMaxAge = rawHtmlMaxAge === "" ? "3600" : rawHtmlMaxAge;
+ const rawApiMaxAge = getEnv("API_MAX_AGE");
+ const apiMaxAge = rawApiMaxAge === "" ? "0" : rawApiMaxAge;
+
+ let cacheControl: string;
+
+ if (this.isStaticAsset(contentType)) {
+ // Static assets: long cache, immutable
+ cacheControl = "public, max-age=" + staticMaxAge + ", immutable";
+ } else if (contentType.includes("text/html")) {
+ // HTML: short cache, must revalidate
+ cacheControl = "public, max-age=" + htmlMaxAge + ", must-revalidate";
+ stream_context.headers.response.add("Vary", "Accept-Encoding");
+ } else if (
+ contentType.includes("application/json") ||
+ contentType.includes("application/xml")
+ ) {
+ // API responses: configurable, private by default
+ if (apiMaxAge === "0") {
+ cacheControl = "no-cache, no-store, must-revalidate";
+ } else {
+ cacheControl = "private, max-age=" + apiMaxAge + ", must-revalidate";
+ }
+ stream_context.headers.response.add("Vary", "Accept, Authorization");
+ } else {
+ // Default: moderate cache
+ cacheControl = "public, max-age=600";
+ }
+
+ stream_context.headers.response.replace("Cache-Control", cacheControl);
+
+ log(
+ LogLevelValues.info,
+ "Cache-Control: " + cacheControl + " (content-type: " + contentType + ")",
+ );
+
+ return FilterHeadersStatusValues.Continue;
+ }
+
+ private isStaticAsset(contentType: string): bool {
+ return (
+ contentType.includes("image/") ||
+ contentType.includes("font/") ||
+ contentType.includes("application/javascript") ||
+ contentType.includes("text/css") ||
+ contentType.includes("text/javascript") ||
+ contentType.includes("application/wasm")
+ );
+ }
+}
+
+registerRootContext((context_id: u32) => {
+ return new CacheControlRoot(context_id);
+}, "cacheControl");
+```
+
+
+### FILE: examples/cacheControl/package.json
+
+```json
+{
+ "name": "fastedge-as-example-cache-control",
+ "version": "1.0.0",
+ "description": "FastEdge AssemblyScript example: Cache Control — content-type-aware cache headers",
+ "scripts": {
+ "asbuild:debug": "asc assembly/index.ts --target debug",
+ "asbuild:release": "asc assembly/index.ts --target release",
+ "asbuild": "npm run asbuild:debug && npm run asbuild:release"
+ },
+ "dependencies": {
+ "@gcoredev/proxy-wasm-sdk-as": "^1.2.3"
+ },
+ "devDependencies": {
+ "@assemblyscript/wasi-shim": "^0.1.0",
+ "assemblyscript": "^0.28.9"
+ }
+}
+```
+
+
+### FILE: examples/cacheControl/README.md
+
+```
+[← Back to examples](../README.md)
+
+# Cache Control
+
+This application sets `Cache-Control` response headers based on the content type and response status, giving you fine-grained control over CDN caching behaviour.
+
+## What it does
+
+In `onResponseHeaders`, the app inspects the `Content-Type` and `response.status` to apply an appropriate caching policy:
+
+| Content Type | Cache Policy | Default Max-Age |
+|---|---|---|
+| Images, fonts, JS, CSS, WASM | `public, max-age=, immutable` | 1 year (31536000s) |
+| `text/html` | `public, max-age=, must-revalidate` | 1 hour (3600s) |
+| `application/json`, `application/xml` | `private, max-age=, must-revalidate` or `no-cache, no-store` | 0 (no cache) |
+| Other | `public, max-age=600` | 10 minutes |
+| Error responses (4xx/5xx) | `no-store` | — |
+
+Also adds `Vary` headers where appropriate (`Accept-Encoding` for HTML, `Accept, Authorization` for API responses).
+
+## Configuration
+
+All environment variables are optional — sensible defaults are applied when unset.
+
+| Variable | Default | Description |
+|----------|---------|-------------|
+| `STATIC_MAX_AGE` | `31536000` | Max-age for static assets (seconds) |
+| `HTML_MAX_AGE` | `3600` | Max-age for HTML responses (seconds) |
+| `API_MAX_AGE` | `0` | Max-age for API responses (0 = no-cache) |
+
+## Build
+
+```sh
+pnpm install
+pnpm run asbuild
+```
+
+Build output:
+
+| File | Description |
+|------|-------------|
+| `build/cacheControl.wasm` | Optimised release binary — upload this to FastEdge |
+| `build/cacheControl-debug.wasm` | Debug binary with source maps |
+
+## Deploy
+
+Upload `build/cacheControl.wasm` to the FastEdge portal and attach it to your CDN application. Optionally configure the max-age environment variables.
+```
diff --git a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/cors-as.md b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/cors-as.md
index 0d9ab54..18a4276 100644
--- a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/cors-as.md
+++ b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/cors-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
diff --git a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/custom-error-pages-as.md b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/custom-error-pages-as.md
index bb0b1ac..a83ce60 100644
--- a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/custom-error-pages-as.md
+++ b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/custom-error-pages-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
diff --git a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/env-secrets-as.md b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/env-secrets-as.md
index 0a75003..af56b70 100644
--- a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/env-secrets-as.md
+++ b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/env-secrets-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
diff --git a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/geo-redirect-as.md b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/geo-redirect-as.md
index c505afe..35f839a 100644
--- a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/geo-redirect-as.md
+++ b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/geo-redirect-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
@@ -266,3 +266,185 @@ Build outputs:
- platform-overview (runtime properties, Geo-IP data)
- deploy skill reference
- manage skill reference (environment variable configuration)
+
+## Source Material
+
+### FILE: examples/geoRedirect/assembly/index.ts
+
+```ts
+export * from "@gcoredev/proxy-wasm-sdk-as/assembly/proxy"; // this exports the required functions for the proxy to interact with us.
+import {
+ Context,
+ FilterHeadersStatusValues,
+ get_property,
+ log,
+ LogLevelValues,
+ registerRootContext,
+ RootContext,
+ send_http_response,
+ set_property,
+} from "@gcoredev/proxy-wasm-sdk-as/assembly";
+import {
+ getEnv,
+ setLogLevel,
+} from "@gcoredev/proxy-wasm-sdk-as/assembly/fastedge";
+
+const BAD_GATEWAY: u32 = 502;
+const INTERNAL_SERVER_ERROR: u32 = 500;
+
+class GeoRedirectRoot extends RootContext {
+ createContext(context_id: u32): Context {
+ setLogLevel(LogLevelValues.info);
+ return new GeoRedirect(context_id, this);
+ }
+}
+
+class GeoRedirect extends Context {
+ constructor(context_id: u32, root_context: GeoRedirectRoot) {
+ super(context_id, root_context);
+ }
+
+ onRequestHeaders(a: u32, end_of_stream: bool): FilterHeadersStatusValues {
+ log(LogLevelValues.info, "onRequestHeaders >> ");
+
+ const defaultOrigin = getEnv("DEFAULT");
+
+ if (!defaultOrigin) {
+ send_http_response(
+ INTERNAL_SERVER_ERROR,
+ "internal server error",
+ String.UTF8.encode("App misconfigured - DEFAULT must be set"),
+ [],
+ );
+ return FilterHeadersStatusValues.StopIteration;
+ }
+
+ const countryArrBuf = get_property("request.country");
+ if (countryArrBuf.byteLength === 0) {
+ send_http_response(
+ BAD_GATEWAY,
+ "bad gateway",
+ String.UTF8.encode("Missing country information"),
+ [],
+ );
+ return FilterHeadersStatusValues.StopIteration;
+ }
+ const countryCode = String.UTF8.decode(countryArrBuf);
+ const countrySpecificOrigin = getEnv(countryCode);
+
+ log(
+ LogLevelValues.info,
+ `Country code: ( ${countryCode} ): ${
+ countrySpecificOrigin === "" ? "no matching origin" : countrySpecificOrigin
+ }`,
+ );
+
+ const hostArrBuf = get_property("request.host");
+ if (hostArrBuf.byteLength > 0) {
+ const host = String.UTF8.decode(hostArrBuf);
+ log(LogLevelValues.info, `Provided Host: ${host}`);
+ }
+
+ const pathArrBuf = get_property("request.path");
+ if (pathArrBuf.byteLength === 0) {
+ send_http_response(
+ INTERNAL_SERVER_ERROR,
+ "internal server error",
+ String.UTF8.encode("Internal server error - no request path"),
+ [],
+ );
+ return FilterHeadersStatusValues.StopIteration;
+ }
+
+ const path = String.UTF8.decode(pathArrBuf);
+ const origin = countrySpecificOrigin === "" ? defaultOrigin : countrySpecificOrigin;
+ // remove trailing slashes from the origin
+ const cleanedOrigin = origin.endsWith("/") ? origin.slice(0, -1) : origin;
+
+ const requestUrl = `${cleanedOrigin}${path}`;
+
+ log(LogLevelValues.info, `request-url: ${requestUrl}`);
+
+ set_property("request.url", String.UTF8.encode(requestUrl));
+
+ return FilterHeadersStatusValues.Continue;
+ }
+}
+
+registerRootContext((context_id: u32) => {
+ return new GeoRedirectRoot(context_id);
+}, "geoRedirect");
+```
+
+
+### FILE: examples/geoRedirect/package.json
+
+```json
+{
+ "name": "fastedge-as-example-georedirect",
+ "version": "1.0.0",
+ "description": "FastEdge AssemblyScript example: Geo Redirect",
+ "scripts": {
+ "asbuild:debug": "asc assembly/index.ts --target debug",
+ "asbuild:release": "asc assembly/index.ts --target release",
+ "asbuild": "npm run asbuild:debug && npm run asbuild:release"
+ },
+ "dependencies": {
+ "@gcoredev/proxy-wasm-sdk-as": "^1.2.3"
+ },
+ "devDependencies": {
+ "@assemblyscript/wasi-shim": "^0.1.0",
+ "assemblyscript": "^0.28.9"
+ }
+}
+```
+
+
+### FILE: examples/geoRedirect/README.md
+
+```
+[← Back to examples](../README.md)
+
+# Geo Redirect
+
+This application redirects requests to different origin URLs based on the client's country code.
+
+## What it does
+
+In `onRequestHeaders`, the app reads the client's country code from the `request.country` runtime property (populated by FastEdge's Geo-IP data) and looks up a matching environment variable by that country code. The `request.url` runtime property is then set to route the upstream fetch to the corresponding origin.
+
+- If a country-specific origin is configured (e.g. env var `DE=https://de.example.com`), the request is routed there.
+- Otherwise it falls back to the `DEFAULT` origin.
+- Uses [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country codes.
+
+> **Routing mechanism:** This is not an HTTP redirect (no `Location` header, no 302 response). Setting `request.url` rewrites the upstream fetch target transparently — the client sees a normal 200 response from the matched origin.
+
+> **Observability:** The app logs the country code, matched origin, and final request URL at INFO level, visible in the FastEdge application logs.
+
+## Configuration
+
+Set the following environment variables on your FastEdge application:
+
+| Variable | Example | Description |
+|----------|---------|-------------|
+| `DEFAULT` | `https://origin.example.com` | Fallback origin URL (required) |
+| `` | `DE=https://de.example.com` | Per-country origin URL (optional, one per country) |
+
+## Build
+
+```sh
+pnpm install
+pnpm run asbuild
+```
+
+Build output:
+
+| File | Description |
+|------|-------------|
+| `build/geoRedirect.wasm` | Optimised release binary — upload this to FastEdge |
+| `build/geoRedirect-debug.wasm` | Debug binary with source maps |
+
+## Deploy
+
+Upload `build/geoRedirect.wasm` to the FastEdge portal and attach it to your CDN application. Configure the `DEFAULT` environment variable and any per-country overrides in the application settings.
+```
diff --git a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/geoblock-as.md b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/geoblock-as.md
index 2593263..2b9a311 100644
--- a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/geoblock-as.md
+++ b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/geoblock-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
@@ -158,10 +158,10 @@ registerRootContext((context_id: u32) => {
Both blocked and allowed requests are logged at `INFO` level, providing an audit trail for all traffic decisions.
-| Event | Log message |
-| ---------------- | ------------------------------------------------ |
-| Request blocked | `"geoBlock: blocked request from " + countryStr` |
-| Request allowed | `"geoBlock: allowed request from " + countryStr` |
+| Event | Log message |
+| --------------- | ------------------------------------------------ |
+| Request blocked | `"geoBlock: blocked request from " + countryStr` |
+| Request allowed | `"geoBlock: allowed request from " + countryStr` |
Log level is set via `setLogLevel(LogLevelValues.info)` inside `createContext`.
diff --git a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/headers-as.md b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/headers-as.md
index 0c7d16c..c68f1a6 100644
--- a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/headers-as.md
+++ b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/headers-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
diff --git a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/http-call-as.md b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/http-call-as.md
index 786000c..410be81 100644
--- a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/http-call-as.md
+++ b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/http-call-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
diff --git a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/kv-store-as.md b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/kv-store-as.md
index 427c7fe..ecbd5ed 100644
--- a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/kv-store-as.md
+++ b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/kv-store-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
diff --git a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/large-dictionary-as.md b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/large-dictionary-as.md
index aaa3722..bb51254 100644
--- a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/large-dictionary-as.md
+++ b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/large-dictionary-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
@@ -138,129 +138,3 @@ No additional dependencies beyond the cdn-base skeleton.
- cdn-base skeleton reference
- proxy-wasm-sdk-as SDK reference (AssemblyScript)
- platform-overview reference for environment variable configuration
-
-## Source Material
-
-### FILE: examples/largeDictionary/assembly/index.ts
-
-```ts
-export * from "@gcoredev/proxy-wasm-sdk-as/assembly/proxy";
-import {
- Context,
- FilterHeadersStatusValues,
- log,
- LogLevelValues,
- registerRootContext,
- RootContext,
- stream_context,
-} from "@gcoredev/proxy-wasm-sdk-as/assembly";
-import {
- getDictionary,
- setLogLevel,
-} from "@gcoredev/proxy-wasm-sdk-as/assembly/fastedge";
-
-class LargeDictionaryRoot extends RootContext {
- createContext(context_id: u32): Context {
- setLogLevel(LogLevelValues.info);
- return new LargeDictionaryContext(context_id, this);
- }
-}
-
-class LargeDictionaryContext extends Context {
- constructor(context_id: u32, root_context: LargeDictionaryRoot) {
- super(context_id, root_context);
- }
-
- onRequestHeaders(a: u32, end_of_stream: bool): FilterHeadersStatusValues {
- // Use getDictionary for environment variables that may exceed 64KB.
- // For normal-sized env vars (< 64KB), use getEnv instead.
- const config = getDictionary("LARGE_CONFIG");
-
- const size = config.length;
- log(LogLevelValues.info, "LARGE_CONFIG size: " + size.toString() + " bytes");
-
- stream_context.headers.request.add("x-config-size", size.toString());
-
- return FilterHeadersStatusValues.Continue;
- }
-}
-
-registerRootContext((context_id: u32) => {
- return new LargeDictionaryRoot(context_id);
-}, "largeDictionary");
-```
-
-
-### FILE: examples/largeDictionary/package.json
-
-```json
-{
- "name": "fastedge-as-example-large-dictionary",
- "version": "1.0.0",
- "description": "FastEdge AssemblyScript example: Large Dictionary — read env vars exceeding the 64KB WASI limit",
- "scripts": {
- "asbuild:debug": "asc assembly/index.ts --target debug",
- "asbuild:release": "asc assembly/index.ts --target release",
- "asbuild": "npm run asbuild:debug && npm run asbuild:release"
- },
- "dependencies": {
- "@gcoredev/proxy-wasm-sdk-as": "^1.2.3"
- },
- "devDependencies": {
- "@assemblyscript/wasi-shim": "^0.1.0",
- "assemblyscript": "^0.28.9"
- }
-}
-```
-
-
-### FILE: examples/largeDictionary/README.md
-
-```
-[← Back to examples](../README.md)
-
-# Large Dictionary
-
-This application demonstrates how to read large environment variables (> 64 KB) using the proxy-wasm dictionary API.
-
-## When to use `getDictionary` vs `getEnv`
-
-| Function | Use when |
-|----------|----------|
-| `getEnv(name)` | Variable value is under 64 KB (most cases) |
-| `getDictionary(name)` | Variable value may exceed the 64 KB WASI env var size limit |
-
-The WASI environment variable interface has a **64 KB size limit** per variable. If your app needs to read larger values (e.g. large JSON configs, PEM certificates, policy documents), use `getDictionary` which calls `proxy_dictionary_get` and bypasses this limit.
-
-For all other environment variable access, prefer `getEnv` as it uses the standard WASI environment interface.
-
-## What it does
-
-In `onRequestHeaders`, the app reads the `LARGE_CONFIG` environment variable using `getDictionary`, logs its size, and adds it as an `x-config-size` request header for the upstream to see.
-
-## Configuration
-
-Set the following on your FastEdge application:
-
-| Name | Type | Description |
-|------|------|-------------|
-| `LARGE_CONFIG` | Environment variable | A large configuration payload (e.g. JSON, PEM certificate) |
-
-## Build
-
-```sh
-pnpm install
-pnpm run asbuild
-```
-
-Build output:
-
-| File | Description |
-|------|-------------|
-| `build/largeDictionary.wasm` | Optimised release binary — upload this to FastEdge |
-| `build/largeDictionary-debug.wasm` | Debug binary with source maps |
-
-## Deploy
-
-Upload `build/largeDictionary.wasm` to the FastEdge portal and attach it to your CDN application. Configure the `LARGE_CONFIG` environment variable in the application settings.
-```
diff --git a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/properties-as.md b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/properties-as.md
index 3761f34..416db62 100644
--- a/plugins/gcore-fastedge/skills/scaffold/reference/cdn/properties-as.md
+++ b/plugins/gcore-fastedge/skills/scaffold/reference/cdn/properties-as.md
@@ -3,8 +3,8 @@
sources:
- id: proxy-wasm-sdk-as
ref: master
- commit: 8e3bb621bc013a0aed7e52122066b417ad62a207
- updated: 2026-08-20
+ commit: ddb8d2e76e8f4fab30de6bce8dd0150231cd7d31
+ updated: 2026-09-22
-->
---
@@ -302,9 +302,12 @@ class Properties extends Context {
}
onRequestHeaders(a: u32, end_of_stream: bool): FilterHeadersStatusValues {
+ // Error codes 551–559 identify the absent property via the HTTP response status:
+ // 551=uri 552=host 553=path 554=scheme 555=extension 556=query 557=x_real_ip 558=country 559=city
if (!this.handleProperty(REQUEST_URI, 551, "uri", "request-uri")) {
return FilterHeadersStatusValues.StopIteration;
}
+ // host must be present for upstream routing; validated but not logged or exposed as a response header
if (!this.handleProperty(REQUEST_HOST, 552)) {
return FilterHeadersStatusValues.StopIteration;
}