@ankhorage/supabase-db 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @ankhorage/supabase-db
2
2
 
3
+ ## 1.0.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 5c0d97e: Update Ankhorage dependencies: `@ankhorage/contracts`.
8
+
9
+ ## 1.0.2
10
+
11
+ ### Patch Changes
12
+
13
+ - fb8cbd4: Update Ankhorage dependencies: `@ankhorage/contracts`.
14
+
3
15
  ## 1.0.1
4
16
 
5
17
  ### Patch Changes
@@ -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;AAkB9E,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,GAAG,iBAAiB,CAoG5F"}
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,CAW5F"}
package/dist/adapter.js CHANGED
@@ -4,73 +4,70 @@ import { createRealtimeApi } from './realtime.js';
4
4
  import { validateFilters, validateKey, validateUrl } from './validation.js';
5
5
  export function createSupabaseDbAdapter(options) {
6
6
  const config = normalizeConfig(options);
7
- const baseAdapter = {
7
+ const baseAdapter = createBaseAdapter(config);
8
+ if (!config.realtime || config.realtimeClient === undefined)
9
+ return baseAdapter;
10
+ return {
11
+ ...baseAdapter,
12
+ realtime: createRealtimeApi({
13
+ client: config.realtimeClient,
14
+ defaultSchema: config.schema,
15
+ }),
16
+ };
17
+ }
18
+ function createBaseAdapter(config) {
19
+ return {
8
20
  capabilities: {
9
21
  transactions: false,
10
22
  returning: true,
11
23
  realtime: config.realtime && config.realtimeClient !== undefined,
12
24
  },
13
- async select(input) {
14
- return requestRows(config, buildSelectUrl(config.url, input), {
15
- method: 'GET',
16
- });
25
+ select(input) {
26
+ return selectRows(config, input);
17
27
  },
18
- async findById(input) {
19
- const result = await requestRows(config, buildSelectUrl(config.url, {
20
- table: input.table,
21
- columns: input.columns,
22
- filters: [{ field: input.idField ?? 'id', operator: 'eq', value: input.id }],
23
- page: { limit: 1 },
24
- schema: input.schema,
25
- }), { method: 'GET' });
26
- if (!result.ok) {
27
- return result;
28
- }
29
- const success = {
30
- ok: true,
31
- data: result.data[0] ?? null,
32
- };
33
- return success;
28
+ findById(input) {
29
+ return findRowById(config, input);
34
30
  },
35
- async insert(input) {
36
- const url = buildMutationUrl(config.url, input.table, []);
37
- return requestRows(config, url, {
38
- method: 'POST',
39
- body: JSON.stringify(input.values),
40
- });
31
+ insert(input) {
32
+ return insertRows(config, input);
41
33
  },
42
- async update(input) {
43
- const validationError = validateRequiredFilters(input.filters, 'Update');
44
- if (validationError !== null) {
45
- return validationError;
46
- }
47
- const url = buildMutationUrl(config.url, input.table, input.filters);
48
- return requestRows(config, url, {
49
- method: 'PATCH',
50
- body: JSON.stringify(input.values),
51
- });
34
+ update(input) {
35
+ return updateRows(config, input);
52
36
  },
53
- async delete(input) {
54
- const validationError = validateRequiredFilters(input.filters, 'Delete');
55
- if (validationError !== null) {
56
- return validationError;
57
- }
58
- const url = buildMutationUrl(config.url, input.table, input.filters);
59
- return requestRows(config, url, {
60
- method: 'DELETE',
61
- });
37
+ delete(input) {
38
+ return deleteRows(config, input);
62
39
  },
63
40
  };
64
- if (!config.realtime || config.realtimeClient === undefined) {
65
- return baseAdapter;
66
- }
67
- return {
68
- ...baseAdapter,
69
- realtime: createRealtimeApi({
70
- client: config.realtimeClient,
71
- defaultSchema: config.schema,
72
- }),
41
+ }
42
+ function deleteRows(config, input) {
43
+ const validationError = validateRequiredFilters(input.filters, 'Delete');
44
+ if (validationError !== null)
45
+ return Promise.resolve(validationError);
46
+ const url = buildMutationUrl(config.url, input.table, input.filters);
47
+ return requestRows(config, url, { method: 'DELETE' });
48
+ }
49
+ async function findRowById(config, input) {
50
+ const result = await requestRows(config, buildSelectUrl(config.url, {
51
+ table: input.table,
52
+ columns: input.columns,
53
+ filters: [{ field: input.idField ?? 'id', operator: 'eq', value: input.id }],
54
+ page: { limit: 1 },
55
+ schema: input.schema,
56
+ }), { method: 'GET' });
57
+ if (!result.ok)
58
+ return result;
59
+ const success = {
60
+ ok: true,
61
+ data: result.data[0] ?? null,
73
62
  };
63
+ return success;
64
+ }
65
+ function insertRows(config, input) {
66
+ const url = buildMutationUrl(config.url, input.table, []);
67
+ return requestRows(config, url, {
68
+ method: 'POST',
69
+ body: JSON.stringify(input.values),
70
+ });
74
71
  }
75
72
  function normalizeConfig(options) {
76
73
  const fetchImplementation = options.fetch ?? globalThis.fetch;
@@ -93,9 +90,8 @@ async function requestRows(config, url, init) {
93
90
  headers: createHeaders(config, init.headers),
94
91
  });
95
92
  const body = await readJsonBody(response);
96
- if (!response.ok) {
93
+ if (!response.ok)
97
94
  return { ok: false, error: mapHttpError(response.status, body) };
98
- }
99
95
  const records = normalizeRecords(body);
100
96
  if (records === null) {
101
97
  return {
@@ -112,6 +108,19 @@ async function requestRows(config, url, init) {
112
108
  return { ok: false, error: mapNetworkError(error) };
113
109
  }
114
110
  }
111
+ function selectRows(config, input) {
112
+ return requestRows(config, buildSelectUrl(config.url, input), { method: 'GET' });
113
+ }
114
+ function updateRows(config, input) {
115
+ const validationError = validateRequiredFilters(input.filters, 'Update');
116
+ if (validationError !== null)
117
+ return Promise.resolve(validationError);
118
+ const url = buildMutationUrl(config.url, input.table, input.filters);
119
+ return requestRows(config, url, {
120
+ method: 'PATCH',
121
+ body: JSON.stringify(input.values),
122
+ });
123
+ }
115
124
  function validateRequiredFilters(filters, label) {
116
125
  try {
117
126
  validateFilters(filters, label);
@@ -139,9 +148,8 @@ function createHeaders(config, existingHeaders) {
139
148
  }
140
149
  async function readJsonBody(response) {
141
150
  const text = await response.text();
142
- if (text.trim().length === 0) {
151
+ if (text.trim().length === 0)
143
152
  return [];
144
- }
145
153
  try {
146
154
  return JSON.parse(text);
147
155
  }
@@ -150,12 +158,10 @@ async function readJsonBody(response) {
150
158
  }
151
159
  }
152
160
  function normalizeRecords(value) {
153
- if (Array.isArray(value)) {
161
+ if (Array.isArray(value))
154
162
  return value.filter((isRecord));
155
- }
156
- if (isRecord(value)) {
163
+ if (isRecord(value))
157
164
  return [value];
158
- }
159
165
  return null;
160
166
  }
161
167
  function isRecord(value) {
@@ -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\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"]}
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 = createBaseAdapter(config);\n if (!config.realtime || config.realtimeClient === undefined) return baseAdapter;\n return {\n ...baseAdapter,\n realtime: createRealtimeApi({\n client: config.realtimeClient,\n defaultSchema: config.schema,\n }),\n };\n}\n\nfunction createBaseAdapter(config: NormalizedConfig): DbAdapter {\n return {\n capabilities: {\n transactions: false,\n returning: true,\n realtime: config.realtime && config.realtimeClient !== undefined,\n },\n select<TRecord extends object = DbRecord>(input: DbSelectInput) {\n return selectRows<TRecord>(config, input);\n },\n findById<TRecord extends object = DbRecord>(input: DbFindByIdInput) {\n return findRowById<TRecord>(config, input);\n },\n insert<TRecord extends object = DbRecord>(input: DbInsertInput<TRecord>) {\n return insertRows<TRecord>(config, input);\n },\n update<TRecord extends object = DbRecord>(input: DbUpdateInput<TRecord>) {\n return updateRows<TRecord>(config, input);\n },\n delete<TRecord extends object = DbRecord>(input: DbDeleteInput) {\n return deleteRows<TRecord>(config, input);\n },\n };\n}\n\nfunction deleteRows<TRecord extends object>(\n config: NormalizedConfig,\n input: DbDeleteInput,\n): Promise<DbResult<TRecord[]>> {\n const validationError = validateRequiredFilters(input.filters, 'Delete');\n if (validationError !== null) return Promise.resolve(validationError);\n const url = buildMutationUrl(config.url, input.table, input.filters);\n return requestRows<TRecord>(config, url, { method: 'DELETE' });\n}\n\nasync function findRowById<TRecord extends object>(\n config: NormalizedConfig,\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 if (!result.ok) return result;\n const success: DbFindByIdSuccessResult<TRecord> = {\n ok: true,\n data: result.data[0] ?? null,\n };\n return success;\n}\n\nfunction insertRows<TRecord extends object>(\n config: NormalizedConfig,\n input: DbInsertInput<TRecord>,\n): Promise<DbResult<TRecord[]>> {\n const url = buildMutationUrl(config.url, input.table, []);\n return requestRows<TRecord>(config, url, {\n method: 'POST',\n body: JSON.stringify(input.values),\n });\n}\n\nfunction normalizeConfig(options: SupabaseDbAdapterOptions): NormalizedConfig {\n const fetchImplementation = options.fetch ?? globalThis.fetch;\n if (typeof fetchImplementation !== 'function') {\n throw new TypeError('A fetch implementation is required to use Supabase Database.');\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 if (!response.ok) return { ok: false, error: mapHttpError(response.status, body) };\n const records = normalizeRecords<TRecord>(body);\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 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 return { ok: false, error: mapNetworkError(error) };\n }\n}\n\nfunction selectRows<TRecord extends object>(\n config: NormalizedConfig,\n input: DbSelectInput,\n): Promise<DbResult<TRecord[]>> {\n return requestRows<TRecord>(config, buildSelectUrl(config.url, input), { method: 'GET' });\n}\n\nfunction updateRows<TRecord extends object>(\n config: NormalizedConfig,\n input: DbUpdateInput<TRecord>,\n): Promise<DbResult<TRecord[]>> {\n const validationError = validateRequiredFilters(input.filters, 'Update');\n if (validationError !== null) return Promise.resolve(validationError);\n const url = buildMutationUrl(config.url, input.table, input.filters);\n return requestRows<TRecord>(config, url, {\n method: 'PATCH',\n body: JSON.stringify(input.values),\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 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 if (config.schema !== 'public') {\n headers.set('Accept-Profile', config.schema);\n headers.set('Content-Profile', config.schema);\n }\n return headers;\n}\n\nasync function readJsonBody(response: Response): Promise<unknown> {\n const text = await response.text();\n if (text.trim().length === 0) return [];\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)) return value.filter(isRecord<TRecord>);\n if (isRecord<TRecord>(value)) return [value];\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 +1 @@
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
+ {"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,CAe1F;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,CAc/B"}
package/dist/realtime.js CHANGED
@@ -2,58 +2,17 @@ import { validateIdentifier } from './validation.js';
2
2
  export function createRealtimeApi(config) {
3
3
  return {
4
4
  subscribeToCollection(input, listener) {
5
- const schema = input.schema ?? config.defaultSchema;
6
- const table = validateIdentifier(input.table, 'Realtime table');
7
- const channel = config.client
8
- .channel(`ankhorage-db:${schema}:${table}`)
9
- .on('postgres_changes', {
10
- event: '*',
11
- schema,
12
- table,
13
- }, (payload) => {
14
- const event = normalizeRealtimeEvent(payload, table, schema);
15
- if (event !== null) {
16
- listener(event);
17
- }
18
- })
19
- .subscribe();
20
- return {
21
- async unsubscribe() {
22
- await config.client.removeChannel(channel);
23
- },
24
- };
5
+ return createCollectionSubscription(config, input, listener);
25
6
  },
26
7
  subscribeToRecord(input, listener) {
27
- const schema = input.schema ?? config.defaultSchema;
28
- const table = validateIdentifier(input.table, 'Realtime table');
29
- const idField = validateIdentifier(input.idField ?? 'id', 'Realtime record id field');
30
- const channel = config.client
31
- .channel(`ankhorage-db:${schema}:${table}:${idField}:${String(input.id)}`)
32
- .on('postgres_changes', {
33
- event: '*',
34
- schema,
35
- table,
36
- filter: `${idField}=eq.${String(input.id)}`,
37
- }, (payload) => {
38
- const event = normalizeRealtimeEvent(payload, table, schema);
39
- if (event !== null) {
40
- listener(event);
41
- }
42
- })
43
- .subscribe();
44
- return {
45
- async unsubscribe() {
46
- await config.client.removeChannel(channel);
47
- },
48
- };
8
+ return createRecordSubscription(config, input, listener);
49
9
  },
50
10
  };
51
11
  }
52
12
  export function normalizeRealtimeEvent(payload, fallbackTable, fallbackSchema) {
53
13
  const kind = normalizeKind(payload.eventType);
54
- if (kind === null) {
14
+ if (kind === null)
55
15
  return null;
56
- }
57
16
  const record = normalizeRecord(payload.new);
58
17
  const previousRecord = normalizeRecord(payload.old);
59
18
  const base = {
@@ -63,14 +22,40 @@ export function normalizeRealtimeEvent(payload, fallbackTable, fallbackSchema) {
63
22
  record: kind === 'delete' ? null : record,
64
23
  committedAt: payload.commit_timestamp,
65
24
  };
66
- if (previousRecord === null) {
25
+ if (previousRecord === null)
67
26
  return base;
68
- }
27
+ return { ...base, previousRecord };
28
+ }
29
+ function createCollectionSubscription(config, input, listener) {
30
+ const schema = input.schema ?? config.defaultSchema;
31
+ const table = validateIdentifier(input.table, 'Realtime table');
32
+ return createSubscription(config, `ankhorage-db:${schema}:${table}`, schema, table, listener);
33
+ }
34
+ function createRecordSubscription(config, input, listener) {
35
+ const schema = input.schema ?? config.defaultSchema;
36
+ const table = validateIdentifier(input.table, 'Realtime table');
37
+ const idField = validateIdentifier(input.idField ?? 'id', 'Realtime record id field');
38
+ const id = String(input.id);
39
+ return createSubscription(config, `ankhorage-db:${schema}:${table}:${idField}:${id}`, schema, table, listener, `${idField}=eq.${id}`);
40
+ }
41
+ function createSubscription(config, channelName, schema, table, listener, filter) {
42
+ const channel = config.client
43
+ .channel(channelName)
44
+ .on('postgres_changes', { event: '*', schema, table, filter }, (payload) => {
45
+ const event = normalizeRealtimeEvent(payload, table, schema);
46
+ if (event !== null)
47
+ listener(event);
48
+ })
49
+ .subscribe();
69
50
  return {
70
- ...base,
71
- previousRecord,
51
+ async unsubscribe() {
52
+ await config.client.removeChannel(channel);
53
+ },
72
54
  };
73
55
  }
56
+ function isRecord(value) {
57
+ return typeof value === 'object' && value !== null;
58
+ }
74
59
  function normalizeKind(value) {
75
60
  switch (value) {
76
61
  case 'INSERT':
@@ -84,12 +69,6 @@ function normalizeKind(value) {
84
69
  }
85
70
  }
86
71
  function normalizeRecord(value) {
87
- if (!isRecord(value)) {
88
- return null;
89
- }
90
- return value;
91
- }
92
- function isRecord(value) {
93
- return typeof value === 'object' && value !== null;
72
+ return isRecord(value) ? value : null;
94
73
  }
95
74
  //# sourceMappingURL=realtime.js.map
@@ -1 +1 @@
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"]}
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 return createCollectionSubscription(config, input, listener);\n },\n subscribeToRecord<TRecord extends object = DbRecord>(\n input: DbRecordSubscriptionInput,\n listener: DbChangeListener<TRecord>,\n ): DbSubscription {\n return createRecordSubscription(config, input, listener);\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 if (kind === null) return null;\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 if (previousRecord === null) return base;\n return { ...base, previousRecord };\n}\n\nfunction createCollectionSubscription<TRecord extends object>(\n config: RealtimeApiConfig,\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 return createSubscription(config, `ankhorage-db:${schema}:${table}`, schema, table, listener);\n}\n\nfunction createRecordSubscription<TRecord extends object>(\n config: RealtimeApiConfig,\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 id = String(input.id);\n return createSubscription(\n config,\n `ankhorage-db:${schema}:${table}:${idField}:${id}`,\n schema,\n table,\n listener,\n `${idField}=eq.${id}`,\n );\n}\n\nfunction createSubscription<TRecord extends object>(\n config: RealtimeApiConfig,\n channelName: string,\n schema: string,\n table: string,\n listener: DbChangeListener<TRecord>,\n filter?: string,\n): DbSubscription {\n const channel = config.client\n .channel(channelName)\n .on('postgres_changes', { event: '*', schema, table, filter }, (payload) => {\n const event = normalizeRealtimeEvent<TRecord>(payload, table, schema);\n if (event !== null) listener(event);\n })\n .subscribe();\n return {\n async unsubscribe(): Promise<void> {\n await config.client.removeChannel(channel);\n },\n };\n}\n\nfunction isRecord<TRecord extends object>(value: unknown): value is TRecord {\n return typeof value === 'object' && value !== null;\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 return isRecord<TRecord>(value) ? value : null;\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/supabase-db",
3
3
  "type": "module",
4
- "version": "1.0.1",
4
+ "version": "1.0.3",
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": "^8.0.0"
28
+ "@ankhorage/contracts": "^8.0.2"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "@supabase/supabase-js": "^2.105.3"
@@ -56,10 +56,9 @@
56
56
  },
57
57
  "scripts": {
58
58
  "build": "rm -rf dist tsconfig.tsbuildinfo && bun x tsc -p tsconfig.json",
59
- "changeset": "changeset",
60
- "changeset:status": "changeset status --since=origin/main",
59
+ "changeset": "ankhorage-changeset",
60
+ "changeset:status": "ankhorage-changeset status --since=origin/main",
61
61
  "docs": "paradox",
62
- "knip": "knip",
63
62
  "lint": "ankhorage-eslint . --max-warnings=0",
64
63
  "lint:fix": "ankhorage-eslint . --fix --max-warnings=0",
65
64
  "format": "ankhorage-prettier --write .",
@@ -67,16 +66,15 @@
67
66
  "prepack": "bun run build",
68
67
  "test": "bun test src",
69
68
  "typecheck": "bun x tsc --noEmit -p tsconfig.json",
70
- "version-packages": "changeset version"
69
+ "version-packages": "ankhorage-changeset version",
70
+ "knip:check": "ankhorage-knip"
71
71
  },
72
72
  "devDependencies": {
73
- "@ankhorage/devtools": "^1.0.6",
74
- "@ankhorage/paradox": "^0.1.6",
75
- "@changesets/cli": "^2.31.0",
76
- "@types/bun": "^1.3.13",
73
+ "@ankhorage/devtools": "^1.9.4",
74
+ "@ankhorage/paradox": "^0.1.24",
75
+ "@types/bun": "^1.4.0",
77
76
  "@types/node": "^25.6.0",
78
- "knip": "^6.12.2",
79
77
  "typescript": "^5.9.3"
80
78
  },
81
- "packageManager": "bun@1.3.13"
79
+ "packageManager": "bun@1.4.0"
82
80
  }