From 770f337c89f45f6c1f48afc680215fdc35e10ac2 Mon Sep 17 00:00:00 2001 From: Julien Goux Date: Mon, 30 Sep 2024 13:29:39 +0200 Subject: [PATCH 1/7] upgrade pgglite --- apps/postgres-new/package.json | 2 +- package-lock.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/postgres-new/package.json b/apps/postgres-new/package.json index ac2b68c0..eb50df7d 100644 --- a/apps/postgres-new/package.json +++ b/apps/postgres-new/package.json @@ -12,7 +12,7 @@ "dependencies": { "@ai-sdk/openai": "^0.0.21", "@dagrejs/dagre": "^1.1.2", - "@electric-sql/pglite": "^0.2.8", + "@electric-sql/pglite": "^0.2.9", "@gregnr/postgres-meta": "^0.82.0-dev.2", "@monaco-editor/react": "^4.6.0", "@radix-ui/react-accordion": "^1.2.0", diff --git a/package-lock.json b/package-lock.json index cca449e1..d1148808 100644 --- a/package-lock.json +++ b/package-lock.json @@ -89,7 +89,7 @@ "dependencies": { "@ai-sdk/openai": "^0.0.21", "@dagrejs/dagre": "^1.1.2", - "@electric-sql/pglite": "^0.2.8", + "@electric-sql/pglite": "^0.2.9", "@gregnr/postgres-meta": "^0.82.0-dev.2", "@monaco-editor/react": "^4.6.0", "@radix-ui/react-accordion": "^1.2.0", @@ -165,9 +165,9 @@ } }, "apps/postgres-new/node_modules/@electric-sql/pglite": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.2.8.tgz", - "integrity": "sha512-0wSmQu22euBRzR5ghqyIHnBH4MfwlkL5WstOrrA3KOsjEWEglvoL/gH92JajEUA6Ufei/+qbkB2hVloC/K/RxQ==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.2.9.tgz", + "integrity": "sha512-KPItmBmPVZJGOv+qkCXmWyIPUPLQIyN+BVtV/zD+083aWSR/2ReaCUN+HJv6Jw4z9zJ00UCPQkeUXvOLuTlumg==", "license": "Apache-2.0" }, "apps/postgres-new/node_modules/nanoid": { From 87a71168bb0d732c9025365979906aba3b055123 Mon Sep 17 00:00:00 2001 From: Julien Goux Date: Mon, 30 Sep 2024 23:20:04 +0200 Subject: [PATCH 2/7] it works --- .../src/pg-dump-middleware/constants.ts | 1 + .../get-extension-membership-query.ts | 108 ++++++ .../get-extensions-query.ts | 125 +++++++ .../pg-dump-middleware/pg-dump-middleware.ts | 76 ++++ .../src/pg-dump-middleware/utils.ts | 38 ++ apps/browser-proxy/src/tcp-server.ts | 7 + apps/browser-proxy/src/websocket-server.ts | 11 +- apps/postgres-new/package.json | 2 +- package-lock.json | 333 ++++++++++-------- 9 files changed, 555 insertions(+), 146 deletions(-) create mode 100644 apps/browser-proxy/src/pg-dump-middleware/constants.ts create mode 100644 apps/browser-proxy/src/pg-dump-middleware/get-extension-membership-query.ts create mode 100644 apps/browser-proxy/src/pg-dump-middleware/get-extensions-query.ts create mode 100644 apps/browser-proxy/src/pg-dump-middleware/pg-dump-middleware.ts create mode 100644 apps/browser-proxy/src/pg-dump-middleware/utils.ts diff --git a/apps/browser-proxy/src/pg-dump-middleware/constants.ts b/apps/browser-proxy/src/pg-dump-middleware/constants.ts new file mode 100644 index 00000000..b25d721a --- /dev/null +++ b/apps/browser-proxy/src/pg-dump-middleware/constants.ts @@ -0,0 +1 @@ +export const VECTOR_OID = '99999' diff --git a/apps/browser-proxy/src/pg-dump-middleware/get-extension-membership-query.ts b/apps/browser-proxy/src/pg-dump-middleware/get-extension-membership-query.ts new file mode 100644 index 00000000..4fd51c00 --- /dev/null +++ b/apps/browser-proxy/src/pg-dump-middleware/get-extension-membership-query.ts @@ -0,0 +1,108 @@ +import { VECTOR_OID } from './constants.ts' +import { parseDataRowFields, parseRowDescription } from './utils.ts' + +export function isGetExtensionMembershipQuery(message: Uint8Array): boolean { + // Check if it's a SimpleQuery message (starts with 'Q') + if (message[0] !== 0x51) { + // 'Q' in ASCII + return false + } + + const query = + "SELECT classid, objid, refobjid FROM pg_depend WHERE refclassid = 'pg_extension'::regclass AND deptype = 'e' ORDER BY 3" + + // Skip the message type (1 byte) and message length (4 bytes) + const messageString = new TextDecoder().decode(message.slice(5)) + + // Trim any trailing null character + const trimmedMessage = messageString.replace(/\0+$/, '') + + // Check if the message exactly matches the query + return trimmedMessage === query +} + +export function patchGetExtensionMembershipResult(data: Uint8Array, vectorOid: string): Uint8Array { + let offset = 0 + const messages: Uint8Array[] = [] + let isDependencyTable = false + let objidIndex = -1 + let refobjidIndex = -1 + let patchedRowCount = 0 + let totalRowsProcessed = 0 + + const expectedColumns = ['classid', 'objid', 'refobjid'] + + while (offset < data.length) { + const messageType = data[offset] + const messageLength = new DataView(data.buffer, data.byteOffset + offset + 1, 4).getUint32( + 0, + false + ) + const message = data.subarray(offset, offset + messageLength + 1) + + if (messageType === 0x54) { + // RowDescription + const columnNames = parseRowDescription(message) + isDependencyTable = + columnNames.length === 3 && columnNames.every((col) => expectedColumns.includes(col)) + if (isDependencyTable) { + objidIndex = columnNames.indexOf('objid') + refobjidIndex = columnNames.indexOf('refobjid') + } + } else if (messageType === 0x44 && isDependencyTable) { + // DataRow + const fields = parseDataRowFields(message) + totalRowsProcessed++ + + if (fields.length === 3) { + const refobjid = fields[refobjidIndex]!.value + + if (refobjid === vectorOid) { + const patchedMessage = patchDependencyRow(message, refobjidIndex) + messages.push(patchedMessage) + patchedRowCount++ + offset += messageLength + 1 + continue + } + } + } + + messages.push(message) + offset += messageLength + 1 + } + + return new Uint8Array( + messages.reduce((acc, val) => { + const combined = new Uint8Array(acc.length + val.length) + combined.set(acc) + combined.set(val, acc.length) + return combined + }, new Uint8Array()) + ) +} + +function patchDependencyRow(message: Uint8Array, refobjidIndex: number): Uint8Array { + const newArray = new Uint8Array(message) + let offset = 7 // Start after message type (1 byte), message length (4 bytes), and field count (2 bytes) + + // Navigate to the refobjid field + for (let i = 0; i < refobjidIndex; i++) { + const fieldLength = new DataView(newArray.buffer, offset, 4).getInt32(0) + offset += 4 // Skip the length field + if (fieldLength > 0) { + offset += fieldLength // Skip the field value + } + } + + // Now we're at the start of the refobjid field + const refobjidLength = new DataView(newArray.buffer, offset, 4).getInt32(0) + offset += 4 // Move past the length field + + const encoder = new TextEncoder() + + // Write the new OID value + const newRefobjidBytes = encoder.encode(VECTOR_OID.padStart(refobjidLength, '0')) + newArray.set(newRefobjidBytes, offset) + + return newArray +} diff --git a/apps/browser-proxy/src/pg-dump-middleware/get-extensions-query.ts b/apps/browser-proxy/src/pg-dump-middleware/get-extensions-query.ts new file mode 100644 index 00000000..6a479a9c --- /dev/null +++ b/apps/browser-proxy/src/pg-dump-middleware/get-extensions-query.ts @@ -0,0 +1,125 @@ +import { VECTOR_OID } from './constants.ts' +import { parseDataRowFields, parseRowDescription } from './utils.ts' + +export function isGetExtensionsQuery(message: Uint8Array): boolean { + // Check if it's a SimpleQuery message (starts with 'Q') + if (message[0] !== 0x51) { + // 'Q' in ASCII + return false + } + + const query = + 'SELECT x.tableoid, x.oid, x.extname, n.nspname, x.extrelocatable, x.extversion, x.extconfig, x.extcondition FROM pg_extension x JOIN pg_namespace n ON n.oid = x.extnamespace' + + // Skip the message type (1 byte) and message length (4 bytes) + const messageString = new TextDecoder().decode(message.slice(5)) + + // Trim any trailing null character + const trimmedMessage = messageString.replace(/\0+$/, '') + + // Check if the message exactly matches the query + return trimmedMessage === query +} + +export function patchGetExtensionsResult(data: Uint8Array) { + let offset = 0 + const messages: Uint8Array[] = [] + let isVectorExtensionTable = false + let oidColumnIndex = -1 + let extnameColumnIndex = -1 + let vectorOid: string | null = null + + const expectedColumns = [ + 'tableoid', + 'oid', + 'extname', + 'nspname', + 'extrelocatable', + 'extversion', + 'extconfig', + 'extcondition', + ] + + while (offset < data.length) { + const messageType = data[offset] + const messageLength = new DataView(data.buffer, data.byteOffset + offset + 1, 4).getUint32( + 0, + false + ) + + const message = data.subarray(offset, offset + messageLength + 1) + + if (messageType === 0x54) { + // RowDescription + const columnNames = parseRowDescription(message) + + isVectorExtensionTable = + columnNames.length === expectedColumns.length && + columnNames.every((col) => expectedColumns.includes(col)) + + if (isVectorExtensionTable) { + oidColumnIndex = columnNames.indexOf('oid') + extnameColumnIndex = columnNames.indexOf('extname') + } + } else if (messageType === 0x44 && isVectorExtensionTable) { + // DataRow + const fields = parseDataRowFields(message) + if (fields[extnameColumnIndex]?.value === 'vector') { + vectorOid = fields[oidColumnIndex]!.value! + const patchedMessage = patchOidField(message, oidColumnIndex, fields) + messages.push(patchedMessage) + offset += messageLength + 1 + continue + } + } + + messages.push(message) + offset += messageLength + 1 + } + + return { + message: Buffer.concat(messages), + vectorOid, + } +} + +function patchOidField( + message: Uint8Array, + oidIndex: number, + fields: { value: string | null; length: number }[] +): Uint8Array { + const oldOidField = fields[oidIndex]! + const newOid = VECTOR_OID.padStart(oldOidField.length, '0') + + const newArray = new Uint8Array(message) + + let offset = 7 // Start after message type (1 byte), message length (4 bytes), and field count (2 bytes) + + // Navigate to the OID field + for (let i = 0; i < oidIndex; i++) { + const fieldLength = new DataView(newArray.buffer, offset, 4).getInt32(0) + offset += 4 // Skip the length field + if (fieldLength > 0) { + offset += fieldLength // Skip the field value + } + } + + // Now we're at the start of the OID field + const oidLength = new DataView(newArray.buffer, offset, 4).getInt32(0) + offset += 4 // Move past the length field + + // Ensure the new OID fits in the allocated space + if (newOid.length !== oidLength) { + console.warn( + `New OID length (${newOid.length}) doesn't match the original length (${oidLength}). Skipping patch.` + ) + return message + } + + // Write the new OID value + for (let i = 0; i < oidLength; i++) { + newArray[offset + i] = newOid.charCodeAt(i) + } + + return newArray +} diff --git a/apps/browser-proxy/src/pg-dump-middleware/pg-dump-middleware.ts b/apps/browser-proxy/src/pg-dump-middleware/pg-dump-middleware.ts new file mode 100644 index 00000000..6e141841 --- /dev/null +++ b/apps/browser-proxy/src/pg-dump-middleware/pg-dump-middleware.ts @@ -0,0 +1,76 @@ +import ExpiryMap from 'expiry-map' +import type { ClientParameters } from 'pg-gateway' +import { isGetExtensionsQuery, patchGetExtensionsResult } from './get-extensions-query.ts' +import { + isGetExtensionMembershipQuery, + patchGetExtensionMembershipResult, +} from './get-extension-membership-query.ts' + +type ConnectionId = string + +const state = new ExpiryMap(1000 * 60 * 5) + +type State = + | { step: 'wait-for-get-extensions-query' } + | { step: 'get-extensions-query-received' } + | { step: 'wait-for-get-extension-membership-query'; vectorOid: string } + | { step: 'get-extension-membership-query-received'; vectorOid: string } + | { step: 'complete' } + +export function pgDumpMiddleware( + connectionId: string, + origin: 'client' | 'server', + context: { + clientParams?: ClientParameters + }, + message: Uint8Array +) { + if (context.clientParams?.application_name !== 'pg_dump') { + return message + } + + if (!state.has(connectionId)) { + state.set(connectionId, { step: 'wait-for-get-extensions-query' }) + } + + const connectionState = state.get(connectionId)! + + switch (connectionState.step) { + case 'wait-for-get-extensions-query': + // https://github.com/postgres/postgres/blob/a19f83f87966f763991cc76404f8e42a36e7e842/src/bin/pg_dump/pg_dump.c#L5834-L5837 + if (origin === 'client' && isGetExtensionsQuery(message)) { + state.set(connectionId, { step: 'get-extensions-query-received' }) + } + return message + case 'get-extensions-query-received': + if (origin === 'client') { + return message + } + const patched = patchGetExtensionsResult(message) + if (patched.vectorOid) { + state.set(connectionId, { + step: 'wait-for-get-extension-membership-query', + vectorOid: patched.vectorOid, + }) + } + return patched.message + case 'wait-for-get-extension-membership-query': + // https://github.com/postgres/postgres/blob/a19f83f87966f763991cc76404f8e42a36e7e842/src/bin/pg_dump/pg_dump.c#L18173-L18178 + if (origin === 'client' && isGetExtensionMembershipQuery(message)) { + state.set(connectionId, { + step: 'get-extension-membership-query-received', + vectorOid: connectionState.vectorOid, + }) + } + return message + case 'get-extension-membership-query-received': + if (origin === 'client') { + return message + } + const patchedMessage = patchGetExtensionMembershipResult(message, connectionState.vectorOid) + state.set(connectionId, { step: 'complete' }) + return patchedMessage + case 'complete': + return message + } +} diff --git a/apps/browser-proxy/src/pg-dump-middleware/utils.ts b/apps/browser-proxy/src/pg-dump-middleware/utils.ts new file mode 100644 index 00000000..9d089a8a --- /dev/null +++ b/apps/browser-proxy/src/pg-dump-middleware/utils.ts @@ -0,0 +1,38 @@ +export function parseRowDescription(message: Uint8Array): string[] { + const fieldCount = new DataView(message.buffer, message.byteOffset + 5, 2).getUint16(0) + const names: string[] = [] + let offset = 7 + + for (let i = 0; i < fieldCount; i++) { + const nameEnd = message.indexOf(0, offset) + names.push(new TextDecoder().decode(message.subarray(offset, nameEnd))) + offset = nameEnd + 19 // Skip null terminator and 18 bytes of field info + } + + return names +} + +export function parseDataRowFields( + message: Uint8Array +): { value: string | null; length: number }[] { + const fieldCount = new DataView(message.buffer, message.byteOffset + 5, 2).getUint16(0) + const fields: { value: string | null; length: number }[] = [] + let offset = 7 + + for (let i = 0; i < fieldCount; i++) { + const fieldLength = new DataView(message.buffer, message.byteOffset + offset, 4).getInt32(0) + offset += 4 + + if (fieldLength === -1) { + fields.push({ value: null, length: -1 }) + } else { + fields.push({ + value: new TextDecoder().decode(message.subarray(offset, offset + fieldLength)), + length: fieldLength, + }) + offset += fieldLength + } + } + + return fields +} diff --git a/apps/browser-proxy/src/tcp-server.ts b/apps/browser-proxy/src/tcp-server.ts index 3efff1c1..c14f81f5 100644 --- a/apps/browser-proxy/src/tcp-server.ts +++ b/apps/browser-proxy/src/tcp-server.ts @@ -9,6 +9,7 @@ import { logEvent, UserConnected, UserDisconnected } from './telemetry.ts' import { connectionManager } from './connection-manager.ts' import { debug as mainDebug } from './debug.ts' import { getConnectionId, serialize } from './protocol.ts' +import { pgDumpMiddleware } from './pg-dump-middleware/pg-dump-middleware.ts' const debug = mainDebug.extend('tcp-server') @@ -89,6 +90,12 @@ tcpServer.on('connection', async (socket) => { } debug('tcp message: %e', () => Buffer.from(message).toString('hex')) + message = pgDumpMiddleware( + connectionState!.connectionId, + 'client', + connection.state, + Buffer.from(message) + ) websocket.send(serialize(connectionState!.connectionId, message)) // return an empty buffer to indicate that the message has been handled diff --git a/apps/browser-proxy/src/websocket-server.ts b/apps/browser-proxy/src/websocket-server.ts index 91eb086a..0d586062 100644 --- a/apps/browser-proxy/src/websocket-server.ts +++ b/apps/browser-proxy/src/websocket-server.ts @@ -7,6 +7,7 @@ import { setSecureContext } from './tls.ts' import { connectionManager } from './connection-manager.ts' import { DatabaseShared, DatabaseUnshared, logEvent } from './telemetry.ts' import { parse } from './protocol.ts' +import { pgDumpMiddleware } from './pg-dump-middleware/pg-dump-middleware.ts' const debug = mainDebug.extend('websocket-server') @@ -84,10 +85,18 @@ websocketServer.on('connection', async (websocket, request) => { logEvent(new DatabaseShared({ databaseId, userId: user.id })) websocket.on('message', (data: Buffer) => { - const { connectionId, message } = parse(data) + let { connectionId, message } = parse(data) const tcpConnection = connectionManager.getSocket(connectionId) if (tcpConnection) { debug('websocket message: %e', () => message.toString('hex')) + message = Buffer.from( + pgDumpMiddleware( + connectionId, + 'server', + tcpConnection.state, + new Uint8Array(message.buffer, message.byteOffset, message.byteLength) + ) + ) tcpConnection.streamWriter?.write(message) } }) diff --git a/apps/postgres-new/package.json b/apps/postgres-new/package.json index eb50df7d..743d3488 100644 --- a/apps/postgres-new/package.json +++ b/apps/postgres-new/package.json @@ -12,7 +12,7 @@ "dependencies": { "@ai-sdk/openai": "^0.0.21", "@dagrejs/dagre": "^1.1.2", - "@electric-sql/pglite": "^0.2.9", + "@electric-sql/pglite": "0.2.8", "@gregnr/postgres-meta": "^0.82.0-dev.2", "@monaco-editor/react": "^4.6.0", "@radix-ui/react-accordion": "^1.2.0", diff --git a/package-lock.json b/package-lock.json index d1148808..7a40e018 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,5 @@ { "name": "postgres-new", - "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { @@ -89,7 +88,7 @@ "dependencies": { "@ai-sdk/openai": "^0.0.21", "@dagrejs/dagre": "^1.1.2", - "@electric-sql/pglite": "^0.2.9", + "@electric-sql/pglite": "0.2.8", "@gregnr/postgres-meta": "^0.82.0-dev.2", "@monaco-editor/react": "^4.6.0", "@radix-ui/react-accordion": "^1.2.0", @@ -165,9 +164,9 @@ } }, "apps/postgres-new/node_modules/@electric-sql/pglite": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.2.9.tgz", - "integrity": "sha512-KPItmBmPVZJGOv+qkCXmWyIPUPLQIyN+BVtV/zD+083aWSR/2ReaCUN+HJv6Jw4z9zJ00UCPQkeUXvOLuTlumg==", + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.2.8.tgz", + "integrity": "sha512-0wSmQu22euBRzR5ghqyIHnBH4MfwlkL5WstOrrA3KOsjEWEglvoL/gH92JajEUA6Ufei/+qbkB2hVloC/K/RxQ==", "license": "Apache-2.0" }, "apps/postgres-new/node_modules/nanoid": { @@ -1441,7 +1440,8 @@ "node_modules/@electric-sql/pglite": { "version": "0.2.0-alpha.9", "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.2.0-alpha.9.tgz", - "integrity": "sha512-euiFGNa2NtwF2DdXCojZXtbBvhkd1ZgG/jfMimAdHp4h2kzz/bqvRYiLoH41zmFCc4XeaQyMEhuVmbdwb67hBA==" + "integrity": "sha512-euiFGNa2NtwF2DdXCojZXtbBvhkd1ZgG/jfMimAdHp4h2kzz/bqvRYiLoH41zmFCc4XeaQyMEhuVmbdwb67hBA==", + "license": "Apache-2.0" }, "node_modules/@emnapi/runtime": { "version": "0.43.1", @@ -1452,371 +1452,411 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", "cpu": [ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -3401,18 +3441,6 @@ "react-dom": ">=17" } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.1.tgz", - "integrity": "sha512-XUXeI9eM8rMP8aGvii/aOOiMvTs7xlCosq9xCjcqI9+5hBxtjDpD+7Abm1ZhVIFE1J2h2VIg0t2DX/gjespC2Q==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ] - }, "node_modules/@rushstack/eslint-patch": { "version": "1.10.3", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.3.tgz", @@ -5636,6 +5664,7 @@ "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-4.0.4.tgz", "integrity": "sha512-cMtq4W5ZsEwcutJrVId+a/tjt8GSbS+h0oNkdl6+6rBuEv8Ot33Bevj5KPm40t309zuhVic8NjpuL42QCiJWWA==", "dev": true, + "license": "ISC", "dependencies": { "cmd-shim": "^6.0.0", "npm-normalize-package-bin": "^3.0.0", @@ -6042,6 +6071,7 @@ "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.3.tgz", "integrity": "sha512-FMabTRlc5t5zjdenF6mS0MBeFZm0XqHqeOkcskKFb/LYCcRQ5fVgLOHVc4Lq9CqABd9zhjwPjMBCJvMCziSVtA==", "dev": true, + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -6322,6 +6352,7 @@ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 12" } @@ -6866,41 +6897,43 @@ } }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.23.1", + "@esbuild/android-arm": "0.23.1", + "@esbuild/android-arm64": "0.23.1", + "@esbuild/android-x64": "0.23.1", + "@esbuild/darwin-arm64": "0.23.1", + "@esbuild/darwin-x64": "0.23.1", + "@esbuild/freebsd-arm64": "0.23.1", + "@esbuild/freebsd-x64": "0.23.1", + "@esbuild/linux-arm": "0.23.1", + "@esbuild/linux-arm64": "0.23.1", + "@esbuild/linux-ia32": "0.23.1", + "@esbuild/linux-loong64": "0.23.1", + "@esbuild/linux-mips64el": "0.23.1", + "@esbuild/linux-ppc64": "0.23.1", + "@esbuild/linux-riscv64": "0.23.1", + "@esbuild/linux-s390x": "0.23.1", + "@esbuild/linux-x64": "0.23.1", + "@esbuild/netbsd-x64": "0.23.1", + "@esbuild/openbsd-arm64": "0.23.1", + "@esbuild/openbsd-x64": "0.23.1", + "@esbuild/sunos-x64": "0.23.1", + "@esbuild/win32-arm64": "0.23.1", + "@esbuild/win32-ia32": "0.23.1", + "@esbuild/win32-x64": "0.23.1" } }, "node_modules/escalade": { @@ -7701,6 +7734,7 @@ "url": "https://paypal.me/jimmywarting" } ], + "license": "MIT", "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" @@ -7835,6 +7869,7 @@ "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", "dev": true, + "license": "MIT", "dependencies": { "fetch-blob": "^3.1.2" }, @@ -10977,6 +11012,7 @@ "url": "https://paypal.me/jimmywarting" } ], + "license": "MIT", "engines": { "node": ">=10.5.0" } @@ -11120,6 +11156,7 @@ "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", "dev": true, + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -11600,6 +11637,7 @@ "version": "0.2.5-alpha.2", "resolved": "https://registry.npmjs.org/pg-gateway/-/pg-gateway-0.2.5-alpha.2.tgz", "integrity": "sha512-boyO9iC6q5O/SvB7+XLJQrj+0tdf9OblwaQbeXzLnXwBbBb37WS6uWY2Zc+KuMxWq4G+QPGc055Unt9cH545Iw==", + "license": "MIT", "dependencies": { "pg-protocol": "^1.6.1" } @@ -12838,6 +12876,7 @@ "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz", "integrity": "sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==", "dev": true, + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -14045,9 +14084,9 @@ } }, "node_modules/supabase": { - "version": "1.191.3", - "resolved": "https://registry.npmjs.org/supabase/-/supabase-1.191.3.tgz", - "integrity": "sha512-5tIG7mPc5lZ9QRbkZssyHiOsx42qGFaVqclauXv+1fJAkZnfA28d0pzEDvfs33+w8YTReO5nNaWAgyzkWQQwfA==", + "version": "1.200.3", + "resolved": "https://registry.npmjs.org/supabase/-/supabase-1.200.3.tgz", + "integrity": "sha512-3NdhqBkfPVlm+rAhWQoVcyr54kykuAlHav/GWaAoQEHBDbbYI1lhbDzugk8ryQg92vSLwr3pWz0s4Hjdte8WyQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -14069,6 +14108,7 @@ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.3.4" }, @@ -14091,6 +14131,7 @@ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, + "license": "MIT", "dependencies": { "agent-base": "^7.0.2", "debug": "4" @@ -14134,6 +14175,7 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "dev": true, + "license": "MIT", "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", @@ -14563,12 +14605,13 @@ "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" }, "node_modules/tsx": { - "version": "4.16.2", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.16.2.tgz", - "integrity": "sha512-C1uWweJDgdtX2x600HjaFaucXTilT7tgUZHbOE4+ypskZ1OP8CRCSDkCxG6Vya9EwaFIVagWwpaVAn5wzypaqQ==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.1.tgz", + "integrity": "sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==", "dev": true, + "license": "MIT", "dependencies": { - "esbuild": "~0.21.5", + "esbuild": "~0.23.0", "get-tsconfig": "^4.7.5" }, "bin": { @@ -15032,6 +15075,7 @@ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -15275,6 +15319,7 @@ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, + "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" From d809d3e20eec61c21a70c1b980ba4b019251bf86 Mon Sep 17 00:00:00 2001 From: Julien Goux Date: Mon, 30 Sep 2024 23:25:24 +0200 Subject: [PATCH 3/7] skip if oid is correct --- .../src/pg-dump-middleware/constants.ts | 3 ++- .../get-extension-membership-query.ts | 2 +- .../pg-dump-middleware/get-extensions-query.ts | 2 +- .../src/pg-dump-middleware/pg-dump-middleware.ts | 15 +++++++++++---- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/apps/browser-proxy/src/pg-dump-middleware/constants.ts b/apps/browser-proxy/src/pg-dump-middleware/constants.ts index b25d721a..b3a03caf 100644 --- a/apps/browser-proxy/src/pg-dump-middleware/constants.ts +++ b/apps/browser-proxy/src/pg-dump-middleware/constants.ts @@ -1 +1,2 @@ -export const VECTOR_OID = '99999' +export const VECTOR_OID = 99999 +export const FIRST_NORMAL_OID = 16384 diff --git a/apps/browser-proxy/src/pg-dump-middleware/get-extension-membership-query.ts b/apps/browser-proxy/src/pg-dump-middleware/get-extension-membership-query.ts index 4fd51c00..74fccd13 100644 --- a/apps/browser-proxy/src/pg-dump-middleware/get-extension-membership-query.ts +++ b/apps/browser-proxy/src/pg-dump-middleware/get-extension-membership-query.ts @@ -101,7 +101,7 @@ function patchDependencyRow(message: Uint8Array, refobjidIndex: number): Uint8Ar const encoder = new TextEncoder() // Write the new OID value - const newRefobjidBytes = encoder.encode(VECTOR_OID.padStart(refobjidLength, '0')) + const newRefobjidBytes = encoder.encode(VECTOR_OID.toString().padStart(refobjidLength, '0')) newArray.set(newRefobjidBytes, offset) return newArray diff --git a/apps/browser-proxy/src/pg-dump-middleware/get-extensions-query.ts b/apps/browser-proxy/src/pg-dump-middleware/get-extensions-query.ts index 6a479a9c..89309074 100644 --- a/apps/browser-proxy/src/pg-dump-middleware/get-extensions-query.ts +++ b/apps/browser-proxy/src/pg-dump-middleware/get-extensions-query.ts @@ -89,7 +89,7 @@ function patchOidField( fields: { value: string | null; length: number }[] ): Uint8Array { const oldOidField = fields[oidIndex]! - const newOid = VECTOR_OID.padStart(oldOidField.length, '0') + const newOid = VECTOR_OID.toString().padStart(oldOidField.length, '0') const newArray = new Uint8Array(message) diff --git a/apps/browser-proxy/src/pg-dump-middleware/pg-dump-middleware.ts b/apps/browser-proxy/src/pg-dump-middleware/pg-dump-middleware.ts index 6e141841..95563d8b 100644 --- a/apps/browser-proxy/src/pg-dump-middleware/pg-dump-middleware.ts +++ b/apps/browser-proxy/src/pg-dump-middleware/pg-dump-middleware.ts @@ -5,6 +5,7 @@ import { isGetExtensionMembershipQuery, patchGetExtensionMembershipResult, } from './get-extension-membership-query.ts' +import { FIRST_NORMAL_OID } from './constants.ts' type ConnectionId = string @@ -48,10 +49,16 @@ export function pgDumpMiddleware( } const patched = patchGetExtensionsResult(message) if (patched.vectorOid) { - state.set(connectionId, { - step: 'wait-for-get-extension-membership-query', - vectorOid: patched.vectorOid, - }) + if (parseInt(patched.vectorOid) >= FIRST_NORMAL_OID) { + state.set(connectionId, { + step: 'complete', + }) + } else { + state.set(connectionId, { + step: 'wait-for-get-extension-membership-query', + vectorOid: patched.vectorOid, + }) + } } return patched.message case 'wait-for-get-extension-membership-query': From b90656017ffa4762452a8079c6ddd7698b9b3c8e Mon Sep 17 00:00:00 2001 From: Julien Goux Date: Tue, 1 Oct 2024 09:20:37 +0200 Subject: [PATCH 4/7] use last version of PGlite --- apps/postgres-new/package.json | 2 +- package-lock.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/postgres-new/package.json b/apps/postgres-new/package.json index 743d3488..eb50df7d 100644 --- a/apps/postgres-new/package.json +++ b/apps/postgres-new/package.json @@ -12,7 +12,7 @@ "dependencies": { "@ai-sdk/openai": "^0.0.21", "@dagrejs/dagre": "^1.1.2", - "@electric-sql/pglite": "0.2.8", + "@electric-sql/pglite": "^0.2.9", "@gregnr/postgres-meta": "^0.82.0-dev.2", "@monaco-editor/react": "^4.6.0", "@radix-ui/react-accordion": "^1.2.0", diff --git a/package-lock.json b/package-lock.json index 7a40e018..ab77caf9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -88,7 +88,7 @@ "dependencies": { "@ai-sdk/openai": "^0.0.21", "@dagrejs/dagre": "^1.1.2", - "@electric-sql/pglite": "0.2.8", + "@electric-sql/pglite": "^0.2.9", "@gregnr/postgres-meta": "^0.82.0-dev.2", "@monaco-editor/react": "^4.6.0", "@radix-ui/react-accordion": "^1.2.0", @@ -164,9 +164,9 @@ } }, "apps/postgres-new/node_modules/@electric-sql/pglite": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.2.8.tgz", - "integrity": "sha512-0wSmQu22euBRzR5ghqyIHnBH4MfwlkL5WstOrrA3KOsjEWEglvoL/gH92JajEUA6Ufei/+qbkB2hVloC/K/RxQ==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.2.9.tgz", + "integrity": "sha512-KPItmBmPVZJGOv+qkCXmWyIPUPLQIyN+BVtV/zD+083aWSR/2ReaCUN+HJv6Jw4z9zJ00UCPQkeUXvOLuTlumg==", "license": "Apache-2.0" }, "apps/postgres-new/node_modules/nanoid": { From 0ad5bea71072e10f782e57aa7159681de9b96ddd Mon Sep 17 00:00:00 2001 From: Julien Goux Date: Tue, 1 Oct 2024 09:42:52 +0200 Subject: [PATCH 5/7] refactor middleware --- .../pg-dump-middleware/pg-dump-middleware.ts | 136 ++++++++++-------- apps/browser-proxy/src/tcp-server.ts | 4 +- apps/browser-proxy/src/websocket-server.ts | 3 +- 3 files changed, 82 insertions(+), 61 deletions(-) diff --git a/apps/browser-proxy/src/pg-dump-middleware/pg-dump-middleware.ts b/apps/browser-proxy/src/pg-dump-middleware/pg-dump-middleware.ts index 95563d8b..f7f9794f 100644 --- a/apps/browser-proxy/src/pg-dump-middleware/pg-dump-middleware.ts +++ b/apps/browser-proxy/src/pg-dump-middleware/pg-dump-middleware.ts @@ -1,4 +1,3 @@ -import ExpiryMap from 'expiry-map' import type { ClientParameters } from 'pg-gateway' import { isGetExtensionsQuery, patchGetExtensionsResult } from './get-extensions-query.ts' import { @@ -6,11 +5,10 @@ import { patchGetExtensionMembershipResult, } from './get-extension-membership-query.ts' import { FIRST_NORMAL_OID } from './constants.ts' +import type { Socket } from 'node:net' type ConnectionId = string -const state = new ExpiryMap(1000 * 60 * 5) - type State = | { step: 'wait-for-get-extensions-query' } | { step: 'get-extensions-query-received' } @@ -18,66 +16,90 @@ type State = | { step: 'get-extension-membership-query-received'; vectorOid: string } | { step: 'complete' } -export function pgDumpMiddleware( - connectionId: string, - origin: 'client' | 'server', - context: { - clientParams?: ClientParameters - }, - message: Uint8Array -) { - if (context.clientParams?.application_name !== 'pg_dump') { - return message - } - - if (!state.has(connectionId)) { - state.set(connectionId, { step: 'wait-for-get-extensions-query' }) - } +class PgDumpMiddleware { + private state: Map = new Map() - const connectionState = state.get(connectionId)! + constructor() {} - switch (connectionState.step) { - case 'wait-for-get-extensions-query': - // https://github.com/postgres/postgres/blob/a19f83f87966f763991cc76404f8e42a36e7e842/src/bin/pg_dump/pg_dump.c#L5834-L5837 - if (origin === 'client' && isGetExtensionsQuery(message)) { - state.set(connectionId, { step: 'get-extensions-query-received' }) - } + client( + socket: Socket, + connectionId: string, + context: { + clientParams?: ClientParameters + }, + message: Uint8Array + ) { + if (context.clientParams?.application_name !== 'pg_dump') { return message - case 'get-extensions-query-received': - if (origin === 'client') { - return message - } - const patched = patchGetExtensionsResult(message) - if (patched.vectorOid) { - if (parseInt(patched.vectorOid) >= FIRST_NORMAL_OID) { - state.set(connectionId, { - step: 'complete', - }) - } else { - state.set(connectionId, { - step: 'wait-for-get-extension-membership-query', - vectorOid: patched.vectorOid, + } + + if (!this.state.has(connectionId)) { + this.state.set(connectionId, { step: 'wait-for-get-extensions-query' }) + socket.on('close', () => { + this.state.delete(connectionId) + }) + } + + const connectionState = this.state.get(connectionId)! + + switch (connectionState.step) { + case 'wait-for-get-extensions-query': + // https://github.com/postgres/postgres/blob/a19f83f87966f763991cc76404f8e42a36e7e842/src/bin/pg_dump/pg_dump.c#L5834-L5837 + if (isGetExtensionsQuery(message)) { + this.state.set(connectionId, { step: 'get-extensions-query-received' }) + } + break + case 'wait-for-get-extension-membership-query': + // https://github.com/postgres/postgres/blob/a19f83f87966f763991cc76404f8e42a36e7e842/src/bin/pg_dump/pg_dump.c#L18173-L18178 + if (isGetExtensionMembershipQuery(message)) { + this.state.set(connectionId, { + step: 'get-extension-membership-query-received', + vectorOid: connectionState.vectorOid, }) } - } - return patched.message - case 'wait-for-get-extension-membership-query': - // https://github.com/postgres/postgres/blob/a19f83f87966f763991cc76404f8e42a36e7e842/src/bin/pg_dump/pg_dump.c#L18173-L18178 - if (origin === 'client' && isGetExtensionMembershipQuery(message)) { - state.set(connectionId, { - step: 'get-extension-membership-query-received', - vectorOid: connectionState.vectorOid, - }) - } + break + } + + return message + } + + server( + connectionId: string, + context: { + clientParams?: ClientParameters + }, + message: Uint8Array + ) { + if (context.clientParams?.application_name !== 'pg_dump' || !this.state.has(connectionId)) { return message - case 'get-extension-membership-query-received': - if (origin === 'client') { + } + + const connectionState = this.state.get(connectionId)! + + switch (connectionState.step) { + case 'get-extensions-query-received': + const patched = patchGetExtensionsResult(message) + if (patched.vectorOid) { + if (parseInt(patched.vectorOid) >= FIRST_NORMAL_OID) { + this.state.set(connectionId, { + step: 'complete', + }) + } else { + this.state.set(connectionId, { + step: 'wait-for-get-extension-membership-query', + vectorOid: patched.vectorOid, + }) + } + } + return patched.message + case 'get-extension-membership-query-received': + const patchedMessage = patchGetExtensionMembershipResult(message, connectionState.vectorOid) + this.state.set(connectionId, { step: 'complete' }) + return patchedMessage + default: return message - } - const patchedMessage = patchGetExtensionMembershipResult(message, connectionState.vectorOid) - state.set(connectionId, { step: 'complete' }) - return patchedMessage - case 'complete': - return message + } } } + +export const pgDumpMiddleware = new PgDumpMiddleware() diff --git a/apps/browser-proxy/src/tcp-server.ts b/apps/browser-proxy/src/tcp-server.ts index c14f81f5..b6eda574 100644 --- a/apps/browser-proxy/src/tcp-server.ts +++ b/apps/browser-proxy/src/tcp-server.ts @@ -90,9 +90,9 @@ tcpServer.on('connection', async (socket) => { } debug('tcp message: %e', () => Buffer.from(message).toString('hex')) - message = pgDumpMiddleware( + message = pgDumpMiddleware.client( + socket, connectionState!.connectionId, - 'client', connection.state, Buffer.from(message) ) diff --git a/apps/browser-proxy/src/websocket-server.ts b/apps/browser-proxy/src/websocket-server.ts index 0d586062..baaf6b1c 100644 --- a/apps/browser-proxy/src/websocket-server.ts +++ b/apps/browser-proxy/src/websocket-server.ts @@ -90,9 +90,8 @@ websocketServer.on('connection', async (websocket, request) => { if (tcpConnection) { debug('websocket message: %e', () => message.toString('hex')) message = Buffer.from( - pgDumpMiddleware( + pgDumpMiddleware.server( connectionId, - 'server', tcpConnection.state, new Uint8Array(message.buffer, message.byteOffset, message.byteLength) ) From b30268e5cd3a88091e60a149beb514eba5c8dcdc Mon Sep 17 00:00:00 2001 From: Julien Goux Date: Tue, 1 Oct 2024 09:46:55 +0200 Subject: [PATCH 6/7] comment the middleware --- .../src/pg-dump-middleware/pg-dump-middleware.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/browser-proxy/src/pg-dump-middleware/pg-dump-middleware.ts b/apps/browser-proxy/src/pg-dump-middleware/pg-dump-middleware.ts index f7f9794f..ee2adcfa 100644 --- a/apps/browser-proxy/src/pg-dump-middleware/pg-dump-middleware.ts +++ b/apps/browser-proxy/src/pg-dump-middleware/pg-dump-middleware.ts @@ -16,6 +16,12 @@ type State = | { step: 'get-extension-membership-query-received'; vectorOid: string } | { step: 'complete' } +/** + * Middleware to patch pg_dump results for PGlite < v0.2.8 + * PGlite < v0.2.8 has a bug in which userland extensions are not dumped because their oid is lower than FIRST_NORMAL_OID + * This middleware patches the results of the get_extensions and get_extension_membership queries to increase the oid of the `vector` extension so it can be dumped + * For more context, see: https://github.com/electric-sql/pglite/issues/352 + */ class PgDumpMiddleware { private state: Map = new Map() From 68d37be1540a050591fa8c09bdcf31358a2aeb7b Mon Sep 17 00:00:00 2001 From: Julien Goux Date: Tue, 1 Oct 2024 10:04:32 +0200 Subject: [PATCH 7/7] gracefully exit for --watch --- apps/browser-proxy/src/index.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps/browser-proxy/src/index.ts b/apps/browser-proxy/src/index.ts index b9a78294..b25c763b 100644 --- a/apps/browser-proxy/src/index.ts +++ b/apps/browser-proxy/src/index.ts @@ -16,3 +16,22 @@ httpsServer.listen(443, () => { tcpServer.listen(5432, () => { console.log('tcp server listening on port 5432') }) + +const shutdown = async () => { + await Promise.allSettled([ + new Promise((res) => + httpsServer.close(() => { + res() + }) + ), + new Promise((res) => + tcpServer.close(() => { + res() + }) + ), + ]) + process.exit(0) +} + +process.on('SIGTERM', shutdown) +process.on('SIGINT', shutdown)