@selvajs/supabase-provider 0.14.3 → 0.14.4-beta.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.
Files changed (86) hide show
  1. package/dist/auth/SupabaseAuthProvider.d.ts +57 -0
  2. package/dist/auth/SupabaseAuthProvider.d.ts.map +1 -1
  3. package/dist/auth/SupabaseAuthProvider.js +116 -4
  4. package/dist/auth/SupabaseAuthProvider.js.map +1 -1
  5. package/dist/auth/__tests__/token-verification.test.d.ts +13 -0
  6. package/dist/auth/__tests__/token-verification.test.d.ts.map +1 -0
  7. package/dist/auth/__tests__/token-verification.test.js +135 -0
  8. package/dist/auth/__tests__/token-verification.test.js.map +1 -0
  9. package/dist/data/SupabaseComputeServerStore.d.ts +34 -2
  10. package/dist/data/SupabaseComputeServerStore.d.ts.map +1 -1
  11. package/dist/data/SupabaseComputeServerStore.js +146 -39
  12. package/dist/data/SupabaseComputeServerStore.js.map +1 -1
  13. package/dist/data/SupabaseDataProvider.d.ts +5 -2
  14. package/dist/data/SupabaseDataProvider.d.ts.map +1 -1
  15. package/dist/data/SupabaseDataProvider.js +24 -18
  16. package/dist/data/SupabaseDataProvider.js.map +1 -1
  17. package/dist/data/SupabaseDefinitionStore.d.ts +2 -1
  18. package/dist/data/SupabaseDefinitionStore.d.ts.map +1 -1
  19. package/dist/data/SupabaseDefinitionStore.js +68 -67
  20. package/dist/data/SupabaseDefinitionStore.js.map +1 -1
  21. package/dist/data/SupabaseInviteStore.d.ts.map +1 -1
  22. package/dist/data/SupabaseInviteStore.js +11 -22
  23. package/dist/data/SupabaseInviteStore.js.map +1 -1
  24. package/dist/data/SupabaseOrgStore.d.ts.map +1 -1
  25. package/dist/data/SupabaseOrgStore.js +49 -86
  26. package/dist/data/SupabaseOrgStore.js.map +1 -1
  27. package/dist/data/SupabaseProjectStore.d.ts.map +1 -1
  28. package/dist/data/SupabaseProjectStore.js +51 -70
  29. package/dist/data/SupabaseProjectStore.js.map +1 -1
  30. package/dist/data/SupabaseShareLinkStore.d.ts.map +1 -1
  31. package/dist/data/SupabaseShareLinkStore.js +13 -27
  32. package/dist/data/SupabaseShareLinkStore.js.map +1 -1
  33. package/dist/data/__tests__/compute-server-conformance.test.js +6 -1
  34. package/dist/data/__tests__/compute-server-conformance.test.js.map +1 -1
  35. package/dist/data/__tests__/compute-server-encryption.test.d.ts +12 -0
  36. package/dist/data/__tests__/compute-server-encryption.test.d.ts.map +1 -0
  37. package/dist/data/__tests__/compute-server-encryption.test.js +143 -0
  38. package/dist/data/__tests__/compute-server-encryption.test.js.map +1 -0
  39. package/dist/data/__tests__/share-link-concurrency.test.d.ts +22 -0
  40. package/dist/data/__tests__/share-link-concurrency.test.d.ts.map +1 -0
  41. package/dist/data/__tests__/share-link-concurrency.test.js +115 -0
  42. package/dist/data/__tests__/share-link-concurrency.test.js.map +1 -0
  43. package/dist/data/client.d.ts +35 -3
  44. package/dist/data/client.d.ts.map +1 -1
  45. package/dist/data/client.js +67 -16
  46. package/dist/data/client.js.map +1 -1
  47. package/dist/data/errors.d.ts +2 -0
  48. package/dist/data/errors.d.ts.map +1 -0
  49. package/dist/data/errors.js +23 -0
  50. package/dist/data/errors.js.map +1 -0
  51. package/dist/data/index.d.ts +7 -2
  52. package/dist/data/index.d.ts.map +1 -1
  53. package/dist/data/index.js +5 -1
  54. package/dist/data/index.js.map +1 -1
  55. package/dist/data/query.d.ts +20 -0
  56. package/dist/data/query.d.ts.map +1 -0
  57. package/dist/data/query.js +13 -0
  58. package/dist/data/query.js.map +1 -0
  59. package/dist/data/rowStamp.d.ts +38 -0
  60. package/dist/data/rowStamp.d.ts.map +1 -0
  61. package/dist/data/rowStamp.js +43 -0
  62. package/dist/data/rowStamp.js.map +1 -0
  63. package/dist/index.d.ts +2 -2
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +1 -1
  66. package/dist/index.js.map +1 -1
  67. package/package.json +2 -2
  68. package/src/auth/SupabaseAuthProvider.ts +140 -4
  69. package/src/auth/__tests__/token-verification.test.ts +158 -0
  70. package/src/data/SupabaseComputeServerStore.ts +163 -43
  71. package/src/data/SupabaseDataProvider.ts +35 -15
  72. package/src/data/SupabaseDefinitionStore.ts +73 -66
  73. package/src/data/SupabaseInviteStore.ts +13 -25
  74. package/src/data/SupabaseOrgStore.ts +51 -95
  75. package/src/data/SupabaseProjectStore.ts +55 -78
  76. package/src/data/SupabaseShareLinkStore.ts +15 -30
  77. package/src/data/__tests__/compute-server-conformance.test.ts +7 -1
  78. package/src/data/__tests__/compute-server-encryption.test.ts +173 -0
  79. package/src/data/__tests__/share-link-concurrency.test.ts +124 -0
  80. package/src/data/client.ts +95 -20
  81. package/src/data/errors.ts +45 -0
  82. package/src/data/index.ts +19 -2
  83. package/src/data/query.ts +22 -0
  84. package/src/data/rowStamp.ts +48 -0
  85. package/src/index.ts +20 -2
  86. package/supabase/migrations/20260711120000_selva_definition_schema_version.sql +18 -0
