@bernierllc/database-adapter-postgresql 1.5.1 → 1.5.3
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/package.json +4 -4
- package/dist/PostgreSQLAdapter.d.ts +0 -65
- package/dist/PostgreSQLAdapter.js +0 -254
- package/dist/PostgreSQLTransaction.d.ts +0 -46
- package/dist/PostgreSQLTransaction.js +0 -203
- package/dist/errors.d.ts +0 -11
- package/dist/errors.js +0 -24
- package/dist/index.d.ts +0 -8
- package/dist/index.js +0 -32
- package/dist/migrations/001_initial_schema.d.ts +0 -2
- package/dist/migrations/001_initial_schema.js +0 -91
- package/dist/realtime/RealtimeManager.d.ts +0 -76
- package/dist/realtime/RealtimeManager.js +0 -245
- package/dist/rls/ClerkIntegration.d.ts +0 -41
- package/dist/rls/ClerkIntegration.js +0 -161
- package/dist/rls/RLSPolicyGenerator.d.ts +0 -38
- package/dist/rls/RLSPolicyGenerator.js +0 -212
- package/dist/search/FullTextSearchManager.d.ts +0 -61
- package/dist/search/FullTextSearchManager.js +0 -267
|
@@ -1,91 +0,0 @@
|
|
|
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.migration_001_initial_schema = void 0;
|
|
11
|
-
exports.migration_001_initial_schema = {
|
|
12
|
-
id: '001_initial_schema',
|
|
13
|
-
name: 'Create initial content management schema',
|
|
14
|
-
async up(adapter) {
|
|
15
|
-
await adapter.query(`
|
|
16
|
-
-- Enable extensions
|
|
17
|
-
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
18
|
-
CREATE EXTENSION IF NOT EXISTS "pg_trgm";
|
|
19
|
-
|
|
20
|
-
-- Users table (Clerk integration)
|
|
21
|
-
CREATE TABLE IF NOT EXISTS users (
|
|
22
|
-
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
23
|
-
clerk_id VARCHAR(255) UNIQUE NOT NULL,
|
|
24
|
-
email VARCHAR(255) UNIQUE NOT NULL,
|
|
25
|
-
username VARCHAR(100),
|
|
26
|
-
full_name VARCHAR(255),
|
|
27
|
-
avatar_url TEXT,
|
|
28
|
-
role VARCHAR(50) DEFAULT 'user',
|
|
29
|
-
tenant_id UUID,
|
|
30
|
-
preferences JSONB DEFAULT '{}',
|
|
31
|
-
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
32
|
-
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
CREATE INDEX IF NOT EXISTS idx_users_clerk_id ON users(clerk_id);
|
|
36
|
-
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
|
37
|
-
|
|
38
|
-
-- Content table
|
|
39
|
-
CREATE TABLE IF NOT EXISTS content (
|
|
40
|
-
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
41
|
-
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
|
42
|
-
title VARCHAR(500) NOT NULL,
|
|
43
|
-
slug VARCHAR(500) UNIQUE,
|
|
44
|
-
body TEXT NOT NULL,
|
|
45
|
-
excerpt TEXT,
|
|
46
|
-
type VARCHAR(50) DEFAULT 'blog',
|
|
47
|
-
format VARCHAR(50) DEFAULT 'markdown',
|
|
48
|
-
status VARCHAR(50) DEFAULT 'draft',
|
|
49
|
-
workflow_stage VARCHAR(100),
|
|
50
|
-
assigned_to UUID REFERENCES users(id),
|
|
51
|
-
ai_reviewed BOOLEAN DEFAULT FALSE,
|
|
52
|
-
ai_review_score DECIMAL(3,2),
|
|
53
|
-
keywords JSONB DEFAULT '[]',
|
|
54
|
-
view_count INTEGER DEFAULT 0,
|
|
55
|
-
share_count INTEGER DEFAULT 0,
|
|
56
|
-
version INTEGER DEFAULT 1,
|
|
57
|
-
published_at TIMESTAMPTZ,
|
|
58
|
-
scheduled_for TIMESTAMPTZ,
|
|
59
|
-
deleted_at TIMESTAMPTZ,
|
|
60
|
-
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
61
|
-
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
-- Content versions
|
|
65
|
-
CREATE TABLE IF NOT EXISTS content_versions (
|
|
66
|
-
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
67
|
-
content_id UUID REFERENCES content(id) ON DELETE CASCADE,
|
|
68
|
-
version_number INTEGER NOT NULL,
|
|
69
|
-
title VARCHAR(500),
|
|
70
|
-
body TEXT NOT NULL,
|
|
71
|
-
change_summary TEXT,
|
|
72
|
-
is_autosave BOOLEAN DEFAULT FALSE,
|
|
73
|
-
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
74
|
-
created_by UUID REFERENCES users(id),
|
|
75
|
-
UNIQUE(content_id, version_number)
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
-- Enable RLS
|
|
79
|
-
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
|
|
80
|
-
ALTER TABLE content ENABLE ROW LEVEL SECURITY;
|
|
81
|
-
ALTER TABLE content_versions ENABLE ROW LEVEL SECURITY;
|
|
82
|
-
`);
|
|
83
|
-
},
|
|
84
|
-
async down(adapter) {
|
|
85
|
-
await adapter.query(`
|
|
86
|
-
DROP TABLE IF EXISTS content_versions CASCADE;
|
|
87
|
-
DROP TABLE IF EXISTS content CASCADE;
|
|
88
|
-
DROP TABLE IF EXISTS users CASCADE;
|
|
89
|
-
`);
|
|
90
|
-
}
|
|
91
|
-
};
|
|
@@ -1,76 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,245 +0,0 @@
|
|
|
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
|
-
adapter;
|
|
16
|
-
channels = new Map();
|
|
17
|
-
logger;
|
|
18
|
-
constructor(adapter) {
|
|
19
|
-
this.adapter = adapter;
|
|
20
|
-
// Access logger from adapter (it's a public property but typed generically)
|
|
21
|
-
this.logger = adapter.logger;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Subscribe to table changes for collaborative editing
|
|
25
|
-
*/
|
|
26
|
-
subscribeToContent(contentId, callbacks) {
|
|
27
|
-
const supabase = this.adapter.getSupabaseClient();
|
|
28
|
-
if (!supabase) {
|
|
29
|
-
throw new Error('Supabase client not configured');
|
|
30
|
-
}
|
|
31
|
-
const channelName = `content:${contentId}`;
|
|
32
|
-
if (this.logger) {
|
|
33
|
-
this.logger.debug('Subscribing to real-time channel', {
|
|
34
|
-
channel: channelName,
|
|
35
|
-
contentId
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
const channel = supabase
|
|
39
|
-
.channel(channelName)
|
|
40
|
-
.on('postgres_changes', {
|
|
41
|
-
event: 'INSERT',
|
|
42
|
-
schema: 'public',
|
|
43
|
-
table: 'content_versions',
|
|
44
|
-
filter: `content_id=eq.${contentId}`
|
|
45
|
-
}, (payload) => {
|
|
46
|
-
if (callbacks.onInsert) {
|
|
47
|
-
callbacks.onInsert(payload);
|
|
48
|
-
}
|
|
49
|
-
if (this.logger) {
|
|
50
|
-
this.logger.debug('Real-time INSERT event', {
|
|
51
|
-
channel: channelName,
|
|
52
|
-
table: 'content_versions'
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
})
|
|
56
|
-
.on('postgres_changes', {
|
|
57
|
-
event: 'UPDATE',
|
|
58
|
-
schema: 'public',
|
|
59
|
-
table: 'content',
|
|
60
|
-
filter: `id=eq.${contentId}`
|
|
61
|
-
}, (payload) => {
|
|
62
|
-
if (callbacks.onUpdate) {
|
|
63
|
-
callbacks.onUpdate(payload);
|
|
64
|
-
}
|
|
65
|
-
if (this.logger) {
|
|
66
|
-
this.logger.debug('Real-time UPDATE event', {
|
|
67
|
-
channel: channelName,
|
|
68
|
-
table: 'content'
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
})
|
|
72
|
-
.on('postgres_changes', {
|
|
73
|
-
event: 'DELETE',
|
|
74
|
-
schema: 'public',
|
|
75
|
-
table: 'content',
|
|
76
|
-
filter: `id=eq.${contentId}`
|
|
77
|
-
}, (payload) => {
|
|
78
|
-
if (callbacks.onDelete) {
|
|
79
|
-
callbacks.onDelete(payload);
|
|
80
|
-
}
|
|
81
|
-
if (this.logger) {
|
|
82
|
-
this.logger.debug('Real-time DELETE event', {
|
|
83
|
-
channel: channelName,
|
|
84
|
-
table: 'content'
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
})
|
|
88
|
-
.subscribe();
|
|
89
|
-
this.channels.set(contentId, channel);
|
|
90
|
-
// Return unsubscribe function
|
|
91
|
-
return () => {
|
|
92
|
-
if (this.logger) {
|
|
93
|
-
this.logger.debug('Unsubscribing from real-time channel', {
|
|
94
|
-
channel: channelName,
|
|
95
|
-
contentId
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
channel.unsubscribe();
|
|
99
|
-
this.channels.delete(contentId);
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Subscribe to generic table changes
|
|
104
|
-
*/
|
|
105
|
-
subscribeToTable(table, filter, callbacks) {
|
|
106
|
-
const supabase = this.adapter.getSupabaseClient();
|
|
107
|
-
if (!supabase) {
|
|
108
|
-
throw new Error('Supabase client not configured');
|
|
109
|
-
}
|
|
110
|
-
const channelName = `table:${table}:${filter || 'all'}`;
|
|
111
|
-
if (this.logger) {
|
|
112
|
-
this.logger.debug('Subscribing to table changes', {
|
|
113
|
-
channel: channelName,
|
|
114
|
-
table,
|
|
115
|
-
filter
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
let channel = supabase.channel(channelName);
|
|
119
|
-
// Subscribe to INSERT events
|
|
120
|
-
if (callbacks.onInsert) {
|
|
121
|
-
channel = channel.on('postgres_changes', {
|
|
122
|
-
event: 'INSERT',
|
|
123
|
-
schema: 'public',
|
|
124
|
-
table,
|
|
125
|
-
...(filter ? { filter } : {})
|
|
126
|
-
}, (payload) => {
|
|
127
|
-
callbacks.onInsert(payload);
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
// Subscribe to UPDATE events
|
|
131
|
-
if (callbacks.onUpdate) {
|
|
132
|
-
channel = channel.on('postgres_changes', {
|
|
133
|
-
event: 'UPDATE',
|
|
134
|
-
schema: 'public',
|
|
135
|
-
table,
|
|
136
|
-
...(filter ? { filter } : {})
|
|
137
|
-
}, (payload) => {
|
|
138
|
-
callbacks.onUpdate(payload);
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
// Subscribe to DELETE events
|
|
142
|
-
if (callbacks.onDelete) {
|
|
143
|
-
channel = channel.on('postgres_changes', {
|
|
144
|
-
event: 'DELETE',
|
|
145
|
-
schema: 'public',
|
|
146
|
-
table,
|
|
147
|
-
...(filter ? { filter } : {})
|
|
148
|
-
}, (payload) => {
|
|
149
|
-
callbacks.onDelete(payload);
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
channel.subscribe();
|
|
153
|
-
this.channels.set(channelName, channel);
|
|
154
|
-
// Return unsubscribe function
|
|
155
|
-
return () => {
|
|
156
|
-
if (this.logger) {
|
|
157
|
-
this.logger.debug('Unsubscribing from table changes', {
|
|
158
|
-
channel: channelName
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
channel.unsubscribe();
|
|
162
|
-
this.channels.delete(channelName);
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Broadcast presence for collaborative editing
|
|
167
|
-
*/
|
|
168
|
-
async broadcastPresence(contentId, userId, cursorPosition) {
|
|
169
|
-
const supabase = this.adapter.getSupabaseClient();
|
|
170
|
-
if (!supabase) {
|
|
171
|
-
if (this.logger) {
|
|
172
|
-
this.logger.warn('Cannot broadcast presence: Supabase client not configured');
|
|
173
|
-
}
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
const channel = this.channels.get(contentId);
|
|
177
|
-
if (!channel) {
|
|
178
|
-
throw new Error(`No channel found for content ${contentId}`);
|
|
179
|
-
}
|
|
180
|
-
const presenceData = {
|
|
181
|
-
userId,
|
|
182
|
-
cursorPosition,
|
|
183
|
-
timestamp: Date.now()
|
|
184
|
-
};
|
|
185
|
-
await channel.track(presenceData);
|
|
186
|
-
if (this.logger) {
|
|
187
|
-
this.logger.debug('Presence broadcasted', {
|
|
188
|
-
contentId,
|
|
189
|
-
userId,
|
|
190
|
-
cursorPosition
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Get current presence state for a channel
|
|
196
|
-
*/
|
|
197
|
-
getPresence(contentId) {
|
|
198
|
-
const channel = this.channels.get(contentId);
|
|
199
|
-
if (!channel) {
|
|
200
|
-
return null;
|
|
201
|
-
}
|
|
202
|
-
const state = channel.presenceState();
|
|
203
|
-
return state;
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Unsubscribe from a specific channel
|
|
207
|
-
*/
|
|
208
|
-
async unsubscribe(contentId) {
|
|
209
|
-
const channel = this.channels.get(contentId);
|
|
210
|
-
if (channel) {
|
|
211
|
-
if (this.logger) {
|
|
212
|
-
this.logger.debug('Unsubscribing from channel', { contentId });
|
|
213
|
-
}
|
|
214
|
-
await channel.unsubscribe();
|
|
215
|
-
this.channels.delete(contentId);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* Unsubscribe from all channels
|
|
220
|
-
*/
|
|
221
|
-
async unsubscribeAll() {
|
|
222
|
-
if (this.logger) {
|
|
223
|
-
this.logger.debug('Unsubscribing from all channels', {
|
|
224
|
-
count: this.channels.size
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
for (const [, channel] of this.channels) {
|
|
228
|
-
await channel.unsubscribe();
|
|
229
|
-
}
|
|
230
|
-
this.channels.clear();
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* Get active channel count
|
|
234
|
-
*/
|
|
235
|
-
getActiveChannelCount() {
|
|
236
|
-
return this.channels.size;
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Check if a channel is active
|
|
240
|
-
*/
|
|
241
|
-
isChannelActive(contentId) {
|
|
242
|
-
return this.channels.has(contentId);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
exports.RealtimeManager = RealtimeManager;
|
|
@@ -1,41 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,161 +0,0 @@
|
|
|
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;
|
|
@@ -1,38 +0,0 @@
|
|
|
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
|
-
}
|