@byollm/server 0.1.0-alpha.67 → 0.1.0-alpha.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/supabase/index.js +27 -3
- package/dist/supabase/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
> [!WARNING]
|
|
2
|
-
> **Alpha (`0.1.0-alpha.
|
|
2
|
+
> **Alpha (`0.1.0-alpha.68`) — under active development. Don't use this yet.**
|
|
3
3
|
>
|
|
4
4
|
> Install it deliberately: `npm install @byollm/server@alpha`.
|
|
5
5
|
>
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
> packages published and `@byollm/server` did not: a Sigstore
|
|
106
106
|
> transparency-log 409 on its provenance attestation. The workflow's
|
|
107
107
|
> "already published" guard correctly refuses to resume a partial publish,
|
|
108
|
-
> so `0.1.0-alpha.
|
|
108
|
+
> so `0.1.0-alpha.68` is that release, whole.
|
|
109
109
|
>
|
|
110
110
|
> If you run the Supabase adapter, `alpha.21` needs
|
|
111
111
|
> `20260819010000_completed_by_lease_id.sql`: alpha.19 shipped §3.6's
|
package/dist/supabase/index.js
CHANGED
|
@@ -22,7 +22,7 @@ var SupabaseRealtimeDelivery = class {
|
|
|
22
22
|
const current = await this.#deps.read(jobId);
|
|
23
23
|
if (current && isTerminal(current.state)) return current;
|
|
24
24
|
const settled = Promise.withResolvers();
|
|
25
|
-
this.#
|
|
25
|
+
this.#resolvers.set(jobId, settled.resolve);
|
|
26
26
|
const channel = this.#client.channel(`byollm_job_${jobId}`).on(
|
|
27
27
|
"postgres_changes",
|
|
28
28
|
{
|
|
@@ -50,14 +50,38 @@ var SupabaseRealtimeDelivery = class {
|
|
|
50
50
|
} finally {
|
|
51
51
|
clearTimeout(timer);
|
|
52
52
|
clearInterval(watcher);
|
|
53
|
+
this.#resolvers.delete(jobId);
|
|
53
54
|
options.signal?.removeEventListener("abort", abort);
|
|
54
55
|
await this.#client.removeChannel(channel);
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
|
-
|
|
58
|
+
/**
|
|
59
|
+
* One resolver per job in flight — P0, 2026-09-02.
|
|
60
|
+
*
|
|
61
|
+
* This was a single field, `#resolve`, assigned by every `waitFor`. One
|
|
62
|
+
* delivery object serves a whole app, so two concurrent waits meant the
|
|
63
|
+
* second assignment clobbered the first, and then:
|
|
64
|
+
*
|
|
65
|
+
* 1. `waitFor(A)` sets the resolver.
|
|
66
|
+
* 2. `waitFor(B)` overwrites it.
|
|
67
|
+
* 3. A's row event arrives, `#check(A)` reads A's result — and resolves
|
|
68
|
+
* **B's** promise with it.
|
|
69
|
+
* 4. A never resolves and waits out its timeout.
|
|
70
|
+
*
|
|
71
|
+
* So an app awaiting two jobs at once got one answer under the wrong job
|
|
72
|
+
* id, with the wrong text, silently — and a spurious timeout beside it. No
|
|
73
|
+
* error anywhere; the failure is that the caller believes it.
|
|
74
|
+
*
|
|
75
|
+
* A map, and the entry is removed in the same `finally` that tears down the
|
|
76
|
+
* channel. A resolver that outlived its wait would be a leak that also
|
|
77
|
+
* resolves a promise nobody is holding.
|
|
78
|
+
*/
|
|
79
|
+
#resolvers = /* @__PURE__ */ new Map();
|
|
58
80
|
async #check(jobId) {
|
|
59
81
|
const current = await this.#deps.read(jobId);
|
|
60
|
-
if (current && isTerminal(current.state))
|
|
82
|
+
if (current && isTerminal(current.state)) {
|
|
83
|
+
this.#resolvers.get(jobId)?.(current);
|
|
84
|
+
}
|
|
61
85
|
}
|
|
62
86
|
/** Poll runner liveness; there is no row event for "nothing is happening". */
|
|
63
87
|
#watchAvailability(jobId, options, settled) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/supabase/realtime.ts","../../src/supabase/index.ts"],"sourcesContent":["import type { SupabaseClient } from \"@supabase/supabase-js\";\nimport type { DeliveredResult } from \"@byollm/protocol\";\nimport {\n NoRunnerAvailableError,\n ResultTimeoutError,\n type PollingDeliveryDeps,\n type ResultDelivery,\n type WaitOptions,\n labelFallback,\n} from \"../delivery.js\";\n\nconst DEFAULT_TIMEOUT_MS = 5 * 60_000;\n/**\n * How long a sustained no-runner signal must persist before it is believed.\n * A daemon restarting must not fail every job in flight.\n */\nconst NO_RUNNER_GRACE_MS = 10_000;\n\n/**\n * Realtime delivery: the app learns a job finished when Postgres says so.\n *\n * byollm_003 Rev 1 requires the server→app path be an explicit channel rather\n * than an implied in-request `await`. Polling is the portable default;\n * this is the one worth having when the app is already on Supabase, because\n * a result arrives in milliseconds instead of on the next poll tick.\n *\n * The no-runner watch still polls, deliberately: runner liveness is a\n * *derived* signal (nobody with matching capability has heartbeated lately),\n * and there is no row change to subscribe to for \"something stopped\n * happening\".\n */\nexport function supabaseRealtimeDelivery(\n client: SupabaseClient,\n): (deps: PollingDeliveryDeps) => ResultDelivery {\n return (deps) => new SupabaseRealtimeDelivery(client, deps);\n}\n\nclass SupabaseRealtimeDelivery implements ResultDelivery {\n readonly #client: SupabaseClient;\n readonly #deps: PollingDeliveryDeps;\n\n constructor(client: SupabaseClient, deps: PollingDeliveryDeps) {\n this.#client = client;\n this.#deps = deps;\n }\n\n async waitFor(\n jobId: string,\n options: WaitOptions = {},\n ): Promise<DeliveredResult> {\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n // Read first. The job may already be terminal, and subscribing to a\n // channel for an event that has already happened waits forever.\n const current = await this.#deps.read(jobId);\n if (current && isTerminal(current.state)) return current;\n\n // Declared before the subscription so the channel callback closes over a\n // `settled` that already exists. Every async path below routes its failure\n // here: a rejection that escapes this object becomes an unhandled\n // rejection, and an unhandled rejection ends the process.\n const settled = Promise.withResolvers<DeliveredResult>();\n this.#resolve = settled.resolve;\n\n const channel = this.#client.channel(`byollm_job_${jobId}`).on(\n \"postgres_changes\",\n {\n event: \"UPDATE\",\n schema: \"public\",\n table: \"byollm_jobs\",\n filter: `id=eq.${jobId}`,\n },\n () => {\n this.#check(jobId).catch(settled.reject);\n },\n );\n\n // `subscribe()` returns the channel, not a promise — awaiting it would be\n // a no-op that reads as if it waited for the subscription to be live.\n channel.subscribe();\n\n // A second read after subscribing closes the race where the job finished\n // between the first read and the subscription taking effect.\n this.#check(jobId).catch(settled.reject);\n\n const timer = setTimeout(() => {\n settled.reject(new ResultTimeoutError(jobId, timeoutMs));\n }, timeoutMs);\n\n const watcher = this.#watchAvailability(jobId, options, settled);\n const abort = (): void => {\n settled.reject(new Error(\"wait aborted\"));\n };\n options.signal?.addEventListener(\"abort\", abort, { once: true });\n\n try {\n return await settled.promise;\n } finally {\n clearTimeout(timer);\n clearInterval(watcher);\n options.signal?.removeEventListener(\"abort\", abort);\n await this.#client.removeChannel(channel);\n }\n }\n\n #resolve: ((result: DeliveredResult) => void) | undefined;\n\n async #check(jobId: string): Promise<void> {\n const current = await this.#deps.read(jobId);\n if (current && isTerminal(current.state)) this.#resolve?.(current);\n }\n\n /** Poll runner liveness; there is no row event for \"nothing is happening\". */\n #watchAvailability(\n jobId: string,\n options: WaitOptions,\n settled: PromiseWithResolvers<DeliveredResult>,\n ): NodeJS.Timeout {\n let noRunnerSince: number | null = null;\n\n return setInterval(() => {\n // `.catch`, not `void`. Two things in here can reject — the store read\n // and the caller's own `onNoRunner` — and discarding either made a\n // transient store error, or an app whose fallback throws, terminate the\n // process. The caller is awaiting `result()`; that is where a failure\n // belongs, and it is what the polling channel already does by virtue of\n // running inside the awaited chain. A delivery adapter must not change\n // what a failure means.\n (async () => {\n // No instrument, no question — see `PollingDeliveryDeps`. On the\n // cloud lane `runnerAvailability` refuses rather than reporting a\n // zero it cannot see, and this timer used to reject the caller's\n // `result()` with that refusal every two seconds.\n const availability = await this.#deps.availability?.(jobId);\n if (\n availability === undefined ||\n availability.available ||\n availability.blocked\n ) {\n noRunnerSince = null;\n return;\n }\n noRunnerSince ??= Date.now();\n if (Date.now() - noRunnerSince < NO_RUNNER_GRACE_MS) return;\n\n const reason = availability.reason ?? \"no-runner-online\";\n const substitute = await options.onNoRunner?.(reason);\n if (substitute !== undefined) {\n // The same labelling the polling channel applies, from the same\n // function — {@link MUSTS.FALLBACK_LABELED} cannot depend on which\n // store an app happened to choose.\n settled.resolve(labelFallback(jobId, substitute));\n } else {\n settled.reject(new NoRunnerAvailableError(jobId, reason));\n }\n })().catch(settled.reject);\n }, 2_000);\n }\n}\n\nfunction isTerminal(state: string): boolean {\n return (\n state === \"ok\" ||\n state === \"error\" ||\n state === \"canceled\" ||\n state === \"expired\"\n );\n}\n","import type { SupabaseClient } from \"@supabase/supabase-js\";\nimport type {\n Audience,\n Capability,\n JobOutcome,\n JobState,\n PublicIdentity,\n} from \"@byollm/protocol\";\nimport type {\n StoredJobInput,\n JobRecord,\n PairingRecord,\n RunnerRecord,\n} from \"../records.js\";\nimport type {\n ApproveArgs,\n ByollmStore,\n ClaimArgs,\n AdoptArgs,\n CompleteArgs,\n CompleteResult,\n LeaseRef,\n ReleaseArgs,\n RenewArgs,\n RenewResult,\n TouchArgs,\n} from \"../store.js\";\n\n/**\n * `@byollm/server/supabase` — the first-party Supabase adapter.\n *\n * The piece the of-tomorrow-framework's runner module consumes verbatim.\n * Migrations ship in `supabase/migrations`; the atomic claim lives in a\n * `security definer` RPC using `FOR UPDATE SKIP LOCKED`, and the audience\n * rules are mirrored in SQL so the server refuses independently of the daemon\n * (byollm_003 §Server-side MUSTs).\n *\n * Requires the **service role** key: a runner authenticates with a bearer\n * token of its own, which is not a Supabase session, so the protocol handler\n * cannot run under RLS as the runner's user. RLS still governs everything the\n * *browser* does — the app-side policies in the migration are what protect\n * one user's jobs from another.\n *\n * @packageDocumentation\n */\n\n/** Row shape of `byollm_jobs`. */\ninterface JobRow {\n id: string;\n kind: string;\n envelope: unknown;\n size_class: \"small\" | \"medium\" | \"large\" | \"unbounded\";\n /**\n * Typed as {@link Audience} rather than re-spelled, after a spelling of it\n * here outlived the enum by a week.\n *\n * `public` was removed on 2026-08-26 and this column may still hold it in a\n * database written before then. Nothing in this adapter validates a row —\n * `kind` and `envelope` are both plain casts — so this is a contract with\n * the schema, not a check, and a legacy row is a **migration** obligation\n * rather than a runtime one. Recorded so the migration is written on\n * purpose: a `public` row must be resolved by the deploy, never quietly\n * reinterpreted here as something narrower.\n */\n audience: Audience;\n /** byollm_016 Amendment L. Null for every job that named no purpose. */\n purpose: string | null;\n owner: string;\n audience_allow: string[] | null;\n depends_on: string[];\n state: JobState;\n lease_id: string | null;\n lease_runner: string | null;\n completed_by_lease_id: string | null;\n lease_expires_at: string | null;\n claimable_at: string | null;\n ttl_ms: number;\n deadline_at: string | null;\n refused_by: string[];\n attempts: number;\n outcome: JobOutcome | null;\n provenance: JobRecord[\"provenance\"];\n created_at: string;\n updated_at: string;\n}\n\n/** Row shape of `byollm_runners`. */\ninterface RunnerRow {\n id: string;\n owner: string;\n label: string;\n platform: \"darwin\" | \"linux\" | \"win32\";\n daemon_version: string;\n capabilities: Capability[];\n device: PublicIdentity;\n paused: boolean;\n revoked_at: string | null;\n last_heartbeat_at: string;\n created_at: string;\n}\n\n/** Row shape of `byollm_pairings`. */\ninterface PairingRow {\n device_code_hash: string;\n user_code: string;\n state: \"pending\" | \"approved\" | \"denied\";\n owner: string | null;\n runner_id: string | null;\n collected_at: string | null;\n label: string;\n platform: \"darwin\" | \"linux\" | \"win32\";\n daemon_version: string;\n capabilities: Capability[];\n device: PublicIdentity;\n expires_at: string;\n created_at: string;\n}\n\nconst ms = (iso: string | null): number | null =>\n iso === null ? null : Date.parse(iso);\n\nconst iso = (epochMs: number): string => new Date(epochMs).toISOString();\n\nfunction toJob(row: JobRow): JobRecord {\n const leaseExpires = ms(row.lease_expires_at);\n return {\n id: row.id,\n kind: row.kind as JobRecord[\"kind\"],\n envelope: row.envelope as JobRecord[\"envelope\"],\n sizeClass: row.size_class,\n audience: row.audience,\n purpose: row.purpose ?? undefined,\n owner: row.owner,\n audienceAllow: row.audience_allow ?? undefined,\n dependsOn: row.depends_on,\n state: row.state,\n completedByLeaseId: row.completed_by_lease_id ?? null,\n lease:\n // Keyed on the lease id, not the runner. A relayed grant has no runner\n // row to point at (see AdoptArgs), and reading the lease as absent\n // because `lease_runner` is null would make an actively-held job look\n // claimable — the exact bug `adopt` exists to prevent.\n leaseExpires !== null && row.lease_id !== null\n ? {\n id: row.lease_id,\n runnerId: row.lease_runner ?? \"\",\n expiresAt: leaseExpires,\n }\n : null,\n createdAt: Date.parse(row.created_at),\n claimableAt: ms(row.claimable_at),\n ttlMs: row.ttl_ms,\n deadlineAt: ms(row.deadline_at),\n refusedBy: row.refused_by,\n attempts: row.attempts,\n outcome: row.outcome,\n provenance: row.provenance,\n updatedAt: Date.parse(row.updated_at),\n };\n}\n\n/**\n * A PostgREST filter matching exactly these (job, lease) pairs.\n *\n * Not two `IN` lists: `id IN (…) AND lease_id IN (…)` is a cross product, and\n * while UUID uniqueness makes a mismatch improbable, \"improbable\" is not the\n * property a lease check should rest on. This says what it means.\n */\nconst leasePairs = (leases: readonly LeaseRef[]): string =>\n leases.map((l) => `and(id.eq.${l.jobId},lease_id.eq.${l.leaseId})`).join(\",\");\n\nfunction toRunner(row: RunnerRow): RunnerRecord {\n return {\n id: row.id,\n owner: row.owner,\n label: row.label,\n platform: row.platform,\n daemonVersion: row.daemon_version,\n capabilities: row.capabilities,\n device: row.device,\n paused: row.paused,\n revokedAt: ms(row.revoked_at),\n lastHeartbeatAt: Date.parse(row.last_heartbeat_at),\n createdAt: Date.parse(row.created_at),\n };\n}\n\nfunction toPairing(row: PairingRow): PairingRecord {\n return {\n deviceCodeHash: row.device_code_hash,\n userCode: row.user_code,\n state: row.state,\n owner: row.owner,\n runnerId: row.runner_id,\n // Collected when it has a timestamp. This was `runner_token_once ===\n // null` — a nulled token standing in for a fact about delivery, which is\n // one field doing two jobs (cloud_008 §2.4a).\n collected: row.collected_at !== null,\n label: row.label,\n platform: row.platform,\n daemonVersion: row.daemon_version,\n capabilities: row.capabilities,\n device: row.device,\n expiresAt: Date.parse(row.expires_at),\n createdAt: Date.parse(row.created_at),\n };\n}\n\nexport interface SupabaseStoreOptions {\n /** A client built with the **service role** key. */\n readonly client: SupabaseClient;\n /** Default TTL for a job once claimable. */\n readonly defaultTtlMs?: number;\n}\n\n/** Build the Supabase-backed store. */\nexport function supabaseStore(options: SupabaseStoreOptions): ByollmStore {\n const db = options.client;\n const defaultTtlMs = options.defaultTtlMs ?? 15 * 60_000;\n\n /**\n * Narrow one PostgREST response, or throw with the Postgres message.\n *\n * `supabase-js` types rows as `any` unless the project has generated\n * database types, so the assertion has to live somewhere. Confining it to\n * these two helpers — against the row interfaces declared above — keeps\n * every call site typed and leaves exactly one place to review.\n */\n /* eslint-disable @typescript-eslint/no-unnecessary-type-parameters --\n T appears only in the return type because these helpers *are* the cast.\n That is the point: one reviewable place where PostgREST's `any` becomes\n one of the row interfaces above. */\n function unwrap<T>(result: {\n data: unknown;\n error: { message: string } | null;\n }): T {\n if (result.error) throw new Error(`supabase: ${result.error.message}`);\n if (result.data === null || result.data === undefined) {\n throw new Error(\"supabase: no data returned\");\n }\n return result.data as T;\n }\n\n /** Same, but a missing row is a legitimate answer rather than an error. */\n function unwrapMaybe<T>(result: {\n data: unknown;\n error: { message: string } | null;\n }): T | null {\n if (result.error) throw new Error(`supabase: ${result.error.message}`);\n return (result.data ?? null) as T | null;\n }\n /* eslint-enable @typescript-eslint/no-unnecessary-type-parameters */\n\n return {\n // -- jobs ---------------------------------------------------------------\n\n async create(input: StoredJobInput, now: number): Promise<JobRecord> {\n const dependsOn = [...(input.dependsOn ?? [])];\n\n // A job with dependencies starts blocked; the trigger sets\n // `claimable_at` when the last one reaches `ok`, which is where its TTL\n // clock starts.\n let claimableAt: string | null = iso(now);\n if (dependsOn.length > 0) {\n const deps = unwrap<JobRow[]>(\n await db.from(\"byollm_jobs\").select(\"id,state\").in(\"id\", dependsOn),\n ) as { id: string; state: JobState }[];\n const allDone =\n deps.length === dependsOn.length &&\n deps.every((dep) => dep.state === \"ok\");\n claimableAt = allDone ? iso(now) : null;\n }\n\n const row = {\n id: input.id,\n kind: input.kind,\n envelope: input.envelope,\n size_class: input.sizeClass,\n audience: input.audience ?? \"private\",\n purpose: input.purpose ?? null,\n owner: input.owner,\n audience_allow: input.audienceAllow ? [...input.audienceAllow] : null,\n depends_on: dependsOn,\n claimable_at: claimableAt,\n ttl_ms: input.ttlMs ?? defaultTtlMs,\n deadline_at:\n input.deadlineAt === undefined ? null : iso(input.deadlineAt),\n };\n\n // Idempotent by caller-supplied id, matching the reference store: an\n // app's retry must not duplicate work.\n const inserted = unwrapMaybe<JobRow>(\n await db\n .from(\"byollm_jobs\")\n .upsert(row, { onConflict: \"id\", ignoreDuplicates: true })\n .select()\n .maybeSingle(),\n );\n\n if (inserted) return toJob(inserted);\n const existing = unwrap<JobRow>(\n await db.from(\"byollm_jobs\").select().eq(\"id\", input.id).single(),\n );\n return toJob(existing);\n },\n\n async get(jobId: string): Promise<JobRecord | null> {\n const row = unwrapMaybe<JobRow>(\n await db.from(\"byollm_jobs\").select().eq(\"id\", jobId).maybeSingle(),\n );\n return row === null ? null : toJob(row);\n },\n\n async claim(args: ClaimArgs): Promise<JobRecord[]> {\n // One RPC, one transaction, `FOR UPDATE SKIP LOCKED` inside\n // ({@link MUSTS.CLAIM_ATOMIC}).\n const rows = unwrap<JobRow[]>(\n await db.rpc(\"byollm_claim_jobs\", {\n p_runner_id: args.runnerId,\n p_capabilities: args.capabilities,\n p_max: args.max,\n p_lease_ms: args.leaseMs,\n }),\n );\n return rows.map(toJob);\n },\n\n async renewLeases(args: RenewArgs): Promise<RenewResult> {\n await db.rpc(\"byollm_expire_due\");\n if (args.leases.length === 0) return { renewed: [], lost: [] };\n\n const expiresAt = iso(args.now + args.leaseMs);\n const renewedRows = unwrap<JobRow[]>(\n await db\n .from(\"byollm_jobs\")\n .update({\n state: \"running\",\n lease_expires_at: expiresAt,\n updated_at: iso(args.now),\n })\n .eq(\"lease_runner\", args.runnerId)\n .or(leasePairs(args.leases))\n .in(\"state\", [\"claimed\", \"running\"])\n .select(\"id\"),\n );\n\n const renewedIds = new Set(renewedRows.map((row) => row.id));\n return {\n renewed: renewedRows.map((row) => ({\n jobId: row.id,\n expiresAt: args.now + args.leaseMs,\n })),\n // Anything the runner thinks it holds but did not renew is gone,\n // named by the grant it asked about rather than by a bare id — V1-3.\n lost: args.leases.filter((lease) => !renewedIds.has(lease.jobId)),\n };\n },\n\n async adopt(args: AdoptArgs): Promise<JobRecord | null> {\n // The predicates are the guard, evaluated in the database rather than\n // read-then-written here: `state in (queued, claimed)` is what makes\n // adopting a terminal or expired job impossible under concurrency.\n const rows = unwrap<JobRow[]>(\n await db\n .from(\"byollm_jobs\")\n .update({\n state: \"claimed\",\n lease_id: args.leaseId,\n // Left null on purpose: `lease_runner` is a foreign key into\n // `byollm_runners`, and a relayed device has no row there. See\n // AdoptArgs — the site records the grant, not a machine it has\n // no relationship with.\n lease_runner: null,\n lease_expires_at: iso(args.expiresAt),\n updated_at: iso(args.now),\n })\n .eq(\"id\", args.jobId)\n .in(\"state\", [\"queued\", \"claimed\"])\n .select(),\n );\n const written = rows[0];\n return written === undefined ? null : toJob(written);\n },\n\n async complete(args: CompleteArgs): Promise<CompleteResult> {\n const state: JobState =\n args.outcome.outcome === \"ok\"\n ? \"ok\"\n : args.outcome.outcome === \"canceled\"\n ? \"canceled\"\n : \"error\";\n\n // The `in('state', ...)` predicate is the idempotency guard: a job that\n // already reached a terminal state matches nothing, so the first\n // outcome wins ({@link MUSTS.RESULT_IDEMPOTENT}). The `lease_runner`\n // predicate is {@link MUSTS.LEASE_HONORED}.\n // The `in('state', ...)` predicate is the idempotency guard. The\n // second predicate is LEASE_HONORED, and which column carries it\n // depends on the plane: a direct runner is named by id, a relayed\n // grant only by its lease. Built as a query rather than branched into\n // two, so there is one update statement and no chance of the two\n // drifting.\n let update = db\n .from(\"byollm_jobs\")\n .update({\n state,\n lease_runner: null,\n lease_expires_at: null,\n // Which grant recorded it, kept after the lease is dropped — §3.6.\n completed_by_lease_id:\n args.holder.by === \"lease\" ? args.holder.leaseId : null,\n outcome: args.outcome,\n provenance: args.provenance,\n updated_at: iso(args.now),\n })\n .eq(\"id\", args.jobId)\n .in(\"state\", [\"claimed\", \"running\"]);\n update =\n args.holder.by === \"runner\"\n ? update.eq(\"lease_runner\", args.holder.runnerId)\n : update.eq(\"lease_id\", args.holder.leaseId);\n const rows = unwrap<JobRow[]>(await update.select());\n\n const written = rows[0];\n if (written === undefined) {\n const current = unwrapMaybe<JobRow>(\n await db\n .from(\"byollm_jobs\")\n .select()\n .eq(\"id\", args.jobId)\n .maybeSingle(),\n );\n const job = current === null ? null : toJob(current);\n // Terminal before holder — cloud_008 §3.6. The update above matched\n // nothing for one of two reasons, and the caller is owed the\n // difference: the device that already recorded this job hears\n // \"duplicate\", and anybody else hears the same refusal they would get\n // for a job that is not terminal, so a job id is not a terminality\n // probe.\n //\n // Decided on the row that is there rather than by a second predicate,\n // because the update is the atomic part and this is only a diagnosis\n // of why it matched nothing.\n const duplicate =\n job !== null &&\n job.provenance?.runnerId === args.runnerId &&\n (job.state === \"ok\" ||\n job.state === \"error\" ||\n job.state === \"canceled\") &&\n args.holder.by === \"lease\" &&\n job.completedByLeaseId !== null &&\n job.completedByLeaseId === args.holder.leaseId;\n return duplicate\n ? { accepted: false, duplicate: true, job }\n : { accepted: false, job };\n }\n return { accepted: true, job: toJob(written) };\n },\n\n async release(args: ReleaseArgs): Promise<string[]> {\n if (args.leases.length === 0) return [];\n\n const held = unwrap<{ id: string; refused_by: string[] }[]>(\n await db\n .from(\"byollm_jobs\")\n .select(\"id,refused_by\")\n .eq(\"lease_runner\", args.runnerId)\n .or(leasePairs(args.leases)),\n );\n\n const released: string[] = [];\n for (const row of held) {\n const refusedBy =\n args.reason === \"refused\"\n ? [...new Set([...row.refused_by, args.runnerId])]\n : row.refused_by;\n\n const { error } = await db\n .from(\"byollm_jobs\")\n .update({\n state: \"queued\",\n lease_id: null,\n lease_runner: null,\n lease_expires_at: null,\n // Newly available again, so the TTL clock restarts.\n claimable_at: iso(args.now),\n refused_by: refusedBy,\n updated_at: iso(args.now),\n })\n .eq(\"id\", row.id)\n .eq(\"lease_runner\", args.runnerId);\n if (error) throw new Error(`supabase: ${error.message}`);\n released.push(row.id);\n }\n return released;\n },\n\n async expireDue(_now: number): Promise<JobRecord[]> {\n // The sweep is a single idempotent SQL function; it reports a count\n // rather than rows, and the caller only needs to know it ran.\n const { error } = await db.rpc(\"byollm_expire_due\");\n if (error) throw new Error(`supabase: ${error.message}`);\n return [];\n },\n\n async cancel(jobId: string, now: number): Promise<JobRecord | null> {\n const current = unwrapMaybe<JobRow>(\n await db.from(\"byollm_jobs\").select().eq(\"id\", jobId).maybeSingle(),\n );\n if (current === null) return null;\n\n if (current.state === \"queued\") {\n const rows = unwrap<JobRow[]>(\n await db\n .from(\"byollm_jobs\")\n .update({ state: \"canceled\", updated_at: iso(now) })\n .eq(\"id\", jobId)\n .eq(\"state\", \"queued\")\n .select(),\n );\n const canceled = rows[0];\n return toJob(canceled ?? current);\n }\n\n if (current.state === \"claimed\" || current.state === \"running\") {\n // Held by a runner: the cancel travels on the next heartbeat and the\n // runner reports `canceled` itself.\n const { error } = await db\n .from(\"byollm_job_cancels\")\n .upsert({ job_id: jobId, requested_at: iso(now) });\n if (error) throw new Error(`supabase: ${error.message}`);\n }\n return toJob(current);\n },\n\n async listClaimedBy(runnerId: string): Promise<JobRecord[]> {\n const rows = unwrap<JobRow[]>(\n await db.from(\"byollm_jobs\").select().eq(\"lease_runner\", runnerId),\n );\n return rows.map(toJob);\n },\n\n async listCancelRequests(runnerId: string): Promise<LeaseRef[]> {\n // The lease comes back with the row — V1-3. A bare job id is ambiguous\n // to a daemon serving two sites that chose the same one, and the lease\n // is already on the joined row.\n const rows = unwrap<{ job_id: string; byollm_jobs: unknown }[]>(\n await db\n .from(\"byollm_job_cancels\")\n .select(\"job_id, byollm_jobs!inner(lease_runner, lease_id)\")\n .eq(\"byollm_jobs.lease_runner\", runnerId),\n ) as {\n job_id: string;\n byollm_jobs: { lease_id: string | null };\n }[];\n return rows\n .filter((row) => row.byollm_jobs.lease_id !== null)\n .map((row) => ({\n jobId: row.job_id,\n leaseId: row.byollm_jobs.lease_id ?? \"\",\n }));\n },\n\n // -- pairing and runners -------------------------------------------------\n\n /**\n * The push seam (byollm_009 §8.3), over Postgres Realtime.\n *\n * Native here, which is the point of requiring it of every adapter: the\n * backend that can push does, the one that cannot polls, and the\n * interface does not change again when streaming arrives.\n */\n subscribe(jobId: string, onChange: () => void): () => void {\n const channel = db\n .channel(`byollm_job_${jobId}`)\n .on(\n \"postgres_changes\",\n {\n event: \"UPDATE\",\n schema: \"public\",\n table: \"byollm_jobs\",\n filter: `id=eq.${jobId}`,\n },\n () => {\n onChange();\n },\n )\n .subscribe();\n\n let live = true;\n return () => {\n if (!live) return;\n live = false;\n // `removeChannel` is async and nothing awaits an unsubscribe, so the\n // rejection is routed rather than dropped — an unhandled one here\n // would end the process (see the Realtime delivery channel).\n void db.removeChannel(channel).catch(() => undefined);\n };\n },\n\n async createPairing(record: PairingRecord): Promise<void> {\n const { error } = await db.from(\"byollm_pairings\").insert({\n device_code_hash: record.deviceCodeHash,\n device: record.device,\n user_code: record.userCode,\n state: record.state,\n label: record.label,\n platform: record.platform,\n daemon_version: record.daemonVersion,\n capabilities: record.capabilities,\n expires_at: iso(record.expiresAt),\n });\n if (error) throw new Error(`supabase: ${error.message}`);\n },\n\n async getPairingByDeviceCodeHash(\n hash: string,\n ): Promise<PairingRecord | null> {\n const row = unwrapMaybe<PairingRow>(\n await db\n .from(\"byollm_pairings\")\n .select()\n .eq(\"device_code_hash\", hash)\n .maybeSingle(),\n );\n return row === null ? null : toPairing(row);\n },\n\n async getPairingByUserCode(\n userCode: string,\n ): Promise<PairingRecord | null> {\n const row = unwrapMaybe<PairingRow>(\n await db\n .from(\"byollm_pairings\")\n .select()\n .eq(\"user_code\", userCode)\n .maybeSingle(),\n );\n return row === null ? null : toPairing(row);\n },\n\n async approvePairing(args: ApproveArgs): Promise<RunnerRecord> {\n // Deliberately *not* the browser RPC: this path runs under the service\n // role with an `owner` the caller has already authenticated. Apps using\n // Supabase Auth in the browser should call `byollm_approve_pairing`\n // instead, which takes the owner from `auth.uid()` and cannot be told\n // who the user is.\n const pairing = unwrap<PairingRow>(\n await db\n .from(\"byollm_pairings\")\n .select()\n .eq(\"user_code\", args.userCode)\n .single(),\n );\n\n if (Date.parse(pairing.expires_at) <= args.now) {\n throw new Error(\"pairing code has expired\");\n }\n if (pairing.state !== \"pending\") {\n throw new Error(`pairing is already ${pairing.state}`);\n }\n\n const runner = unwrap<RunnerRow>(\n await db\n .from(\"byollm_runners\")\n .insert({\n owner: args.owner,\n label: pairing.label,\n platform: pairing.platform,\n daemon_version: pairing.daemon_version,\n capabilities: pairing.capabilities,\n // Carried from the pairing, exactly as the SQL RPC does. There\n // are two approval paths — this service-role one and\n // `byollm_approve_pairing` for browser callers — and a field\n // added to one and not the other produces a runner that is\n // correct through one door and broken through the other.\n device: pairing.device,\n })\n .select()\n .single(),\n );\n\n const { error } = await db\n .from(\"byollm_pairings\")\n .update({\n state: \"approved\",\n owner: args.owner,\n runner_id: runner.id,\n // Marks the approval collectable — cloud_008 §2.4. The column held\n // a bearer token; it now holds a marker, and the next migration\n // renames it. Written as a constant rather than left null because\n // `collected` reads `=== null`, and a schema change and a code\n // change landing in one step is how a rollback strands rows.\n collected_at: null,\n })\n .eq(\"device_code_hash\", pairing.device_code_hash);\n if (error) throw new Error(`supabase: ${error.message}`);\n\n return toRunner(runner);\n },\n\n async denyPairing(userCode: string, _now: number): Promise<void> {\n const { error } = await db\n .from(\"byollm_pairings\")\n .update({ state: \"denied\" })\n .eq(\"user_code\", userCode);\n if (error) throw new Error(`supabase: ${error.message}`);\n },\n\n async consumePairingToken(deviceCodeHash: string): Promise<void> {\n const { error } = await db\n .from(\"byollm_pairings\")\n // The database's clock, not this process's — the same rule the\n // relay's lease stamps follow: two writers measuring one fact against\n // two clocks is how a \"collected\" row looks uncollected.\n .update({ collected_at: \"now()\" })\n .eq(\"device_code_hash\", deviceCodeHash);\n if (error) throw new Error(`supabase: ${error.message}`);\n },\n\n async getRunner(runnerId: string): Promise<RunnerRecord | null> {\n const row = unwrapMaybe<RunnerRow>(\n await db\n .from(\"byollm_runners\")\n .select()\n .eq(\"id\", runnerId)\n .maybeSingle(),\n );\n return row === null ? null : toRunner(row);\n },\n\n async touchRunner(args: TouchArgs): Promise<RunnerRecord | null> {\n const row = unwrapMaybe<RunnerRow>(\n await db\n .from(\"byollm_runners\")\n .update({\n capabilities: args.capabilities,\n daemon_version: args.daemonVersion,\n paused: args.paused,\n last_heartbeat_at: iso(args.now),\n })\n .eq(\"id\", args.runnerId)\n .select()\n .maybeSingle(),\n );\n return row === null ? null : toRunner(row);\n },\n\n async revokeRunner(runnerId: string, now: number): Promise<void> {\n // `is('revoked_at', null)` keeps revocation one-way: an already-revoked\n // runner keeps its first revocation time.\n const { error } = await db\n .from(\"byollm_runners\")\n .update({ revoked_at: iso(now) })\n .eq(\"id\", runnerId)\n .is(\"revoked_at\", null);\n if (error) throw new Error(`supabase: ${error.message}`);\n },\n\n async listRunners(owner?: string): Promise<RunnerRecord[]> {\n const query = db.from(\"byollm_runners\").select();\n const rows = unwrap<RunnerRow[]>(\n owner === undefined ? await query : await query.eq(\"owner\", owner),\n );\n return rows.map(toRunner);\n },\n };\n}\n\nexport { supabaseRealtimeDelivery } from \"./realtime.js\";\n"],"mappings":";;;;;;;AAWA,IAAM,qBAAqB,IAAI;AAK/B,IAAM,qBAAqB;AAepB,SAAS,yBACd,QAC+C;AAC/C,SAAO,CAAC,SAAS,IAAI,yBAAyB,QAAQ,IAAI;AAC5D;AAEA,IAAM,2BAAN,MAAyD;AAAA,EAC9C;AAAA,EACA;AAAA,EAET,YAAY,QAAwB,MAA2B;AAC7D,SAAK,UAAU;AACf,SAAK,QAAQ;AAAA,EACf;AAAA,EAEA,MAAM,QACJ,OACA,UAAuB,CAAC,GACE;AAC1B,UAAM,YAAY,QAAQ,aAAa;AAIvC,UAAM,UAAU,MAAM,KAAK,MAAM,KAAK,KAAK;AAC3C,QAAI,WAAW,WAAW,QAAQ,KAAK,EAAG,QAAO;AAMjD,UAAM,UAAU,QAAQ,cAA+B;AACvD,SAAK,WAAW,QAAQ;AAExB,UAAM,UAAU,KAAK,QAAQ,QAAQ,cAAc,KAAK,EAAE,EAAE;AAAA,MAC1D;AAAA,MACA;AAAA,QACE,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,OAAO;AAAA,QACP,QAAQ,SAAS,KAAK;AAAA,MACxB;AAAA,MACA,MAAM;AACJ,aAAK,OAAO,KAAK,EAAE,MAAM,QAAQ,MAAM;AAAA,MACzC;AAAA,IACF;AAIA,YAAQ,UAAU;AAIlB,SAAK,OAAO,KAAK,EAAE,MAAM,QAAQ,MAAM;AAEvC,UAAM,QAAQ,WAAW,MAAM;AAC7B,cAAQ,OAAO,IAAI,mBAAmB,OAAO,SAAS,CAAC;AAAA,IACzD,GAAG,SAAS;AAEZ,UAAM,UAAU,KAAK,mBAAmB,OAAO,SAAS,OAAO;AAC/D,UAAM,QAAQ,MAAY;AACxB,cAAQ,OAAO,IAAI,MAAM,cAAc,CAAC;AAAA,IAC1C;AACA,YAAQ,QAAQ,iBAAiB,SAAS,OAAO,EAAE,MAAM,KAAK,CAAC;AAE/D,QAAI;AACF,aAAO,MAAM,QAAQ;AAAA,IACvB,UAAE;AACA,mBAAa,KAAK;AAClB,oBAAc,OAAO;AACrB,cAAQ,QAAQ,oBAAoB,SAAS,KAAK;AAClD,YAAM,KAAK,QAAQ,cAAc,OAAO;AAAA,IAC1C;AAAA,EACF;AAAA,EAEA;AAAA,EAEA,MAAM,OAAO,OAA8B;AACzC,UAAM,UAAU,MAAM,KAAK,MAAM,KAAK,KAAK;AAC3C,QAAI,WAAW,WAAW,QAAQ,KAAK,EAAG,MAAK,WAAW,OAAO;AAAA,EACnE;AAAA;AAAA,EAGA,mBACE,OACA,SACA,SACgB;AAChB,QAAI,gBAA+B;AAEnC,WAAO,YAAY,MAAM;AAQvB,OAAC,YAAY;AAKX,cAAM,eAAe,MAAM,KAAK,MAAM,eAAe,KAAK;AAC1D,YACE,iBAAiB,UACjB,aAAa,aACb,aAAa,SACb;AACA,0BAAgB;AAChB;AAAA,QACF;AACA,0BAAkB,KAAK,IAAI;AAC3B,YAAI,KAAK,IAAI,IAAI,gBAAgB,mBAAoB;AAErD,cAAM,SAAS,aAAa,UAAU;AACtC,cAAM,aAAa,MAAM,QAAQ,aAAa,MAAM;AACpD,YAAI,eAAe,QAAW;AAI5B,kBAAQ,QAAQ,cAAc,OAAO,UAAU,CAAC;AAAA,QAClD,OAAO;AACL,kBAAQ,OAAO,IAAI,uBAAuB,OAAO,MAAM,CAAC;AAAA,QAC1D;AAAA,MACF,GAAG,EAAE,MAAM,QAAQ,MAAM;AAAA,IAC3B,GAAG,GAAK;AAAA,EACV;AACF;AAEA,SAAS,WAAW,OAAwB;AAC1C,SACE,UAAU,QACV,UAAU,WACV,UAAU,cACV,UAAU;AAEd;;;ACjDA,IAAM,KAAK,CAACA,SACVA,SAAQ,OAAO,OAAO,KAAK,MAAMA,IAAG;AAEtC,IAAM,MAAM,CAAC,YAA4B,IAAI,KAAK,OAAO,EAAE,YAAY;AAEvE,SAAS,MAAM,KAAwB;AACrC,QAAM,eAAe,GAAG,IAAI,gBAAgB;AAC5C,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,MAAM,IAAI;AAAA,IACV,UAAU,IAAI;AAAA,IACd,WAAW,IAAI;AAAA,IACf,UAAU,IAAI;AAAA,IACd,SAAS,IAAI,WAAW;AAAA,IACxB,OAAO,IAAI;AAAA,IACX,eAAe,IAAI,kBAAkB;AAAA,IACrC,WAAW,IAAI;AAAA,IACf,OAAO,IAAI;AAAA,IACX,oBAAoB,IAAI,yBAAyB;AAAA,IACjD;AAAA;AAAA;AAAA;AAAA;AAAA,MAKE,iBAAiB,QAAQ,IAAI,aAAa,OACtC;AAAA,QACE,IAAI,IAAI;AAAA,QACR,UAAU,IAAI,gBAAgB;AAAA,QAC9B,WAAW;AAAA,MACb,IACA;AAAA;AAAA,IACN,WAAW,KAAK,MAAM,IAAI,UAAU;AAAA,IACpC,aAAa,GAAG,IAAI,YAAY;AAAA,IAChC,OAAO,IAAI;AAAA,IACX,YAAY,GAAG,IAAI,WAAW;AAAA,IAC9B,WAAW,IAAI;AAAA,IACf,UAAU,IAAI;AAAA,IACd,SAAS,IAAI;AAAA,IACb,YAAY,IAAI;AAAA,IAChB,WAAW,KAAK,MAAM,IAAI,UAAU;AAAA,EACtC;AACF;AASA,IAAM,aAAa,CAAC,WAClB,OAAO,IAAI,CAAC,MAAM,aAAa,EAAE,KAAK,gBAAgB,EAAE,OAAO,GAAG,EAAE,KAAK,GAAG;AAE9E,SAAS,SAAS,KAA8B;AAC9C,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,OAAO,IAAI;AAAA,IACX,OAAO,IAAI;AAAA,IACX,UAAU,IAAI;AAAA,IACd,eAAe,IAAI;AAAA,IACnB,cAAc,IAAI;AAAA,IAClB,QAAQ,IAAI;AAAA,IACZ,QAAQ,IAAI;AAAA,IACZ,WAAW,GAAG,IAAI,UAAU;AAAA,IAC5B,iBAAiB,KAAK,MAAM,IAAI,iBAAiB;AAAA,IACjD,WAAW,KAAK,MAAM,IAAI,UAAU;AAAA,EACtC;AACF;AAEA,SAAS,UAAU,KAAgC;AACjD,SAAO;AAAA,IACL,gBAAgB,IAAI;AAAA,IACpB,UAAU,IAAI;AAAA,IACd,OAAO,IAAI;AAAA,IACX,OAAO,IAAI;AAAA,IACX,UAAU,IAAI;AAAA;AAAA;AAAA;AAAA,IAId,WAAW,IAAI,iBAAiB;AAAA,IAChC,OAAO,IAAI;AAAA,IACX,UAAU,IAAI;AAAA,IACd,eAAe,IAAI;AAAA,IACnB,cAAc,IAAI;AAAA,IAClB,QAAQ,IAAI;AAAA,IACZ,WAAW,KAAK,MAAM,IAAI,UAAU;AAAA,IACpC,WAAW,KAAK,MAAM,IAAI,UAAU;AAAA,EACtC;AACF;AAUO,SAAS,cAAc,SAA4C;AACxE,QAAM,KAAK,QAAQ;AACnB,QAAM,eAAe,QAAQ,gBAAgB,KAAK;AAclD,WAAS,OAAU,QAGb;AACJ,QAAI,OAAO,MAAO,OAAM,IAAI,MAAM,aAAa,OAAO,MAAM,OAAO,EAAE;AACrE,QAAI,OAAO,SAAS,QAAQ,OAAO,SAAS,QAAW;AACrD,YAAM,IAAI,MAAM,4BAA4B;AAAA,IAC9C;AACA,WAAO,OAAO;AAAA,EAChB;AAGA,WAAS,YAAe,QAGX;AACX,QAAI,OAAO,MAAO,OAAM,IAAI,MAAM,aAAa,OAAO,MAAM,OAAO,EAAE;AACrE,WAAQ,OAAO,QAAQ;AAAA,EACzB;AAGA,SAAO;AAAA;AAAA,IAGL,MAAM,OAAO,OAAuB,KAAiC;AACnE,YAAM,YAAY,CAAC,GAAI,MAAM,aAAa,CAAC,CAAE;AAK7C,UAAI,cAA6B,IAAI,GAAG;AACxC,UAAI,UAAU,SAAS,GAAG;AACxB,cAAM,OAAO;AAAA,UACX,MAAM,GAAG,KAAK,aAAa,EAAE,OAAO,UAAU,EAAE,GAAG,MAAM,SAAS;AAAA,QACpE;AACA,cAAM,UACJ,KAAK,WAAW,UAAU,UAC1B,KAAK,MAAM,CAAC,QAAQ,IAAI,UAAU,IAAI;AACxC,sBAAc,UAAU,IAAI,GAAG,IAAI;AAAA,MACrC;AAEA,YAAM,MAAM;AAAA,QACV,IAAI,MAAM;AAAA,QACV,MAAM,MAAM;AAAA,QACZ,UAAU,MAAM;AAAA,QAChB,YAAY,MAAM;AAAA,QAClB,UAAU,MAAM,YAAY;AAAA,QAC5B,SAAS,MAAM,WAAW;AAAA,QAC1B,OAAO,MAAM;AAAA,QACb,gBAAgB,MAAM,gBAAgB,CAAC,GAAG,MAAM,aAAa,IAAI;AAAA,QACjE,YAAY;AAAA,QACZ,cAAc;AAAA,QACd,QAAQ,MAAM,SAAS;AAAA,QACvB,aACE,MAAM,eAAe,SAAY,OAAO,IAAI,MAAM,UAAU;AAAA,MAChE;AAIA,YAAM,WAAW;AAAA,QACf,MAAM,GACH,KAAK,aAAa,EAClB,OAAO,KAAK,EAAE,YAAY,MAAM,kBAAkB,KAAK,CAAC,EACxD,OAAO,EACP,YAAY;AAAA,MACjB;AAEA,UAAI,SAAU,QAAO,MAAM,QAAQ;AACnC,YAAM,WAAW;AAAA,QACf,MAAM,GAAG,KAAK,aAAa,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,EAAE,EAAE,OAAO;AAAA,MAClE;AACA,aAAO,MAAM,QAAQ;AAAA,IACvB;AAAA,IAEA,MAAM,IAAI,OAA0C;AAClD,YAAM,MAAM;AAAA,QACV,MAAM,GAAG,KAAK,aAAa,EAAE,OAAO,EAAE,GAAG,MAAM,KAAK,EAAE,YAAY;AAAA,MACpE;AACA,aAAO,QAAQ,OAAO,OAAO,MAAM,GAAG;AAAA,IACxC;AAAA,IAEA,MAAM,MAAM,MAAuC;AAGjD,YAAM,OAAO;AAAA,QACX,MAAM,GAAG,IAAI,qBAAqB;AAAA,UAChC,aAAa,KAAK;AAAA,UAClB,gBAAgB,KAAK;AAAA,UACrB,OAAO,KAAK;AAAA,UACZ,YAAY,KAAK;AAAA,QACnB,CAAC;AAAA,MACH;AACA,aAAO,KAAK,IAAI,KAAK;AAAA,IACvB;AAAA,IAEA,MAAM,YAAY,MAAuC;AACvD,YAAM,GAAG,IAAI,mBAAmB;AAChC,UAAI,KAAK,OAAO,WAAW,EAAG,QAAO,EAAE,SAAS,CAAC,GAAG,MAAM,CAAC,EAAE;AAE7D,YAAM,YAAY,IAAI,KAAK,MAAM,KAAK,OAAO;AAC7C,YAAM,cAAc;AAAA,QAClB,MAAM,GACH,KAAK,aAAa,EAClB,OAAO;AAAA,UACN,OAAO;AAAA,UACP,kBAAkB;AAAA,UAClB,YAAY,IAAI,KAAK,GAAG;AAAA,QAC1B,CAAC,EACA,GAAG,gBAAgB,KAAK,QAAQ,EAChC,GAAG,WAAW,KAAK,MAAM,CAAC,EAC1B,GAAG,SAAS,CAAC,WAAW,SAAS,CAAC,EAClC,OAAO,IAAI;AAAA,MAChB;AAEA,YAAM,aAAa,IAAI,IAAI,YAAY,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;AAC3D,aAAO;AAAA,QACL,SAAS,YAAY,IAAI,CAAC,SAAS;AAAA,UACjC,OAAO,IAAI;AAAA,UACX,WAAW,KAAK,MAAM,KAAK;AAAA,QAC7B,EAAE;AAAA;AAAA;AAAA,QAGF,MAAM,KAAK,OAAO,OAAO,CAAC,UAAU,CAAC,WAAW,IAAI,MAAM,KAAK,CAAC;AAAA,MAClE;AAAA,IACF;AAAA,IAEA,MAAM,MAAM,MAA4C;AAItD,YAAM,OAAO;AAAA,QACX,MAAM,GACH,KAAK,aAAa,EAClB,OAAO;AAAA,UACN,OAAO;AAAA,UACP,UAAU,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,UAKf,cAAc;AAAA,UACd,kBAAkB,IAAI,KAAK,SAAS;AAAA,UACpC,YAAY,IAAI,KAAK,GAAG;AAAA,QAC1B,CAAC,EACA,GAAG,MAAM,KAAK,KAAK,EACnB,GAAG,SAAS,CAAC,UAAU,SAAS,CAAC,EACjC,OAAO;AAAA,MACZ;AACA,YAAM,UAAU,KAAK,CAAC;AACtB,aAAO,YAAY,SAAY,OAAO,MAAM,OAAO;AAAA,IACrD;AAAA,IAEA,MAAM,SAAS,MAA6C;AAC1D,YAAM,QACJ,KAAK,QAAQ,YAAY,OACrB,OACA,KAAK,QAAQ,YAAY,aACvB,aACA;AAYR,UAAI,SAAS,GACV,KAAK,aAAa,EAClB,OAAO;AAAA,QACN;AAAA,QACA,cAAc;AAAA,QACd,kBAAkB;AAAA;AAAA,QAElB,uBACE,KAAK,OAAO,OAAO,UAAU,KAAK,OAAO,UAAU;AAAA,QACrD,SAAS,KAAK;AAAA,QACd,YAAY,KAAK;AAAA,QACjB,YAAY,IAAI,KAAK,GAAG;AAAA,MAC1B,CAAC,EACA,GAAG,MAAM,KAAK,KAAK,EACnB,GAAG,SAAS,CAAC,WAAW,SAAS,CAAC;AACrC,eACE,KAAK,OAAO,OAAO,WACf,OAAO,GAAG,gBAAgB,KAAK,OAAO,QAAQ,IAC9C,OAAO,GAAG,YAAY,KAAK,OAAO,OAAO;AAC/C,YAAM,OAAO,OAAiB,MAAM,OAAO,OAAO,CAAC;AAEnD,YAAM,UAAU,KAAK,CAAC;AACtB,UAAI,YAAY,QAAW;AACzB,cAAM,UAAU;AAAA,UACd,MAAM,GACH,KAAK,aAAa,EAClB,OAAO,EACP,GAAG,MAAM,KAAK,KAAK,EACnB,YAAY;AAAA,QACjB;AACA,cAAM,MAAM,YAAY,OAAO,OAAO,MAAM,OAAO;AAWnD,cAAM,YACJ,QAAQ,QACR,IAAI,YAAY,aAAa,KAAK,aACjC,IAAI,UAAU,QACb,IAAI,UAAU,WACd,IAAI,UAAU,eAChB,KAAK,OAAO,OAAO,WACnB,IAAI,uBAAuB,QAC3B,IAAI,uBAAuB,KAAK,OAAO;AACzC,eAAO,YACH,EAAE,UAAU,OAAO,WAAW,MAAM,IAAI,IACxC,EAAE,UAAU,OAAO,IAAI;AAAA,MAC7B;AACA,aAAO,EAAE,UAAU,MAAM,KAAK,MAAM,OAAO,EAAE;AAAA,IAC/C;AAAA,IAEA,MAAM,QAAQ,MAAsC;AAClD,UAAI,KAAK,OAAO,WAAW,EAAG,QAAO,CAAC;AAEtC,YAAM,OAAO;AAAA,QACX,MAAM,GACH,KAAK,aAAa,EAClB,OAAO,eAAe,EACtB,GAAG,gBAAgB,KAAK,QAAQ,EAChC,GAAG,WAAW,KAAK,MAAM,CAAC;AAAA,MAC/B;AAEA,YAAM,WAAqB,CAAC;AAC5B,iBAAW,OAAO,MAAM;AACtB,cAAM,YACJ,KAAK,WAAW,YACZ,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,IAAI,YAAY,KAAK,QAAQ,CAAC,CAAC,IAC/C,IAAI;AAEV,cAAM,EAAE,MAAM,IAAI,MAAM,GACrB,KAAK,aAAa,EAClB,OAAO;AAAA,UACN,OAAO;AAAA,UACP,UAAU;AAAA,UACV,cAAc;AAAA,UACd,kBAAkB;AAAA;AAAA,UAElB,cAAc,IAAI,KAAK,GAAG;AAAA,UAC1B,YAAY;AAAA,UACZ,YAAY,IAAI,KAAK,GAAG;AAAA,QAC1B,CAAC,EACA,GAAG,MAAM,IAAI,EAAE,EACf,GAAG,gBAAgB,KAAK,QAAQ;AACnC,YAAI,MAAO,OAAM,IAAI,MAAM,aAAa,MAAM,OAAO,EAAE;AACvD,iBAAS,KAAK,IAAI,EAAE;AAAA,MACtB;AACA,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,UAAU,MAAoC;AAGlD,YAAM,EAAE,MAAM,IAAI,MAAM,GAAG,IAAI,mBAAmB;AAClD,UAAI,MAAO,OAAM,IAAI,MAAM,aAAa,MAAM,OAAO,EAAE;AACvD,aAAO,CAAC;AAAA,IACV;AAAA,IAEA,MAAM,OAAO,OAAe,KAAwC;AAClE,YAAM,UAAU;AAAA,QACd,MAAM,GAAG,KAAK,aAAa,EAAE,OAAO,EAAE,GAAG,MAAM,KAAK,EAAE,YAAY;AAAA,MACpE;AACA,UAAI,YAAY,KAAM,QAAO;AAE7B,UAAI,QAAQ,UAAU,UAAU;AAC9B,cAAM,OAAO;AAAA,UACX,MAAM,GACH,KAAK,aAAa,EAClB,OAAO,EAAE,OAAO,YAAY,YAAY,IAAI,GAAG,EAAE,CAAC,EAClD,GAAG,MAAM,KAAK,EACd,GAAG,SAAS,QAAQ,EACpB,OAAO;AAAA,QACZ;AACA,cAAM,WAAW,KAAK,CAAC;AACvB,eAAO,MAAM,YAAY,OAAO;AAAA,MAClC;AAEA,UAAI,QAAQ,UAAU,aAAa,QAAQ,UAAU,WAAW;AAG9D,cAAM,EAAE,MAAM,IAAI,MAAM,GACrB,KAAK,oBAAoB,EACzB,OAAO,EAAE,QAAQ,OAAO,cAAc,IAAI,GAAG,EAAE,CAAC;AACnD,YAAI,MAAO,OAAM,IAAI,MAAM,aAAa,MAAM,OAAO,EAAE;AAAA,MACzD;AACA,aAAO,MAAM,OAAO;AAAA,IACtB;AAAA,IAEA,MAAM,cAAc,UAAwC;AAC1D,YAAM,OAAO;AAAA,QACX,MAAM,GAAG,KAAK,aAAa,EAAE,OAAO,EAAE,GAAG,gBAAgB,QAAQ;AAAA,MACnE;AACA,aAAO,KAAK,IAAI,KAAK;AAAA,IACvB;AAAA,IAEA,MAAM,mBAAmB,UAAuC;AAI9D,YAAM,OAAO;AAAA,QACX,MAAM,GACH,KAAK,oBAAoB,EACzB,OAAO,mDAAmD,EAC1D,GAAG,4BAA4B,QAAQ;AAAA,MAC5C;AAIA,aAAO,KACJ,OAAO,CAAC,QAAQ,IAAI,YAAY,aAAa,IAAI,EACjD,IAAI,CAAC,SAAS;AAAA,QACb,OAAO,IAAI;AAAA,QACX,SAAS,IAAI,YAAY,YAAY;AAAA,MACvC,EAAE;AAAA,IACN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAWA,UAAU,OAAe,UAAkC;AACzD,YAAM,UAAU,GACb,QAAQ,cAAc,KAAK,EAAE,EAC7B;AAAA,QACC;AAAA,QACA;AAAA,UACE,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,OAAO;AAAA,UACP,QAAQ,SAAS,KAAK;AAAA,QACxB;AAAA,QACA,MAAM;AACJ,mBAAS;AAAA,QACX;AAAA,MACF,EACC,UAAU;AAEb,UAAI,OAAO;AACX,aAAO,MAAM;AACX,YAAI,CAAC,KAAM;AACX,eAAO;AAIP,aAAK,GAAG,cAAc,OAAO,EAAE,MAAM,MAAM,MAAS;AAAA,MACtD;AAAA,IACF;AAAA,IAEA,MAAM,cAAc,QAAsC;AACxD,YAAM,EAAE,MAAM,IAAI,MAAM,GAAG,KAAK,iBAAiB,EAAE,OAAO;AAAA,QACxD,kBAAkB,OAAO;AAAA,QACzB,QAAQ,OAAO;AAAA,QACf,WAAW,OAAO;AAAA,QAClB,OAAO,OAAO;AAAA,QACd,OAAO,OAAO;AAAA,QACd,UAAU,OAAO;AAAA,QACjB,gBAAgB,OAAO;AAAA,QACvB,cAAc,OAAO;AAAA,QACrB,YAAY,IAAI,OAAO,SAAS;AAAA,MAClC,CAAC;AACD,UAAI,MAAO,OAAM,IAAI,MAAM,aAAa,MAAM,OAAO,EAAE;AAAA,IACzD;AAAA,IAEA,MAAM,2BACJ,MAC+B;AAC/B,YAAM,MAAM;AAAA,QACV,MAAM,GACH,KAAK,iBAAiB,EACtB,OAAO,EACP,GAAG,oBAAoB,IAAI,EAC3B,YAAY;AAAA,MACjB;AACA,aAAO,QAAQ,OAAO,OAAO,UAAU,GAAG;AAAA,IAC5C;AAAA,IAEA,MAAM,qBACJ,UAC+B;AAC/B,YAAM,MAAM;AAAA,QACV,MAAM,GACH,KAAK,iBAAiB,EACtB,OAAO,EACP,GAAG,aAAa,QAAQ,EACxB,YAAY;AAAA,MACjB;AACA,aAAO,QAAQ,OAAO,OAAO,UAAU,GAAG;AAAA,IAC5C;AAAA,IAEA,MAAM,eAAe,MAA0C;AAM7D,YAAM,UAAU;AAAA,QACd,MAAM,GACH,KAAK,iBAAiB,EACtB,OAAO,EACP,GAAG,aAAa,KAAK,QAAQ,EAC7B,OAAO;AAAA,MACZ;AAEA,UAAI,KAAK,MAAM,QAAQ,UAAU,KAAK,KAAK,KAAK;AAC9C,cAAM,IAAI,MAAM,0BAA0B;AAAA,MAC5C;AACA,UAAI,QAAQ,UAAU,WAAW;AAC/B,cAAM,IAAI,MAAM,sBAAsB,QAAQ,KAAK,EAAE;AAAA,MACvD;AAEA,YAAM,SAAS;AAAA,QACb,MAAM,GACH,KAAK,gBAAgB,EACrB,OAAO;AAAA,UACN,OAAO,KAAK;AAAA,UACZ,OAAO,QAAQ;AAAA,UACf,UAAU,QAAQ;AAAA,UAClB,gBAAgB,QAAQ;AAAA,UACxB,cAAc,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAMtB,QAAQ,QAAQ;AAAA,QAClB,CAAC,EACA,OAAO,EACP,OAAO;AAAA,MACZ;AAEA,YAAM,EAAE,MAAM,IAAI,MAAM,GACrB,KAAK,iBAAiB,EACtB,OAAO;AAAA,QACN,OAAO;AAAA,QACP,OAAO,KAAK;AAAA,QACZ,WAAW,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMlB,cAAc;AAAA,MAChB,CAAC,EACA,GAAG,oBAAoB,QAAQ,gBAAgB;AAClD,UAAI,MAAO,OAAM,IAAI,MAAM,aAAa,MAAM,OAAO,EAAE;AAEvD,aAAO,SAAS,MAAM;AAAA,IACxB;AAAA,IAEA,MAAM,YAAY,UAAkB,MAA6B;AAC/D,YAAM,EAAE,MAAM,IAAI,MAAM,GACrB,KAAK,iBAAiB,EACtB,OAAO,EAAE,OAAO,SAAS,CAAC,EAC1B,GAAG,aAAa,QAAQ;AAC3B,UAAI,MAAO,OAAM,IAAI,MAAM,aAAa,MAAM,OAAO,EAAE;AAAA,IACzD;AAAA,IAEA,MAAM,oBAAoB,gBAAuC;AAC/D,YAAM,EAAE,MAAM,IAAI,MAAM,GACrB,KAAK,iBAAiB,EAItB,OAAO,EAAE,cAAc,QAAQ,CAAC,EAChC,GAAG,oBAAoB,cAAc;AACxC,UAAI,MAAO,OAAM,IAAI,MAAM,aAAa,MAAM,OAAO,EAAE;AAAA,IACzD;AAAA,IAEA,MAAM,UAAU,UAAgD;AAC9D,YAAM,MAAM;AAAA,QACV,MAAM,GACH,KAAK,gBAAgB,EACrB,OAAO,EACP,GAAG,MAAM,QAAQ,EACjB,YAAY;AAAA,MACjB;AACA,aAAO,QAAQ,OAAO,OAAO,SAAS,GAAG;AAAA,IAC3C;AAAA,IAEA,MAAM,YAAY,MAA+C;AAC/D,YAAM,MAAM;AAAA,QACV,MAAM,GACH,KAAK,gBAAgB,EACrB,OAAO;AAAA,UACN,cAAc,KAAK;AAAA,UACnB,gBAAgB,KAAK;AAAA,UACrB,QAAQ,KAAK;AAAA,UACb,mBAAmB,IAAI,KAAK,GAAG;AAAA,QACjC,CAAC,EACA,GAAG,MAAM,KAAK,QAAQ,EACtB,OAAO,EACP,YAAY;AAAA,MACjB;AACA,aAAO,QAAQ,OAAO,OAAO,SAAS,GAAG;AAAA,IAC3C;AAAA,IAEA,MAAM,aAAa,UAAkB,KAA4B;AAG/D,YAAM,EAAE,MAAM,IAAI,MAAM,GACrB,KAAK,gBAAgB,EACrB,OAAO,EAAE,YAAY,IAAI,GAAG,EAAE,CAAC,EAC/B,GAAG,MAAM,QAAQ,EACjB,GAAG,cAAc,IAAI;AACxB,UAAI,MAAO,OAAM,IAAI,MAAM,aAAa,MAAM,OAAO,EAAE;AAAA,IACzD;AAAA,IAEA,MAAM,YAAY,OAAyC;AACzD,YAAM,QAAQ,GAAG,KAAK,gBAAgB,EAAE,OAAO;AAC/C,YAAM,OAAO;AAAA,QACX,UAAU,SAAY,MAAM,QAAQ,MAAM,MAAM,GAAG,SAAS,KAAK;AAAA,MACnE;AACA,aAAO,KAAK,IAAI,QAAQ;AAAA,IAC1B;AAAA,EACF;AACF;","names":["iso"]}
|
|
1
|
+
{"version":3,"sources":["../../src/supabase/realtime.ts","../../src/supabase/index.ts"],"sourcesContent":["import type { SupabaseClient } from \"@supabase/supabase-js\";\nimport type { DeliveredResult } from \"@byollm/protocol\";\nimport {\n NoRunnerAvailableError,\n ResultTimeoutError,\n type PollingDeliveryDeps,\n type ResultDelivery,\n type WaitOptions,\n labelFallback,\n} from \"../delivery.js\";\n\nconst DEFAULT_TIMEOUT_MS = 5 * 60_000;\n/**\n * How long a sustained no-runner signal must persist before it is believed.\n * A daemon restarting must not fail every job in flight.\n */\nconst NO_RUNNER_GRACE_MS = 10_000;\n\n/**\n * Realtime delivery: the app learns a job finished when Postgres says so.\n *\n * byollm_003 Rev 1 requires the server→app path be an explicit channel rather\n * than an implied in-request `await`. Polling is the portable default;\n * this is the one worth having when the app is already on Supabase, because\n * a result arrives in milliseconds instead of on the next poll tick.\n *\n * The no-runner watch still polls, deliberately: runner liveness is a\n * *derived* signal (nobody with matching capability has heartbeated lately),\n * and there is no row change to subscribe to for \"something stopped\n * happening\".\n */\nexport function supabaseRealtimeDelivery(\n client: SupabaseClient,\n): (deps: PollingDeliveryDeps) => ResultDelivery {\n return (deps) => new SupabaseRealtimeDelivery(client, deps);\n}\n\nclass SupabaseRealtimeDelivery implements ResultDelivery {\n readonly #client: SupabaseClient;\n readonly #deps: PollingDeliveryDeps;\n\n constructor(client: SupabaseClient, deps: PollingDeliveryDeps) {\n this.#client = client;\n this.#deps = deps;\n }\n\n async waitFor(\n jobId: string,\n options: WaitOptions = {},\n ): Promise<DeliveredResult> {\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n // Read first. The job may already be terminal, and subscribing to a\n // channel for an event that has already happened waits forever.\n const current = await this.#deps.read(jobId);\n if (current && isTerminal(current.state)) return current;\n\n // Declared before the subscription so the channel callback closes over a\n // `settled` that already exists. Every async path below routes its failure\n // here: a rejection that escapes this object becomes an unhandled\n // rejection, and an unhandled rejection ends the process.\n const settled = Promise.withResolvers<DeliveredResult>();\n // Keyed by job, not held as one field — see `#resolvers`.\n this.#resolvers.set(jobId, settled.resolve);\n\n const channel = this.#client.channel(`byollm_job_${jobId}`).on(\n \"postgres_changes\",\n {\n event: \"UPDATE\",\n schema: \"public\",\n table: \"byollm_jobs\",\n filter: `id=eq.${jobId}`,\n },\n () => {\n this.#check(jobId).catch(settled.reject);\n },\n );\n\n // `subscribe()` returns the channel, not a promise — awaiting it would be\n // a no-op that reads as if it waited for the subscription to be live.\n channel.subscribe();\n\n // A second read after subscribing closes the race where the job finished\n // between the first read and the subscription taking effect.\n this.#check(jobId).catch(settled.reject);\n\n const timer = setTimeout(() => {\n settled.reject(new ResultTimeoutError(jobId, timeoutMs));\n }, timeoutMs);\n\n const watcher = this.#watchAvailability(jobId, options, settled);\n const abort = (): void => {\n settled.reject(new Error(\"wait aborted\"));\n };\n options.signal?.addEventListener(\"abort\", abort, { once: true });\n\n try {\n return await settled.promise;\n } finally {\n clearTimeout(timer);\n clearInterval(watcher);\n this.#resolvers.delete(jobId);\n options.signal?.removeEventListener(\"abort\", abort);\n await this.#client.removeChannel(channel);\n }\n }\n\n /**\n * One resolver per job in flight — P0, 2026-09-02.\n *\n * This was a single field, `#resolve`, assigned by every `waitFor`. One\n * delivery object serves a whole app, so two concurrent waits meant the\n * second assignment clobbered the first, and then:\n *\n * 1. `waitFor(A)` sets the resolver.\n * 2. `waitFor(B)` overwrites it.\n * 3. A's row event arrives, `#check(A)` reads A's result — and resolves\n * **B's** promise with it.\n * 4. A never resolves and waits out its timeout.\n *\n * So an app awaiting two jobs at once got one answer under the wrong job\n * id, with the wrong text, silently — and a spurious timeout beside it. No\n * error anywhere; the failure is that the caller believes it.\n *\n * A map, and the entry is removed in the same `finally` that tears down the\n * channel. A resolver that outlived its wait would be a leak that also\n * resolves a promise nobody is holding.\n */\n readonly #resolvers = new Map<string, (result: DeliveredResult) => void>();\n\n async #check(jobId: string): Promise<void> {\n const current = await this.#deps.read(jobId);\n // The job's own resolver. `#check` has always taken a `jobId` and read\n // the right row; what it did with the answer was the bug.\n if (current && isTerminal(current.state)) {\n this.#resolvers.get(jobId)?.(current);\n }\n }\n\n /** Poll runner liveness; there is no row event for \"nothing is happening\". */\n #watchAvailability(\n jobId: string,\n options: WaitOptions,\n settled: PromiseWithResolvers<DeliveredResult>,\n ): NodeJS.Timeout {\n let noRunnerSince: number | null = null;\n\n return setInterval(() => {\n // `.catch`, not `void`. Two things in here can reject — the store read\n // and the caller's own `onNoRunner` — and discarding either made a\n // transient store error, or an app whose fallback throws, terminate the\n // process. The caller is awaiting `result()`; that is where a failure\n // belongs, and it is what the polling channel already does by virtue of\n // running inside the awaited chain. A delivery adapter must not change\n // what a failure means.\n (async () => {\n // No instrument, no question — see `PollingDeliveryDeps`. On the\n // cloud lane `runnerAvailability` refuses rather than reporting a\n // zero it cannot see, and this timer used to reject the caller's\n // `result()` with that refusal every two seconds.\n const availability = await this.#deps.availability?.(jobId);\n if (\n availability === undefined ||\n availability.available ||\n availability.blocked\n ) {\n noRunnerSince = null;\n return;\n }\n noRunnerSince ??= Date.now();\n if (Date.now() - noRunnerSince < NO_RUNNER_GRACE_MS) return;\n\n const reason = availability.reason ?? \"no-runner-online\";\n const substitute = await options.onNoRunner?.(reason);\n if (substitute !== undefined) {\n // The same labelling the polling channel applies, from the same\n // function — {@link MUSTS.FALLBACK_LABELED} cannot depend on which\n // store an app happened to choose.\n settled.resolve(labelFallback(jobId, substitute));\n } else {\n settled.reject(new NoRunnerAvailableError(jobId, reason));\n }\n })().catch(settled.reject);\n }, 2_000);\n }\n}\n\nfunction isTerminal(state: string): boolean {\n return (\n state === \"ok\" ||\n state === \"error\" ||\n state === \"canceled\" ||\n state === \"expired\"\n );\n}\n","import type { SupabaseClient } from \"@supabase/supabase-js\";\nimport type {\n Audience,\n Capability,\n JobOutcome,\n JobState,\n PublicIdentity,\n} from \"@byollm/protocol\";\nimport type {\n StoredJobInput,\n JobRecord,\n PairingRecord,\n RunnerRecord,\n} from \"../records.js\";\nimport type {\n ApproveArgs,\n ByollmStore,\n ClaimArgs,\n AdoptArgs,\n CompleteArgs,\n CompleteResult,\n LeaseRef,\n ReleaseArgs,\n RenewArgs,\n RenewResult,\n TouchArgs,\n} from \"../store.js\";\n\n/**\n * `@byollm/server/supabase` — the first-party Supabase adapter.\n *\n * The piece the of-tomorrow-framework's runner module consumes verbatim.\n * Migrations ship in `supabase/migrations`; the atomic claim lives in a\n * `security definer` RPC using `FOR UPDATE SKIP LOCKED`, and the audience\n * rules are mirrored in SQL so the server refuses independently of the daemon\n * (byollm_003 §Server-side MUSTs).\n *\n * Requires the **service role** key: a runner authenticates with a bearer\n * token of its own, which is not a Supabase session, so the protocol handler\n * cannot run under RLS as the runner's user. RLS still governs everything the\n * *browser* does — the app-side policies in the migration are what protect\n * one user's jobs from another.\n *\n * @packageDocumentation\n */\n\n/** Row shape of `byollm_jobs`. */\ninterface JobRow {\n id: string;\n kind: string;\n envelope: unknown;\n size_class: \"small\" | \"medium\" | \"large\" | \"unbounded\";\n /**\n * Typed as {@link Audience} rather than re-spelled, after a spelling of it\n * here outlived the enum by a week.\n *\n * `public` was removed on 2026-08-26 and this column may still hold it in a\n * database written before then. Nothing in this adapter validates a row —\n * `kind` and `envelope` are both plain casts — so this is a contract with\n * the schema, not a check, and a legacy row is a **migration** obligation\n * rather than a runtime one. Recorded so the migration is written on\n * purpose: a `public` row must be resolved by the deploy, never quietly\n * reinterpreted here as something narrower.\n */\n audience: Audience;\n /** byollm_016 Amendment L. Null for every job that named no purpose. */\n purpose: string | null;\n owner: string;\n audience_allow: string[] | null;\n depends_on: string[];\n state: JobState;\n lease_id: string | null;\n lease_runner: string | null;\n completed_by_lease_id: string | null;\n lease_expires_at: string | null;\n claimable_at: string | null;\n ttl_ms: number;\n deadline_at: string | null;\n refused_by: string[];\n attempts: number;\n outcome: JobOutcome | null;\n provenance: JobRecord[\"provenance\"];\n created_at: string;\n updated_at: string;\n}\n\n/** Row shape of `byollm_runners`. */\ninterface RunnerRow {\n id: string;\n owner: string;\n label: string;\n platform: \"darwin\" | \"linux\" | \"win32\";\n daemon_version: string;\n capabilities: Capability[];\n device: PublicIdentity;\n paused: boolean;\n revoked_at: string | null;\n last_heartbeat_at: string;\n created_at: string;\n}\n\n/** Row shape of `byollm_pairings`. */\ninterface PairingRow {\n device_code_hash: string;\n user_code: string;\n state: \"pending\" | \"approved\" | \"denied\";\n owner: string | null;\n runner_id: string | null;\n collected_at: string | null;\n label: string;\n platform: \"darwin\" | \"linux\" | \"win32\";\n daemon_version: string;\n capabilities: Capability[];\n device: PublicIdentity;\n expires_at: string;\n created_at: string;\n}\n\nconst ms = (iso: string | null): number | null =>\n iso === null ? null : Date.parse(iso);\n\nconst iso = (epochMs: number): string => new Date(epochMs).toISOString();\n\nfunction toJob(row: JobRow): JobRecord {\n const leaseExpires = ms(row.lease_expires_at);\n return {\n id: row.id,\n kind: row.kind as JobRecord[\"kind\"],\n envelope: row.envelope as JobRecord[\"envelope\"],\n sizeClass: row.size_class,\n audience: row.audience,\n purpose: row.purpose ?? undefined,\n owner: row.owner,\n audienceAllow: row.audience_allow ?? undefined,\n dependsOn: row.depends_on,\n state: row.state,\n completedByLeaseId: row.completed_by_lease_id ?? null,\n lease:\n // Keyed on the lease id, not the runner. A relayed grant has no runner\n // row to point at (see AdoptArgs), and reading the lease as absent\n // because `lease_runner` is null would make an actively-held job look\n // claimable — the exact bug `adopt` exists to prevent.\n leaseExpires !== null && row.lease_id !== null\n ? {\n id: row.lease_id,\n runnerId: row.lease_runner ?? \"\",\n expiresAt: leaseExpires,\n }\n : null,\n createdAt: Date.parse(row.created_at),\n claimableAt: ms(row.claimable_at),\n ttlMs: row.ttl_ms,\n deadlineAt: ms(row.deadline_at),\n refusedBy: row.refused_by,\n attempts: row.attempts,\n outcome: row.outcome,\n provenance: row.provenance,\n updatedAt: Date.parse(row.updated_at),\n };\n}\n\n/**\n * A PostgREST filter matching exactly these (job, lease) pairs.\n *\n * Not two `IN` lists: `id IN (…) AND lease_id IN (…)` is a cross product, and\n * while UUID uniqueness makes a mismatch improbable, \"improbable\" is not the\n * property a lease check should rest on. This says what it means.\n */\nconst leasePairs = (leases: readonly LeaseRef[]): string =>\n leases.map((l) => `and(id.eq.${l.jobId},lease_id.eq.${l.leaseId})`).join(\",\");\n\nfunction toRunner(row: RunnerRow): RunnerRecord {\n return {\n id: row.id,\n owner: row.owner,\n label: row.label,\n platform: row.platform,\n daemonVersion: row.daemon_version,\n capabilities: row.capabilities,\n device: row.device,\n paused: row.paused,\n revokedAt: ms(row.revoked_at),\n lastHeartbeatAt: Date.parse(row.last_heartbeat_at),\n createdAt: Date.parse(row.created_at),\n };\n}\n\nfunction toPairing(row: PairingRow): PairingRecord {\n return {\n deviceCodeHash: row.device_code_hash,\n userCode: row.user_code,\n state: row.state,\n owner: row.owner,\n runnerId: row.runner_id,\n // Collected when it has a timestamp. This was `runner_token_once ===\n // null` — a nulled token standing in for a fact about delivery, which is\n // one field doing two jobs (cloud_008 §2.4a).\n collected: row.collected_at !== null,\n label: row.label,\n platform: row.platform,\n daemonVersion: row.daemon_version,\n capabilities: row.capabilities,\n device: row.device,\n expiresAt: Date.parse(row.expires_at),\n createdAt: Date.parse(row.created_at),\n };\n}\n\nexport interface SupabaseStoreOptions {\n /** A client built with the **service role** key. */\n readonly client: SupabaseClient;\n /** Default TTL for a job once claimable. */\n readonly defaultTtlMs?: number;\n}\n\n/** Build the Supabase-backed store. */\nexport function supabaseStore(options: SupabaseStoreOptions): ByollmStore {\n const db = options.client;\n const defaultTtlMs = options.defaultTtlMs ?? 15 * 60_000;\n\n /**\n * Narrow one PostgREST response, or throw with the Postgres message.\n *\n * `supabase-js` types rows as `any` unless the project has generated\n * database types, so the assertion has to live somewhere. Confining it to\n * these two helpers — against the row interfaces declared above — keeps\n * every call site typed and leaves exactly one place to review.\n */\n /* eslint-disable @typescript-eslint/no-unnecessary-type-parameters --\n T appears only in the return type because these helpers *are* the cast.\n That is the point: one reviewable place where PostgREST's `any` becomes\n one of the row interfaces above. */\n function unwrap<T>(result: {\n data: unknown;\n error: { message: string } | null;\n }): T {\n if (result.error) throw new Error(`supabase: ${result.error.message}`);\n if (result.data === null || result.data === undefined) {\n throw new Error(\"supabase: no data returned\");\n }\n return result.data as T;\n }\n\n /** Same, but a missing row is a legitimate answer rather than an error. */\n function unwrapMaybe<T>(result: {\n data: unknown;\n error: { message: string } | null;\n }): T | null {\n if (result.error) throw new Error(`supabase: ${result.error.message}`);\n return (result.data ?? null) as T | null;\n }\n /* eslint-enable @typescript-eslint/no-unnecessary-type-parameters */\n\n return {\n // -- jobs ---------------------------------------------------------------\n\n async create(input: StoredJobInput, now: number): Promise<JobRecord> {\n const dependsOn = [...(input.dependsOn ?? [])];\n\n // A job with dependencies starts blocked; the trigger sets\n // `claimable_at` when the last one reaches `ok`, which is where its TTL\n // clock starts.\n let claimableAt: string | null = iso(now);\n if (dependsOn.length > 0) {\n const deps = unwrap<JobRow[]>(\n await db.from(\"byollm_jobs\").select(\"id,state\").in(\"id\", dependsOn),\n ) as { id: string; state: JobState }[];\n const allDone =\n deps.length === dependsOn.length &&\n deps.every((dep) => dep.state === \"ok\");\n claimableAt = allDone ? iso(now) : null;\n }\n\n const row = {\n id: input.id,\n kind: input.kind,\n envelope: input.envelope,\n size_class: input.sizeClass,\n audience: input.audience ?? \"private\",\n purpose: input.purpose ?? null,\n owner: input.owner,\n audience_allow: input.audienceAllow ? [...input.audienceAllow] : null,\n depends_on: dependsOn,\n claimable_at: claimableAt,\n ttl_ms: input.ttlMs ?? defaultTtlMs,\n deadline_at:\n input.deadlineAt === undefined ? null : iso(input.deadlineAt),\n };\n\n // Idempotent by caller-supplied id, matching the reference store: an\n // app's retry must not duplicate work.\n const inserted = unwrapMaybe<JobRow>(\n await db\n .from(\"byollm_jobs\")\n .upsert(row, { onConflict: \"id\", ignoreDuplicates: true })\n .select()\n .maybeSingle(),\n );\n\n if (inserted) return toJob(inserted);\n const existing = unwrap<JobRow>(\n await db.from(\"byollm_jobs\").select().eq(\"id\", input.id).single(),\n );\n return toJob(existing);\n },\n\n async get(jobId: string): Promise<JobRecord | null> {\n const row = unwrapMaybe<JobRow>(\n await db.from(\"byollm_jobs\").select().eq(\"id\", jobId).maybeSingle(),\n );\n return row === null ? null : toJob(row);\n },\n\n async claim(args: ClaimArgs): Promise<JobRecord[]> {\n // One RPC, one transaction, `FOR UPDATE SKIP LOCKED` inside\n // ({@link MUSTS.CLAIM_ATOMIC}).\n const rows = unwrap<JobRow[]>(\n await db.rpc(\"byollm_claim_jobs\", {\n p_runner_id: args.runnerId,\n p_capabilities: args.capabilities,\n p_max: args.max,\n p_lease_ms: args.leaseMs,\n }),\n );\n return rows.map(toJob);\n },\n\n async renewLeases(args: RenewArgs): Promise<RenewResult> {\n await db.rpc(\"byollm_expire_due\");\n if (args.leases.length === 0) return { renewed: [], lost: [] };\n\n const expiresAt = iso(args.now + args.leaseMs);\n const renewedRows = unwrap<JobRow[]>(\n await db\n .from(\"byollm_jobs\")\n .update({\n state: \"running\",\n lease_expires_at: expiresAt,\n updated_at: iso(args.now),\n })\n .eq(\"lease_runner\", args.runnerId)\n .or(leasePairs(args.leases))\n .in(\"state\", [\"claimed\", \"running\"])\n .select(\"id\"),\n );\n\n const renewedIds = new Set(renewedRows.map((row) => row.id));\n return {\n renewed: renewedRows.map((row) => ({\n jobId: row.id,\n expiresAt: args.now + args.leaseMs,\n })),\n // Anything the runner thinks it holds but did not renew is gone,\n // named by the grant it asked about rather than by a bare id — V1-3.\n lost: args.leases.filter((lease) => !renewedIds.has(lease.jobId)),\n };\n },\n\n async adopt(args: AdoptArgs): Promise<JobRecord | null> {\n // The predicates are the guard, evaluated in the database rather than\n // read-then-written here: `state in (queued, claimed)` is what makes\n // adopting a terminal or expired job impossible under concurrency.\n const rows = unwrap<JobRow[]>(\n await db\n .from(\"byollm_jobs\")\n .update({\n state: \"claimed\",\n lease_id: args.leaseId,\n // Left null on purpose: `lease_runner` is a foreign key into\n // `byollm_runners`, and a relayed device has no row there. See\n // AdoptArgs — the site records the grant, not a machine it has\n // no relationship with.\n lease_runner: null,\n lease_expires_at: iso(args.expiresAt),\n updated_at: iso(args.now),\n })\n .eq(\"id\", args.jobId)\n .in(\"state\", [\"queued\", \"claimed\"])\n .select(),\n );\n const written = rows[0];\n return written === undefined ? null : toJob(written);\n },\n\n async complete(args: CompleteArgs): Promise<CompleteResult> {\n const state: JobState =\n args.outcome.outcome === \"ok\"\n ? \"ok\"\n : args.outcome.outcome === \"canceled\"\n ? \"canceled\"\n : \"error\";\n\n // The `in('state', ...)` predicate is the idempotency guard: a job that\n // already reached a terminal state matches nothing, so the first\n // outcome wins ({@link MUSTS.RESULT_IDEMPOTENT}). The `lease_runner`\n // predicate is {@link MUSTS.LEASE_HONORED}.\n // The `in('state', ...)` predicate is the idempotency guard. The\n // second predicate is LEASE_HONORED, and which column carries it\n // depends on the plane: a direct runner is named by id, a relayed\n // grant only by its lease. Built as a query rather than branched into\n // two, so there is one update statement and no chance of the two\n // drifting.\n let update = db\n .from(\"byollm_jobs\")\n .update({\n state,\n lease_runner: null,\n lease_expires_at: null,\n // Which grant recorded it, kept after the lease is dropped — §3.6.\n completed_by_lease_id:\n args.holder.by === \"lease\" ? args.holder.leaseId : null,\n outcome: args.outcome,\n provenance: args.provenance,\n updated_at: iso(args.now),\n })\n .eq(\"id\", args.jobId)\n .in(\"state\", [\"claimed\", \"running\"]);\n update =\n args.holder.by === \"runner\"\n ? update.eq(\"lease_runner\", args.holder.runnerId)\n : update.eq(\"lease_id\", args.holder.leaseId);\n const rows = unwrap<JobRow[]>(await update.select());\n\n const written = rows[0];\n if (written === undefined) {\n const current = unwrapMaybe<JobRow>(\n await db\n .from(\"byollm_jobs\")\n .select()\n .eq(\"id\", args.jobId)\n .maybeSingle(),\n );\n const job = current === null ? null : toJob(current);\n // Terminal before holder — cloud_008 §3.6. The update above matched\n // nothing for one of two reasons, and the caller is owed the\n // difference: the device that already recorded this job hears\n // \"duplicate\", and anybody else hears the same refusal they would get\n // for a job that is not terminal, so a job id is not a terminality\n // probe.\n //\n // Decided on the row that is there rather than by a second predicate,\n // because the update is the atomic part and this is only a diagnosis\n // of why it matched nothing.\n const duplicate =\n job !== null &&\n job.provenance?.runnerId === args.runnerId &&\n (job.state === \"ok\" ||\n job.state === \"error\" ||\n job.state === \"canceled\") &&\n args.holder.by === \"lease\" &&\n job.completedByLeaseId !== null &&\n job.completedByLeaseId === args.holder.leaseId;\n return duplicate\n ? { accepted: false, duplicate: true, job }\n : { accepted: false, job };\n }\n return { accepted: true, job: toJob(written) };\n },\n\n async release(args: ReleaseArgs): Promise<string[]> {\n if (args.leases.length === 0) return [];\n\n const held = unwrap<{ id: string; refused_by: string[] }[]>(\n await db\n .from(\"byollm_jobs\")\n .select(\"id,refused_by\")\n .eq(\"lease_runner\", args.runnerId)\n .or(leasePairs(args.leases)),\n );\n\n const released: string[] = [];\n for (const row of held) {\n const refusedBy =\n args.reason === \"refused\"\n ? [...new Set([...row.refused_by, args.runnerId])]\n : row.refused_by;\n\n const { error } = await db\n .from(\"byollm_jobs\")\n .update({\n state: \"queued\",\n lease_id: null,\n lease_runner: null,\n lease_expires_at: null,\n // Newly available again, so the TTL clock restarts.\n claimable_at: iso(args.now),\n refused_by: refusedBy,\n updated_at: iso(args.now),\n })\n .eq(\"id\", row.id)\n .eq(\"lease_runner\", args.runnerId);\n if (error) throw new Error(`supabase: ${error.message}`);\n released.push(row.id);\n }\n return released;\n },\n\n async expireDue(_now: number): Promise<JobRecord[]> {\n // The sweep is a single idempotent SQL function; it reports a count\n // rather than rows, and the caller only needs to know it ran.\n const { error } = await db.rpc(\"byollm_expire_due\");\n if (error) throw new Error(`supabase: ${error.message}`);\n return [];\n },\n\n async cancel(jobId: string, now: number): Promise<JobRecord | null> {\n const current = unwrapMaybe<JobRow>(\n await db.from(\"byollm_jobs\").select().eq(\"id\", jobId).maybeSingle(),\n );\n if (current === null) return null;\n\n if (current.state === \"queued\") {\n const rows = unwrap<JobRow[]>(\n await db\n .from(\"byollm_jobs\")\n .update({ state: \"canceled\", updated_at: iso(now) })\n .eq(\"id\", jobId)\n .eq(\"state\", \"queued\")\n .select(),\n );\n const canceled = rows[0];\n return toJob(canceled ?? current);\n }\n\n if (current.state === \"claimed\" || current.state === \"running\") {\n // Held by a runner: the cancel travels on the next heartbeat and the\n // runner reports `canceled` itself.\n const { error } = await db\n .from(\"byollm_job_cancels\")\n .upsert({ job_id: jobId, requested_at: iso(now) });\n if (error) throw new Error(`supabase: ${error.message}`);\n }\n return toJob(current);\n },\n\n async listClaimedBy(runnerId: string): Promise<JobRecord[]> {\n const rows = unwrap<JobRow[]>(\n await db.from(\"byollm_jobs\").select().eq(\"lease_runner\", runnerId),\n );\n return rows.map(toJob);\n },\n\n async listCancelRequests(runnerId: string): Promise<LeaseRef[]> {\n // The lease comes back with the row — V1-3. A bare job id is ambiguous\n // to a daemon serving two sites that chose the same one, and the lease\n // is already on the joined row.\n const rows = unwrap<{ job_id: string; byollm_jobs: unknown }[]>(\n await db\n .from(\"byollm_job_cancels\")\n .select(\"job_id, byollm_jobs!inner(lease_runner, lease_id)\")\n .eq(\"byollm_jobs.lease_runner\", runnerId),\n ) as {\n job_id: string;\n byollm_jobs: { lease_id: string | null };\n }[];\n return rows\n .filter((row) => row.byollm_jobs.lease_id !== null)\n .map((row) => ({\n jobId: row.job_id,\n leaseId: row.byollm_jobs.lease_id ?? \"\",\n }));\n },\n\n // -- pairing and runners -------------------------------------------------\n\n /**\n * The push seam (byollm_009 §8.3), over Postgres Realtime.\n *\n * Native here, which is the point of requiring it of every adapter: the\n * backend that can push does, the one that cannot polls, and the\n * interface does not change again when streaming arrives.\n */\n subscribe(jobId: string, onChange: () => void): () => void {\n const channel = db\n .channel(`byollm_job_${jobId}`)\n .on(\n \"postgres_changes\",\n {\n event: \"UPDATE\",\n schema: \"public\",\n table: \"byollm_jobs\",\n filter: `id=eq.${jobId}`,\n },\n () => {\n onChange();\n },\n )\n .subscribe();\n\n let live = true;\n return () => {\n if (!live) return;\n live = false;\n // `removeChannel` is async and nothing awaits an unsubscribe, so the\n // rejection is routed rather than dropped — an unhandled one here\n // would end the process (see the Realtime delivery channel).\n void db.removeChannel(channel).catch(() => undefined);\n };\n },\n\n async createPairing(record: PairingRecord): Promise<void> {\n const { error } = await db.from(\"byollm_pairings\").insert({\n device_code_hash: record.deviceCodeHash,\n device: record.device,\n user_code: record.userCode,\n state: record.state,\n label: record.label,\n platform: record.platform,\n daemon_version: record.daemonVersion,\n capabilities: record.capabilities,\n expires_at: iso(record.expiresAt),\n });\n if (error) throw new Error(`supabase: ${error.message}`);\n },\n\n async getPairingByDeviceCodeHash(\n hash: string,\n ): Promise<PairingRecord | null> {\n const row = unwrapMaybe<PairingRow>(\n await db\n .from(\"byollm_pairings\")\n .select()\n .eq(\"device_code_hash\", hash)\n .maybeSingle(),\n );\n return row === null ? null : toPairing(row);\n },\n\n async getPairingByUserCode(\n userCode: string,\n ): Promise<PairingRecord | null> {\n const row = unwrapMaybe<PairingRow>(\n await db\n .from(\"byollm_pairings\")\n .select()\n .eq(\"user_code\", userCode)\n .maybeSingle(),\n );\n return row === null ? null : toPairing(row);\n },\n\n async approvePairing(args: ApproveArgs): Promise<RunnerRecord> {\n // Deliberately *not* the browser RPC: this path runs under the service\n // role with an `owner` the caller has already authenticated. Apps using\n // Supabase Auth in the browser should call `byollm_approve_pairing`\n // instead, which takes the owner from `auth.uid()` and cannot be told\n // who the user is.\n const pairing = unwrap<PairingRow>(\n await db\n .from(\"byollm_pairings\")\n .select()\n .eq(\"user_code\", args.userCode)\n .single(),\n );\n\n if (Date.parse(pairing.expires_at) <= args.now) {\n throw new Error(\"pairing code has expired\");\n }\n if (pairing.state !== \"pending\") {\n throw new Error(`pairing is already ${pairing.state}`);\n }\n\n const runner = unwrap<RunnerRow>(\n await db\n .from(\"byollm_runners\")\n .insert({\n owner: args.owner,\n label: pairing.label,\n platform: pairing.platform,\n daemon_version: pairing.daemon_version,\n capabilities: pairing.capabilities,\n // Carried from the pairing, exactly as the SQL RPC does. There\n // are two approval paths — this service-role one and\n // `byollm_approve_pairing` for browser callers — and a field\n // added to one and not the other produces a runner that is\n // correct through one door and broken through the other.\n device: pairing.device,\n })\n .select()\n .single(),\n );\n\n const { error } = await db\n .from(\"byollm_pairings\")\n .update({\n state: \"approved\",\n owner: args.owner,\n runner_id: runner.id,\n // Marks the approval collectable — cloud_008 §2.4. The column held\n // a bearer token; it now holds a marker, and the next migration\n // renames it. Written as a constant rather than left null because\n // `collected` reads `=== null`, and a schema change and a code\n // change landing in one step is how a rollback strands rows.\n collected_at: null,\n })\n .eq(\"device_code_hash\", pairing.device_code_hash);\n if (error) throw new Error(`supabase: ${error.message}`);\n\n return toRunner(runner);\n },\n\n async denyPairing(userCode: string, _now: number): Promise<void> {\n const { error } = await db\n .from(\"byollm_pairings\")\n .update({ state: \"denied\" })\n .eq(\"user_code\", userCode);\n if (error) throw new Error(`supabase: ${error.message}`);\n },\n\n async consumePairingToken(deviceCodeHash: string): Promise<void> {\n const { error } = await db\n .from(\"byollm_pairings\")\n // The database's clock, not this process's — the same rule the\n // relay's lease stamps follow: two writers measuring one fact against\n // two clocks is how a \"collected\" row looks uncollected.\n .update({ collected_at: \"now()\" })\n .eq(\"device_code_hash\", deviceCodeHash);\n if (error) throw new Error(`supabase: ${error.message}`);\n },\n\n async getRunner(runnerId: string): Promise<RunnerRecord | null> {\n const row = unwrapMaybe<RunnerRow>(\n await db\n .from(\"byollm_runners\")\n .select()\n .eq(\"id\", runnerId)\n .maybeSingle(),\n );\n return row === null ? null : toRunner(row);\n },\n\n async touchRunner(args: TouchArgs): Promise<RunnerRecord | null> {\n const row = unwrapMaybe<RunnerRow>(\n await db\n .from(\"byollm_runners\")\n .update({\n capabilities: args.capabilities,\n daemon_version: args.daemonVersion,\n paused: args.paused,\n last_heartbeat_at: iso(args.now),\n })\n .eq(\"id\", args.runnerId)\n .select()\n .maybeSingle(),\n );\n return row === null ? null : toRunner(row);\n },\n\n async revokeRunner(runnerId: string, now: number): Promise<void> {\n // `is('revoked_at', null)` keeps revocation one-way: an already-revoked\n // runner keeps its first revocation time.\n const { error } = await db\n .from(\"byollm_runners\")\n .update({ revoked_at: iso(now) })\n .eq(\"id\", runnerId)\n .is(\"revoked_at\", null);\n if (error) throw new Error(`supabase: ${error.message}`);\n },\n\n async listRunners(owner?: string): Promise<RunnerRecord[]> {\n const query = db.from(\"byollm_runners\").select();\n const rows = unwrap<RunnerRow[]>(\n owner === undefined ? await query : await query.eq(\"owner\", owner),\n );\n return rows.map(toRunner);\n },\n };\n}\n\nexport { supabaseRealtimeDelivery } from \"./realtime.js\";\n"],"mappings":"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","names":["iso"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@byollm/server",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.68",
|
|
4
4
|
"description": "Framework-agnostic BYOLLM protocol handlers, a reference in-memory store, a Next.js mount, and a Supabase adapter.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"node": ">=22.14"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@byollm/protocol": "0.1.0-alpha.
|
|
33
|
+
"@byollm/protocol": "0.1.0-alpha.68"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"@supabase/supabase-js": "^2.58.0"
|