@ankhorage/supabase-db 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +181 -0
- package/dist/adapter.d.ts +3 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +160 -0
- package/dist/adapter.js.map +1 -0
- package/dist/admin.d.ts +3 -0
- package/dist/admin.d.ts.map +1 -0
- package/dist/admin.js +148 -0
- package/dist/admin.js.map +1 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +37 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/query.d.ts +4 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +94 -0
- package/dist/query.js.map +1 -0
- package/dist/realtime.d.ts +10 -0
- package/dist/realtime.d.ts.map +1 -0
- package/dist/realtime.js +95 -0
- package/dist/realtime.js.map +1 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +7 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +43 -0
- package/dist/validation.js.map +1 -0
- package/package.json +82 -0
- package/src/adapter.test.ts +173 -0
- package/src/adapter.ts +249 -0
- package/src/admin.test.ts +102 -0
- package/src/admin.ts +205 -0
- package/src/errors.ts +66 -0
- package/src/index.ts +23 -0
- package/src/query.ts +122 -0
- package/src/realtime.test.ts +178 -0
- package/src/realtime.ts +145 -0
- package/src/types.ts +145 -0
- package/src/validation.ts +57 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# @ankhorage/supabase-db
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8d13cfd: Implement the initial Supabase database adapter package with provider-neutral CRUD operations, optional realtime subscriptions, guarded schema/admin SQL generation, offline tests, README documentation, and agent rules.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ankhorage
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
<!-- markdownlint-disable MD013 -->
|
|
2
|
+
|
|
3
|
+
# @ankhorage/supabase-db
|
|
4
|
+
|
|
5
|
+
Supabase database adapter for Ankhorage data contracts.
|
|
6
|
+
|
|
7
|
+
`@ankhorage/supabase-db` implements Supabase-specific persistence behind the provider-neutral database interfaces from `@ankhorage/contracts/db`. Higher-level packages can use typed CRUD operations, optional realtime subscriptions, and guarded schema helpers without importing Supabase details into UI, runtime rendering, generated apps, or ZORA components.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun add @ankhorage/supabase-db @ankhorage/contracts @supabase/supabase-js
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Boundaries
|
|
16
|
+
|
|
17
|
+
This package owns Supabase Database behavior:
|
|
18
|
+
|
|
19
|
+
- adapter creation
|
|
20
|
+
- table select workflows
|
|
21
|
+
- insert, update, and delete workflows
|
|
22
|
+
- filter, order, and pagination mapping
|
|
23
|
+
- provider error normalization
|
|
24
|
+
- optional realtime change subscriptions
|
|
25
|
+
- guarded schema SQL generation and privileged execution hooks
|
|
26
|
+
|
|
27
|
+
This package does not own:
|
|
28
|
+
|
|
29
|
+
- ZORA components or patterns
|
|
30
|
+
- Studio UI
|
|
31
|
+
- runtime manifest interpretation
|
|
32
|
+
- generated routes or layouts
|
|
33
|
+
- CLI generation
|
|
34
|
+
- deployment orchestration
|
|
35
|
+
- auth or storage adapters
|
|
36
|
+
|
|
37
|
+
## Runtime CRUD adapter
|
|
38
|
+
|
|
39
|
+
Use the runtime adapter with client-safe Supabase credentials. The adapter speaks the `DbAdapter` shape from `@ankhorage/contracts/db`.
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { createSupabaseDbAdapter } from '@ankhorage/supabase-db';
|
|
43
|
+
|
|
44
|
+
const db = createSupabaseDbAdapter({
|
|
45
|
+
url: process.env.SUPABASE_URL ?? '',
|
|
46
|
+
anonKey: process.env.SUPABASE_ANON_KEY ?? '',
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const posts = await db.select({
|
|
50
|
+
table: 'posts',
|
|
51
|
+
columns: ['id', 'title', 'created_at'],
|
|
52
|
+
filters: [{ field: 'published', operator: 'eq', value: true }],
|
|
53
|
+
sort: [{ field: 'created_at', direction: 'desc' }],
|
|
54
|
+
page: { limit: 20 },
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (posts.ok) {
|
|
58
|
+
console.log(posts.data);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The runtime adapter uses Supabase PostgREST endpoints and returns normalized `DbResult` values. Expected provider failures, permission errors, missing tables, and invalid queries are returned as stable adapter errors instead of raw Supabase response objects.
|
|
63
|
+
|
|
64
|
+
## Supported CRUD operations
|
|
65
|
+
|
|
66
|
+
The adapter implements:
|
|
67
|
+
|
|
68
|
+
- `select`
|
|
69
|
+
- `findById`
|
|
70
|
+
- `insert`
|
|
71
|
+
- `update`
|
|
72
|
+
- `delete`
|
|
73
|
+
|
|
74
|
+
`update` and `delete` require at least one filter to avoid accidental whole-table mutations.
|
|
75
|
+
|
|
76
|
+
## Realtime
|
|
77
|
+
|
|
78
|
+
Realtime is optional. It is not part of the mandatory CRUD contract.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { createClient } from '@supabase/supabase-js';
|
|
82
|
+
import { createSupabaseDbAdapter } from '@ankhorage/supabase-db';
|
|
83
|
+
|
|
84
|
+
const supabase = createClient(process.env.SUPABASE_URL ?? '', process.env.SUPABASE_ANON_KEY ?? '');
|
|
85
|
+
|
|
86
|
+
const db = createSupabaseDbAdapter({
|
|
87
|
+
url: process.env.SUPABASE_URL ?? '',
|
|
88
|
+
anonKey: process.env.SUPABASE_ANON_KEY ?? '',
|
|
89
|
+
realtime: true,
|
|
90
|
+
realtimeClient: supabase,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const subscription = db.realtime?.subscribeToCollection({ table: 'posts' }, (event) => {
|
|
94
|
+
console.log(event.kind, event.record, event.previousRecord);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
await subscription?.unsubscribe();
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Realtime events are normalized to provider-neutral kinds:
|
|
101
|
+
|
|
102
|
+
- `insert`
|
|
103
|
+
- `update`
|
|
104
|
+
- `delete`
|
|
105
|
+
|
|
106
|
+
Supabase projects must have database change replication configured for realtime table events. If realtime is not enabled or no realtime client is provided, CRUD still works and `capabilities.supportsRealtime` is `false`.
|
|
107
|
+
|
|
108
|
+
## Admin/schema adapter
|
|
109
|
+
|
|
110
|
+
Schema operations are privileged and separate from runtime CRUD.
|
|
111
|
+
|
|
112
|
+
By default, the admin adapter generates SQL only:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { createSupabaseDbAdminAdapter } from '@ankhorage/supabase-db';
|
|
116
|
+
|
|
117
|
+
const admin = createSupabaseDbAdminAdapter({
|
|
118
|
+
url: process.env.SUPABASE_URL ?? '',
|
|
119
|
+
serviceRoleKey: process.env.SUPABASE_SERVICE_ROLE_KEY,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const plan = admin.generateCreateCollectionSql({
|
|
123
|
+
name: 'posts',
|
|
124
|
+
fields: [
|
|
125
|
+
{ name: 'title', type: 'text', required: true },
|
|
126
|
+
{ name: 'body', type: 'text' },
|
|
127
|
+
{ name: 'created_at', type: 'datetime' },
|
|
128
|
+
],
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
if (plan.ok) {
|
|
132
|
+
console.log(plan.sql);
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Direct execution requires all of the following:
|
|
137
|
+
|
|
138
|
+
- `execute: true`
|
|
139
|
+
- a `serviceRoleKey`
|
|
140
|
+
- an injected `executeSql(sql)` callback from a privileged environment
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
const admin = createSupabaseDbAdminAdapter({
|
|
144
|
+
url: process.env.SUPABASE_URL ?? '',
|
|
145
|
+
serviceRoleKey: process.env.SUPABASE_SERVICE_ROLE_KEY,
|
|
146
|
+
execute: true,
|
|
147
|
+
executeSql: async (sql) => {
|
|
148
|
+
// Run SQL through a trusted backend, migration runner, or RPC you control.
|
|
149
|
+
return { ok: true };
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
> Do not use service-role credentials in client/runtime code.
|
|
155
|
+
|
|
156
|
+
The admin adapter is intended for trusted Studio backends, CLI tooling, deployment tooling, or migration workflows. It must not be bundled into generated client screens.
|
|
157
|
+
|
|
158
|
+
## Data binding architecture
|
|
159
|
+
|
|
160
|
+
A future app flow should look like this:
|
|
161
|
+
|
|
162
|
+
```txt
|
|
163
|
+
Studio creates a collection definition
|
|
164
|
+
-> privileged adapter generates or applies schema
|
|
165
|
+
Runtime queries rows through DbAdapter
|
|
166
|
+
-> runtime maps row fields to component props
|
|
167
|
+
ZORA renders presentational patterns only
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
For example, a future ZORA `PostCard` should receive props. It should not import Supabase or this package.
|
|
171
|
+
|
|
172
|
+
## Development
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
bun install
|
|
176
|
+
bun run build
|
|
177
|
+
bun run lint:fix
|
|
178
|
+
bun run test
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Tests are mocked and must not call real Supabase services.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAY9E,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,GAAG,iBAAiB,CAgG5F"}
|
package/dist/adapter.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { createDbError, mapHttpError, mapNetworkError } from './errors.js';
|
|
2
|
+
import { buildMutationUrl, buildSelectUrl } from './query.js';
|
|
3
|
+
import { createRealtimeApi } from './realtime.js';
|
|
4
|
+
import { validateFilters, validateKey, validateUrl } from './validation.js';
|
|
5
|
+
export function createSupabaseDbAdapter(options) {
|
|
6
|
+
const config = normalizeConfig(options);
|
|
7
|
+
const baseAdapter = {
|
|
8
|
+
capabilities: {
|
|
9
|
+
supportsTransactions: false,
|
|
10
|
+
supportsReturning: true,
|
|
11
|
+
supportsRealtime: config.realtime && config.realtimeClient !== undefined,
|
|
12
|
+
},
|
|
13
|
+
async select(input) {
|
|
14
|
+
return requestRows(config, buildSelectUrl(config.url, input), {
|
|
15
|
+
method: 'GET',
|
|
16
|
+
});
|
|
17
|
+
},
|
|
18
|
+
async findById(input) {
|
|
19
|
+
const result = await requestRows(config, buildSelectUrl(config.url, {
|
|
20
|
+
table: input.table,
|
|
21
|
+
columns: input.columns,
|
|
22
|
+
filters: [{ field: 'id', operator: 'eq', value: input.id }],
|
|
23
|
+
page: { limit: 1 },
|
|
24
|
+
}), { method: 'GET' });
|
|
25
|
+
if (!result.ok) {
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
const records = result.data ?? [];
|
|
29
|
+
return { ok: true, data: records[0] ?? null };
|
|
30
|
+
},
|
|
31
|
+
async insert(input) {
|
|
32
|
+
const url = buildMutationUrl(config.url, input.table, []);
|
|
33
|
+
return requestRows(config, url, {
|
|
34
|
+
method: 'POST',
|
|
35
|
+
body: JSON.stringify(input.values),
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
async update(input) {
|
|
39
|
+
const validationError = validateRequiredFilters(input.filters, 'Update');
|
|
40
|
+
if (validationError !== null) {
|
|
41
|
+
return validationError;
|
|
42
|
+
}
|
|
43
|
+
const url = buildMutationUrl(config.url, input.table, input.filters);
|
|
44
|
+
return requestRows(config, url, {
|
|
45
|
+
method: 'PATCH',
|
|
46
|
+
body: JSON.stringify(input.values),
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
async delete(input) {
|
|
50
|
+
const validationError = validateRequiredFilters(input.filters, 'Delete');
|
|
51
|
+
if (validationError !== null) {
|
|
52
|
+
return validationError;
|
|
53
|
+
}
|
|
54
|
+
const url = buildMutationUrl(config.url, input.table, input.filters);
|
|
55
|
+
return requestRows(config, url, {
|
|
56
|
+
method: 'DELETE',
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
if (!config.realtime || config.realtimeClient === undefined) {
|
|
61
|
+
return baseAdapter;
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
...baseAdapter,
|
|
65
|
+
realtime: createRealtimeApi({
|
|
66
|
+
client: config.realtimeClient,
|
|
67
|
+
defaultSchema: config.schema,
|
|
68
|
+
}),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function normalizeConfig(options) {
|
|
72
|
+
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
73
|
+
if (typeof fetchImplementation !== 'function') {
|
|
74
|
+
throw new TypeError('A fetch implementation is required to use Supabase Database.');
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
url: validateUrl(options.url),
|
|
78
|
+
anonKey: validateKey(options.anonKey, 'Supabase anon key'),
|
|
79
|
+
schema: options.schema ?? 'public',
|
|
80
|
+
fetch: fetchImplementation,
|
|
81
|
+
realtime: options.realtime ?? false,
|
|
82
|
+
realtimeClient: options.realtimeClient,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
async function requestRows(config, url, init) {
|
|
86
|
+
try {
|
|
87
|
+
const response = await config.fetch(url, {
|
|
88
|
+
...init,
|
|
89
|
+
headers: createHeaders(config, init.headers),
|
|
90
|
+
});
|
|
91
|
+
const body = await readJsonBody(response);
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
return { ok: false, error: mapHttpError(response.status, body) };
|
|
94
|
+
}
|
|
95
|
+
const records = normalizeRecords(body);
|
|
96
|
+
if (records === null) {
|
|
97
|
+
return {
|
|
98
|
+
ok: false,
|
|
99
|
+
error: createDbError('provider_error', 'Supabase Database returned an invalid records response.', body),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return { ok: true, data: records };
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (error instanceof TypeError) {
|
|
106
|
+
return { ok: false, error: createDbError('validation_error', error.message, error) };
|
|
107
|
+
}
|
|
108
|
+
return { ok: false, error: mapNetworkError(error) };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function validateRequiredFilters(filters, label) {
|
|
112
|
+
try {
|
|
113
|
+
validateFilters(filters, label);
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
return {
|
|
118
|
+
ok: false,
|
|
119
|
+
error: createDbError('validation_error', error instanceof Error ? error.message : `${label} filters are invalid.`, error),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function createHeaders(config, existingHeaders) {
|
|
124
|
+
const headers = new Headers(existingHeaders);
|
|
125
|
+
headers.set('apikey', config.anonKey);
|
|
126
|
+
headers.set('Authorization', `Bearer ${config.anonKey}`);
|
|
127
|
+
headers.set('Accept', 'application/json');
|
|
128
|
+
headers.set('Content-Type', 'application/json');
|
|
129
|
+
headers.set('Prefer', 'return=representation');
|
|
130
|
+
if (config.schema !== 'public') {
|
|
131
|
+
headers.set('Accept-Profile', config.schema);
|
|
132
|
+
headers.set('Content-Profile', config.schema);
|
|
133
|
+
}
|
|
134
|
+
return headers;
|
|
135
|
+
}
|
|
136
|
+
async function readJsonBody(response) {
|
|
137
|
+
const text = await response.text();
|
|
138
|
+
if (text.trim().length === 0) {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
return JSON.parse(text);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return text;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function normalizeRecords(value) {
|
|
149
|
+
if (Array.isArray(value)) {
|
|
150
|
+
return value.filter((isRecord));
|
|
151
|
+
}
|
|
152
|
+
if (isRecord(value)) {
|
|
153
|
+
return [value];
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
function isRecord(value) {
|
|
158
|
+
return typeof value === 'object' && value !== null;
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElD,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAW5E,MAAM,UAAU,uBAAuB,CAAC,OAAiC;IACvE,MAAM,MAAM,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IACxC,MAAM,WAAW,GAAc;QAC7B,YAAY,EAAE;YACZ,oBAAoB,EAAE,KAAK;YAC3B,iBAAiB,EAAE,IAAI;YACvB,gBAAgB,EAAE,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,cAAc,KAAK,SAAS;SACzE;QAED,KAAK,CAAC,MAAM,CACV,KAAoB;YAEpB,OAAO,WAAW,CAAU,MAAM,EAAE,cAAc,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE;gBACrE,MAAM,EAAE,KAAK;aACd,CAAC,CAAC;QACL,CAAC;QAED,KAAK,CAAC,QAAQ,CACZ,KAAsB;YAEtB,MAAM,MAAM,GAAG,MAAM,WAAW,CAC9B,MAAM,EACN,cAAc,CAAC,MAAM,CAAC,GAAG,EAAE;gBACzB,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;gBAC3D,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE;aACnB,CAAC,EACF,EAAE,MAAM,EAAE,KAAK,EAAE,CAClB,CAAC;YAEF,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;gBACf,OAAO,MAAM,CAAC;YAChB,CAAC;YAED,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YAElC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;QAChD,CAAC;QAED,KAAK,CAAC,MAAM,CACV,KAA6B;YAE7B,MAAM,GAAG,GAAG,gBAAgB,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAE1D,OAAO,WAAW,CAAU,MAAM,EAAE,GAAG,EAAE;gBACvC,MAAM,EAAE,MAAM;gBACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC;aACnC,CAAC,CAAC;QACL,CAAC;QAED,KAAK,CAAC,MAAM,CACV,KAA6B;YAE7B,MAAM,eAAe,GAAG,uBAAuB,CAAC,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YAEzE,IAAI,eAAe,KAAK,IAAI,EAAE,CAAC;gBAC7B,OAAO,eAAe,CAAC;YACzB,CAAC;YAED,MAAM,GAAG,GAAG,gBAAgB,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YAErE,OAAO,WAAW,CAAU,MAAM,EAAE,GAAG,EAAE;gBACvC,MAAM,EAAE,OAAO;gBACf,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC;aACnC,CAAC,CAAC;QACL,CAAC;QAED,KAAK,CAAC,MAAM,CACV,KAAoB;YAEpB,MAAM,eAAe,GAAG,uBAAuB,CAAC,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YAEzE,IAAI,eAAe,KAAK,IAAI,EAAE,CAAC;gBAC7B,OAAO,eAAe,CAAC;YACzB,CAAC;YAED,MAAM,GAAG,GAAG,gBAAgB,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YAErE,OAAO,WAAW,CAAU,MAAM,EAAE,GAAG,EAAE;gBACvC,MAAM,EAAE,QAAQ;aACjB,CAAC,CAAC;QACL,CAAC;KACF,CAAC;IAEF,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;QAC5D,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,OAAO;QACL,GAAG,WAAW;QACd,QAAQ,EAAE,iBAAiB,CAAC;YAC1B,MAAM,EAAE,MAAM,CAAC,cAAc;YAC7B,aAAa,EAAE,MAAM,CAAC,MAAM;SAC7B,CAAC;KACH,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,OAAiC;IACxD,MAAM,mBAAmB,GAAG,OAAO,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC;IAE9D,IAAI,OAAO,mBAAmB,KAAK,UAAU,EAAE,CAAC;QAC9C,MAAM,IAAI,SAAS,CAAC,8DAA8D,CAAC,CAAC;IACtF,CAAC;IAED,OAAO;QACL,GAAG,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC;QAC7B,OAAO,EAAE,WAAW,CAAC,OAAO,CAAC,OAAO,EAAE,mBAAmB,CAAC;QAC1D,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,QAAQ;QAClC,KAAK,EAAE,mBAAmB;QAC1B,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,KAAK;QACnC,cAAc,EAAE,OAAO,CAAC,cAAc;KACvC,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,WAAW,CACxB,MAAwB,EACxB,GAAQ,EACR,IAAiB;IAEjB,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE;YACvC,GAAG,IAAI;YACP,OAAO,EAAE,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC;SAC7C,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAC;QAE1C,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC;QACnE,CAAC;QAED,MAAM,OAAO,GAAG,gBAAgB,CAAU,IAAI,CAAC,CAAC;QAEhD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,aAAa,CAClB,gBAAgB,EAChB,yDAAyD,EACzD,IAAI,CACL;aACF,CAAC;QACJ,CAAC;QAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IACrC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,SAAS,EAAE,CAAC;YAC/B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,aAAa,CAAC,kBAAkB,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC;QACvF,CAAC;QAED,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC;IACtD,CAAC;AACH,CAAC;AAED,SAAS,uBAAuB,CAC9B,OAA4B,EAC5B,KAAa;IAEb,IAAI,CAAC;QACH,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAChC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,aAAa,CAClB,kBAAkB,EAClB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,KAAK,uBAAuB,EACxE,KAAK,CACN;SACF,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CACpB,MAAwB,EACxB,eAAwC;IAExC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;IAE7C,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IACtC,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;IACzD,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC;IAC1C,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;IAChD,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,uBAAuB,CAAC,CAAC;IAE/C,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QAC7C,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAChD,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,QAAkB;IAC5C,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IAEnC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAyB,KAAc;IAC9D,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,MAAM,CAAC,CAAA,QAAiB,CAAA,CAAC,CAAC;IACzC,CAAC;IAED,IAAI,QAAQ,CAAU,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,KAAK,CAAC,CAAC;IACjB,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,QAAQ,CAAyB,KAAc;IACtD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC;AACrD,CAAC","sourcesContent":["import type {\n DbAdapter,\n DbDeleteInput,\n DbFilter,\n DbFindByIdInput,\n DbInsertInput,\n DbRecord,\n DbResult,\n DbSelectInput,\n DbUpdateInput,\n} from '@ankhorage/contracts/db';\n\nimport { createDbError, mapHttpError, mapNetworkError } from './errors.js';\nimport { buildMutationUrl, buildSelectUrl } from './query.js';\nimport { createRealtimeApi } from './realtime.js';\nimport type { SupabaseDbAdapter, SupabaseDbAdapterOptions } from './types.js';\nimport { validateFilters, validateKey, validateUrl } from './validation.js';\n\ninterface NormalizedConfig {\n readonly url: string;\n readonly anonKey: string;\n readonly schema: string;\n readonly fetch: typeof fetch;\n readonly realtime: boolean;\n readonly realtimeClient: SupabaseDbAdapterOptions['realtimeClient'];\n}\n\nexport function createSupabaseDbAdapter(options: SupabaseDbAdapterOptions): SupabaseDbAdapter {\n const config = normalizeConfig(options);\n const baseAdapter: DbAdapter = {\n capabilities: {\n supportsTransactions: false,\n supportsReturning: true,\n supportsRealtime: config.realtime && config.realtimeClient !== undefined,\n },\n\n async select<TRecord extends object = DbRecord>(\n input: DbSelectInput,\n ): Promise<DbResult<TRecord[]>> {\n return requestRows<TRecord>(config, buildSelectUrl(config.url, input), {\n method: 'GET',\n });\n },\n\n async findById<TRecord extends object = DbRecord>(\n input: DbFindByIdInput,\n ): Promise<DbResult<TRecord | null>> {\n const result = await requestRows<TRecord>(\n config,\n buildSelectUrl(config.url, {\n table: input.table,\n columns: input.columns,\n filters: [{ field: 'id', operator: 'eq', value: input.id }],\n page: { limit: 1 },\n }),\n { method: 'GET' },\n );\n\n if (!result.ok) {\n return result;\n }\n\n const records = result.data ?? [];\n\n return { ok: true, data: records[0] ?? null };\n },\n\n async insert<TRecord extends object = DbRecord>(\n input: DbInsertInput<TRecord>,\n ): Promise<DbResult<TRecord[]>> {\n const url = buildMutationUrl(config.url, input.table, []);\n\n return requestRows<TRecord>(config, url, {\n method: 'POST',\n body: JSON.stringify(input.values),\n });\n },\n\n async update<TRecord extends object = DbRecord>(\n input: DbUpdateInput<TRecord>,\n ): Promise<DbResult<TRecord[]>> {\n const validationError = validateRequiredFilters(input.filters, 'Update');\n\n if (validationError !== null) {\n return validationError;\n }\n\n const url = buildMutationUrl(config.url, input.table, input.filters);\n\n return requestRows<TRecord>(config, url, {\n method: 'PATCH',\n body: JSON.stringify(input.values),\n });\n },\n\n async delete<TRecord extends object = DbRecord>(\n input: DbDeleteInput,\n ): Promise<DbResult<TRecord[]>> {\n const validationError = validateRequiredFilters(input.filters, 'Delete');\n\n if (validationError !== null) {\n return validationError;\n }\n\n const url = buildMutationUrl(config.url, input.table, input.filters);\n\n return requestRows<TRecord>(config, url, {\n method: 'DELETE',\n });\n },\n };\n\n if (!config.realtime || config.realtimeClient === undefined) {\n return baseAdapter;\n }\n\n return {\n ...baseAdapter,\n realtime: createRealtimeApi({\n client: config.realtimeClient,\n defaultSchema: config.schema,\n }),\n };\n}\n\nfunction normalizeConfig(options: SupabaseDbAdapterOptions): NormalizedConfig {\n const fetchImplementation = options.fetch ?? globalThis.fetch;\n\n if (typeof fetchImplementation !== 'function') {\n throw new TypeError('A fetch implementation is required to use Supabase Database.');\n }\n\n return {\n url: validateUrl(options.url),\n anonKey: validateKey(options.anonKey, 'Supabase anon key'),\n schema: options.schema ?? 'public',\n fetch: fetchImplementation,\n realtime: options.realtime ?? false,\n realtimeClient: options.realtimeClient,\n };\n}\n\nasync function requestRows<TRecord extends object>(\n config: NormalizedConfig,\n url: URL,\n init: RequestInit,\n): Promise<DbResult<TRecord[]>> {\n try {\n const response = await config.fetch(url, {\n ...init,\n headers: createHeaders(config, init.headers),\n });\n const body = await readJsonBody(response);\n\n if (!response.ok) {\n return { ok: false, error: mapHttpError(response.status, body) };\n }\n\n const records = normalizeRecords<TRecord>(body);\n\n if (records === null) {\n return {\n ok: false,\n error: createDbError(\n 'provider_error',\n 'Supabase Database returned an invalid records response.',\n body,\n ),\n };\n }\n\n return { ok: true, data: records };\n } catch (error) {\n if (error instanceof TypeError) {\n return { ok: false, error: createDbError('validation_error', error.message, error) };\n }\n\n return { ok: false, error: mapNetworkError(error) };\n }\n}\n\nfunction validateRequiredFilters(\n filters: readonly DbFilter[],\n label: string,\n): DbResult<never> | null {\n try {\n validateFilters(filters, label);\n return null;\n } catch (error) {\n return {\n ok: false,\n error: createDbError(\n 'validation_error',\n error instanceof Error ? error.message : `${label} filters are invalid.`,\n error,\n ),\n };\n }\n}\n\nfunction createHeaders(\n config: NormalizedConfig,\n existingHeaders: HeadersInit | undefined,\n): Headers {\n const headers = new Headers(existingHeaders);\n\n headers.set('apikey', config.anonKey);\n headers.set('Authorization', `Bearer ${config.anonKey}`);\n headers.set('Accept', 'application/json');\n headers.set('Content-Type', 'application/json');\n headers.set('Prefer', 'return=representation');\n\n if (config.schema !== 'public') {\n headers.set('Accept-Profile', config.schema);\n headers.set('Content-Profile', config.schema);\n }\n\n return headers;\n}\n\nasync function readJsonBody(response: Response): Promise<unknown> {\n const text = await response.text();\n\n if (text.trim().length === 0) {\n return [];\n }\n\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\nfunction normalizeRecords<TRecord extends object>(value: unknown): TRecord[] | null {\n if (Array.isArray(value)) {\n return value.filter(isRecord<TRecord>);\n }\n\n if (isRecord<TRecord>(value)) {\n return [value];\n }\n\n return null;\n}\n\nfunction isRecord<TRecord extends object>(value: unknown): value is TRecord {\n return typeof value === 'object' && value !== null;\n}\n"]}
|
package/dist/admin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,sBAAsB,EACtB,6BAA6B,EAE9B,MAAM,YAAY,CAAC;AAWpB,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,6BAA6B,GACrC,sBAAsB,CAqCxB"}
|
package/dist/admin.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { quoteIdentifier, validateKey, validateUrl } from './validation.js';
|
|
2
|
+
export function createSupabaseDbAdminAdapter(options) {
|
|
3
|
+
const config = normalizeAdminConfig(options);
|
|
4
|
+
return {
|
|
5
|
+
capabilities: {
|
|
6
|
+
supportsSchemaGeneration: true,
|
|
7
|
+
supportsDirectExecution: config.execute && config.executeSql !== undefined,
|
|
8
|
+
},
|
|
9
|
+
async createCollection(input) {
|
|
10
|
+
const generated = generateCreateCollectionSql(input, config.schema);
|
|
11
|
+
if (!generated.ok || !config.execute) {
|
|
12
|
+
return generated;
|
|
13
|
+
}
|
|
14
|
+
return executeSql(config, generated.sql);
|
|
15
|
+
},
|
|
16
|
+
async deleteCollection(input) {
|
|
17
|
+
const generated = generateDeleteCollectionSql(input, config.schema);
|
|
18
|
+
if (!generated.ok || !config.execute) {
|
|
19
|
+
return generated;
|
|
20
|
+
}
|
|
21
|
+
return executeSql(config, generated.sql);
|
|
22
|
+
},
|
|
23
|
+
generateCreateCollectionSql(input) {
|
|
24
|
+
return generateCreateCollectionSql(input, config.schema);
|
|
25
|
+
},
|
|
26
|
+
generateDeleteCollectionSql(input) {
|
|
27
|
+
return generateDeleteCollectionSql(input, config.schema);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function normalizeAdminConfig(options) {
|
|
32
|
+
const normalized = {
|
|
33
|
+
url: validateUrl(options.url),
|
|
34
|
+
schema: options.schema ?? 'public',
|
|
35
|
+
execute: options.execute ?? false,
|
|
36
|
+
executeSql: options.executeSql,
|
|
37
|
+
};
|
|
38
|
+
if (options.serviceRoleKey === undefined) {
|
|
39
|
+
return normalized;
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
...normalized,
|
|
43
|
+
serviceRoleKey: validateKey(options.serviceRoleKey, 'Supabase service role key'),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function generateCreateCollectionSql(input, schemaValue) {
|
|
47
|
+
try {
|
|
48
|
+
const schema = quoteIdentifier(schemaValue);
|
|
49
|
+
const table = quoteIdentifier(input.name);
|
|
50
|
+
const primaryKey = input.primaryKey ?? 'id';
|
|
51
|
+
const fields = input.fields.map((field) => formatField(field));
|
|
52
|
+
const primaryKeyFieldExists = input.fields.some((field) => field.name === primaryKey);
|
|
53
|
+
const columns = primaryKeyFieldExists
|
|
54
|
+
? fields
|
|
55
|
+
: [`${quoteIdentifier(primaryKey)} uuid primary key default gen_random_uuid()`, ...fields];
|
|
56
|
+
if (columns.length === 0) {
|
|
57
|
+
return createAdminError('validation_error', 'A collection requires at least one field.');
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
ok: true,
|
|
61
|
+
sql: `create schema if not exists ${schema};\ncreate table if not exists ${schema}.${table} (\n ${columns.join(',\n ')}\n);`,
|
|
62
|
+
executed: false,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
return createAdminError('validation_error', 'Invalid collection definition.', error);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function generateDeleteCollectionSql(input, schemaValue) {
|
|
70
|
+
try {
|
|
71
|
+
const schema = quoteIdentifier(schemaValue);
|
|
72
|
+
const table = quoteIdentifier(input.name);
|
|
73
|
+
return {
|
|
74
|
+
ok: true,
|
|
75
|
+
sql: `drop table if exists ${schema}.${table};`,
|
|
76
|
+
executed: false,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
return createAdminError('validation_error', 'Invalid collection name.', error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function executeSql(config, sql) {
|
|
84
|
+
if (config.executeSql === undefined) {
|
|
85
|
+
return createAdminError('execution_not_configured', 'Schema execution requires an executeSql callback.');
|
|
86
|
+
}
|
|
87
|
+
if (config.serviceRoleKey === undefined) {
|
|
88
|
+
return createAdminError('missing_service_role_key', 'Schema execution requires a service role key in privileged environments only.');
|
|
89
|
+
}
|
|
90
|
+
const result = await config.executeSql(sql);
|
|
91
|
+
if (!result.ok) {
|
|
92
|
+
return {
|
|
93
|
+
ok: false,
|
|
94
|
+
error: result.error ?? {
|
|
95
|
+
code: 'provider_error',
|
|
96
|
+
message: 'Supabase schema execution failed.',
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
ok: true,
|
|
102
|
+
sql,
|
|
103
|
+
executed: true,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function formatField(field) {
|
|
107
|
+
const name = quoteIdentifier(field.name);
|
|
108
|
+
const type = mapFieldType(field.type);
|
|
109
|
+
const required = field.required === true ? ' not null' : '';
|
|
110
|
+
const unique = field.unique === true ? ' unique' : '';
|
|
111
|
+
const defaultValue = formatDefaultValue(field.defaultValue);
|
|
112
|
+
return `${name} ${type}${required}${unique}${defaultValue}`;
|
|
113
|
+
}
|
|
114
|
+
function mapFieldType(type) {
|
|
115
|
+
switch (type) {
|
|
116
|
+
case 'text':
|
|
117
|
+
return 'text';
|
|
118
|
+
case 'number':
|
|
119
|
+
return 'double precision';
|
|
120
|
+
case 'boolean':
|
|
121
|
+
return 'boolean';
|
|
122
|
+
case 'datetime':
|
|
123
|
+
return 'timestamptz';
|
|
124
|
+
case 'json':
|
|
125
|
+
return 'jsonb';
|
|
126
|
+
case 'uuid':
|
|
127
|
+
return 'uuid';
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function formatDefaultValue(value) {
|
|
131
|
+
if (value === undefined) {
|
|
132
|
+
return '';
|
|
133
|
+
}
|
|
134
|
+
if (value === null) {
|
|
135
|
+
return ' default null';
|
|
136
|
+
}
|
|
137
|
+
if (typeof value === 'string') {
|
|
138
|
+
return ` default '${value.replaceAll("'", "''")}'`;
|
|
139
|
+
}
|
|
140
|
+
return ` default ${String(value)}`;
|
|
141
|
+
}
|
|
142
|
+
function createAdminError(code, message, cause) {
|
|
143
|
+
return {
|
|
144
|
+
ok: false,
|
|
145
|
+
error: cause === undefined ? { code, message } : { code, message, cause },
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=admin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin.js","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n DbAdminResult,\n SupabaseCollectionDefinition,\n SupabaseDbAdminAdapter,\n SupabaseDbAdminAdapterOptions,\n SupabaseFieldDefinition,\n} from './types.js';\nimport { quoteIdentifier, validateKey, validateUrl } from './validation.js';\n\ninterface NormalizedAdminConfig {\n readonly url: string;\n readonly schema: string;\n readonly serviceRoleKey?: string;\n readonly execute: boolean;\n readonly executeSql?: SupabaseDbAdminAdapterOptions['executeSql'];\n}\n\nexport function createSupabaseDbAdminAdapter(\n options: SupabaseDbAdminAdapterOptions,\n): SupabaseDbAdminAdapter {\n const config = normalizeAdminConfig(options);\n\n return {\n capabilities: {\n supportsSchemaGeneration: true,\n supportsDirectExecution: config.execute && config.executeSql !== undefined,\n },\n\n async createCollection(input: SupabaseCollectionDefinition): Promise<DbAdminResult> {\n const generated = generateCreateCollectionSql(input, config.schema);\n\n if (!generated.ok || !config.execute) {\n return generated;\n }\n\n return executeSql(config, generated.sql);\n },\n\n async deleteCollection(input: { readonly name: string }): Promise<DbAdminResult> {\n const generated = generateDeleteCollectionSql(input, config.schema);\n\n if (!generated.ok || !config.execute) {\n return generated;\n }\n\n return executeSql(config, generated.sql);\n },\n\n generateCreateCollectionSql(input: SupabaseCollectionDefinition): DbAdminResult {\n return generateCreateCollectionSql(input, config.schema);\n },\n\n generateDeleteCollectionSql(input: { readonly name: string }): DbAdminResult {\n return generateDeleteCollectionSql(input, config.schema);\n },\n };\n}\n\nfunction normalizeAdminConfig(options: SupabaseDbAdminAdapterOptions): NormalizedAdminConfig {\n const normalized: NormalizedAdminConfig = {\n url: validateUrl(options.url),\n schema: options.schema ?? 'public',\n execute: options.execute ?? false,\n executeSql: options.executeSql,\n };\n\n if (options.serviceRoleKey === undefined) {\n return normalized;\n }\n\n return {\n ...normalized,\n serviceRoleKey: validateKey(options.serviceRoleKey, 'Supabase service role key'),\n };\n}\n\nfunction generateCreateCollectionSql(\n input: SupabaseCollectionDefinition,\n schemaValue: string,\n): DbAdminResult {\n try {\n const schema = quoteIdentifier(schemaValue);\n const table = quoteIdentifier(input.name);\n const primaryKey = input.primaryKey ?? 'id';\n const fields = input.fields.map((field) => formatField(field));\n const primaryKeyFieldExists = input.fields.some((field) => field.name === primaryKey);\n const columns = primaryKeyFieldExists\n ? fields\n : [`${quoteIdentifier(primaryKey)} uuid primary key default gen_random_uuid()`, ...fields];\n\n if (columns.length === 0) {\n return createAdminError('validation_error', 'A collection requires at least one field.');\n }\n\n return {\n ok: true,\n sql: `create schema if not exists ${schema};\\ncreate table if not exists ${schema}.${table} (\\n ${columns.join(',\\n ')}\\n);`,\n executed: false,\n };\n } catch (error) {\n return createAdminError('validation_error', 'Invalid collection definition.', error);\n }\n}\n\nfunction generateDeleteCollectionSql(\n input: { readonly name: string },\n schemaValue: string,\n): DbAdminResult {\n try {\n const schema = quoteIdentifier(schemaValue);\n const table = quoteIdentifier(input.name);\n\n return {\n ok: true,\n sql: `drop table if exists ${schema}.${table};`,\n executed: false,\n };\n } catch (error) {\n return createAdminError('validation_error', 'Invalid collection name.', error);\n }\n}\n\nasync function executeSql(config: NormalizedAdminConfig, sql: string): Promise<DbAdminResult> {\n if (config.executeSql === undefined) {\n return createAdminError(\n 'execution_not_configured',\n 'Schema execution requires an executeSql callback.',\n );\n }\n\n if (config.serviceRoleKey === undefined) {\n return createAdminError(\n 'missing_service_role_key',\n 'Schema execution requires a service role key in privileged environments only.',\n );\n }\n\n const result = await config.executeSql(sql);\n\n if (!result.ok) {\n return {\n ok: false,\n error: result.error ?? {\n code: 'provider_error',\n message: 'Supabase schema execution failed.',\n },\n };\n }\n\n return {\n ok: true,\n sql,\n executed: true,\n };\n}\n\nfunction formatField(field: SupabaseFieldDefinition): string {\n const name = quoteIdentifier(field.name);\n const type = mapFieldType(field.type);\n const required = field.required === true ? ' not null' : '';\n const unique = field.unique === true ? ' unique' : '';\n const defaultValue = formatDefaultValue(field.defaultValue);\n\n return `${name} ${type}${required}${unique}${defaultValue}`;\n}\n\nfunction mapFieldType(type: SupabaseFieldDefinition['type']): string {\n switch (type) {\n case 'text':\n return 'text';\n case 'number':\n return 'double precision';\n case 'boolean':\n return 'boolean';\n case 'datetime':\n return 'timestamptz';\n case 'json':\n return 'jsonb';\n case 'uuid':\n return 'uuid';\n }\n}\n\nfunction formatDefaultValue(value: SupabaseFieldDefinition['defaultValue']): string {\n if (value === undefined) {\n return '';\n }\n\n if (value === null) {\n return ' default null';\n }\n\n if (typeof value === 'string') {\n return ` default '${value.replaceAll(\"'\", \"''\")}'`;\n }\n\n return ` default ${String(value)}`;\n}\n\nfunction createAdminError(code: string, message: string, cause?: unknown): DbAdminResult {\n return {\n ok: false,\n error: cause === undefined ? { code, message } : { code, message, cause },\n };\n}\n"]}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DbAdapterError } from '@ankhorage/contracts/db';
|
|
2
|
+
export declare function createDbError(code: string, message: string, cause?: unknown): DbAdapterError;
|
|
3
|
+
export declare function mapNetworkError(error: unknown): DbAdapterError;
|
|
4
|
+
export declare function mapHttpError(status: number, body: unknown): DbAdapterError;
|
|
5
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE9D,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,cAAc,CAE5F;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAE9D;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,cAAc,CAgC1E"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export function createDbError(code, message, cause) {
|
|
2
|
+
return cause === undefined ? { code, message } : { code, message, cause };
|
|
3
|
+
}
|
|
4
|
+
export function mapNetworkError(error) {
|
|
5
|
+
return createDbError('network_error', 'Supabase Database request failed.', error);
|
|
6
|
+
}
|
|
7
|
+
export function mapHttpError(status, body) {
|
|
8
|
+
const providerMessage = readProviderMessage(body);
|
|
9
|
+
if (status === 401 || status === 403) {
|
|
10
|
+
return createDbError('permission_denied', providerMessage ?? 'Supabase Database denied the request.', body);
|
|
11
|
+
}
|
|
12
|
+
if (status === 404) {
|
|
13
|
+
return createDbError('missing_table', providerMessage ?? 'Supabase Database resource was not found.', body);
|
|
14
|
+
}
|
|
15
|
+
if (status >= 400 && status < 500) {
|
|
16
|
+
return createDbError('invalid_query', providerMessage ?? 'Supabase Database rejected the query.', body);
|
|
17
|
+
}
|
|
18
|
+
return createDbError('provider_error', providerMessage ?? 'Supabase Database returned an unexpected error.', body);
|
|
19
|
+
}
|
|
20
|
+
function readProviderMessage(body) {
|
|
21
|
+
if (!isRecord(body)) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
const { message } = body;
|
|
25
|
+
const { error } = body;
|
|
26
|
+
if (typeof message === 'string' && message.trim().length > 0) {
|
|
27
|
+
return message;
|
|
28
|
+
}
|
|
29
|
+
if (typeof error === 'string' && error.trim().length > 0) {
|
|
30
|
+
return error;
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
function isRecord(value) {
|
|
35
|
+
return typeof value === 'object' && value !== null;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,OAAe,EAAE,KAAe;IAC1E,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,OAAO,aAAa,CAAC,eAAe,EAAE,mCAAmC,EAAE,KAAK,CAAC,CAAC;AACpF,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,IAAa;IACxD,MAAM,eAAe,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAElD,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACrC,OAAO,aAAa,CAClB,mBAAmB,EACnB,eAAe,IAAI,uCAAuC,EAC1D,IAAI,CACL,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,OAAO,aAAa,CAClB,eAAe,EACf,eAAe,IAAI,2CAA2C,EAC9D,IAAI,CACL,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,IAAI,GAAG,IAAI,MAAM,GAAG,GAAG,EAAE,CAAC;QAClC,OAAO,aAAa,CAClB,eAAe,EACf,eAAe,IAAI,uCAAuC,EAC1D,IAAI,CACL,CAAC;IACJ,CAAC;IAED,OAAO,aAAa,CAClB,gBAAgB,EAChB,eAAe,IAAI,iDAAiD,EACpE,IAAI,CACL,CAAC;AACJ,CAAC;AAED,SAAS,mBAAmB,CAAC,IAAa;IACxC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACpB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;IAEvB,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7D,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC;AACrD,CAAC","sourcesContent":["import type { DbAdapterError } from '@ankhorage/contracts/db';\n\nexport function createDbError(code: string, message: string, cause?: unknown): DbAdapterError {\n return cause === undefined ? { code, message } : { code, message, cause };\n}\n\nexport function mapNetworkError(error: unknown): DbAdapterError {\n return createDbError('network_error', 'Supabase Database request failed.', error);\n}\n\nexport function mapHttpError(status: number, body: unknown): DbAdapterError {\n const providerMessage = readProviderMessage(body);\n\n if (status === 401 || status === 403) {\n return createDbError(\n 'permission_denied',\n providerMessage ?? 'Supabase Database denied the request.',\n body,\n );\n }\n\n if (status === 404) {\n return createDbError(\n 'missing_table',\n providerMessage ?? 'Supabase Database resource was not found.',\n body,\n );\n }\n\n if (status >= 400 && status < 500) {\n return createDbError(\n 'invalid_query',\n providerMessage ?? 'Supabase Database rejected the query.',\n body,\n );\n }\n\n return createDbError(\n 'provider_error',\n providerMessage ?? 'Supabase Database returned an unexpected error.',\n body,\n );\n}\n\nfunction readProviderMessage(body: unknown): string | undefined {\n if (!isRecord(body)) {\n return undefined;\n }\n\n const { message } = body;\n const { error } = body;\n\n if (typeof message === 'string' && message.trim().length > 0) {\n return message;\n }\n\n if (typeof error === 'string' && error.trim().length > 0) {\n return error;\n }\n\n return undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createSupabaseDbAdapter } from './adapter.js';
|
|
2
|
+
export { createSupabaseDbAdminAdapter } from './admin.js';
|
|
3
|
+
export { normalizeRealtimeEvent } from './realtime.js';
|
|
4
|
+
export type { DbAdminResult, DbAdminSqlExecutionResult, DbChangeEvent, DbChangeKind, DbChangeListener, DbCollectionSubscriptionInput, DbRecordSubscriptionInput, DbSubscription, RealtimeDbAdapter, SupabaseCollectionDefinition, SupabaseDbAdapter, SupabaseDbAdapterOptions, SupabaseDbAdminAdapter, SupabaseDbAdminAdapterOptions, SupabaseFieldDefinition, SupabaseFieldType, SupabaseRealtimeClient, SupabaseRealtimePayload, } from './types.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AACvD,YAAY,EACV,aAAa,EACb,yBAAyB,EACzB,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,6BAA6B,EAC7B,yBAAyB,EACzB,cAAc,EACd,iBAAiB,EACjB,4BAA4B,EAC5B,iBAAiB,EACjB,wBAAwB,EACxB,sBAAsB,EACtB,6BAA6B,EAC7B,uBAAuB,EACvB,iBAAiB,EACjB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,YAAY,CAAC"}
|