@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 +21 -0
- package/README.md +140 -0
- package/dist/index.cjs +1825 -0
- package/dist/index.d.cts +165 -0
- package/dist/index.d.ts +165 -0
- package/dist/index.js +1790 -0
- package/package.json +59 -0
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).
|