@ankhorage/supabase-db 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +23 -5
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +10 -6
- package/dist/adapter.js.map +1 -1
- package/dist/admin.d.ts +3 -2
- package/dist/admin.d.ts.map +1 -1
- package/dist/admin.js +9 -12
- package/dist/admin.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/realtime.d.ts +3 -3
- package/dist/realtime.d.ts.map +1 -1
- package/dist/realtime.js.map +1 -1
- package/dist/types.d.ts +8 -72
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/src/adapter.test.ts +1 -1
- package/src/adapter.ts +17 -7
- package/src/admin.test.ts +8 -0
- package/src/admin.ts +26 -27
- package/src/index.ts +15 -11
- package/src/realtime.ts +6 -7
- package/src/types.ts +13 -89
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @ankhorage/supabase-db
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8eff108: Align the Supabase database adapter with the canonical database realtime and admin/schema contracts from `@ankhorage/contracts/db`.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 3897e95: Fix CI pipeline
|
|
12
|
+
|
|
3
13
|
## 0.2.1
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -9,7 +9,13 @@ Supabase database adapter for Ankhorage data contracts.
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
bun add @ankhorage/supabase-db @ankhorage/contracts
|
|
12
|
+
bun add @ankhorage/supabase-db @ankhorage/contracts
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Install `@supabase/supabase-js` as well when you want to pass a Supabase realtime client:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bun add @supabase/supabase-js
|
|
13
19
|
```
|
|
14
20
|
|
|
15
21
|
## Boundaries
|
|
@@ -36,7 +42,7 @@ This package does not own:
|
|
|
36
42
|
|
|
37
43
|
## Runtime CRUD adapter
|
|
38
44
|
|
|
39
|
-
Use the runtime adapter with client-safe Supabase credentials. The adapter speaks the `DbAdapter` shape from `@ankhorage/contracts/db`.
|
|
45
|
+
Use the runtime adapter with client-safe Supabase credentials. The adapter speaks the canonical `DbAdapter` shape from `@ankhorage/contracts/db`.
|
|
40
46
|
|
|
41
47
|
```ts
|
|
42
48
|
import { createSupabaseDbAdapter } from '@ankhorage/supabase-db';
|
|
@@ -73,9 +79,21 @@ The adapter implements:
|
|
|
73
79
|
|
|
74
80
|
`update` and `delete` require at least one filter to avoid accidental whole-table mutations.
|
|
75
81
|
|
|
82
|
+
## Capabilities
|
|
83
|
+
|
|
84
|
+
The adapter exposes the canonical `DbAdapterCapabilities` contract:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const capabilities = db.capabilities;
|
|
88
|
+
|
|
89
|
+
console.log(capabilities.transactions); // false
|
|
90
|
+
console.log(capabilities.returning); // true
|
|
91
|
+
console.log(capabilities.realtime); // true only when realtime is enabled and configured
|
|
92
|
+
```
|
|
93
|
+
|
|
76
94
|
## Realtime
|
|
77
95
|
|
|
78
|
-
Realtime is optional. It is
|
|
96
|
+
Realtime is optional. It is exposed through the canonical `DbRealtimeAdapter` contract only when enabled and configured.
|
|
79
97
|
|
|
80
98
|
```ts
|
|
81
99
|
import { createClient } from '@supabase/supabase-js';
|
|
@@ -103,11 +121,11 @@ Realtime events are normalized to provider-neutral kinds:
|
|
|
103
121
|
- `update`
|
|
104
122
|
- `delete`
|
|
105
123
|
|
|
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.
|
|
124
|
+
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.realtime` is `false`.
|
|
107
125
|
|
|
108
126
|
## Admin/schema adapter
|
|
109
127
|
|
|
110
|
-
Schema operations are privileged and separate from runtime CRUD.
|
|
128
|
+
Schema operations are privileged and separate from runtime CRUD. The admin adapter implements the canonical `DbAdminAdapter` contract from `@ankhorage/contracts/db`.
|
|
111
129
|
|
|
112
130
|
By default, the admin adapter generates SQL only:
|
|
113
131
|
|
package/dist/adapter.d.ts.map
CHANGED
|
@@ -1 +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;
|
|
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;AAkB9E,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,GAAG,iBAAiB,CAoG5F"}
|
package/dist/adapter.js
CHANGED
|
@@ -6,9 +6,9 @@ export function createSupabaseDbAdapter(options) {
|
|
|
6
6
|
const config = normalizeConfig(options);
|
|
7
7
|
const baseAdapter = {
|
|
8
8
|
capabilities: {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
transactions: false,
|
|
10
|
+
returning: true,
|
|
11
|
+
realtime: config.realtime && config.realtimeClient !== undefined,
|
|
12
12
|
},
|
|
13
13
|
async select(input) {
|
|
14
14
|
return requestRows(config, buildSelectUrl(config.url, input), {
|
|
@@ -19,14 +19,18 @@ export function createSupabaseDbAdapter(options) {
|
|
|
19
19
|
const result = await requestRows(config, buildSelectUrl(config.url, {
|
|
20
20
|
table: input.table,
|
|
21
21
|
columns: input.columns,
|
|
22
|
-
filters: [{ field: 'id', operator: 'eq', value: input.id }],
|
|
22
|
+
filters: [{ field: input.idField ?? 'id', operator: 'eq', value: input.id }],
|
|
23
23
|
page: { limit: 1 },
|
|
24
|
+
schema: input.schema,
|
|
24
25
|
}), { method: 'GET' });
|
|
25
26
|
if (!result.ok) {
|
|
26
27
|
return result;
|
|
27
28
|
}
|
|
28
|
-
const
|
|
29
|
-
|
|
29
|
+
const success = {
|
|
30
|
+
ok: true,
|
|
31
|
+
data: result.data[0] ?? null,
|
|
32
|
+
};
|
|
33
|
+
return success;
|
|
30
34
|
},
|
|
31
35
|
async insert(input) {
|
|
32
36
|
const url = buildMutationUrl(config.url, input.table, []);
|
package/dist/adapter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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","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"]}
|
|
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;AAiB5E,MAAM,UAAU,uBAAuB,CAAC,OAAiC;IACvE,MAAM,MAAM,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IACxC,MAAM,WAAW,GAAc;QAC7B,YAAY,EAAE;YACZ,YAAY,EAAE,KAAK;YACnB,SAAS,EAAE,IAAI;YACf,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,cAAc,KAAK,SAAS;SACjE;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,KAAK,CAAC,OAAO,IAAI,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;gBAC5E,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE;gBAClB,MAAM,EAAE,KAAK,CAAC,MAAM;aACrB,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,GAAqC;gBAChD,EAAE,EAAE,IAAI;gBACR,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI;aAC7B,CAAC;YAEF,OAAO,OAAO,CAAC;QACjB,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\ntype DbFailureResult = Extract<DbResult<unknown>, { readonly ok: false }>;\ninterface DbFindByIdSuccessResult<TRecord extends object> {\n readonly ok: true;\n readonly data: TRecord | null;\n}\n\nexport function createSupabaseDbAdapter(options: SupabaseDbAdapterOptions): SupabaseDbAdapter {\n const config = normalizeConfig(options);\n const baseAdapter: DbAdapter = {\n capabilities: {\n transactions: false,\n returning: true,\n realtime: 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: input.idField ?? 'id', operator: 'eq', value: input.id }],\n page: { limit: 1 },\n schema: input.schema,\n }),\n { method: 'GET' },\n );\n\n if (!result.ok) {\n return result;\n }\n\n const success: DbFindByIdSuccessResult<TRecord> = {\n ok: true,\n data: result.data[0] ?? null,\n };\n\n return success;\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): DbFailureResult | 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
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type { DbAdminAdapter } from '@ankhorage/contracts/db';
|
|
2
|
+
import type { SupabaseDbAdminAdapterOptions } from './types.js';
|
|
3
|
+
export declare function createSupabaseDbAdminAdapter(options: SupabaseDbAdminAdapterOptions): DbAdminAdapter;
|
|
3
4
|
//# sourceMappingURL=admin.d.ts.map
|
package/dist/admin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EAKf,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,YAAY,CAAC;AAWhE,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,6BAA6B,GACrC,cAAc,CAqChB"}
|
package/dist/admin.js
CHANGED
|
@@ -3,22 +3,22 @@ export function createSupabaseDbAdminAdapter(options) {
|
|
|
3
3
|
const config = normalizeAdminConfig(options);
|
|
4
4
|
return {
|
|
5
5
|
capabilities: {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
schemaGeneration: true,
|
|
7
|
+
directExecution: config.execute && config.executeSql !== undefined,
|
|
8
8
|
},
|
|
9
9
|
async createCollection(input) {
|
|
10
10
|
const generated = generateCreateCollectionSql(input, config.schema);
|
|
11
11
|
if (!generated.ok || !config.execute) {
|
|
12
12
|
return generated;
|
|
13
13
|
}
|
|
14
|
-
return executeSql(config, generated.sql);
|
|
14
|
+
return executeSql(config, generated.sql ?? '');
|
|
15
15
|
},
|
|
16
16
|
async deleteCollection(input) {
|
|
17
17
|
const generated = generateDeleteCollectionSql(input, config.schema);
|
|
18
18
|
if (!generated.ok || !config.execute) {
|
|
19
19
|
return generated;
|
|
20
20
|
}
|
|
21
|
-
return executeSql(config, generated.sql);
|
|
21
|
+
return executeSql(config, generated.sql ?? '');
|
|
22
22
|
},
|
|
23
23
|
generateCreateCollectionSql(input) {
|
|
24
24
|
return generateCreateCollectionSql(input, config.schema);
|
|
@@ -43,9 +43,9 @@ function normalizeAdminConfig(options) {
|
|
|
43
43
|
serviceRoleKey: validateKey(options.serviceRoleKey, 'Supabase service role key'),
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
-
function generateCreateCollectionSql(input,
|
|
46
|
+
function generateCreateCollectionSql(input, defaultSchema) {
|
|
47
47
|
try {
|
|
48
|
-
const schema = quoteIdentifier(
|
|
48
|
+
const schema = quoteIdentifier(input.schema ?? defaultSchema);
|
|
49
49
|
const table = quoteIdentifier(input.name);
|
|
50
50
|
const primaryKey = input.primaryKey ?? 'id';
|
|
51
51
|
const fields = input.fields.map((field) => formatField(field));
|
|
@@ -66,9 +66,9 @@ function generateCreateCollectionSql(input, schemaValue) {
|
|
|
66
66
|
return createAdminError('validation_error', 'Invalid collection definition.', error);
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
-
function generateDeleteCollectionSql(input,
|
|
69
|
+
function generateDeleteCollectionSql(input, defaultSchema) {
|
|
70
70
|
try {
|
|
71
|
-
const schema = quoteIdentifier(
|
|
71
|
+
const schema = quoteIdentifier(input.schema ?? defaultSchema);
|
|
72
72
|
const table = quoteIdentifier(input.name);
|
|
73
73
|
return {
|
|
74
74
|
ok: true,
|
|
@@ -91,10 +91,7 @@ async function executeSql(config, sql) {
|
|
|
91
91
|
if (!result.ok) {
|
|
92
92
|
return {
|
|
93
93
|
ok: false,
|
|
94
|
-
error: result.error
|
|
95
|
-
code: 'provider_error',
|
|
96
|
-
message: 'Supabase schema execution failed.',
|
|
97
|
-
},
|
|
94
|
+
error: result.error,
|
|
98
95
|
};
|
|
99
96
|
}
|
|
100
97
|
return {
|
package/dist/admin.js.map
CHANGED
|
@@ -1 +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"]}
|
|
1
|
+
{"version":3,"file":"admin.js","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAU5E,MAAM,UAAU,4BAA4B,CAC1C,OAAsC;IAEtC,MAAM,MAAM,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAE7C,OAAO;QACL,YAAY,EAAE;YACZ,gBAAgB,EAAE,IAAI;YACtB,eAAe,EAAE,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS;SACnE;QAED,KAAK,CAAC,gBAAgB,CAAC,KAA6B;YAClD,MAAM,SAAS,GAAG,2BAA2B,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;YAEpE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACrC,OAAO,SAAS,CAAC;YACnB,CAAC;YAED,OAAO,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;QACjD,CAAC;QAED,KAAK,CAAC,gBAAgB,CAAC,KAA4B;YACjD,MAAM,SAAS,GAAG,2BAA2B,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;YAEpE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACrC,OAAO,SAAS,CAAC;YACnB,CAAC;YAED,OAAO,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;QACjD,CAAC;QAED,2BAA2B,CAAC,KAA6B;YACvD,OAAO,2BAA2B,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QAC3D,CAAC;QAED,2BAA2B,CAAC,KAA4B;YACtD,OAAO,2BAA2B,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QAC3D,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,oBAAoB,CAAC,OAAsC;IAClE,MAAM,UAAU,GAA0B;QACxC,GAAG,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC;QAC7B,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,QAAQ;QAClC,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,KAAK;QACjC,UAAU,EAAE,OAAO,CAAC,UAAU;KAC/B,CAAC;IAEF,IAAI,OAAO,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;QACzC,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,OAAO;QACL,GAAG,UAAU;QACb,cAAc,EAAE,WAAW,CAAC,OAAO,CAAC,cAAc,EAAE,2BAA2B,CAAC;KACjF,CAAC;AACJ,CAAC;AAED,SAAS,2BAA2B,CAClC,KAA6B,EAC7B,aAAqB;IAErB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,MAAM,IAAI,aAAa,CAAC,CAAC;QAC9D,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,IAAI,IAAI,CAAC;QAC5C,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;QAC/D,MAAM,qBAAqB,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;QACtF,MAAM,OAAO,GAAG,qBAAqB;YACnC,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,UAAU,CAAC,6CAA6C,EAAE,GAAG,MAAM,CAAC,CAAC;QAE7F,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,gBAAgB,CAAC,kBAAkB,EAAE,2CAA2C,CAAC,CAAC;QAC3F,CAAC;QAED,OAAO;YACL,EAAE,EAAE,IAAI;YACR,GAAG,EAAE,+BAA+B,MAAM,iCAAiC,MAAM,IAAI,KAAK,SAAS,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM;YAC9H,QAAQ,EAAE,KAAK;SAChB,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,gBAAgB,CAAC,kBAAkB,EAAE,gCAAgC,EAAE,KAAK,CAAC,CAAC;IACvF,CAAC;AACH,CAAC;AAED,SAAS,2BAA2B,CAClC,KAA4B,EAC5B,aAAqB;IAErB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,MAAM,IAAI,aAAa,CAAC,CAAC;QAC9D,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAE1C,OAAO;YACL,EAAE,EAAE,IAAI;YACR,GAAG,EAAE,wBAAwB,MAAM,IAAI,KAAK,GAAG;YAC/C,QAAQ,EAAE,KAAK;SAChB,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,gBAAgB,CAAC,kBAAkB,EAAE,0BAA0B,EAAE,KAAK,CAAC,CAAC;IACjF,CAAC;AACH,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,MAA6B,EAAE,GAAW;IAClE,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACpC,OAAO,gBAAgB,CACrB,0BAA0B,EAC1B,mDAAmD,CACpD,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;QACxC,OAAO,gBAAgB,CACrB,0BAA0B,EAC1B,+EAA+E,CAChF,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAE5C,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,MAAM,CAAC,KAAK;SACpB,CAAC;IACJ,CAAC;IAED,OAAO;QACL,EAAE,EAAE,IAAI;QACR,GAAG;QACH,QAAQ,EAAE,IAAI;KACf,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,KAAwB;IAC3C,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5D,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;IACtD,MAAM,YAAY,GAAG,kBAAkB,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAE5D,OAAO,GAAG,IAAI,IAAI,IAAI,GAAG,QAAQ,GAAG,MAAM,GAAG,YAAY,EAAE,CAAC;AAC9D,CAAC;AAED,SAAS,YAAY,CAAC,IAA+B;IACnD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,MAAM;YACT,OAAO,MAAM,CAAC;QAChB,KAAK,QAAQ;YACX,OAAO,kBAAkB,CAAC;QAC5B,KAAK,SAAS;YACZ,OAAO,SAAS,CAAC;QACnB,KAAK,UAAU;YACb,OAAO,aAAa,CAAC;QACvB,KAAK,MAAM;YACT,OAAO,OAAO,CAAC;QACjB,KAAK,MAAM;YACT,OAAO,MAAM,CAAC;IAClB,CAAC;AACH,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAwC;IAClE,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,OAAO,eAAe,CAAC;IACzB,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,aAAa,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC;IACrD,CAAC;IAED,OAAO,YAAY,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AACrC,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAY,EAAE,OAAe,EAAE,KAAe;IACtE,OAAO;QACL,EAAE,EAAE,KAAK;QACT,KAAK,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE;KAC1E,CAAC;AACJ,CAAC","sourcesContent":["import type {\n DbAdminAdapter,\n DbAdminResult,\n DbCollectionDefinition,\n DbCollectionReference,\n DbFieldDefinition,\n} from '@ankhorage/contracts/db';\n\nimport type { SupabaseDbAdminAdapterOptions } 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): DbAdminAdapter {\n const config = normalizeAdminConfig(options);\n\n return {\n capabilities: {\n schemaGeneration: true,\n directExecution: config.execute && config.executeSql !== undefined,\n },\n\n async createCollection(input: DbCollectionDefinition): 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: DbCollectionReference): 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: DbCollectionDefinition): DbAdminResult {\n return generateCreateCollectionSql(input, config.schema);\n },\n\n generateDeleteCollectionSql(input: DbCollectionReference): 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: DbCollectionDefinition,\n defaultSchema: string,\n): DbAdminResult {\n try {\n const schema = quoteIdentifier(input.schema ?? defaultSchema);\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: DbCollectionReference,\n defaultSchema: string,\n): DbAdminResult {\n try {\n const schema = quoteIdentifier(input.schema ?? defaultSchema);\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 };\n }\n\n return {\n ok: true,\n sql,\n executed: true,\n };\n}\n\nfunction formatField(field: DbFieldDefinition): 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: DbFieldDefinition['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: DbFieldDefinition['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/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { createSupabaseDbAdapter } from './adapter.js';
|
|
2
2
|
export { createSupabaseDbAdminAdapter } from './admin.js';
|
|
3
3
|
export { normalizeRealtimeEvent } from './realtime.js';
|
|
4
|
-
export type {
|
|
4
|
+
export type { DbAdminSqlExecutionResult, SupabaseDbAdapter, SupabaseDbAdapterOptions, SupabaseDbAdminAdapter, SupabaseDbAdminAdapterOptions, SupabaseRealtimeClient, SupabaseRealtimePayload, } from './types.js';
|
|
5
|
+
export type { DbAdminAdapter, DbAdminResult, DbChangeEvent, DbChangeKind, DbChangeListener, DbCollectionDefinition, DbCollectionReference, DbCollectionSubscriptionInput, DbFieldDefinition, DbFieldType, DbRealtimeAdapter, DbRecordSubscriptionInput, DbSubscription, } from '@ankhorage/contracts/db';
|
|
5
6
|
//# 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":"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,
|
|
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,yBAAyB,EACzB,iBAAiB,EACjB,wBAAwB,EACxB,sBAAsB,EACtB,6BAA6B,EAC7B,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,cAAc,EACd,aAAa,EACb,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,EACrB,6BAA6B,EAC7B,iBAAiB,EACjB,WAAW,EACX,iBAAiB,EACjB,yBAAyB,EACzB,cAAc,GACf,MAAM,yBAAyB,CAAC"}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","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","sourcesContent":["export { createSupabaseDbAdapter } from './adapter.js';\nexport { createSupabaseDbAdminAdapter } from './admin.js';\nexport { normalizeRealtimeEvent } from './realtime.js';\nexport type {\n
|
|
1
|
+
{"version":3,"file":"index.js","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","sourcesContent":["export { createSupabaseDbAdapter } from './adapter.js';\nexport { createSupabaseDbAdminAdapter } from './admin.js';\nexport { normalizeRealtimeEvent } from './realtime.js';\nexport type {\n DbAdminSqlExecutionResult,\n SupabaseDbAdapter,\n SupabaseDbAdapterOptions,\n SupabaseDbAdminAdapter,\n SupabaseDbAdminAdapterOptions,\n SupabaseRealtimeClient,\n SupabaseRealtimePayload,\n} from './types.js';\nexport type {\n DbAdminAdapter,\n DbAdminResult,\n DbChangeEvent,\n DbChangeKind,\n DbChangeListener,\n DbCollectionDefinition,\n DbCollectionReference,\n DbCollectionSubscriptionInput,\n DbFieldDefinition,\n DbFieldType,\n DbRealtimeAdapter,\n DbRecordSubscriptionInput,\n DbSubscription,\n} from '@ankhorage/contracts/db';\n"]}
|
package/dist/realtime.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { DbRecord } from '@ankhorage/contracts/db';
|
|
2
|
-
import type {
|
|
1
|
+
import type { DbChangeEvent, DbRealtimeAdapter, DbRecord } from '@ankhorage/contracts/db';
|
|
2
|
+
import type { SupabaseRealtimeClient, SupabaseRealtimePayload } from './types.js';
|
|
3
3
|
interface RealtimeApiConfig {
|
|
4
4
|
readonly client: SupabaseRealtimeClient;
|
|
5
5
|
readonly defaultSchema: string;
|
|
6
6
|
}
|
|
7
|
-
export declare function createRealtimeApi(config: RealtimeApiConfig):
|
|
7
|
+
export declare function createRealtimeApi(config: RealtimeApiConfig): DbRealtimeAdapter['realtime'];
|
|
8
8
|
export declare function normalizeRealtimeEvent<TRecord extends object = DbRecord>(payload: SupabaseRealtimePayload, fallbackTable: string, fallbackSchema: string): DbChangeEvent<TRecord> | null;
|
|
9
9
|
export {};
|
|
10
10
|
//# sourceMappingURL=realtime.d.ts.map
|
package/dist/realtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EAIb,iBAAiB,EACjB,QAAQ,EAGT,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAGlF,UAAU,iBAAiB;IACzB,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC;IACxC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAoE1F;AAED,wBAAgB,sBAAsB,CAAC,OAAO,SAAS,MAAM,GAAG,QAAQ,EACtE,OAAO,EAAE,uBAAuB,EAChC,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,GACrB,aAAa,CAAC,OAAO,CAAC,GAAG,IAAI,CAyB/B"}
|
package/dist/realtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime.js","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"realtime.js","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n DbChangeEvent,\n DbChangeKind,\n DbChangeListener,\n DbCollectionSubscriptionInput,\n DbRealtimeAdapter,\n DbRecord,\n DbRecordSubscriptionInput,\n DbSubscription,\n} from '@ankhorage/contracts/db';\n\nimport type { SupabaseRealtimeClient, SupabaseRealtimePayload } from './types.js';\nimport { validateIdentifier } from './validation.js';\n\ninterface RealtimeApiConfig {\n readonly client: SupabaseRealtimeClient;\n readonly defaultSchema: string;\n}\n\nexport function createRealtimeApi(config: RealtimeApiConfig): DbRealtimeAdapter['realtime'] {\n return {\n subscribeToCollection<TRecord extends object = DbRecord>(\n input: DbCollectionSubscriptionInput,\n listener: DbChangeListener<TRecord>,\n ): DbSubscription {\n const schema = input.schema ?? config.defaultSchema;\n const table = validateIdentifier(input.table, 'Realtime table');\n const channel = config.client\n .channel(`ankhorage-db:${schema}:${table}`)\n .on(\n 'postgres_changes',\n {\n event: '*',\n schema,\n table,\n },\n (payload) => {\n const event = normalizeRealtimeEvent<TRecord>(payload, table, schema);\n\n if (event !== null) {\n listener(event);\n }\n },\n )\n .subscribe();\n\n return {\n async unsubscribe(): Promise<void> {\n await config.client.removeChannel(channel);\n },\n };\n },\n\n subscribeToRecord<TRecord extends object = DbRecord>(\n input: DbRecordSubscriptionInput,\n listener: DbChangeListener<TRecord>,\n ): DbSubscription {\n const schema = input.schema ?? config.defaultSchema;\n const table = validateIdentifier(input.table, 'Realtime table');\n const idField = validateIdentifier(input.idField ?? 'id', 'Realtime record id field');\n const channel = config.client\n .channel(`ankhorage-db:${schema}:${table}:${idField}:${String(input.id)}`)\n .on(\n 'postgres_changes',\n {\n event: '*',\n schema,\n table,\n filter: `${idField}=eq.${String(input.id)}`,\n },\n (payload) => {\n const event = normalizeRealtimeEvent<TRecord>(payload, table, schema);\n\n if (event !== null) {\n listener(event);\n }\n },\n )\n .subscribe();\n\n return {\n async unsubscribe(): Promise<void> {\n await config.client.removeChannel(channel);\n },\n };\n },\n };\n}\n\nexport function normalizeRealtimeEvent<TRecord extends object = DbRecord>(\n payload: SupabaseRealtimePayload,\n fallbackTable: string,\n fallbackSchema: string,\n): DbChangeEvent<TRecord> | null {\n const kind = normalizeKind(payload.eventType);\n\n if (kind === null) {\n return null;\n }\n\n const record = normalizeRecord<TRecord>(payload.new);\n const previousRecord = normalizeRecord<TRecord>(payload.old);\n const base = {\n table: payload.table ?? fallbackTable,\n schema: payload.schema ?? fallbackSchema,\n kind,\n record: kind === 'delete' ? null : record,\n committedAt: payload.commit_timestamp,\n };\n\n if (previousRecord === null) {\n return base;\n }\n\n return {\n ...base,\n previousRecord,\n };\n}\n\nfunction normalizeKind(value: string | undefined): DbChangeKind | null {\n switch (value) {\n case 'INSERT':\n return 'insert';\n case 'UPDATE':\n return 'update';\n case 'DELETE':\n return 'delete';\n default:\n return null;\n }\n}\n\nfunction normalizeRecord<TRecord extends object>(value: unknown): TRecord | null {\n if (!isRecord<TRecord>(value)) {\n return null;\n }\n\n return value;\n}\n\nfunction isRecord<TRecord extends object>(value: unknown): value is TRecord {\n return typeof value === 'object' && value !== null;\n}\n"]}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DbAdapter,
|
|
1
|
+
import type { DbAdapter, DbAdminAdapter, DbAdminResult, DbRealtimeAdapter } from '@ankhorage/contracts/db';
|
|
2
2
|
interface SupabaseRealtimePostgresChangesFilter {
|
|
3
3
|
readonly event: 'INSERT' | 'UPDATE' | 'DELETE' | '*';
|
|
4
4
|
readonly schema: string;
|
|
@@ -38,80 +38,16 @@ export interface SupabaseDbAdminAdapterOptions {
|
|
|
38
38
|
readonly execute?: boolean;
|
|
39
39
|
readonly executeSql?: (sql: string) => Promise<DbAdminSqlExecutionResult>;
|
|
40
40
|
}
|
|
41
|
-
export
|
|
42
|
-
readonly ok: boolean;
|
|
43
|
-
readonly error?: {
|
|
44
|
-
readonly code: string;
|
|
45
|
-
readonly message: string;
|
|
46
|
-
readonly cause?: unknown;
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
export type SupabaseDbAdapter = DbAdapter & Partial<RealtimeDbAdapter>;
|
|
50
|
-
export interface RealtimeDbAdapter {
|
|
51
|
-
readonly realtime: {
|
|
52
|
-
subscribeToCollection<TRecord extends object = DbRecord>(input: DbCollectionSubscriptionInput, listener: DbChangeListener<TRecord>): DbSubscription;
|
|
53
|
-
subscribeToRecord<TRecord extends object = DbRecord>(input: DbRecordSubscriptionInput, listener: DbChangeListener<TRecord>): DbSubscription;
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
export interface DbCollectionSubscriptionInput {
|
|
57
|
-
readonly table: string;
|
|
58
|
-
readonly schema?: string;
|
|
59
|
-
}
|
|
60
|
-
export interface DbRecordSubscriptionInput extends DbCollectionSubscriptionInput {
|
|
61
|
-
readonly id: string | number;
|
|
62
|
-
readonly idField?: string;
|
|
63
|
-
}
|
|
64
|
-
export type DbChangeKind = 'insert' | 'update' | 'delete';
|
|
65
|
-
export interface DbChangeEvent<TRecord extends object = DbRecord> {
|
|
66
|
-
readonly table: string;
|
|
67
|
-
readonly schema: string;
|
|
68
|
-
readonly kind: DbChangeKind;
|
|
69
|
-
readonly record: TRecord | null;
|
|
70
|
-
readonly previousRecord?: TRecord;
|
|
71
|
-
readonly committedAt?: string;
|
|
72
|
-
}
|
|
73
|
-
export type DbChangeListener<TRecord extends object = DbRecord> = (event: DbChangeEvent<TRecord>) => void;
|
|
74
|
-
export interface DbSubscription {
|
|
75
|
-
unsubscribe(): Promise<void>;
|
|
76
|
-
}
|
|
77
|
-
export interface SupabaseCollectionDefinition {
|
|
78
|
-
readonly name: string;
|
|
79
|
-
readonly fields: readonly SupabaseFieldDefinition[];
|
|
80
|
-
readonly primaryKey?: string;
|
|
81
|
-
}
|
|
82
|
-
export type SupabaseFieldType = 'text' | 'number' | 'boolean' | 'datetime' | 'json' | 'uuid';
|
|
83
|
-
export interface SupabaseFieldDefinition {
|
|
84
|
-
readonly name: string;
|
|
85
|
-
readonly type: SupabaseFieldType;
|
|
86
|
-
readonly required?: boolean;
|
|
87
|
-
readonly unique?: boolean;
|
|
88
|
-
readonly defaultValue?: string | number | boolean | null;
|
|
89
|
-
}
|
|
90
|
-
export interface SupabaseDbAdminAdapter {
|
|
91
|
-
readonly capabilities: {
|
|
92
|
-
readonly supportsSchemaGeneration: true;
|
|
93
|
-
readonly supportsDirectExecution: boolean;
|
|
94
|
-
};
|
|
95
|
-
createCollection(input: SupabaseCollectionDefinition): Promise<DbAdminResult>;
|
|
96
|
-
deleteCollection(input: {
|
|
97
|
-
readonly name: string;
|
|
98
|
-
}): Promise<DbAdminResult>;
|
|
99
|
-
generateCreateCollectionSql(input: SupabaseCollectionDefinition): DbAdminResult;
|
|
100
|
-
generateDeleteCollectionSql(input: {
|
|
101
|
-
readonly name: string;
|
|
102
|
-
}): DbAdminResult;
|
|
103
|
-
}
|
|
104
|
-
export type DbAdminResult = {
|
|
41
|
+
export type DbAdminSqlExecutionResult = {
|
|
105
42
|
readonly ok: true;
|
|
106
|
-
readonly sql: string;
|
|
107
|
-
readonly executed: boolean;
|
|
108
43
|
} | {
|
|
109
44
|
readonly ok: false;
|
|
110
|
-
readonly error: {
|
|
111
|
-
readonly
|
|
112
|
-
readonly
|
|
113
|
-
|
|
114
|
-
};
|
|
45
|
+
readonly error: DbAdminResult extends {
|
|
46
|
+
readonly ok: false;
|
|
47
|
+
readonly error: infer TError;
|
|
48
|
+
} ? TError : never;
|
|
115
49
|
};
|
|
50
|
+
export type SupabaseDbAdapter = DbAdapter & Partial<DbRealtimeAdapter>;
|
|
51
|
+
export type SupabaseDbAdminAdapter = DbAdminAdapter;
|
|
116
52
|
export {};
|
|
117
53
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,cAAc,EACd,aAAa,EACb,iBAAiB,EAClB,MAAM,yBAAyB,CAAC;AAEjC,UAAU,qCAAqC;IAC7C,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,GAAG,CAAC;IACrD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,uBAAuB;IACtC,EAAE,CACA,IAAI,EAAE,kBAAkB,EACxB,MAAM,EAAE,qCAAqC,EAC7C,QAAQ,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,IAAI,GACnD,uBAAuB,CAAC;IAC3B,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,uBAAuB,CAAC;IACxE,WAAW,IAAI,OAAO,CAAC,IAAI,GAAG,WAAW,GAAG,OAAO,CAAC,CAAC;CACtD;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,uBAAuB,CAAC;IAChD,aAAa,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,GAAG,WAAW,GAAG,OAAO,CAAC,CAAC;CACxF;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,cAAc,CAAC,EAAE,sBAAsB,CAAC;CAClD;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,yBAAyB,CAAC,CAAC;CAC3E;AAED,MAAM,MAAM,yBAAyB,GACjC;IACE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;CACnB,GACD;IACE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE,aAAa,SAAS;QAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,MAAM,CAAA;KAAE,GACtF,MAAM,GACN,KAAK,CAAC;CACX,CAAC;AAEN,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;AACvE,MAAM,MAAM,sBAAsB,GAAG,cAAc,CAAC"}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n DbAdapter,\n DbAdminAdapter,\n DbAdminResult,\n DbRealtimeAdapter,\n} from '@ankhorage/contracts/db';\n\ninterface SupabaseRealtimePostgresChangesFilter {\n readonly event: 'INSERT' | 'UPDATE' | 'DELETE' | '*';\n readonly schema: string;\n readonly table: string;\n readonly filter?: string;\n}\n\nexport interface SupabaseRealtimeChannel {\n on(\n type: 'postgres_changes',\n filter: SupabaseRealtimePostgresChangesFilter,\n callback: (payload: SupabaseRealtimePayload) => void,\n ): SupabaseRealtimeChannel;\n subscribe(callback?: (status: string) => void): SupabaseRealtimeChannel;\n unsubscribe(): Promise<'ok' | 'timed out' | 'error'>;\n}\n\nexport interface SupabaseRealtimeClient {\n channel(topic: string): SupabaseRealtimeChannel;\n removeChannel(channel: SupabaseRealtimeChannel): Promise<'ok' | 'timed out' | 'error'>;\n}\n\nexport interface SupabaseRealtimePayload {\n eventType?: string;\n schema?: string;\n table?: string;\n commit_timestamp?: string;\n new?: unknown;\n old?: unknown;\n errors?: string[] | null;\n}\n\nexport interface SupabaseDbAdapterOptions {\n readonly url: string;\n readonly anonKey: string;\n readonly schema?: string;\n readonly fetch?: typeof fetch;\n readonly realtime?: boolean;\n readonly realtimeClient?: SupabaseRealtimeClient;\n}\n\nexport interface SupabaseDbAdminAdapterOptions {\n readonly url: string;\n readonly serviceRoleKey?: string;\n readonly schema?: string;\n readonly execute?: boolean;\n readonly executeSql?: (sql: string) => Promise<DbAdminSqlExecutionResult>;\n}\n\nexport type DbAdminSqlExecutionResult =\n | {\n readonly ok: true;\n }\n | {\n readonly ok: false;\n readonly error: DbAdminResult extends { readonly ok: false; readonly error: infer TError }\n ? TError\n : never;\n };\n\nexport type SupabaseDbAdapter = DbAdapter & Partial<DbRealtimeAdapter>;\nexport type SupabaseDbAdminAdapter = DbAdminAdapter;\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ankhorage/supabase-db",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"description": "Supabase database adapter for Ankhorage contracts with CRUD, schema management, and optional realtime support.",
|
|
6
6
|
"homepage": "https://github.com/ankhorage/supabase-db#readme",
|
|
7
7
|
"bugs": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"adapter"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@ankhorage/contracts": "^1.
|
|
28
|
+
"@ankhorage/contracts": "^1.3.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"@supabase/supabase-js": "^2.105.3"
|
package/src/adapter.test.ts
CHANGED
|
@@ -142,7 +142,7 @@ describe('createSupabaseDbAdapter', () => {
|
|
|
142
142
|
fetch: () => Promise.resolve(jsonResponse([])),
|
|
143
143
|
});
|
|
144
144
|
|
|
145
|
-
expect(adapter.capabilities
|
|
145
|
+
expect(adapter.capabilities.realtime).toBe(false);
|
|
146
146
|
expect('realtime' in adapter).toBe(false);
|
|
147
147
|
});
|
|
148
148
|
});
|
package/src/adapter.ts
CHANGED
|
@@ -25,13 +25,19 @@ interface NormalizedConfig {
|
|
|
25
25
|
readonly realtimeClient: SupabaseDbAdapterOptions['realtimeClient'];
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
type DbFailureResult = Extract<DbResult<unknown>, { readonly ok: false }>;
|
|
29
|
+
interface DbFindByIdSuccessResult<TRecord extends object> {
|
|
30
|
+
readonly ok: true;
|
|
31
|
+
readonly data: TRecord | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
28
34
|
export function createSupabaseDbAdapter(options: SupabaseDbAdapterOptions): SupabaseDbAdapter {
|
|
29
35
|
const config = normalizeConfig(options);
|
|
30
36
|
const baseAdapter: DbAdapter = {
|
|
31
37
|
capabilities: {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
transactions: false,
|
|
39
|
+
returning: true,
|
|
40
|
+
realtime: config.realtime && config.realtimeClient !== undefined,
|
|
35
41
|
},
|
|
36
42
|
|
|
37
43
|
async select<TRecord extends object = DbRecord>(
|
|
@@ -50,8 +56,9 @@ export function createSupabaseDbAdapter(options: SupabaseDbAdapterOptions): Supa
|
|
|
50
56
|
buildSelectUrl(config.url, {
|
|
51
57
|
table: input.table,
|
|
52
58
|
columns: input.columns,
|
|
53
|
-
filters: [{ field: 'id', operator: 'eq', value: input.id }],
|
|
59
|
+
filters: [{ field: input.idField ?? 'id', operator: 'eq', value: input.id }],
|
|
54
60
|
page: { limit: 1 },
|
|
61
|
+
schema: input.schema,
|
|
55
62
|
}),
|
|
56
63
|
{ method: 'GET' },
|
|
57
64
|
);
|
|
@@ -60,9 +67,12 @@ export function createSupabaseDbAdapter(options: SupabaseDbAdapterOptions): Supa
|
|
|
60
67
|
return result;
|
|
61
68
|
}
|
|
62
69
|
|
|
63
|
-
const
|
|
70
|
+
const success: DbFindByIdSuccessResult<TRecord> = {
|
|
71
|
+
ok: true,
|
|
72
|
+
data: result.data[0] ?? null,
|
|
73
|
+
};
|
|
64
74
|
|
|
65
|
-
return
|
|
75
|
+
return success;
|
|
66
76
|
},
|
|
67
77
|
|
|
68
78
|
async insert<TRecord extends object = DbRecord>(
|
|
@@ -182,7 +192,7 @@ async function requestRows<TRecord extends object>(
|
|
|
182
192
|
function validateRequiredFilters(
|
|
183
193
|
filters: readonly DbFilter[],
|
|
184
194
|
label: string,
|
|
185
|
-
):
|
|
195
|
+
): DbFailureResult | null {
|
|
186
196
|
try {
|
|
187
197
|
validateFilters(filters, label);
|
|
188
198
|
return null;
|
package/src/admin.test.ts
CHANGED
|
@@ -18,6 +18,10 @@ describe('createSupabaseDbAdminAdapter', () => {
|
|
|
18
18
|
],
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
expect(adapter.capabilities).toEqual({
|
|
22
|
+
schemaGeneration: true,
|
|
23
|
+
directExecution: false,
|
|
24
|
+
});
|
|
21
25
|
expect(result.ok).toBe(true);
|
|
22
26
|
expect(result.ok ? result.executed : true).toBe(false);
|
|
23
27
|
expect(result.ok ? result.sql : '').toContain('create table if not exists "public"."posts"');
|
|
@@ -46,6 +50,10 @@ describe('createSupabaseDbAdminAdapter', () => {
|
|
|
46
50
|
fields: [{ name: 'title', type: 'text' }],
|
|
47
51
|
});
|
|
48
52
|
|
|
53
|
+
expect(adapter.capabilities).toEqual({
|
|
54
|
+
schemaGeneration: true,
|
|
55
|
+
directExecution: true,
|
|
56
|
+
});
|
|
49
57
|
expect(result).toEqual({
|
|
50
58
|
ok: true,
|
|
51
59
|
sql: executedSql[0],
|
package/src/admin.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
DbAdminAdapter,
|
|
2
3
|
DbAdminResult,
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
DbCollectionDefinition,
|
|
5
|
+
DbCollectionReference,
|
|
6
|
+
DbFieldDefinition,
|
|
7
|
+
} from '@ankhorage/contracts/db';
|
|
8
|
+
|
|
9
|
+
import type { SupabaseDbAdminAdapterOptions } from './types.js';
|
|
8
10
|
import { quoteIdentifier, validateKey, validateUrl } from './validation.js';
|
|
9
11
|
|
|
10
12
|
interface NormalizedAdminConfig {
|
|
@@ -17,40 +19,40 @@ interface NormalizedAdminConfig {
|
|
|
17
19
|
|
|
18
20
|
export function createSupabaseDbAdminAdapter(
|
|
19
21
|
options: SupabaseDbAdminAdapterOptions,
|
|
20
|
-
):
|
|
22
|
+
): DbAdminAdapter {
|
|
21
23
|
const config = normalizeAdminConfig(options);
|
|
22
24
|
|
|
23
25
|
return {
|
|
24
26
|
capabilities: {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
schemaGeneration: true,
|
|
28
|
+
directExecution: config.execute && config.executeSql !== undefined,
|
|
27
29
|
},
|
|
28
30
|
|
|
29
|
-
async createCollection(input:
|
|
31
|
+
async createCollection(input: DbCollectionDefinition): Promise<DbAdminResult> {
|
|
30
32
|
const generated = generateCreateCollectionSql(input, config.schema);
|
|
31
33
|
|
|
32
34
|
if (!generated.ok || !config.execute) {
|
|
33
35
|
return generated;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
return executeSql(config, generated.sql);
|
|
38
|
+
return executeSql(config, generated.sql ?? '');
|
|
37
39
|
},
|
|
38
40
|
|
|
39
|
-
async deleteCollection(input:
|
|
41
|
+
async deleteCollection(input: DbCollectionReference): Promise<DbAdminResult> {
|
|
40
42
|
const generated = generateDeleteCollectionSql(input, config.schema);
|
|
41
43
|
|
|
42
44
|
if (!generated.ok || !config.execute) {
|
|
43
45
|
return generated;
|
|
44
46
|
}
|
|
45
47
|
|
|
46
|
-
return executeSql(config, generated.sql);
|
|
48
|
+
return executeSql(config, generated.sql ?? '');
|
|
47
49
|
},
|
|
48
50
|
|
|
49
|
-
generateCreateCollectionSql(input:
|
|
51
|
+
generateCreateCollectionSql(input: DbCollectionDefinition): DbAdminResult {
|
|
50
52
|
return generateCreateCollectionSql(input, config.schema);
|
|
51
53
|
},
|
|
52
54
|
|
|
53
|
-
generateDeleteCollectionSql(input:
|
|
55
|
+
generateDeleteCollectionSql(input: DbCollectionReference): DbAdminResult {
|
|
54
56
|
return generateDeleteCollectionSql(input, config.schema);
|
|
55
57
|
},
|
|
56
58
|
};
|
|
@@ -75,11 +77,11 @@ function normalizeAdminConfig(options: SupabaseDbAdminAdapterOptions): Normalize
|
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
function generateCreateCollectionSql(
|
|
78
|
-
input:
|
|
79
|
-
|
|
80
|
+
input: DbCollectionDefinition,
|
|
81
|
+
defaultSchema: string,
|
|
80
82
|
): DbAdminResult {
|
|
81
83
|
try {
|
|
82
|
-
const schema = quoteIdentifier(
|
|
84
|
+
const schema = quoteIdentifier(input.schema ?? defaultSchema);
|
|
83
85
|
const table = quoteIdentifier(input.name);
|
|
84
86
|
const primaryKey = input.primaryKey ?? 'id';
|
|
85
87
|
const fields = input.fields.map((field) => formatField(field));
|
|
@@ -103,11 +105,11 @@ function generateCreateCollectionSql(
|
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
function generateDeleteCollectionSql(
|
|
106
|
-
input:
|
|
107
|
-
|
|
108
|
+
input: DbCollectionReference,
|
|
109
|
+
defaultSchema: string,
|
|
108
110
|
): DbAdminResult {
|
|
109
111
|
try {
|
|
110
|
-
const schema = quoteIdentifier(
|
|
112
|
+
const schema = quoteIdentifier(input.schema ?? defaultSchema);
|
|
111
113
|
const table = quoteIdentifier(input.name);
|
|
112
114
|
|
|
113
115
|
return {
|
|
@@ -140,10 +142,7 @@ async function executeSql(config: NormalizedAdminConfig, sql: string): Promise<D
|
|
|
140
142
|
if (!result.ok) {
|
|
141
143
|
return {
|
|
142
144
|
ok: false,
|
|
143
|
-
error: result.error
|
|
144
|
-
code: 'provider_error',
|
|
145
|
-
message: 'Supabase schema execution failed.',
|
|
146
|
-
},
|
|
145
|
+
error: result.error,
|
|
147
146
|
};
|
|
148
147
|
}
|
|
149
148
|
|
|
@@ -154,7 +153,7 @@ async function executeSql(config: NormalizedAdminConfig, sql: string): Promise<D
|
|
|
154
153
|
};
|
|
155
154
|
}
|
|
156
155
|
|
|
157
|
-
function formatField(field:
|
|
156
|
+
function formatField(field: DbFieldDefinition): string {
|
|
158
157
|
const name = quoteIdentifier(field.name);
|
|
159
158
|
const type = mapFieldType(field.type);
|
|
160
159
|
const required = field.required === true ? ' not null' : '';
|
|
@@ -164,7 +163,7 @@ function formatField(field: SupabaseFieldDefinition): string {
|
|
|
164
163
|
return `${name} ${type}${required}${unique}${defaultValue}`;
|
|
165
164
|
}
|
|
166
165
|
|
|
167
|
-
function mapFieldType(type:
|
|
166
|
+
function mapFieldType(type: DbFieldDefinition['type']): string {
|
|
168
167
|
switch (type) {
|
|
169
168
|
case 'text':
|
|
170
169
|
return 'text';
|
|
@@ -181,7 +180,7 @@ function mapFieldType(type: SupabaseFieldDefinition['type']): string {
|
|
|
181
180
|
}
|
|
182
181
|
}
|
|
183
182
|
|
|
184
|
-
function formatDefaultValue(value:
|
|
183
|
+
function formatDefaultValue(value: DbFieldDefinition['defaultValue']): string {
|
|
185
184
|
if (value === undefined) {
|
|
186
185
|
return '';
|
|
187
186
|
}
|
package/src/index.ts
CHANGED
|
@@ -2,22 +2,26 @@ export { createSupabaseDbAdapter } from './adapter.js';
|
|
|
2
2
|
export { createSupabaseDbAdminAdapter } from './admin.js';
|
|
3
3
|
export { normalizeRealtimeEvent } from './realtime.js';
|
|
4
4
|
export type {
|
|
5
|
-
DbAdminResult,
|
|
6
5
|
DbAdminSqlExecutionResult,
|
|
7
|
-
DbChangeEvent,
|
|
8
|
-
DbChangeKind,
|
|
9
|
-
DbChangeListener,
|
|
10
|
-
DbCollectionSubscriptionInput,
|
|
11
|
-
DbRecordSubscriptionInput,
|
|
12
|
-
DbSubscription,
|
|
13
|
-
RealtimeDbAdapter,
|
|
14
|
-
SupabaseCollectionDefinition,
|
|
15
6
|
SupabaseDbAdapter,
|
|
16
7
|
SupabaseDbAdapterOptions,
|
|
17
8
|
SupabaseDbAdminAdapter,
|
|
18
9
|
SupabaseDbAdminAdapterOptions,
|
|
19
|
-
SupabaseFieldDefinition,
|
|
20
|
-
SupabaseFieldType,
|
|
21
10
|
SupabaseRealtimeClient,
|
|
22
11
|
SupabaseRealtimePayload,
|
|
23
12
|
} from './types.js';
|
|
13
|
+
export type {
|
|
14
|
+
DbAdminAdapter,
|
|
15
|
+
DbAdminResult,
|
|
16
|
+
DbChangeEvent,
|
|
17
|
+
DbChangeKind,
|
|
18
|
+
DbChangeListener,
|
|
19
|
+
DbCollectionDefinition,
|
|
20
|
+
DbCollectionReference,
|
|
21
|
+
DbCollectionSubscriptionInput,
|
|
22
|
+
DbFieldDefinition,
|
|
23
|
+
DbFieldType,
|
|
24
|
+
DbRealtimeAdapter,
|
|
25
|
+
DbRecordSubscriptionInput,
|
|
26
|
+
DbSubscription,
|
|
27
|
+
} from '@ankhorage/contracts/db';
|
package/src/realtime.ts
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import type { DbRecord } from '@ankhorage/contracts/db';
|
|
2
|
-
|
|
3
1
|
import type {
|
|
4
2
|
DbChangeEvent,
|
|
5
3
|
DbChangeKind,
|
|
6
4
|
DbChangeListener,
|
|
7
5
|
DbCollectionSubscriptionInput,
|
|
6
|
+
DbRealtimeAdapter,
|
|
7
|
+
DbRecord,
|
|
8
8
|
DbRecordSubscriptionInput,
|
|
9
9
|
DbSubscription,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} from './types.js';
|
|
10
|
+
} from '@ankhorage/contracts/db';
|
|
11
|
+
|
|
12
|
+
import type { SupabaseRealtimeClient, SupabaseRealtimePayload } from './types.js';
|
|
14
13
|
import { validateIdentifier } from './validation.js';
|
|
15
14
|
|
|
16
15
|
interface RealtimeApiConfig {
|
|
@@ -18,7 +17,7 @@ interface RealtimeApiConfig {
|
|
|
18
17
|
readonly defaultSchema: string;
|
|
19
18
|
}
|
|
20
19
|
|
|
21
|
-
export function createRealtimeApi(config: RealtimeApiConfig):
|
|
20
|
+
export function createRealtimeApi(config: RealtimeApiConfig): DbRealtimeAdapter['realtime'] {
|
|
22
21
|
return {
|
|
23
22
|
subscribeToCollection<TRecord extends object = DbRecord>(
|
|
24
23
|
input: DbCollectionSubscriptionInput,
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
DbAdapter,
|
|
3
|
+
DbAdminAdapter,
|
|
4
|
+
DbAdminResult,
|
|
5
|
+
DbRealtimeAdapter,
|
|
6
|
+
} from '@ankhorage/contracts/db';
|
|
2
7
|
|
|
3
8
|
interface SupabaseRealtimePostgresChangesFilter {
|
|
4
9
|
readonly event: 'INSERT' | 'UPDATE' | 'DELETE' | '*';
|
|
@@ -49,97 +54,16 @@ export interface SupabaseDbAdminAdapterOptions {
|
|
|
49
54
|
readonly executeSql?: (sql: string) => Promise<DbAdminSqlExecutionResult>;
|
|
50
55
|
}
|
|
51
56
|
|
|
52
|
-
export
|
|
53
|
-
readonly ok: boolean;
|
|
54
|
-
readonly error?: {
|
|
55
|
-
readonly code: string;
|
|
56
|
-
readonly message: string;
|
|
57
|
-
readonly cause?: unknown;
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export type SupabaseDbAdapter = DbAdapter & Partial<RealtimeDbAdapter>;
|
|
62
|
-
|
|
63
|
-
export interface RealtimeDbAdapter {
|
|
64
|
-
readonly realtime: {
|
|
65
|
-
subscribeToCollection<TRecord extends object = DbRecord>(
|
|
66
|
-
input: DbCollectionSubscriptionInput,
|
|
67
|
-
listener: DbChangeListener<TRecord>,
|
|
68
|
-
): DbSubscription;
|
|
69
|
-
subscribeToRecord<TRecord extends object = DbRecord>(
|
|
70
|
-
input: DbRecordSubscriptionInput,
|
|
71
|
-
listener: DbChangeListener<TRecord>,
|
|
72
|
-
): DbSubscription;
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export interface DbCollectionSubscriptionInput {
|
|
77
|
-
readonly table: string;
|
|
78
|
-
readonly schema?: string;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export interface DbRecordSubscriptionInput extends DbCollectionSubscriptionInput {
|
|
82
|
-
readonly id: string | number;
|
|
83
|
-
readonly idField?: string;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export type DbChangeKind = 'insert' | 'update' | 'delete';
|
|
87
|
-
|
|
88
|
-
export interface DbChangeEvent<TRecord extends object = DbRecord> {
|
|
89
|
-
readonly table: string;
|
|
90
|
-
readonly schema: string;
|
|
91
|
-
readonly kind: DbChangeKind;
|
|
92
|
-
readonly record: TRecord | null;
|
|
93
|
-
readonly previousRecord?: TRecord;
|
|
94
|
-
readonly committedAt?: string;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export type DbChangeListener<TRecord extends object = DbRecord> = (
|
|
98
|
-
event: DbChangeEvent<TRecord>,
|
|
99
|
-
) => void;
|
|
100
|
-
|
|
101
|
-
export interface DbSubscription {
|
|
102
|
-
unsubscribe(): Promise<void>;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export interface SupabaseCollectionDefinition {
|
|
106
|
-
readonly name: string;
|
|
107
|
-
readonly fields: readonly SupabaseFieldDefinition[];
|
|
108
|
-
readonly primaryKey?: string;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export type SupabaseFieldType = 'text' | 'number' | 'boolean' | 'datetime' | 'json' | 'uuid';
|
|
112
|
-
|
|
113
|
-
export interface SupabaseFieldDefinition {
|
|
114
|
-
readonly name: string;
|
|
115
|
-
readonly type: SupabaseFieldType;
|
|
116
|
-
readonly required?: boolean;
|
|
117
|
-
readonly unique?: boolean;
|
|
118
|
-
readonly defaultValue?: string | number | boolean | null;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export interface SupabaseDbAdminAdapter {
|
|
122
|
-
readonly capabilities: {
|
|
123
|
-
readonly supportsSchemaGeneration: true;
|
|
124
|
-
readonly supportsDirectExecution: boolean;
|
|
125
|
-
};
|
|
126
|
-
createCollection(input: SupabaseCollectionDefinition): Promise<DbAdminResult>;
|
|
127
|
-
deleteCollection(input: { readonly name: string }): Promise<DbAdminResult>;
|
|
128
|
-
generateCreateCollectionSql(input: SupabaseCollectionDefinition): DbAdminResult;
|
|
129
|
-
generateDeleteCollectionSql(input: { readonly name: string }): DbAdminResult;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export type DbAdminResult =
|
|
57
|
+
export type DbAdminSqlExecutionResult =
|
|
133
58
|
| {
|
|
134
59
|
readonly ok: true;
|
|
135
|
-
readonly sql: string;
|
|
136
|
-
readonly executed: boolean;
|
|
137
60
|
}
|
|
138
61
|
| {
|
|
139
62
|
readonly ok: false;
|
|
140
|
-
readonly error: {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
readonly cause?: unknown;
|
|
144
|
-
};
|
|
63
|
+
readonly error: DbAdminResult extends { readonly ok: false; readonly error: infer TError }
|
|
64
|
+
? TError
|
|
65
|
+
: never;
|
|
145
66
|
};
|
|
67
|
+
|
|
68
|
+
export type SupabaseDbAdapter = DbAdapter & Partial<DbRealtimeAdapter>;
|
|
69
|
+
export type SupabaseDbAdminAdapter = DbAdminAdapter;
|