@avelonjs/supabase 0.1.0 → 0.3.1

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 CHANGED
@@ -39,23 +39,23 @@ await db.execute(published)
39
39
 
40
40
  ## Capabilities
41
41
 
42
- | Capability | Value | Notes |
43
- |---|---|---|
44
- | `transactions` | `false` | PostgREST has no interactive transactions |
45
- | `rowSecurity` | `true` | `syncWards()` compiles registered wards to RLS |
46
- | `maxRelationDepth` | `2` | Measured against live PostgREST relation loading |
47
- | `fullTextSearch` | `false` | No portable `search()` surface in v1 |
48
- | `upsert` | `true` | Wildcard upserts are native; explicit update lists use `avelon_upsert_subset` |
49
- | `returning` | `true` | Write queries may project rows |
50
- | `windowFunctions` | `false` | Not available over PostgREST |
51
- | `jsonOperators` | `true` | Informational |
42
+ | Capability | Value | Notes |
43
+ | ------------------ | ------- | ----------------------------------------------------------------------------- |
44
+ | `transactions` | `false` | PostgREST has no interactive transactions |
45
+ | `rowSecurity` | `true` | `syncWards()` compiles registered wards to RLS |
46
+ | `maxRelationDepth` | `2` | Measured against live PostgREST relation loading |
47
+ | `fullTextSearch` | `false` | No portable `search()` surface in v1 |
48
+ | `upsert` | `true` | Wildcard upserts are native; explicit update lists use `avelon_upsert_subset` |
49
+ | `returning` | `true` | Write queries may project rows |
50
+ | `windowFunctions` | `false` | Not available over PostgREST |
51
+ | `jsonOperators` | `true` | Informational |
52
52
 
53
53
  ## Ward Synchronization
54
54
 
55
55
  ```ts
56
- import { createSupabaseDatabase } from '@avelonjs/supabase'
56
+ import { createSupabaseBunDatabase } from '@avelonjs/supabase/database/bun'
57
57
 
58
- const db = createSupabaseDatabase({
58
+ const db = createSupabaseBunDatabase({
59
59
  wards: [
60
60
  {
61
61
  name: 'posts_owner_read',
@@ -76,14 +76,25 @@ await db.syncWards()
76
76
 
77
77
  `{ claim: 'uid' }` compiles to `auth.uid()::text`. A live two-identity denial test in this package proves a cross-tenant read is rejected after sync.
78
78
 
79
+ ## Two entries
80
+
81
+ Query traffic goes over PostgREST, so `@avelonjs/supabase/database` carries no `bun` specifier and runs on a Node runtime. Migrations, fixtures and `syncWards()` issue DDL, which PostgREST cannot, so they need a Postgres socket. `@avelonjs/supabase/database/bun` supplies one through Bun's SQL client.
82
+
83
+ | Entry | Request path | Migrations, fixtures, `syncWards()` |
84
+ | --------------------------------- | ------------ | ----------------------------------- |
85
+ | `@avelonjs/supabase/database` | PostgREST | raise `Unavailable` |
86
+ | `@avelonjs/supabase/database/bun` | PostgREST | run over Bun's SQL client |
87
+
88
+ Either way the schema cache the request path validates against comes from PostgREST's own OpenAPI document, filtered by the bearer token the queries use.
89
+
79
90
  ## Migrations
80
91
 
81
- Query traffic stays on PostgREST. Migration history uses the same driver-owned SQL pairs as `@avelonjs/postgres`, applied over the admin connection.
92
+ Migration history uses the same driver-owned SQL pairs as `@avelonjs/postgres`, applied over the admin connection.
82
93
 
83
94
  ```ts
84
- import { createSupabaseDatabase } from '@avelonjs/supabase'
95
+ import { createSupabaseBunDatabase } from '@avelonjs/supabase/database/bun'
85
96
 