@@ -12,9 +12,18 @@ import type {
12
12
  Page,
13
13
  UISchema
14
14
  } from '@selvajs/platform';
15
- import { ProviderError, auditSoftDelete, actorFrom, NoopEventSink } from '@selvajs/platform';
15
+ import { ProviderError, actorFrom, NoopEventSink } from '@selvajs/platform';
16
16
  import type { ClientBundle } from './client.js';
17
+ import { mapPostgrestError } from './errors.js';
17
18
  import { nextCursorFromRange, toRange } from './pagination.js';
19
+ import { stampSoftDelete, stampUpdate } from './rowStamp.js';
20
+
21
+ /** Explicit column list for `definitions` — every field `rowToRecord` consumes. */
22
+ const DEFINITION_COLUMNS =
23
+ 'guid, project_id, owner_id, created_by, updated_by, compute_server_id, display_name, description, category, tags, cover_image, status, run_count, live_version_id, draft_version_id, created_at, updated_at, deleted_at';
24
+ /** Explicit column list for `definition_versions` — every field `rowToVersion` consumes. */
25
+ const DEFINITION_VERSION_COLUMNS =
26
+ 'id, definition_guid, version_number, file_ext, file_key, original_filename, uploaded_by, uploaded_at, change_note, schema, schema_extracted_at';
18
27
 
