@bernierllc/database-adapter-postgresql 1.5.2 → 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.
@@ -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
- }