86
- const db = createSupabaseDatabase({
97
+ const db = createSupabaseBunDatabase({
87
98
  migrations: [
88
99
  {
89
100
  id: '20260827_create_posts',
@@ -109,8 +120,10 @@ Fixture provisioning is owned by this package and reloads the PostgREST schema c
109
120
 
110
121
  ## Identity
111
122
 
123
+ Next on Node must import `@avelonjs/supabase/identity`. The package root also exports the database driver, which loads Bun SQL.
124
+
112
125
  ```ts
113
- import { createSupabaseIdentity } from '@avelonjs/supabase'
126
+ import { createSupabaseIdentity } from '@avelonjs/supabase/identity'
114
127
 
115
128
  const auth = createSupabaseIdentity({
116
129
  authUrl: process.env.SUPABASE_AUTH_URL,
@@ -122,7 +135,23 @@ export async function currentUser(cookies: import('@avelonjs/core').RequestCooki
122
135
  }
123
136
  ```
124
137
 
125
- Capabilities: `passwords: true`, `magicLinks: true`, `oauth: false`, `organizations: false`, `mfa: []`. OAuth linking and MFA are deferred until a live GoTrue stack is available in CI; declaring them early would hide missing surfaces.
138
+ Capabilities: `passwords: true`, `magicLinks: true`, `oauth: false`, `organizations: false`, `mfa: ['totp']`, `emailVerification: false`. After password sign-in you challenge the enrolled TOTP factor and verify the authenticator code. `challengeMfa()` without an argument uses `totp`. Email confirmation stays on GoTrue's signup flow and is not an Auth method here.
139
+
140
+ ```ts
141
+ import { createSupabaseIdentity } from '@avelonjs/supabase'
142
+ import type { RequestCookies } from '@avelonjs/core'
143
+
144
+ export async function completeTotpChallenge(cookies: RequestCookies) {
145
+ const identity = createSupabaseIdentity({
146
+ authUrl: process.env.SUPABASE_AUTH_URL,
147
+ apiKey: process.env.SUPABASE_ANON_KEY,
148
+ })(cookies)
149
+
150
+ await identity.signInWithPassword('jordan@example.com', 'correct-horse-battery')
151
+ const challenge = await identity.challengeMfa('totp')
152
+ await identity.verifyMfa(challenge.id, '123456')
153
+ }
154
+ ```
126
155
 
127
156
  ## Social
128
157
 
@@ -175,7 +204,7 @@ Transforms are undeclared in v1 (D28). Signed uploads and listing are deferred.
175
204
 
176
205
  ## Queue
177
206
 
178
- Durable jobs use Postgres `FOR UPDATE SKIP LOCKED`. The Wave A package names pgmq; this environment does not ship that extension, so skip-locked tables carry the same retry, delay, and dead-letter semantics.
207
+ Durable jobs use Postgres `FOR UPDATE SKIP LOCKED`. This package names pgmq; this environment does not ship that extension, so skip-locked tables carry the same retry, delay, and dead-letter semantics.
179
208
 
180
209
  ```ts
181
210
  import { createSupabaseQueue } from '@avelonjs/supabase'
@@ -189,81 +218,89 @@ await queue.drain(async (receipt) => {
189
218
 
190
219
  ## Method Reference
191
220
 
192
- | Method | Signature | Description |
193
- |---|---|---|
194
- | `createSupabaseDatabase` | `(options?: SupabaseDatabaseOptions) => SupabaseDatabase` | Constructs the PostgREST database driver. |
195
- | `SupabaseDatabase.execute` | `(query: QueryIR) => Promise<QueryResult>` | Executes IR as the service role. |
196
- | `SupabaseDatabase.executeAs` | `(token: string, query: QueryIR) => Promise<QueryResult>` | Executes IR as an arbitrary bearer token. |
197
- | `SupabaseDatabase.rpc` | `(routine: string, args: Readonly<Record<string, unknown>>) => Promise<T>` | Invokes a PostgREST RPC; missing routines raise `Invalid`. |
198
- | `SupabaseDatabase.plan` | `() => Promise<MigrationPlan>` | Returns pending migration identifiers and SQL steps. |
199
- | `SupabaseDatabase.apply` | `() => Promise<readonly MigrationStatus[]>` | Applies pending migrations over the admin connection. |
200
- | `SupabaseDatabase.rollback` | `(steps?: number) => Promise<readonly MigrationStatus[]>` | Rolls back the newest applied migration batches. |
201
- | `SupabaseDatabase.status` | `() => Promise<readonly MigrationStatus[]>` | Lists applied and pending migration states. |
202
- | `SupabaseDatabase.syncWards` | `() => Promise<void>` | Applies registered ward policies as Postgres RLS. |
203
- | `SupabaseDatabase.resetFixtures` | `() => Promise<void>` | Recreates assay fixtures, roles, and helper RPCs. |
204
- | `SupabaseDatabase.raw` | `() => { restUrl: string }` | Returns the REST root at the vendor boundary. |
205
- | `SupabaseDatabase.close` | `() => Promise<void>` | Closes the direct Postgres admin client. |
206
- | `createSupabaseIdentity` | `(options?: SupabaseIdentityOptions) => (cookies: RequestCookies) => SupabaseIdentity` | Returns the pinned config-time identity factory. |
207
- | `SupabaseIdentity.user` | `() => Promise<SupabaseActor \| null>` | Returns the current actor from the request cookie session. |
208
- | `SupabaseIdentity.session` | `() => Promise<SupabaseSession \| null>` | Returns the current session or null. |
209
- | `SupabaseIdentity.register` | `(email: string, password: string) => Promise<SupabaseActor>` | Registers and establishes a session cookie. |
210
- | `SupabaseIdentity.signInWithPassword` | `(email: string, password: string) => Promise<SupabaseActor>` | Signs in and writes the session cookie. |
211
- | `SupabaseIdentity.signOut` | `() => Promise<void>` | Ends the Auth session and clears the cookie. |
212
- | `SupabaseIdentity.sendPasswordReset` | `(email: string) => Promise<void>` | Sends a recovery request without revealing account existence. |
213
- | `SupabaseIdentity.resetPassword` | `(token: string, password: string) => Promise<void>` | Replaces a password after validating a recovery token. |
214
- | `SupabaseIdentity.updatePassword` | `(password: string) => Promise<void>` | Changes the current actor's password. |
215
- | `SupabaseIdentity.sendMagicLink` | `(email: string, redirectTo?: string) => Promise<void>` | Sends a passwordless sign-in link. |
216
- | `SupabaseIdentity.raw` | `() => { authUrl: string }` | Returns the Auth HTTP root at the vendor boundary. |
217
- | `readSessionCookie` | `(cookies: RequestCookies, cookieName: string) => SupabaseSession \| null` | Parses the request-scoped session cookie. |
218
- | `writeSessionCookie` | `(cookies: RequestCookies, cookieName: string, session: SupabaseSession) => void` | Writes the session cookie for the current request. |
219
- | `clearSessionCookie` | `(cookies: RequestCookies, cookieName: string) => void` | Deletes the session cookie for the current request. |
220
- | `LocalAuthServer.start` | `() => Promise<string>` | Starts a GoTrue-shaped Auth fixture on an ephemeral port. |
221
- | `LocalAuthServer.reset` | `() => void` | Clears users, sessions, and recovery tokens. |
222
- | `LocalAuthServer.stop` | `() => Promise<void>` | Stops the fixture listener. |
223
- | `LocalAuthServer.recoveryToken` | `(email: string) => string \| undefined` | Returns the recovery token issued for an email. |
224
- | `createSupabaseSocial` | `(options?: SupabaseSocialOptions) => SupabaseSocial` | Constructs the GoTrue social driver. |
225
- | `SupabaseSocial.redirect` | `(provider: string, callbackUrl: string, state?: string) => Promise<string>` | Builds a GoTrue authorize URL and records CSRF state. |
226
- | `SupabaseSocial.callback` | `(provider: string, params: Readonly<Record<string, string>>, callbackUrl: string) => Promise<SocialIdentity>` | Verifies state and exchanges the authorization code. |
227
- | `SupabaseSocial.raw` | `() => { authUrl: string }` | Returns the Auth HTTP root at the vendor boundary. |
228
- | `LocalSocialServer.start` | `() => Promise<string>` | Starts a GoTrue-shaped token fixture on an ephemeral port. |
229
- | `LocalSocialServer.stop` | `() => Promise<void>` | Stops the social fixture listener. |
230
- | `createSupabaseTokens` | `(options?: SupabaseTokenOptions) => SupabaseTokens` | Constructs the Postgres-backed Signet driver. |
231
- | `SupabaseTokens.issue` | `(name: string, options?: TokenIssueOptions) => Promise<IssuedToken>` | Issues a named token and returns plaintext once. |
232
- | `SupabaseTokens.verify` | `(plainText: string) => Promise<TokenRecord>` | Authenticates a plaintext token or raises `Unauthenticated`. |
233
- | `SupabaseTokens.list` | `() => Promise<readonly TokenRecord[]>` | Lists metadata for the current subject without plaintext. |
234
- | `SupabaseTokens.revoke` | `(id: string) => Promise<void>` | Revokes one token by stable identifier. |
235
- | `SupabaseTokens.reset` | `() => Promise<void>` | Recreates the empty signet table. |
236
- | `SupabaseTokens.raw` | `() => SQL` | Returns the Bun SQL client at the vendor boundary. |
237
- | `SupabaseTokens.close` | `() => Promise<void>` | Closes the SQL client pool. |
238
- | `createSupabaseStorage` | `(options?: SupabaseStorageOptions) => SupabaseStorage` | Constructs the Postgres-backed storage driver. |
239
- | `SupabaseStorage.put` | `(path: string, contents: Uint8Array \| AsyncIterable<Uint8Array>, options?: { contentType?: string }) => Promise<StorageObject>` | Stores bytes and returns metadata. |
240
- | `SupabaseStorage.get` | `(path: string) => Promise<Uint8Array>` | Reads object bytes or raises `NotFound`. |
241
- | `SupabaseStorage.delete` | `(path: string) => Promise<void>` | Deletes an object if it exists. |
242
- | `SupabaseStorage.exists` | `(path: string) => Promise<boolean>` | Reports whether an object exists. |
243
- | `SupabaseStorage.signedUrl` | `(path: string, expiresInSeconds: number) => Promise<string>` | Creates a fetchable HMAC-signed read URL. |
244
- | `SupabaseStorage.reset` | `() => Promise<void>` | Recreates the empty object table. |
245
- | `SupabaseStorage.raw` | `() => SQL` | Returns the Bun SQL client at the vendor boundary. |
246
- | `SupabaseStorage.close` | `() => Promise<void>` | Stops the signed-URL listener and closes SQL. |
247
- | `createSupabaseQueue` | `(options?: SupabaseQueueOptions) => SupabaseQueue` | Constructs the skip-locked Postgres queue driver. |
248
- | `SupabaseQueue.enqueue` | `(job: QueueJob) => Promise<string>` | Enqueues a job for immediate delivery. |
249
- | `SupabaseQueue.enqueueAt` | `(job: QueueJob, availableAt: Date) => Promise<string>` | Enqueues a job no earlier than the supplied time. |
250
- | `SupabaseQueue.drain` | `(handler: (receipt: QueueReceipt) => Promise<void>, options?: { queue?: string; limit?: number }) => Promise<number>` | Delivers available jobs and counts attempts. |
251
- | `SupabaseQueue.retry` | `(id: string, delaySeconds?: number) => Promise<void>` | Releases a failed job, optionally after a delay. |
252
- | `SupabaseQueue.failed` | `(queue?: string) => Promise<readonly FailedQueueJob[]>` | Lists retained terminal failures. |
253
- | `SupabaseQueue.replay` | `(id: string) => Promise<void>` | Replays a terminal failure with a fresh attempt budget. |
254
- | `SupabaseQueue.forget` | `(id: string) => Promise<void>` | Permanently removes a terminal failure. |
255
- | `SupabaseQueue.reset` | `() => Promise<void>` | Recreates empty job tables. |
256
- | `SupabaseQueue.raw` | `() => SQL` | Returns the Bun SQL client at the vendor boundary. |
257
- | `SupabaseQueue.close` | `() => Promise<void>` | Closes the SQL client pool. |
258
- | `compilePostgrest` | `(ir: QueryIR, predicate?: Predicate) => CompiledPostgrestRequest` | Compiles IR to an HTTP request. |
259
- | `applyPostgrestPredicate` | `(parameters: URLSearchParams, predicate: Predicate) => void` | Writes a normalized predicate into PostgREST `and=` filters. |
260
- | `compileWardPredicate` | `(predicate: Predicate \| boolean) => string` | Compiles a ward predicate to RLS SQL. |
261
- | `compileWardPolicySql` | `(policy: WardPolicy) => string[]` | Builds DROP/CREATE POLICY statements for one ward. |
262
- | `compileAllWardPolicies` | `(policies: readonly WardPolicy[]) => string[]` | Compiles every registered ward into ordered SQL. |
263
- | `mapPostgrestError` | `(status: number, bodyText: string, operation: string) => never` | Maps PostgREST failures into framework errors. |
264
- | `resetSupabaseAssayFixtures` | `(sql: SQL) => Promise<void>` | Provisions empty assay fixtures on a SQL client. |
265
- | `normalizePredicate` | `(predicate: Predicate) => Predicate` | Applies empty-list and constant identities. |
266
- | `combinedPredicate` | `(ir: Pick<QueryIR, 'where' \| 'ward'>) => Predicate` | ANDs where and ward, then normalizes. |
221
+ | Method | Signature | Description |
222
+ | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
223
+ | `createSupabaseDatabase` | `(options?: SupabaseDatabaseOptions) => SupabaseDatabase` | Constructs the PostgREST database driver. |
224
+ | `createSupabaseBunDatabase` | `(options?: SupabaseBunDatabaseOptions) => SupabaseDatabase` | Same driver, holding a Bun SQL admin connection for DDL. |
225
+ | `SupabaseBunDatabaseOptions` | `interface` | `SupabaseDatabaseOptions` without `admin`, plus `databaseUrl`. |
226
+ | `loadSchemaCacheFromRest` | `(restUrl: string, headers: Readonly<Record<string, string>>) => Promise<SchemaCache>` | Reads tables and columns from PostgREST's OpenAPI document. |
227
+ | `SupabaseDatabase.execute` | `(query: QueryIR) => Promise<QueryResult>` | Executes IR as the service role. |
228
+ | `SupabaseDatabase.executeAs` | `(token: string, query: QueryIR) => Promise<QueryResult>` | Executes IR as an arbitrary bearer token. |
229
+ | `SupabaseDatabase.rpc` | `(routine: string, args: Readonly<Record<string, unknown>>) => Promise<T>` | Invokes a PostgREST RPC; missing routines raise `Invalid`. |
230
+ | `SupabaseDatabase.plan` | `() => Promise<MigrationPlan>` | Returns pending migration identifiers and SQL steps. |
231
+ | `SupabaseDatabase.apply` | `() => Promise<readonly MigrationStatus[]>` | Applies pending migrations over the admin connection. |
232
+ | `SupabaseDatabase.rollback` | `(steps?: number) => Promise<readonly MigrationStatus[]>` | Rolls back the newest applied migration batches. |
233
+ | `SupabaseDatabase.status` | `() => Promise<readonly MigrationStatus[]>` | Lists applied and pending migration states. |
234
+ | `SupabaseDatabase.syncWards` | `() => Promise<void>` | Applies registered ward policies as Postgres RLS. |
235
+ | `SupabaseDatabase.resetFixtures` | `() => Promise<void>` | Recreates assay fixtures, roles, and helper RPCs. |
236
+ | `SupabaseDatabase.raw` | `() => { restUrl: string }` | Returns the REST root at the vendor boundary. |
237
+ | `SupabaseDatabase.close` | `() => Promise<void>` | Closes the direct Postgres admin client. |
238
+ | `createSupabaseIdentity` | `(options?: SupabaseIdentityOptions) => (cookies: RequestCookies) => SupabaseIdentity` | Returns the pinned config-time identity factory. |
239
+ | `SupabaseIdentity.user` | `() => Promise<SupabaseActor \| null>` | Returns the current actor from the request cookie session. |
240
+ | `SupabaseIdentity.session` | `() => Promise<SupabaseSession \| null>` | Returns the current session or null. |
241
+ | `SupabaseIdentity.register` | `(email: string, password: string) => Promise<SupabaseActor>` | Registers and establishes a session cookie. |
242
+ | `SupabaseIdentity.signInWithPassword` | `(email: string, password: string) => Promise<SupabaseActor>` | Signs in and writes the session cookie. |
243
+ | `SupabaseIdentity.signOut` | `() => Promise<void>` | Ends the Auth session and clears the cookie. |
244
+ | `SupabaseIdentity.sendPasswordReset` | `(email: string) => Promise<void>` | Sends a recovery request without revealing account existence. |
245
+ | `SupabaseIdentity.resetPassword` | `(token: string, password: string) => Promise<void>` | Replaces a password after validating a recovery token. |
246
+ | `SupabaseIdentity.updatePassword` | `(password: string) => Promise<void>` | Changes the current actor's password. |
247
+ | `SupabaseIdentity.sendMagicLink` | `(email: string, redirectTo?: string) => Promise<void>` | Sends a passwordless sign-in link. |
248
+ | `SupabaseIdentity.signInWithMagicLink` | `(token: string) => Promise<SupabaseActor>` | Redeems a link token for a session. |
249
+ | `SupabaseIdentity.challengeMfa` | `(factor?: 'totp') => Promise<MfaChallenge<'totp'>>` | Begins a TOTP challenge for the current session. |
250
+ | `SupabaseIdentity.verifyMfa` | `(challengeId: string, code: string) => Promise<void>` | Verifies a TOTP challenge response. |
251
+ | `SupabaseIdentity.raw` | `() => { authUrl: string }` | Returns the Auth HTTP root at the vendor boundary. |
252
+ | `readSessionCookie` | `(cookies: RequestCookies, cookieName: string) => SupabaseSession \| null` | Parses the request-scoped session cookie. |
253
+ | `writeSessionCookie` | `(cookies: RequestCookies, cookieName: string, session: SupabaseSession) => void` | Writes the session cookie for the current request. |
254
+ | `clearSessionCookie` | `(cookies: RequestCookies, cookieName: string) => void` | Deletes the session cookie for the current request. |
255
+ | `LocalAuthServer.start` | `() => Promise<string>` | Starts a GoTrue-shaped Auth fixture on an ephemeral port. |
256
+ | `LocalAuthServer.reset` | `() => void` | Clears users, sessions, and recovery tokens. |
257
+ | `LocalAuthServer.stop` | `() => Promise<void>` | Stops the fixture listener. |
258
+ | `LocalAuthServer.recoveryToken` | `(email: string) => string \| undefined` | Returns the recovery token issued for an email. |
259
+ | `LocalAuthServer.magicLinkToken` | `(email: string) => string \| undefined` | Returns the magic-link token issued for an email. |
260
+ | `SUPABASE_IDENTITY_ERROR_MAP` | `readonly { code: string; framework: string; meaning: string }[]` | GoTrue error codes and the taxonomy member each becomes. |
261
+ | `createSupabaseSocial` | `(options?: SupabaseSocialOptions) => SupabaseSocial` | Constructs the GoTrue social driver. |
262
+ | `SupabaseSocial.redirect` | `(provider: string, callbackUrl: string, state?: string) => Promise<string>` | Builds a GoTrue authorize URL and records CSRF state. |
263
+ | `SupabaseSocial.callback` | `(provider: string, params: Readonly<Record<string, string>>, callbackUrl: string) => Promise<SocialIdentity>` | Verifies state and exchanges the authorization code. |
264
+ | `SupabaseSocial.raw` | `() => { authUrl: string }` | Returns the Auth HTTP root at the vendor boundary. |
265
+ | `LocalSocialServer.start` | `() => Promise<string>` | Starts a GoTrue-shaped token fixture on an ephemeral port. |
266
+ | `LocalSocialServer.stop` | `() => Promise<void>` | Stops the social fixture listener. |
267
+ | `createSupabaseTokens` | `(options?: SupabaseTokenOptions) => SupabaseTokens` | Constructs the Postgres-backed Signet driver. |
268
+ | `SupabaseTokens.issue` | `(name: string, options?: TokenIssueOptions) => Promise<IssuedToken>` | Issues a named token and returns plaintext once. |
269
+ | `SupabaseTokens.verify` | `(plainText: string) => Promise<TokenRecord>` | Authenticates a plaintext token or raises `Unauthenticated`. |
270
+ | `SupabaseTokens.list` | `() => Promise<readonly TokenRecord[]>` | Lists metadata for the current subject without plaintext. |
271
+ | `SupabaseTokens.revoke` | `(id: string) => Promise<void>` | Revokes one token by stable identifier. |
272
+ | `SupabaseTokens.reset` | `() => Promise<void>` | Recreates the empty signet table. |
273
+ | `SupabaseTokens.raw` | `() => SQL` | Returns the Bun SQL client at the vendor boundary. |
274
+ | `SupabaseTokens.close` | `() => Promise<void>` | Closes the SQL client pool. |
275
+ | `createSupabaseStorage` | `(options?: SupabaseStorageOptions) => SupabaseStorage` | Constructs the Postgres-backed storage driver. |
276
+ | `SupabaseStorage.put` | `(path: string, contents: Uint8Array \| AsyncIterable<Uint8Array>, options?: { contentType?: string }) => Promise<StorageObject>` | Stores bytes and returns metadata. |
277
+ | `SupabaseStorage.get` | `(path: string) => Promise<Uint8Array>` | Reads object bytes or raises `NotFound`. |
278
+ | `SupabaseStorage.delete` | `(path: string) => Promise<void>` | Deletes an object if it exists. |
279
+ | `SupabaseStorage.exists` | `(path: string) => Promise<boolean>` | Reports whether an object exists. |
280
+ | `SupabaseStorage.signedUrl` | `(path: string, expiresInSeconds: number) => Promise<string>` | Creates a fetchable HMAC-signed read URL. |
281
+ | `SupabaseStorage.reset` | `() => Promise<void>` | Recreates the empty object table. |
282
+ | `SupabaseStorage.raw` | `() => SQL` | Returns the Bun SQL client at the vendor boundary. |
283
+ | `SupabaseStorage.close` | `() => Promise<void>` | Stops the signed-URL listener and closes SQL. |
284
+ | `createSupabaseQueue` | `(options?: SupabaseQueueOptions) => SupabaseQueue` | Constructs the skip-locked Postgres queue driver. |
285
+ | `SupabaseQueue.enqueue` | `(job: QueueJob) => Promise<string>` | Enqueues a job for immediate delivery. |
286
+ | `SupabaseQueue.enqueueAt` | `(job: QueueJob, availableAt: Date) => Promise<string>` | Enqueues a job no earlier than the supplied time. |
287
+ | `SupabaseQueue.drain` | `(handler: (receipt: QueueReceipt) => Promise<void>, options?: { queue?: string; limit?: number }) => Promise<number>` | Delivers available jobs and counts attempts. |
288
+ | `SupabaseQueue.retry` | `(id: string, delaySeconds?: number) => Promise<void>` | Releases a failed job, optionally after a delay. |
289
+ | `SupabaseQueue.failed` | `(queue?: string) => Promise<readonly FailedQueueJob[]>` | Lists retained terminal failures. |
290
+ | `SupabaseQueue.replay` | `(id: string) => Promise<void>` | Replays a terminal failure with a fresh attempt budget. |
291
+ | `SupabaseQueue.forget` | `(id: string) => Promise<void>` | Permanently removes a terminal failure. |
292
+ | `SupabaseQueue.reset` | `() => Promise<void>` | Recreates empty job tables. |
293
+ | `SupabaseQueue.raw` | `() => SQL` | Returns the Bun SQL client at the vendor boundary. |
294
+ | `SupabaseQueue.close` | `() => Promise<void>` | Closes the SQL client pool. |
295
+ | `compilePostgrest` | `(ir: QueryIR, predicate?: Predicate) => CompiledPostgrestRequest` | Compiles IR to an HTTP request. |
296
+ | `applyPostgrestPredicate` | `(parameters: URLSearchParams, predicate: Predicate) => void` | Writes a normalized predicate into PostgREST `and=` filters. |
297
+ | `compileWardPredicate` | `(predicate: Predicate \| boolean) => string` | Compiles a ward predicate to RLS SQL. |
298
+ | `compileWardPolicySql` | `(policy: WardPolicy) => string[]` | Builds DROP/CREATE POLICY statements for one ward. |
299
+ | `compileAllWardPolicies` | `(policies: readonly WardPolicy[]) => string[]` | Compiles every registered ward into ordered SQL. |
300
+ | `mapPostgrestError` | `(status: number, bodyText: string, operation: string) => never` | Maps PostgREST failures into framework errors. |
301
+ | `resetSupabaseAssayFixtures` | `(sql: SQL) => Promise<void>` | Provisions empty assay fixtures on a SQL client. |
302
+ | `normalizePredicate` | `(predicate: Predicate) => Predicate` | Applies empty-list and constant identities. |
303
+ | `combinedPredicate` | `(ir: Pick<QueryIR, 'where' \| 'ward'>) => Predicate` | ANDs where and ward, then normalizes. |
267
304
 
268
305
  ## Testing
269
306
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@avelonjs/supabase",
3
- "version": "0.1.0",
3
+ "version": "0.3.1",
4
4
  "private": false,
5
5
  "description": "Supabase drivers for Avelon's database, identity, social, tokens, storage, and queue contracts.",
6
6
  "license": "MIT",
@@ -31,7 +31,9 @@
31
31
  ],
32
32
  "exports": {
33
33
  ".": "./src/index.ts",
34
- "./database": "./src/database/index.ts"
34
+ "./database": "./src/database/index.ts",
35
+ "./database/bun": "./src/database/bun.ts",
36
+ "./identity": "./src/identity/driver.ts"
35
37
  },
36
38
  "scripts": {
37
39
  "test": "bun test",
@@ -0,0 +1,33 @@
1
+ import { SQL } from 'bun'
2
+ import { bunSqlRunner } from '@avelonjs/postgres'
3
+ import { SupabaseDatabase, type SupabaseDatabaseOptions } from './driver'
4
+
5
+ /** Construction options for a driver that also holds a direct Postgres connection. */
6
+ export interface SupabaseBunDatabaseOptions extends Omit<SupabaseDatabaseOptions, 'admin'> {
7
+ /** Direct Postgres URL for migrations, fixtures and ward sync. */
8
+ databaseUrl?: string
9
+ }
10
+
11
+ function defaultDatabaseUrl(): string {
12
+ return (
13
+ process.env.SUPABASE_DB_URL ??
14
+ process.env.DATABASE_URL ??
15
+ 'postgresql://postgres:avelon@127.0.0.1:5432/avelon_supabase'
16
+ )
17
+ }
18
+
19
+ /**
20
+ * Builds a Supabase driver whose migrations, fixtures and ward sync run over Bun's SQL client.
21
+ *
22
+ * The request path is the same PostgREST code either way. Only the operations needing a Postgres
23
+ * socket differ, which is why they live behind this entry rather than the runtime one.
24
+ */
25
+ export function createSupabaseBunDatabase(
26
+ options: SupabaseBunDatabaseOptions = {},
27
+ ): SupabaseDatabase {
28
+ const { databaseUrl, ...rest } = options
29
+ return new SupabaseDatabase({
30
+ ...rest,
31
+ admin: bunSqlRunner(new SQL(databaseUrl ?? defaultDatabaseUrl())),
32
+ })
33
+ }
@@ -1,5 +1,5 @@
1
1
  import { Invalid, type CompareOp, type Predicate, type QueryIR } from '@avelonjs/core'
2
- import { combinedPredicate } from '@avelonjs/postgres'
2
+ import { combinedPredicate } from '@avelonjs/postgres/sql'
3
3
 
4
4
  const POSTGREST_OPERATOR: Record<CompareOp, string> = {
5
5
  '=': 'eq',
@@ -37,12 +37,17 @@ function postgrestProjection(projection: string[] | '*'): string {
37
37
  function postgrestScalar(value: unknown): string {
38
38
  if (typeof value === 'string') return `"${value.replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`
39
39
  if (typeof value === 'number') {
40
- if (!Number.isFinite(value)) invalid('PostgREST cannot encode a non-finite number.', 'value', 'Invalid number')
40
+ if (!Number.isFinite(value))
41
+ invalid('PostgREST cannot encode a non-finite number.', 'value', 'Invalid number')
41
42
  return String(value)
42
43
  }
43
44
  if (typeof value === 'boolean') return String(value)
44
45
  if (value === null) return 'null'
45
- invalid(`PostgREST scalar type '${typeof value}' is not supported.`, 'value', 'Unsupported scalar')
46
+ invalid(
47
+ `PostgREST scalar type '${typeof value}' is not supported.`,
48
+ 'value',
49
+ 'Unsupported scalar',
50
+ )
46
51
  }
47
52
 
48
53
  function postgrestLeaf(
@@ -69,7 +74,8 @@ function postgrestLeaf(
69
74
  }
70
75
 
71
76
  function postgrestExpression(predicate: Predicate, negated = false): string {
72
- if (predicate.kind === 'const') invalid('predicate was not normalized for PostgREST.', 'where', 'Unexpected const')
77
+ if (predicate.kind === 'const')
78
+ invalid('predicate was not normalized for PostgREST.', 'where', 'Unexpected const')
73
79
  if (predicate.kind === 'not') return postgrestExpression(predicate.predicate, !negated)
74
80
  if (predicate.kind === 'and' || predicate.kind === 'or') {
75
81
  const kind = negated ? (predicate.kind === 'and' ? 'or' : 'and') : predicate.kind
@@ -168,7 +174,10 @@ export function compilePostgrest(
168
174
  ensureUnscopedWritePredicate(predicate, 'upsert')
169
175
  method = 'POST'
170
176
  body = JSON.stringify(ir.values)
171
- parameters.set('on_conflict', (ir.conflict as NonNullable<QueryIR['conflict']>).columns.join(','))
177
+ parameters.set(
178
+ 'on_conflict',
179
+ (ir.conflict as NonNullable<QueryIR['conflict']>).columns.join(','),
180
+ )
172
181
  headers.Prefer = 'resolution=merge-duplicates'
173
182
  }
174
183
 
@@ -182,10 +191,7 @@ export function compilePostgrest(
182
191
  // Always request representation so `affected` is observable; the driver drops rows when
183
192
  // `returning` is absent.
184
193
  headers.Prefer = [headers.Prefer, 'return=representation'].filter(Boolean).join(',')
185
- parameters.set(
186
- 'select',
187
- postgrestProjection(ir.returning === undefined ? '*' : ir.returning),
188
- )
194
+ parameters.set('select', postgrestProjection(ir.returning === undefined ? '*' : ir.returning))
189
195
  }
190
196
 
191
197
  return {