19
28
  /**
20
29
  * Definition metadata + version store backed by Postgres. Spec §6 versioning:
@@ -23,7 +32,7 @@ import { nextCursorFromRange, toRange } from './pagination.js';
23
32
  *
24
33
  * Deletion protection (§6) is enforced by FK constraints — `live_version_id`
25
34
  * and `draft_version_id` are ON DELETE RESTRICT. Trying to delete a
26
- * referenced version raises 23503 which `mapError` turns into a 409.
35
+ * referenced version raises 23503 which `mapPostgrestError` turns into a 409.
27
36
  *
28
37
  * `incrementSolveCount` uses a SQL function for atomic UPDATE — no
29
38
  * read-modify-write race like the local provider has.
@@ -69,27 +78,27 @@ export class SupabaseDefinitionStore implements IDefinitionStore {
69
78
  const range = toRange(opts);
70
79
  const direction = opts?.orderDir ?? 'desc';
71
80
 
81
+ // The public path embeds `project` to filter on its visibility/org; the
82
+ // non-public path selects the definition columns alone. Pick the select
83
+ // string up front so there's a single `.select()` call and the builder
84
+ // keeps one type across both branches.
85
+ const selectColumns = filter?.publicOnly
86
+ ? `${DEFINITION_COLUMNS}, project:projects!inner(visibility, org_id)`
87
+ : DEFINITION_COLUMNS;
88
+
72
89
  let query = this.clients
73
90
  .forRequest(ctx)
74
91
  .from('definitions')
75
- .select('*', { count: 'exact' })
92
+ .select(selectColumns, { count: 'exact' })
76
93
  .is('deleted_at', null);
77
94
 
78
- if (filter?.projectId) query = query.eq('project_id', filter.projectId);
79
-
80
95
  if (filter?.publicOnly) {
81
- query = this.clients
82
- .forRequest(ctx)
83
- .from('definitions')
84
- .select('*, project:projects!inner(visibility, org_id)', {
85
- count: 'exact'
86
- })
87
- .is('deleted_at', null)
88
- .eq('project.visibility', 'public');
96
+ query = query.eq('project.visibility', 'public');
89
97
  if (filter.orgId) query = query.eq('project.org_id', filter.orgId);
90
- if (filter.projectId) query = query.eq('project_id', filter.projectId);
91
98
  }
92
99
 
100
+ if (filter?.projectId) query = query.eq('project_id', filter.projectId);
101
+
93
102
  if (opts?.statuses?.length) {
94
103
  query = query.in('status', opts.statuses);
95
104
  } else {
@@ -106,8 +115,11 @@ export class SupabaseDefinitionStore implements IDefinitionStore {
106
115
  .range(range.from, range.to);
107
116
 
108
117
  const { data, error, count } = await query;
109
- if (error) throw mapError(error);
110
- const items = (data ?? []).map(rowToRecord);
118
+ if (error) throw mapPostgrestError(error);
119
+ // `data` is typed against the dynamic select string, which the PostgREST
120
+ // parser can't resolve to a row shape; the definition columns are all
121
+ // present regardless, so map through the known `DefinitionRow`.
122
+ const items = ((data ?? []) as unknown as DefinitionRow[]).map(rowToRecord);
111
123
  return { items, nextCursor: nextCursorFromRange(range, items.length, count) };
112
124
  }
113
125
 
@@ -115,11 +127,11 @@ export class SupabaseDefinitionStore implements IDefinitionStore {
115
127
  const { data, error } = await this.clients
116
128
  .forRequest(ctx)
117
129
  .from('definitions')
118
- .select('*')
130
+ .select(DEFINITION_COLUMNS)
119
131
  .eq('guid', guid)
120
132
  .is('deleted_at', null)
121
133
  .maybeSingle();
122
- if (error) throw mapError(error);
134
+ if (error) throw mapPostgrestError(error);
123
135
  return data ? rowToRecord(data) : null;
124
136
  }
125
137
 
@@ -128,7 +140,7 @@ export class SupabaseDefinitionStore implements IDefinitionStore {
128
140
  .forRequest(ctx)
129
141
  .from('definitions')
130
142
  .insert(recordToRow(record));
131
- if (error) throw mapError(error);
143
+ if (error) throw mapPostgrestError(error);
132
144
  await this.events.emit({
133
145
  type: 'definition.created',
134
146
  definitionId: record.guid,
@@ -140,7 +152,7 @@ export class SupabaseDefinitionStore implements IDefinitionStore {
140
152
  async update(ctx: RequestContext, guid: string, patch: DefinitionRecordPatch): Promise<void> {
141
153
  const row = patchToRow(patch);
142
154
  if (Object.keys(row).length === 0) return;
143
- if (ctx.userId) row.updated_by = ctx.userId;
155
+ Object.assign(row, stampUpdate(ctx));
144
156
 
145
157
  const { data, error } = await this.clients
146
158
  .forRequest(ctx)
@@ -149,7 +161,7 @@ export class SupabaseDefinitionStore implements IDefinitionStore {
149
161
  .eq('guid', guid)
150
162
  .is('deleted_at', null)
151
163
  .select('guid');
152
- if (error) throw mapError(error);
164
+ if (error) throw mapPostgrestError(error);
153
165
  if (!data || data.length === 0) throw new ProviderError(`Definition '${guid}' not found`, 404);
154
166
  }
155
167
 
@@ -157,19 +169,13 @@ export class SupabaseDefinitionStore implements IDefinitionStore {
157
169
  // Soft-delete (matches the local provider and spec §9). A retention
158
170
  // sweep running as service-role hard-deletes later, which cascades
159
171
  // versions + share_links via FK.
160
- const stamp = auditSoftDelete(ctx, ctx.userId);
161
- const row: Record<string, unknown> = {
162
- deleted_at: stamp.deletedAt,
163
- updated_at: stamp.updatedAt
164
- };
165
- if (stamp.updatedBy) row.updated_by = stamp.updatedBy;
166
172
  const { error } = await this.clients
167
173
  .forRequest(ctx)
168
174
  .from('definitions')
169
- .update(row)
175
+ .update(stampSoftDelete(ctx))
170
176
  .eq('guid', guid)
171
177
  .is('deleted_at', null);
172
- if (error) throw mapError(error);
178
+ if (error) throw mapPostgrestError(error);
173
179
  await this.events.emit({
174
180
  type: 'definition.deleted',
175
181
  definitionId: guid,
@@ -177,9 +183,31 @@ export class SupabaseDefinitionStore implements IDefinitionStore {
177
183
  });
178
184
  }
179
185
 
186
+ async deleteByProject(ctx: RequestContext, projectId: string): Promise<void> {
187
+ // Cascade from `deleteProject` — soft-delete every live definition in the
188
+ // project in one statement. `select('guid')` returns the affected rows so
189
+ // we can emit a `definition.deleted` per record (parity with the local
190
+ // provider and the single-`delete` path above).
191
+ const { data, error } = await this.clients
192
+ .forRequest(ctx)
193
+ .from('definitions')
194
+ .update(stampSoftDelete(ctx))
195
+ .eq('project_id', projectId)
196
+ .is('deleted_at', null)
197
+ .select('guid');
198
+ if (error) throw mapPostgrestError(error);
199
+ for (const row of data ?? []) {
200
+ await this.events.emit({
201
+ type: 'definition.deleted',
202
+ definitionId: row.guid,
203
+ actorId: actorFrom(ctx)
204
+ });
205
+ }
206
+ }
207
+
180
208
  async incrementSolveCount(ctx: RequestContext, guid: string): Promise<void> {
181
209
  const { error } = await this.clients.forRequest(ctx).rpc('increment_run_count', { g: guid });
182
- if (error) throw mapError(error);
210
+ if (error) throw mapPostgrestError(error);
183
211
  }
184
212
 
185
213
  // ============================================================================
@@ -191,7 +219,7 @@ export class SupabaseDefinitionStore implements IDefinitionStore {
191
219
  .forRequest(ctx)
192
220
  .from('definition_versions')
193
221
  .insert(versionToRow(version));
194
- if (error) throw mapError(error);
222
+ if (error) throw mapPostgrestError(error);
195
223
  await this.events.emit({
196
224
  type: 'definition_version.created',
197
225
  versionId: version.id,
@@ -209,11 +237,11 @@ export class SupabaseDefinitionStore implements IDefinitionStore {
209
237
  const { data, error, count } = await this.clients
210
238
  .forRequest(ctx)
211
239
  .from('definition_versions')
212
- .select('*', { count: 'exact' })
240
+ .select(DEFINITION_VERSION_COLUMNS, { count: 'exact' })
213
241
  .eq('definition_guid', definitionId)
214
242
  .order('version_number', { ascending: false })
215
243
  .range(range.from, range.to);
216
- if (error) throw mapError(error);
244
+ if (error) throw mapPostgrestError(error);
217
245
  const items = (data ?? []).map(rowToVersion);
218
246
  return { items, nextCursor: nextCursorFromRange(range, items.length, count) };
219
247
  }
@@ -222,10 +250,10 @@ export class SupabaseDefinitionStore implements IDefinitionStore {
222
250
  const { data, error } = await this.clients
223
251
  .forRequest(ctx)
224
252
  .from('definition_versions')
225
- .select('*')
253
+ .select(DEFINITION_VERSION_COLUMNS)
226
254
  .eq('id', versionId)
227
255
  .maybeSingle();
228
- if (error) throw mapError(error);
256
+ if (error) throw mapPostgrestError(error);
229
257
  return data ? rowToVersion(data) : null;
230
258
  }
231
259
 
@@ -235,19 +263,19 @@ export class SupabaseDefinitionStore implements IDefinitionStore {
235
263
  .from('definition_versions')
236
264
  .update({ schema, schema_extracted_at: new Date().toISOString() })
237
265
  .eq('id', versionId);
238
- if (error) throw mapError(error);
266
+ if (error) throw mapPostgrestError(error);
239
267
  }
240
268
 
241
269
  async deleteVersion(ctx: RequestContext, versionId: string): Promise<void> {
242
270
  // FK enforcement at the DB layer: live_version_id / draft_version_id
243
271
  // are ON DELETE RESTRICT. If the version is referenced by either
244
- // channel, Postgres raises 23503 → mapError → ProviderError(409).
272
+ // channel, Postgres raises 23503 → mapPostgrestError → ProviderError(409).
245
273
  const { error } = await this.clients
246
274
  .forRequest(ctx)
247
275
  .from('definition_versions')
248
276
  .delete()
249
277
  .eq('id', versionId);
250
- if (error) throw mapError(error);
278
+ if (error) throw mapPostgrestError(error);
251
279
  await this.events.emit({
252
280
  type: 'definition_version.deleted',
253
281
  versionId,
@@ -293,7 +321,7 @@ export class SupabaseDefinitionStore implements IDefinitionStore {
293
321
  .select('id, definition_guid')
294
322
  .eq('id', versionId)
295
323
  .maybeSingle();
296
- if (vError) throw mapError(vError);
324
+ if (vError) throw mapPostgrestError(vError);
297
325
  if (!version || version.definition_guid !== definitionId) {
298
326
  throw new ProviderError(`Version '${versionId}' not found for this definition`, 404);
299
327
  }
@@ -303,9 +331,9 @@ export class SupabaseDefinitionStore implements IDefinitionStore {
303
331
  const row: Record<string, unknown> = {
304
332
  live_version_id: versionId,
305
333
  draft_version_id: versionId,
306
- status: 'draft'
334
+ status: 'draft',
335
+ ...stampUpdate(ctx)
307
336
  };
308
- if (ctx.userId) row.updated_by = ctx.userId;
309
337
 
310
338
  const { data, error } = await client
311
339
  .from('definitions')
@@ -313,7 +341,7 @@ export class SupabaseDefinitionStore implements IDefinitionStore {
313
341
  .eq('guid', definitionId)
314
342
  .is('deleted_at', null)
315
343
  .select('guid');
316
- if (error) throw mapError(error);
344
+ if (error) throw mapPostgrestError(error);
317
345
  if (!data || data.length === 0) {
318
346
  throw new ProviderError(`Definition '${definitionId}' not found`, 404);
319
347
  }
@@ -333,20 +361,19 @@ export class SupabaseDefinitionStore implements IDefinitionStore {
333
361
  .select('id, definition_guid')
334
362
  .eq('id', versionId)
335
363
  .maybeSingle();
336
- if (vError) throw mapError(vError);
364
+ if (vError) throw mapPostgrestError(vError);
337
365
  if (!version || version.definition_guid !== definitionId) {
338
366
  throw new ProviderError(`Version '${versionId}' not found for this definition`, 404);
339
367
  }
340
368
 
341
- const row: Record<string, unknown> = { [column]: versionId };
342
- if (ctx.userId) row.updated_by = ctx.userId;
369
+ const row: Record<string, unknown> = { [column]: versionId, ...stampUpdate(ctx) };
343
370
  const { data, error } = await client
344
371
  .from('definitions')
345
372
  .update(row)
346
373
  .eq('guid', definitionId)
347
374
  .is('deleted_at', null)
348
375
  .select('guid');
349
- if (error) throw mapError(error);
376
+ if (error) throw mapPostgrestError(error);
350
377
  if (!data || data.length === 0) {
351
378
  throw new ProviderError(`Definition '${definitionId}' not found`, 404);
352
379
  }
@@ -498,23 +525,3 @@ function definitionOrderColumn(orderBy: DefinitionListOptions['orderBy'] | undef
498
525
  return 'created_at';
499
526
  }
500
527
  }
501
-
502
- interface PostgrestError {
503
- code?: string;
504
- message?: string;
505
- }
506
-
507
- function mapError(e: unknown): Error {
508
- const pg = e as PostgrestError;
509
- if (pg?.code === '23505') return new ProviderError(pg.message ?? 'Duplicate record', 409);
510
- if (pg?.code === '23503') return new ProviderError(pg.message ?? 'Foreign key violation', 409);
511
- if (e instanceof Error) return e;
512
- if (e && typeof e === 'object') {
513
- const obj = e as { message?: string; details?: string; hint?: string; code?: string };
514
- const msg = obj.message ?? obj.details ?? obj.hint ?? 'Unknown Postgres error';
515
- const err = new Error(obj.code ? `[${obj.code}] ${msg}` : msg);
516
- Object.assign(err, obj);
517
- return err;
518
- }
519
- return new Error(String(e));
520
- }
@@ -6,10 +6,15 @@ import type {
6
6
  ListOptions,
7
7
  Page
8
8
  } from '@selvajs/platform';
9
- import { NoopEventSink, ProviderError, actorFrom } from '@selvajs/platform';
9
+ import { NoopEventSink, actorFrom } from '@selvajs/platform';
10
10
  import type { ClientBundle } from './client.js';
11
+ import { mapPostgrestError } from './errors.js';
11
12
  import { nextCursorFromRange, toRange } from './pagination.js';
12
13
 
14
+ /** Explicit column list for `invites` — every field `rowToInvite` consumes. */
15
+ const INVITE_COLUMNS =
16
+ 'id, token_hash, email, org_id, org_role, org_permissions, invited_by, created_at, expires_at, accepted_at, accepted_by_user_id';
17
+
13
18
  /**
14
19
  * Invite store. `getByTokenHash` routes through a SECURITY DEFINER RPC so
15
20
  * unauthenticated / just-signed-up callers can validate and consume the
@@ -29,7 +34,7 @@ export class SupabaseInviteStore implements IInviteStore {
29
34
  .forRequest(ctx)
30
35
  .from('invites')
31
36
  .insert(inviteToRow(invite));
32
- if (error) throw mapError(error);
37
+ if (error) throw mapPostgrestError(error);
33
38
  await this.events.emit({
34
39
  type: 'invite.created',
35
40
  inviteId: invite.id,
@@ -47,7 +52,7 @@ export class SupabaseInviteStore implements IInviteStore {
47
52
  const { data, error } = await this.clients
48
53
  .forRequest(ctx)
49
54
  .rpc('get_invite_by_token_hash', { h: tokenHash });
50
- if (error) throw mapError(error);
55
+ if (error) throw mapPostgrestError(error);
51
56
  // rpc returns the matching row (or null). PostgREST may wrap it in
52
57
  // an array depending on the signature — normalize both shapes.
53
58
  const row = Array.isArray(data) ? data[0] : data;
@@ -59,11 +64,11 @@ export class SupabaseInviteStore implements IInviteStore {
59
64
  const { data, error, count } = await this.clients
60
65
  .forRequest(ctx)
61
66
  .from('invites')
62
- .select('*', { count: 'exact' })
67
+ .select(INVITE_COLUMNS, { count: 'exact' })
63
68
  .eq('org_id', orgId)
64
69
  .order('created_at', { ascending: (opts?.orderDir ?? 'desc') === 'asc' })
65
70
  .range(range.from, range.to);
66
- if (error) throw mapError(error);
71
+ if (error) throw mapPostgrestError(error);
67
72
  const items = (data ?? []).map(rowToInvite);
68
73
  return { items, nextCursor: nextCursorFromRange(range, items.length, count) };
69
74
  }
@@ -78,7 +83,7 @@ export class SupabaseInviteStore implements IInviteStore {
78
83
  .eq('id', id)
79
84
  .is('accepted_at', null)
80
85
  .select('org_id');
81
- if (error) throw mapError(error);
86
+ if (error) throw mapPostgrestError(error);
82
87
  const row = data?.[0];
83
88
  if (!row) return;
84
89
  await this.events.emit({
@@ -98,7 +103,7 @@ export class SupabaseInviteStore implements IInviteStore {
98
103
  .eq('id', id)
99
104
  .is('accepted_at', null)
100
105
  .select('org_id');
101
- if (error) throw mapError(error);
106
+ if (error) throw mapPostgrestError(error);
102
107
  const row = data?.[0];
103
108
  if (!row) return;
104
109
  await this.events.emit({
@@ -115,7 +120,7 @@ export class SupabaseInviteStore implements IInviteStore {
115
120
  .from('invites')
116
121
  .delete()
117
122
  .eq('org_id', orgId);
118
- if (error) throw mapError(error);
123
+ if (error) throw mapPostgrestError(error);
119
124
  }
120
125
  }
121
126
 
@@ -164,20 +169,3 @@ function inviteToRow(i: Invite): InviteRow {
164
169
  accepted_by_user_id: i.acceptedByUserId ?? null
165
170
  };
166
171
  }
167
-
168
- interface PostgrestError {
169
- code?: string;
170
- message?: string;
171
- }
172
-
173
- function mapError(e: unknown): Error {
174
- const pg = e as PostgrestError;
175
- if (pg?.code === '23505') return new ProviderError(pg.message ?? 'Duplicate record', 409);
176
- if (pg?.code === '23503') return new ProviderError(pg.message ?? 'Foreign key violation', 409);
177
- if (e instanceof Error) return e;
178
- if (e && typeof e === 'object') {
179
- const obj = e as { message?: string; code?: string };
180
- return new Error(obj.code ? `[${obj.code}] ${obj.message ?? ''}` : (obj.message ?? String(e)));
181
- }
182
- return new Error(String(e));
183
- }