@dotdo/postgres 0.1.1 → 0.1.3

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 (111) hide show
  1. package/README.md +73 -1
  2. package/dist/client/index.d.ts +47 -0
  3. package/dist/client/index.d.ts.map +1 -0
  4. package/dist/client/index.js +47 -0
  5. package/dist/client/index.js.map +1 -0
  6. package/dist/client/postgres-client.d.ts +273 -0
  7. package/dist/client/postgres-client.d.ts.map +1 -0
  8. package/dist/client/postgres-client.js +389 -0
  9. package/dist/client/postgres-client.js.map +1 -0
  10. package/dist/client/types.d.ts +167 -0
  11. package/dist/client/types.d.ts.map +1 -0
  12. package/dist/client/types.js +7 -0
  13. package/dist/client/types.js.map +1 -0
  14. package/dist/do/index.d.ts +18 -0
  15. package/dist/do/index.d.ts.map +1 -0
  16. package/dist/do/index.js +18 -0
  17. package/dist/do/index.js.map +1 -0
  18. package/dist/do/postgres.d.ts +110 -0
  19. package/dist/do/postgres.d.ts.map +1 -0
  20. package/dist/do/postgres.js +266 -0
  21. package/dist/do/postgres.js.map +1 -0
  22. package/dist/do/sql.d.ts +92 -0
  23. package/dist/do/sql.d.ts.map +1 -0
  24. package/dist/do/sql.js +204 -0
  25. package/dist/do/sql.js.map +1 -0
  26. package/dist/index.d.ts +25 -30
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +29 -30
  29. package/dist/index.js.map +1 -1
  30. package/dist/mcp/binding.d.ts +47 -0
  31. package/dist/mcp/binding.d.ts.map +1 -0
  32. package/dist/mcp/binding.js +183 -0
  33. package/dist/mcp/binding.js.map +1 -0
  34. package/dist/mcp/index.d.ts +92 -0
  35. package/dist/mcp/index.d.ts.map +1 -0
  36. package/dist/mcp/index.js +91 -0
  37. package/dist/mcp/index.js.map +1 -0
  38. package/dist/mcp/server.d.ts +62 -0
  39. package/dist/mcp/server.d.ts.map +1 -0
  40. package/dist/mcp/server.js +278 -0
  41. package/dist/mcp/server.js.map +1 -0
  42. package/dist/mcp/tools.d.ts +58 -0
  43. package/dist/mcp/tools.d.ts.map +1 -0
  44. package/dist/mcp/tools.js +356 -0
  45. package/dist/mcp/tools.js.map +1 -0
  46. package/dist/mcp/types.d.ts +139 -0
  47. package/dist/mcp/types.d.ts.map +1 -0
  48. package/dist/mcp/types.js +7 -0
  49. package/dist/mcp/types.js.map +1 -0
  50. package/dist/pglite/workers-pglite.d.ts +13 -4
  51. package/dist/pglite/workers-pglite.d.ts.map +1 -1
  52. package/dist/pglite/workers-pglite.js +110 -5
  53. package/dist/pglite/workers-pglite.js.map +1 -1
  54. package/dist/pglite-assets/pglite.data +0 -0
  55. package/dist/pglite-assets/pglite.wasm +0 -0
  56. package/dist/worker/auth.d.ts.map +1 -1
  57. package/dist/worker/auth.js +16 -6
  58. package/dist/worker/auth.js.map +1 -1
  59. package/dist/worker/background-pglite-manager.d.ts +243 -0
  60. package/dist/worker/background-pglite-manager.d.ts.map +1 -0
  61. package/dist/worker/background-pglite-manager.js +528 -0
  62. package/dist/worker/background-pglite-manager.js.map +1 -0
  63. package/dist/worker/do-pglite-manager.d.ts +77 -0
  64. package/dist/worker/do-pglite-manager.d.ts.map +1 -1
  65. package/dist/worker/do-pglite-manager.js +189 -12
  66. package/dist/worker/do-pglite-manager.js.map +1 -1
  67. package/dist/worker/entry.d.ts.map +1 -1
  68. package/dist/worker/entry.js +108 -26
  69. package/dist/worker/entry.js.map +1 -1
  70. package/dist/worker/index.d.ts +7 -1
  71. package/dist/worker/index.d.ts.map +1 -1
  72. package/dist/worker/index.js +19 -1
  73. package/dist/worker/index.js.map +1 -1
  74. package/dist/worker/lazy-pglite-manager.d.ts +242 -0
  75. package/dist/worker/lazy-pglite-manager.d.ts.map +1 -0
  76. package/dist/worker/lazy-pglite-manager.js +463 -0
  77. package/dist/worker/lazy-pglite-manager.js.map +1 -0
  78. package/package.json +20 -6
  79. package/src/client/index.ts +61 -0
  80. package/src/client/postgres-client.ts +442 -0
  81. package/src/client/types.ts +211 -0
  82. package/src/do/index.ts +18 -0
  83. package/src/do/postgres.ts +367 -0
  84. package/src/do/sql.ts +280 -0
  85. package/src/index.ts +50 -30
  86. package/src/mcp/binding.ts +236 -0
  87. package/src/mcp/index.ts +122 -0
  88. package/src/mcp/server.ts +361 -0
  89. package/src/mcp/tools.ts +464 -0
  90. package/src/mcp/types.ts +148 -0
  91. package/src/pglite/workers-pglite.ts +141 -12
  92. package/src/pglite-assets/pglite.data +0 -0
  93. package/src/pglite-assets/pglite.wasm +0 -0
  94. package/src/worker/auth.ts +17 -6
  95. package/src/worker/background-pglite-manager.ts +680 -0
  96. package/src/worker/do-pglite-manager.ts +235 -19
  97. package/src/worker/entry.ts +112 -30
  98. package/src/worker/index.ts +71 -1
  99. package/src/worker/lazy-pglite-manager.ts +595 -0
  100. package/dist/iceberg/duckdb-wasm.d.ts +0 -447
  101. package/dist/iceberg/duckdb-wasm.d.ts.map +0 -1
  102. package/dist/iceberg/duckdb-wasm.js +0 -600
  103. package/dist/iceberg/duckdb-wasm.js.map +0 -1
  104. package/dist/iceberg/test-fixtures.d.ts +0 -151
  105. package/dist/iceberg/test-fixtures.d.ts.map +0 -1
  106. package/dist/iceberg/test-fixtures.js +0 -446
  107. package/dist/iceberg/test-fixtures.js.map +0 -1
  108. package/dist/worker/__mocks__/cloudflare-workers.d.ts +0 -31
  109. package/dist/worker/__mocks__/cloudflare-workers.d.ts.map +0 -1
  110. package/dist/worker/__mocks__/cloudflare-workers.js +0 -33
  111. package/dist/worker/__mocks__/cloudflare-workers.js.map +0 -1
