orez 0.2.27 → 0.2.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cf-do/worker.d.ts +3 -0
- package/dist/cf-do/worker.d.ts.map +1 -1
- package/dist/cf-do/worker.js +37 -15
- package/dist/cf-do/worker.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -4
- package/src/admin/admin-data.test.ts +0 -348
- package/src/admin/http-proxy.ts +0 -252
- package/src/admin/log-store.ts +0 -192
- package/src/admin/server.ts +0 -471
- package/src/admin/ui.ts +0 -1322
- package/src/bench/proxy-throughput.bench.ts +0 -343
- package/src/bench/serial-mutations.bench.ts +0 -270
- package/src/browser.ts +0 -203
- package/src/cf-do/.wrangler/cache/cf.json +0 -1
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm +0 -0
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/0ffaabee41a60e04dd0eb7db3073f0a40139e6a97ccd26823967acb652b89a7b.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-shm +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-wal +0 -0
- package/src/cf-do/.wrangler/tmp/bundle-0z4CpE/middleware-insertion-facade.js +0 -11
- package/src/cf-do/.wrangler/tmp/bundle-0z4CpE/middleware-loader.entry.ts +0 -134
- package/src/cf-do/.wrangler/tmp/bundle-vYmw0E/middleware-insertion-facade.js +0 -11
- package/src/cf-do/.wrangler/tmp/bundle-vYmw0E/middleware-loader.entry.ts +0 -134
- package/src/cf-do/.wrangler/tmp/dev-cbILNo/worker.js +0 -1059
- package/src/cf-do/.wrangler/tmp/dev-cbILNo/worker.js.map +0 -8
- package/src/cf-do/.wrangler/tmp/dev-qbho19/worker.js +0 -1059
- package/src/cf-do/.wrangler/tmp/dev-qbho19/worker.js.map +0 -8
- package/src/cf-do/ARCHITECTURE.md +0 -93
- package/src/cf-do/CHAT_E2E.md +0 -213
- package/src/cf-do/watermark.test.ts +0 -103
- package/src/cf-do/watermark.ts +0 -118
- package/src/cf-do/worker.ts +0 -1041
- package/src/cf-do/wrangler.toml +0 -11
- package/src/cf-pglite/README.md +0 -19
- package/src/change-tracking.ts +0 -25
- package/src/child-process.test.ts +0 -147
- package/src/child-process.ts +0 -90
- package/src/cli-entry.ts +0 -72
- package/src/cli.test.ts +0 -40
- package/src/cli.ts +0 -1214
- package/src/config.ts +0 -150
- package/src/do-sql-tracking.test.ts +0 -19
- package/src/do-sql-tracking.ts +0 -19
- package/src/index.ts +0 -1215
- package/src/integration/integration.test.ts +0 -517
- package/src/integration/native-binary.guard.test.ts +0 -13
- package/src/integration/native-startup.test.ts +0 -44
- package/src/integration/replication-latency.test.ts +0 -428
- package/src/integration/restore-live-stress.test.ts +0 -433
- package/src/integration/restore-reset.test.ts +0 -400
- package/src/integration/restore.test.ts +0 -274
- package/src/integration/test-permissions.ts +0 -147
- package/src/load-config.ts +0 -46
- package/src/log.ts +0 -96
- package/src/mutex.ts +0 -47
- package/src/pg-proxy-browser.singledb.test.ts +0 -233
- package/src/pg-proxy-browser.ts +0 -2022
- package/src/pg-proxy-do-backend.test.ts +0 -3890
- package/src/pg-proxy-do-backend.ts +0 -7191
- package/src/pg-proxy.ts +0 -1087
- package/src/pg-sqlite-compiler/README.md +0 -53
- package/src/pg-sqlite-compiler/catalog/seed.ts +0 -524
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/arithmetic.json +0 -307
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/array.json +0 -377
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/cast.json +0 -12
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/catalog.json +0 -447
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/create-table.json +0 -32
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/datetime.json +0 -397
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/enum.json +0 -337
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/insert.json +0 -337
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/json.json +0 -537
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/misc.json +0 -1837
- package/src/pg-sqlite-compiler/index.ts +0 -73
- package/src/pg-sqlite-compiler/integration.test.ts +0 -136
- package/src/pg-sqlite-compiler/passes/ast-utils.ts +0 -113
- package/src/pg-sqlite-compiler/passes/catalog.ts +0 -65
- package/src/pg-sqlite-compiler/passes/datetime.ts +0 -74
- package/src/pg-sqlite-compiler/passes/index.ts +0 -49
- package/src/pg-sqlite-compiler/passes/types.ts +0 -156
- package/src/pg-sqlite-compiler/smoke.test.ts +0 -69
- package/src/pg-sqlite-compiler/test/catalog.test.ts +0 -171
- package/src/pg-sqlite-compiler/test/corpus.test.ts +0 -161
- package/src/pg-sqlite-compiler/test/datetime.oracle.test.ts +0 -102
- package/src/pg-sqlite-compiler/test/oracle.ts +0 -237
- package/src/pg-sqlite-compiler/test/types.test.ts +0 -109
- package/src/pg-sqlite-compiler/types.ts +0 -63
- package/src/pglite-ipc.test.ts +0 -116
- package/src/pglite-ipc.ts +0 -266
- package/src/pglite-manager.ts +0 -557
- package/src/pglite-web-proxy.test.ts +0 -57
- package/src/pglite-web-proxy.ts +0 -221
- package/src/pglite-web-worker.ts +0 -152
- package/src/pglite-worker-thread.ts +0 -253
- package/src/port.ts +0 -25
- package/src/process-title.ts +0 -9
- package/src/recovery.ts +0 -155
- package/src/replication/change-tracker.test.ts +0 -357
- package/src/replication/change-tracker.ts +0 -279
- package/src/replication/handler.test.ts +0 -511
- package/src/replication/handler.ts +0 -1190
- package/src/replication/pgoutput-encoder.test.ts +0 -697
- package/src/replication/pgoutput-encoder.ts +0 -373
- package/src/replication/tcp-replication.test.ts +0 -876
- package/src/replication/zero-compat.test.ts +0 -1150
- package/src/restore-stress.test.ts +0 -188
- package/src/s3-local.ts +0 -203
- package/src/shim/hooks.mjs +0 -120
- package/src/shim/register.mjs +0 -4
- package/src/sqlite-mode/apply-mode.ts +0 -224
- package/src/sqlite-mode/index.ts +0 -15
- package/src/sqlite-mode/native-binary.ts +0 -89
- package/src/sqlite-mode/package-resolve.ts +0 -17
- package/src/sqlite-mode/resolve-mode.ts +0 -80
- package/src/sqlite-mode/shim-template.ts +0 -159
- package/src/sqlite-mode/sqlite-mode.test.ts +0 -427
- package/src/sqlite-mode/types.ts +0 -30
- package/src/vite-plugin.ts +0 -67
- package/src/wasm-sqlite.test.ts +0 -537
- package/src/worker/browser-admin.ts +0 -52
- package/src/worker/browser-build-config.test.ts +0 -71
- package/src/worker/browser-build-config.ts +0 -109
- package/src/worker/browser-embed-admin.test.ts +0 -75
- package/src/worker/browser-embed.ts +0 -345
- package/src/worker/cf-patches.ts +0 -384
- package/src/worker/embed-integration.test.ts +0 -321
- package/src/worker/index.ts +0 -138
- package/src/worker/shims/fastify.test.ts +0 -255
- package/src/worker/shims/fastify.ts +0 -306
- package/src/worker/shims/http-service.test.ts +0 -355
- package/src/worker/shims/http-service.ts +0 -293
- package/src/worker/shims/node-stub.ts +0 -290
- package/src/worker/shims/oxfmt.ts +0 -3
- package/src/worker/shims/postgres-browser.ts +0 -59
- package/src/worker/shims/postgres-socket.test.ts +0 -576
- package/src/worker/shims/postgres-socket.ts +0 -310
- package/src/worker/shims/postgres.test.ts +0 -364
- package/src/worker/shims/postgres.ts +0 -1454
- package/src/worker/shims/sqlite-browser.test.ts +0 -233
- package/src/worker/shims/sqlite-browser.ts +0 -175
- package/src/worker/shims/sqlite.test.ts +0 -786
- package/src/worker/shims/sqlite.ts +0 -978
- package/src/worker/shims/stream-browser.ts +0 -15
- package/src/worker/shims/ws-browser.test.ts +0 -205
- package/src/worker/shims/ws-browser.ts +0 -248
- package/src/worker/shims/ws.test.ts +0 -288
- package/src/worker/shims/ws.ts +0 -467
- package/src/worker/shims/zero-process-env.ts +0 -11
- package/src/worker/types.ts +0 -75
- package/src/worker/worker-integration.test.ts +0 -223
- package/src/worker/worker.test.ts +0 -136
- package/src/worker/zero-cache-embed-cf.ts +0 -463
- package/src/worker/zero-cache-embed.ts +0 -277
package/src/pglite-manager.ts
DELETED
|
@@ -1,557 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
readFileSync,
|
|
3
|
-
readdirSync,
|
|
4
|
-
existsSync,
|
|
5
|
-
mkdirSync,
|
|
6
|
-
renameSync,
|
|
7
|
-
unlinkSync,
|
|
8
|
-
} from 'node:fs'
|
|
9
|
-
import { join, resolve } from 'node:path'
|
|
10
|
-
|
|
11
|
-
import { PGlite } from '@electric-sql/pglite'
|
|
12
|
-
import { btree_gin } from '@electric-sql/pglite/contrib/btree_gin'
|
|
13
|
-
import { btree_gist } from '@electric-sql/pglite/contrib/btree_gist'
|
|
14
|
-
import { citext } from '@electric-sql/pglite/contrib/citext'
|
|
15
|
-
import { cube } from '@electric-sql/pglite/contrib/cube'
|
|
16
|
-
import { earthdistance } from '@electric-sql/pglite/contrib/earthdistance'
|
|
17
|
-
import { fuzzystrmatch } from '@electric-sql/pglite/contrib/fuzzystrmatch'
|
|
18
|
-
import { hstore } from '@electric-sql/pglite/contrib/hstore'
|
|
19
|
-
import { ltree } from '@electric-sql/pglite/contrib/ltree'
|
|
20
|
-
import { pg_trgm } from '@electric-sql/pglite/contrib/pg_trgm'
|
|
21
|
-
import { pgcrypto } from '@electric-sql/pglite/contrib/pgcrypto'
|
|
22
|
-
import { uuid_ossp } from '@electric-sql/pglite/contrib/uuid_ossp'
|
|
23
|
-
import { vector } from '@electric-sql/pglite/vector'
|
|
24
|
-
|
|
25
|
-
import { log } from './log.js'
|
|
26
|
-
import { PGliteWorkerProxy } from './pglite-ipc.js'
|
|
27
|
-
|
|
28
|
-
import type { ZeroLiteConfig } from './config.js'
|
|
29
|
-
|
|
30
|
-
// check if a process is running (works on unix systems)
|
|
31
|
-
function isProcessRunning(pid: number): boolean {
|
|
32
|
-
try {
|
|
33
|
-
process.kill(pid, 0) // signal 0 = check existence, don't actually kill
|
|
34
|
-
return true
|
|
35
|
-
} catch {
|
|
36
|
-
return false
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// clean stale lock files left behind by crashes
|
|
41
|
-
// returns true if locks were cleaned
|
|
42
|
-
function cleanStaleLocks(dataPath: string): boolean {
|
|
43
|
-
const pidFile = join(dataPath, 'postmaster.pid')
|
|
44
|
-
if (!existsSync(pidFile)) return false
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
const content = readFileSync(pidFile, 'utf-8')
|
|
48
|
-
const pid = parseInt(content.split('\n')[0], 10)
|
|
49
|
-
|
|
50
|
-
if (pid && isProcessRunning(pid)) {
|
|
51
|
-
// process is still running, don't touch it
|
|
52
|
-
return false
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// process is gone, clean up stale locks
|
|
56
|
-
const lockFiles = [
|
|
57
|
-
pidFile,
|
|
58
|
-
...readdirSync(dataPath)
|
|
59
|
-
.filter((f) => f.startsWith('.s.PGSQL.'))
|
|
60
|
-
.map((f) => join(dataPath, f)),
|
|
61
|
-
]
|
|
62
|
-
|
|
63
|
-
for (const file of lockFiles) {
|
|
64
|
-
try {
|
|
65
|
-
unlinkSync(file)
|
|
66
|
-
} catch {}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return lockFiles.length > 0
|
|
70
|
-
} catch {
|
|
71
|
-
return false
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export interface PGliteInstances {
|
|
76
|
-
postgres: PGlite
|
|
77
|
-
cvr: PGlite
|
|
78
|
-
cdb: PGlite
|
|
79
|
-
/** read replicas of the postgres instance (empty if disabled) */
|
|
80
|
-
postgresReplicas: PGlite[]
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// shared setup extracted from the 4 factory functions below
|
|
84
|
-
|
|
85
|
-
/** migrate old single-instance pgdata dir to the new pgdata-postgres layout */
|
|
86
|
-
function migrateDataDir(config: ZeroLiteConfig): void {
|
|
87
|
-
const pgliteDataDir = (config.pgliteOptions as Record<string, any>)?.dataDir
|
|
88
|
-
if (!pgliteDataDir || !String(pgliteDataDir).startsWith('memory://')) {
|
|
89
|
-
const oldDataPath = resolve(config.dataDir, 'pgdata')
|
|
90
|
-
const newDataPath = resolve(config.dataDir, 'pgdata-postgres')
|
|
91
|
-
if (existsSync(oldDataPath) && !existsSync(newDataPath)) {
|
|
92
|
-
renameSync(oldDataPath, newDataPath)
|
|
93
|
-
log.debug.pglite('migrated pgdata → pgdata-postgres')
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** create publication if ZERO_APP_PUBLICATIONS is set and publication doesn't exist */
|
|
99
|
-
async function ensurePublication(db: {
|
|
100
|
-
exec(sql: string): Promise<any>
|
|
101
|
-
query<T>(sql: string, params?: any[]): Promise<{ rows: T[] }>
|
|
102
|
-
}): Promise<void> {
|
|
103
|
-
await db.exec('CREATE EXTENSION IF NOT EXISTS plpgsql')
|
|
104
|
-
|
|
105
|
-
const pubName = process.env.ZERO_APP_PUBLICATIONS?.trim()
|
|
106
|
-
if (pubName) {
|
|
107
|
-
const pubs = await db.query<{ count: string }>(
|
|
108
|
-
`SELECT count(*) as count FROM pg_publication WHERE pubname = $1`,
|
|
109
|
-
[pubName]
|
|
110
|
-
)
|
|
111
|
-
if (Number(pubs.rows[0].count) === 0) {
|
|
112
|
-
const quoted = '"' + pubName.replace(/"/g, '""') + '"'
|
|
113
|
-
await db.exec(`CREATE PUBLICATION ${quoted}`)
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const PGLITE_BASE_FLAGS = [
|
|
119
|
-
'--single',
|
|
120
|
-
'-F',
|
|
121
|
-
'-O',
|
|
122
|
-
'-j',
|
|
123
|
-
'-c',
|
|
124
|
-
'search_path=public',
|
|
125
|
-
'-c',
|
|
126
|
-
'exit_on_error=false',
|
|
127
|
-
'-c',
|
|
128
|
-
'log_checkpoints=false',
|
|
129
|
-
'-c',
|
|
130
|
-
'jit=off',
|
|
131
|
-
'-c',
|
|
132
|
-
'max_connections=5',
|
|
133
|
-
'-c',
|
|
134
|
-
'temp_buffers=1MB',
|
|
135
|
-
]
|
|
136
|
-
|
|
137
|
-
// main instance: tuned for development (matching soot browser config)
|
|
138
|
-
const MAIN_START_PARAMS = [
|
|
139
|
-
...PGLITE_BASE_FLAGS,
|
|
140
|
-
'-c',
|
|
141
|
-
'shared_buffers=1MB',
|
|
142
|
-
'-c',
|
|
143
|
-
'wal_buffers=64kB',
|
|
144
|
-
'-c',
|
|
145
|
-
'work_mem=1MB',
|
|
146
|
-
'-c',
|
|
147
|
-
'maintenance_work_mem=4MB',
|
|
148
|
-
'-c',
|
|
149
|
-
'effective_cache_size=16MB',
|
|
150
|
-
]
|
|
151
|
-
|
|
152
|
-
// cvr/cdb are just zero-cache bookkeeping — minimal fixed memory
|
|
153
|
-
const ZERO_START_PARAMS = [
|
|
154
|
-
...PGLITE_BASE_FLAGS,
|
|
155
|
-
'-c',
|
|
156
|
-
'shared_buffers=128kB',
|
|
157
|
-
'-c',
|
|
158
|
-
'wal_buffers=32kB',
|
|
159
|
-
'-c',
|
|
160
|
-
'work_mem=64kB',
|
|
161
|
-
'-c',
|
|
162
|
-
'maintenance_work_mem=512kB',
|
|
163
|
-
'-c',
|
|
164
|
-
'temp_buffers=400kB',
|
|
165
|
-
'-c',
|
|
166
|
-
'max_connections=1',
|
|
167
|
-
]
|
|
168
|
-
|
|
169
|
-
// create a single pglite instance with given dataDir suffix
|
|
170
|
-
async function createInstance(
|
|
171
|
-
config: ZeroLiteConfig,
|
|
172
|
-
name: string,
|
|
173
|
-
withExtensions: boolean
|
|
174
|
-
): Promise<PGlite> {
|
|
175
|
-
const {
|
|
176
|
-
dataDir: userDataDir,
|
|
177
|
-
debug: _dbg,
|
|
178
|
-
...userOpts
|
|
179
|
-
} = config.pgliteOptions as Record<string, any>
|
|
180
|
-
|
|
181
|
-
const useMemory = typeof userDataDir === 'string' && userDataDir.startsWith('memory://')
|
|
182
|
-
const dataPath = useMemory ? 'memory://' : resolve(config.dataDir, `pgdata-${name}`)
|
|
183
|
-
|
|
184
|
-
if (!useMemory) {
|
|
185
|
-
mkdirSync(dataPath, { recursive: true })
|
|
186
|
-
// clean stale locks from previous crashes before trying to open
|
|
187
|
-
if (cleanStaleLocks(dataPath)) {
|
|
188
|
-
log.debug.pglite(`cleaned stale locks in ${name}`)
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
log.debug.pglite(`creating ${name} instance at ${dataPath}`)
|
|
193
|
-
|
|
194
|
-
const isMain = withExtensions
|
|
195
|
-
|
|
196
|
-
try {
|
|
197
|
-
const db = new PGlite({
|
|
198
|
-
dataDir: dataPath,
|
|
199
|
-
debug: config.logLevel === 'debug' ? 1 : 0,
|
|
200
|
-
relaxedDurability: true,
|
|
201
|
-
initialMemory: isMain ? 16 * 1024 * 1024 : 8 * 1024 * 1024,
|
|
202
|
-
...(isMain ? {} : { startParams: ZERO_START_PARAMS }),
|
|
203
|
-
// main instance: user overrides via pgliteOptions, zero instances: fixed
|
|
204
|
-
...(isMain
|
|
205
|
-
? {
|
|
206
|
-
startParams: MAIN_START_PARAMS,
|
|
207
|
-
...userOpts,
|
|
208
|
-
extensions: userOpts.extensions || {
|
|
209
|
-
vector,
|
|
210
|
-
pg_trgm,
|
|
211
|
-
pgcrypto,
|
|
212
|
-
uuid_ossp,
|
|
213
|
-
citext,
|
|
214
|
-
hstore,
|
|
215
|
-
ltree,
|
|
216
|
-
fuzzystrmatch,
|
|
217
|
-
btree_gin,
|
|
218
|
-
btree_gist,
|
|
219
|
-
cube,
|
|
220
|
-
earthdistance,
|
|
221
|
-
},
|
|
222
|
-
}
|
|
223
|
-
: { extensions: {} }),
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
await db.waitReady
|
|
227
|
-
|
|
228
|
-
if (isMain) {
|
|
229
|
-
await db.exec(`
|
|
230
|
-
SET random_page_cost = 1.1;
|
|
231
|
-
`)
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
log.debug.pglite(`${name} ready`)
|
|
235
|
-
return db
|
|
236
|
-
} catch (err) {
|
|
237
|
-
const msg = String(err)
|
|
238
|
-
if (msg.includes('Aborted()') || msg.includes('_pg_initdb')) {
|
|
239
|
-
log.pglite(`failed to start ${name} database`)
|
|
240
|
-
log.pglite(``)
|
|
241
|
-
log.pglite(`the data directory may be corrupted or locked.`)
|
|
242
|
-
log.pglite(`to fix, try one of:`)
|
|
243
|
-
log.pglite(``)
|
|
244
|
-
log.pglite(` 1. remove lock files:`)
|
|
245
|
-
log.pglite(` rm -f ${dataPath}/postmaster.pid ${dataPath}/.s.PGSQL.*`)
|
|
246
|
-
log.pglite(``)
|
|
247
|
-
log.pglite(` 2. start fresh (loses data):`)
|
|
248
|
-
log.pglite(
|
|
249
|
-
` rm -rf ${config.dataDir}/pgdata-* ${config.dataDir}/zero-replica.db*`
|
|
250
|
-
)
|
|
251
|
-
log.pglite(``)
|
|
252
|
-
}
|
|
253
|
-
throw err
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* create separate pglite instances for each "database".
|
|
259
|
-
*
|
|
260
|
-
* this mirrors real postgresql where postgres, zero_cvr, and zero_cdb are
|
|
261
|
-
* independent databases with separate transaction contexts. each instance
|
|
262
|
-
* has its own session state, so transactions on one database can't be
|
|
263
|
-
* corrupted by queries on another.
|
|
264
|
-
*/
|
|
265
|
-
export async function createPGliteInstances(
|
|
266
|
-
config: ZeroLiteConfig
|
|
267
|
-
): Promise<PGliteInstances> {
|
|
268
|
-
migrateDataDir(config)
|
|
269
|
-
|
|
270
|
-
const [postgres, cvr, cdb] = await Promise.all([
|
|
271
|
-
createInstance(config, 'postgres', true),
|
|
272
|
-
createInstance(config, 'cvr', false),
|
|
273
|
-
createInstance(config, 'cdb', false),
|
|
274
|
-
])
|
|
275
|
-
|
|
276
|
-
await ensurePublication(postgres)
|
|
277
|
-
return { postgres, cvr, cdb, postgresReplicas: [] }
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* create worker-backed pglite instances.
|
|
282
|
-
*
|
|
283
|
-
* each instance runs in its own worker thread with a separate event loop,
|
|
284
|
-
* so PGlite WASM execution doesn't block the proxy or replication handler.
|
|
285
|
-
* ArrayBuffers are transferred (not copied) for wire protocol data.
|
|
286
|
-
*/
|
|
287
|
-
export async function createPGliteWorkerInstances(
|
|
288
|
-
config: ZeroLiteConfig
|
|
289
|
-
): Promise<PGliteInstances> {
|
|
290
|
-
migrateDataDir(config)
|
|
291
|
-
|
|
292
|
-
const pgliteDataDir = (config.pgliteOptions as Record<string, any>)?.dataDir
|
|
293
|
-
const useMemory =
|
|
294
|
-
typeof pgliteDataDir === 'string' && pgliteDataDir.startsWith('memory://')
|
|
295
|
-
const {
|
|
296
|
-
dataDir: _ud,
|
|
297
|
-
debug: _dbg,
|
|
298
|
-
...userOpts
|
|
299
|
-
} = config.pgliteOptions as Record<string, any>
|
|
300
|
-
|
|
301
|
-
function makeWorkerConfig(name: string, withExtensions: boolean) {
|
|
302
|
-
const dataPath = useMemory ? 'memory://' : resolve(config.dataDir, `pgdata-${name}`)
|
|
303
|
-
if (!useMemory) {
|
|
304
|
-
mkdirSync(dataPath, { recursive: true })
|
|
305
|
-
if (cleanStaleLocks(dataPath)) {
|
|
306
|
-
log.debug.pglite(`cleaned stale locks in ${name}`)
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
return {
|
|
310
|
-
dataDir: dataPath,
|
|
311
|
-
name,
|
|
312
|
-
withExtensions,
|
|
313
|
-
debug: config.logLevel === 'debug' ? 1 : 0,
|
|
314
|
-
pgliteOptions: userOpts,
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
log.pglite('starting worker threads for postgres, cvr, cdb')
|
|
319
|
-
|
|
320
|
-
const pgProxy = new PGliteWorkerProxy(makeWorkerConfig('postgres', true))
|
|
321
|
-
const cvrProxy = new PGliteWorkerProxy(makeWorkerConfig('cvr', false))
|
|
322
|
-
const cdbProxy = new PGliteWorkerProxy(makeWorkerConfig('cdb', false))
|
|
323
|
-
|
|
324
|
-
await Promise.all([pgProxy.waitReady, cvrProxy.waitReady, cdbProxy.waitReady])
|
|
325
|
-
log.pglite('all worker threads ready')
|
|
326
|
-
|
|
327
|
-
await ensurePublication(pgProxy)
|
|
328
|
-
|
|
329
|
-
return {
|
|
330
|
-
postgres: pgProxy as unknown as PGlite,
|
|
331
|
-
cvr: cvrProxy as unknown as PGlite,
|
|
332
|
-
cdb: cdbProxy as unknown as PGlite,
|
|
333
|
-
postgresReplicas: [],
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* create a single pglite instance shared across all databases.
|
|
339
|
-
*
|
|
340
|
-
* uses one instance for postgres, cvr, and cdb — much lighter than three
|
|
341
|
-
* separate instances. intended for constrained environments like cloudflare
|
|
342
|
-
* workers where running 3 pglite instances is too expensive.
|
|
343
|
-
*/
|
|
344
|
-
export async function createSinglePGliteInstance(
|
|
345
|
-
config: ZeroLiteConfig
|
|
346
|
-
): Promise<PGliteInstances> {
|
|
347
|
-
migrateDataDir(config)
|
|
348
|
-
log.pglite('starting single shared pglite instance')
|
|
349
|
-
|
|
350
|
-
const db = await createInstance(config, 'postgres', true)
|
|
351
|
-
await ensurePublication(db)
|
|
352
|
-
|
|
353
|
-
// same instance for all three — pg-proxy detects this and shares a mutex
|
|
354
|
-
return { postgres: db, cvr: db, cdb: db, postgresReplicas: [] }
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* create a single worker-backed pglite instance shared across all databases.
|
|
359
|
-
*/
|
|
360
|
-
export async function createSinglePGliteWorkerInstance(
|
|
361
|
-
config: ZeroLiteConfig
|
|
362
|
-
): Promise<PGliteInstances> {
|
|
363
|
-
migrateDataDir(config)
|
|
364
|
-
|
|
365
|
-
const pgliteDataDir = (config.pgliteOptions as Record<string, any>)?.dataDir
|
|
366
|
-
const useMemory =
|
|
367
|
-
typeof pgliteDataDir === 'string' && pgliteDataDir.startsWith('memory://')
|
|
368
|
-
const {
|
|
369
|
-
dataDir: _ud,
|
|
370
|
-
debug: _dbg,
|
|
371
|
-
...userOpts
|
|
372
|
-
} = config.pgliteOptions as Record<string, any>
|
|
373
|
-
|
|
374
|
-
const dataPath = useMemory ? 'memory://' : resolve(config.dataDir, 'pgdata-postgres')
|
|
375
|
-
if (!useMemory) {
|
|
376
|
-
mkdirSync(dataPath, { recursive: true })
|
|
377
|
-
if (cleanStaleLocks(dataPath)) {
|
|
378
|
-
log.debug.pglite('cleaned stale locks in postgres')
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
log.pglite('starting single shared pglite worker thread')
|
|
383
|
-
|
|
384
|
-
const proxy = new PGliteWorkerProxy({
|
|
385
|
-
dataDir: dataPath,
|
|
386
|
-
name: 'postgres',
|
|
387
|
-
withExtensions: true,
|
|
388
|
-
debug: config.logLevel === 'debug' ? 1 : 0,
|
|
389
|
-
pgliteOptions: userOpts,
|
|
390
|
-
})
|
|
391
|
-
|
|
392
|
-
await proxy.waitReady
|
|
393
|
-
log.pglite('single worker thread ready')
|
|
394
|
-
|
|
395
|
-
await ensurePublication(proxy)
|
|
396
|
-
|
|
397
|
-
const db = proxy as unknown as PGlite
|
|
398
|
-
return { postgres: db, cvr: db, cdb: db, postgresReplicas: [] }
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
/** create a single worker-backed PGlite instance (for CVR/CDB recreation during reset) */
|
|
402
|
-
export function createPGliteWorker(dataDir: string, name: string): PGliteWorkerProxy {
|
|
403
|
-
return new PGliteWorkerProxy({
|
|
404
|
-
dataDir,
|
|
405
|
-
name,
|
|
406
|
-
withExtensions: false,
|
|
407
|
-
debug: 0,
|
|
408
|
-
pgliteOptions: {},
|
|
409
|
-
})
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* create read replicas of the postgres instance.
|
|
414
|
-
*
|
|
415
|
-
* dumps the primary's data directory and initializes N new worker threads
|
|
416
|
-
* from the dump. each replica is an independent PGlite instance on its own
|
|
417
|
-
* core, handling read queries concurrently.
|
|
418
|
-
*
|
|
419
|
-
* call this AFTER migrations, seed, on-db-ready — the dump captures the
|
|
420
|
-
* full database state at the time of cloning.
|
|
421
|
-
*/
|
|
422
|
-
export async function createReadReplicas(
|
|
423
|
-
primary: PGlite,
|
|
424
|
-
count: number,
|
|
425
|
-
config: ZeroLiteConfig
|
|
426
|
-
): Promise<PGlite[]> {
|
|
427
|
-
if (count <= 0) return []
|
|
428
|
-
|
|
429
|
-
const proxy = primary as unknown as PGliteWorkerProxy
|
|
430
|
-
if (typeof proxy.dumpDataDir !== 'function') {
|
|
431
|
-
log.pglite('read replicas require worker threads (dumpDataDir not available)')
|
|
432
|
-
return []
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
log.pglite(`creating ${count} read replica(s)...`)
|
|
436
|
-
const t0 = performance.now()
|
|
437
|
-
|
|
438
|
-
const dump = await proxy.dumpDataDir()
|
|
439
|
-
log.debug.pglite(`primary dump: ${(dump.byteLength / 1024 / 1024).toFixed(1)}MB`)
|
|
440
|
-
|
|
441
|
-
const {
|
|
442
|
-
dataDir: _ud,
|
|
443
|
-
debug: _dbg,
|
|
444
|
-
...userOpts
|
|
445
|
-
} = config.pgliteOptions as Record<string, any>
|
|
446
|
-
|
|
447
|
-
const replicas: PGliteWorkerProxy[] = []
|
|
448
|
-
for (let i = 0; i < count; i++) {
|
|
449
|
-
const replica = new PGliteWorkerProxy({
|
|
450
|
-
dataDir: 'memory://',
|
|
451
|
-
name: `postgres-replica-${i}`,
|
|
452
|
-
withExtensions: true,
|
|
453
|
-
debug: config.logLevel === 'debug' ? 1 : 0,
|
|
454
|
-
pgliteOptions: userOpts,
|
|
455
|
-
loadDataDir: dump,
|
|
456
|
-
})
|
|
457
|
-
replicas.push(replica)
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
await Promise.all(replicas.map((r) => r.waitReady))
|
|
461
|
-
log.pglite(`${count} read replica(s) ready in ${(performance.now() - t0).toFixed(0)}ms`)
|
|
462
|
-
|
|
463
|
-
return replicas as unknown as PGlite[]
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
/** run pending migrations, returns count of newly applied migrations */
|
|
467
|
-
export async function runMigrations(db: PGlite, config: ZeroLiteConfig): Promise<number> {
|
|
468
|
-
if (!config.migrationsDir) {
|
|
469
|
-
log.debug.orez('no migrations directory configured, skipping')
|
|
470
|
-
return 0
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
const migrationsDir = resolve(config.migrationsDir)
|
|
474
|
-
if (!existsSync(migrationsDir)) {
|
|
475
|
-
log.debug.orez('no migrations directory found, skipping')
|
|
476
|
-
return 0
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
// create migrations tracking table
|
|
480
|
-
await db.exec(`
|
|
481
|
-
CREATE TABLE IF NOT EXISTS public.migrations (
|
|
482
|
-
id SERIAL PRIMARY KEY,
|
|
483
|
-
name TEXT NOT NULL UNIQUE,
|
|
484
|
-
applied_at TIMESTAMPTZ DEFAULT NOW()
|
|
485
|
-
)
|
|
486
|
-
`)
|
|
487
|
-
|
|
488
|
-
// read drizzle journal for correct migration order
|
|
489
|
-
const journalPath = join(migrationsDir, 'meta', '_journal.json')
|
|
490
|
-
let files: string[]
|
|
491
|
-
if (existsSync(journalPath)) {
|
|
492
|
-
const journal = JSON.parse(readFileSync(journalPath, 'utf-8'))
|
|
493
|
-
files = journal.entries.map((e: { tag: string }) => `${e.tag}.sql`)
|
|
494
|
-
} else {
|
|
495
|
-
files = readdirSync(migrationsDir)
|
|
496
|
-
.filter((f) => f.endsWith('.sql'))
|
|
497
|
-
.sort()
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
let applied = 0
|
|
501
|
-
for (const file of files) {
|
|
502
|
-
const name = file.replace(/\.sql$/, '')
|
|
503
|
-
|
|
504
|
-
// check if already applied
|
|
505
|
-
const result = await db.query<{ count: string }>(
|
|
506
|
-
'SELECT count(*) as count FROM public.migrations WHERE name = $1',
|
|
507
|
-
[name]
|
|
508
|
-
)
|
|
509
|
-
if (Number(result.rows[0].count) > 0) {
|
|
510
|
-
continue
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
log.debug.orez(`applying migration: ${name}`)
|
|
514
|
-
const sql = readFileSync(join(migrationsDir, file), 'utf-8')
|
|
515
|
-
|
|
516
|
-
// split by drizzle's statement-breakpoint marker
|
|
517
|
-
const statements = sql
|
|
518
|
-
.split('--> statement-breakpoint')
|
|
519
|
-
.map((s) => s.trim())
|
|
520
|
-
.filter(Boolean)
|
|
521
|
-
|
|
522
|
-
for (const stmt of statements) {
|
|
523
|
-
await db.exec(stmt)
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
await db.query('INSERT INTO public.migrations (name) VALUES ($1)', [name])
|
|
527
|
-
log.debug.orez(`applied migration: ${name}`)
|
|
528
|
-
applied++
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
log.debug.orez('migrations complete')
|
|
532
|
-
return applied
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* run periodic CHECKPOINT on all pglite instances to compact WAL files.
|
|
537
|
-
* without this, pg_wal/ grows unboundedly since relaxedDurability defers writes.
|
|
538
|
-
* returns a cleanup function to stop the timer.
|
|
539
|
-
*/
|
|
540
|
-
export function startPeriodicCheckpoint(
|
|
541
|
-
instances: PGliteInstances,
|
|
542
|
-
intervalMs = 5 * 60 * 1000
|
|
543
|
-
): () => void {
|
|
544
|
-
const checkpoint = async () => {
|
|
545
|
-
for (const [name, db] of Object.entries(instances) as [string, PGlite][]) {
|
|
546
|
-
if (!db || name === 'postgresReplicas') continue
|
|
547
|
-
try {
|
|
548
|
-
await db.exec('CHECKPOINT')
|
|
549
|
-
} catch {}
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
const timer = setInterval(checkpoint, intervalMs)
|
|
553
|
-
if (timer.unref) timer.unref()
|
|
554
|
-
// run one immediately on startup to reclaim any WAL from previous runs
|
|
555
|
-
checkpoint()
|
|
556
|
-
return () => clearInterval(timer)
|
|
557
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { PGliteWebProxy } from './pglite-web-proxy.js'
|
|
4
|
-
|
|
5
|
-
class FakeWorker {
|
|
6
|
-
messages: unknown[] = []
|
|
7
|
-
terminated = false
|
|
8
|
-
private listeners = new Map<string, Set<(event: any) => void>>()
|
|
9
|
-
|
|
10
|
-
addEventListener(type: string, handler: (event: any) => void) {
|
|
11
|
-
let handlers = this.listeners.get(type)
|
|
12
|
-
if (!handlers) {
|
|
13
|
-
handlers = new Set()
|
|
14
|
-
this.listeners.set(type, handlers)
|
|
15
|
-
}
|
|
16
|
-
handlers.add(handler)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
removeEventListener(type: string, handler: (event: any) => void) {
|
|
20
|
-
this.listeners.get(type)?.delete(handler)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
postMessage(message: unknown) {
|
|
24
|
-
this.messages.push(message)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
terminate() {
|
|
28
|
-
this.terminated = true
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
dispatch(type: string, event: any) {
|
|
32
|
-
for (const handler of this.listeners.get(type) ?? []) {
|
|
33
|
-
handler(event)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
describe('PGliteWebProxy', () => {
|
|
39
|
-
test('rejects pending and future requests when the worker errors', async () => {
|
|
40
|
-
const worker = new FakeWorker()
|
|
41
|
-
const proxy = new PGliteWebProxy(worker as unknown as Worker, 'postgres')
|
|
42
|
-
|
|
43
|
-
worker.dispatch('message', { data: { type: 'ready' } })
|
|
44
|
-
await proxy.waitReady
|
|
45
|
-
|
|
46
|
-
const pending = proxy.query('SELECT 1')
|
|
47
|
-
expect(worker.messages).toHaveLength(1)
|
|
48
|
-
|
|
49
|
-
worker.dispatch('error', {
|
|
50
|
-
error: new Error('pglite worker crashed'),
|
|
51
|
-
message: 'pglite worker crashed',
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
await expect(pending).rejects.toThrow('pglite worker crashed')
|
|
55
|
-
await expect(proxy.query('SELECT 2')).rejects.toThrow('pglite worker crashed')
|
|
56
|
-
})
|
|
57
|
-
})
|