server.ts135 lines · main
1// Runs inside the Deno isolate. Materialized by host into
2// /tmp/briven-isolate-<id>/.briven-runtime/server.ts. Customers reach
3// this via `import { query } from '@briven/cli/server'` resolved through
4// the host-controlled import-map.json.
5
6import { runQuery, type Ctx } from './loop.ts';
7
8export type { Ctx };
9
10/**
11 * Wrap a customer query function. Customer code does:
12 *
13 * import { query } from '@briven/cli/server';
14 * export const poolStats = query(async (ctx, args) => {
15 * const pools = await ctx.db('pools').select();
16 * return { count: pools.length };
17 * });
18 *
19 * The wrapper is mostly a marker — the dispatcher in __entry.ts already
20 * knows what's exported. Returning the function untouched keeps semantics
21 * symmetric with the inline executor.
22 */
23export function query<A, R>(
24 fn: (ctx: Ctx, args: A) => Promise<R> | R,
25): (ctx: Ctx, args: A) => Promise<R> | R {
26 return fn;
27}
28
29/**
30 * Wrap a customer mutation (write) function. Identical wiring to `query` —
31 * the `ctx.db(...)` builders already emit INSERT / UPDATE / DELETE frames to
32 * the host, which runs them inside the project's writable transaction
33 * (query-builder.ts → tx.unsafe). So a mutation needs no extra plumbing; the
34 * wrapper is a marker that mirrors the customer CLI's `@briven/cli/server`
35 * surface so a function written against the CLI runs unchanged here.
36 */
37export function mutation<A, R>(
38 fn: (ctx: Ctx, args: A) => Promise<R> | R,
39): (ctx: Ctx, args: A) => Promise<R> | R {
40 return fn;
41}
42
43/**
44 * Wrap a customer action function — general compute with no implicit DB
45 * transaction semantics. It still receives the same `ctx` (so `ctx.db`,
46 * `ctx.env`, `ctx.log` all work), and any network it does is governed by the
47 * isolate's Deno `--allow-net` / `--deny-net` flags and the fetch shim in
48 * loop.ts. Identity wrapper, matching the customer CLI's `action`.
49 */
50export function action<A, R>(
51 fn: (ctx: Ctx, args: A) => Promise<R> | R,
52): (ctx: Ctx, args: A) => Promise<R> | R {
53 return fn;
54}
55
56// ---------------------------------------------------------------------------
57// ulid — pure prefixed-ULID string generator.
58//
59// Mirrors the customer CLI's `ulid` (and @briven/shared's prefixed-id shape,
60// e.g. `newId('td')` → "td_01HZ..."). Scaffolded functions call `ulid('td')`
61// to mint primary keys, so the runtime must provide it from
62// `@briven/cli/server`. Pure — no host round-trip, no imports (loop.ts is
63// resolved by Deno with no node_modules), so we inline a Crockford-base32
64// ULID here instead of importing the `ulid` npm package.
65// ---------------------------------------------------------------------------
66
67const CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; // no I, L, O, U
68const TIME_LEN = 10;
69const RANDOM_LEN = 16;
70
71function encodeTime(now: number): string {
72 let out = '';
73 let n = now;
74 for (let i = TIME_LEN - 1; i >= 0; i--) {
75 const mod = n % 32;
76 out = CROCKFORD[mod] + out;
77 n = (n - mod) / 32;
78 }
79 return out;
80}
81
82function encodeRandom(): string {
83 const bytes = new Uint8Array(RANDOM_LEN);
84 crypto.getRandomValues(bytes);
85 let out = '';
86 for (let i = 0; i < RANDOM_LEN; i++) {
87 out += CROCKFORD[bytes[i]! % 32];
88 }
89 return out;
90}
91
92/**
93 * Generate a ULID. With a prefix, returns `<prefix>_<ULID>` (e.g.
94 * `ulid('td')` → "td_01HZ5E4..."); without one, returns the bare 26-char
95 * ULID. Monotonic-within-ms ordering is NOT guaranteed (fresh randomness per
96 * call) — fine for primary keys, which only need uniqueness.
97 */
98export function ulid(prefix?: string): string {
99 const id = encodeTime(Date.now()) + encodeRandom();
100 return prefix ? `${prefix}_${id}` : id;
101}
102
103/**
104 * Customer-facing error base, mirrored from `@briven/shared` so functions can
105 * `import { brivenError } from '@briven/cli/server'` INSIDE the isolate — which
106 * has no node_modules to resolve the real package. Keep the shape in lockstep
107 * with packages/shared/src/errors.ts.
108 */
109export class brivenError extends Error {
110 readonly code: string;
111 readonly status: number;
112 readonly cause?: unknown;
113 readonly context?: Readonly<Record<string, unknown>>;
114
115 constructor(
116 code: string,
117 message: string,
118 options: { status?: number; cause?: unknown; context?: Record<string, unknown> } = {},
119 ) {
120 super(message);
121 this.name = 'brivenError';
122 this.code = code;
123 this.status = options.status ?? 500;
124 this.cause = options.cause;
125 this.context = options.context ? Object.freeze({ ...options.context }) : undefined;
126 }
127
128 toJSON(): { code: string; message: string; status: number } {
129 return { code: this.code, message: this.message, status: this.status };
130 }
131}
132
133// Full runtime surface exposed to deployed functions via `@briven/cli/server`:
134// query · mutation · action · ulid · brivenError (+ low-level runQuery).
135export { runQuery };