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