@donotdev/supabase 0.0.1 → 0.0.3

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
@@ -2,19 +2,183 @@
2
2
 
3
3
  Supabase provider for DoNotDev: CRUD, auth, storage, and server auth adapters. Use this package when you want to run a DoNotDev app on Supabase instead of Firebase.
4
4
 
5
- ## Setup
5
+ ---
6
6
 
7
- ### Environment
7
+ ## Complete Supabase Setup
8
8
 
9
- - **Client:** `VITE_SUPABASE_URL`, `VITE_SUPABASE_PUBLIC_KEY` (or `VITE_SUPABASE_ANON_KEY` for legacy) — public, safe to ship in client bundle
10
- - **Server:** same URL, use `SUPABASE_SECRET_KEY` (or `SUPABASE_SERVICE_ROLE_KEY` for legacy) for admin auth (never expose this key to the client)
9
+ ### Prerequisites
11
10
 
12
- Run `dndev supabase:setup` to configure these interactively.
11
+ 1. Create a Supabase project at [supabase.com/dashboard](https://supabase.com/dashboard)
12
+ 2. From **Settings > API**, copy:
13
+ - **Project URL** (`https://xxx.supabase.co`)
14
+ - **Public key** (`sb_publishable_...` or legacy anon key `eyJ...`)
15
+ - **Secret key** (`sb_secret_...` or legacy service_role key)
16
+ 3. From **Settings > Database**, copy:
17
+ - **Connection string (URI)** (`postgresql://postgres.[ref]:[password]@...`)
13
18
 
14
- ### Tables and storage
19
+ Then run `dndev setup supabase` — it handles the rest.
15
20
 
16
- - **CRUD:** Entity `collection` name is used as the Supabase **table** name (e.g. `products` -> table `products`). Tables must exist and must have an `id` column (text or uuid).
17
- - **Storage:** Default bucket is `uploads`. Create the bucket in the Supabase dashboard and set public policies as needed.
21
+ ### What `dndev setup supabase` does
22
+
23
+ | Step | Needs keys? | What it does |
24
+ |------|-------------|--------------|
25
+ | Write client `.env` | Public key only | `VITE_SUPABASE_URL` + `VITE_SUPABASE_PUBLIC_KEY` |
26
+ | Generate entity SQL | No | Tables, RLS policies, triggers from entity definitions |
27
+ | Generate `providers.ts` | No | Supabase client init file |
28
+ | Run framework migrations | Secret key | Creates `idempotency`, `rate_limits`, `operation_metrics` tables |
29
+ | Apply entity migrations | CLI or manual | Push generated SQL to live database |
30
+ | Deploy edge functions | CLI | CRUD + custom claims functions |
31
+
32
+ ---
33
+
34
+ ## Setup Checklist
35
+
36
+ ### 1. Database (FREE — automated by `dndev setup supabase`)
37
+
38
+ Everything below is generated from entity definitions. No manual SQL needed.
39
+
40
+ - [x] **Tables** — one per entity, columns from field definitions
41
+ - [x] **RLS policies** — derived from entity `access` config (`read`, `create`, `update`, `delete`)
42
+ - [x] **Status filtering** — `draft`/`deleted` rows hidden from non-admin in SELECT policies
43
+ - [x] **Triggers** — `updated_at` auto-set on UPDATE
44
+ - [x] **Framework tables** — `idempotency`, `rate_limits`, `operation_metrics`
45
+
46
+ **Extensions** (add to first migration if needed):
47
+ ```sql
48
+ CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
49
+ CREATE EXTENSION IF NOT EXISTS "pgcrypto";
50
+ ```
51
+
52
+ ### 2. Auth (FREE)
53
+
54
+ #### Automated by framework
55
+ - [x] **Email/password sign-up** — works out of the box
56
+ - [x] **Custom claims** — edge functions for `set-custom-claims`, `get-custom-claims`, `remove-custom-claims`
57
+ - [x] **Role hierarchy** — `guest < user < admin < super` stored in `app_metadata.role`
58
+
59
+ #### One-time Dashboard setup (Settings > Auth)
60
+ - [ ] **Site URL** — set to your production domain (e.g. `https://myapp.com`)
61
+ - [ ] **Redirect URLs** — add:
62
+ - `http://localhost:5173` (Vite dev)
63
+ - `http://localhost:3000` (Next.js dev)
64
+ - `https://myapp.com` (production)
65
+ - `https://*.vercel.app` (preview deploys, if using Vercel)
66
+ - [ ] **Email templates** — customize confirmation, invite, magic link, password reset (Settings > Auth > Email Templates). Works with defaults but branded templates are better UX.
67
+ - [ ] **Rate limits** — defaults are fine for most apps. Tighten if needed (Settings > Auth > Rate Limits).
68
+
69
+ #### OAuth providers (optional, FREE)
70
+ Each provider needs a client ID + secret from the provider's developer console, then paste into Supabase Dashboard > Auth > Providers.
71
+
72
+ | Provider | Get credentials from |
73
+ |----------|---------------------|
74
+ | Google | [console.cloud.google.com](https://console.cloud.google.com) > APIs & Services > Credentials |
75
+ | GitHub | [github.com/settings/developers](https://github.com/settings/developers) > OAuth Apps |
76
+ | Apple | [developer.apple.com](https://developer.apple.com) > Certificates, Identifiers & Profiles |
77
+ | Facebook | [developers.facebook.com](https://developers.facebook.com) > My Apps |
78
+
79
+ Set the **callback URL** shown in the Supabase Dashboard as the redirect URI in each provider's console.
80
+
81
+ ### 3. Storage (FREE — 1 GB included)
82
+
83
+ #### What to automate (TODO — not yet in `dndev setup supabase`)
84
+ Storage buckets should be auto-created from entity field types. Any entity with `image`, `images`, `file`, `files`, `document`, or `documents` fields needs a storage bucket.
85
+
86
+ **Default bucket structure:**
87
+ ```
88
+ uploads/ → default bucket (public or private per entity access)
89
+ {collection}/ → folder per entity (e.g. uploads/apartments/)
90
+ {id}/ → folder per document
91
+ ```
92
+
93
+ **SQL to create buckets + policies** (should be generated):
94
+ ```sql
95
+ -- Public bucket (for entities with read: 'guest')
96
+ INSERT INTO storage.buckets (id, name, public) VALUES ('uploads', 'uploads', true)
97
+ ON CONFLICT (id) DO NOTHING;
98
+
99
+ -- Upload policy: authenticated users can upload
100
+ CREATE POLICY "auth_upload" ON storage.objects FOR INSERT
101
+ TO authenticated WITH CHECK (bucket_id = 'uploads');
102
+
103
+ -- Read policy: public access (matches entity read: 'guest')
104
+ CREATE POLICY "public_read" ON storage.objects FOR SELECT
105
+ USING (bucket_id = 'uploads');
106
+
107
+ -- Delete policy: owner or admin can delete
108
+ CREATE POLICY "owner_delete" ON storage.objects FOR DELETE
109
+ TO authenticated USING (
110
+ bucket_id = 'uploads' AND (
111
+ auth.uid()::text = (storage.foldername(name))[2]
112
+ OR (auth.jwt()->'app_metadata'->>'role') IN ('admin', 'super')
113
+ )
114
+ );
115
+ ```
116
+
117
+ ### 4. Edge Functions (FREE — 500K invocations/month)
118
+
119
+ #### Deployed by `dndev setup supabase`
120
+ - [x] **CRUD function** — server-side CRUD with schema validation + HIDDEN_STATUSES enforcement
121
+ - [x] **Custom claims functions** — `set-custom-claims`, `get-custom-claims`, `remove-custom-claims`
122
+
123
+ #### Function secrets
124
+ After deploying, set secrets via CLI:
125
+ ```bash
126
+ supabase secrets set SUPABASE_SECRET_KEY=sb_secret_...
127
+ ```
128
+
129
+ Or via `dndev sync-secrets --target supabase`.
130
+
131
+ ### 5. Realtime (FREE — included)
132
+
133
+ #### What to automate (TODO — not yet in `dndev setup supabase`)
134
+ Entities using `subscribeToCollection` or `subscribe` need their tables added to the Supabase realtime publication:
135
+
136
+ ```sql
137
+ -- Enable realtime on specific tables
138
+ ALTER PUBLICATION supabase_realtime ADD TABLE apartments;
139
+ ALTER PUBLICATION supabase_realtime ADD TABLE inquiries;
140
+ ```
141
+
142
+ This should be auto-generated for any entity whose CRUD hooks use subscriptions.
143
+
144
+ ### 6. Client Config (automated by `dndev setup supabase`)
145
+
146
+ - [x] `.env` with public URL + key
147
+ - [x] `providers.ts` with Supabase client init + `configureProviders()`
148
+
149
+ ---
150
+
151
+ ## What's FREE vs PAID
152
+
153
+ | Feature | Free tier | Limit |
154
+ |---------|-----------|-------|
155
+ | Database | Yes | 500 MB storage, 2 GB egress/month |
156
+ | Auth | Yes | 50,000 MAU |
157
+ | Storage | Yes | 1 GB storage, 2 GB egress/month |
158
+ | Edge Functions | Yes | 500K invocations/month |
159
+ | Realtime | Yes | Included |
160
+ | Custom domains | No | Pro plan ($25/mo) |
161
+ | Daily backups | No | Pro plan |
162
+ | No project pausing | No | Pro plan (free tier pauses after 7 days inactivity) |
163
+ | SLA | No | Team plan ($599/mo) |
164
+
165
+ Everything DoNotDev generates and configures uses free-tier features only. Paid features are never assumed or required.
166
+
167
+ ---
168
+
169
+ ## TODO — Framework Automation Gaps
170
+
171
+ These are not yet automated by `dndev setup supabase` but should be:
172
+
173
+ | Gap | Priority | Approach |
174
+ |-----|----------|----------|
175
+ | Storage buckets from entity fields | High | Scan entities for file/image fields → generate bucket SQL |
176
+ | Storage RLS policies | High | Mirror entity `access` config to storage policies |
177
+ | Realtime publication | Medium | Detect subscription usage → generate `ALTER PUBLICATION` SQL |
178
+ | Auth redirect URLs | Low | Set via Management API (needs access token, not service role) |
179
+ | Email templates | Low | Ship branded defaults as HTML templates |
180
+
181
+ ---
18
182
 
19
183
  ## Usage
20
184
 
@@ -31,7 +195,7 @@ import {
31
195
 
32
196
  const supabase = createClient(
33
197
  import.meta.env.VITE_SUPABASE_URL,
34
- import.meta.env.VITE_SUPABASE_PUBLIC_KEY || import.meta.env.VITE_SUPABASE_ANON_KEY // New: sb_publishable_..., Legacy: anon key
198
+ import.meta.env.VITE_SUPABASE_PUBLIC_KEY || import.meta.env.VITE_SUPABASE_ANON_KEY
35
199
  );
36
200
 
37
201
  configureProviders({
@@ -52,7 +216,7 @@ import { SupabaseCrudAdapter } from '@donotdev/supabase';
52
216
 
53
217
  const supabase = createServerClient(
54
218
  process.env.SUPABASE_URL!,
55
- process.env.SUPABASE_SECRET_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY! // New: sb_secret_..., Legacy: service_role
219
+ process.env.SUPABASE_SECRET_KEY || process.env.SUPABASE_SERVICE_ROLE_KEY!
56
220
  );
57
221
 
58
222
  configureProviders({
@@ -68,8 +232,6 @@ configureProviders({
68
232
 
69
233
  ## What Works
70
234
 
71
- All core framework features work when you register Supabase providers via `configureProviders()`:
72
-
73
235
  | Feature | Status |
74
236
  |---------|--------|
75
237
  | CRUD (create, read, update, delete) | Full support, schema validation via valibot |
@@ -80,6 +242,8 @@ All core framework features work when you register Supabase providers via `confi
80
242
  | Storage (upload, delete, get URL) | Full support |
81
243
  | Server auth (token verification, custom claims) | Full support |
82
244
  | Account linking | Supported via `linkIdentity()` |
245
+ | Field-level visibility | Adapter selects only visible columns per role |
246
+ | Hidden status filtering | RLS blocks draft/deleted from non-admin SELECT |
83
247
 
84
248
  ## Known Limitations
85
249
 
@@ -87,39 +251,27 @@ All core framework features work when you register Supabase providers via `confi
87
251
 
88
252
  | Method | Behavior | Workaround |
89
253
  |--------|----------|------------|
90
- | `deleteAccount()` | Throws `DELETE_ACCOUNT_REQUIRES_SERVER` | Implement via server endpoint using `auth.admin.deleteUser()` |
254
+ | `deleteAccount()` | Requires `callable` provider + `delete-account` edge function (both auto-configured by `dndev setup supabase`) | N/A — works out of the box |
91
255
  | `signInWithGoogleCredential()` | Returns `null` | Use `signInWithPartner('google')` for standard OAuth flow |
92
256
  | `reauthenticateWithProvider()` | Triggers full OAuth redirect | Works but navigates away from page (no popup) |
93
257
 
94
258
  ### Framework Components (Firebase-Only)
95
259
 
96
- These framework features import Firebase SDK directly and **will not work** with Supabase:
97
-
98
- | Feature | Alternative |
99
- |---------|------------|
100
- | `EmailPasswordForm` component | Build custom form using `useAuth()` hook |
101
- | `useGoogleOneTap` hook | Use `signInWithPartner('google')` |
102
- | `FirebaseSmartRecovery` | Not needed for Supabase |
103
- | `FirebaseAccountLinking` | Use `SupabaseAuth.linkWithPartner()` directly |
104
- | `useStripeBilling` | Implement Stripe via Supabase Edge Functions or API routes |
260
+ | Feature | Status |
261
+ |---------|--------|
262
+ | `EmailPasswordForm` component | Works with Supabase (provider-neutral via `useAuth()`) |
263
+ | `useStripeBilling` | Works with Supabase (provider-neutral wrapper) |
264
+ | `FirebaseSmartRecovery` | Firebase-only (not needed for Supabase) |
265
+ | `FirebaseAccountLinking` | Firebase-only — use `SupabaseAuth.linkWithPartner()` directly |
105
266
 
106
- ### CLI Commands (Firebase-Only)
267
+ ### CLI Commands
107
268
 
108
- | Command | Alternative |
109
- |---------|------------|
110
- | `dndev emu` | Use `supabase start` (Supabase CLI) for local development |
111
- | `dndev deploy` | Deploy via Vercel, Netlify, or other hosting |
269
+ | Command | Supabase support |
270
+ |---------|-----------------|
271
+ | `dndev emu` | Supported — runs `supabase start` (Docker-based local dev) |
272
+ | `dndev deploy` | Supported Vercel frontend deploy via `vercel.json` detection |
112
273
  | `dndev sync-secrets` (Firebase target) | Use `dndev sync-secrets --target github` for CI/CD secrets |
113
274
 
114
- ## Future Work
115
-
116
- These gaps are tracked for future implementation:
117
-
118
- - **Provider-agnostic EmailPasswordForm** — refactor to use `getProvider('auth')` instead of Firebase SDK
119
- - **Provider-agnostic useStripeBilling** — abstract callable functions behind provider interface
120
- - **Google One Tap for Supabase** — potentially implement via `signInWithIdToken()`
121
- - **deleteAccount server template** — Supabase Edge Function template for user deletion
122
-
123
275
  ## License
124
276
 
125
277
  See [LICENSE.md](./LICENSE.md).
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/client/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/client/auth.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EACV,YAAY,EACZ,QAAQ,EACR,UAAU,EACV,aAAa,EACb,UAAU,EACV,aAAa,EACb,QAAQ,EACR,SAAS,EACT,WAAW,EAGX,eAAe,EAEhB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AA8C5D;;;;;GAKG;AACH,qBAAa,YAAa,YAAW,YAAY;IAanC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAZnC,OAAO,CAAC,KAAK,CAA0C;IACvD,OAAO,CAAC,WAAW,CAAS;IAC5B,6DAA6D;IAC7D,OAAO,CAAC,QAAQ,CAAgC;IAChD;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAuB;gBAE3B,MAAM,EAAE,cAAc;IAEnD;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,WAAW,GAAG,IAAI;IAI9C;;;;;;OAMG;IACH,WAAW,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI;IAI5C;;;;OAIG;IACH,OAAO,KAAK,UAAU,GAErB;IAED;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,cAAc;IAIhB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB3B,cAAc,IAAI,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAOhD;;;OAGG;YACW,qBAAqB;IAkC7B,0BAA0B,CAC9B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,QAAQ,CAAC;IA0Cd,8BAA8B,CAClC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,QAAQ,CAAC;IA8Cd,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBxB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBpD,iBAAiB,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IA6B5D,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAUxC,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAS5C,kBAAkB,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,KAAK,IAAI,GAAG,aAAa;IA2BtE,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IAOzC,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAS/C,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAS/C,eAAe,CACnB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAKvB,mBAAmB,CACvB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAMvB,iBAAiB,CACrB,SAAS,EAAE,aAAa,EACxB,OAAO,CAAC,EAAE,UAAU,GACnB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IA2GvB,eAAe,CACnB,SAAS,EAAE,aAAa,EACxB,OAAO,CAAC,EAAE,UAAU,GACnB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IA0BvB,0BAA0B,CAC9B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAIvB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYlD,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAkBtC,qBAAqB,CACzB,KAAK,EAAE,MAAM,EACb,kBAAkB,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,OAAO,CAAA;KAAE,GAC7D,OAAO,CAAC,IAAI,CAAC;IAqBV,mBAAmB,CACvB,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAiD7B,qBAAqB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO;IAqB5C,0BAA0B,IAAI,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IASzD,0BAA0B,IAAI,OAAO,CAAC,OAAO,CAAC;IAI9C,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiC3D,0BAA0B,CAC9B,SAAS,EAAE,aAAa,EACxB,OAAO,CAAC,EAAE,UAAU,GACnB,OAAO,CAAC,IAAI,CAAC;IA2BhB;;;OAGG;IACG,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAsBvD"}
@@ -1 +1 @@
1
- {"version":3,"file":"callableProvider.d.ts","sourceRoot":"","sources":["../../src/client/callableProvider.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAM5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,qBAAa,wBAAyB,YAAW,iBAAiB;IAO9D,OAAO,CAAC,QAAQ,CAAC,MAAM;IANzB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqB;IAErD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAqD;gBAGvE,MAAM,EAAE,cAAc,EACvC,OAAO,CAAC,EAAE;QACR,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B;;;;;WAKG;QACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;KAC5B;IAQG,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAwCxE"}
1
+ {"version":3,"file":"callableProvider.d.ts","sourceRoot":"","sources":["../../src/client/callableProvider.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAM5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,qBAAa,wBAAyB,YAAW,iBAAiB;IAQ9D,OAAO,CAAC,QAAQ,CAAC,MAAM;IAPzB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqB;IAErD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CACe;gBAGjC,MAAM,EAAE,cAAc,EACvC,OAAO,CAAC,EAAE;QACR,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B;;;;;WAKG;QACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;KAC5B;IAQG,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CA4CxE"}
@@ -12,9 +12,9 @@
12
12
  * The adapter's field mapper converts app ↔ backend names at the boundary only.
13
13
  *
14
14
  * **Field mapping (single boundary):**
15
- * By default (`fieldMapping: 'camelToSnake'`), app field names (camelCase) are converted to/from
16
- * backend column names (snake_case). Set `fieldMapping: 'identity'` if your entity uses snake_case
17
- * and DB columns match. No normalization outside this adapter.
15
+ * Write paths convert app field names to backend column names (snake_case) via blind camelToSnake.
16
+ * Read paths use an entity-aware mapper that preserves entity field names as defined in the schema
17
+ * (which may be snake_case or camelCase) so `filterVisibleFields` can match them.
18
18
  *
19
19
  * **Cursor pagination:**
20
20
  * `QueryOptions.startAfter` must be the value of the first `orderBy` field from the last row
@@ -32,7 +32,6 @@
32
32
  */
33
33
  import type { ICrudAdapter, QueryOptions, PaginatedQueryResult, DocumentSubscriptionCallback, CollectionSubscriptionCallback, dndevSchema } from '@donotdev/core';
34
34
  import type { SupabaseClient } from '@supabase/supabase-js';
35
- import type { FieldMappingMode } from '../fieldMapper';
36
35
  export declare class SupabaseCrudAdapter implements ICrudAdapter {
37
36
  private readonly client;
38
37
  private readonly idColumn;
@@ -57,10 +56,7 @@ export declare class SupabaseCrudAdapter implements ICrudAdapter {
57
56
  private readonly mapper;
58
57
  private _roleCache;
59
58
  private readonly _unsubscribeAuthState;
60
- constructor(client: SupabaseClient, idColumn?: string, options?: {
61
- normalizeColumns?: boolean;
62
- fieldMapping?: FieldMappingMode;
63
- });
59
+ constructor(client: SupabaseClient, idColumn?: string);
64
60
  /**
65
61
  * Invalidate the role cache immediately.
66
62
  * Call this when you know the session has changed (e.g., after sign-out) and cannot
@@ -72,13 +68,23 @@ export declare class SupabaseCrudAdapter implements ICrudAdapter {
72
68
  * to prevent memory leaks in long-running server processes.
73
69
  */
74
70
  dispose(): void;
75
- private normalize;
71
+ /**
72
+ * Build an entity-aware mapper when schema is available.
73
+ * Returns entity field names from `fromBackendRow` so `filterVisibleFields` can match them.
74
+ */
75
+ private createEntityMapper;
76
76
  /**
77
77
  * Resolve the current user's role from the Supabase JWT app_metadata.
78
78
  * Result is cached for 30 seconds to avoid a getSession() call on every read/query.
79
79
  * Falls back to 'guest' when no session exists.
80
80
  */
81
81
  private getUserRole;
82
+ /**
83
+ * Build a PostgREST select string from visible fields for the current role.
84
+ * Always includes idColumn and status (needed for hidden-status logic).
85
+ * Falls back to '*' if schema is unavailable or getVisibleFields returns empty.
86
+ */
87
+ private buildSelectColumns;
82
88
  get<T>(collection: string, id: string, schema?: dndevSchema<unknown>): Promise<T | null>;
83
89
  add<T>(collection: string, data: T, schema?: dndevSchema<T>): Promise<{
84
90
  id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"crudAdapter.d.ts","sourceRoot":"","sources":["../../src/client/crudAdapter.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,YAAY,EAEZ,oBAAoB,EACpB,4BAA4B,EAC5B,8BAA8B,EAE9B,WAAW,EACZ,MAAM,gBAAgB,CAAC;AAUxB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AA0HvD,qBAAa,mBAAoB,YAAW,YAAY;IAwBpD,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAxB3B;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,eAAe,QAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuC;IAC9D,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAa;gBAGhC,MAAM,EAAE,cAAc,EACtB,QAAQ,GAAE,MAA0B,EACrD,OAAO,CAAC,EAAE;QAAE,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,gBAAgB,CAAA;KAAE;IAe3E;;;;OAIG;IACH,mBAAmB,IAAI,IAAI;IAI3B;;;OAGG;IACH,OAAO,IAAI,IAAI;IAIf,OAAO,CAAC,SAAS;IAIjB;;;;OAIG;YACW,WAAW;IAsBnB,GAAG,CAAC,CAAC,EACT,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,EACV,MAAM,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,GAC5B,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IA2Bd,GAAG,CAAC,CAAC,EACT,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,CAAC,EACP,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,GACtB,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;IAgDnD,GAAG,CAAC,CAAC,EACT,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,CAAC,EACP,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,GACtB,OAAO,CAAC,IAAI,CAAC;IAuCV,MAAM,CAAC,CAAC,EACZ,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,GACf,OAAO,CAAC,IAAI,CAAC;IAqCV,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiCrD,KAAK,CAAC,CAAC,EACX,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,YAAY,EACrB,MAAM,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,EAC7B,WAAW,CAAC,EAAE,MAAM,GAAG,UAAU,GAChC,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC;IAqFnC,SAAS,CAAC,CAAC,CAAC,EACV,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,4BAA4B,CAAC,CAAC,CAAC,EACzC,MAAM,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,GAC5B,MAAM,IAAI;IA6Db,qBAAqB,CAAC,CAAC,CAAC,EACtB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,8BAA8B,CAAC,CAAC,CAAC,EAC3C,MAAM,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,GAC5B,MAAM,IAAI;CAkFd"}
1
+ {"version":3,"file":"crudAdapter.d.ts","sourceRoot":"","sources":["../../src/client/crudAdapter.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,YAAY,EAEZ,oBAAoB,EACpB,4BAA4B,EAC5B,8BAA8B,EAE9B,WAAW,EACZ,MAAM,gBAAgB,CAAC;AAWxB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AA0I5D,qBAAa,mBAAoB,YAAW,YAAY;IAwBpD,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAxB3B;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,eAAe,QAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4B;IACnD,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAa;gBAGhC,MAAM,EAAE,cAAc,EACtB,QAAQ,GAAE,MAA0B;IAcvD;;;;OAIG;IACH,mBAAmB,IAAI,IAAI;IAI3B;;;OAGG;IACH,OAAO,IAAI,IAAI;IAIf;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAQ1B;;;;OAIG;YACW,WAAW;IAwBzB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAmBpB,GAAG,CAAC,CAAC,EACT,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,EACV,MAAM,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,GAC5B,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAoCd,GAAG,CAAC,CAAC,EACT,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,CAAC,EACP,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,GACtB,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;IA4DnD,GAAG,CAAC,CAAC,EACT,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,CAAC,EACP,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,GACtB,OAAO,CAAC,IAAI,CAAC;IA0CV,MAAM,CAAC,CAAC,EACZ,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,GACf,OAAO,CAAC,IAAI,CAAC;IA4CV,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwCrD,KAAK,CAAC,CAAC,EACX,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,YAAY,EACrB,MAAM,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,EAC7B,WAAW,CAAC,EAAE,MAAM,GAAG,UAAU,GAChC,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC;IA2HnC,SAAS,CAAC,CAAC,CAAC,EACV,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,4BAA4B,CAAC,CAAC,CAAC,EACzC,MAAM,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,GAC5B,MAAM,IAAI;IA6Eb,qBAAqB,CAAC,CAAC,CAAC,EACtB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,8BAA8B,CAAC,CAAC,CAAC,EAC3C,MAAM,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,GAC5B,MAAM,IAAI;CA4Gd"}
@@ -1 +1 @@
1
- import{wrapCrudError as c,validateWithSchema as v,DoNotDevError as D,CRUD_OPERATORS as f,ERROR_CODES as U,filterVisibleFields as b}from"@donotdev/core";import{createFieldMapper as q}from"../fieldMapper";const L="id";function R(u){const{createdAt:t,updatedAt:r,created_at:s,updated_at:a,...e}=u;return e}function $(u){if(/[.(),]/.test(u))throw new Error("[crudAdapter] Invalid filter value: contains PostgREST operator characters");return u}function A(u,t){return typeof u=="string"?u:String(u)}function x(u,t,r){const{field:s,operator:a,value:e}=t,n=r(A(s,"where"));switch(a){case f.EQ:return u.eq(n,e);case f.NEQ:return u.neq(n,e);case f.LT:return u.lt(n,e);case f.LTE:return u.lte(n,e);case f.GT:return u.gt(n,e);case f.GTE:return u.gte(n,e);case f.IN:return u.in(n,Array.isArray(e)?e:[e]);case f.NOT_IN:return u.not(n,"in",Array.isArray(e)?e:[e]);case f.ARRAY_CONTAINS:return u.contains(n,e);case f.ARRAY_CONTAINS_ANY:if(!Array.isArray(e)||e.length===0)throw new D("SupabaseCrudAdapter: ARRAY_CONTAINS_ANY requires a non-empty array",U.INVALID_ARGUMENT,{context:{operator:a,field:s}});return u.overlaps(n,e);default:throw new D(`SupabaseCrudAdapter: unsupported operator "${a}"`,U.INVALID_ARGUMENT,{context:{operator:a,field:s}})}}class P{client;idColumn;dbLevelSecurity=!0;mapper;_roleCache=null;_unsubscribeAuthState;constructor(t,r=L,s){this.client=t,this.idColumn=r;const a=s?.fieldMapping??(s?.normalizeColumns===!1?"identity":"camelToSnake");this.mapper=q(a);const{data:{subscription:e}}=this.client.auth.onAuthStateChange(()=>{this._roleCache=null});this._unsubscribeAuthState=()=>e.unsubscribe()}invalidateRoleCache(){this._roleCache=null}dispose(){this._unsubscribeAuthState()}normalize(t){return this.mapper.fromBackendRow(t)}async getUserRole(){const t=Date.now();if(this._roleCache&&t<this._roleCache.expiresAt)return this._roleCache.role;try{const{data:r}=await this.client.auth.getSession(),s=r?.session?.user?.app_metadata;let a="guest";return s&&(s.role==="super"||s.isSuper===!0?a="super":s.role==="admin"||s.isAdmin===!0?a="admin":s.role==="user"?a="user":a=s.role||"guest"),this._roleCache={role:a,expiresAt:t+3e4},a}catch{return"guest"}}async get(t,r,s){const{data:a,error:e}=await this.client.from(t).select("*").eq(this.idColumn,r).maybeSingle();if(e){if(e.code==="PGRST116"||e.message?.includes("No rows"))return null;throw c(e,"SupabaseCrudAdapter","get",t,r)}if(!a)return null;const n=a,o=String(n[this.idColumn]??r),h={...n,id:o},d=this.normalize(h);if(s){const p=await this.getUserRole();return{...b(d,s,p),id:o}}return{...d,id:o}}async add(t,r,s){const a=s?v(s,r,"SupabaseCrudAdapter.add"):r,e=R(a);let n;try{const{data:i}=await this.client.auth.getSession();n=i?.session?.user?.id}catch(i){throw c(i instanceof Error?i:new Error(String(i)),"SupabaseCrudAdapter","add",t)}if(!n)throw c(new Error("Authentication required for this operation"),"SupabaseCrudAdapter","add",t);const o={...e,createdById:n,updatedById:n},h=this.mapper.toBackendKeys(o),{data:d,error:p}=await this.client.from(t).insert(h).select().single();if(p)throw c(p,"SupabaseCrudAdapter","add",t);if(!d)throw c(new Error("Insert returned no data"),"SupabaseCrudAdapter","add",t);const l=String(d[this.idColumn]??""),C=this.normalize({...d,id:l});return{id:l,data:C}}async set(t,r,s,a){const e=a?v(a,s,"SupabaseCrudAdapter.set"):s;let n;try{const{data:d}=await this.client.auth.getSession();n=d?.session?.user?.id}catch(d){throw c(d instanceof Error?d:new Error(String(d)),"SupabaseCrudAdapter","set",t,r)}if(!n)throw c(new Error("Authentication required for this operation"),"SupabaseCrudAdapter","set",t,r);const o=R({...e,[this.idColumn]:r,updatedById:n}),{error:h}=await this.client.from(t).upsert(this.mapper.toBackendKeys(o),{onConflict:this.idColumn});if(h)throw c(h,"SupabaseCrudAdapter","set",t,r)}async update(t,r,s){let a;try{const{data:o}=await this.client.auth.getSession();a=o?.session?.user?.id}catch(o){throw c(o instanceof Error?o:new Error(String(o)),"SupabaseCrudAdapter","update",t,r)}if(!a)throw c(new Error("Authentication required for this operation"),"SupabaseCrudAdapter","update",t,r);const e=R({...s,updatedById:a}),{error:n}=await this.client.from(t).update(this.mapper.toBackendKeys(e)).eq(this.idColumn,r);if(n)throw c(n,"SupabaseCrudAdapter","update",t,r)}async delete(t,r){let s;try{const{data:e}=await this.client.auth.getSession();s=e?.session?.user?.id}catch(e){throw c(e instanceof Error?e:new Error(String(e)),"SupabaseCrudAdapter","delete",t,r)}if(!s)throw c(new Error("Authentication required for this operation"),"SupabaseCrudAdapter","delete",t,r);const{error:a}=await this.client.from(t).delete().eq(this.idColumn,r);if(a)throw c(a,"SupabaseCrudAdapter","delete",t,r)}async query(t,r,s,a){let e=this.client.from(t).select("*",{count:"exact"});const n=m=>this.mapper.toBackendField(m);if(r.where)for(const m of r.where)e=x(e,m,n);const o=r.orderBy?.[0],h=A(o?.field??this.idColumn,"orderBy"),d=n(h),p=o?.direction??"asc";if(r.orderBy&&r.orderBy.length>0)for(const m of r.orderBy){const S=n(A(m.field,"orderBy"));e=e.order(S,{ascending:(m.direction??"asc")==="asc"})}else e=e.order(this.idColumn,{ascending:!0});const l=r.limit??50,C=r.startAfter??null;C&&(p==="asc"?e=e.gt(d,C):e=e.lt(d,C)),e=e.limit(l+1);const i=await e,{data:y,error:w,count:F}=i;if(w)throw c(w instanceof Error?w:new Error(w.message||String(w)),"SupabaseCrudAdapter","query",t);const E=y??[],I=E.length>l,_=I?E.slice(0,l):E,g=_[_.length-1],O=g!=null?String(g[d]??g[this.idColumn]??g.id??""):null,T=s?await this.getUserRole():null,N=[];for(const m of _){const S=String(m[this.idColumn]??m.id??""),z={...m,id:S},B=this.normalize(z),M=s&&T?b(B,s,T):B;N.push({...M,id:S})}return{items:N,total:F??void 0,hasMore:I,lastVisible:O}}subscribe(t,r,s,a){const e=this.client.channel(`doc:${encodeURIComponent(t)}:${encodeURIComponent(r)}`);return e.on("postgres_changes",{event:"*",schema:"public",table:t,filter:`${this.idColumn}=eq.${$(r)}`},n=>{const o=n.new;if(!o){s(null);return}const h={...o,id:o[this.idColumn]??r},d=this.normalize(h);a?this.getUserRole().then(p=>{const l=b(d,a,p);s({...l,id:r})},()=>{const p=b(d,a,"guest");s({...p,id:r})}):s({...d,id:r})}).subscribe(n=>{(n==="CHANNEL_ERROR"||n==="TIMED_OUT")&&s(null,c(new Error(`Subscription error: ${n}`),"SupabaseCrudAdapter","subscribe",t,r))}),()=>{this.client.removeChannel(e)}}subscribeToCollection(t,r,s,a){const e=(r.where??[]).filter(i=>i.operator===f.EQ&&i.value!=null),n=i=>this.mapper.toBackendField(i),o=e.length>0?e.map(i=>`${n(A(i.field,"subscribeToCollection.where"))}=eq.${$(String(i.value))}`).join("&"):void 0,h=e.length>0?e.map(i=>`${n(A(i.field,"subscribeToCollection.where"))}=eq.${encodeURIComponent(String(i.value))}`).join("&"):void 0,d=h?`col:${encodeURIComponent(t)}:${h}`:`col:${encodeURIComponent(t)}`,p=this.client.channel(d);let l=null;const C=()=>{l&&clearTimeout(l),l=setTimeout(()=>{this.query(t,r,a).then(i=>s(i.items),i=>{const y=i instanceof Error?i:new Error(String(i));s([],c(y,"SupabaseCrudAdapter","subscribeToCollection",t))})},100)};return p.on("postgres_changes",{event:"*",schema:"public",table:t,...o?{filter:o}:{}},C).subscribe(i=>{(i==="CHANNEL_ERROR"||i==="TIMED_OUT")&&s([],c(new Error(`Subscription error: ${i}`),"SupabaseCrudAdapter","subscribeToCollection",t))}),()=>{l&&clearTimeout(l),this.client.removeChannel(p)}}}export{P as SupabaseCrudAdapter};
1
+ import{wrapCrudError as l,validateWithSchema as $,DoNotDevError as M,CRUD_OPERATORS as m,ERROR_CODES as D,filterVisibleFields as R,getVisibleFields as Y}from"@donotdev/core";import{createEntityAwareMapper as G,defaultFieldMapper as P,getEntityFieldNames as Q}from"../fieldMapper";const W="id";function N(d){const{createdAt:t,updatedAt:e,created_at:r,updated_at:s,...n}=d;return n}function U(d){if(/[(),]/.test(d))throw new Error("[crudAdapter] Invalid filter value: contains PostgREST operator characters");return d}function y(d,t){return typeof d=="string"?d:String(d)}function O(d,t,e){const{field:r,operator:s,value:n}=t,o=e(y(r,"where"));switch(s){case m.EQ:return d.eq(o,n);case m.NEQ:return d.neq(o,n);case m.LT:return d.lt(o,n);case m.LTE:return d.lte(o,n);case m.GT:return d.gt(o,n);case m.GTE:return d.gte(o,n);case m.IN:return d.in(o,Array.isArray(n)?n:[n]);case m.NOT_IN:return d.not(o,"in",Array.isArray(n)?n:[n]);case m.ARRAY_CONTAINS:return d.contains(o,n);case m.ARRAY_CONTAINS_ANY:if(!Array.isArray(n)||n.length===0)throw new M("SupabaseCrudAdapter: ARRAY_CONTAINS_ANY requires a non-empty array",D.INVALID_ARGUMENT,{context:{operator:s,field:r}});return d.overlaps(o,n);default:throw new M(`SupabaseCrudAdapter: unsupported operator "${s}"`,D.INVALID_ARGUMENT,{context:{operator:s,field:r}})}}class H{client;idColumn;dbLevelSecurity=!0;mapper;_roleCache=null;_unsubscribeAuthState;constructor(t,e=W){this.client=t,this.idColumn=e,this.mapper=P;const{data:{subscription:r}}=this.client.auth.onAuthStateChange(()=>{this._roleCache=null});this._unsubscribeAuthState=()=>r.unsubscribe()}invalidateRoleCache(){this._roleCache=null}dispose(){this._unsubscribeAuthState()}createEntityMapper(t){const e=Q(t);return e.length===0?this.mapper:G(e)}async getUserRole(){const t=Date.now();if(this._roleCache&&t<this._roleCache.expiresAt)return this._roleCache.role;try{const{data:e}=await this.client.auth.getSession(),r=e?.session?.user?.app_metadata;let s="guest";return r&&(r.role==="super"||r.isSuper===!0?s="super":r.role==="admin"||r.isAdmin===!0?s="admin":r.role==="user"?s="user":s=r.role||"guest"),this._roleCache={role:s,expiresAt:t+3e4},s}catch{return"guest"}}buildSelectColumns(t,e){if(!t)return"*";try{const r=Y(t,e);if(r.length===0)return"*";const s=new Set(r.map(n=>this.mapper.toBackendField(n)));return s.add(this.idColumn),s.add("status"),Array.from(s).join(",")}catch{return"*"}}async get(t,e,r){const s=r?await this.getUserRole():"guest",n=this.buildSelectColumns(r,s),{data:o,error:a}=await this.client.from(t).select(n).eq(this.idColumn,e).maybeSingle();if(a){if(a.code==="PGRST116"||a.message?.includes("No rows"))return null;throw l(a,"SupabaseCrudAdapter","get",t,e)}if(!o)return null;const c=o,i=String(c[this.idColumn]??e),p={...c,id:i},f=this.createEntityMapper(r).fromBackendRow(p);return r?{...R(f,r,s),id:i}:{...f,id:i}}async add(t,e,r){const s=r?$(r,e,"SupabaseCrudAdapter.add"):e,n=N(s);let o;try{const{data:w}=await this.client.auth.getSession();o=w?.session?.user?.id}catch(w){throw l(w instanceof Error?w:new Error(String(w)),"SupabaseCrudAdapter","add",t)}if(!o)throw l(new Error("Authentication required for this operation"),"SupabaseCrudAdapter","add",t);const a={...n,createdById:o,updatedById:o},c=this.mapper.toBackendKeys(a),{data:i,error:p}=await this.client.from(t).insert(c).select().single();if(p)throw l(p,"SupabaseCrudAdapter","add",t);if(!i)throw l(new Error("Insert returned no data"),"SupabaseCrudAdapter","add",t);const h=String(i[this.idColumn]??""),f=this.mapper.fromBackendRow({...i,id:h});return{id:h,data:f}}async set(t,e,r,s){const n=s?$(s,r,"SupabaseCrudAdapter.set"):r;let o;try{const{data:i}=await this.client.auth.getSession();o=i?.session?.user?.id}catch(i){throw l(i instanceof Error?i:new Error(String(i)),"SupabaseCrudAdapter","set",t,e)}if(!o)throw l(new Error("Authentication required for this operation"),"SupabaseCrudAdapter","set",t,e);const a=N({...n,[this.idColumn]:e,updatedById:o}),{error:c}=await this.client.from(t).upsert(this.mapper.toBackendKeys(a),{onConflict:this.idColumn});if(c)throw l(c,"SupabaseCrudAdapter","set",t,e)}async update(t,e,r){let s;try{const{data:a}=await this.client.auth.getSession();s=a?.session?.user?.id}catch(a){throw l(a instanceof Error?a:new Error(String(a)),"SupabaseCrudAdapter","update",t,e)}if(!s)throw l(new Error("Authentication required for this operation"),"SupabaseCrudAdapter","update",t,e);const n=N({...r,updatedById:s}),{error:o}=await this.client.from(t).update(this.mapper.toBackendKeys(n)).eq(this.idColumn,e);if(o)throw l(o,"SupabaseCrudAdapter","update",t,e)}async delete(t,e){let r;try{const{data:n}=await this.client.auth.getSession();r=n?.session?.user?.id}catch(n){throw l(n instanceof Error?n:new Error(String(n)),"SupabaseCrudAdapter","delete",t,e)}if(!r)throw l(new Error("Authentication required for this operation"),"SupabaseCrudAdapter","delete",t,e);const{error:s}=await this.client.from(t).delete().eq(this.idColumn,e);if(s)throw l(s,"SupabaseCrudAdapter","delete",t,e)}async query(t,e,r,s){const n=r?await this.getUserRole():null,o=r&&n?this.buildSelectColumns(r,n):"*";let a=this.client.from(t).select(o,{count:"exact"});const c=C=>this.mapper.toBackendField(C);if(e.where)for(const C of e.where)a=O(a,C,c);const i=e.orderBy?.[0],p=y(i?.field??this.idColumn,"orderBy"),h=c(p),f=i?.direction??"asc";if(e.orderBy&&e.orderBy.length>0)for(const C of e.orderBy){const b=c(y(C.field,"orderBy"));a=a.order(b,{ascending:(C.direction??"asc")==="asc"})}else a=a.order(this.idColumn,{ascending:!0});const w=e.limit??50,u=e.startAfter??null;let g;if(u){let C=this.client.from(t).select(o,{count:"exact",head:!0});if(e.where)for(const E of e.where)C=O(C,E,c);const b=await C,{count:T}=b;g=T??void 0,f==="asc"?a=a.gt(h,u):a=a.lt(h,u)}a=a.limit(w+1);const q=await a,{data:k,error:A,count:x}=q;if(A)throw l(A instanceof Error?A:new Error(A.message||String(A)),"SupabaseCrudAdapter","query",t);const _=k??[],B=_.length>w,I=B?_.slice(0,w):_,S=I[I.length-1],L=S!=null?String(S[h]??S[this.idColumn]??S.id??""):null,F=n,z=this.createEntityMapper(r),v=[];for(const C of I){const b=String(C[this.idColumn]??C.id??""),T={...C,id:b},E=z.fromBackendRow(T),V=r&&F?R(E,r,F):E;v.push({...V,id:b})}return{items:v,total:g??x??void 0,hasMore:B,lastVisible:L}}subscribe(t,e,r,s){const n=this.client.channel(`doc:${encodeURIComponent(t)}:${encodeURIComponent(e)}`),o=this.createEntityMapper(s);return n.on("postgres_changes",{event:"*",schema:"public",table:t,filter:`${this.idColumn}=eq.${U(e)}`},a=>{const c=a.new;if(!c){r(null);return}const i={...c,id:c[this.idColumn]??e};if(s)this.getUserRole().then(p=>{const h=o.fromBackendRow(i),f=R(h,s,p);r({...f,id:e})},()=>{const p=o.fromBackendRow(i),h=R(p,s,"guest");r({...h,id:e})});else{const p=o.fromBackendRow(i);r({...p,id:e})}}).subscribe(a=>{(a==="CHANNEL_ERROR"||a==="TIMED_OUT")&&r(null,l(new Error(`Subscription error: ${a}`),"SupabaseCrudAdapter","subscribe",t,e))}),()=>{this.client.removeChannel(n)}}subscribeToCollection(t,e,r,s){const n=(e.where??[]).filter(u=>u.operator===m.EQ&&u.value!=null),o=u=>this.mapper.toBackendField(u);n.length>1;const a=n[0],c=a?`${o(y(a.field,"subscribeToCollection.where"))}=eq.${U(String(a.value))}`:void 0,i=n.length>0?n.map(u=>`${o(y(u.field,"subscribeToCollection.where"))}=eq.${encodeURIComponent(String(u.value))}`).join("&"):void 0,p=i?`col:${encodeURIComponent(t)}:${i}`:`col:${encodeURIComponent(t)}`,h=this.client.channel(p);let f=null;const w=()=>{f&&clearTimeout(f),f=setTimeout(()=>{this.query(t,e,s).then(u=>r(u.items),u=>{const g=u instanceof Error?u:new Error(String(u));r([],l(g,"SupabaseCrudAdapter","subscribeToCollection",t))})},100)};return h.on("postgres_changes",{event:"*",schema:"public",table:t,...c?{filter:c}:{}},w).subscribe(u=>{(u==="CHANNEL_ERROR"||u==="TIMED_OUT")&&r([],l(new Error(`Subscription error: ${u}`),"SupabaseCrudAdapter","subscribeToCollection",t))}),()=>{f&&clearTimeout(f),this.client.removeChannel(h)}}}export{H as SupabaseCrudAdapter};
@@ -1 +1 @@
1
- {"version":3,"file":"storageAdapter.d.ts","sourceRoot":"","sources":["../../src/client/storageAdapter.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EACb,YAAY,EACb,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAI5D;;;;;GAKG;AACH,qBAAa,sBAAuB,YAAW,eAAe;IAE1D,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,MAAM,EAAE,cAAc,EACtB,MAAM,GAAE,MAAuB;IAG5C,MAAM,CACV,IAAI,EAAE,IAAI,GAAG,IAAI,EACjB,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,YAAY,CAAC;IAiClB,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYxC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAO3C,OAAO,CAAC,WAAW;CA8BpB"}
1
+ {"version":3,"file":"storageAdapter.d.ts","sourceRoot":"","sources":["../../src/client/storageAdapter.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EACb,YAAY,EACb,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAI5D;;;;;GAKG;AACH,qBAAa,sBAAuB,YAAW,eAAe;IAE1D,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,MAAM,EAAE,cAAc,EACtB,MAAM,GAAE,MAAuB;IAG5C,MAAM,CACV,IAAI,EAAE,IAAI,GAAG,IAAI,EACjB,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,YAAY,CAAC;IA6ClB,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBxC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAO3C,OAAO,CAAC,WAAW;CAoCpB"}
@@ -1,14 +1,3 @@
1
- /**
2
- * @fileoverview Supabase field name mapper — single boundary for app ↔ backend (Postgres) names.
3
- * @description Entity field names (app) are translated to/from backend column names here only.
4
- * Used by SupabaseCrudAdapter (client) and Supabase Edge Function CRUD handlers.
5
- *
6
- * Default: camelCase (app) ↔ snake_case (backend). Option: identity for entities using snake_case.
7
- *
8
- * @version 0.0.1
9
- * @since 0.0.1
10
- */
11
- export type FieldMappingMode = 'camelToSnake' | 'identity';
12
1
  export interface SupabaseFieldMapper {
13
2
  /** App field name → backend column name */
14
3
  toBackendField(appFieldName: string): string;
@@ -17,11 +6,28 @@ export interface SupabaseFieldMapper {
17
6
  /** Backend row → app-shaped object (keys mapped, timestamps to ISO) */
18
7
  fromBackendRow(row: Record<string, unknown>): Record<string, unknown>;
19
8
  }
9
+ /** Default mapper (camelToSnake) for use by adapters and edge functions */
10
+ export declare const defaultFieldMapper: SupabaseFieldMapper;
11
+ /**
12
+ * Extract entity field names from a Valibot schema.
13
+ * Works with any schema that has an `entries` property (object schemas).
14
+ * Returns empty array if schema is missing or has no entries.
15
+ *
16
+ * @param schema - Valibot schema (or undefined)
17
+ * @returns Array of entity field name strings
18
+ */
19
+ export declare function getEntityFieldNames(schema: unknown): string[];
20
20
  /**
21
- * Create the single boundary mapper for Supabase/Postgres.
22
- * @param fieldMapping - 'camelToSnake' (default) or 'identity' when entity uses snake_case
21
+ * Entity-aware mapper factory. Builds a reverse map from DB column (snake_case)
22
+ * back to the entity field name as defined in the schema.
23
+ *
24
+ * This ensures `fromBackendRow` returns keys that match entity field names —
25
+ * so `filterVisibleFields` can find them regardless of whether the entity
26
+ * uses snake_case (`first_name`) or camelCase (`firstName`) field names.
27
+ *
28
+ * Write paths (`toBackendField`, `toBackendKeys`) are shared with the default mapper.
29
+ *
30
+ * @param entityFieldNames - Field names from the entity schema (use `getEntityFieldNames(schema)`)
23
31
  */
24
- export declare function createFieldMapper(fieldMapping?: FieldMappingMode): SupabaseFieldMapper;
25
- /** Default mapper (camelToSnake) for use when no adapter option is set */
26
- export declare const defaultFieldMapper: SupabaseFieldMapper;
32
+ export declare function createEntityAwareMapper(entityFieldNames: string[]): SupabaseFieldMapper;
27
33
  //# sourceMappingURL=fieldMapper.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fieldMapper.d.ts","sourceRoot":"","sources":["../src/fieldMapper.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AAEH,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG,UAAU,CAAC;AA0B3D,MAAM,WAAW,mBAAmB;IAClC,2CAA2C;IAC3C,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7C,kEAAkE;IAClE,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrE,uEAAuE;IACvE,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,GAAE,gBAAiC,GAC9C,mBAAmB,CA8BrB;AAED,0EAA0E;AAC1E,eAAO,MAAM,kBAAkB,qBAAoC,CAAC"}
1
+ {"version":3,"file":"fieldMapper.d.ts","sourceRoot":"","sources":["../src/fieldMapper.ts"],"names":[],"mappings":"AAwCA,MAAM,WAAW,mBAAmB;IAClC,2CAA2C;IAC3C,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7C,kEAAkE;IAClE,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrE,uEAAuE;IACvE,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvE;AAqCD,2EAA2E;AAC3E,eAAO,MAAM,kBAAkB,qBAA8B,CAAC;AAY9D;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,EAAE,CAK7D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,gBAAgB,EAAE,MAAM,EAAE,GACzB,mBAAmB,CAyBrB"}
@@ -1 +1 @@
1
- function s(e){return typeof e!="string"?String(e):e.replace(/[A-Z]/g,i=>`_${i.toLowerCase()}`)}function a(e){return typeof e!="string"?String(e):e.replace(/_([a-z])/g,(i,c)=>c.toUpperCase())}const f=new Set(["createdAt","updatedAt"]);function u(e){if(e instanceof Date)return e.toISOString();if(typeof e=="string"){if(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(e))return e;if(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/.test(e))return new Date(e.replace(" ","T")).toISOString()}return e}function y(e="camelToSnake"){return{toBackendField:t=>{if(typeof t!="string")throw new TypeError(`Supabase field mapper: field name must be a string (e.g. "createdById"), got ${typeof t}. Check where query options (orderBy/where) are built \u2014 ensure "field" is the field name string, not a schema or other value.`);return e==="identity"?t:s(t)},toBackendKeys:t=>{if(e==="identity")return t;const n={};for(const[r,o]of Object.entries(t))n[s(r)]=o;return n},fromBackendRow:t=>{const n={};for(const[r,o]of Object.entries(t)){const d=e==="identity"?r:a(r);n[d]=f.has(d)?u(o):o}return n}}}const S=y("camelToSnake");export{y as createFieldMapper,S as defaultFieldMapper};
1
+ function d(e){return typeof e!="string"?String(e):e.replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").toLowerCase()}function i(e){return typeof e!="string"?String(e):e.replace(/_([a-z])/g,(r,t)=>t.toUpperCase())}const f=new Set(["createdAt","updatedAt"]);function u(e){if(e instanceof Date)return e.toISOString();if(typeof e=="string"){if(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(e))return e;if(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/.test(e))return new Date(e.replace(" ","T")).toISOString()}return e}function y(){return{toBackendField:n=>{if(typeof n!="string")throw new TypeError(`Supabase field mapper: field name must be a string (e.g. "createdById"), got ${typeof n}. Check where query options (orderBy/where) are built \u2014 ensure "field" is the field name string, not a schema or other value.`);return d(n)},toBackendKeys:n=>{const o={};for(const[a,c]of Object.entries(n))o[d(a)]=c;return o},fromBackendRow:n=>{const o={};for(const[a,c]of Object.entries(n)){const s=i(a);o[s]=f.has(s)?u(c):c}return o}}}const p=y(),g=["id","createdAt","updatedAt","createdById","updatedById","status"];function k(e){if(!e||typeof e!="object")return[];const r=e.entries;return!r||typeof r!="object"?[]:Object.keys(r)}function l(e){const r=new Map;for(const t of e)r.set(d(t),t);for(const t of g)r.set(d(t),t);return{toBackendField:p.toBackendField,toBackendKeys:p.toBackendKeys,fromBackendRow:t=>{const n={};for(const[o,a]of Object.entries(t)){const c=r.get(o)??i(o);n[c]=f.has(c)?u(a):a}return n}}}export{l as createEntityAwareMapper,p as defaultFieldMapper,k as getEntityFieldNames};
package/dist/index.d.ts CHANGED
@@ -5,5 +5,5 @@
5
5
  * @packageDocumentation
6
6
  */
7
7
  export * from './client';
8
- export { createFieldMapper, defaultFieldMapper, type FieldMappingMode, type SupabaseFieldMapper, } from './fieldMapper';
8
+ export { createEntityAwareMapper, defaultFieldMapper, getEntityFieldNames, type SupabaseFieldMapper, } from './fieldMapper';
9
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,cAAc,UAAU,CAAC;AACzB,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,GACzB,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,cAAc,UAAU,CAAC;AACzB,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,EACnB,KAAK,mBAAmB,GACzB,MAAM,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export*from"./client";import{createFieldMapper as a,defaultFieldMapper as o}from"./fieldMapper";export{a as createFieldMapper,o as defaultFieldMapper};
1
+ export*from"./client";import{createEntityAwareMapper as a,defaultFieldMapper as p,getEntityFieldNames as i}from"./fieldMapper";export{a as createEntityAwareMapper,p as defaultFieldMapper,i as getEntityFieldNames};
@@ -1 +1 @@
1
- {"version":3,"file":"authAdapter.d.ts","sourceRoot":"","sources":["../../src/server/authAdapter.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,aAAa,EACb,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAO5D;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,GAChB,cAAc,CAOhB;AAMD;;;;;;;GAOG;AACH,qBAAa,yBAA0B,YAAW,kBAAkB;IACtD,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,cAAc;IAE7C,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAmClD,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IA0BtD,eAAe,CACnB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC;CAwBjB"}
1
+ {"version":3,"file":"authAdapter.d.ts","sourceRoot":"","sources":["../../src/server/authAdapter.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,aAAa,EACb,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAO5D;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,GAChB,cAAc,CAOhB;AAMD;;;;;;;GAOG;AACH,qBAAa,yBAA0B,YAAW,kBAAkB;IACtD,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,cAAc;IAE7C,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAwClD,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IA0BtD,eAAe,CACnB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC,IAAI,CAAC;CAmCjB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donotdev/supabase",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -22,7 +22,7 @@
22
22
  "scripts": {
23
23
  "dev": "tsc --noEmit --watch --listFiles false --listEmittedFiles false",
24
24
  "clean": "rimraf dist tsconfig.tsbuildinfo",
25
- "type-check": "tsc --noEmit",
25
+ "type-check": "bunx tsc --noEmit",
26
26
  "test": "vitest run",
27
27
  "test:watch": "vitest"
28
28
  },
@@ -30,8 +30,8 @@
30
30
  "valibot": "^1.2.0"
31
31
  },
32
32
  "peerDependencies": {
33
- "@donotdev/core": "^0.0.24",
34
- "@supabase/supabase-js": "^2.45.0"
33
+ "@donotdev/core": "^0.0.25",
34
+ "@supabase/supabase-js": "^2.76.11"
35
35
  },
36
36
  "peerDependenciesMeta": {},
37
37
  "files": [