@@ -0,0 +1,680 @@
1
+ /**
2
+ * Background PGLite Manager for PostgresDO
3
+ *
4
+ * This module implements a "eager-but-non-blocking" loading pattern:
5
+ * - WASM loading starts immediately on initialize() but doesn't block
6
+ * - Non-query endpoints respond instantly while WASM loads in background
7
+ * - Queries wait for WASM if not ready, but loading already started
8
+ * - Uses ctx.waitUntil() to keep DO alive during background loading
9
+ *
10
+ * ## Why This is Better Than Pure Lazy Loading
11
+ *
12
+ * Pure lazy loading just kicks the can down the road:
13
+ * - First query still pays full ~1200ms WASM load time
14
+ *
15
+ * Background loading gives us the best of both worlds:
16
+ * - Non-query endpoints respond instantly
17
+ * - WASM starts loading immediately
18
+ * - First query only waits for remaining load time (often near-zero)
19
+ * - If query arrives before WASM ready, it waits but loading is already in progress
20
+ *
21
+ * ## Usage with Durable Objects
22
+ *
23
+ * ```typescript
24
+ * export class MyDO extends DurableObject {
25
+ * private manager: BackgroundPGLiteManager
26
+ *
27
+ * constructor(ctx: DurableObjectState, env: Env) {
28
+ * super(ctx, env)
29
+ * this.manager = new BackgroundPGLiteManager({
30
+ * database: 'mydb',
31
+ * waitUntil: (p) => ctx.waitUntil(p), // Keep DO alive during load
32
+ * })
33
+ * }
34
+ *
35
+ * async init() {
36
+ * // Starts WASM loading in background, returns immediately
37
+ * await this.manager.initialize()
38
+ * }
39
+ *
40
+ * // Health check - responds instantly, triggers background load if needed
41
+ * async ping() {
42
+ * return { ok: true, wasmLoading: this.manager.isLoading() }
43
+ * }
44
+ *
45
+ * // Query - waits for WASM if not ready
46
+ * async query(sql: string) {
47
+ * return this.manager.query(sql)
48
+ * }
49
+ * }
50
+ * ```
51
+ *
52
+ * @module worker/background-pglite-manager
53
+ */
54
+
55
+ import type { PostgresConfig } from './types'
56
+ import { PluginManager, type PluginManagerConfig } from './plugin-manager'
57
+ import type { PGliteLike } from './do-pglite-manager'
58
+
59
+ // Import WASM module and data bundle for Workers
60
+ import pgliteWasm from '../pglite-assets/pglite.wasm'
61
+ import pgliteData from '../pglite-assets/pglite.data'
62
+
63
+ // Import Workers-compatible PGLite wrapper
64
+ import { createWorkersPGLite, type WorkersPGLite } from '../pglite/workers-pglite'
65
+
66
+ // =============================================================================
67
+ // Module-Level WASM State (for hoisting across DO reinstantiation)
68
+ // =============================================================================
69
+
70
+ /**
71
+ * Hoisted PGLite instance - survives DO class reinstantiation within same isolate.
72
+ */
73
+ let bgHoistedPglite: WorkersPGLite | null = null
74
+
75
+ /**
76
+ * Promise for in-progress PGLite initialization.
77
+ * Shared across all manager instances for deduplication.
78
+ */
79
+ let bgHoistedPglitePromise: Promise<WorkersPGLite> | null = null
80
+
81
+ /**
82
+ * Timestamp when WASM loading started
83
+ */
84
+ let wasmLoadStartedAt: number | null = null
85
+
86
+ /**
87
+ * Timestamp when WASM was loaded
88
+ */
89
+ let wasmLoadedAt: number | null = null
90
+
91
+ /**
92
+ * Unique identifier for this module instance
93
+ */
94
+ const BG_MODULE_INSTANCE_ID = Math.random().toString(36).slice(2, 10)
95
+
96
+ /**
97
+ * Check if the hoisted PGLite instance exists.
98
+ */
99
+ export function hasBgHoistedPglite(): boolean {
100
+ return bgHoistedPglite !== null
101
+ }
102
+
103
+ /**
104
+ * Check if WASM loading is in progress.
105
+ */
106
+ export function isBgWasmLoading(): boolean {
107
+ return bgHoistedPglitePromise !== null && bgHoistedPglite === null
108
+ }
109
+
110
+ /**
111
+ * Get diagnostics about the background hoisted WASM state.
112
+ */
113
+ export function getBgHoistedPgliteDiagnostics(): {
114
+ hasInstance: boolean
115
+ isLoading: boolean
116
+ moduleInstanceId: string
117
+ wasmLoadStartedAt: number | null
118
+ wasmLoadedAt: number | null
119
+ loadDurationMs: number | null
120
+ timeSinceLoadMs: number | null
121
+ } {
122
+ const now = Date.now()
123
+ return {
124
+ hasInstance: bgHoistedPglite !== null,
125
+ isLoading: bgHoistedPglitePromise !== null && bgHoistedPglite === null,
126
+ moduleInstanceId: BG_MODULE_INSTANCE_ID,
127
+ wasmLoadStartedAt,
128
+ wasmLoadedAt,
129
+ loadDurationMs: wasmLoadStartedAt && wasmLoadedAt ? wasmLoadedAt - wasmLoadStartedAt : null,
130
+ timeSinceLoadMs: wasmLoadedAt !== null ? now - wasmLoadedAt : null,
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Reset the hoisted PGLite instance.
136
+ * WARNING: This should only be used in tests.
137
+ * @internal
138
+ */
139
+ export function resetBgHoistedPglite(): void {
140
+ bgHoistedPglite = null
141
+ bgHoistedPglitePromise = null
142
+ wasmLoadStartedAt = null
143
+ wasmLoadedAt = null
144
+ }
145
+
146
+ /**
147
+ * Start WASM loading and return the promise.
148
+ * This is called by initialize() but not awaited.
149
+ */
150
+ function startWasmLoading(options: {
151
+ database?: string
152
+ debug?: 0 | 1
153
+ }): Promise<WorkersPGLite> {
154
+ // Fast path: already initialized
155
+ if (bgHoistedPglite) {
156
+ return Promise.resolve(bgHoistedPglite)
157
+ }
158
+
159
+ // Deduplication: return existing promise if loading in progress
160
+ if (bgHoistedPglitePromise) {
161
+ return bgHoistedPglitePromise
162
+ }
163
+
164
+ wasmLoadStartedAt = Date.now()
165
+ console.log(`[BackgroundPGLiteManager] Starting WASM load in background - instance: ${BG_MODULE_INSTANCE_ID}`)
166
+
167
+ // Start initialization
168
+ bgHoistedPglitePromise = createWorkersPGLite({
169
+ wasmModule: pgliteWasm,
170
+ fsBundle: pgliteData,
171
+ database: options.database ?? 'postgres',
172
+ debug: options.debug ?? 0,
173
+ }).then((pglite) => {
174
+ bgHoistedPglite = pglite
175
+ wasmLoadedAt = Date.now()
176
+ const loadDuration = wasmLoadedAt - (wasmLoadStartedAt ?? wasmLoadedAt)
177
+ console.log(
178
+ `[BackgroundPGLiteManager] WASM LOADED - took ${loadDuration}ms, instance: ${BG_MODULE_INSTANCE_ID}`
179
+ )
180
+ return pglite
181
+ }).catch((error) => {
182
+ console.error(`[BackgroundPGLiteManager] WASM load failed:`, error)
183
+ // Clear the promise so we can retry
184
+ bgHoistedPglitePromise = null
185
+ throw error
186
+ })
187
+
188
+ return bgHoistedPglitePromise
189
+ }
190
+
191
+ /**
192
+ * Get the hoisted instance, waiting for loading if in progress.
193
+ */
194
+ async function getOrAwaitHoistedPglite(options: {
195
+ database?: string
196
+ debug?: 0 | 1
197
+ }): Promise<WorkersPGLite> {
198
+ // Fast path: already loaded
199
+ if (bgHoistedPglite) {
200
+ return bgHoistedPglite
201
+ }
202
+
203
+ // Loading in progress - wait for it
204
+ if (bgHoistedPglitePromise) {
205
+ return bgHoistedPglitePromise
206
+ }
207
+
208
+ // Not started yet - start now (shouldn't happen if initialize() was called)
209
+ return startWasmLoading(options)
210
+ }
211
+
212
+ // =============================================================================
213
+ // Loading State
214
+ // =============================================================================
215
+
216
+ export type BGWASMLoadingState = 'not_started' | 'loading' | 'loaded' | 'error'
217
+
218
+ // =============================================================================
219
+ // Configuration
220
+ // =============================================================================
221
+
222
+ export interface BackgroundPGLiteManagerConfig {
223
+ /** Database name */
224
+ database?: string
225
+ /** Enable debug mode */
226
+ debug?: boolean
227
+ /** Plugin configuration */
228
+ plugins?: PostgresConfig['plugins']
229
+ /** Custom PGLite factory for testing */
230
+ createPGLite?: () => Promise<PGliteLike>
231
+ /**
232
+ * Disable WASM hoisting optimization.
233
+ * @default false
234
+ */
235
+ disableHoisting?: boolean
236
+ /**
237
+ * waitUntil function from Durable Object context.
238
+ * Used to keep the DO alive while WASM loads in background.
239
+ *
240
+ * @example
241
+ * ```typescript
242
+ * new BackgroundPGLiteManager({
243
+ * waitUntil: (p) => ctx.waitUntil(p)
244
+ * })
245
+ * ```
246
+ */
247
+ waitUntil?: (promise: Promise<unknown>) => void
248
+ }
249
+
250
+ // =============================================================================
251
+ // BackgroundPGLiteManager Class
252
+ // =============================================================================
253
+
254
+ /**
255
+ * BackgroundPGLiteManager starts WASM loading immediately but doesn't block.
256
+ *
257
+ * This gives the best of both worlds:
258
+ * - Non-query endpoints respond instantly
259
+ * - WASM starts loading on first init
260
+ * - Queries wait only for remaining load time (often zero if loaded)
261
+ * - Uses ctx.waitUntil() to keep DO alive during background loading
262
+ */
263
+ export class BackgroundPGLiteManager {
264
+ private pglite: PGliteLike | null = null
265
+ private initialized = false
266
+ private loadingState: BGWASMLoadingState = 'not_started'
267
+ private loadError: Error | null = null
268
+ private config: BackgroundPGLiteManagerConfig
269
+ private pluginManager: PluginManager
270
+ private usingHoistedInstance = false
271
+ private loadPromise: Promise<PGliteLike> | null = null
272
+
273
+ /** Timing metrics for diagnostics */
274
+ private metrics = {
275
+ initializeCalledAt: null as number | null,
276
+ wasmLoadStartAt: null as number | null,
277
+ wasmLoadEndAt: null as number | null,
278
+ firstQueryAt: null as number | null,
279
+ firstQueryWaitedMs: null as number | null,
280
+ }
281
+
282
+ constructor(config: BackgroundPGLiteManagerConfig = {}) {
283
+ this.config = {
284
+ database: config.database ?? 'postgres',
285
+ debug: config.debug ?? false,
286
+ ...config,
287
+ }
288
+
289
+ const pluginManagerConfig: PluginManagerConfig = {}
290
+ if (config.plugins !== undefined) {
291
+ pluginManagerConfig.plugins = config.plugins
292
+ }
293
+ if (config.debug !== undefined) {
294
+ pluginManagerConfig.debug = config.debug
295
+ }
296
+ this.pluginManager = new PluginManager(pluginManagerConfig)
297
+ }
298
+
299
+ /**
300
+ * Initialize the manager.
301
+ *
302
+ * This starts WASM loading in the background but returns immediately.
303
+ * Non-query operations can proceed while WASM loads.
304
+ *
305
+ * If waitUntil is provided, it's used to keep the DO alive during loading.
306
+ */
307
+ async initialize(): Promise<void> {
308
+ if (this.initialized) {
309
+ return
310
+ }
311
+
312
+ this.metrics.initializeCalledAt = Date.now()
313
+ this.initialized = true
314
+
315
+ // If custom factory provided, use it (for testing)
316
+ if (this.config.createPGLite) {
317
+ this.loadingState = 'loading'
318
+ this.loadPromise = this.config.createPGLite()
319
+
320
+ this.loadPromise.then((pg) => {
321
+ this.pglite = pg
322
+ this.loadingState = 'loaded'
323
+ this.metrics.wasmLoadEndAt = Date.now()
324
+ }).catch((err) => {
325
+ this.loadingState = 'error'
326
+ this.loadError = err
327
+ })
328
+
329
+ if (this.config.waitUntil) {
330
+ this.config.waitUntil(this.loadPromise.catch(() => {}))
331
+ }
332
+
333
+ console.log(`[BackgroundPGLiteManager] Initialized (custom factory, loading in background)`)
334
+ return
335
+ }
336
+
337
+ // If hoisting is disabled, start loading but don't hoist
338
+ if (this.config.disableHoisting) {
339
+ this.loadingState = 'loading'
340
+ this.metrics.wasmLoadStartAt = Date.now()
341
+
342
+ this.loadPromise = createWorkersPGLite({
343
+ wasmModule: pgliteWasm,
344
+ fsBundle: pgliteData,
345
+ database: this.config.database ?? 'postgres',
346
+ debug: this.config.debug ? 1 : 0,
347
+ })
348
+
349
+ this.loadPromise.then((pg) => {
350
+ this.pglite = pg
351
+ this.loadingState = 'loaded'
352
+ this.metrics.wasmLoadEndAt = Date.now()
353
+ console.log(`[BackgroundPGLiteManager] WASM LOADED (non-hoisted) - took ${this.metrics.wasmLoadEndAt - (this.metrics.wasmLoadStartAt ?? 0)}ms`)
354
+ }).catch((err) => {
355
+ this.loadingState = 'error'
356
+ this.loadError = err
357
+ console.error(`[BackgroundPGLiteManager] WASM load failed:`, err)
358
+ })
359
+
360
+ if (this.config.waitUntil) {
361
+ this.config.waitUntil(this.loadPromise.catch(() => {}))
362
+ }
363
+
364
+ console.log(`[BackgroundPGLiteManager] Initialized (hoisting disabled, loading in background)`)
365
+ return
366
+ }
367
+
368
+ // Use hoisted instance
369
+ this.usingHoistedInstance = true
370
+
371
+ // Check if already loaded
372
+ if (bgHoistedPglite) {
373
+ this.pglite = bgHoistedPglite
374
+ this.loadingState = 'loaded'
375
+ console.log(`[BackgroundPGLiteManager] Initialized (WASM already loaded, reusing hoisted instance)`)
376
+ return
377
+ }
378
+
379
+ // Start loading in background
380
+ this.loadingState = 'loading'
381
+ this.metrics.wasmLoadStartAt = Date.now()
382
+
383
+ const loadPromise = startWasmLoading({
384
+ database: this.config.database,
385
+ debug: this.config.debug ? 1 : 0,
386
+ })
387
+
388
+ // Track when loading completes for this manager
389
+ loadPromise.then((pg) => {
390
+ this.pglite = pg
391
+ this.loadingState = 'loaded'
392
+ this.metrics.wasmLoadEndAt = Date.now()
393
+ }).catch((err) => {
394
+ this.loadingState = 'error'
395
+ this.loadError = err
396
+ })
397
+
398
+ // Use waitUntil to keep DO alive during loading
399
+ if (this.config.waitUntil) {
400
+ this.config.waitUntil(loadPromise.catch(() => {}))
401
+ }
402
+
403
+ console.log(`[BackgroundPGLiteManager] Initialized (starting WASM load in background)`)
404
+ }
405
+
406
+ /**
407
+ * Check if WASM is fully loaded and ready for queries.
408
+ */
409
+ isWASMLoaded(): boolean {
410
+ return this.loadingState === 'loaded' && this.pglite !== null
411
+ }
412
+
413
+ /**
414
+ * Check if WASM is currently loading.
415
+ */
416
+ isLoading(): boolean {
417
+ return this.loadingState === 'loading'
418
+ }
419
+
420
+ /**
421
+ * Get the current loading state.
422
+ */
423
+ getLoadingState(): BGWASMLoadingState {
424
+ return this.loadingState
425
+ }
426
+
427
+ /**
428
+ * Get the PGLite instance, waiting for loading to complete if needed.
429
+ * This is called internally by query methods.
430
+ */
431
+ private async getPglite(): Promise<PGliteLike> {
432
+ // Already loaded
433
+ if (this.pglite) {
434
+ return this.pglite
435
+ }
436
+
437
+ // Using hoisted instance
438
+ if (this.usingHoistedInstance && !this.config.createPGLite) {
439
+ const waitStart = performance.now()
440
+ const pg = await getOrAwaitHoistedPglite({
441
+ database: this.config.database,
442
+ debug: this.config.debug ? 1 : 0,
443
+ })
444
+ this.pglite = pg
445
+ this.loadingState = 'loaded'
446
+ const waitMs = performance.now() - waitStart
447
+ if (waitMs > 1) {
448
+ console.log(`[BackgroundPGLiteManager] Query waited ${waitMs.toFixed(2)}ms for WASM to finish loading`)
449
+ }
450
+ return pg
451
+ }
452
+
453
+ // Using custom factory or non-hoisted
454
+ if (this.loadPromise) {
455
+ const waitStart = performance.now()
456
+ const pg = await this.loadPromise
457
+ const waitMs = performance.now() - waitStart
458
+ if (waitMs > 1) {
459
+ console.log(`[BackgroundPGLiteManager] Query waited ${waitMs.toFixed(2)}ms for WASM to finish loading`)
460
+ }
461
+ return pg
462
+ }
463
+
464
+ throw new Error('PGLite not initialized - call initialize() first')
465
+ }
466
+
467
+ /**
468
+ * Execute a query.
469
+ *
470
+ * If WASM is not loaded yet, this will wait for loading to complete.
471
+ * If WASM is already loaded, this returns immediately after query execution.
472
+ */
473
+ async query<T = unknown>(
474
+ sql: string,
475
+ params?: unknown[]
476
+ ): Promise<{
477
+ rows: T[]
478
+ fields: { name: string; dataTypeID: number }[]
479
+ affectedRows?: number
480
+ }> {
481
+ if (!this.initialized) {
482
+ throw new Error('Manager not initialized - call initialize() first')
483
+ }
484
+
485
+ if (this.loadingState === 'error') {
486
+ throw this.loadError ?? new Error('WASM loading failed')
487
+ }
488
+
489
+ // Track first query timing
490
+ if (this.metrics.firstQueryAt === null) {
491
+ this.metrics.firstQueryAt = Date.now()
492
+ }
493
+
494
+ const pg = await this.getPglite()
495
+ return pg.query<T>(sql, params)
496
+ }
497
+
498
+ /**
499
+ * Execute a SQL statement without returning rows.
500
+ */
501
+ async exec(sql: string): Promise<void> {
502
+ const pg = await this.getPglite()
503
+ if (pg.exec) {
504
+ await pg.exec(sql)
505
+ } else {
506
+ await pg.query(sql)
507
+ }
508
+ }
509
+
510
+ /**
511
+ * Check if the manager is initialized (not the same as WASM loaded).
512
+ */
513
+ isInitialized(): boolean {
514
+ return this.initialized
515
+ }
516
+
517
+ /**
518
+ * Get the PGLite instance if loaded, null otherwise.
519
+ * Does NOT wait for loading - use for checking readiness.
520
+ */
521
+ getInstanceOrNull(): PGliteLike | null {
522
+ return this.pglite
523
+ }
524
+
525
+ /**
526
+ * Get the PGLite instance (throws if not loaded).
527
+ * Use query() instead to automatically wait for loading.
528
+ */
529
+ getInstance(): PGliteLike {
530
+ if (!this.pglite) {
531
+ throw new Error('PGLite not loaded yet')
532
+ }
533
+ return this.pglite
534
+ }
535
+
536
+ /**
537
+ * Wait for WASM to be loaded.
538
+ * Call this if you want to ensure WASM is ready before proceeding.
539
+ */
540
+ async ensureWASMLoaded(): Promise<void> {
541
+ await this.getPglite()
542
+ }
543
+
544
+ /**
545
+ * Check if PGLite is responsive.
546
+ */
547
+ async checkLiveness(): Promise<boolean> {
548
+ if (!this.isWASMLoaded()) {
549
+ return false
550
+ }
551
+
552
+ try {
553
+ const result = await this.pglite!.query('SELECT 1 as alive')
554
+ return result.rows.length > 0
555
+ } catch {
556
+ return false
557
+ }
558
+ }
559
+
560
+ /**
561
+ * Get timing metrics for diagnostics.
562
+ */
563
+ getMetrics(): {
564
+ initializeCalledAt: number | null
565
+ wasmLoadStartAt: number | null
566
+ wasmLoadEndAt: number | null
567
+ wasmLoadDurationMs: number | null
568
+ firstQueryAt: number | null
569
+ timeFromInitToFirstQueryMs: number | null
570
+ isUsingHoistedInstance: boolean
571
+ } {
572
+ const loadDuration = this.metrics.wasmLoadStartAt && this.metrics.wasmLoadEndAt
573
+ ? this.metrics.wasmLoadEndAt - this.metrics.wasmLoadStartAt
574
+ : null
575
+ const initToFirstQuery = this.metrics.initializeCalledAt && this.metrics.firstQueryAt
576
+ ? this.metrics.firstQueryAt - this.metrics.initializeCalledAt
577
+ : null
578
+
579
+ return {
580
+ ...this.metrics,
581
+ wasmLoadDurationMs: loadDuration,
582
+ timeFromInitToFirstQueryMs: initToFirstQuery,
583
+ isUsingHoistedInstance: this.usingHoistedInstance,
584
+ }
585
+ }
586
+
587
+ /**
588
+ * Get comprehensive diagnostics.
589
+ */
590
+ getDiagnostics(): {
591
+ manager: {
592
+ initialized: boolean
593
+ loadingState: BGWASMLoadingState
594
+ hasInstance: boolean
595
+ usingHoistedInstance: boolean
596
+ error: string | null
597
+ }
598
+ module: ReturnType<typeof getBgHoistedPgliteDiagnostics>
599
+ metrics: ReturnType<BackgroundPGLiteManager['getMetrics']>
600
+ } {
601
+ return {
602
+ manager: {
603
+ initialized: this.initialized,
604
+ loadingState: this.loadingState,
605
+ hasInstance: this.pglite !== null,
606
+ usingHoistedInstance: this.usingHoistedInstance,
607
+ error: this.loadError?.message ?? null,
608
+ },
609
+ module: getBgHoistedPgliteDiagnostics(),
610
+ metrics: this.getMetrics(),
611
+ }
612
+ }
613
+
614
+ /**
615
+ * Close the manager.
616
+ * Note: Does NOT close hoisted instances (they're reused across DO reinstantiations).
617
+ */
618
+ async close(timeoutMs: number = 5000): Promise<void> {
619
+ // Don't close hoisted instances
620
+ if (this.usingHoistedInstance) {
621
+ console.log(`[BackgroundPGLiteManager] Skipping close of hoisted WASM instance (will be reused)`)
622
+ this.pglite = null
623
+ this.initialized = false
624
+ return
625
+ }
626
+
627
+ // Close non-hoisted instance
628
+ if (this.pglite?.close) {
629
+ try {
630
+ await Promise.race([
631
+ this.pglite.close(),
632
+ new Promise<void>((_, reject) =>
633
+ setTimeout(() => reject(new Error('Close timeout')), timeoutMs)
634
+ ),
635
+ ])
636
+ } catch (error) {
637
+ console.error('[BackgroundPGLiteManager] Error closing PGLite:', error)
638
+ }
639
+ }
640
+
641
+ this.pglite = null
642
+ this.initialized = false
643
+ this.loadingState = 'not_started'
644
+ }
645
+
646
+ /**
647
+ * Reset the manager state.
648
+ */
649
+ reset(): void {
650
+ this.pglite = null
651
+ this.initialized = false
652
+ this.loadingState = 'not_started'
653
+ this.loadError = null
654
+ this.loadPromise = null
655
+ this.usingHoistedInstance = false
656
+ this.metrics = {
657
+ initializeCalledAt: null,
658
+ wasmLoadStartAt: null,
659
+ wasmLoadEndAt: null,
660
+ firstQueryAt: null,
661
+ firstQueryWaitedMs: null,
662
+ }
663
+ }
664
+
665
+ /**
666
+ * Get the plugin manager.
667
+ */
668
+ getPluginManager(): PluginManager {
669
+ return this.pluginManager
670
+ }
671
+ }
672
+
673
+ /**
674
+ * Factory function to create a BackgroundPGLiteManager.
675
+ */
676
+ export function createBackgroundPGLiteManager(
677
+ config: BackgroundPGLiteManagerConfig = {}
678
+ ): BackgroundPGLiteManager {
679
+ return new BackgroundPGLiteManager(config)
680
+ }