@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 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 @supabase/supabase-js
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 not part of the mandatory CRUD contract.
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.supportsRealtime` is `false`.
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
 
@@ -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;AAY9E,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,GAAG,iBAAiB,CAgG5F"}
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
- supportsTransactions: false,
10
- supportsReturning: true,
11
- supportsRealtime: config.realtime && config.realtimeClient !== undefined,
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 records = result.data ?? [];
29
- return { ok: true, data: records[0] ?? null };
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, []);
@@ -1 +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"]}
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\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 { SupabaseDbAdminAdapter, SupabaseDbAdminAdapterOptions } from './types.js';
2
- export declare function createSupabaseDbAdminAdapter(options: SupabaseDbAdminAdapterOptions): SupabaseDbAdminAdapter;
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
@@ -1 +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"}
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
- supportsSchemaGeneration: true,
7
- supportsDirectExecution: config.execute && config.executeSql !== undefined,
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, schemaValue) {
46
+ function generateCreateCollectionSql(input, defaultSchema) {
47
47
  try {
48
- const schema = quoteIdentifier(schemaValue);
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, schemaValue) {
69
+ function generateDeleteCollectionSql(input, defaultSchema) {
70
70
  try {
71
- const schema = quoteIdentifier(schemaValue);
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 { DbAdminResult, DbAdminSqlExecutionResult, DbChangeEvent, DbChangeKind, DbChangeListener, DbCollectionSubscriptionInput, DbRecordSubscriptionInput, DbSubscription, RealtimeDbAdapter, SupabaseCollectionDefinition, SupabaseDbAdapter, SupabaseDbAdapterOptions, SupabaseDbAdminAdapter, SupabaseDbAdminAdapterOptions, SupabaseFieldDefinition, SupabaseFieldType, SupabaseRealtimeClient, SupabaseRealtimePayload, } from './types.js';
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
@@ -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,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"}
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 DbAdminResult,\n DbAdminSqlExecutionResult,\n DbChangeEvent,\n DbChangeKind,\n DbChangeListener,\n DbCollectionSubscriptionInput,\n DbRecordSubscriptionInput,\n DbSubscription,\n RealtimeDbAdapter,\n SupabaseCollectionDefinition,\n SupabaseDbAdapter,\n SupabaseDbAdapterOptions,\n SupabaseDbAdminAdapter,\n SupabaseDbAdminAdapterOptions,\n SupabaseFieldDefinition,\n SupabaseFieldType,\n SupabaseRealtimeClient,\n SupabaseRealtimePayload,\n} from './types.js';\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"]}
@@ -1,10 +1,10 @@
1
- import type { DbRecord } from '@ankhorage/contracts/db';
2
- import type { DbChangeEvent, RealtimeDbAdapter, SupabaseRealtimeClient, SupabaseRealtimePayload } from './types.js';
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): RealtimeDbAdapter['realtime'];
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
@@ -1 +1 @@
1
- {"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAExD,OAAO,KAAK,EACV,aAAa,EAMb,iBAAiB,EACjB,sBAAsB,EACtB,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAGpB,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"}
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"}
@@ -1 +1 @@
1
- {"version":3,"file":"realtime.js","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DbRecord } from '@ankhorage/contracts/db';\n\nimport type {\n DbChangeEvent,\n DbChangeKind,\n DbChangeListener,\n DbCollectionSubscriptionInput,\n DbRecordSubscriptionInput,\n DbSubscription,\n RealtimeDbAdapter,\n SupabaseRealtimeClient,\n SupabaseRealtimePayload,\n} 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): RealtimeDbAdapter['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"]}
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, DbRecord } from '@ankhorage/contracts/db';
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 interface DbAdminSqlExecutionResult {
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 code: string;
112
- readonly message: string;
113
- readonly cause?: unknown;
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
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
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 { DbAdapter, DbRecord } 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 interface DbAdminSqlExecutionResult {\n readonly ok: boolean;\n readonly error?: {\n readonly code: string;\n readonly message: string;\n readonly cause?: unknown;\n };\n}\n\nexport type SupabaseDbAdapter = DbAdapter & Partial<RealtimeDbAdapter>;\n\nexport interface RealtimeDbAdapter {\n readonly realtime: {\n subscribeToCollection<TRecord extends object = DbRecord>(\n input: DbCollectionSubscriptionInput,\n listener: DbChangeListener<TRecord>,\n ): DbSubscription;\n subscribeToRecord<TRecord extends object = DbRecord>(\n input: DbRecordSubscriptionInput,\n listener: DbChangeListener<TRecord>,\n ): DbSubscription;\n };\n}\n\nexport interface DbCollectionSubscriptionInput {\n readonly table: string;\n readonly schema?: string;\n}\n\nexport interface DbRecordSubscriptionInput extends DbCollectionSubscriptionInput {\n readonly id: string | number;\n readonly idField?: string;\n}\n\nexport type DbChangeKind = 'insert' | 'update' | 'delete';\n\nexport interface DbChangeEvent<TRecord extends object = DbRecord> {\n readonly table: string;\n readonly schema: string;\n readonly kind: DbChangeKind;\n readonly record: TRecord | null;\n readonly previousRecord?: TRecord;\n readonly committedAt?: string;\n}\n\nexport type DbChangeListener<TRecord extends object = DbRecord> = (\n event: DbChangeEvent<TRecord>,\n) => void;\n\nexport interface DbSubscription {\n unsubscribe(): Promise<void>;\n}\n\nexport interface SupabaseCollectionDefinition {\n readonly name: string;\n readonly fields: readonly SupabaseFieldDefinition[];\n readonly primaryKey?: string;\n}\n\nexport type SupabaseFieldType = 'text' | 'number' | 'boolean' | 'datetime' | 'json' | 'uuid';\n\nexport interface SupabaseFieldDefinition {\n readonly name: string;\n readonly type: SupabaseFieldType;\n readonly required?: boolean;\n readonly unique?: boolean;\n readonly defaultValue?: string | number | boolean | null;\n}\n\nexport interface SupabaseDbAdminAdapter {\n readonly capabilities: {\n readonly supportsSchemaGeneration: true;\n readonly supportsDirectExecution: boolean;\n };\n createCollection(input: SupabaseCollectionDefinition): Promise<DbAdminResult>;\n deleteCollection(input: { readonly name: string }): Promise<DbAdminResult>;\n generateCreateCollectionSql(input: SupabaseCollectionDefinition): DbAdminResult;\n generateDeleteCollectionSql(input: { readonly name: string }): DbAdminResult;\n}\n\nexport type DbAdminResult =\n | {\n readonly ok: true;\n readonly sql: string;\n readonly executed: boolean;\n }\n | {\n readonly ok: false;\n readonly error: {\n readonly code: string;\n readonly message: string;\n readonly cause?: unknown;\n };\n };\n"]}
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.2.1",
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.2.0"
28
+ "@ankhorage/contracts": "^1.3.0"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "@supabase/supabase-js": "^2.105.3"
@@ -142,7 +142,7 @@ describe('createSupabaseDbAdapter', () => {
142
142
  fetch: () => Promise.resolve(jsonResponse([])),
143
143
  });
144
144
 
145
- expect(adapter.capabilities?.supportsRealtime).toBe(false);
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
- supportsTransactions: false,
33
- supportsReturning: true,
34
- supportsRealtime: config.realtime && config.realtimeClient !== undefined,
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 records = result.data ?? [];
70
+ const success: DbFindByIdSuccessResult<TRecord> = {
71
+ ok: true,
72
+ data: result.data[0] ?? null,
73
+ };
64
74
 
65
- return { ok: true, data: records[0] ?? null };
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
- ): DbResult<never> | null {
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
- SupabaseCollectionDefinition,
4
- SupabaseDbAdminAdapter,
5
- SupabaseDbAdminAdapterOptions,
6
- SupabaseFieldDefinition,
7
- } from './types.js';
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
- ): SupabaseDbAdminAdapter {
22
+ ): DbAdminAdapter {
21
23
  const config = normalizeAdminConfig(options);
22
24
 
23
25
  return {
24
26
  capabilities: {
25
- supportsSchemaGeneration: true,
26
- supportsDirectExecution: config.execute && config.executeSql !== undefined,
27
+ schemaGeneration: true,
28
+ directExecution: config.execute && config.executeSql !== undefined,
27
29
  },
28
30
 
29
- async createCollection(input: SupabaseCollectionDefinition): Promise<DbAdminResult> {
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: { readonly name: string }): Promise<DbAdminResult> {
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: SupabaseCollectionDefinition): DbAdminResult {
51
+ generateCreateCollectionSql(input: DbCollectionDefinition): DbAdminResult {
50
52
  return generateCreateCollectionSql(input, config.schema);
51
53
  },
52
54
 
53
- generateDeleteCollectionSql(input: { readonly name: string }): DbAdminResult {
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: SupabaseCollectionDefinition,
79
- schemaValue: string,
80
+ input: DbCollectionDefinition,
81
+ defaultSchema: string,
80
82
  ): DbAdminResult {
81
83
  try {
82
- const schema = quoteIdentifier(schemaValue);
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: { readonly name: string },
107
- schemaValue: string,
108
+ input: DbCollectionReference,
109
+ defaultSchema: string,
108
110
  ): DbAdminResult {
109
111
  try {
110
- const schema = quoteIdentifier(schemaValue);
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: SupabaseFieldDefinition): string {
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: SupabaseFieldDefinition['type']): string {
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: SupabaseFieldDefinition['defaultValue']): string {
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
- RealtimeDbAdapter,
11
- SupabaseRealtimeClient,
12
- SupabaseRealtimePayload,
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): RealtimeDbAdapter['realtime'] {
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 { DbAdapter, DbRecord } from '@ankhorage/contracts/db';
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 interface DbAdminSqlExecutionResult {
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
- readonly code: string;
142
- readonly message: string;
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;