Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- id: fastedge-sdk-js
ref: main
commit: 81145a9a43ec499240c687bd49376ab20c72b11c
updated: 2026-07-23
updated: 2026-08-17
-->

# A/B Testing — FastEdge Example
Expand Down Expand Up @@ -207,3 +207,121 @@ Iterates each test in `testConfig`, maps `xid * 100` into the normalized variant
- **`xid` range**: `Math.random()` can return `0` but not `1`. `slice(1, 5)` on `"0.473..."` yields `".473"` — always a 4-character string starting with `.`.
- **Empty cookie after strip**: If `x-fastedge-abid` was the only cookie, the `cookie` header is deleted entirely rather than set to an empty string.
- **Last variant not assigned**: If `xid * 100` falls exactly at or beyond the sum of all normalized percentages (due to floating-point rounding), no variant header is set for that test. In practice this is extremely rare given `xid` is always `< 1`.

## Source Material

### FILE: examples/ab-testing/src/index.js

```js
import { getEnv } from 'fastedge::env';

const testConfig = {
logo: [
{ variant: 'hops', weight: 50 },
{ variant: 'bottle', weight: 50 },
],
font: [
{ variant: 'exo2', weight: 40 },
{ variant: 'gloria', weight: 65 },
{ variant: 'standard', weight: 45 },
],
};

async function eventHandler({ request }) {
const [xid, slicedHeaders] = sliceAbTestIdFromCookie(request);

const headers = createAbTestHeaders(slicedHeaders, testConfig, xid);

// This is the URL of the outbound service - i.e. could be a url to your origin
// e.g. https://template-invoice-ab-test-123456.fastedge.cdn.gc.onl/
const outboundUrl = getEnv('OUTBOUND_URL');
if (!outboundUrl || !String(outboundUrl).trim()) {
return new Response('OUTBOUND_URL environment variable is not configured', {
status: 500,
});
}

const response = await fetch(outboundUrl, { headers });

// Request/Response Headers are immutable, so we need to create a new Headers object
const resHeaders = new Headers(response.headers);
resHeaders.set(
'set-cookie',
`x-fastedge-abid=${xid}; Max-Age=31536000; Path=/; Secure; HttpOnly; SameSite=Lax;`,
);

return new Response(response.body, {
status: response.status,
headers: resHeaders,
});
}

addEventListener('fetch', (event) => {
event.respondWith(eventHandler(event));
});

const sliceAbTestIdFromCookie = ({ headers: reqHeaders }) => {
// Request/Response Headers are immutable, so we need to create a new Headers object
const headers = new Headers(reqHeaders);
const cookie = headers.get('cookie') || '';
// Read the existing `xid` cookie value.
const xid = (cookie.match(/(?:^|;) *x-fastedge-abid=((0|1|)\.\d+) *(?:;|$)/u) || [])[1];
if (xid) {
// Request contains A/B cookie, hide it from the origin
const newCookie = cookie.replace(/x-fastedge-abid=[^;]+;?\s*/gu, '');
if (newCookie) {
headers.set('cookie', newCookie);
} else {
headers.delete('cookie');
}
return [xid, headers];
}
const randomXid = `${Math.random()}`.slice(1, 5);
// Request does not contain A/B cookie, return random number
return [randomXid, headers];
};

const forceWeightsToPercentages = (testValues) => {
const total = testValues.reduce((acc, { weight }) => acc + weight, 0);
return testValues.map(({ variant, weight }) => ({
variant,
percentage: (weight / total) * 100,
}));
};

const createAbTestHeaders = (reqHeaders, testConfig, xid) => {
const headers = new Headers(reqHeaders);
for (const testName of Object.keys(testConfig)) {
const xidPercentage = Number.parseFloat(xid) * 100;
const testValues = forceWeightsToPercentages(testConfig[testName]);
let start = 0;
for (const { variant, percentage } of testValues) {
const end = start + percentage;
if (xidPercentage >= start && xidPercentage < end) {
headers.set(`ab-test-${testName}`, variant);
break;
}
start = end;
}
}
return headers;
};
```

### FILE: examples/ab-testing/package.json

