Skip to content
Open
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
13 changes: 13 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
root = true

[*]
charset = utf-8
end_of_line = lf
indent_style = tab
indent_size = 1
insert_final_newline = true
trim_trailing_whitespace = true

[*.{json,yml,yaml,md}]
indent_style = space
indent_size = 2
38 changes: 38 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: CI

on:
push:
branches: [master]
pull_request:

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
with:
version: 10

- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- run: pnpm install --frozen-lockfile

- name: Lint
run: pnpm lint

- name: Typecheck
run: pnpm typecheck

- name: Build
run: pnpm build

- name: Confirm bundle exists
run: test -s dist/index.js
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
dist/
node_modules/

# Lock files — we ship pnpm-lock.yaml but also welcome yarn / npm
# contributors; the build doesn't depend on a specific manager.
.pnp.*
.yarn/
yarn-error.log
99 changes: 88 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,95 @@
# Beak variable extension
# Beak variable extension template

**Note: The Beak Extensions API is still in an experimental phase, and so are the docs.**
A starting point for a [Beak](https://getbeak.app) realtime-value
variable extension. Fork, rename, replace the example variable with
yours, and ship.

This is a template repo to help you get started building a variable extension for Beak.
The template targets the v2 extension SDK
(`@getbeak/extension-sdk@^0.3.0`) and ships an `apiVersion: 2`
manifest. For older Beak releases on `apiVersion: 1`, fork from the
[`v1` tag](https://github.com/getbeak/extension-variable-template/releases)
instead.

## About Beak

Beak is a cross-platform API crafting tool that makes it easy to build and test API's across platforms and teams.
Beak is a cross-platform API crafting tool for building and testing
APIs across platforms and teams. Extensions plug into its
realtime-value system — anywhere a string can go (URL, headers, body,
form fields, variable-set values), a variable extension can compute it.

## Getting started
## Quickstart

- Clone this repo wherever you want
- Make the whatever changes you want
- Update the name of the extension in the `package.json`
- Add this package as a dependency in the `extensions` folder of your Beak project
- Run `yarn`
- Restart Beak
```sh
git clone https://github.com/getbeak/extension-variable-template.git my-extension
cd my-extension
pnpm install
pnpm build
```

The bundle lands at `dist/index.js`. Beak loads that file when it sees
this package in your project's `extensions/` folder.

### What to change

1. **Rename in `package.json`** — set `name`, `description`, `author`, etc.
2. **Rewrite `src/index.ts`** — the example variable computes square
roots. Replace it with your own. One package can contribute multiple
variables; add more `defineVariable(...)` entries to the `variables`
array.
3. **Build** — `pnpm build`.
4. **Install into a Beak project** — add this package as a dependency
of your project's `extensions/` folder. Beak picks the change up
live; no restart needed.

## The v2 contract in one sentence

Each variable exposes one `resolve(extCtx, ctx, payload)` callback that
returns a `ResolvedValue`:

```ts
type ResolvedValue =
| { kind: 'text'; text: string; contentType?: string }
| { kind: 'bytes'; bytes: Uint8Array; contentType?: string }
| { kind: 'asset'; ref: AssetRef }
| { kind: 'stream'; stream: ReadableStream<Uint8Array>; size?: number; contentType?: string };
```

The renderer coerces between any kind and the consumer's expected sink
(`text` / `binary` / `stream`), so most variables can just return text.
The `ctx.sink.kind` parameter is there if you want to optimise — e.g. a
file-reading variable returning `{ kind: 'asset' }` for a binary sink
skips a UTF-8 round-trip.

See the [extension SDK migration guide](https://github.com/getbeak/beak/blob/master/docs/extension-sdk-v2-migration.md)
for the full surface (sinks, editors, sandbox model).

## Scripts

| Script | Purpose |
| ---------------- | -------------------------------------------------------------------- |
| `pnpm build` | Clean, type-check, then bundle `src/index.ts` to `dist/index.js`. |
| `pnpm typecheck` | `tsc --noEmit` against `tsconfig.json`. |
| `pnpm lint` | Biome lint. |
| `pnpm format` | Biome auto-format. |
| `pnpm check` | Biome lint + format + organize imports, applies fixes. |

## Sandbox model

Extensions run in an isolated V8 context (or a Web Worker on the web
shell). The only host-provided functionality is what arrives on the
`extCtx` argument — `log` for structured logging, `parseValueSections`
to recurse into Beak's variable system. Extensions have **no**
filesystem, network, `process`, or `require` access. Write pure
functions; Beak provides the IO.

## Links

- Beak — https://getbeak.app
- Docs — https://docs.getbeak.app
- [`@getbeak/extension-sdk`](https://www.npmjs.com/package/@getbeak/extension-sdk)
- [Example extension (Lorem Swiftsum)](https://github.com/getbeak/extension-variable-lorem-swiftsum)
- [SDK migration guide](https://github.com/getbeak/beak/blob/master/docs/extension-sdk-v2-migration.md)

## License

MIT
47 changes: 47 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.16/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"includes": ["src/**/*.ts", "*.json"]
},
"formatter": {
"enabled": true,
"indentStyle": "tab",
"indentWidth": 1,
"lineWidth": 120,
"lineEnding": "lf"
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"jsxQuoteStyle": "single",
"trailingCommas": "all",
"semicolons": "always",
"arrowParentheses": "asNeeded"
}
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"correctness": {
"noUnusedImports": "error",
"noUnusedVariables": "error"
},
"style": {
"useImportType": "error",
"useTemplate": "warn"
},
"suspicious": {
"noConsole": {
"level": "error",
"options": { "allow": ["info", "warn", "error"] }
}
}
}
}
}
57 changes: 40 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,42 @@
{
"name": "@getbeak/extension-variable-template",
"version": "1.0.0",
"main": "dist/index.js",
"beakExtensionType": "variable",
"author": "Alexander Forbes-Reed <git@alex.forbes.red>",
"license": "MIT",
"type": "module",
"devDependencies": {
"@getbeak/types": "^2.1.0",
"@getbeak/types-variables": "^2.1.0",
"esbuild": "^0.24.0",
"typescript": "^5.6.3"
},
"scripts": {
"build": "esbuild src/index.ts --outfile=dist/index.js --bundle --target=node14 --minify --platform=node",
"prebuild": "tsc --noEmit"
}
"name": "@getbeak/extension-variable-template",
"version": "2.0.0",
"description": "Template for a Beak variable extension. Fork and rename to start your own.",
"main": "dist/index.js",
"beakExtensionType": "variable",
"beak": {
"apiVersion": 2
},
"private": true,
"author": "Alexander Forbes-Reed <git@alex.forbes.red>",
"license": "MIT",
"type": "module",
"keywords": [
"beak",
"beak-extension",
"variable",
"realtime-value",
"template"
],
"repository": {
"type": "git",
"url": "https://github.com/getbeak/extension-variable-template.git"
},
"files": [
"dist"
],
"scripts": {
"build": "pnpm clean && pnpm typecheck && esbuild src/index.ts --outfile=dist/index.js --bundle --target=es2022 --format=cjs --platform=neutral --main-fields=main",
"clean": "rm -rf dist",
"typecheck": "tsc --noEmit",
"lint": "biome lint .",
"format": "biome format --write .",
"check": "biome check --write ."
},
"devDependencies": {
"@biomejs/biome": "^2.4.16",
"@getbeak/extension-sdk": "^0.3.0",
"esbuild": "^0.24.0",
"typescript": "^5.6.3"
}
}
99 changes: 66 additions & 33 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,73 @@
import type { ValueSections } from '@getbeak/types/values';
import type { EditableVariable } from '@getbeak/types-variables';
/**
* Beak variable extension template.
*
* Replace the variable below with your own. One extension can contribute
* multiple variables — just add more `defineVariable(...)` entries to the
* `variables` array.
*
* Quick reference:
*
* - `id` is the stable identifier inside this package. Combined with the
* package name to form the fully-qualified type Beak uses internally
* (`external:<package-name>/<id>`). Renaming it is a breaking change
* for any project referencing the variable.
*
* - `resolve(extCtx, ctx, payload)` is the single hook Beak calls when
* it needs the variable's value. Return one of:
* - `{ kind: 'text', text }` ← string output
* - `{ kind: 'bytes', bytes, contentType? }` ← in-memory binary
* - `{ kind: 'asset', ref }` ← content-addressed file
* - `{ kind: 'stream', stream, size?, contentType? }` ← ReadableStream
*
* The renderer coerces between these and the consumer's sink kind
* (text / binary / stream), so most variables can just return text.
*
* - `extCtx` provides host helpers — `log()` and
* `parseValueSections()` (recurse into Beak's variable system).
* `ctx.variableContext` carries the project tree, variable sets,
* flight history, and the current request id. `ctx.sink.kind` tells
* you what shape the consumer wants if you can optimise for it.
*
* - `editor` is optional. When present, double-clicking the variable
* blob in the editor opens the popover you describe.
*/

interface Payload {
import type { ValueSections } from '@getbeak/extension-sdk';
import { defineExtension, defineVariable } from '@getbeak/extension-sdk';

interface SquareRootPayload {
value: ValueSections;
}

const extension: EditableVariable<Payload, Payload> = {
name: 'Square root',
description: 'Calculates the square root of a number.',
sensitive: false,
attributes: {
requiresRequestId: false,
},

createDefaultPayload: async () => ({ value: [] }),
getValue: async (ctx, payload) => {
// Use the BeakAPI to take the array of value parts, and parse them into a string
const parsed = await beakApi.parseValueSections(ctx, payload.value);

// Take the parsed string and convert it to an integer
const integer = parseInt(parsed, 10) || 0;

// Calculate the square root
return Math.sqrt(integer).toString(10);
},
export default defineExtension({
variables: [
defineVariable<SquareRootPayload>({
id: 'squareRoot',
name: 'Square root',
description: 'Calculates the square root of a number.',
keywords: ['math', 'sqrt', 'square', 'root'],

editor: {
createUserInterface: async () => [{
type: 'value_parts_input',
stateBinding: 'value',
label: 'What do you want to square root?',
}],
createDefaultPayload: () => ({ value: [] }),

load: async (_ctx, payload) => payload,
save: async (_ctx, _existingPayload, state) => state,
},
};
resolve: async (extCtx, ctx, payload) => {
// `parseValueSections` recurses into Beak's variable system so
// the inner value can itself contain other variables.
const parsed = await extCtx.parseValueSections(ctx.variableContext, payload.value);
const integer = Number.parseInt(parsed, 10) || 0;
return { kind: 'text', text: Math.sqrt(integer).toString(10) };
},

export default extension;
editor: {
createUserInterface: () => [
{
type: 'value_parts_input',
stateBinding: 'value',
label: 'What do you want to square root?',
},
],
load: (_extCtx, _ctx, payload) => payload,
save: (_extCtx, _ctx, _existing, state) => state,
},
}),
],
});
Loading
Loading