@bernierllc/database-adapter-postgresql 1.0.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/README.md +456 -0
- package/dist/PostgreSQLAdapter.d.ts +65 -0
- package/dist/PostgreSQLAdapter.js +252 -0
- package/dist/PostgreSQLTransaction.d.ts +46 -0
- package/dist/PostgreSQLTransaction.js +199 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +29 -0
- package/dist/migrations/001_initial_schema.d.ts +2 -0
- package/dist/migrations/001_initial_schema.js +91 -0
- package/dist/realtime/RealtimeManager.d.ts +76 -0
- package/dist/realtime/RealtimeManager.js +243 -0
- package/dist/rls/ClerkIntegration.d.ts +41 -0
- package/dist/rls/ClerkIntegration.js +161 -0
- package/dist/rls/RLSPolicyGenerator.d.ts +38 -0
- package/dist/rls/RLSPolicyGenerator.js +212 -0
- package/dist/search/FullTextSearchManager.d.ts +61 -0
- package/dist/search/FullTextSearchManager.js +265 -0
- package/package.json +65 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { PostgreSQLAdapter } from '../PostgreSQLAdapter';
|
|
2
|
+
/**
|
|
3
|
+
* Payload types for real-time events
|
|
4
|
+
*/
|
|
5
|
+
export interface RealtimePayload {
|
|
6
|
+
schema: string;
|
|
7
|
+
table: string;
|
|
8
|
+
commit_timestamp: string;
|
|
9
|
+
eventType: 'INSERT' | 'UPDATE' | 'DELETE';
|
|
10
|
+
new: Record<string, unknown>;
|
|
11
|
+
old: Record<string, unknown>;
|
|
12
|
+
errors: string[] | null;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Callbacks for real-time subscriptions
|
|
16
|
+
*/
|
|
17
|
+
export interface RealtimeCallbacks {
|
|
18
|
+
onInsert?: (payload: RealtimePayload) => void;
|
|
19
|
+
onUpdate?: (payload: RealtimePayload) => void;
|
|
20
|
+
onDelete?: (payload: RealtimePayload) => void;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Presence data for collaborative editing
|
|
24
|
+
*/
|
|
25
|
+
export interface PresenceData {
|
|
26
|
+
userId: string;
|
|
27
|
+
cursorPosition?: {
|
|
28
|
+
line: number;
|
|
29
|
+
column: number;
|
|
30
|
+
};
|
|
31
|
+
timestamp: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Manages Supabase real-time subscriptions for collaborative editing
|
|
35
|
+
*/
|
|
36
|
+
export declare class RealtimeManager {
|
|
37
|
+
private adapter;
|
|
38
|
+
private channels;
|
|
39
|
+
private logger?;
|
|
40
|
+
constructor(adapter: PostgreSQLAdapter);
|
|
41
|
+
/**
|
|
42
|
+
* Subscribe to table changes for collaborative editing
|
|
43
|
+
*/
|
|
44
|
+
subscribeToContent(contentId: string, callbacks: RealtimeCallbacks): () => void;
|
|
45
|
+
/**
|
|
46
|
+
* Subscribe to generic table changes
|
|
47
|
+
*/
|
|
48
|
+
subscribeToTable(table: string, filter: string | null, callbacks: RealtimeCallbacks): () => void;
|
|
49
|
+
/**
|
|
50
|
+
* Broadcast presence for collaborative editing
|
|
51
|
+
*/
|
|
52
|
+
broadcastPresence(contentId: string, userId: string, cursorPosition?: {
|
|
53
|
+
line: number;
|
|
54
|
+
column: number;
|
|
55
|
+
}): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Get current presence state for a channel
|
|
58
|
+
*/
|
|
59
|
+
getPresence(contentId: string): Record<string, PresenceData[]> | null;
|
|
60
|
+
/**
|
|
61
|
+
* Unsubscribe from a specific channel
|
|
62
|
+
*/
|
|
63
|
+
unsubscribe(contentId: string): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Unsubscribe from all channels
|
|
66
|
+
*/
|
|
67
|
+
unsubscribeAll(): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Get active channel count
|
|
70
|
+
*/
|
|
71
|
+
getActiveChannelCount(): number;
|
|
72
|
+
/**
|
|
73
|
+
* Check if a channel is active
|
|
74
|
+
*/
|
|
75
|
+
isChannelActive(contentId: string): boolean;
|
|
76
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.RealtimeManager = void 0;
|
|
11
|
+
/**
|
|
12
|
+
* Manages Supabase real-time subscriptions for collaborative editing
|
|
13
|
+
*/
|
|
14
|
+
class RealtimeManager {
|
|
15
|
+
constructor(adapter) {
|
|
16
|
+
this.adapter = adapter;
|
|
17
|
+
this.channels = new Map();
|
|
18
|
+
// Access logger from adapter (it's a public property but typed generically)
|
|
19
|
+
this.logger = adapter.logger;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Subscribe to table changes for collaborative editing
|
|
23
|
+
*/
|
|
24
|
+
subscribeToContent(contentId, callbacks) {
|
|
25
|
+
const supabase = this.adapter.getSupabaseClient();
|
|
26
|
+
if (!supabase) {
|
|
27
|
+
throw new Error('Supabase client not configured');
|
|
28
|
+
}
|
|
29
|
+
const channelName = `content:${contentId}`;
|
|
30
|
+
if (this.logger) {
|
|
31
|
+
this.logger.debug('Subscribing to real-time channel', {
|
|
32
|
+
channel: channelName,
|
|
33
|
+
contentId
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
const channel = supabase
|
|
37
|
+
.channel(channelName)
|
|
38
|
+
.on('postgres_changes', {
|
|
39
|
+
event: 'INSERT',
|
|
40
|
+
schema: 'public',
|
|
41
|
+
table: 'content_versions',
|
|
42
|
+
filter: `content_id=eq.${contentId}`
|
|
43
|
+
}, (payload) => {
|
|
44
|
+
if (callbacks.onInsert) {
|
|
45
|
+
callbacks.onInsert(payload);
|
|
46
|
+
}
|
|
47
|
+
if (this.logger) {
|
|
48
|
+
this.logger.debug('Real-time INSERT event', {
|
|
49
|
+
channel: channelName,
|
|
50
|
+
table: 'content_versions'
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
.on('postgres_changes', {
|
|
55
|
+
event: 'UPDATE',
|
|
56
|
+
schema: 'public',
|
|
57
|
+
table: 'content',
|
|
58
|
+
filter: `id=eq.${contentId}`
|
|
59
|
+
}, (payload) => {
|
|
60
|
+
if (callbacks.onUpdate) {
|
|
61
|
+
callbacks.onUpdate(payload);
|
|
62
|
+
}
|
|
63
|
+
if (this.logger) {
|
|
64
|
+
this.logger.debug('Real-time UPDATE event', {
|
|
65
|
+
channel: channelName,
|
|
66
|
+
table: 'content'
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
.on('postgres_changes', {
|
|
71
|
+
event: 'DELETE',
|
|
72
|
+
schema: 'public',
|
|
73
|
+
table: 'content',
|
|
74
|
+
filter: `id=eq.${contentId}`
|
|
75
|
+
}, (payload) => {
|
|
76
|
+
if (callbacks.onDelete) {
|
|
77
|
+
callbacks.onDelete(payload);
|
|
78
|
+
}
|
|
79
|
+
if (this.logger) {
|
|
80
|
+
this.logger.debug('Real-time DELETE event', {
|
|
81
|
+
channel: channelName,
|
|
82
|
+
table: 'content'
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
.subscribe();
|
|
87
|
+
this.channels.set(contentId, channel);
|
|
88
|
+
// Return unsubscribe function
|
|
89
|
+
return () => {
|
|
90
|
+
if (this.logger) {
|
|
91
|
+
this.logger.debug('Unsubscribing from real-time channel', {
|
|
92
|
+
channel: channelName,
|
|
93
|
+
contentId
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
channel.unsubscribe();
|
|
97
|
+
this.channels.delete(contentId);
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Subscribe to generic table changes
|
|
102
|
+
*/
|
|
103
|
+
subscribeToTable(table, filter, callbacks) {
|
|
104
|
+
const supabase = this.adapter.getSupabaseClient();
|
|
105
|
+
if (!supabase) {
|
|
106
|
+
throw new Error('Supabase client not configured');
|
|
107
|
+
}
|
|
108
|
+
const channelName = `table:${table}:${filter || 'all'}`;
|
|
109
|
+
if (this.logger) {
|
|
110
|
+
this.logger.debug('Subscribing to table changes', {
|
|
111
|
+
channel: channelName,
|
|
112
|
+
table,
|
|
113
|
+
filter
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
let channel = supabase.channel(channelName);
|
|
117
|
+
// Subscribe to INSERT events
|
|
118
|
+
if (callbacks.onInsert) {
|
|
119
|
+
channel = channel.on('postgres_changes', {
|
|
120
|
+
event: 'INSERT',
|
|
121
|
+
schema: 'public',
|
|
122
|
+
table,
|
|
123
|
+
...(filter ? { filter } : {})
|
|
124
|
+
}, (payload) => {
|
|
125
|
+
callbacks.onInsert(payload);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// Subscribe to UPDATE events
|
|
129
|
+
if (callbacks.onUpdate) {
|
|
130
|
+
channel = channel.on('postgres_changes', {
|
|
131
|
+
event: 'UPDATE',
|
|
132
|
+
schema: 'public',
|
|
133
|
+
table,
|
|
134
|
+
...(filter ? { filter } : {})
|
|
135
|
+
}, (payload) => {
|
|
136
|
+
callbacks.onUpdate(payload);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
// Subscribe to DELETE events
|
|
140
|
+
if (callbacks.onDelete) {
|
|
141
|
+
channel = channel.on('postgres_changes', {
|
|
142
|
+
event: 'DELETE',
|
|
143
|
+
schema: 'public',
|
|
144
|
+
table,
|
|
145
|
+
...(filter ? { filter } : {})
|
|
146
|
+
}, (payload) => {
|
|
147
|
+
callbacks.onDelete(payload);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
channel.subscribe();
|
|
151
|
+
this.channels.set(channelName, channel);
|
|
152
|
+
// Return unsubscribe function
|
|
153
|
+
return () => {
|
|
154
|
+
if (this.logger) {
|
|
155
|
+
this.logger.debug('Unsubscribing from table changes', {
|
|
156
|
+
channel: channelName
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
channel.unsubscribe();
|
|
160
|
+
this.channels.delete(channelName);
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Broadcast presence for collaborative editing
|
|
165
|
+
*/
|
|
166
|
+
async broadcastPresence(contentId, userId, cursorPosition) {
|
|
167
|
+
const supabase = this.adapter.getSupabaseClient();
|
|
168
|
+
if (!supabase) {
|
|
169
|
+
if (this.logger) {
|
|
170
|
+
this.logger.warn('Cannot broadcast presence: Supabase client not configured');
|
|
171
|
+
}
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const channel = this.channels.get(contentId);
|
|
175
|
+
if (!channel) {
|
|
176
|
+
throw new Error(`No channel found for content ${contentId}`);
|
|
177
|
+
}
|
|
178
|
+
const presenceData = {
|
|
179
|
+
userId,
|
|
180
|
+
cursorPosition,
|
|
181
|
+
timestamp: Date.now()
|
|
182
|
+
};
|
|
183
|
+
await channel.track(presenceData);
|
|
184
|
+
if (this.logger) {
|
|
185
|
+
this.logger.debug('Presence broadcasted', {
|
|
186
|
+
contentId,
|
|
187
|
+
userId,
|
|
188
|
+
cursorPosition
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Get current presence state for a channel
|
|
194
|
+
*/
|
|
195
|
+
getPresence(contentId) {
|
|
196
|
+
const channel = this.channels.get(contentId);
|
|
197
|
+
if (!channel) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
const state = channel.presenceState();
|
|
201
|
+
return state;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Unsubscribe from a specific channel
|
|
205
|
+
*/
|
|
206
|
+
async unsubscribe(contentId) {
|
|
207
|
+
const channel = this.channels.get(contentId);
|
|
208
|
+
if (channel) {
|
|
209
|
+
if (this.logger) {
|
|
210
|
+
this.logger.debug('Unsubscribing from channel', { contentId });
|
|
211
|
+
}
|
|
212
|
+
await channel.unsubscribe();
|
|
213
|
+
this.channels.delete(contentId);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Unsubscribe from all channels
|
|
218
|
+
*/
|
|
219
|
+
async unsubscribeAll() {
|
|
220
|
+
if (this.logger) {
|
|
221
|
+
this.logger.debug('Unsubscribing from all channels', {
|
|
222
|
+
count: this.channels.size
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
for (const [, channel] of this.channels) {
|
|
226
|
+
await channel.unsubscribe();
|
|
227
|
+
}
|
|
228
|
+
this.channels.clear();
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Get active channel count
|
|
232
|
+
*/
|
|
233
|
+
getActiveChannelCount() {
|
|
234
|
+
return this.channels.size;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Check if a channel is active
|
|
238
|
+
*/
|
|
239
|
+
isChannelActive(contentId) {
|
|
240
|
+
return this.channels.has(contentId);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
exports.RealtimeManager = RealtimeManager;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clerk JWT integration utilities for PostgreSQL RLS
|
|
3
|
+
*/
|
|
4
|
+
export declare class ClerkIntegration {
|
|
5
|
+
/**
|
|
6
|
+
* Generate SQL function for extracting Clerk user ID from JWT
|
|
7
|
+
*/
|
|
8
|
+
static generateClerkUserIdFunction(): string;
|
|
9
|
+
/**
|
|
10
|
+
* Generate SQL function for getting internal user ID from Clerk ID
|
|
11
|
+
*/
|
|
12
|
+
static generateUserIdFunction(): string;
|
|
13
|
+
/**
|
|
14
|
+
* Generate SQL function for checking if user is admin
|
|
15
|
+
*/
|
|
16
|
+
static generateIsAdminFunction(): string;
|
|
17
|
+
/**
|
|
18
|
+
* Generate SQL function for getting user role
|
|
19
|
+
*/
|
|
20
|
+
static generateUserRoleFunction(): string;
|
|
21
|
+
/**
|
|
22
|
+
* Generate SQL function for checking if user has specific role
|
|
23
|
+
*/
|
|
24
|
+
static generateHasRoleFunction(): string;
|
|
25
|
+
/**
|
|
26
|
+
* Generate SQL function for getting user's tenant ID
|
|
27
|
+
*/
|
|
28
|
+
static generateUserTenantIdFunction(): string;
|
|
29
|
+
/**
|
|
30
|
+
* Generate all Clerk integration functions
|
|
31
|
+
*/
|
|
32
|
+
static generateAllFunctions(): string;
|
|
33
|
+
/**
|
|
34
|
+
* Generate SQL to create auth schema if not exists
|
|
35
|
+
*/
|
|
36
|
+
static generateAuthSchema(): string;
|
|
37
|
+
/**
|
|
38
|
+
* Generate complete Clerk integration setup
|
|
39
|
+
*/
|
|
40
|
+
static generateCompleteSetup(): string;
|
|
41
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.ClerkIntegration = void 0;
|
|
11
|
+
/**
|
|
12
|
+
* Clerk JWT integration utilities for PostgreSQL RLS
|
|
13
|
+
*/
|
|
14
|
+
class ClerkIntegration {
|
|
15
|
+
/**
|
|
16
|
+
* Generate SQL function for extracting Clerk user ID from JWT
|
|
17
|
+
*/
|
|
18
|
+
static generateClerkUserIdFunction() {
|
|
19
|
+
return `
|
|
20
|
+
-- PostgreSQL function for Clerk JWT verification
|
|
21
|
+
-- Extracts user_id from Clerk JWT token in request headers
|
|
22
|
+
|
|
23
|
+
CREATE OR REPLACE FUNCTION auth.clerk_user_id()
|
|
24
|
+
RETURNS TEXT
|
|
25
|
+
LANGUAGE plpgsql
|
|
26
|
+
SECURITY DEFINER
|
|
27
|
+
AS $$
|
|
28
|
+
DECLARE
|
|
29
|
+
jwt_claims JSONB;
|
|
30
|
+
clerk_user_id TEXT;
|
|
31
|
+
BEGIN
|
|
32
|
+
-- Extract JWT claims from request
|
|
33
|
+
jwt_claims := current_setting('request.jwt.claims', true)::jsonb;
|
|
34
|
+
|
|
35
|
+
-- Get Clerk user ID from 'sub' claim
|
|
36
|
+
clerk_user_id := jwt_claims->>'sub';
|
|
37
|
+
|
|
38
|
+
RETURN clerk_user_id;
|
|
39
|
+
EXCEPTION
|
|
40
|
+
WHEN OTHERS THEN
|
|
41
|
+
RETURN NULL;
|
|
42
|
+
END;
|
|
43
|
+
$$;
|
|
44
|
+
`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Generate SQL function for getting internal user ID from Clerk ID
|
|
48
|
+
*/
|
|
49
|
+
static generateUserIdFunction() {
|
|
50
|
+
return `
|
|
51
|
+
-- Helper function to get internal user ID from Clerk ID
|
|
52
|
+
CREATE OR REPLACE FUNCTION auth.user_id()
|
|
53
|
+
RETURNS UUID
|
|
54
|
+
LANGUAGE sql
|
|
55
|
+
STABLE
|
|
56
|
+
SECURITY DEFINER
|
|
57
|
+
AS $$
|
|
58
|
+
SELECT id FROM users WHERE clerk_id = auth.clerk_user_id();
|
|
59
|
+
$$;
|
|
60
|
+
`;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Generate SQL function for checking if user is admin
|
|
64
|
+
*/
|
|
65
|
+
static generateIsAdminFunction() {
|
|
66
|
+
return `
|
|
67
|
+
-- Helper function to check if user is admin
|
|
68
|
+
CREATE OR REPLACE FUNCTION auth.is_admin()
|
|
69
|
+
RETURNS BOOLEAN
|
|
70
|
+
LANGUAGE sql
|
|
71
|
+
STABLE
|
|
72
|
+
SECURITY DEFINER
|
|
73
|
+
AS $$
|
|
74
|
+
SELECT role = 'admin' FROM users WHERE clerk_id = auth.clerk_user_id();
|
|
75
|
+
$$;
|
|
76
|
+
`;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Generate SQL function for getting user role
|
|
80
|
+
*/
|
|
81
|
+
static generateUserRoleFunction() {
|
|
82
|
+
return `
|
|
83
|
+
-- Helper function to get user role
|
|
84
|
+
CREATE OR REPLACE FUNCTION auth.user_role()
|
|
85
|
+
RETURNS VARCHAR(50)
|
|
86
|
+
LANGUAGE sql
|
|
87
|
+
STABLE
|
|
88
|
+
SECURITY DEFINER
|
|
89
|
+
AS $$
|
|
90
|
+
SELECT role FROM users WHERE clerk_id = auth.clerk_user_id();
|
|
91
|
+
$$;
|
|
92
|
+
`;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Generate SQL function for checking if user has specific role
|
|
96
|
+
*/
|
|
97
|
+
static generateHasRoleFunction() {
|
|
98
|
+
return `
|
|
99
|
+
-- Helper function to check if user has specific role
|
|
100
|
+
CREATE OR REPLACE FUNCTION auth.has_role(required_role VARCHAR)
|
|
101
|
+
RETURNS BOOLEAN
|
|
102
|
+
LANGUAGE sql
|
|
103
|
+
STABLE
|
|
104
|
+
SECURITY DEFINER
|
|
105
|
+
AS $$
|
|
106
|
+
SELECT role = required_role FROM users WHERE clerk_id = auth.clerk_user_id();
|
|
107
|
+
$$;
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Generate SQL function for getting user's tenant ID
|
|
112
|
+
*/
|
|
113
|
+
static generateUserTenantIdFunction() {
|
|
114
|
+
return `
|
|
115
|
+
-- Helper function to get user's tenant ID
|
|
116
|
+
CREATE OR REPLACE FUNCTION auth.user_tenant_id()
|
|
117
|
+
RETURNS UUID
|
|
118
|
+
LANGUAGE sql
|
|
119
|
+
STABLE
|
|
120
|
+
SECURITY DEFINER
|
|
121
|
+
AS $$
|
|
122
|
+
SELECT tenant_id FROM users WHERE clerk_id = auth.clerk_user_id();
|
|
123
|
+
$$;
|
|
124
|
+
`;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Generate all Clerk integration functions
|
|
128
|
+
*/
|
|
129
|
+
static generateAllFunctions() {
|
|
130
|
+
return [
|
|
131
|
+
this.generateClerkUserIdFunction(),
|
|
132
|
+
this.generateUserIdFunction(),
|
|
133
|
+
this.generateIsAdminFunction(),
|
|
134
|
+
this.generateUserRoleFunction(),
|
|
135
|
+
this.generateHasRoleFunction(),
|
|
136
|
+
this.generateUserTenantIdFunction()
|
|
137
|
+
].join('\n\n');
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Generate SQL to create auth schema if not exists
|
|
141
|
+
*/
|
|
142
|
+
static generateAuthSchema() {
|
|
143
|
+
return `
|
|
144
|
+
-- Create auth schema for Clerk integration functions
|
|
145
|
+
CREATE SCHEMA IF NOT EXISTS auth;
|
|
146
|
+
|
|
147
|
+
-- Grant usage on auth schema
|
|
148
|
+
GRANT USAGE ON SCHEMA auth TO authenticated, anon;
|
|
149
|
+
`;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Generate complete Clerk integration setup
|
|
153
|
+
*/
|
|
154
|
+
static generateCompleteSetup() {
|
|
155
|
+
return [
|
|
156
|
+
this.generateAuthSchema(),
|
|
157
|
+
this.generateAllFunctions()
|
|
158
|
+
].join('\n\n');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
exports.ClerkIntegration = ClerkIntegration;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RLS policy generator for Supabase
|
|
3
|
+
* Creates policies for multi-tenant security with Clerk integration
|
|
4
|
+
*/
|
|
5
|
+
export declare class RLSPolicyGenerator {
|
|
6
|
+
/**
|
|
7
|
+
* Generate RLS policy for user-scoped access
|
|
8
|
+
*/
|
|
9
|
+
static generateUserPolicy(table: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Generate admin bypass policy
|
|
12
|
+
*/
|
|
13
|
+
static generateAdminPolicy(table: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Generate multi-tenant policy
|
|
16
|
+
*/
|
|
17
|
+
static generateTenantPolicy(table: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Generate read-only policy for public data
|
|
20
|
+
*/
|
|
21
|
+
static generatePublicReadPolicy(table: string, condition?: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Generate policy for shared resources
|
|
24
|
+
*/
|
|
25
|
+
static generateSharedResourcePolicy(table: string, sharingTable?: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Generate policy for role-based access
|
|
28
|
+
*/
|
|
29
|
+
static generateRoleBasedPolicy(table: string, allowedRoles: string[]): string;
|
|
30
|
+
/**
|
|
31
|
+
* Disable RLS on a table (use with caution)
|
|
32
|
+
*/
|
|
33
|
+
static disableRLS(table: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* Generate policy for time-based access (e.g., scheduled content)
|
|
36
|
+
*/
|
|
37
|
+
static generateTimeBasedPolicy(table: string): string;
|
|
38
|
+
}
|