```json
{
"name": "fastedge-example-ab-testing",
"version": "1.0.0",
"description": "FastEdge JS example: cookie-based A/B testing",
"type": "module",
"main": "src/index.js",
"scripts": {
"build": "fastedge-build src/index.js dist/ab-testing.wasm"
},
"dependencies": {
"@gcoredev/fastedge-sdk-js": "^2.3.0"
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- id: fastedge-sdk-js
ref: main
commit: 81145a9a43ec499240c687bd49376ab20c72b11c
updated: 2026-07-23
updated: 2026-08-17
-->

# Authentication Patterns (JavaScript)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- id: fastedge-sdk-js
ref: main
commit: 81145a9a43ec499240c687bd49376ab20c72b11c
updated: 2026-07-23
updated: 2026-08-17
-->

# FastEdge Cache — JavaScript Examples
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- id: fastedge-sdk-js
ref: main
commit: 81145a9a43ec499240c687bd49376ab20c72b11c
updated: 2026-07-23
updated: 2026-08-17
-->

## fetch — Outbound HTTP Requests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- id: fastedge-sdk-js
ref: main
commit: 81145a9a43ec499240c687bd49376ab20c72b11c
updated: 2026-07-23
updated: 2026-08-17
-->

## Example: Geo-Redirect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- id: fastedge-sdk-js
ref: main
commit: 81145a9a43ec499240c687bd49376ab20c72b11c
updated: 2026-07-23
updated: 2026-08-17
-->

## Headers Example — FastEdge JS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- id: fastedge-sdk-js
ref: main
commit: 81145a9a43ec499240c687bd49376ab20c72b11c
updated: 2026-07-23
updated: 2026-08-17
-->

# Hono Patterns on FastEdge (JavaScript/TypeScript)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- id: fastedge-sdk-js
ref: main
commit: 81145a9a43ec499240c687bd49376ab20c72b11c
updated: 2026-07-23
updated: 2026-08-17
-->

## KV Store — Example Reference
Expand Down Expand Up @@ -132,6 +132,14 @@ Validation is performed by `validateQueryParams(queryParams: URLSearchParams)` i
- `min` and `max` are required for: `zrange`.
- `item` is required for: `bfExists`.

**Type definitions (from `utils.ts`):**
```ts
const ALL_ACTIONS = ['get', 'scan', 'zscan', 'zrange', 'bfExists'] as const;
export type Action = (typeof ALL_ACTIONS)[number];
type ParamKey = 'action' | 'store' | 'key' | 'match' | 'min' | 'max' | 'item' | 'error';
type Params = { [key in ParamKey]: string };
```

---

### Error Handling
Expand Down Expand Up @@ -246,10 +254,31 @@ addEventListener('fetch', (event: FetchEvent) => {

Decodes an `ArrayBuffer` to a UTF-8 string. Returns `''` if `arrVal` is `null`.

```ts
export const decodeValueArray = (arrVal: ArrayBuffer | null) => {
if (arrVal) {
const decoder = new TextDecoder();
return decoder.decode(arrVal);
}
return '';
};
```

#### `stringifyValueScoreTuples(tupleList: Array<[ArrayBuffer, number]>): string`

Formats sorted-set result tuples as a string: `[{ Value: <decoded>, Score: <number> }, ...]`.

```ts
export const stringifyValueScoreTuples = (tupleList: Array<[ArrayBuffer, number]>): string => {
let strResponse = '[';
for (const tuple of tupleList) {
strResponse += `{ Value: ${decodeValueArray(tuple[0])}, Score: ${tuple[1]} }, `;
}
strResponse += ']';
return strResponse;
};
```

---

### Build Configuration
Expand Down Expand Up @@ -299,3 +328,4 @@ TypeScript types for FastEdge globals (`FetchEvent`, etc.) are provided by `@gco
- `"type": "module"` must be set in `package.json` for ESM compatibility with `fastedge-build`.
- The SDK dependency version is `^2.3.0`.
- `tsconfig.json` `target` is `ES2023`; `moduleResolution` is `Bundler`; `lib` is `["ES2023"]`; `types` is `["@gcoredev/fastedge-sdk-js"]`.
- Empty string values for required query parameters are treated as missing — validation rejects them the same as absent params.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- id: fastedge-sdk-js
ref: main
commit: 81145a9a43ec499240c687bd49376ab20c72b11c
updated: 2026-07-23
updated: 2026-08-17
-->

# Proxy and Response Transform Patterns (JavaScript/TypeScript)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- id: fastedge-sdk-js
ref: main
commit: 81145a9a43ec499240c687bd49376ab20c72b11c
updated: 2026-07-23
updated: 2026-08-17
-->

# FastEdge JS Runtime — Constraints & Compatibility
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- id: fastedge-sdk-js
ref: main
commit: 81145a9a43ec499240c687bd49376ab20c72b11c
updated: 2026-07-23
updated: 2026-08-17
-->

# FastEdge JavaScript Quickstart
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- id: fastedge-sdk-js
ref: main
commit: 81145a9a43ec499240c687bd49376ab20c72b11c
updated: 2026-07-23
updated: 2026-08-17
-->

# JavaScript SDK Reference (`@gcoredev/fastedge-sdk-js`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- id: fastedge-sdk-js
ref: main
commit: 81145a9a43ec499240c687bd49376ab20c72b11c
updated: 2026-07-23
updated: 2026-08-17
-->

## fastedge-build CLI Reference
Expand Down
Loading