@nightowlsdev/storage-supabase 0.3.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Night Owls contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # @nightowlsdev/storage-supabase
2
+
3
+ A Supabase/Postgres-backed `StorageAdapter` for `@nightowlsdev/core` — implements the
4
+ `agents` / `runs` / `events` / `messages` seams, a redacted Realtime broadcast event
5
+ bus (`events.subscribe`), and a Postgres-backed Mastra store for cross-process durable
6
+ suspend/resume.
7
+
8
+ The server adapter authenticates with the **secret** key (`BYPASSRLS`) and is the real
9
+ authorization boundary — it scopes every query by `tenantId`/`org_id` in code. The SQL
10
+ RLS in the packaged `0001_core` migration is defense-in-depth for the browser /
11
+ Realtime read path only.
12
+
13
+ The tables live in a dedicated **`owl`** schema with **bare names**
14
+ (`nightowls.orgs`, `nightowls.runs`, `nightowls.events`, `nightowls.agents`, …). The adapter pool
15
+ sets `search_path=nightowls,public`, so the adapter's own queries stay unqualified. The
16
+ schema + tables are created by this package's **inlined migrations** (`MIGRATIONS`),
17
+ which `@nightowlsdev/cli` **installs** into your host's `supabase/migrations/` — you apply them
18
+ with your own tooling (`supabase db push`). Night Owls never runs DDL itself.
19
+
20
+ ## Connection rules
21
+
22
+ ### Use the Session/Direct pooler port (5432) — NEVER the Transaction pooler (6543)
23
+
24
+ `pg` and `@mastra/pg` rely on prepared statements, which **break on the Transaction
25
+ pooler (port 6543)**. Always pass the **Session/Direct** Postgres URL (port **5432**).
26
+ Both `createSupabaseStorage` and `createMastraPgStore` throw if they detect `:6543` in
27
+ the `dbUrl`.
28
+
29
+ ```
30
+ ✅ postgresql://…@db.<ref>.supabase.co:5432/postgres (Session/Direct)
31
+ ❌ postgresql://…@…pooler.supabase.com:6543/postgres (Transaction — breaks pg)
32
+ ```
33
+
34
+ ### Two keys, two roles
35
+
36
+ | Key | Where it goes | Why |
37
+ |---|---|---|
38
+ | **secret** (`sb_secret_…` / `service_role`) | the server **adapter** (`secretKey`) | `BYPASSRLS`; the real authz boundary. Never ships to the browser. |
39
+ | **publishable** (`sb_publishable_…` / `anon`) | the **browser / Realtime** client | RLS-gated; used by the host app to subscribe to `run:<id>` broadcasts as the end user. |
40
+
41
+ The adapter is server-only. The publishable key is for the host app's browser Realtime
42
+ subscription, which the defense-in-depth RLS policy (`members_receive_run_events`)
43
+ gates by org membership.
44
+
45
+ ## Applying the schema: install via `@nightowlsdev/cli`
46
+
47
+ This package ships its migrations **inlined** (`MIGRATIONS`: `{ version, name, sql }[]`,
48
+ fully-qualified `nightowls.*`) and exports the `nightOwlsPlugin` manifest that `@nightowlsdev/cli`
49
+ discovers. You don't apply them from this package — instead the CLI **installs** them into
50
+ your host's classic `supabase/migrations/`, and you apply them with your own tooling:
51
+
52
+ ```bash
53
+ owl install storage-supabase # installs the migrations into supabase/migrations/
54
+ supabase db push # apply them (your tooling — Night Owls never runs DDL)
55
+ ```
56
+
57
+ The install writes one `<timestamp>_corale_<version>.sql` per migration and is idempotent
58
+ (already-installed versions are skipped). `owl db install` re-installs after an upgrade.
59
+
60
+ ## Mastra durable resume: `disableInit:true` + the `0002_mastra` migration
61
+
62
+ On an RLS-enabled Supabase project you do **not** want `PostgresStore` to run its own
63
+ DDL/init at runtime. `createMastraPgStore` defaults to `disableInit: true` and passes
64
+ `schemaName: 'nightowls'`, so the `nightowls.mastra_*` tables **must pre-exist**. They are
65
+ created by the packaged `0002_mastra` migration (the real `@mastra/pg` DDL via
66
+ `exportSchemas('nightowls')`), including the resume-critical
67
+ `nightowls.mastra_workflow_snapshot`. The installed migrations (above) create them.
68
+
69
+ ## Usage
70
+
71
+ ```ts
72
+ import {
73
+ createSupabaseStorage,
74
+ createMastraPgStore,
75
+ publishAgentVersion,
76
+ } from "@nightowlsdev/storage-supabase";
77
+ import { SwarmEngine, allowListModelProvider } from "@nightowlsdev/core";
78
+
79
+ const storage = createSupabaseStorage({
80
+ url: process.env.SUPABASE_URL!, // API_URL — for supabase-js Realtime
81
+ secretKey: process.env.SUPABASE_SECRET_KEY!, // sb_secret_… / service_role (BYPASSRLS)
82
+ dbUrl: process.env.SUPABASE_DB_URL!, // Session/Direct Postgres URL (port 5432)
83
+ // ssl defaults to `true` (verify cert) for remote dbUrl; localhost is unencrypted.
84
+ });
85
+
86
+ // Seed/publish an agent version (management helper, not part of the core interface).
87
+ // Pass the adapter's typed internal handle `storage.ctx` (no `as any` coupling).
88
+ await publishAgentVersion(storage.ctx, {
89
+ tenantId: orgId,
90
+ slug: "support",
91
+ role: "specialist",
92
+ personality: "warm",
93
+ capabilities: [],
94
+ skillNames: [],
95
+ delegateSlugs: [],
96
+ modelId: "openai/gpt-5.5",
97
+ });
98
+
99
+ // Cross-process durable suspend/resume: inject the Postgres-backed Mastra store.
100
+ const engine = new SwarmEngine({
101
+ storage,
102
+ model: allowListModelProvider({ allow: ["openai/gpt-5.5"] }),
103
+ modelFactory: (modelId) => /* your LanguageModelV3 for modelId */ undefined as never,
104
+ cost: { maxSteps: 10, maxCostUsd: 1 },
105
+ mastraStore: createMastraPgStore({ dbUrl: process.env.SUPABASE_DB_URL! }), // disableInit:true
106
+ });
107
+
108
+ for await (const e of engine.run({ message: "hi" }, ctx)) {
109
+ // SwarmEvents are persisted to nightowls.events and broadcast on run:<id>
110
+ }
111
+
112
+ await storage.close(); // tears down Realtime channels, then ends the pg pool
113
+ ```
114
+
115
+ Subscribe to the live event stream (server-side, BYPASSRLS):
116
+
117
+ ```ts
118
+ for await (const event of storage.events.subscribe(runId)) {
119
+ // redacted SwarmEvent forwarded from the DB broadcast trigger, with authoritative seq
120
+ }
121
+ ```
122
+
123
+ ## Known limitations
124
+
125
+ - **Multi-tenant read-scoping (C1) deferred.** `events.list`, `runs.loadSnapshot`, and
126
+ `messages.history` do **not** independently org-scope by `tenantId`, because the core
127
+ `EventStore.list` / `RunStore.loadSnapshot` / `MessageStore.history` signatures don't
128
+ pass it. For single-tenant v1 the caller (engine/runner) authorizes the `runId` /
129
+ `threadId` before reading. Multi-tenant hardening = widening those core signatures to
130
+ take `tenantId`, tracked for the runner/auth plan (Plan 3).
131
+ - **Cross-instance row-cache bust deferred.** The adapter's per-instance `RowCache`
132
+ (LRU+TTL) for run→org lookups, and the engine's `AgentVersion` cache, are not busted
133
+ across instances. A `postgres_changes` subscription on `nightowls.agents.current_version_id`
134
+ to invalidate caches cluster-wide is deferred to the runner/multi-instance plan; the
135
+ Plan-1 TTL is sufficient until then.
136
+ - **Other v1 simplifications:** `RunStore.setStatus` ignores its optional `patch`
137
+ (status-only persistence); `runner` is hardcoded `'nextjs'` (`NewRun` has no runner
138
+ field); `events.subscribe` shares the adapter's single secret-role `SupabaseClient`
139
+ (a per-user-token subscription needs its own client because `setAuth` mutates shared
140
+ state). All are tracked for the runner/auth plan (Plan 3).