@selvajs/supabase-provider 0.14.4-beta.0 → 0.15.0-beta.2
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 +1 -1
- package/README.md +2 -0
- package/dist/auth/SupabaseAuthProvider.d.ts.map +1 -1
- package/dist/auth/SupabaseAuthProvider.js +7 -14
- package/dist/auth/SupabaseAuthProvider.js.map +1 -1
- package/dist/data/SupabaseComputeServerStore.d.ts +14 -3
- package/dist/data/SupabaseComputeServerStore.d.ts.map +1 -1
- package/dist/data/SupabaseComputeServerStore.js +67 -16
- package/dist/data/SupabaseComputeServerStore.js.map +1 -1
- package/dist/data/SupabaseDataProvider.d.ts +36 -8
- package/dist/data/SupabaseDataProvider.d.ts.map +1 -1
- package/dist/data/SupabaseDataProvider.js +119 -15
- package/dist/data/SupabaseDataProvider.js.map +1 -1
- package/dist/data/SupabaseDefinitionStore.d.ts +1 -0
- package/dist/data/SupabaseDefinitionStore.d.ts.map +1 -1
- package/dist/data/SupabaseDefinitionStore.js +41 -4
- package/dist/data/SupabaseDefinitionStore.js.map +1 -1
- package/dist/data/SupabaseEventSink.d.ts +5 -2
- package/dist/data/SupabaseEventSink.d.ts.map +1 -1
- package/dist/data/SupabaseEventSink.js +20 -3
- package/dist/data/SupabaseEventSink.js.map +1 -1
- package/dist/data/SupabaseSolveMetricSink.d.ts +49 -4
- package/dist/data/SupabaseSolveMetricSink.d.ts.map +1 -1
- package/dist/data/SupabaseSolveMetricSink.js +109 -19
- package/dist/data/SupabaseSolveMetricSink.js.map +1 -1
- package/dist/data/index.d.ts +1 -0
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/index.js +1 -0
- package/dist/data/index.js.map +1 -1
- package/dist/data/migrationHead.d.ts +14 -0
- package/dist/data/migrationHead.d.ts.map +1 -0
- package/dist/data/migrationHead.js +14 -0
- package/dist/data/migrationHead.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/permissions/SupabasePlatformPermissionStore.d.ts +5 -3
- package/dist/permissions/SupabasePlatformPermissionStore.d.ts.map +1 -1
- package/dist/permissions/SupabasePlatformPermissionStore.js +17 -30
- package/dist/permissions/SupabasePlatformPermissionStore.js.map +1 -1
- package/dist/storage/SupabaseStorageProvider.d.ts.map +1 -1
- package/dist/storage/SupabaseStorageProvider.js +3 -0
- package/dist/storage/SupabaseStorageProvider.js.map +1 -1
- package/package.json +23 -14
- package/supabase/migrations/20260712120000_user_profiles_disabled_and_gin.sql +95 -0
- package/supabase/migrations/20260712123000_selva_migration_head_fn.sql +38 -0
- package/supabase/migrations/20260713120000_selva_next_version_number.sql +59 -0
- package/supabase/migrations/20260713140000_selva_solve_cache_limit.sql +15 -0
- package/supabase/migrations/20260716120000_definition_status_and_audit_version.sql +36 -0
- package/supabase/migrations/20260716130000_erase_user_audit_email.sql +36 -0
- package/supabase/migrations/20260717120000_compute_server_has_api_key.sql +20 -0
- package/dist/auth/__tests__/conformance.test.d.ts +0 -2
- package/dist/auth/__tests__/conformance.test.d.ts.map +0 -1
- package/dist/auth/__tests__/conformance.test.js +0 -76
- package/dist/auth/__tests__/conformance.test.js.map +0 -1
- package/dist/auth/__tests__/token-verification.test.d.ts +0 -13
- package/dist/auth/__tests__/token-verification.test.d.ts.map +0 -1
- package/dist/auth/__tests__/token-verification.test.js +0 -135
- package/dist/auth/__tests__/token-verification.test.js.map +0 -1
- package/dist/data/__tests__/compute-server-conformance.test.d.ts +0 -2
- package/dist/data/__tests__/compute-server-conformance.test.d.ts.map +0 -1
- package/dist/data/__tests__/compute-server-conformance.test.js +0 -45
- package/dist/data/__tests__/compute-server-conformance.test.js.map +0 -1
- package/dist/data/__tests__/compute-server-encryption.test.d.ts +0 -12
- package/dist/data/__tests__/compute-server-encryption.test.d.ts.map +0 -1
- package/dist/data/__tests__/compute-server-encryption.test.js +0 -143
- package/dist/data/__tests__/compute-server-encryption.test.js.map +0 -1
- package/dist/data/__tests__/definition-conformance.test.d.ts +0 -2
- package/dist/data/__tests__/definition-conformance.test.d.ts.map +0 -1
- package/dist/data/__tests__/definition-conformance.test.js +0 -85
- package/dist/data/__tests__/definition-conformance.test.js.map +0 -1
- package/dist/data/__tests__/event-sink-conformance.test.d.ts +0 -2
- package/dist/data/__tests__/event-sink-conformance.test.d.ts.map +0 -1
- package/dist/data/__tests__/event-sink-conformance.test.js +0 -27
- package/dist/data/__tests__/event-sink-conformance.test.js.map +0 -1
- package/dist/data/__tests__/invite-conformance.test.d.ts +0 -2
- package/dist/data/__tests__/invite-conformance.test.d.ts.map +0 -1
- package/dist/data/__tests__/invite-conformance.test.js +0 -45
- package/dist/data/__tests__/invite-conformance.test.js.map +0 -1
- package/dist/data/__tests__/org-conformance.test.d.ts +0 -2
- package/dist/data/__tests__/org-conformance.test.d.ts.map +0 -1
- package/dist/data/__tests__/org-conformance.test.js +0 -29
- package/dist/data/__tests__/org-conformance.test.js.map +0 -1
- package/dist/data/__tests__/project-conformance.test.d.ts +0 -2
- package/dist/data/__tests__/project-conformance.test.d.ts.map +0 -1
- package/dist/data/__tests__/project-conformance.test.js +0 -57
- package/dist/data/__tests__/project-conformance.test.js.map +0 -1
- package/dist/data/__tests__/share-link-concurrency.test.d.ts +0 -22
- package/dist/data/__tests__/share-link-concurrency.test.d.ts.map +0 -1
- package/dist/data/__tests__/share-link-concurrency.test.js +0 -115
- package/dist/data/__tests__/share-link-concurrency.test.js.map +0 -1
- package/dist/data/__tests__/share-link-conformance.test.d.ts +0 -2
- package/dist/data/__tests__/share-link-conformance.test.d.ts.map +0 -1
- package/dist/data/__tests__/share-link-conformance.test.js +0 -88
- package/dist/data/__tests__/share-link-conformance.test.js.map +0 -1
- package/dist/data/__tests__/solve-metric-sink-conformance.test.d.ts +0 -2
- package/dist/data/__tests__/solve-metric-sink-conformance.test.d.ts.map +0 -1
- package/dist/data/__tests__/solve-metric-sink-conformance.test.js +0 -52
- package/dist/data/__tests__/solve-metric-sink-conformance.test.js.map +0 -1
- package/dist/data/__tests__/test-helpers.d.ts +0 -52
- package/dist/data/__tests__/test-helpers.d.ts.map +0 -1
- package/dist/data/__tests__/test-helpers.js +0 -177
- package/dist/data/__tests__/test-helpers.js.map +0 -1
- package/dist/permissions/__tests__/conformance.test.d.ts +0 -2
- package/dist/permissions/__tests__/conformance.test.d.ts.map +0 -1
- package/dist/permissions/__tests__/conformance.test.js +0 -27
- package/dist/permissions/__tests__/conformance.test.js.map +0 -1
- package/dist/storage/__tests__/conformance.test.d.ts +0 -2
- package/dist/storage/__tests__/conformance.test.d.ts.map +0 -1
- package/dist/storage/__tests__/conformance.test.js +0 -149
- package/dist/storage/__tests__/conformance.test.js.map +0 -1
- package/dist/userProfile/__tests__/conformance.test.d.ts +0 -2
- package/dist/userProfile/__tests__/conformance.test.d.ts.map +0 -1
- package/dist/userProfile/__tests__/conformance.test.js +0 -38
- package/dist/userProfile/__tests__/conformance.test.js.map +0 -1
- package/src/auth/SupabaseAuthProvider.ts +0 -600
- package/src/auth/__tests__/conformance.test.ts +0 -78
- package/src/auth/__tests__/token-verification.test.ts +0 -158
- package/src/auth/index.ts +0 -2
- package/src/cli/sync-migrations.ts +0 -133
- package/src/data/SupabaseAuditQuery.ts +0 -103
- package/src/data/SupabaseComputeServerStore.ts +0 -382
- package/src/data/SupabaseDataProvider.ts +0 -153
- package/src/data/SupabaseDefinitionStore.ts +0 -527
- package/src/data/SupabaseEventSink.ts +0 -30
- package/src/data/SupabaseInviteStore.ts +0 -171
- package/src/data/SupabaseOrgStore.ts +0 -508
- package/src/data/SupabaseProjectStore.ts +0 -431
- package/src/data/SupabaseShareLinkStore.ts +0 -176
- package/src/data/SupabaseSolveMetricSink.ts +0 -38
- package/src/data/__tests__/compute-server-conformance.test.ts +0 -46
- package/src/data/__tests__/compute-server-encryption.test.ts +0 -173
- package/src/data/__tests__/definition-conformance.test.ts +0 -89
- package/src/data/__tests__/event-sink-conformance.test.ts +0 -32
- package/src/data/__tests__/invite-conformance.test.ts +0 -46
- package/src/data/__tests__/org-conformance.test.ts +0 -30
- package/src/data/__tests__/project-conformance.test.ts +0 -57
- package/src/data/__tests__/share-link-concurrency.test.ts +0 -124
- package/src/data/__tests__/share-link-conformance.test.ts +0 -87
- package/src/data/__tests__/solve-metric-sink-conformance.test.ts +0 -59
- package/src/data/__tests__/test-helpers.ts +0 -188
- package/src/data/client.ts +0 -173
- package/src/data/errors.ts +0 -45
- package/src/data/index.ts +0 -29
- package/src/data/pagination.ts +0 -68
- package/src/data/query.ts +0 -22
- package/src/data/rowStamp.ts +0 -48
- package/src/index.ts +0 -42
- package/src/permissions/SupabasePlatformPermissionStore.ts +0 -161
- package/src/permissions/__tests__/conformance.test.ts +0 -28
- package/src/permissions/index.ts +0 -1
- package/src/storage/SupabaseStorageProvider.ts +0 -182
- package/src/storage/__tests__/conformance.test.ts +0 -152
- package/src/storage/index.ts +0 -2
- package/src/userProfile/SupabaseUserProfileProvider.ts +0 -177
- package/src/userProfile/__tests__/conformance.test.ts +0 -39
- package/src/userProfile/index.ts +0 -1
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { describe, beforeEach, it } from 'vitest';
|
|
2
|
-
import { runSolveMetricSinkConformance, type RecordedSolveMetric } from '@selvajs/platform/testing';
|
|
3
|
-
import { SupabaseSolveMetricSink } from '../SupabaseSolveMetricSink.js';
|
|
4
|
-
import { buildClientBundle } from '../client.js';
|
|
5
|
-
import { readEnv, resetAllData } from './test-helpers.js';
|
|
6
|
-
|
|
7
|
-
const envCtx = readEnv();
|
|
8
|
-
|
|
9
|
-
if (!envCtx) {
|
|
10
|
-
describe.skip('SupabaseSolveMetricSink (skipped: no live stack)', () => {
|
|
11
|
-
it('populate packages/providers/supabase/.env.test with Supabase creds to run these tests', () => {});
|
|
12
|
-
});
|
|
13
|
-
} else {
|
|
14
|
-
const ctx = envCtx;
|
|
15
|
-
describe('SupabaseSolveMetricSink', () => {
|
|
16
|
-
beforeEach(async () => {
|
|
17
|
-
await resetAllData(ctx);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
runSolveMetricSinkConformance({
|
|
21
|
-
name: 'SupabaseSolveMetricSink',
|
|
22
|
-
createSink: () => new SupabaseSolveMetricSink(ctx.bundle),
|
|
23
|
-
readRows: async () => {
|
|
24
|
-
const { data, error } = await ctx.adminClient
|
|
25
|
-
.from('solve_metrics')
|
|
26
|
-
.select(
|
|
27
|
-
'actor_id, definition_url, definition_id, version_id, channel, org_id, duration_ms, ok, failure_kind, error_count, warning_count'
|
|
28
|
-
)
|
|
29
|
-
.order('occurred_at', { ascending: true });
|
|
30
|
-
if (error) throw new Error(`solve_metrics read failed: ${error.message}`);
|
|
31
|
-
return (data ?? []).map(
|
|
32
|
-
(r): RecordedSolveMetric => ({
|
|
33
|
-
actorId: r.actor_id,
|
|
34
|
-
definitionUrl: r.definition_url,
|
|
35
|
-
definitionId: r.definition_id,
|
|
36
|
-
versionId: r.version_id,
|
|
37
|
-
channel: r.channel,
|
|
38
|
-
orgId: r.org_id,
|
|
39
|
-
durationMs: r.duration_ms,
|
|
40
|
-
ok: r.ok,
|
|
41
|
-
failureKind: r.failure_kind,
|
|
42
|
-
errorCount: r.error_count,
|
|
43
|
-
warningCount: r.warning_count
|
|
44
|
-
})
|
|
45
|
-
);
|
|
46
|
-
},
|
|
47
|
-
// Point the sink at a bundle whose service key is bogus, so the insert
|
|
48
|
-
// fails. record() must still resolve (contract: never throw).
|
|
49
|
-
createFailingSink: () =>
|
|
50
|
-
new SupabaseSolveMetricSink(
|
|
51
|
-
buildClientBundle({
|
|
52
|
-
supabaseUrl: ctx.url,
|
|
53
|
-
anonKey: ctx.anonKey,
|
|
54
|
-
serviceRoleKey: 'invalid-service-role-key'
|
|
55
|
-
})
|
|
56
|
-
)
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
}
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared test helpers for Supabase data conformance tests.
|
|
3
|
-
*
|
|
4
|
-
* Every suite starts with a fully wiped DB so tests are isolated. We use the
|
|
5
|
-
* service-role client for both — seeding users in `auth.users` and wiping
|
|
6
|
-
* every relevant table.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { createClient } from '@supabase/supabase-js';
|
|
10
|
-
import { buildClientBundle, type ClientBundle, type SelvaSchemaClient } from '../client.js';
|
|
11
|
-
|
|
12
|
-
export interface TestContext {
|
|
13
|
-
url: string;
|
|
14
|
-
anonKey: string;
|
|
15
|
-
serviceRoleKey: string;
|
|
16
|
-
adminClient: SelvaSchemaClient;
|
|
17
|
-
bundle: ClientBundle;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function readEnv(): TestContext | null {
|
|
21
|
-
const url = process.env.SUPABASE_URL;
|
|
22
|
-
const anonKey = process.env.SUPABASE_ANON_KEY;
|
|
23
|
-
const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
|
24
|
-
if (!url || !anonKey || !serviceRoleKey) return null;
|
|
25
|
-
// `.env.test` is checked in with local-Supabase defaults so contributors can
|
|
26
|
-
// `npx supabase start` and run the suite. But `createClient` transitively
|
|
27
|
-
// loads `@supabase/realtime-js`, which on Node < 22 throws at construction
|
|
28
|
-
// without a native WebSocket. Swallow that so the suite cleanly skips
|
|
29
|
-
// instead of crashing test collection.
|
|
30
|
-
let adminClient: SelvaSchemaClient;
|
|
31
|
-
try {
|
|
32
|
-
adminClient = createClient(url, serviceRoleKey, {
|
|
33
|
-
db: { schema: 'selva' },
|
|
34
|
-
auth: { persistSession: false, autoRefreshToken: false }
|
|
35
|
-
});
|
|
36
|
-
} catch (err) {
|
|
37
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
38
|
-
if (msg.includes('WebSocket')) return null;
|
|
39
|
-
throw err;
|
|
40
|
-
}
|
|
41
|
-
const bundle = buildClientBundle({ supabaseUrl: url, anonKey, serviceRoleKey });
|
|
42
|
-
return { url, anonKey, serviceRoleKey, adminClient, bundle };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Reset every data table this adapter owns. Leaves Supabase-managed tables
|
|
47
|
-
* (`auth.users`, etc.) alone except via `deleteAllAuthUsers` below.
|
|
48
|
-
*
|
|
49
|
-
* Order-sensitive because some FKs don't cascade: we go bottom up from
|
|
50
|
-
* dependents to parents.
|
|
51
|
-
*/
|
|
52
|
-
export async function resetAllData(ctx: TestContext): Promise<void> {
|
|
53
|
-
await truncate(ctx, 'solve_metrics');
|
|
54
|
-
await truncate(ctx, 'share_links');
|
|
55
|
-
await truncate(ctx, 'invites');
|
|
56
|
-
// Versions FK to definitions ON DELETE CASCADE, but `definitions` can't
|
|
57
|
-
// be wiped while live/draft pointers reference versions (ON DELETE
|
|
58
|
-
// RESTRICT, spec §6). Null the pointers, drop versions, then drop defs.
|
|
59
|
-
await ctx.adminClient
|
|
60
|
-
.from('definitions')
|
|
61
|
-
.update({ live_version_id: null, draft_version_id: null })
|
|
62
|
-
.not('guid', 'is', null);
|
|
63
|
-
await truncate(ctx, 'definition_versions');
|
|
64
|
-
await truncate(ctx, 'definitions');
|
|
65
|
-
await truncate(ctx, 'project_members');
|
|
66
|
-
// compute defaults have a platform_default sibling — reset all three.
|
|
67
|
-
await truncate(ctx, 'compute_server_org_defaults');
|
|
68
|
-
await truncate(ctx, 'compute_server_shares');
|
|
69
|
-
await resetComputePlatformDefault(ctx);
|
|
70
|
-
await truncate(ctx, 'compute_servers');
|
|
71
|
-
await truncate(ctx, 'org_members');
|
|
72
|
-
await truncate(ctx, 'orgs');
|
|
73
|
-
await truncate(ctx, 'user_profiles');
|
|
74
|
-
await deleteAllAuthUsers(ctx);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async function resetComputePlatformDefault(ctx: TestContext): Promise<void> {
|
|
78
|
-
// Singleton row — clear `default_server_id` rather than deleting.
|
|
79
|
-
const { error } = await ctx.adminClient
|
|
80
|
-
.from('compute_server_platform_default')
|
|
81
|
-
.update({ default_server_id: null })
|
|
82
|
-
.eq('singleton', true);
|
|
83
|
-
if (error) throw error;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async function truncate(ctx: TestContext, table: string): Promise<void> {
|
|
87
|
-
const { error } = await ctx.adminClient
|
|
88
|
-
.from(table)
|
|
89
|
-
.delete()
|
|
90
|
-
.neq(columnForDeleteAll(table), '00000000-0000-0000-0000-000000000000');
|
|
91
|
-
if (error) throw error;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function columnForDeleteAll(table: string): string {
|
|
95
|
-
if (table === 'org_members') return 'user_id';
|
|
96
|
-
if (table === 'project_members') return 'user_id';
|
|
97
|
-
if (table === 'user_profiles') return 'user_id';
|
|
98
|
-
if (table === 'definitions') return 'guid';
|
|
99
|
-
if (table === 'definition_versions') return 'id';
|
|
100
|
-
if (table === 'compute_server_org_defaults') return 'org_id';
|
|
101
|
-
if (table === 'compute_server_shares') return 'server_id';
|
|
102
|
-
return 'id';
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async function deleteAllAuthUsers(ctx: TestContext): Promise<void> {
|
|
106
|
-
const { data, error } = await ctx.adminClient.auth.admin.listUsers({ perPage: 1000 });
|
|
107
|
-
if (error) throw error;
|
|
108
|
-
for (const user of data.users) {
|
|
109
|
-
const { error: delError } = await ctx.adminClient.auth.admin.deleteUser(user.id);
|
|
110
|
-
if (delError) throw delError;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Seed an `auth.users` row, mark the user as `instance_admin`, sign in, and
|
|
116
|
-
* return the real id + access token. `auth.admin.createUser` doesn't accept a
|
|
117
|
-
* caller-supplied id — Supabase generates one. The conformance suite calls us
|
|
118
|
-
* with a *suggested* id (for adapters that honor it); we ignore it and return
|
|
119
|
-
* GoTrue's real id.
|
|
120
|
-
*
|
|
121
|
-
* The instance_admin grant is what makes RLS open up for the conformance
|
|
122
|
-
* tests: every policy in the schema accepts `is_instance_admin()` as a
|
|
123
|
-
* universal pass, mirroring the `instance_admin` default in `makeCtx`.
|
|
124
|
-
*
|
|
125
|
-
* The session token is what makes RLS *see* the user at all. Without it, the
|
|
126
|
-
* Supabase client falls back to the anon role and every write fails.
|
|
127
|
-
*/
|
|
128
|
-
export async function seedUser(
|
|
129
|
-
ctx: TestContext,
|
|
130
|
-
suggestedId: string
|
|
131
|
-
): Promise<{ userId: string; sessionToken: string }> {
|
|
132
|
-
return seedUserCore(ctx, suggestedId, { promoteToInstanceAdmin: true });
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Variant of `seedUser` that leaves `platform_permissions` empty. Use this
|
|
137
|
-
* when a test specifically asserts on a fresh user's permission state (e.g.
|
|
138
|
-
* the platform-permission store conformance suite).
|
|
139
|
-
*/
|
|
140
|
-
export async function seedPlainUser(
|
|
141
|
-
ctx: TestContext,
|
|
142
|
-
suggestedId: string
|
|
143
|
-
): Promise<{ userId: string; sessionToken: string }> {
|
|
144
|
-
return seedUserCore(ctx, suggestedId, { promoteToInstanceAdmin: false });
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
async function seedUserCore(
|
|
148
|
-
ctx: TestContext,
|
|
149
|
-
_suggestedId: string,
|
|
150
|
-
opts: { promoteToInstanceAdmin: boolean }
|
|
151
|
-
): Promise<{ userId: string; sessionToken: string }> {
|
|
152
|
-
// Unique email per seed so repeated calls don't collide. GoTrue requires
|
|
153
|
-
// an email even for password-less users.
|
|
154
|
-
const email = `conformance-${crypto.randomUUID()}@conformance.test`;
|
|
155
|
-
const password = 'conformance-test-password-1234';
|
|
156
|
-
const { data, error } = await ctx.adminClient.auth.admin.createUser({
|
|
157
|
-
email,
|
|
158
|
-
password,
|
|
159
|
-
email_confirm: true
|
|
160
|
-
});
|
|
161
|
-
if (error) throw error;
|
|
162
|
-
const userId = data.user.id;
|
|
163
|
-
|
|
164
|
-
if (opts.promoteToInstanceAdmin) {
|
|
165
|
-
// Trigger auto-created the user_profiles row; promote to instance_admin
|
|
166
|
-
// so every RLS policy treats this user as fully authorized. Mirrors the
|
|
167
|
-
// `instance_admin` default in `makeCtx`.
|
|
168
|
-
const { error: promoteError } = await ctx.adminClient
|
|
169
|
-
.from('user_profiles')
|
|
170
|
-
.update({ platform_permissions: ['instance_admin'] })
|
|
171
|
-
.eq('user_id', userId);
|
|
172
|
-
if (promoteError) throw promoteError;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Sign in to get an access token. The conformance suite stuffs this into
|
|
176
|
-
// `adapterContext.sessionToken` and our client.ts uses it to scope the
|
|
177
|
-
// request to this user instead of falling back to anon.
|
|
178
|
-
const signInClient = createClient(ctx.url, ctx.anonKey, {
|
|
179
|
-
auth: { persistSession: false, autoRefreshToken: false }
|
|
180
|
-
});
|
|
181
|
-
const { data: session, error: signInError } = await signInClient.auth.signInWithPassword({
|
|
182
|
-
email,
|
|
183
|
-
password
|
|
184
|
-
});
|
|
185
|
-
if (signInError) throw signInError;
|
|
186
|
-
if (!session.session) throw new Error('seedUser: signInWithPassword returned no session');
|
|
187
|
-
return { userId, sessionToken: session.session.access_token };
|
|
188
|
-
}
|
package/src/data/client.ts
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import { createClient, type SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
-
import type { RequestContext } from '@selvajs/platform';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* All engine tables live in the `selva` schema (not `public`) so a consuming
|
|
6
|
-
* app keeps `public` for its own tables. Every data client targets `selva` via
|
|
7
|
-
* `db: { schema: 'selva' }`, which the supabase-js type system surfaces as the
|
|
8
|
-
* client's schema generic — hence this alias rather than the bare default
|
|
9
|
-
* `SupabaseClient` (which is pinned to `'public'`).
|
|
10
|
-
*/
|
|
11
|
-
export type SelvaSchemaClient = SupabaseClient<any, 'public', 'selva'>;
|
|
12
|
-
|
|
13
|
-
/** Default schema every engine store targets. */
|
|
14
|
-
export const DEFAULT_SCHEMA = 'selva' as const;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* A client pinned to a caller-chosen schema. When the schema is `'selva'` this
|
|
18
|
-
* is the narrow `SelvaSchemaClient`; for an app-owned schema (e.g. `'public'`)
|
|
19
|
-
* the schema generic is opaque — the store surface (`.from` / `.rpc`) is the
|
|
20
|
-
* same either way.
|
|
21
|
-
*/
|
|
22
|
-
export type SchemaClient = SupabaseClient<any, string, any>;
|
|
23
|
-
|
|
24
|
-
export interface ForRequestOptions {
|
|
25
|
-
/**
|
|
26
|
-
* Postgres schema this client targets. Defaults to `'selva'` (the engine's
|
|
27
|
-
* tables). Pass `'public'` (or another app schema) to build stores on the
|
|
28
|
-
* consuming app's own tables while keeping the same fail-closed RLS
|
|
29
|
-
* dispatch as the engine stores.
|
|
30
|
-
*/
|
|
31
|
-
schema?: string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Build a Supabase client for a given `RequestContext`.
|
|
36
|
-
*
|
|
37
|
-
* Three modes — fail-closed:
|
|
38
|
-
* - **Service-role** ONLY when `ctx.system === true`. Bypasses RLS. Used for
|
|
39
|
-
* admin paths, janitors, bootstrap, and any explicitly-trusted server
|
|
40
|
-
* flow. The system flag must never be derived from a user session.
|
|
41
|
-
* - **User-scoped** when `ctx.adapterContext.sessionToken` is a JWT: runs
|
|
42
|
-
* under `authenticated` role with `auth.uid()` from the token. RLS
|
|
43
|
-
* policies enforce per-user visibility.
|
|
44
|
-
* - **Anonymous** otherwise: runs under the `anon` role. RLS is active and
|
|
45
|
-
* will scope reads/writes accordingly. This is the safety net — any code
|
|
46
|
-
* that constructs a context without forwarding the session token AND
|
|
47
|
-
* without setting `system: true` is treated as untrusted.
|
|
48
|
-
*
|
|
49
|
-
* Previous versions fell back to service-role when the session token was
|
|
50
|
-
* absent — a fail-OPEN footgun: any synthetic ctx (e.g. share-token resolve)
|
|
51
|
-
* silently bypassed RLS. The current contract is explicit: opt into
|
|
52
|
-
* service-role with `system: true`, or you get RLS.
|
|
53
|
-
*
|
|
54
|
-
* Per-request WeakMap caching keeps a single client alive for the lifetime
|
|
55
|
-
* of a RequestContext — multiple store calls in one HTTP handler share one
|
|
56
|
-
* fetch connection without leaking auth across requests.
|
|
57
|
-
*/
|
|
58
|
-
export interface ClientBundle {
|
|
59
|
-
/** Long-lived service-role client, pinned to the `'selva'` schema. Bypasses RLS. */
|
|
60
|
-
serviceClient: SelvaSchemaClient;
|
|
61
|
-
/**
|
|
62
|
-
* Produce (or reuse) a client scoped to this request's identity, targeting
|
|
63
|
-
* `opts.schema` (default `'selva'`).
|
|
64
|
-
* - `ctx.system === true` → service-role (RLS bypassed).
|
|
65
|
-
* - `ctx.adapterContext.sessionToken` set → user-scoped (RLS active).
|
|
66
|
-
* - neither → anon client (RLS active, no `auth.uid()`).
|
|
67
|
-
*
|
|
68
|
-
* The schema pin and the RLS dispatch travel together: an app building
|
|
69
|
-
* stores on its own `'public'` tables gets the same fail-closed behavior as
|
|
70
|
-
* engine stores, just aimed at a different schema.
|
|
71
|
-
*/
|
|
72
|
-
forRequest(ctx: RequestContext, opts?: ForRequestOptions): SchemaClient;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export interface BuildClientOptions {
|
|
76
|
-
supabaseUrl: string;
|
|
77
|
-
anonKey: string;
|
|
78
|
-
serviceRoleKey: string;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function buildClientBundle(opts: BuildClientOptions): ClientBundle {
|
|
82
|
-
// Long-lived service-role + anon clients, one per schema. Both are stateless
|
|
83
|
-
// w.r.t. request identity, so a single instance is safe to share across all
|
|
84
|
-
// requests targeting the same schema.
|
|
85
|
-
const serviceBySchema = new Map<string, SchemaClient>();
|
|
86
|
-
const anonBySchema = new Map<string, SchemaClient>();
|
|
87
|
-
|
|
88
|
-
function serviceFor(schema: string): SchemaClient {
|
|
89
|
-
let client = serviceBySchema.get(schema);
|
|
90
|
-
if (!client) {
|
|
91
|
-
client = createClient(opts.supabaseUrl, opts.serviceRoleKey, {
|
|
92
|
-
db: { schema },
|
|
93
|
-
auth: { persistSession: false, autoRefreshToken: false }
|
|
94
|
-
});
|
|
95
|
-
serviceBySchema.set(schema, client);
|
|
96
|
-
}
|
|
97
|
-
return client;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function anonFor(schema: string): SchemaClient {
|
|
101
|
-
let client = anonBySchema.get(schema);
|
|
102
|
-
if (!client) {
|
|
103
|
-
client = createClient(opts.supabaseUrl, opts.anonKey, {
|
|
104
|
-
db: { schema },
|
|
105
|
-
auth: { persistSession: false, autoRefreshToken: false }
|
|
106
|
-
});
|
|
107
|
-
anonBySchema.set(schema, client);
|
|
108
|
-
}
|
|
109
|
-
return client;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// The `'selva'`-pinned service client is exposed directly on the bundle for
|
|
113
|
-
// engine code that reaches for RLS-bypassing reads without a request context.
|
|
114
|
-
const serviceClient = serviceFor(DEFAULT_SCHEMA) as SelvaSchemaClient;
|
|
115
|
-
|
|
116
|
-
// Per-request user-scoped clients, keyed first by context then by schema so
|
|
117
|
-
// a handler that touches both `'selva'` and an app schema reuses one client
|
|
118
|
-
// per (request, schema) without leaking auth across requests.
|
|
119
|
-
const perRequestCache = new WeakMap<RequestContext, Map<string, SchemaClient>>();
|
|
120
|
-
|
|
121
|
-
return {
|
|
122
|
-
serviceClient,
|
|
123
|
-
forRequest(ctx: RequestContext, forOpts?: ForRequestOptions): SchemaClient {
|
|
124
|
-
const schema = forOpts?.schema ?? DEFAULT_SCHEMA;
|
|
125
|
-
if (ctx.system) return serviceFor(schema);
|
|
126
|
-
|
|
127
|
-
const token = extractSessionToken(ctx.adapterContext);
|
|
128
|
-
if (!token) return anonFor(schema);
|
|
129
|
-
|
|
130
|
-
let bySchema = perRequestCache.get(ctx);
|
|
131
|
-
if (!bySchema) {
|
|
132
|
-
bySchema = new Map();
|
|
133
|
-
perRequestCache.set(ctx, bySchema);
|
|
134
|
-
}
|
|
135
|
-
const cached = bySchema.get(schema);
|
|
136
|
-
if (cached) return cached;
|
|
137
|
-
|
|
138
|
-
const client = createClient(opts.supabaseUrl, opts.anonKey, {
|
|
139
|
-
db: { schema },
|
|
140
|
-
auth: { persistSession: false, autoRefreshToken: false },
|
|
141
|
-
global: {
|
|
142
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
bySchema.set(schema, client);
|
|
146
|
-
return client;
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Build a `ClientBundle` straight from environment variables — the
|
|
153
|
-
* `SUPABASE_URL` / `SUPABASE_ANON_KEY` / `SUPABASE_SERVICE_ROLE_KEY` triple,
|
|
154
|
-
* nothing else. Deliberately does NOT require `SELVA_AT_REST_KEY`: that key
|
|
155
|
-
* only matters for compute-server secret encryption, which a consuming app
|
|
156
|
-
* building its own `'public'` stores never touches. `SupabaseDataProvider.fromEnv`
|
|
157
|
-
* layers the at-rest-key requirement on top of this.
|
|
158
|
-
*/
|
|
159
|
-
export function clientBundleFromEnv(env: Record<string, string | undefined>): ClientBundle {
|
|
160
|
-
const supabaseUrl = env.SUPABASE_URL;
|
|
161
|
-
const anonKey = env.SUPABASE_ANON_KEY;
|
|
162
|
-
const serviceRoleKey = env.SUPABASE_SERVICE_ROLE_KEY;
|
|
163
|
-
if (!supabaseUrl) throw new Error('Missing required env var: SUPABASE_URL');
|
|
164
|
-
if (!anonKey) throw new Error('Missing required env var: SUPABASE_ANON_KEY');
|
|
165
|
-
if (!serviceRoleKey) throw new Error('Missing required env var: SUPABASE_SERVICE_ROLE_KEY');
|
|
166
|
-
return buildClientBundle({ supabaseUrl, anonKey, serviceRoleKey });
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function extractSessionToken(adapterContext: unknown): string | undefined {
|
|
170
|
-
if (!adapterContext || typeof adapterContext !== 'object') return undefined;
|
|
171
|
-
const maybe = (adapterContext as { sessionToken?: unknown }).sessionToken;
|
|
172
|
-
return typeof maybe === 'string' && maybe.length > 0 ? maybe : undefined;
|
|
173
|
-
}
|
package/src/data/errors.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { ProviderError } from '@selvajs/platform';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Canonical PostgREST/Postgres error translation shared by every Supabase
|
|
5
|
-
* store. Replaces the six near-identical private `mapError` copies that had
|
|
6
|
-
* drifted into two variants (a "full" one preserving `details`/`hint`, and a
|
|
7
|
-
* slimmer message-only one) and none of which handled `PGRST116`.
|
|
8
|
-
*
|
|
9
|
-
* Mapping:
|
|
10
|
-
* - `PGRST116` (single-row selector matched zero rows) → 404. PostgREST
|
|
11
|
-
* raises this when `.single()` finds nothing; treating it as "not found"
|
|
12
|
-
* is almost always what a store's caller wants.
|
|
13
|
-
* - `23505` (unique violation) / `23503` (foreign-key violation) → 409.
|
|
14
|
-
* - Anything else → a plain `Error` (500 downstream via `api-errors`), with
|
|
15
|
-
* `details`/`hint`/`code` preserved on the error object for logs.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/** The subset of a `PostgrestError` this mapper inspects. */
|
|
19
|
-
interface PostgrestErrorShape {
|
|
20
|
-
code?: string;
|
|
21
|
-
message?: string;
|
|
22
|
-
details?: string;
|
|
23
|
-
hint?: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function mapPostgrestError(e: unknown): Error {
|
|
27
|
-
const pg = e as PostgrestErrorShape;
|
|
28
|
-
switch (pg?.code) {
|
|
29
|
-
case 'PGRST116':
|
|
30
|
-
return new ProviderError(pg.message ?? 'Not found', 404);
|
|
31
|
-
case '23505':
|
|
32
|
-
return new ProviderError(pg.message ?? 'Duplicate record', 409);
|
|
33
|
-
case '23503':
|
|
34
|
-
return new ProviderError(pg.message ?? 'Foreign key violation', 409);
|
|
35
|
-
}
|
|
36
|
-
if (e instanceof Error) return e;
|
|
37
|
-
if (e && typeof e === 'object') {
|
|
38
|
-
const obj = e as PostgrestErrorShape;
|
|
39
|
-
const msg = obj.message ?? obj.details ?? obj.hint ?? 'Unknown Postgres error';
|
|
40
|
-
const err = new Error(obj.code ? `[${obj.code}] ${msg}` : msg);
|
|
41
|
-
Object.assign(err, obj);
|
|
42
|
-
return err;
|
|
43
|
-
}
|
|
44
|
-
return new Error(String(e));
|
|
45
|
-
}
|
package/src/data/index.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export { SupabaseOrgStore } from './SupabaseOrgStore.js';
|
|
2
|
-
export { SupabaseProjectStore } from './SupabaseProjectStore.js';
|
|
3
|
-
export { SupabaseDefinitionStore } from './SupabaseDefinitionStore.js';
|
|
4
|
-
export { SupabaseInviteStore } from './SupabaseInviteStore.js';
|
|
5
|
-
export { SupabaseComputeServerStore } from './SupabaseComputeServerStore.js';
|
|
6
|
-
export { SupabaseShareLinkStore } from './SupabaseShareLinkStore.js';
|
|
7
|
-
export { SupabaseDataProvider } from './SupabaseDataProvider.js';
|
|
8
|
-
export { SupabaseEventSink } from './SupabaseEventSink.js';
|
|
9
|
-
export { SupabaseSolveMetricSink } from './SupabaseSolveMetricSink.js';
|
|
10
|
-
export { SupabaseAuditQuery } from './SupabaseAuditQuery.js';
|
|
11
|
-
export { buildClientBundle, clientBundleFromEnv, DEFAULT_SCHEMA } from './client.js';
|
|
12
|
-
export type {
|
|
13
|
-
ClientBundle,
|
|
14
|
-
BuildClientOptions,
|
|
15
|
-
ForRequestOptions,
|
|
16
|
-
SchemaClient,
|
|
17
|
-
SelvaSchemaClient
|
|
18
|
-
} from './client.js';
|
|
19
|
-
export { mapPostgrestError } from './errors.js';
|
|
20
|
-
export { stampUpdate, stampSoftDelete } from './rowStamp.js';
|
|
21
|
-
export { notDeleted } from './query.js';
|
|
22
|
-
export {
|
|
23
|
-
toRange,
|
|
24
|
-
orderColumn,
|
|
25
|
-
nextCursorFromRange,
|
|
26
|
-
encodeCursor,
|
|
27
|
-
decodeCursor
|
|
28
|
-
} from './pagination.js';
|
|
29
|
-
export type { RangeSpec } from './pagination.js';
|
package/src/data/pagination.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pagination helpers shared across Supabase stores.
|
|
3
|
-
*
|
|
4
|
-
* The platform contract specifies cursor pagination (`ListOptions.cursor`).
|
|
5
|
-
* Because Supabase/Postgres has no built-in opaque cursor format, we encode
|
|
6
|
-
* an offset as the cursor — simple, correct for stable sorts, and good
|
|
7
|
-
* enough for the volumes Selva deals with (admin/definition lists are
|
|
8
|
-
* bounded; nothing is infinite scroll over millions of rows).
|
|
9
|
-
*
|
|
10
|
-
* Keyset pagination would be faster at scale if it ever matters.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { DEFAULT_PAGE_LIMIT, MAX_PAGE_LIMIT } from '@selvajs/platform';
|
|
14
|
-
import type { ListOptions } from '@selvajs/platform';
|
|
15
|
-
|
|
16
|
-
export interface RangeSpec {
|
|
17
|
-
from: number;
|
|
18
|
-
to: number;
|
|
19
|
-
limit: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** Decode an opaque cursor string. Returns 0 for missing/invalid cursors. */
|
|
23
|
-
export function decodeCursor(cursor: string | undefined): number {
|
|
24
|
-
if (!cursor) return 0;
|
|
25
|
-
const n = parseInt(cursor, 10);
|
|
26
|
-
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function encodeCursor(offset: number): string {
|
|
30
|
-
return String(offset);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** Clamp limit + compute the PostgREST `range(from, to)` bounds. */
|
|
34
|
-
export function toRange(opts: Pick<ListOptions, 'limit' | 'cursor'> | undefined): RangeSpec {
|
|
35
|
-
const limit = Math.min(Math.max(opts?.limit ?? DEFAULT_PAGE_LIMIT, 1), MAX_PAGE_LIMIT);
|
|
36
|
-
const from = decodeCursor(opts?.cursor);
|
|
37
|
-
return { from, to: from + limit - 1, limit };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Build the `nextCursor` from the count returned by PostgREST. Returns
|
|
42
|
-
* undefined when there is no next page.
|
|
43
|
-
*/
|
|
44
|
-
export function nextCursorFromRange(
|
|
45
|
-
range: RangeSpec,
|
|
46
|
-
returnedCount: number,
|
|
47
|
-
totalCount: number | null | undefined
|
|
48
|
-
): string | undefined {
|
|
49
|
-
const consumed = range.from + returnedCount;
|
|
50
|
-
if (totalCount != null) {
|
|
51
|
-
return consumed < totalCount ? encodeCursor(consumed) : undefined;
|
|
52
|
-
}
|
|
53
|
-
// Without total, we can only infer "there might be more" if we got a full page.
|
|
54
|
-
return returnedCount >= range.limit ? encodeCursor(consumed) : undefined;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Map `ListOptions.orderBy` to a Postgres column name. */
|
|
58
|
-
export function orderColumn(orderBy: ListOptions['orderBy'] | undefined): string {
|
|
59
|
-
switch (orderBy) {
|
|
60
|
-
case 'name':
|
|
61
|
-
return 'name';
|
|
62
|
-
case 'updatedAt':
|
|
63
|
-
return 'updated_at';
|
|
64
|
-
case 'createdAt':
|
|
65
|
-
default:
|
|
66
|
-
return 'created_at';
|
|
67
|
-
}
|
|
68
|
-
}
|
package/src/data/query.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Small query-builder helpers shared across Supabase stores.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* The slice of a PostgREST filter builder these helpers touch: an `.is()`
|
|
7
|
-
* method that returns the same builder for chaining. Generic over the builder
|
|
8
|
-
* type so the caller's concrete builder (and its row generic) flows through.
|
|
9
|
-
*/
|
|
10
|
-
interface DeletableQuery {
|
|
11
|
-
is(column: string, value: null): this;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Apply the soft-delete filter `deleted_at IS NULL`. The `.is('deleted_at', null)`
|
|
16
|
-
* idiom is repeated across every store that soft-deletes; this names it.
|
|
17
|
-
*
|
|
18
|
-
* let q = notDeleted(client.from('projects').select(COLS));
|
|
19
|
-
*/
|
|
20
|
-
export function notDeleted<Q extends DeletableQuery>(query: Q): Q {
|
|
21
|
-
return query.is('deleted_at', null);
|
|
22
|
-
}
|
package/src/data/rowStamp.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import type { RequestContext } from '@selvajs/platform';
|
|
2
|
-
import { auditSoftDelete } from '@selvajs/platform';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Row-level (snake_case) audit-stamp helpers for Supabase stores.
|
|
6
|
-
*
|
|
7
|
-
* Platform's `auditUpdate` / `auditSoftDelete` emit camelCase *domain* fields
|
|
8
|
-
* (`updatedAt` / `updatedBy`), but Postgres rows are snake_case, so every store
|
|
9
|
-
* hand-translated them. These helpers own that translation once. Row-column
|
|
10
|
-
* naming is a provider concern, so they live here rather than in platform.
|
|
11
|
-
*
|
|
12
|
-
* Two deliberate rules, matched to how the engine's stores already write rows:
|
|
13
|
-
* - `updated_by` is written ONLY when `ctx.userId` is set. On system contexts
|
|
14
|
-
* it's the empty string, and the column FKs `auth.users(id)` with
|
|
15
|
-
* `ON DELETE SET NULL` — writing `''` would violate the FK, so it's omitted
|
|
16
|
-
* and the existing value is left untouched.
|
|
17
|
-
* - Plain updates DON'T stamp `updated_at`: a per-table `updated_at` trigger
|
|
18
|
-
* already sets it. Only the soft-delete stamp writes `updated_at` explicitly,
|
|
19
|
-
* so it matches `deleted_at` (a single timestamp for the deletion event).
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Stamp for a plain UPDATE: `{ updated_by }` when the context carries a user,
|
|
24
|
-
* `{}` otherwise (the DB trigger handles `updated_at`). Spread into the row:
|
|
25
|
-
* `const row = { ...patch, ...stampUpdate(ctx) }`.
|
|
26
|
-
*/
|
|
27
|
-
export function stampUpdate(ctx: RequestContext): { updated_by?: string } {
|
|
28
|
-
return ctx.userId ? { updated_by: ctx.userId } : {};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Stamp for a soft-delete: `{ deleted_at, updated_at, updated_by? }`. `updated_at`
|
|
33
|
-
* is set explicitly to equal `deleted_at`; `updated_by` follows the same
|
|
34
|
-
* omit-when-empty rule as {@link stampUpdate}. `fallbackActor` defaults to
|
|
35
|
-
* `ctx.userId` (matching the engine stores' `auditSoftDelete(ctx, ctx.userId)`).
|
|
36
|
-
*/
|
|
37
|
-
export function stampSoftDelete(
|
|
38
|
-
ctx: RequestContext,
|
|
39
|
-
fallbackActor: string | null | undefined = ctx.userId
|
|
40
|
-
): { deleted_at: string; updated_at: string; updated_by?: string } {
|
|
41
|
-
const stamp = auditSoftDelete(ctx, fallbackActor);
|
|
42
|
-
const row: { deleted_at: string; updated_at: string; updated_by?: string } = {
|
|
43
|
-
deleted_at: stamp.deletedAt,
|
|
44
|
-
updated_at: stamp.updatedAt
|
|
45
|
-
};
|
|
46
|
-
if (stamp.updatedBy) row.updated_by = stamp.updatedBy;
|
|
47
|
-
return row;
|
|
48
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
export { SupabaseStorageProvider } from './storage/SupabaseStorageProvider.js';
|
|
2
|
-
export type { SupabaseStorageProviderConfig } from './storage/SupabaseStorageProvider.js';
|
|
3
|
-
|
|
4
|
-
export {
|
|
5
|
-
SupabaseOrgStore,
|
|
6
|
-
SupabaseProjectStore,
|
|
7
|
-
SupabaseDefinitionStore,
|
|
8
|
-
SupabaseInviteStore,
|
|
9
|
-
SupabaseComputeServerStore,
|
|
10
|
-
SupabaseShareLinkStore,
|
|
11
|
-
SupabaseDataProvider,
|
|
12
|
-
SupabaseEventSink,
|
|
13
|
-
SupabaseSolveMetricSink,
|
|
14
|
-
SupabaseAuditQuery,
|
|
15
|
-
buildClientBundle,
|
|
16
|
-
clientBundleFromEnv,
|
|
17
|
-
DEFAULT_SCHEMA,
|
|
18
|
-
mapPostgrestError,
|
|
19
|
-
stampUpdate,
|
|
20
|
-
stampSoftDelete,
|
|
21
|
-
notDeleted,
|
|
22
|
-
toRange,
|
|
23
|
-
orderColumn,
|
|
24
|
-
nextCursorFromRange,
|
|
25
|
-
encodeCursor,
|
|
26
|
-
decodeCursor
|
|
27
|
-
} from './data/index.js';
|
|
28
|
-
export type {
|
|
29
|
-
ClientBundle,
|
|
30
|
-
BuildClientOptions,
|
|
31
|
-
ForRequestOptions,
|
|
32
|
-
SchemaClient,
|
|
33
|
-
SelvaSchemaClient,
|
|
34
|
-
RangeSpec
|
|
35
|
-
} from './data/index.js';
|
|
36
|
-
|
|
37
|
-
export { SupabaseUserProfileProvider } from './userProfile/index.js';
|
|
38
|
-
|
|
39
|
-
export { SupabaseAuthProvider } from './auth/index.js';
|
|
40
|
-
export type { SupabaseAuthProviderConfig } from './auth/index.js';
|
|
41
|
-
|
|
42
|
-
export { SupabasePlatformPermissionStore } from './permissions/index.js';
|