@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,383 @@
|
|
|
1
|
+
import { createClient } from '@supabase/supabase-js';
|
|
2
|
+
export class SupabaseAuthProvider {
|
|
3
|
+
name = 'Supabase Auth';
|
|
4
|
+
passwordAuth;
|
|
5
|
+
oauth;
|
|
6
|
+
emailLink;
|
|
7
|
+
admin;
|
|
8
|
+
anon;
|
|
9
|
+
anonKey;
|
|
10
|
+
supabaseUrl;
|
|
11
|
+
constructor(config) {
|
|
12
|
+
this.supabaseUrl = config.supabaseUrl;
|
|
13
|
+
this.anonKey = config.anonKey;
|
|
14
|
+
this.admin = createClient(config.supabaseUrl, config.serviceRoleKey, {
|
|
15
|
+
auth: { persistSession: false, autoRefreshToken: false }
|
|
16
|
+
});
|
|
17
|
+
this.anon = createClient(config.supabaseUrl, config.anonKey, {
|
|
18
|
+
auth: { persistSession: false, autoRefreshToken: false }
|
|
19
|
+
});
|
|
20
|
+
this.passwordAuth = new SupabasePasswordAuth(this.admin, this.anon, (email, password) => this.signIn(email, password), config.enableSelfSignup ?? false, (user) => this.hydrate(user));
|
|
21
|
+
this.oauth = new SupabaseOAuthAuth(this.anon, (user) => this.hydrate(user), config.oauthProviders ?? []);
|
|
22
|
+
this.emailLink = new SupabaseEmailLinkAuth(this.anon, (user) => this.hydrate(user), config.allowEmailLinkSignup ?? true);
|
|
23
|
+
}
|
|
24
|
+
static fromEnv(env) {
|
|
25
|
+
const supabaseUrl = env.SUPABASE_URL;
|
|
26
|
+
const anonKey = env.SUPABASE_ANON_KEY;
|
|
27
|
+
const serviceRoleKey = env.SUPABASE_SERVICE_ROLE_KEY;
|
|
28
|
+
if (!supabaseUrl)
|
|
29
|
+
throw new Error('Missing required env var: SUPABASE_URL');
|
|
30
|
+
if (!anonKey)
|
|
31
|
+
throw new Error('Missing required env var: SUPABASE_ANON_KEY');
|
|
32
|
+
if (!serviceRoleKey)
|
|
33
|
+
throw new Error('Missing required env var: SUPABASE_SERVICE_ROLE_KEY');
|
|
34
|
+
const oauthProviders = (env.SUPABASE_OAUTH_PROVIDERS ?? '')
|
|
35
|
+
.split(',')
|
|
36
|
+
.map((p) => p.trim().toLowerCase())
|
|
37
|
+
.filter((p) => p.length > 0);
|
|
38
|
+
return new SupabaseAuthProvider({
|
|
39
|
+
supabaseUrl,
|
|
40
|
+
anonKey,
|
|
41
|
+
serviceRoleKey,
|
|
42
|
+
enableSelfSignup: env.SUPABASE_ENABLE_SELF_SIGNUP === 'true',
|
|
43
|
+
oauthProviders,
|
|
44
|
+
// Default true; set SUPABASE_ALLOW_EMAIL_LINK_SIGNUP=false to lock down
|
|
45
|
+
// to invite-only and reject magic-link signups for new addresses.
|
|
46
|
+
allowEmailLinkSignup: env.SUPABASE_ALLOW_EMAIL_LINK_SIGNUP !== 'false'
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async verifyToken(token) {
|
|
50
|
+
if (!token)
|
|
51
|
+
return null;
|
|
52
|
+
// Per-request anon client with the JWT attached. `getUser(token)` also
|
|
53
|
+
// works, but routing through a scoped client keeps future sub-calls
|
|
54
|
+
// (profile fetch, etc.) RLS-aware if we ever move them here.
|
|
55
|
+
const { data, error } = await this.anon.auth.getUser(token);
|
|
56
|
+
if (error || !data.user)
|
|
57
|
+
return null;
|
|
58
|
+
if (data.user.user_metadata?.disabled === true)
|
|
59
|
+
return null;
|
|
60
|
+
return this.hydrate(data.user);
|
|
61
|
+
}
|
|
62
|
+
async getUser(id) {
|
|
63
|
+
const { data, error } = await this.admin.auth.admin.getUserById(id);
|
|
64
|
+
if (error || !data.user)
|
|
65
|
+
return null;
|
|
66
|
+
return this.hydrate(data.user);
|
|
67
|
+
}
|
|
68
|
+
async listUsers(opts) {
|
|
69
|
+
// GoTrue's admin.listUsers uses its own pagination (page/perPage).
|
|
70
|
+
// Translate our cursor (offset) into page number.
|
|
71
|
+
const perPage = opts?.limit ?? 50;
|
|
72
|
+
const page = opts?.cursor ? parseInt(opts.cursor, 10) : 1;
|
|
73
|
+
const { data, error } = await this.admin.auth.admin.listUsers({
|
|
74
|
+
page: Number.isFinite(page) && page >= 1 ? page : 1,
|
|
75
|
+
perPage
|
|
76
|
+
});
|
|
77
|
+
if (error)
|
|
78
|
+
throw error;
|
|
79
|
+
const users = data.users;
|
|
80
|
+
const hydrated = users.map((u) => this.hydrate(u));
|
|
81
|
+
// If the page was full, assume there may be more.
|
|
82
|
+
const nextCursor = users.length >= perPage ? String(page + 1) : undefined;
|
|
83
|
+
return { items: hydrated, nextCursor };
|
|
84
|
+
}
|
|
85
|
+
async createUser(email) {
|
|
86
|
+
// Allowlist variant: create without a password. The user receives a
|
|
87
|
+
// magic-link / OIDC handoff from the consuming app. Identity-only;
|
|
88
|
+
// platform permissions are granted via IPlatformPermissionStore.
|
|
89
|
+
const { data, error } = await this.admin.auth.admin.createUser({
|
|
90
|
+
email,
|
|
91
|
+
email_confirm: true
|
|
92
|
+
});
|
|
93
|
+
if (error)
|
|
94
|
+
throw error;
|
|
95
|
+
if (!data.user)
|
|
96
|
+
throw new Error('createUser returned no user');
|
|
97
|
+
return this.hydrate(data.user);
|
|
98
|
+
}
|
|
99
|
+
async deleteUser(id) {
|
|
100
|
+
// Identity-only delete. The §2 sole-`instance_admin` invariant lives
|
|
101
|
+
// on IPlatformPermissionStore; callers consult it before calling here.
|
|
102
|
+
const { error } = await this.admin.auth.admin.deleteUser(id);
|
|
103
|
+
if (error) {
|
|
104
|
+
// 404-shaped errors from GoTrue surface as { status: 404 } or a
|
|
105
|
+
// specific message. Normalize to 'not_found' rather than throwing.
|
|
106
|
+
const e = error;
|
|
107
|
+
if (e.status === 404 || /not.?found/i.test(e.message ?? ''))
|
|
108
|
+
return 'not_found';
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
return 'ok';
|
|
112
|
+
}
|
|
113
|
+
async disableUser(id) {
|
|
114
|
+
const { data: existing, error: fetchError } = await this.admin.auth.admin.getUserById(id);
|
|
115
|
+
if (fetchError || !existing.user)
|
|
116
|
+
return 'not_found';
|
|
117
|
+
// GoTrue's user_metadata update merges shallowly — preserve existing
|
|
118
|
+
// fields by spreading them in.
|
|
119
|
+
const mergedMetadata = {
|
|
120
|
+
...(existing.user.user_metadata ?? {}),
|
|
121
|
+
disabled: true
|
|
122
|
+
};
|
|
123
|
+
const { error } = await this.admin.auth.admin.updateUserById(id, {
|
|
124
|
+
user_metadata: mergedMetadata
|
|
125
|
+
});
|
|
126
|
+
if (error) {
|
|
127
|
+
const e = error;
|
|
128
|
+
if (e.status === 404 || /not.?found/i.test(e.message ?? ''))
|
|
129
|
+
return 'not_found';
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
return 'ok';
|
|
133
|
+
}
|
|
134
|
+
async touchLastLogin(id) {
|
|
135
|
+
// 60-second debounce. Matches the local provider.
|
|
136
|
+
const { data, error } = await this.admin
|
|
137
|
+
.from('user_profiles')
|
|
138
|
+
.select('last_login_at')
|
|
139
|
+
.eq('user_id', id)
|
|
140
|
+
.maybeSingle();
|
|
141
|
+
if (error)
|
|
142
|
+
return;
|
|
143
|
+
if (data?.last_login_at) {
|
|
144
|
+
const prev = Date.parse(data.last_login_at);
|
|
145
|
+
if (Number.isFinite(prev) && Date.now() - prev < 60_000)
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
await this.admin
|
|
149
|
+
.from('user_profiles')
|
|
150
|
+
.update({ last_login_at: new Date().toISOString() })
|
|
151
|
+
.eq('user_id', id);
|
|
152
|
+
}
|
|
153
|
+
// OAuth lives on `this.oauth` (typed `IOAuthAuth`); see `SupabaseOAuthAuth`
|
|
154
|
+
// below. Mirrors the `passwordAuth` capability split.
|
|
155
|
+
// ============================================================================
|
|
156
|
+
// Internals
|
|
157
|
+
// ============================================================================
|
|
158
|
+
/**
|
|
159
|
+
* Sign in via GoTrue. Returns the access token string and the hydrated
|
|
160
|
+
* user. Called by `SupabasePasswordAuth.verifyLogin` — kept on the
|
|
161
|
+
* provider class so it has access to the hydrate helper without plumbing.
|
|
162
|
+
*/
|
|
163
|
+
async signIn(email, password) {
|
|
164
|
+
const { data, error } = await this.anon.auth.signInWithPassword({ email, password });
|
|
165
|
+
if (error || !data.user || !data.session)
|
|
166
|
+
return null;
|
|
167
|
+
if (data.user.user_metadata?.disabled === true)
|
|
168
|
+
return null;
|
|
169
|
+
return { user: this.hydrate(data.user), sessionToken: data.session.access_token };
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Map a GoTrue `User` to our identity-only `AuthUser`. Platform permissions
|
|
173
|
+
* live on `IPlatformPermissionStore` and profile fields (displayName,
|
|
174
|
+
* starred, recentRuns) on `IUserProfileStore` — neither is in AuthUser.
|
|
175
|
+
*/
|
|
176
|
+
hydrate(user) {
|
|
177
|
+
return {
|
|
178
|
+
id: user.id,
|
|
179
|
+
email: user.email ?? undefined,
|
|
180
|
+
createdAt: user.created_at ?? undefined,
|
|
181
|
+
lastLoginAt: user.last_sign_in_at ?? undefined,
|
|
182
|
+
disabled: user.user_metadata?.disabled === true,
|
|
183
|
+
metadata: user.user_metadata
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Expose the anon URL + key so other code (e.g. the same process's
|
|
188
|
+
* storage provider or per-request client factories) can build user-scoped
|
|
189
|
+
* clients without requiring a second config read.
|
|
190
|
+
*/
|
|
191
|
+
getAnonClientConfig() {
|
|
192
|
+
return { supabaseUrl: this.supabaseUrl, anonKey: this.anonKey };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
class SupabasePasswordAuth {
|
|
196
|
+
admin;
|
|
197
|
+
anon;
|
|
198
|
+
signIn;
|
|
199
|
+
enableSelfSignup;
|
|
200
|
+
hydrate;
|
|
201
|
+
constructor(admin, anon, signIn, enableSelfSignup, hydrate) {
|
|
202
|
+
this.admin = admin;
|
|
203
|
+
this.anon = anon;
|
|
204
|
+
this.signIn = signIn;
|
|
205
|
+
this.enableSelfSignup = enableSelfSignup;
|
|
206
|
+
this.hydrate = hydrate;
|
|
207
|
+
}
|
|
208
|
+
async verifyLogin(email, password) {
|
|
209
|
+
const result = await this.signIn(email, password);
|
|
210
|
+
if (!result)
|
|
211
|
+
return { kind: 'failed', reason: 'invalid_credentials' };
|
|
212
|
+
return { kind: 'success', user: result.user, sessionToken: result.sessionToken };
|
|
213
|
+
}
|
|
214
|
+
async createUserWithPassword(email, password) {
|
|
215
|
+
// Identity-only — platform permissions are granted separately via
|
|
216
|
+
// IPlatformPermissionStore.set after creation.
|
|
217
|
+
const { data, error } = await this.admin.auth.admin.createUser({
|
|
218
|
+
email,
|
|
219
|
+
password,
|
|
220
|
+
email_confirm: true
|
|
221
|
+
});
|
|
222
|
+
if (error)
|
|
223
|
+
throw error;
|
|
224
|
+
if (!data.user)
|
|
225
|
+
throw new Error('createUserWithPassword returned no user');
|
|
226
|
+
return this.hydrate(data.user);
|
|
227
|
+
}
|
|
228
|
+
async registerUser(email, password) {
|
|
229
|
+
if (!this.enableSelfSignup)
|
|
230
|
+
return null;
|
|
231
|
+
const { data, error } = await this.anon.auth.signUp({ email, password });
|
|
232
|
+
if (error || !data.user)
|
|
233
|
+
return null;
|
|
234
|
+
return this.hydrate(data.user);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Supabase-backed `IOAuthAuth`. Thin wrappers over GoTrue's `signInWithOAuth`
|
|
239
|
+
* / `exchangeCodeForSession` / `refreshSession` — the failure-shape mapping
|
|
240
|
+
* (return null vs throw) is the only logic.
|
|
241
|
+
*/
|
|
242
|
+
class SupabaseOAuthAuth {
|
|
243
|
+
anon;
|
|
244
|
+
hydrate;
|
|
245
|
+
providers;
|
|
246
|
+
constructor(anon, hydrate, providers) {
|
|
247
|
+
this.anon = anon;
|
|
248
|
+
this.hydrate = hydrate;
|
|
249
|
+
this.providers = providers;
|
|
250
|
+
}
|
|
251
|
+
listProviders() {
|
|
252
|
+
return this.providers;
|
|
253
|
+
}
|
|
254
|
+
async getOAuthAuthorizationUrl(provider, redirectTo) {
|
|
255
|
+
const { data, error } = await this.anon.auth.signInWithOAuth({
|
|
256
|
+
provider,
|
|
257
|
+
options: { redirectTo, skipBrowserRedirect: true }
|
|
258
|
+
});
|
|
259
|
+
if (error || !data.url)
|
|
260
|
+
throw error ?? new Error('signInWithOAuth returned no URL');
|
|
261
|
+
return data.url;
|
|
262
|
+
}
|
|
263
|
+
async exchangeOAuthCode(code) {
|
|
264
|
+
const { data, error } = await this.anon.auth.exchangeCodeForSession(code);
|
|
265
|
+
if (error || !data.user || !data.session)
|
|
266
|
+
return null;
|
|
267
|
+
if (data.user.user_metadata?.disabled === true)
|
|
268
|
+
return null;
|
|
269
|
+
return {
|
|
270
|
+
user: this.hydrate(data.user),
|
|
271
|
+
sessionToken: data.session.access_token,
|
|
272
|
+
refreshToken: data.session.refresh_token
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
async refreshSession(refreshToken) {
|
|
276
|
+
const { data, error } = await this.anon.auth.refreshSession({ refresh_token: refreshToken });
|
|
277
|
+
if (error || !data.session)
|
|
278
|
+
return null;
|
|
279
|
+
return {
|
|
280
|
+
sessionToken: data.session.access_token,
|
|
281
|
+
refreshToken: data.session.refresh_token
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Supabase-backed `IEmailLinkAuth`. Wraps GoTrue's `signInWithOtp` (send the
|
|
287
|
+
* link) and `verifyOtp` with `type: 'magiclink' | 'email' | 'signup'`
|
|
288
|
+
* (verify on click).
|
|
289
|
+
*
|
|
290
|
+
* Token shape: Supabase emails the user `{callbackUrl}?token_hash=…&type=…`.
|
|
291
|
+
* `verifyMagicLink` accepts either the full URL or just the raw `token_hash`
|
|
292
|
+
* — adapters MAY support either to give the route layer flexibility. The
|
|
293
|
+
* `type` query param decides which OTP variant `verifyOtp` runs (signup vs
|
|
294
|
+
* magic-link returning user vs invite acceptance), so we pass it through.
|
|
295
|
+
*/
|
|
296
|
+
class SupabaseEmailLinkAuth {
|
|
297
|
+
anon;
|
|
298
|
+
hydrate;
|
|
299
|
+
allowSignup;
|
|
300
|
+
constructor(anon, hydrate, allowSignup) {
|
|
301
|
+
this.anon = anon;
|
|
302
|
+
this.hydrate = hydrate;
|
|
303
|
+
this.allowSignup = allowSignup;
|
|
304
|
+
}
|
|
305
|
+
async sendMagicLink(email, callbackUrl) {
|
|
306
|
+
const { error } = await this.anon.auth.signInWithOtp({
|
|
307
|
+
email,
|
|
308
|
+
options: {
|
|
309
|
+
emailRedirectTo: callbackUrl,
|
|
310
|
+
shouldCreateUser: this.allowSignup
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
if (!error)
|
|
314
|
+
return { ok: true };
|
|
315
|
+
// Map GoTrue's classified errors to our coarse reasons. Anything we
|
|
316
|
+
// don't recognize we throw — the route returns 500 and we get a stack
|
|
317
|
+
// trace, instead of swallowing a real bug as "rate limited".
|
|
318
|
+
const status = error.status;
|
|
319
|
+
const code = error.code;
|
|
320
|
+
const message = error.message ?? '';
|
|
321
|
+
if (status === 429 || /rate.?limit/i.test(message))
|
|
322
|
+
return { ok: false, reason: 'rate_limited' };
|
|
323
|
+
if (code === 'otp_disabled' ||
|
|
324
|
+
code === 'signup_disabled' ||
|
|
325
|
+
/signup.*disabled/i.test(message)) {
|
|
326
|
+
return { ok: false, reason: 'signup_disabled' };
|
|
327
|
+
}
|
|
328
|
+
if (code === 'validation_failed' || /invalid.*email|email.*invalid/i.test(message)) {
|
|
329
|
+
return { ok: false, reason: 'invalid_email' };
|
|
330
|
+
}
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
333
|
+
async verifyMagicLink(rawCallbackUrl) {
|
|
334
|
+
const params = parseCallbackParams(rawCallbackUrl);
|
|
335
|
+
if (!params)
|
|
336
|
+
return null;
|
|
337
|
+
const { tokenHash, type } = params;
|
|
338
|
+
const { data, error } = await this.anon.auth.verifyOtp({
|
|
339
|
+
token_hash: tokenHash,
|
|
340
|
+
type
|
|
341
|
+
});
|
|
342
|
+
if (error || !data.user || !data.session)
|
|
343
|
+
return null;
|
|
344
|
+
if (data.user.user_metadata?.disabled === true)
|
|
345
|
+
return null;
|
|
346
|
+
return {
|
|
347
|
+
user: this.hydrate(data.user),
|
|
348
|
+
sessionToken: data.session.access_token,
|
|
349
|
+
refreshToken: data.session.refresh_token
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Pull `token_hash` + `type` from either a full callback URL or a bare
|
|
355
|
+
* `token_hash=…&type=…` query string. Tolerant of both shapes so the route
|
|
356
|
+
* layer can pass whichever form is convenient.
|
|
357
|
+
*
|
|
358
|
+
* Returns null when the inputs aren't recognizable — the caller treats null
|
|
359
|
+
* as "send the user back to /login".
|
|
360
|
+
*/
|
|
361
|
+
function parseCallbackParams(raw) {
|
|
362
|
+
let search;
|
|
363
|
+
try {
|
|
364
|
+
// Full URL path
|
|
365
|
+
search = new URL(raw).searchParams;
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
// Query-string-only path — treat as bare params.
|
|
369
|
+
search = new URLSearchParams(raw.startsWith('?') ? raw.slice(1) : raw);
|
|
370
|
+
}
|
|
371
|
+
const tokenHash = search.get('token_hash');
|
|
372
|
+
const type = search.get('type');
|
|
373
|
+
if (!tokenHash)
|
|
374
|
+
return null;
|
|
375
|
+
const allowed = new Set(['magiclink', 'email', 'signup', 'invite', 'recovery']);
|
|
376
|
+
if (!type || !allowed.has(type))
|
|
377
|
+
return null;
|
|
378
|
+
return {
|
|
379
|
+
tokenHash,
|
|
380
|
+
type: type
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
//# sourceMappingURL=SupabaseAuthProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SupabaseAuthProvider.js","sourceRoot":"","sources":["../../src/auth/SupabaseAuthProvider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conformance.test.d.ts","sourceRoot":"","sources":["../../../src/auth/__tests__/conformance.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, beforeEach, it } from 'vitest';
|
|
2
|
+
import { runAuthProviderConformance, runEmailLinkAuthConformance } from '@selvajs/platform/testing';
|
|
3
|
+
import { SupabaseAuthProvider } from '../SupabaseAuthProvider.js';
|
|
4
|
+
import { readEnv, resetAllData } from '../../data/__tests__/test-helpers.js';
|
|
5
|
+
const envCtx = readEnv();
|
|
6
|
+
if (!envCtx) {
|
|
7
|
+
describe.skip('SupabaseAuthProvider (skipped: no live stack)', () => {
|
|
8
|
+
it('populate packages/providers/supabase/.env.test with Supabase creds to run these tests', () => { });
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
const ctx = envCtx; // non-null in this branch
|
|
13
|
+
// The auth conformance suite expects `verifyLogin('', adminPassword)` to
|
|
14
|
+
// succeed. Supabase requires a non-empty email, so we seed a deterministic
|
|
15
|
+
// admin email and have the suite use that via the `adminEmail` option.
|
|
16
|
+
const ADMIN_EMAIL = 'admin@conformance.test';
|
|
17
|
+
const ADMIN_PASSWORD = 'conformance-admin-password-1234';
|
|
18
|
+
async function ensureAdmin() {
|
|
19
|
+
const { data: existing } = await ctx.adminClient.auth.admin.listUsers({ perPage: 1000 });
|
|
20
|
+
const match = existing.users.find((u) => u.email === ADMIN_EMAIL);
|
|
21
|
+
if (match)
|
|
22
|
+
return match.id;
|
|
23
|
+
const { data, error } = await ctx.adminClient.auth.admin.createUser({
|
|
24
|
+
email: ADMIN_EMAIL,
|
|
25
|
+
password: ADMIN_PASSWORD,
|
|
26
|
+
email_confirm: true
|
|
27
|
+
});
|
|
28
|
+
if (error)
|
|
29
|
+
throw error;
|
|
30
|
+
return data.user.id;
|
|
31
|
+
}
|
|
32
|
+
async function makeProvider() {
|
|
33
|
+
const provider = new SupabaseAuthProvider({
|
|
34
|
+
supabaseUrl: ctx.url,
|
|
35
|
+
anonKey: ctx.anonKey,
|
|
36
|
+
serviceRoleKey: ctx.serviceRoleKey
|
|
37
|
+
});
|
|
38
|
+
// Ensure the admin account exists + has instance_admin.
|
|
39
|
+
const id = await ensureAdmin();
|
|
40
|
+
const { error: upError } = await ctx.adminClient
|
|
41
|
+
.from('user_profiles')
|
|
42
|
+
.update({ platform_permissions: ['instance_admin'] })
|
|
43
|
+
.eq('user_id', id);
|
|
44
|
+
if (upError)
|
|
45
|
+
throw upError;
|
|
46
|
+
return { provider, adminPassword: ADMIN_PASSWORD };
|
|
47
|
+
}
|
|
48
|
+
describe('SupabaseAuthProvider', () => {
|
|
49
|
+
beforeEach(async () => {
|
|
50
|
+
await resetAllData(ctx);
|
|
51
|
+
});
|
|
52
|
+
runAuthProviderConformance({
|
|
53
|
+
name: 'SupabaseAuthProvider',
|
|
54
|
+
createProvider: async () => {
|
|
55
|
+
const { provider, adminPassword } = await makeProvider();
|
|
56
|
+
return { provider, adminPassword, adminEmail: ADMIN_EMAIL };
|
|
57
|
+
},
|
|
58
|
+
userManagement: true
|
|
59
|
+
});
|
|
60
|
+
runEmailLinkAuthConformance({
|
|
61
|
+
name: 'SupabaseEmailLinkAuth',
|
|
62
|
+
createAdapter: async () => {
|
|
63
|
+
const { provider } = await makeProvider();
|
|
64
|
+
return {
|
|
65
|
+
adapter: provider.emailLink,
|
|
66
|
+
// `validEmail` isn't used by the suite for live mail delivery;
|
|
67
|
+
// it just needs to look like a real address. The malformed
|
|
68
|
+
// case is what we actually assert on.
|
|
69
|
+
validEmail: 'real-looking@conformance.test',
|
|
70
|
+
invalidEmail: 'not-an-email-address'
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=conformance.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conformance.test.js","sourceRoot":"","sources":["../../../src/auth/__tests__/conformance.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,YAAY,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AuditQueryFilters, AuditQueryResult, IAuditQuery, RequestContext } from '@selvajs/platform';
|
|
2
|
+
import type { ClientBundle } from './client.js';
|
|
3
|
+
/**
|
|
4
|
+
* Read-side for `audit_events`. Always uses the service-role client — RLS
|
|
5
|
+
* blocks all read access by design, so the route layer is the single trust
|
|
6
|
+
* boundary (callers must gate on `instance_admin`).
|
|
7
|
+
*
|
|
8
|
+
* Pagination is keyset on `(occurred_at desc, id desc)`. The `id` tie-breaker
|
|
9
|
+
* prevents skipped rows when multiple events land in the same millisecond.
|
|
10
|
+
*/
|
|
11
|
+
export declare class SupabaseAuditQuery implements IAuditQuery {
|
|
12
|
+
private readonly clients;
|
|
13
|
+
constructor(clients: ClientBundle);
|
|
14
|
+
list(_ctx: RequestContext, filters: AuditQueryFilters): Promise<AuditQueryResult>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=SupabaseAuditQuery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SupabaseAuditQuery.d.ts","sourceRoot":"","sources":["../../src/data/SupabaseAuditQuery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEX,iBAAiB,EACjB,gBAAgB,EAEhB,WAAW,EACX,cAAc,EACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAUhD;;;;;;;GAOG;AACH,qBAAa,kBAAmB,YAAW,WAAW;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,YAAY;IAE5C,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAmEvF"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const MAX_LIMIT = 200;
|
|
2
|
+
const DEFAULT_LIMIT = 100;
|
|
3
|
+
// Cursor values are interpolated into PostgREST filter strings — both formats
|
|
4
|
+
// must be validated before any string concatenation reaches the query.
|
|
5
|
+
const ISO_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})$/;
|
|
6
|
+
const UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
7
|
+
/**
|
|
8
|
+
* Read-side for `audit_events`. Always uses the service-role client — RLS
|
|
9
|
+
* blocks all read access by design, so the route layer is the single trust
|
|
10
|
+
* boundary (callers must gate on `instance_admin`).
|
|
11
|
+
*
|
|
12
|
+
* Pagination is keyset on `(occurred_at desc, id desc)`. The `id` tie-breaker
|
|
13
|
+
* prevents skipped rows when multiple events land in the same millisecond.
|
|
14
|
+
*/
|
|
15
|
+
export class SupabaseAuditQuery {
|
|
16
|
+
clients;
|
|
17
|
+
constructor(clients) {
|
|
18
|
+
this.clients = clients;
|
|
19
|
+
}
|
|
20
|
+
async list(_ctx, filters) {
|
|
21
|
+
const limit = clampLimit(filters.limit);
|
|
22
|
+
let q = this.clients.serviceClient
|
|
23
|
+
.from('audit_events')
|
|
24
|
+
.select('id, type, actor_id, occurred_at, data')
|
|
25
|
+
.order('occurred_at', { ascending: false })
|
|
26
|
+
.order('id', { ascending: false })
|
|
27
|
+
// Fetch one extra row to detect "has next page" without a count query.
|
|
28
|
+
.limit(limit + 1);
|
|
29
|
+
if (filters.types && filters.types.length > 0) {
|
|
30
|
+
q = q.in('type', [...filters.types]);
|
|
31
|
+
}
|
|
32
|
+
if (filters.actorId) {
|
|
33
|
+
q = q.eq('actor_id', filters.actorId);
|
|
34
|
+
}
|
|
35
|
+
if (filters.sinceIso) {
|
|
36
|
+
q = q.gte('occurred_at', filters.sinceIso);
|
|
37
|
+
}
|
|
38
|
+
if (filters.untilIso) {
|
|
39
|
+
q = q.lte('occurred_at', filters.untilIso);
|
|
40
|
+
}
|
|
41
|
+
if (filters.cursor) {
|
|
42
|
+
// `(occurred_at, id) < (cursorTs, cursorId)` expressed for PostgREST:
|
|
43
|
+
// strictly older OR same timestamp with a strictly smaller id.
|
|
44
|
+
//
|
|
45
|
+
// Both values are interpolated into a `.or()` filter string. Validate
|
|
46
|
+
// shape before interpolation — without these guards a malformed cursor
|
|
47
|
+
// (or a hand-crafted URL by a route caller) could break out of the
|
|
48
|
+
// filter and combine with arbitrary PostgREST operators.
|
|
49
|
+
const ts = filters.cursor.occurredAt;
|
|
50
|
+
const id = filters.cursor.id;
|
|
51
|
+
if (!ISO_RE.test(ts) || !UUID_RE.test(id)) {
|
|
52
|
+
throw new Error('audit_events query: malformed cursor');
|
|
53
|
+
}
|
|
54
|
+
q = q.or(`occurred_at.lt.${ts},and(occurred_at.eq.${ts},id.lt.${id})`);
|
|
55
|
+
}
|
|
56
|
+
const { data, error } = await q;
|
|
57
|
+
if (error) {
|
|
58
|
+
throw new Error(`audit_events query failed: ${error.message}`);
|
|
59
|
+
}
|
|
60
|
+
const fetched = (data ?? []);
|
|
61
|
+
const hasMore = fetched.length > limit;
|
|
62
|
+
const page = hasMore ? fetched.slice(0, limit) : fetched;
|
|
63
|
+
const last = page[page.length - 1];
|
|
64
|
+
return {
|
|
65
|
+
rows: page.map((r) => ({
|
|
66
|
+
id: r.id,
|
|
67
|
+
type: r.type,
|
|
68
|
+
actorId: r.actor_id,
|
|
69
|
+
occurredAt: r.occurred_at,
|
|
70
|
+
data: r.data
|
|
71
|
+
})),
|
|
72
|
+
nextCursor: hasMore && last ? { occurredAt: last.occurred_at, id: last.id } : null
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function clampLimit(requested) {
|
|
77
|
+
if (!requested || requested <= 0)
|
|
78
|
+
return DEFAULT_LIMIT;
|
|
79
|
+
if (requested > MAX_LIMIT)
|
|
80
|
+
return MAX_LIMIT;
|
|
81
|
+
return Math.floor(requested);
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=SupabaseAuditQuery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SupabaseAuditQuery.js","sourceRoot":"","sources":["../../src/data/SupabaseAuditQuery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type IComputeServerStore, type ComputeConfig, type ComputeServerConfig, type RequestContext } from '@selvajs/platform';
|
|
2
|
+
import type { ClientBundle } from './client.js';
|
|
3
|
+
/**
|
|
4
|
+
* Compute-server config backed by three tables:
|
|
5
|
+
* - `compute_servers`: one row per server. `scope = 'platform' | 'org'`.
|
|
6
|
+
* Platform rows carry a `shared_with_all` flag + `compute_server_shares`
|
|
7
|
+
* allowlist; org rows carry `owner_org_id`.
|
|
8
|
+
* - `compute_server_shares`: many-to-many for platform servers shared
|
|
9
|
+
* with specific orgs (when `shared_with_all = false`).
|
|
10
|
+
* - `compute_server_org_defaults`: per-org `orgDefaults[orgId]` choice.
|
|
11
|
+
* - `compute_server_platform_default`: single-row sentinel for the global
|
|
12
|
+
* `defaultServerId`.
|
|
13
|
+
*
|
|
14
|
+
* `getConfig` returns the full doc; the visibility predicate is applied by
|
|
15
|
+
* callers (resolver, page loaders) using helpers in `@selvajs/platform`.
|
|
16
|
+
*/
|
|
17
|
+
export declare class SupabaseComputeServerStore implements IComputeServerStore {
|
|
18
|
+
private readonly clients;
|
|
19
|
+
constructor(clients: ClientBundle);
|
|
20
|
+
getConfig(ctx: RequestContext): Promise<ComputeConfig>;
|
|
21
|
+
savePlatformServers(ctx: RequestContext, servers: ComputeServerConfig[], defaultServerId: string | undefined): Promise<void>;
|
|
22
|
+
saveOrgServers(ctx: RequestContext, orgId: string, servers: ComputeServerConfig[], defaultServerId?: string | null): Promise<void>;
|
|
23
|
+
setOrgDefault(ctx: RequestContext, orgId: string, serverId: string | null): Promise<void>;
|
|
24
|
+
deleteByOrg(ctx: RequestContext, orgId: string): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=SupabaseComputeServerStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SupabaseComputeServerStore.d.ts","sourceRoot":"","sources":["../../src/data/SupabaseComputeServerStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAGN,KAAK,mBAAmB,EACxB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;;;;;;;;GAaG;AACH,qBAAa,0BAA2B,YAAW,mBAAmB;IACzD,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,YAAY;IAE5C,SAAS,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAyCtD,mBAAmB,CACxB,GAAG,EAAE,cAAc,EACnB,OAAO,EAAE,mBAAmB,EAAE,EAC9B,eAAe,EAAE,MAAM,GAAG,SAAS,GACjC,OAAO,CAAC,IAAI,CAAC;IAiCV,cAAc,CACnB,GAAG,EAAE,cAAc,EACnB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,mBAAmB,EAAE,EAC9B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,GAC7B,OAAO,CAAC,IAAI,CAAC;IAgCV,aAAa,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBzF,WAAW,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CA0BpE"}
|