@selvajs/supabase-provider 0.11.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/LICENSE +21 -0
- package/README.md +271 -0
- package/dist/auth/SupabaseAuthProvider.d.ts +81 -0
- package/dist/auth/SupabaseAuthProvider.d.ts.map +1 -0
- package/dist/auth/SupabaseAuthProvider.js +383 -0
- package/dist/auth/SupabaseAuthProvider.js.map +1 -0
- package/dist/auth/__tests__/conformance.test.d.ts +2 -0
- package/dist/auth/__tests__/conformance.test.d.ts.map +1 -0
- package/dist/auth/__tests__/conformance.test.js +76 -0
- package/dist/auth/__tests__/conformance.test.js.map +1 -0
- package/dist/auth/index.d.ts +3 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/data/SupabaseAuditQuery.d.ts +16 -0
- package/dist/data/SupabaseAuditQuery.d.ts.map +1 -0
- package/dist/data/SupabaseAuditQuery.js +83 -0
- package/dist/data/SupabaseAuditQuery.js.map +1 -0
- package/dist/data/SupabaseComputeServerStore.d.ts +26 -0
- package/dist/data/SupabaseComputeServerStore.d.ts.map +1 -0
- package/dist/data/SupabaseComputeServerStore.js +227 -0
- package/dist/data/SupabaseComputeServerStore.js.map +1 -0
- package/dist/data/SupabaseDataProvider.d.ts +55 -0
- package/dist/data/SupabaseDataProvider.d.ts.map +1 -0
- package/dist/data/SupabaseDataProvider.js +114 -0
- package/dist/data/SupabaseDataProvider.js.map +1 -0
- package/dist/data/SupabaseDefinitionStore.d.ts +39 -0
- package/dist/data/SupabaseDefinitionStore.d.ts.map +1 -0
- package/dist/data/SupabaseDefinitionStore.js +419 -0
- package/dist/data/SupabaseDefinitionStore.js.map +1 -0
- package/dist/data/SupabaseEventSink.d.ts +17 -0
- package/dist/data/SupabaseEventSink.d.ts.map +1 -0
- package/dist/data/SupabaseEventSink.js +31 -0
- package/dist/data/SupabaseEventSink.js.map +1 -0
- package/dist/data/SupabaseInviteStore.d.ts +22 -0
- package/dist/data/SupabaseInviteStore.d.ts.map +1 -0
- package/dist/data/SupabaseInviteStore.js +159 -0
- package/dist/data/SupabaseInviteStore.js.map +1 -0
- package/dist/data/SupabaseOrgStore.d.ts +33 -0
- package/dist/data/SupabaseOrgStore.d.ts.map +1 -0
- package/dist/data/SupabaseOrgStore.js +455 -0
- package/dist/data/SupabaseOrgStore.js.map +1 -0
- package/dist/data/SupabaseProjectStore.d.ts +29 -0
- package/dist/data/SupabaseProjectStore.d.ts.map +1 -0
- package/dist/data/SupabaseProjectStore.js +328 -0
- package/dist/data/SupabaseProjectStore.js.map +1 -0
- package/dist/data/SupabaseShareLinkStore.d.ts +26 -0
- package/dist/data/SupabaseShareLinkStore.d.ts.map +1 -0
- package/dist/data/SupabaseShareLinkStore.js +150 -0
- package/dist/data/SupabaseShareLinkStore.js.map +1 -0
- package/dist/data/__tests__/compute-server-conformance.test.d.ts +2 -0
- package/dist/data/__tests__/compute-server-conformance.test.d.ts.map +1 -0
- package/dist/data/__tests__/compute-server-conformance.test.js +40 -0
- package/dist/data/__tests__/compute-server-conformance.test.js.map +1 -0
- package/dist/data/__tests__/definition-conformance.test.d.ts +2 -0
- package/dist/data/__tests__/definition-conformance.test.d.ts.map +1 -0
- package/dist/data/__tests__/definition-conformance.test.js +85 -0
- package/dist/data/__tests__/definition-conformance.test.js.map +1 -0
- package/dist/data/__tests__/event-sink-conformance.test.d.ts +2 -0
- package/dist/data/__tests__/event-sink-conformance.test.d.ts.map +1 -0
- package/dist/data/__tests__/event-sink-conformance.test.js +27 -0
- package/dist/data/__tests__/event-sink-conformance.test.js.map +1 -0
- package/dist/data/__tests__/invite-conformance.test.d.ts +2 -0
- package/dist/data/__tests__/invite-conformance.test.d.ts.map +1 -0
- package/dist/data/__tests__/invite-conformance.test.js +45 -0
- package/dist/data/__tests__/invite-conformance.test.js.map +1 -0
- package/dist/data/__tests__/org-conformance.test.d.ts +2 -0
- package/dist/data/__tests__/org-conformance.test.d.ts.map +1 -0
- package/dist/data/__tests__/org-conformance.test.js +29 -0
- package/dist/data/__tests__/org-conformance.test.js.map +1 -0
- package/dist/data/__tests__/project-conformance.test.d.ts +2 -0
- package/dist/data/__tests__/project-conformance.test.d.ts.map +1 -0
- package/dist/data/__tests__/project-conformance.test.js +57 -0
- package/dist/data/__tests__/project-conformance.test.js.map +1 -0
- package/dist/data/__tests__/share-link-conformance.test.d.ts +2 -0
- package/dist/data/__tests__/share-link-conformance.test.d.ts.map +1 -0
- package/dist/data/__tests__/share-link-conformance.test.js +88 -0
- package/dist/data/__tests__/share-link-conformance.test.js.map +1 -0
- package/dist/data/__tests__/test-helpers.d.ts +53 -0
- package/dist/data/__tests__/test-helpers.d.ts.map +1 -0
- package/dist/data/__tests__/test-helpers.js +175 -0
- package/dist/data/__tests__/test-helpers.js.map +1 -0
- package/dist/data/client.d.ts +44 -0
- package/dist/data/client.d.ts.map +1 -0
- package/dist/data/client.js +40 -0
- package/dist/data/client.js.map +1 -0
- package/dist/data/index.d.ts +12 -0
- package/dist/data/index.d.ts.map +1 -0
- package/dist/data/index.js +11 -0
- package/dist/data/index.js.map +1 -0
- package/dist/data/pagination.d.ts +30 -0
- package/dist/data/pagination.d.ts.map +1 -0
- package/dist/data/pagination.js +53 -0
- package/dist/data/pagination.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/permissions/SupabasePlatformPermissionStore.d.ts +25 -0
- package/dist/permissions/SupabasePlatformPermissionStore.d.ts.map +1 -0
- package/dist/permissions/SupabasePlatformPermissionStore.js +153 -0
- package/dist/permissions/SupabasePlatformPermissionStore.js.map +1 -0
- package/dist/permissions/__tests__/conformance.test.d.ts +2 -0
- package/dist/permissions/__tests__/conformance.test.d.ts.map +1 -0
- package/dist/permissions/__tests__/conformance.test.js +27 -0
- package/dist/permissions/__tests__/conformance.test.js.map +1 -0
- package/dist/permissions/index.d.ts +2 -0
- package/dist/permissions/index.d.ts.map +1 -0
- package/dist/permissions/index.js +2 -0
- package/dist/permissions/index.js.map +1 -0
- package/dist/storage/SupabaseStorageProvider.d.ts +67 -0
- package/dist/storage/SupabaseStorageProvider.d.ts.map +1 -0
- package/dist/storage/SupabaseStorageProvider.js +136 -0
- package/dist/storage/SupabaseStorageProvider.js.map +1 -0
- package/dist/storage/__tests__/conformance.test.d.ts +2 -0
- package/dist/storage/__tests__/conformance.test.d.ts.map +1 -0
- package/dist/storage/__tests__/conformance.test.js +149 -0
- package/dist/storage/__tests__/conformance.test.js.map +1 -0
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/userProfile/SupabaseUserProfileProvider.d.ts +16 -0
- package/dist/userProfile/SupabaseUserProfileProvider.d.ts.map +1 -0
- package/dist/userProfile/SupabaseUserProfileProvider.js +148 -0
- package/dist/userProfile/SupabaseUserProfileProvider.js.map +1 -0
- package/dist/userProfile/__tests__/conformance.test.d.ts +2 -0
- package/dist/userProfile/__tests__/conformance.test.d.ts.map +1 -0
- package/dist/userProfile/__tests__/conformance.test.js +38 -0
- package/dist/userProfile/__tests__/conformance.test.js.map +1 -0
- package/dist/userProfile/index.d.ts +2 -0
- package/dist/userProfile/index.d.ts.map +1 -0
- package/dist/userProfile/index.js +2 -0
- package/dist/userProfile/index.js.map +1 -0
- package/package.json +75 -0
- package/src/auth/SupabaseAuthProvider.ts +464 -0
- package/src/auth/__tests__/conformance.test.ts +78 -0
- package/src/auth/index.ts +2 -0
- package/src/data/SupabaseAuditQuery.ts +103 -0
- package/src/data/SupabaseComputeServerStore.ts +262 -0
- package/src/data/SupabaseDataProvider.ts +125 -0
- package/src/data/SupabaseDefinitionStore.ts +504 -0
- package/src/data/SupabaseEventSink.ts +30 -0
- package/src/data/SupabaseInviteStore.ts +183 -0
- package/src/data/SupabaseOrgStore.ts +547 -0
- package/src/data/SupabaseProjectStore.ts +414 -0
- package/src/data/SupabaseShareLinkStore.ts +191 -0
- package/src/data/__tests__/compute-server-conformance.test.ts +40 -0
- package/src/data/__tests__/definition-conformance.test.ts +89 -0
- package/src/data/__tests__/event-sink-conformance.test.ts +32 -0
- package/src/data/__tests__/invite-conformance.test.ts +46 -0
- package/src/data/__tests__/org-conformance.test.ts +30 -0
- package/src/data/__tests__/project-conformance.test.ts +57 -0
- package/src/data/__tests__/share-link-conformance.test.ts +87 -0
- package/src/data/__tests__/test-helpers.ts +186 -0
- package/src/data/client.ts +86 -0
- package/src/data/index.ts +11 -0
- package/src/data/pagination.ts +68 -0
- package/src/index.ts +23 -0
- package/src/permissions/SupabasePlatformPermissionStore.ts +161 -0
- package/src/permissions/__tests__/conformance.test.ts +28 -0
- package/src/permissions/index.ts +1 -0
- package/src/storage/SupabaseStorageProvider.ts +177 -0
- package/src/storage/__tests__/conformance.test.ts +152 -0
- package/src/storage/index.ts +2 -0
- package/src/userProfile/SupabaseUserProfileProvider.ts +177 -0
- package/src/userProfile/__tests__/conformance.test.ts +39 -0
- package/src/userProfile/index.ts +1 -0
- package/supabase/.branches/_current_branch +1 -0
- package/supabase/.temp/cli-latest +1 -0
- package/supabase/config.toml +68 -0
- package/supabase/migrations/0001_initial.sql +1084 -0
- package/supabase/seed.sql +13 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IInviteStore,
|
|
3
|
+
IEventSink,
|
|
4
|
+
Invite,
|
|
5
|
+
RequestContext,
|
|
6
|
+
ListOptions,
|
|
7
|
+
Page
|
|
8
|
+
} from '@selvajs/platform';
|
|
9
|
+
import { NoopEventSink, ProviderError, actorFrom } from '@selvajs/platform';
|
|
10
|
+
import type { ClientBundle } from './client.js';
|
|
11
|
+
import { nextCursorFromRange, toRange } from './pagination.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Invite store. `getByTokenHash` routes through a SECURITY DEFINER RPC so
|
|
15
|
+
* unauthenticated / just-signed-up callers can validate and consume the
|
|
16
|
+
* invite without RLS blocking them. The raw token is hashed at the route
|
|
17
|
+
* layer (the selva app's `invites/token.server.ts`); the store sees only the
|
|
18
|
+
* HMAC digest. Other operations (create, list, revoke) go through the normal
|
|
19
|
+
* REST API with RLS enforcing `manage_org_members`.
|
|
20
|
+
*/
|
|
21
|
+
export class SupabaseInviteStore implements IInviteStore {
|
|
22
|
+
constructor(
|
|
23
|
+
private readonly clients: ClientBundle,
|
|
24
|
+
private readonly events: IEventSink = new NoopEventSink()
|
|
25
|
+
) {}
|
|
26
|
+
|
|
27
|
+
async create(ctx: RequestContext, invite: Invite): Promise<void> {
|
|
28
|
+
const { error } = await this.clients
|
|
29
|
+
.forRequest(ctx)
|
|
30
|
+
.from('invites')
|
|
31
|
+
.insert(inviteToRow(invite));
|
|
32
|
+
if (error) throw mapError(error);
|
|
33
|
+
await this.events.emit({
|
|
34
|
+
type: 'invite.created',
|
|
35
|
+
inviteId: invite.id,
|
|
36
|
+
orgId: invite.orgId,
|
|
37
|
+
email: invite.email,
|
|
38
|
+
actorId: actorFrom(ctx)
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async getByTokenHash(ctx: RequestContext, tokenHash: string): Promise<Invite | null> {
|
|
43
|
+
// Use the SECURITY DEFINER RPC so the token itself is the capability —
|
|
44
|
+
// unauthenticated callers visiting /accept-invite can resolve it. The
|
|
45
|
+
// caller has already hashed the raw URL token; the RPC just looks up
|
|
46
|
+
// the row by digest.
|
|
47
|
+
const { data, error } = await this.clients
|
|
48
|
+
.forRequest(ctx)
|
|
49
|
+
.rpc('get_invite_by_token_hash', { h: tokenHash });
|
|
50
|
+
if (error) throw mapError(error);
|
|
51
|
+
// rpc returns the matching row (or null). PostgREST may wrap it in
|
|
52
|
+
// an array depending on the signature — normalize both shapes.
|
|
53
|
+
const row = Array.isArray(data) ? data[0] : data;
|
|
54
|
+
return row ? rowToInvite(row as InviteRow) : null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async listByOrg(ctx: RequestContext, orgId: string, opts?: ListOptions): Promise<Page<Invite>> {
|
|
58
|
+
const range = toRange(opts);
|
|
59
|
+
const { data, error, count } = await this.clients
|
|
60
|
+
.forRequest(ctx)
|
|
61
|
+
.from('invites')
|
|
62
|
+
.select('*', { count: 'exact' })
|
|
63
|
+
.eq('org_id', orgId)
|
|
64
|
+
.order('created_at', { ascending: (opts?.orderDir ?? 'desc') === 'asc' })
|
|
65
|
+
.range(range.from, range.to);
|
|
66
|
+
if (error) throw mapError(error);
|
|
67
|
+
const items = (data ?? []).map(rowToInvite);
|
|
68
|
+
return { items, nextCursor: nextCursorFromRange(range, items.length, count) };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async markAccepted(ctx: RequestContext, id: string, userId: string): Promise<void> {
|
|
72
|
+
// Direct UPDATE — the caller is authenticated (they just signed up).
|
|
73
|
+
// `markAccepted` is idempotent so we don't error if no row matches.
|
|
74
|
+
const { data, error } = await this.clients
|
|
75
|
+
.forRequest(ctx)
|
|
76
|
+
.from('invites')
|
|
77
|
+
.update({ accepted_at: new Date().toISOString(), accepted_by_user_id: userId })
|
|
78
|
+
.eq('id', id)
|
|
79
|
+
.is('accepted_at', null)
|
|
80
|
+
.select('org_id');
|
|
81
|
+
if (error) throw mapError(error);
|
|
82
|
+
const row = data?.[0];
|
|
83
|
+
if (!row) return;
|
|
84
|
+
await this.events.emit({
|
|
85
|
+
type: 'invite.accepted',
|
|
86
|
+
inviteId: id,
|
|
87
|
+
orgId: (row as { org_id: string }).org_id,
|
|
88
|
+
userId,
|
|
89
|
+
actorId: actorFrom(ctx)
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async revoke(ctx: RequestContext, id: string): Promise<void> {
|
|
94
|
+
const { data, error } = await this.clients
|
|
95
|
+
.forRequest(ctx)
|
|
96
|
+
.from('invites')
|
|
97
|
+
.delete()
|
|
98
|
+
.eq('id', id)
|
|
99
|
+
.is('accepted_at', null)
|
|
100
|
+
.select('org_id');
|
|
101
|
+
if (error) throw mapError(error);
|
|
102
|
+
const row = data?.[0];
|
|
103
|
+
if (!row) return;
|
|
104
|
+
await this.events.emit({
|
|
105
|
+
type: 'invite.revoked',
|
|
106
|
+
inviteId: id,
|
|
107
|
+
orgId: (row as { org_id: string }).org_id,
|
|
108
|
+
actorId: actorFrom(ctx)
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async deleteByOrg(ctx: RequestContext, orgId: string): Promise<void> {
|
|
113
|
+
const { error } = await this.clients
|
|
114
|
+
.forRequest(ctx)
|
|
115
|
+
.from('invites')
|
|
116
|
+
.delete()
|
|
117
|
+
.eq('org_id', orgId);
|
|
118
|
+
if (error) throw mapError(error);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface InviteRow {
|
|
123
|
+
id: string;
|
|
124
|
+
token_hash: string;
|
|
125
|
+
email: string;
|
|
126
|
+
org_id: string;
|
|
127
|
+
org_role: Invite['orgRole'];
|
|
128
|
+
org_permissions: string[];
|
|
129
|
+
invited_by: string;
|
|
130
|
+
created_at: string;
|
|
131
|
+
expires_at: string;
|
|
132
|
+
accepted_at: string | null;
|
|
133
|
+
accepted_by_user_id: string | null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function rowToInvite(row: InviteRow): Invite {
|
|
137
|
+
return {
|
|
138
|
+
id: row.id,
|
|
139
|
+
tokenHash: row.token_hash,
|
|
140
|
+
email: row.email,
|
|
141
|
+
orgId: row.org_id,
|
|
142
|
+
orgRole: row.org_role,
|
|
143
|
+
orgPermissions: row.org_permissions as Invite['orgPermissions'],
|
|
144
|
+
invitedBy: row.invited_by,
|
|
145
|
+
createdAt: row.created_at,
|
|
146
|
+
expiresAt: row.expires_at,
|
|
147
|
+
acceptedAt: row.accepted_at ?? undefined,
|
|
148
|
+
acceptedByUserId: row.accepted_by_user_id ?? undefined
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function inviteToRow(i: Invite): InviteRow {
|
|
153
|
+
return {
|
|
154
|
+
id: i.id,
|
|
155
|
+
token_hash: i.tokenHash,
|
|
156
|
+
email: i.email,
|
|
157
|
+
org_id: i.orgId,
|
|
158
|
+
org_role: i.orgRole,
|
|
159
|
+
org_permissions: i.orgPermissions,
|
|
160
|
+
invited_by: i.invitedBy,
|
|
161
|
+
created_at: i.createdAt,
|
|
162
|
+
expires_at: i.expiresAt,
|
|
163
|
+
accepted_at: i.acceptedAt ?? null,
|
|
164
|
+
accepted_by_user_id: i.acceptedByUserId ?? null
|
|
165
|
+
};
|
|
166
|
+
}
|
|
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
|
+
}
|