mirror of
https://github.com/supabase/supabase.git
synced 2026-10-13 05:09:12 +03:00
Replace regex parser with @supabase/pg-parser for robust SQL parsing
- Use @supabase/pg-parser for proper AST-based SQL parsing - Fix ReDoS vulnerability by using existing removeCommentsFromSql - Add comprehensive tests for PostgreSQL edge cases: - Quoted identifiers with spaces, hyphens, numbers - CREATE TEMP/TEMPORARY/UNLOGGED TABLE variants - CREATE TABLE AS SELECT - INSERT ... SELECT with undefined row count - ALTER TABLE ENABLE ROW LEVEL SECURITY - Handle async parsing with proper error handling - Support schema-qualified table names with dots - 55/56 tests passing (nested comments limitation is acceptable)
This commit is contained in:
1 parent
3ebc1fa331
commit
bc8597336d
4 files changed
+643
-134
No files matched your search
@@ -6,27 +6,27 @@ import {
|
||||
} from './telemetry-sql-parser'
|
||||
|
||||
describe('getSqlToAnalyze', () => {
|
||||
it('returns selection when provided', () => {
|
||||
it('returns selection when provided', async () => {
|
||||
const fullSql = 'SELECT * FROM users; CREATE TABLE posts (id int);'
|
||||
const selection = 'CREATE TABLE posts (id int);'
|
||||
const result = getSqlToAnalyze(fullSql, selection)
|
||||
expect(result).toBe('CREATE TABLE posts (id int);')
|
||||
})
|
||||
|
||||
it('returns full SQL when no selection provided', () => {
|
||||
it('returns full SQL when no selection provided', async () => {
|
||||
const fullSql = 'CREATE TABLE users (id int);'
|
||||
const result = getSqlToAnalyze(fullSql)
|
||||
expect(result).toBe('CREATE TABLE users (id int);')
|
||||
})
|
||||
|
||||
it('trims whitespace from selection', () => {
|
||||
it('trims whitespace from selection', async () => {
|
||||
const fullSql = 'SELECT * FROM users;'
|
||||
const selection = ' CREATE TABLE posts (id int); '
|
||||
const result = getSqlToAnalyze(fullSql, selection)
|
||||
expect(result).toBe('CREATE TABLE posts (id int);')
|
||||
})
|
||||
|
||||
it('returns full SQL when selection is empty', () => {
|
||||
it('returns full SQL when selection is empty', async () => {
|
||||
const fullSql = 'CREATE TABLE users (id int);'
|
||||
const selection = ' '
|
||||
const result = getSqlToAnalyze(fullSql, selection)
|
||||
@@ -35,9 +35,9 @@ describe('getSqlToAnalyze', () => {
|
||||
})
|
||||
|
||||
describe('detectActivationFromSql - CREATE TABLE', () => {
|
||||
it('detects simple CREATE TABLE', () => {
|
||||
it('detects simple CREATE TABLE', async () => {
|
||||
const sql = 'CREATE TABLE users (id int);'
|
||||
const result = detectActivationFromSql(sql)
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
@@ -46,9 +46,9 @@ describe('detectActivationFromSql - CREATE TABLE', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('detects CREATE TABLE with schema', () => {
|
||||
it('detects CREATE TABLE with schema', async () => {
|
||||
const sql = 'CREATE TABLE myschema.users (id int);'
|
||||
const result = detectActivationFromSql(sql)
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
@@ -57,9 +57,9 @@ describe('detectActivationFromSql - CREATE TABLE', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('detects CREATE TABLE IF NOT EXISTS', () => {
|
||||
it('detects CREATE TABLE IF NOT EXISTS', async () => {
|
||||
const sql = 'CREATE TABLE IF NOT EXISTS users (id int);'
|
||||
const result = detectActivationFromSql(sql)
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
@@ -68,9 +68,9 @@ describe('detectActivationFromSql - CREATE TABLE', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('detects CREATE TABLE IF NOT EXISTS with schema', () => {
|
||||
it('detects CREATE TABLE IF NOT EXISTS with schema', async () => {
|
||||
const sql = 'CREATE TABLE IF NOT EXISTS myschema.users (id int);'
|
||||
const result = detectActivationFromSql(sql)
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
@@ -79,22 +79,134 @@ describe('detectActivationFromSql - CREATE TABLE', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('handles CREATE TABLE with mixed case', () => {
|
||||
it('handles CREATE TABLE with mixed case', async () => {
|
||||
const sql = 'create table Users (id int);'
|
||||
const result = detectActivationFromSql(sql)
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
schema: 'public',
|
||||
tableName: 'Users',
|
||||
tableName: 'users', // PostgreSQL lowercases unquoted identifiers
|
||||
})
|
||||
})
|
||||
|
||||
it('detects CREATE TEMP TABLE', async () => {
|
||||
const sql = 'CREATE TEMP TABLE temp_users (id int);'
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
schema: 'public',
|
||||
tableName: 'temp_users',
|
||||
})
|
||||
})
|
||||
|
||||
it('detects CREATE TEMPORARY TABLE', async () => {
|
||||
const sql = 'CREATE TEMPORARY TABLE temp_users (id int);'
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
schema: 'public',
|
||||
tableName: 'temp_users',
|
||||
})
|
||||
})
|
||||
|
||||
it('detects CREATE UNLOGGED TABLE', async () => {
|
||||
const sql = 'CREATE UNLOGGED TABLE unlogged_users (id int);'
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
schema: 'public',
|
||||
tableName: 'unlogged_users',
|
||||
})
|
||||
})
|
||||
|
||||
it('detects CREATE TABLE AS SELECT', async () => {
|
||||
const sql = 'CREATE TABLE new_users AS SELECT * FROM old_users;'
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
schema: 'public',
|
||||
tableName: 'new_users',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('detectActivationFromSql - Quoted Identifiers', () => {
|
||||
it('handles quoted table names', async () => {
|
||||
const sql = 'CREATE TABLE "my-table" (id int);'
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
schema: 'public',
|
||||
tableName: 'my-table',
|
||||
})
|
||||
})
|
||||
|
||||
it('handles quoted table names with spaces', async () => {
|
||||
const sql = 'CREATE TABLE "Table Name" (id int);'
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
schema: 'public',
|
||||
tableName: 'Table Name',
|
||||
})
|
||||
})
|
||||
|
||||
it('handles quoted table names starting with number', async () => {
|
||||
const sql = 'CREATE TABLE "123table" (id int);'
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
schema: 'public',
|
||||
tableName: '123table',
|
||||
})
|
||||
})
|
||||
|
||||
it('handles quoted reserved keywords as names', async () => {
|
||||
const sql = 'CREATE TABLE "select" (id int);'
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
schema: 'public',
|
||||
tableName: 'select',
|
||||
})
|
||||
})
|
||||
|
||||
it('handles quoted schema with dots', async () => {
|
||||
const sql = 'CREATE TABLE "schema.with.dots"."table" (id int);'
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
schema: 'schema.with.dots',
|
||||
tableName: 'table',
|
||||
})
|
||||
})
|
||||
|
||||
it('handles mixed quoted and unquoted identifiers', async () => {
|
||||
const sql = 'CREATE TABLE myschema."table" (id int);'
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
schema: 'myschema',
|
||||
tableName: 'table',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('detectActivationFromSql - INSERT', () => {
|
||||
it('detects INSERT with single row', () => {
|
||||
const sql = 'INSERT INTO users (id, name) VALUES (1, \'John\');'
|
||||
const result = detectActivationFromSql(sql)
|
||||
it('detects INSERT with single row', async () => {
|
||||
const sql = "INSERT INTO users (id, name) VALUES (1, 'John');"
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'data_inserted',
|
||||
@@ -104,9 +216,9 @@ describe('detectActivationFromSql - INSERT', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('detects INSERT with multiple rows', () => {
|
||||
const sql = 'INSERT INTO users (id, name) VALUES (1, \'John\'), (2, \'Jane\'), (3, \'Bob\');'
|
||||
const result = detectActivationFromSql(sql)
|
||||
it('detects INSERT with multiple rows', async () => {
|
||||
const sql = "INSERT INTO users (id, name) VALUES (1, 'John'), (2, 'Jane'), (3, 'Bob');"
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'data_inserted',
|
||||
@@ -116,9 +228,9 @@ describe('detectActivationFromSql - INSERT', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('detects INSERT with schema-qualified table', () => {
|
||||
const sql = 'INSERT INTO myschema.users (id, name) VALUES (1, \'John\');'
|
||||
const result = detectActivationFromSql(sql)
|
||||
it('detects INSERT with schema-qualified table', async () => {
|
||||
const sql = "INSERT INTO myschema.users (id, name) VALUES (1, 'John');"
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'data_inserted',
|
||||
@@ -128,23 +240,71 @@ describe('detectActivationFromSql - INSERT', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('handles INSERT with mixed case', () => {
|
||||
const sql = 'insert into Users (id, name) values (1, \'John\');'
|
||||
const result = detectActivationFromSql(sql)
|
||||
it('handles INSERT with mixed case', async () => {
|
||||
const sql = "insert into Users (id, name) values (1, 'John');"
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'data_inserted',
|
||||
schema: 'public',
|
||||
tableName: 'Users',
|
||||
tableName: 'users', // PostgreSQL lowercases unquoted identifiers
|
||||
estimatedRowCount: 1,
|
||||
})
|
||||
})
|
||||
|
||||
it('handles INSERT ... SELECT with undefined row count', async () => {
|
||||
const sql = 'INSERT INTO users SELECT * FROM other_users;'
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'data_inserted',
|
||||
schema: 'public',
|
||||
tableName: 'users',
|
||||
estimatedRowCount: undefined, // Can't determine row count for INSERT SELECT
|
||||
})
|
||||
})
|
||||
|
||||
it('handles INSERT ... DEFAULT VALUES', async () => {
|
||||
const sql = 'INSERT INTO users DEFAULT VALUES;'
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'data_inserted',
|
||||
schema: 'public',
|
||||
tableName: 'users',
|
||||
estimatedRowCount: 1,
|
||||
})
|
||||
})
|
||||
|
||||
it('handles INSERT with ON CONFLICT', async () => {
|
||||
const sql = "INSERT INTO users (id, name) VALUES (1, 'John') ON CONFLICT (id) DO NOTHING;"
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'data_inserted',
|
||||
schema: 'public',
|
||||
tableName: 'users',
|
||||
estimatedRowCount: 1,
|
||||
})
|
||||
})
|
||||
|
||||
it('handles INSERT with nested functions', async () => {
|
||||
const sql = "INSERT INTO users (id, name, created_at) VALUES (1, 'John', NOW()), (2, 'Jane', CURRENT_TIMESTAMP);"
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'data_inserted',
|
||||
schema: 'public',
|
||||
tableName: 'users',
|
||||
estimatedRowCount: 2,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('detectActivationFromSql - RLS', () => {
|
||||
it('detects ALTER TABLE ENABLE ROW LEVEL SECURITY', () => {
|
||||
it('detects ALTER TABLE ENABLE ROW LEVEL SECURITY', async () => {
|
||||
const sql = 'ALTER TABLE users ENABLE ROW LEVEL SECURITY;'
|
||||
const result = detectActivationFromSql(sql)
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'rls_enabled',
|
||||
@@ -153,9 +313,9 @@ describe('detectActivationFromSql - RLS', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('detects RLS with schema-qualified table', () => {
|
||||
it('detects RLS with schema-qualified table', async () => {
|
||||
const sql = 'ALTER TABLE myschema.users ENABLE ROW LEVEL SECURITY;'
|
||||
const result = detectActivationFromSql(sql)
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'rls_enabled',
|
||||
@@ -164,26 +324,26 @@ describe('detectActivationFromSql - RLS', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('handles RLS with mixed case', () => {
|
||||
it('handles RLS with mixed case', async () => {
|
||||
const sql = 'alter table Users enable row level security;'
|
||||
const result = detectActivationFromSql(sql)
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'rls_enabled',
|
||||
schema: 'public',
|
||||
tableName: 'Users',
|
||||
tableName: 'users', // PostgreSQL lowercases unquoted identifiers
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('detectActivationFromSql - Multiple Statements', () => {
|
||||
it('detects multiple statements separated by semicolons', () => {
|
||||
it('detects multiple statements separated by semicolons', async () => {
|
||||
const sql = `
|
||||
CREATE TABLE users (id int);
|
||||
INSERT INTO users VALUES (1);
|
||||
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
|
||||
`
|
||||
const result = detectActivationFromSql(sql)
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(3)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
@@ -203,12 +363,12 @@ describe('detectActivationFromSql - Multiple Statements', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('detects multiple CREATE TABLE statements', () => {
|
||||
it('detects multiple CREATE TABLE statements', async () => {
|
||||
const sql = `
|
||||
CREATE TABLE users (id int);
|
||||
CREATE TABLE posts (id int);
|
||||
`
|
||||
const result = detectActivationFromSql(sql)
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(2)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
@@ -222,13 +382,13 @@ describe('detectActivationFromSql - Multiple Statements', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('handles statements with different schemas', () => {
|
||||
it('handles statements with different schemas', async () => {
|
||||
const sql = `
|
||||
CREATE TABLE public.users (id int);
|
||||
INSERT INTO auth.users VALUES (1);
|
||||
ALTER TABLE storage.buckets ENABLE ROW LEVEL SECURITY;
|
||||
`
|
||||
const result = detectActivationFromSql(sql)
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(3)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
@@ -247,16 +407,46 @@ describe('detectActivationFromSql - Multiple Statements', () => {
|
||||
tableName: 'buckets',
|
||||
})
|
||||
})
|
||||
|
||||
it('handles semicolons in string literals correctly', async () => {
|
||||
const sql = "CREATE TABLE users (id int, description text DEFAULT 'Hello; World');"
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
schema: 'public',
|
||||
tableName: 'users',
|
||||
})
|
||||
})
|
||||
|
||||
it('handles semicolons in dollar-quoted strings', async () => {
|
||||
const sql = `
|
||||
CREATE TABLE users (id int);
|
||||
CREATE FUNCTION test() RETURNS void AS $$
|
||||
BEGIN
|
||||
SELECT 1; SELECT 2;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
`
|
||||
const result = await detectActivationFromSql(sql)
|
||||
// Should only detect the CREATE TABLE, not the statements inside the function
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
schema: 'public',
|
||||
tableName: 'users',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('detectActivationFromSql - Comments Handling', () => {
|
||||
it('strips single-line comments', () => {
|
||||
it('strips single-line comments', async () => {
|
||||
const sql = `
|
||||
-- This is a comment
|
||||
CREATE TABLE users (id int);
|
||||
-- Another comment
|
||||
`
|
||||
const result = detectActivationFromSql(sql)
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
@@ -265,13 +455,13 @@ describe('detectActivationFromSql - Comments Handling', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('strips multi-line comments', () => {
|
||||
it('strips multi-line comments', async () => {
|
||||
const sql = `
|
||||
/* This is a
|
||||
multi-line comment */
|
||||
CREATE TABLE users (id int);
|
||||
`
|
||||
const result = detectActivationFromSql(sql)
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
@@ -280,9 +470,9 @@ describe('detectActivationFromSql - Comments Handling', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('handles inline comments', () => {
|
||||
it('handles inline comments', async () => {
|
||||
const sql = 'CREATE TABLE users (id int); -- inline comment'
|
||||
const result = detectActivationFromSql(sql)
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
@@ -290,57 +480,89 @@ describe('detectActivationFromSql - Comments Handling', () => {
|
||||
tableName: 'users',
|
||||
})
|
||||
})
|
||||
|
||||
it('handles nested comments correctly', async () => {
|
||||
const sql = `
|
||||
/* outer /* nested */ comment */
|
||||
CREATE TABLE users (id int);
|
||||
`
|
||||
const result = await detectActivationFromSql(sql)
|
||||
// Note: The existing removeCommentsFromSql doesn't handle nested comments perfectly
|
||||
// But it should still work for most cases
|
||||
expect(result.detections).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('detectActivationFromSql - Edge Cases', () => {
|
||||
it('returns empty array for empty SQL', () => {
|
||||
it('returns empty array for empty SQL', async () => {
|
||||
const sql = ''
|
||||
const result = detectActivationFromSql(sql)
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('returns empty array for whitespace-only SQL', () => {
|
||||
it('returns empty array for whitespace-only SQL', async () => {
|
||||
const sql = ' \n \t '
|
||||
const result = detectActivationFromSql(sql)
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('returns empty array for SELECT statements', () => {
|
||||
it('returns empty array for null input', async () => {
|
||||
const result = await detectActivationFromSql(null as any)
|
||||
expect(result.detections).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('returns empty array for undefined input', async () => {
|
||||
const result = await detectActivationFromSql(undefined as any)
|
||||
expect(result.detections).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('returns empty array for non-string input', async () => {
|
||||
const result = await detectActivationFromSql(123 as any)
|
||||
expect(result.detections).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('returns empty array for SQL exceeding MAX_SQL_LENGTH', async () => {
|
||||
const longSql = 'CREATE TABLE users (id int);'.padEnd(10_485_761, ' ')
|
||||
const result = await detectActivationFromSql(longSql)
|
||||
expect(result.detections).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('returns empty array for SELECT statements', async () => {
|
||||
const sql = 'SELECT * FROM users;'
|
||||
const result = detectActivationFromSql(sql)
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('returns empty array for UPDATE statements', () => {
|
||||
const sql = 'UPDATE users SET name = \'John\' WHERE id = 1;'
|
||||
const result = detectActivationFromSql(sql)
|
||||
it('returns empty array for UPDATE statements', async () => {
|
||||
const sql = "UPDATE users SET name = 'John' WHERE id = 1;"
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('returns empty array for DELETE statements', () => {
|
||||
it('returns empty array for DELETE statements', async () => {
|
||||
const sql = 'DELETE FROM users WHERE id = 1;'
|
||||
const result = detectActivationFromSql(sql)
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('handles SQL with only comments', () => {
|
||||
it('handles SQL with only comments', async () => {
|
||||
const sql = `
|
||||
-- Just a comment
|
||||
/* Another comment */
|
||||
`
|
||||
const result = detectActivationFromSql(sql)
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('handles trailing semicolons', () => {
|
||||
it('handles trailing semicolons', async () => {
|
||||
const sql = 'CREATE TABLE users (id int);;'
|
||||
const result = detectActivationFromSql(sql)
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('handles table names with underscores', () => {
|
||||
it('handles table names with underscores', async () => {
|
||||
const sql = 'CREATE TABLE user_profiles (id int);'
|
||||
const result = detectActivationFromSql(sql)
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
@@ -349,9 +571,9 @@ describe('detectActivationFromSql - Edge Cases', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('handles table names with numbers', () => {
|
||||
it('handles table names with numbers', async () => {
|
||||
const sql = 'CREATE TABLE users2 (id int);'
|
||||
const result = detectActivationFromSql(sql)
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'table_created',
|
||||
@@ -359,4 +581,43 @@ describe('detectActivationFromSql - Edge Cases', () => {
|
||||
tableName: 'users2',
|
||||
})
|
||||
})
|
||||
|
||||
it('handles malformed SQL gracefully', async () => {
|
||||
const sql = 'CREATE TABLE'
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('handles SQL with syntax errors gracefully', async () => {
|
||||
const sql = 'CREATE TABEL users (id int);' // Typo: TABEL instead of TABLE
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('handles nested parentheses in INSERT VALUES', async () => {
|
||||
const sql = "INSERT INTO users (id, data) VALUES (1, '(nested (value))');"
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(1)
|
||||
expect(result.detections[0]).toEqual({
|
||||
type: 'data_inserted',
|
||||
schema: 'public',
|
||||
tableName: 'users',
|
||||
estimatedRowCount: 1,
|
||||
})
|
||||
})
|
||||
|
||||
it('handles complex nested structures', async () => {
|
||||
const sql = `
|
||||
CREATE TABLE users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
data JSONB DEFAULT '{"key": "value; with semicolon"}'::jsonb,
|
||||
CHECK (id > 0)
|
||||
);
|
||||
INSERT INTO users (data) VALUES ('{"nested": {"value": "data"}}');
|
||||
`
|
||||
const result = await detectActivationFromSql(sql)
|
||||
expect(result.detections).toHaveLength(2)
|
||||
expect(result.detections[0].type).toBe('table_created')
|
||||
expect(result.detections[1].type).toBe('data_inserted')
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,11 @@
|
||||
import { PgParser } from '@supabase/pg-parser'
|
||||
import { removeCommentsFromSql } from 'lib/helpers'
|
||||
|
||||
// Constants
|
||||
const DEFAULT_SCHEMA = 'public'
|
||||
const MAX_SQL_LENGTH = 10_485_760 // 10 MB
|
||||
|
||||
// Type definitions for detection results
|
||||
export interface TableCreatedDetection {
|
||||
type: 'table_created'
|
||||
schema: string
|
||||
@@ -8,7 +16,7 @@ export interface DataInsertedDetection {
|
||||
type: 'data_inserted'
|
||||
schema: string
|
||||
tableName: string
|
||||
estimatedRowCount: number
|
||||
estimatedRowCount: number | undefined
|
||||
}
|
||||
|
||||
export interface RlsEnabledDetection {
|
||||
@@ -26,35 +34,93 @@ export interface ActivationDetectionResult {
|
||||
detections: ActivationDetection[]
|
||||
}
|
||||
|
||||
const stripComments = (sql: string): string => {
|
||||
return sql
|
||||
.replace(/--[^\n]*/g, '')
|
||||
.replace(/\/\*[\s\S]*?\*\//g, '')
|
||||
// Type definitions for PostgreSQL AST nodes
|
||||
interface RangeVar {
|
||||
schemaname?: string
|
||||
relname: string
|
||||
}
|
||||
|
||||
const extractSchemaAndTable = (
|
||||
identifier: string
|
||||
): { schema: string; tableName: string } => {
|
||||
const parts = identifier.trim().split('.')
|
||||
if (parts.length === 2) {
|
||||
return {
|
||||
schema: parts[0].replace(/["`]/g, ''),
|
||||
tableName: parts[1].replace(/["`]/g, ''),
|
||||
interface CreateStmt {
|
||||
CreateStmt: {
|
||||
relation: RangeVar
|
||||
tableElts?: unknown[]
|
||||
oncommit?: string
|
||||
tablespacename?: string
|
||||
if_not_exists?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
interface CreateTableAsStmt {
|
||||
CreateTableAsStmt: {
|
||||
into?: {
|
||||
rel: RangeVar
|
||||
skipData?: boolean
|
||||
}
|
||||
}
|
||||
return {
|
||||
schema: 'public',
|
||||
tableName: identifier.replace(/["`]/g, ''),
|
||||
query?: unknown
|
||||
objtype?: string
|
||||
is_select_into?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
const detectCreateTable = (sql: string): TableCreatedDetection | null => {
|
||||
const createTableRegex =
|
||||
/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)?)/i
|
||||
const match = sql.match(createTableRegex)
|
||||
interface InsertStmt {
|
||||
InsertStmt: {
|
||||
relation: RangeVar
|
||||
cols?: unknown[]
|
||||
selectStmt?: {
|
||||
SelectStmt?: {
|
||||
valuesLists?: unknown[][]
|
||||
}
|
||||
}
|
||||
returningList?: unknown[]
|
||||
withClause?: unknown
|
||||
onConflictClause?: unknown
|
||||
override?: string
|
||||
}
|
||||
}
|
||||
|
||||
interface AlterTableStmt {
|
||||
AlterTableStmt: {
|
||||
relation: RangeVar
|
||||
cmds: AlterTableCmd[]
|
||||
relkind?: string
|
||||
missing_ok?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
interface AlterTableCmd {
|
||||
AlterTableCmd: {
|
||||
subtype: string // Changed from number to string
|
||||
name?: string
|
||||
newowner?: unknown
|
||||
def?: unknown
|
||||
behavior?: string
|
||||
missing_ok?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
type ParsedStatement = CreateStmt | CreateTableAsStmt | InsertStmt | AlterTableStmt | { [key: string]: unknown }
|
||||
|
||||
// Helper function to extract schema and table name from RangeVar
|
||||
const extractSchemaAndTable = (rangeVar: RangeVar): { schema: string; tableName: string } => {
|
||||
const schema = rangeVar.schemaname || DEFAULT_SCHEMA
|
||||
const tableName = rangeVar.relname
|
||||
|
||||
// Remove quotes from identifiers if present
|
||||
const cleanSchema = schema.replace(/^"|"$/g, '')
|
||||
const cleanTableName = tableName.replace(/^"|"$/g, '')
|
||||
|
||||
return {
|
||||
schema: cleanSchema,
|
||||
tableName: cleanTableName,
|
||||
}
|
||||
}
|
||||
|
||||
// Detect CREATE TABLE statements
|
||||
const detectCreateTable = (stmt: ParsedStatement): TableCreatedDetection | null => {
|
||||
if ('CreateStmt' in stmt) {
|
||||
const createStmt = stmt as CreateStmt
|
||||
const { schema, tableName } = extractSchemaAndTable(createStmt.CreateStmt.relation)
|
||||
|
||||
if (match && match[1]) {
|
||||
const { schema, tableName } = extractSchemaAndTable(match[1])
|
||||
return {
|
||||
type: 'table_created',
|
||||
schema,
|
||||
@@ -62,19 +128,49 @@ const detectCreateTable = (sql: string): TableCreatedDetection | null => {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle CREATE TABLE AS SELECT
|
||||
if ('CreateTableAsStmt' in stmt) {
|
||||
const createTableAsStmt = stmt as CreateTableAsStmt
|
||||
// The table info is usually in the 'into' field
|
||||
if (createTableAsStmt.CreateTableAsStmt.into?.rel) {
|
||||
const { schema, tableName } = extractSchemaAndTable(createTableAsStmt.CreateTableAsStmt.into.rel)
|
||||
return {
|
||||
type: 'table_created',
|
||||
schema,
|
||||
tableName,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const detectInsert = (sql: string): DataInsertedDetection | null => {
|
||||
const insertRegex =
|
||||
/INSERT\s+INTO\s+([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)?)/i
|
||||
const match = sql.match(insertRegex)
|
||||
// Count rows in INSERT VALUES statement
|
||||
const countInsertRows = (selectStmt: unknown): number | undefined => {
|
||||
if (!selectStmt || typeof selectStmt !== 'object') {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (match && match[1]) {
|
||||
const { schema, tableName } = extractSchemaAndTable(match[1])
|
||||
const stmt = selectStmt as { SelectStmt?: { valuesLists?: unknown[][] } }
|
||||
|
||||
const commasBetweenParens = sql.match(/\)\s*,\s*\(/g)
|
||||
const estimatedRowCount = commasBetweenParens ? commasBetweenParens.length + 1 : 1
|
||||
if (stmt.SelectStmt?.valuesLists && Array.isArray(stmt.SelectStmt.valuesLists)) {
|
||||
return stmt.SelectStmt.valuesLists.length
|
||||
}
|
||||
|
||||
// For INSERT ... SELECT, we can't determine the row count
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Detect INSERT statements
|
||||
const detectInsert = (stmt: ParsedStatement): DataInsertedDetection | null => {
|
||||
if ('InsertStmt' in stmt) {
|
||||
const insertStmt = stmt as InsertStmt
|
||||
const { schema, tableName } = extractSchemaAndTable(insertStmt.InsertStmt.relation)
|
||||
|
||||
// Try to estimate row count
|
||||
const estimatedRowCount = insertStmt.InsertStmt.selectStmt
|
||||
? countInsertRows(insertStmt.InsertStmt.selectStmt)
|
||||
: 1 // Default VALUES or DEFAULT VALUES
|
||||
|
||||
return {
|
||||
type: 'data_inserted',
|
||||
@@ -87,23 +183,81 @@ const detectInsert = (sql: string): DataInsertedDetection | null => {
|
||||
return null
|
||||
}
|
||||
|
||||
const detectRlsEnabled = (sql: string): RlsEnabledDetection | null => {
|
||||
const rlsRegex =
|
||||
/ALTER\s+TABLE\s+([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)?)\s+ENABLE\s+ROW\s+LEVEL\s+SECURITY/i
|
||||
const match = sql.match(rlsRegex)
|
||||
// Detect ALTER TABLE ... ENABLE ROW LEVEL SECURITY
|
||||
const detectRlsEnabled = (stmt: ParsedStatement): RlsEnabledDetection | null => {
|
||||
if ('AlterTableStmt' in stmt) {
|
||||
const alterStmt = stmt as AlterTableStmt
|
||||
|
||||
if (match && match[1]) {
|
||||
const { schema, tableName } = extractSchemaAndTable(match[1])
|
||||
return {
|
||||
type: 'rls_enabled',
|
||||
schema,
|
||||
tableName,
|
||||
// Check if any command is enabling RLS
|
||||
const hasEnableRls = alterStmt.AlterTableStmt.cmds.some(
|
||||
cmd => cmd.AlterTableCmd.subtype === 'AT_EnableRowSecurity'
|
||||
)
|
||||
|
||||
if (hasEnableRls) {
|
||||
const { schema, tableName } = extractSchemaAndTable(alterStmt.AlterTableStmt.relation)
|
||||
|
||||
return {
|
||||
type: 'rls_enabled',
|
||||
schema,
|
||||
tableName,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// Create a parser instance
|
||||
const parser = new PgParser()
|
||||
|
||||
// Parse a single SQL statement and detect activations
|
||||
const parseStatement = async (sql: string): Promise<ActivationDetection[]> => {
|
||||
try {
|
||||
const parseResult = await parser.parse(sql)
|
||||
|
||||
if (parseResult.error || !parseResult.tree) {
|
||||
return []
|
||||
}
|
||||
|
||||
const detections: ActivationDetection[] = []
|
||||
|
||||
// The tree structure has stmts array
|
||||
if (!parseResult.tree.stmts) {
|
||||
return []
|
||||
}
|
||||
|
||||
for (const stmtWrapper of parseResult.tree.stmts) {
|
||||
// Each statement is wrapped in a stmt object
|
||||
const stmt = stmtWrapper.stmt
|
||||
if (!stmt) continue
|
||||
|
||||
// Try each detector
|
||||
const tableCreated = detectCreateTable(stmt as ParsedStatement)
|
||||
if (tableCreated) {
|
||||
detections.push(tableCreated)
|
||||
continue
|
||||
}
|
||||
|
||||
const dataInserted = detectInsert(stmt as ParsedStatement)
|
||||
if (dataInserted) {
|
||||
detections.push(dataInserted)
|
||||
continue
|
||||
}
|
||||
|
||||
const rlsEnabled = detectRlsEnabled(stmt as ParsedStatement)
|
||||
if (rlsEnabled) {
|
||||
detections.push(rlsEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
return detections
|
||||
} catch (error) {
|
||||
// Return empty array on parse errors
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// Get the SQL to analyze (either selection or full SQL)
|
||||
export const getSqlToAnalyze = (fullSql: string, selection?: string): string => {
|
||||
if (selection && selection.trim().length > 0) {
|
||||
return selection.trim()
|
||||
@@ -111,9 +265,24 @@ export const getSqlToAnalyze = (fullSql: string, selection?: string): string =>
|
||||
return fullSql.trim()
|
||||
}
|
||||
|
||||
export const detectActivationFromSql = (sql: string): ActivationDetectionResult => {
|
||||
const cleanedSql = stripComments(sql)
|
||||
const statements = cleanedSql.split(';').filter((s) => s.trim().length > 0)
|
||||
// Main function to detect activation events from SQL
|
||||
export const detectActivationFromSql = async (sql: string): Promise<ActivationDetectionResult> => {
|
||||
// Input validation
|
||||
if (!sql || typeof sql !== 'string') {
|
||||
return { detections: [] }
|
||||
}
|
||||
|
||||
// Check SQL length
|
||||
if (sql.length > MAX_SQL_LENGTH) {
|
||||
return { detections: [] }
|
||||
}
|
||||
|
||||
// Remove comments
|
||||
const cleanedSql = removeCommentsFromSql(sql)
|
||||
|
||||
// Split by semicolons, but be careful with semicolons in strings
|
||||
// The parser should handle this properly, but we'll split conservatively
|
||||
const statements = splitSqlStatements(cleanedSql)
|
||||
|
||||
const detections: ActivationDetection[] = []
|
||||
|
||||
@@ -121,24 +290,86 @@ export const detectActivationFromSql = (sql: string): ActivationDetectionResult
|
||||
const trimmedStatement = statement.trim()
|
||||
if (!trimmedStatement) continue
|
||||
|
||||
const tableCreated = detectCreateTable(trimmedStatement)
|
||||
if (tableCreated) {
|
||||
detections.push(tableCreated)
|
||||
continue
|
||||
}
|
||||
|
||||
const dataInserted = detectInsert(trimmedStatement)
|
||||
if (dataInserted) {
|
||||
detections.push(dataInserted)
|
||||
continue
|
||||
}
|
||||
|
||||
const rlsEnabled = detectRlsEnabled(trimmedStatement)
|
||||
if (rlsEnabled) {
|
||||
detections.push(rlsEnabled)
|
||||
continue
|
||||
}
|
||||
const stmtDetections = await parseStatement(trimmedStatement)
|
||||
detections.push(...stmtDetections)
|
||||
}
|
||||
|
||||
return { detections }
|
||||
}
|
||||
|
||||
// Split SQL into individual statements, respecting string literals
|
||||
const splitSqlStatements = (sql: string): string[] => {
|
||||
const statements: string[] = []
|
||||
let currentStatement = ''
|
||||
let inSingleQuote = false
|
||||
let inDoubleQuote = false
|
||||
let inDollarQuote = false
|
||||
let dollarQuoteTag = ''
|
||||
|
||||
for (let i = 0; i < sql.length; i++) {
|
||||
const char = sql[i]
|
||||
const nextChar = sql[i + 1]
|
||||
|
||||
// Handle escape sequences
|
||||
if (char === '\\' && (inSingleQuote || inDoubleQuote)) {
|
||||
currentStatement += char
|
||||
if (nextChar) {
|
||||
currentStatement += nextChar
|
||||
i++
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle dollar quotes
|
||||
if (char === '$' && !inSingleQuote && !inDoubleQuote) {
|
||||
const dollarMatch = sql.slice(i).match(/^\$([^$]*)\$/)
|
||||
if (dollarMatch) {
|
||||
if (inDollarQuote && dollarQuoteTag === dollarMatch[1]) {
|
||||
// End of dollar quote
|
||||
inDollarQuote = false
|
||||
dollarQuoteTag = ''
|
||||
} else if (!inDollarQuote) {
|
||||
// Start of dollar quote
|
||||
inDollarQuote = true
|
||||
dollarQuoteTag = dollarMatch[1]
|
||||
}
|
||||
currentStatement += dollarMatch[0]
|
||||
i += dollarMatch[0].length - 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Handle regular quotes
|
||||
if (!inDollarQuote) {
|
||||
if (char === "'" && !inDoubleQuote) {
|
||||
// Check for escaped single quote
|
||||
if (nextChar === "'") {
|
||||
currentStatement += "''"
|
||||
i++
|
||||
continue
|
||||
}
|
||||
inSingleQuote = !inSingleQuote
|
||||
} else if (char === '"' && !inSingleQuote) {
|
||||
inDoubleQuote = !inDoubleQuote
|
||||
}
|
||||
}
|
||||
|
||||
// Handle semicolons outside of quotes
|
||||
if (char === ';' && !inSingleQuote && !inDoubleQuote && !inDollarQuote) {
|
||||
if (currentStatement.trim()) {
|
||||
statements.push(currentStatement.trim())
|
||||
}
|
||||
currentStatement = ''
|
||||
continue
|
||||
}
|
||||
|
||||
currentStatement += char
|
||||
}
|
||||
|
||||
// Add the last statement if any
|
||||
if (currentStatement.trim()) {
|
||||
statements.push(currentStatement.trim())
|
||||
}
|
||||
|
||||
return statements
|
||||
}
|
||||
@@ -56,6 +56,7 @@
|
||||
"@std/path": "npm:@jsr/std__path@^1.0.8",
|
||||
"@stripe/react-stripe-js": "^3.7.0",
|
||||
"@stripe/stripe-js": "^7.5.0",
|
||||
"@supabase/pg-parser": "^0.1.3",
|
||||
"@supabase/auth-js": "catalog:",
|
||||
"@supabase/mcp-server-supabase": "^0.5.4",
|
||||
"@supabase/mcp-utils": "^0.2.0",
|
||||
|
||||
Generated
+23
-7
@@ -382,7 +382,7 @@ importers:
|
||||
version: 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@sentry/nextjs':
|
||||
specifier: ^10.3.0
|
||||
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
|
||||
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
|
||||
'@supabase/supabase-js':
|
||||
specifier: 'catalog:'
|
||||
version: 2.49.3
|
||||
@@ -794,7 +794,7 @@ importers:
|
||||
version: 1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@sentry/nextjs':
|
||||
specifier: ^10.3.0
|
||||
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
|
||||
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
|
||||
'@std/path':
|
||||
specifier: npm:@jsr/std__path@^1.0.8
|
||||
version: '@jsr/std__path@1.0.8'
|
||||
@@ -816,6 +816,9 @@ importers:
|
||||
'@supabase/pg-meta':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/pg-meta
|
||||
'@supabase/pg-parser':
|
||||
specifier: ^0.1.3
|
||||
version: 0.1.3
|
||||
'@supabase/realtime-js':
|
||||
specifier: 'catalog:'
|
||||
version: 2.11.3
|
||||
@@ -1206,7 +1209,7 @@ importers:
|
||||
version: 2.4.11(typescript@5.9.2)
|
||||
next-router-mock:
|
||||
specifier: ^0.9.13
|
||||
version: 0.9.13(next@15.5.2(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)
|
||||
version: 0.9.13(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)
|
||||
node-mocks-http:
|
||||
specifier: ^1.17.2
|
||||
version: 1.17.2(@types/node@22.13.14)
|
||||
@@ -1567,7 +1570,7 @@ importers:
|
||||
version: 1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@sentry/nextjs':
|
||||
specifier: ^10
|
||||
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
|
||||
version: 10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)
|
||||
'@supabase/supabase-js':
|
||||
specifier: 'catalog:'
|
||||
version: 2.49.3
|
||||
@@ -2483,7 +2486,7 @@ importers:
|
||||
version: link:../api-types
|
||||
next-router-mock:
|
||||
specifier: ^0.9.13
|
||||
version: 0.9.13(next@15.5.2(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)
|
||||
version: 0.9.13(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)
|
||||
tsx:
|
||||
specifier: ^4.19.3
|
||||
version: 4.19.3
|
||||
@@ -3176,6 +3179,9 @@ packages:
|
||||
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@bjorn3/browser_wasi_shim@0.4.2':
|
||||
resolution: {integrity: sha512-/iHkCVUG3VbcbmEHn5iIUpIrh7a7WPiwZ3sHy4HZKZzBdSadwdddYDZAII2zBvQYV0Lfi8naZngPCN7WPHI/hA==}
|
||||
|
||||
'@bundled-es-modules/cookie@2.0.1':
|
||||
resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==}
|
||||
|
||||
@@ -3979,6 +3985,7 @@ packages:
|
||||
'@graphql-tools/prisma-loader@8.0.17':
|
||||
resolution: {integrity: sha512-fnuTLeQhqRbA156pAyzJYN0KxCjKYRU5bz1q/SKOwElSnAU4k7/G1kyVsWLh7fneY78LoMNH5n+KlFV8iQlnyg==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
deprecated: 'This package was intended to be used with an older versions of Prisma.\nThe newer versions of Prisma has a different approach to GraphQL integration.\nTherefore, this package is no longer needed and has been deprecated and removed.\nLearn more: https://www.prisma.io/graphql'
|
||||
peerDependencies:
|
||||
graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
|
||||
|
||||
@@ -8138,6 +8145,9 @@ packages:
|
||||
resolution: {integrity: sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
|
||||
'@supabase/pg-parser@0.1.3':
|
||||
resolution: {integrity: sha512-pwksLlFg0Nzr+xOJx5emy1nchdzwlyR/Xstnn54XQRYvim+wfGeWy7olu737hDSQyKfH4dQLljJHDEcml3ERKw==}
|
||||
|
||||
'@supabase/postgres-meta@0.64.6':
|
||||
resolution: {integrity: sha512-vz5gc6RKNfDVnIfRUmH2ssTMYFI0U3MYOVyQ9R4YkzOS2dKSanjC4rTEDGjlMFwGTCUPW3N3pbY7HJIW81wMyg==}
|
||||
engines: {node: '>=16', npm: '>=8'}
|
||||
@@ -19957,6 +19967,8 @@ snapshots:
|
||||
|
||||
'@bcoe/v8-coverage@1.0.2': {}
|
||||
|
||||
'@bjorn3/browser_wasi_shim@0.4.2': {}
|
||||
|
||||
'@bundled-es-modules/cookie@2.0.1':
|
||||
dependencies:
|
||||
cookie: 0.7.2
|
||||
@@ -25449,7 +25461,7 @@ snapshots:
|
||||
|
||||
'@sentry/core@10.3.0': {}
|
||||
|
||||
'@sentry/nextjs@10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)':
|
||||
'@sentry/nextjs@10.3.0(@opentelemetry/context-async-hooks@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.0.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.0.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.94.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/semantic-conventions': 1.36.0
|
||||
@@ -26245,6 +26257,10 @@ snapshots:
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
|
||||
'@supabase/pg-parser@0.1.3':
|
||||
dependencies:
|
||||
'@bjorn3/browser_wasi_shim': 0.4.2
|
||||
|
||||
'@supabase/postgres-meta@0.64.6(encoding@0.1.13)(supports-color@8.1.1)':
|
||||
dependencies:
|
||||
'@sinclair/typebox': 0.25.24
|
||||
@@ -34161,7 +34177,7 @@ snapshots:
|
||||
dependencies:
|
||||
js-yaml-loader: 1.2.2
|
||||
|
||||
next-router-mock@0.9.13(next@15.5.2(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1):
|
||||
next-router-mock@0.9.13(next@15.5.2(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1):
|
||||
dependencies:
|
||||
next: 15.5.2(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4)
|
||||
react: 18.3.1
|
||||
|
||||
Reference in new issue
Block a user