orez 0.2.27 → 0.2.29

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.
Files changed (150) hide show
  1. package/package.json +3 -4
  2. package/src/admin/admin-data.test.ts +0 -348
  3. package/src/admin/http-proxy.ts +0 -252
  4. package/src/admin/log-store.ts +0 -192
  5. package/src/admin/server.ts +0 -471
  6. package/src/admin/ui.ts +0 -1322
  7. package/src/bench/proxy-throughput.bench.ts +0 -343
  8. package/src/bench/serial-mutations.bench.ts +0 -270
  9. package/src/browser.ts +0 -203
  10. package/src/cf-do/.wrangler/cache/cf.json +0 -1
  11. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite +0 -0
  12. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm +0 -0
  13. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal +0 -0
  14. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/0ffaabee41a60e04dd0eb7db3073f0a40139e6a97ccd26823967acb652b89a7b.sqlite +0 -0
  15. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite +0 -0
  16. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-shm +0 -0
  17. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-wal +0 -0
  18. package/src/cf-do/.wrangler/tmp/bundle-0z4CpE/middleware-insertion-facade.js +0 -11
  19. package/src/cf-do/.wrangler/tmp/bundle-0z4CpE/middleware-loader.entry.ts +0 -134
  20. package/src/cf-do/.wrangler/tmp/bundle-vYmw0E/middleware-insertion-facade.js +0 -11
  21. package/src/cf-do/.wrangler/tmp/bundle-vYmw0E/middleware-loader.entry.ts +0 -134
  22. package/src/cf-do/.wrangler/tmp/dev-cbILNo/worker.js +0 -1059
  23. package/src/cf-do/.wrangler/tmp/dev-cbILNo/worker.js.map +0 -8
  24. package/src/cf-do/.wrangler/tmp/dev-qbho19/worker.js +0 -1059
  25. package/src/cf-do/.wrangler/tmp/dev-qbho19/worker.js.map +0 -8
  26. package/src/cf-do/ARCHITECTURE.md +0 -93
  27. package/src/cf-do/CHAT_E2E.md +0 -213
  28. package/src/cf-do/watermark.test.ts +0 -103
  29. package/src/cf-do/watermark.ts +0 -118
  30. package/src/cf-do/worker.ts +0 -1041
  31. package/src/cf-do/wrangler.toml +0 -11
  32. package/src/cf-pglite/README.md +0 -19
  33. package/src/change-tracking.ts +0 -25
  34. package/src/child-process.test.ts +0 -147
  35. package/src/child-process.ts +0 -90
  36. package/src/cli-entry.ts +0 -72
  37. package/src/cli.test.ts +0 -40
  38. package/src/cli.ts +0 -1214
  39. package/src/config.ts +0 -150
  40. package/src/do-sql-tracking.test.ts +0 -19
  41. package/src/do-sql-tracking.ts +0 -19
  42. package/src/index.ts +0 -1215
  43. package/src/integration/integration.test.ts +0 -517
  44. package/src/integration/native-binary.guard.test.ts +0 -13
  45. package/src/integration/native-startup.test.ts +0 -44
  46. package/src/integration/replication-latency.test.ts +0 -428
  47. package/src/integration/restore-live-stress.test.ts +0 -433
  48. package/src/integration/restore-reset.test.ts +0 -400
  49. package/src/integration/restore.test.ts +0 -274
  50. package/src/integration/test-permissions.ts +0 -147
  51. package/src/load-config.ts +0 -46
  52. package/src/log.ts +0 -96
  53. package/src/mutex.ts +0 -47
  54. package/src/pg-proxy-browser.singledb.test.ts +0 -233
  55. package/src/pg-proxy-browser.ts +0 -2022
  56. package/src/pg-proxy-do-backend.test.ts +0 -3890
  57. package/src/pg-proxy-do-backend.ts +0 -7191
  58. package/src/pg-proxy.ts +0 -1087
  59. package/src/pg-sqlite-compiler/README.md +0 -53
  60. package/src/pg-sqlite-compiler/catalog/seed.ts +0 -524
  61. package/src/pg-sqlite-compiler/fixtures/pgsqlite/arithmetic.json +0 -307
  62. package/src/pg-sqlite-compiler/fixtures/pgsqlite/array.json +0 -377
  63. package/src/pg-sqlite-compiler/fixtures/pgsqlite/cast.json +0 -12
  64. package/src/pg-sqlite-compiler/fixtures/pgsqlite/catalog.json +0 -447
  65. package/src/pg-sqlite-compiler/fixtures/pgsqlite/create-table.json +0 -32
  66. package/src/pg-sqlite-compiler/fixtures/pgsqlite/datetime.json +0 -397
  67. package/src/pg-sqlite-compiler/fixtures/pgsqlite/enum.json +0 -337
  68. package/src/pg-sqlite-compiler/fixtures/pgsqlite/insert.json +0 -337
  69. package/src/pg-sqlite-compiler/fixtures/pgsqlite/json.json +0 -537
  70. package/src/pg-sqlite-compiler/fixtures/pgsqlite/misc.json +0 -1837
  71. package/src/pg-sqlite-compiler/index.ts +0 -73
  72. package/src/pg-sqlite-compiler/integration.test.ts +0 -136
  73. package/src/pg-sqlite-compiler/passes/ast-utils.ts +0 -113
  74. package/src/pg-sqlite-compiler/passes/catalog.ts +0 -65
  75. package/src/pg-sqlite-compiler/passes/datetime.ts +0 -74
  76. package/src/pg-sqlite-compiler/passes/index.ts +0 -49
  77. package/src/pg-sqlite-compiler/passes/types.ts +0 -156
  78. package/src/pg-sqlite-compiler/smoke.test.ts +0 -69
  79. package/src/pg-sqlite-compiler/test/catalog.test.ts +0 -171
  80. package/src/pg-sqlite-compiler/test/corpus.test.ts +0 -161
  81. package/src/pg-sqlite-compiler/test/datetime.oracle.test.ts +0 -102
  82. package/src/pg-sqlite-compiler/test/oracle.ts +0 -237
  83. package/src/pg-sqlite-compiler/test/types.test.ts +0 -109
  84. package/src/pg-sqlite-compiler/types.ts +0 -63
  85. package/src/pglite-ipc.test.ts +0 -116
  86. package/src/pglite-ipc.ts +0 -266
  87. package/src/pglite-manager.ts +0 -557
  88. package/src/pglite-web-proxy.test.ts +0 -57
  89. package/src/pglite-web-proxy.ts +0 -221
  90. package/src/pglite-web-worker.ts +0 -152
  91. package/src/pglite-worker-thread.ts +0 -253
  92. package/src/port.ts +0 -25
  93. package/src/process-title.ts +0 -9
  94. package/src/recovery.ts +0 -155
  95. package/src/replication/change-tracker.test.ts +0 -357
  96. package/src/replication/change-tracker.ts +0 -279
  97. package/src/replication/handler.test.ts +0 -511
  98. package/src/replication/handler.ts +0 -1190
  99. package/src/replication/pgoutput-encoder.test.ts +0 -697
  100. package/src/replication/pgoutput-encoder.ts +0 -373
  101. package/src/replication/tcp-replication.test.ts +0 -876
  102. package/src/replication/zero-compat.test.ts +0 -1150
  103. package/src/restore-stress.test.ts +0 -188
  104. package/src/s3-local.ts +0 -203
  105. package/src/shim/hooks.mjs +0 -120
  106. package/src/shim/register.mjs +0 -4
  107. package/src/sqlite-mode/apply-mode.ts +0 -224
  108. package/src/sqlite-mode/index.ts +0 -15
  109. package/src/sqlite-mode/native-binary.ts +0 -89
  110. package/src/sqlite-mode/package-resolve.ts +0 -17
  111. package/src/sqlite-mode/resolve-mode.ts +0 -80
  112. package/src/sqlite-mode/shim-template.ts +0 -159
  113. package/src/sqlite-mode/sqlite-mode.test.ts +0 -427
  114. package/src/sqlite-mode/types.ts +0 -30
  115. package/src/vite-plugin.ts +0 -67
  116. package/src/wasm-sqlite.test.ts +0 -537
  117. package/src/worker/browser-admin.ts +0 -52
  118. package/src/worker/browser-build-config.test.ts +0 -71
  119. package/src/worker/browser-build-config.ts +0 -109
  120. package/src/worker/browser-embed-admin.test.ts +0 -75
  121. package/src/worker/browser-embed.ts +0 -345
  122. package/src/worker/cf-patches.ts +0 -384
  123. package/src/worker/embed-integration.test.ts +0 -321
  124. package/src/worker/index.ts +0 -138
  125. package/src/worker/shims/fastify.test.ts +0 -255
  126. package/src/worker/shims/fastify.ts +0 -306
  127. package/src/worker/shims/http-service.test.ts +0 -355
  128. package/src/worker/shims/http-service.ts +0 -293
  129. package/src/worker/shims/node-stub.ts +0 -290
  130. package/src/worker/shims/oxfmt.ts +0 -3
  131. package/src/worker/shims/postgres-browser.ts +0 -59
  132. package/src/worker/shims/postgres-socket.test.ts +0 -576
  133. package/src/worker/shims/postgres-socket.ts +0 -310
  134. package/src/worker/shims/postgres.test.ts +0 -364
  135. package/src/worker/shims/postgres.ts +0 -1454
  136. package/src/worker/shims/sqlite-browser.test.ts +0 -233
  137. package/src/worker/shims/sqlite-browser.ts +0 -175
  138. package/src/worker/shims/sqlite.test.ts +0 -786
  139. package/src/worker/shims/sqlite.ts +0 -978
  140. package/src/worker/shims/stream-browser.ts +0 -15
  141. package/src/worker/shims/ws-browser.test.ts +0 -205
  142. package/src/worker/shims/ws-browser.ts +0 -248
  143. package/src/worker/shims/ws.test.ts +0 -288
  144. package/src/worker/shims/ws.ts +0 -467
  145. package/src/worker/shims/zero-process-env.ts +0 -11
  146. package/src/worker/types.ts +0 -75
  147. package/src/worker/worker-integration.test.ts +0 -223
  148. package/src/worker/worker.test.ts +0 -136
  149. package/src/worker/zero-cache-embed-cf.ts +0 -463
  150. package/src/worker/zero-cache-embed.ts +0 -277
@@ -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
- })