@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 +188 -36
- package/dist/client/auth.d.ts.map +1 -1
- package/dist/client/callableProvider.d.ts.map +1 -1
- package/dist/client/crudAdapter.d.ts +15 -9
- package/dist/client/crudAdapter.d.ts.map +1 -1
- package/dist/client/crudAdapter.js +1 -1
- package/dist/client/storageAdapter.d.ts.map +1 -1
- package/dist/fieldMapper.d.ts +22 -16
- package/dist/fieldMapper.d.ts.map +1 -1
- package/dist/fieldMapper.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/server/authAdapter.d.ts.map +1 -1
- package/package.json +4 -4
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
|
-
|
|
5
|
+
---
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Complete Supabase Setup
|
|
8
8
|
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19
|
+
Then run `dndev setup supabase` — it handles the rest.
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
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!
|
|
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()` |
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
|
99
|
-
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
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
|
|
267
|
+
### CLI Commands
|
|
107
268
|
|
|
108
|
-
| Command |
|
|
109
|
-
|
|
110
|
-
| `dndev emu` |
|
|
111
|
-
| `dndev deploy` |
|
|
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":"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;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/client/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -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;
|
|
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
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
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
|
|
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
|
-
|
|
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;
|
|
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
|
|
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;
|
|
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"}
|
package/dist/fieldMapper.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
22
|
-
*
|
|
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
|
|
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":"
|
|
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"}
|
package/dist/fieldMapper.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
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 {
|
|
8
|
+
export { createEntityAwareMapper, defaultFieldMapper, getEntityFieldNames, type SupabaseFieldMapper, } from './fieldMapper';
|
|
9
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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{
|
|
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;
|
|
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.
|
|
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.
|
|
34
|
-
"@supabase/supabase-js": "^2.
|
|
33
|
+
"@donotdev/core": "^0.0.25",
|
|
34
|
+
"@supabase/supabase-js": "^2.76.11"
|
|
35
35
|
},
|
|
36
36
|
"peerDependenciesMeta": {},
|
|
37
37
|
"files": [
|