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