@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,595 @@
1
+ /**
2
+ * Lazy PGLite Manager for PostgresDO
3
+ *
4
+ * SPIKE: Lazy WASM Loading Experiment
5
+ *
6
+ * This module implements a lazy loading variant of PGLiteManager that defers
7
+ * WASM loading until the first query is executed, rather than loading on
8
+ * DO instantiation.
9
+ *
10
+ * ## Hypothesis
11
+ *
12
+ * Loading WASM only on first query (not on DO instantiation) may improve:
13
+ * 1. Cold start perception (faster initial response for non-query endpoints)
14
+ * 2. Memory usage (don't load if no queries made)
15
+ *
16
+ * ## Trade-offs
17
+ *
18
+ * - Eager loading: ~1200ms cold start, all subsequent requests fast
19
+ * - Lazy loading: ~0ms until first query, first query pays ~1200ms, subsequent fast
20
+ *
21
+ * For workloads where:
22
+ * - Many requests hit non-query endpoints (health checks, metadata) -> lazy wins
23
+ * - All requests are queries -> eager wins (same total latency, better predictability)
24
+ * - Mixed workload -> depends on query ratio
25
+ *
26
+ * ## Implementation
27
+ *
28
+ * The manager provides:
29
+ * - `isWASMLoaded()` - Check if WASM is loaded without triggering load
30
+ * - `ensureWASMLoaded()` - Explicitly trigger WASM loading
31
+ * - `query()` - Execute query (lazy loads WASM if needed)
32
+ * - Promise deduplication for concurrent first queries
33
+ *
34
+ * @module worker/lazy-pglite-manager
35
+ */
36
+
37
+ import type { PostgresConfig } from './types'
38
+ import { PluginManager, type PluginManagerConfig } from './plugin-manager'
39
+ import type { PGliteLike } from './do-pglite-manager'
40
+
41
+ // Import WASM module and data bundle for Workers
42
+ // These are bundled at build time as CompiledWasm and Data modules
43
+ import pgliteWasm from '../pglite-assets/pglite.wasm'
44
+ import pgliteData from '../pglite-assets/pglite.data'
45
+
46
+ // Import Workers-compatible PGLite wrapper
47
+ import { createWorkersPGLite, type WorkersPGLite } from '../pglite/workers-pglite'
48
+
49
+ // =============================================================================
50
+ // Module-Level Lazy WASM State (for hoisting across DO reinstantiation)
51
+ // =============================================================================
52
+
53
+ /**
54
+ * Hoisted PGLite instance - survives DO class reinstantiation within same isolate.
55
+ * Same as eager loading, but only populated on first query.
56
+ */
57
+ let lazyHoistedPglite: WorkersPGLite | null = null
58
+
59
+ /**
60
+ * Promise for in-progress PGLite initialization.
61
+ * Prevents duplicate WASM loading when multiple queries execute concurrently.
62
+ */
63
+ let lazyHoistedPglitePromise: Promise<WorkersPGLite> | null = null
64
+
65
+ /**
66
+ * Timestamp when WASM was first loaded (for diagnostics)
67
+ */
68
+ let wasmLoadedAt: number | null = null
69
+
70
+ /**
71
+ * Unique identifier for this module instance (for debugging/diagnostics).
72
+ */
73
+ const LAZY_MODULE_INSTANCE_ID = Math.random().toString(36).slice(2, 10)
74
+
75
+ /**
76
+ * Check if the lazy hoisted PGLite instance exists.
77
+ */
78
+ export function hasLazyHoistedPglite(): boolean {
79
+ return lazyHoistedPglite !== null
80
+ }
81
+
82
+ /**
83
+ * Get diagnostics about the lazy hoisted WASM state.
84
+ */
85
+ export function getLazyHoistedPgliteDiagnostics(): {
86
+ hasInstance: boolean
87
+ hasPendingPromise: boolean
88
+ moduleInstanceId: string
89
+ wasmLoadedAt: number | null
90
+ timeSinceLoad: number | null
91
+ } {
92
+ const now = Date.now()
93
+ return {
94
+ hasInstance: lazyHoistedPglite !== null,
95
+ hasPendingPromise: lazyHoistedPglitePromise !== null,
96
+ moduleInstanceId: LAZY_MODULE_INSTANCE_ID,
97
+ wasmLoadedAt,
98
+ timeSinceLoad: wasmLoadedAt !== null ? now - wasmLoadedAt : null,
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Reset the lazy hoisted PGLite instance.
104
+ * WARNING: This should only be used in tests.
105
+ * @internal
106
+ */
107
+ export function resetLazyHoistedPglite(): void {
108
+ lazyHoistedPglite = null
109
+ lazyHoistedPglitePromise = null
110
+ wasmLoadedAt = null
111
+ }
112
+
113
+ /**
114
+ * Lazily get or create the hoisted PGLite instance.
115
+ *
116
+ * This function is called ONLY when a query is executed, not during
117
+ * manager initialization.
118
+ */
119
+ async function lazyGetOrCreateHoistedPglite(options: {
120
+ database?: string
121
+ debug?: 0 | 1
122
+ }): Promise<WorkersPGLite> {
123
+ // Fast path: already initialized
124
+ if (lazyHoistedPglite) {
125
+ return lazyHoistedPglite
126
+ }
127
+
128
+ // Deduplication: return existing promise if initialization in progress
129
+ if (lazyHoistedPglitePromise) {
130
+ return lazyHoistedPglitePromise
131
+ }
132
+
133
+ const loadStartTime = performance.now()
134
+
135
+ // Start initialization
136
+ lazyHoistedPglitePromise = createWorkersPGLite({
137
+ wasmModule: pgliteWasm,
138
+ fsBundle: pgliteData,
139
+ database: options.database ?? 'postgres',
140
+ debug: options.debug ?? 0,
141
+ })
142
+
143
+ try {
144
+ lazyHoistedPglite = await lazyHoistedPglitePromise
145
+ wasmLoadedAt = Date.now()
146
+
147
+ const loadDuration = performance.now() - loadStartTime
148
+ console.log(
149
+ `[LazyPGLiteManager] WASM LOADED (lazy, on first query) - took ${loadDuration.toFixed(2)}ms, instance: ${LAZY_MODULE_INSTANCE_ID}`
150
+ )
151
+
152
+ return lazyHoistedPglite
153
+ } finally {
154
+ // Clear the promise after resolution (success or failure)
155
+ lazyHoistedPglitePromise = null
156
+ }
157
+ }
158
+
159
+ // =============================================================================
160
+ // Loading State Enum
161
+ // =============================================================================
162
+
163
+ /**
164
+ * WASM loading state for diagnostics
165
+ */
166
+ export type WASMLoadingState = 'not_loaded' | 'loading' | 'loaded' | 'error'
167
+
168
+ // =============================================================================
169
+ // Configuration
170
+ // =============================================================================
171
+
172
+ /**
173
+ * Configuration for Lazy PGLite manager
174
+ */
175
+ export interface LazyPGLiteManagerConfig {
176
+ /** Database name */
177
+ database?: string
178
+ /** Enable debug mode */
179
+ debug?: boolean
180
+ /** Plugin configuration */
181
+ plugins?: PostgresConfig['plugins']
182
+ /** Custom PGLite factory for testing */
183
+ createPGLite?: () => Promise<PGliteLike>
184
+ /**
185
+ * Disable WASM hoisting optimization.
186
+ * When false (default), the PGLite WASM instance is cached at module level.
187
+ * @default false
188
+ */
189
+ disableHoisting?: boolean
190
+ }
191
+
192
+ // =============================================================================
193
+ // LazyPGLiteManager Class
194
+ // =============================================================================
195
+
196
+ /**
197
+ * LazyPGLiteManager defers WASM loading until the first query.
198
+ *
199
+ * Unlike the eager PGLiteManager, this manager does NOT load WASM during
200
+ * initialize(). WASM is only loaded when query() or ensureWASMLoaded() is called.
201
+ *
202
+ * Benefits:
203
+ * - Non-query endpoints (health, metadata, stats) respond immediately
204
+ * - Memory is not consumed until queries are actually executed
205
+ * - Better cold start perception for mixed workloads
206
+ *
207
+ * Trade-offs:
208
+ * - First query incurs the full WASM loading time (~1200ms)
209
+ * - Less predictable query latency (first vs subsequent)
210
+ *
211
+ * @example
212
+ * ```typescript
213
+ * const manager = new LazyPGLiteManager({ database: 'mydb' })
214
+ *
215
+ * // Initialize (fast - no WASM loading)
216
+ * await manager.initialize()
217
+ *
218
+ * // Check if WASM is loaded (without triggering load)
219
+ * console.log(manager.isWASMLoaded()) // false
220
+ *
221
+ * // First query triggers WASM load (~1200ms)
222
+ * const result1 = await manager.query('SELECT 1')
223
+ *
224
+ * // Now WASM is loaded
225
+ * console.log(manager.isWASMLoaded()) // true
226
+ *
227
+ * // Subsequent queries are fast (~ms)
228
+ * const result2 = await manager.query('SELECT 2')
229
+ * ```
230
+ */
231
+ export class LazyPGLiteManager {
232
+ private pglite: PGliteLike | null = null
233
+ private initialized = false
234
+ private loadingState: WASMLoadingState = 'not_loaded'
235
+ private loadError: Error | null = null
236
+ private config: LazyPGLiteManagerConfig
237
+ private pluginManager: PluginManager
238
+
239
+ /** Track whether this manager is using the hoisted instance */
240
+ private usingHoistedInstance = false
241
+
242
+ /** Timing metrics for diagnostics */
243
+ private metrics = {
244
+ initializeCalledAt: null as number | null,
245
+ firstQueryAt: null as number | null,
246
+ wasmLoadStartAt: null as number | null,
247
+ wasmLoadEndAt: null as number | null,
248
+ }
249
+
250
+ constructor(config: LazyPGLiteManagerConfig = {}) {
251
+ this.config = {
252
+ database: config.database ?? 'postgres',
253
+ debug: config.debug ?? false,
254
+ }
255
+ if (config.plugins !== undefined) {
256
+ this.config.plugins = config.plugins
257
+ }
258
+ if (config.createPGLite !== undefined) {
259
+ this.config.createPGLite = config.createPGLite
260
+ }
261
+ if (config.disableHoisting !== undefined) {
262
+ this.config.disableHoisting = config.disableHoisting
263
+ }
264
+
265
+ // Build plugin manager config
266
+ const pluginManagerConfig: PluginManagerConfig = {}
267
+ if (config.plugins !== undefined) {
268
+ pluginManagerConfig.plugins = config.plugins
269
+ }
270
+ if (config.debug !== undefined) {
271
+ pluginManagerConfig.debug = config.debug
272
+ }
273
+ this.pluginManager = new PluginManager(pluginManagerConfig)
274
+ }
275
+
276
+ /**
277
+ * Check if the manager is initialized.
278
+ * Note: This returns true after initialize() is called, even if WASM is not loaded.
279
+ */
280
+ isInitialized(): boolean {
281
+ return this.initialized
282
+ }
283
+
284
+ /**
285
+ * Check if WASM is loaded (without triggering load).
286
+ * This is the key difference from eager loading.
287
+ */
288
+ isWASMLoaded(): boolean {
289
+ return this.pglite !== null
290
+ }
291
+
292
+ /**
293
+ * Get the current WASM loading state.
294
+ */
295
+ getLoadingState(): WASMLoadingState {
296
+ return this.loadingState
297
+ }
298
+
299
+ /**
300
+ * Get any error that occurred during WASM loading.
301
+ */
302
+ getLoadError(): Error | null {
303
+ return this.loadError
304
+ }
305
+
306
+ /**
307
+ * Get the PGLite instance (throws if not loaded).
308
+ * For lazy loading, prefer using query() which handles loading automatically.
309
+ */
310
+ getInstance(): PGliteLike {
311
+ if (!this.pglite) {
312
+ throw new Error('PGLite WASM not loaded. Call ensureWASMLoaded() or query() first.')
313
+ }
314
+ return this.pglite
315
+ }
316
+
317
+ /**
318
+ * Get the PGLite instance or null if not loaded.
319
+ */
320
+ getInstanceOrNull(): PGliteLike | null {
321
+ return this.pglite
322
+ }
323
+
324
+ /**
325
+ * Get the plugin manager.
326
+ */
327
+ getPluginManager(): PluginManager {
328
+ return this.pluginManager
329
+ }
330
+
331
+ /**
332
+ * Get timing metrics for diagnostics.
333
+ */
334
+ getMetrics(): {
335
+ initializeCalledAt: number | null
336
+ firstQueryAt: number | null
337
+ wasmLoadStartAt: number | null
338
+ wasmLoadEndAt: number | null
339
+ wasmLoadDurationMs: number | null
340
+ timeToFirstQuery: number | null
341
+ } {
342
+ const wasmLoadDurationMs =
343
+ this.metrics.wasmLoadStartAt !== null && this.metrics.wasmLoadEndAt !== null
344
+ ? this.metrics.wasmLoadEndAt - this.metrics.wasmLoadStartAt
345
+ : null
346
+
347
+ const timeToFirstQuery =
348
+ this.metrics.initializeCalledAt !== null && this.metrics.firstQueryAt !== null
349
+ ? this.metrics.firstQueryAt - this.metrics.initializeCalledAt
350
+ : null
351
+
352
+ return {
353
+ ...this.metrics,
354
+ wasmLoadDurationMs,
355
+ timeToFirstQuery,
356
+ }
357
+ }
358
+
359
+ /**
360
+ * Initialize the manager (does NOT load WASM).
361
+ *
362
+ * This is intentionally fast - WASM loading is deferred to first query.
363
+ * Safe to call multiple times.
364
+ */
365
+ async initialize(): Promise<void> {
366
+ if (this.initialized) {
367
+ return
368
+ }
369
+
370
+ this.metrics.initializeCalledAt = performance.now()
371
+ this.initialized = true
372
+
373
+ console.log(`[LazyPGLiteManager] Initialized (WASM not loaded yet)`)
374
+ }
375
+
376
+ /**
377
+ * Explicitly ensure WASM is loaded.
378
+ *
379
+ * Use this if you want to pre-warm WASM loading before the first query,
380
+ * but still want the option to skip loading entirely for non-query requests.
381
+ */
382
+ async ensureWASMLoaded(): Promise<void> {
383
+ if (this.pglite) {
384
+ return
385
+ }
386
+
387
+ await this.loadWASM()
388
+ }
389
+
390
+ /**
391
+ * Internal method to load WASM.
392
+ */
393
+ private async loadWASM(): Promise<PGliteLike> {
394
+ if (this.pglite) {
395
+ return this.pglite
396
+ }
397
+
398
+ if (this.loadingState === 'loading') {
399
+ // Wait for existing load to complete
400
+ if (lazyHoistedPglitePromise) {
401
+ const result = await lazyHoistedPglitePromise
402
+ this.pglite = result
403
+ this.loadingState = 'loaded'
404
+ return result
405
+ }
406
+ }
407
+
408
+ this.loadingState = 'loading'
409
+ this.metrics.wasmLoadStartAt = performance.now()
410
+
411
+ try {
412
+ // Use custom factory if provided (for testing)
413
+ if (this.config.createPGLite) {
414
+ this.usingHoistedInstance = false
415
+ this.pglite = await this.config.createPGLite()
416
+ } else if (this.config.disableHoisting) {
417
+ // Create new instance without hoisting
418
+ this.usingHoistedInstance = false
419
+ console.log(`[LazyPGLiteManager] WASM hoisting disabled - creating new instance`)
420
+ this.pglite = await createWorkersPGLite({
421
+ wasmModule: pgliteWasm,
422
+ fsBundle: pgliteData,
423
+ database: this.config.database ?? 'postgres',
424
+ debug: this.config.debug ? 1 : 0,
425
+ })
426
+ } else {
427
+ // Use the lazy hoisted instance
428
+ const wasmWasAlreadyLoaded = lazyHoistedPglite !== null
429
+ this.pglite = await lazyGetOrCreateHoistedPglite({
430
+ database: this.config.database,
431
+ debug: this.config.debug ? 1 : 0,
432
+ })
433
+ this.usingHoistedInstance = true
434
+
435
+ if (wasmWasAlreadyLoaded) {
436
+ const diagnostics = getLazyHoistedPgliteDiagnostics()
437
+ console.log(
438
+ `[LazyPGLiteManager] WASM REUSED (lazy hoisted) - loaded ${diagnostics.timeSinceLoad}ms ago, instance: ${diagnostics.moduleInstanceId}`
439
+ )
440
+ }
441
+ }
442
+
443
+ this.metrics.wasmLoadEndAt = performance.now()
444
+ this.loadingState = 'loaded'
445
+
446
+ // Wait for ready if needed
447
+ if (this.pglite.waitReady) {
448
+ await this.pglite.waitReady
449
+ }
450
+
451
+ // Run auto-create for plugins
452
+ await this.pluginManager.runAutoCreate(this.pglite)
453
+
454
+ return this.pglite
455
+ } catch (error) {
456
+ this.loadingState = 'error'
457
+ this.loadError = error instanceof Error ? error : new Error(String(error))
458
+ throw error
459
+ }
460
+ }
461
+
462
+ /**
463
+ * Execute a query (lazy loads WASM if needed).
464
+ *
465
+ * This is the primary method for interacting with the database.
466
+ * On first call, it will trigger WASM loading.
467
+ */
468
+ async query<T = unknown>(
469
+ sql: string,
470
+ params?: unknown[]
471
+ ): Promise<{
472
+ rows: T[]
473
+ fields: { name: string; dataTypeID: number }[]
474
+ affectedRows?: number
475
+ }> {
476
+ // Track first query time
477
+ if (this.metrics.firstQueryAt === null) {
478
+ this.metrics.firstQueryAt = performance.now()
479
+ }
480
+
481
+ // Lazy load WASM if not loaded
482
+ const pglite = await this.loadWASM()
483
+
484
+ // Execute query
485
+ return pglite.query<T>(sql, params)
486
+ }
487
+
488
+ /**
489
+ * Check if PGLite is responsive by running a simple query.
490
+ * This will trigger WASM loading if not already loaded.
491
+ */
492
+ async checkLiveness(): Promise<boolean> {
493
+ try {
494
+ const result = await this.query('SELECT 1 as alive')
495
+ return result.rows.length > 0
496
+ } catch {
497
+ return false
498
+ }
499
+ }
500
+
501
+ /**
502
+ * Close PGLite instance.
503
+ */
504
+ async close(timeoutMs: number = 5000): Promise<void> {
505
+ // If using hoisted instance, don't close it
506
+ if (this.usingHoistedInstance) {
507
+ console.log(`[LazyPGLiteManager] Skipping close of hoisted WASM instance (will be reused)`)
508
+ this.pglite = null
509
+ this.initialized = false
510
+ this.usingHoistedInstance = false
511
+ this.loadingState = 'not_loaded'
512
+ return
513
+ }
514
+
515
+ if (!this.pglite?.close) {
516
+ this.pglite = null
517
+ this.initialized = false
518
+ this.loadingState = 'not_loaded'
519
+ return
520
+ }
521
+
522
+ try {
523
+ await Promise.race([
524
+ this.pglite.close(),
525
+ new Promise<void>((_, reject) =>
526
+ setTimeout(() => reject(new Error('PGlite close timeout')), timeoutMs)
527
+ ),
528
+ ])
529
+ } catch (error) {
530
+ console.error('Error closing PGlite:', error)
531
+ }
532
+
533
+ this.pglite = null
534
+ this.initialized = false
535
+ this.loadingState = 'not_loaded'
536
+ }
537
+
538
+ /**
539
+ * Reset manager state (for testing or hibernation recovery).
540
+ */
541
+ reset(): void {
542
+ this.pglite = null
543
+ this.initialized = false
544
+ this.usingHoistedInstance = false
545
+ this.loadingState = 'not_loaded'
546
+ this.loadError = null
547
+ this.metrics = {
548
+ initializeCalledAt: null,
549
+ firstQueryAt: null,
550
+ wasmLoadStartAt: null,
551
+ wasmLoadEndAt: null,
552
+ }
553
+ }
554
+
555
+ /**
556
+ * Check if this manager is using the hoisted WASM instance.
557
+ */
558
+ isUsingHoistedInstance(): boolean {
559
+ return this.usingHoistedInstance
560
+ }
561
+
562
+ /**
563
+ * Get comprehensive diagnostics about lazy loading state.
564
+ */
565
+ getDiagnostics(): {
566
+ initialized: boolean
567
+ wasmLoaded: boolean
568
+ loadingState: WASMLoadingState
569
+ usingHoistedInstance: boolean
570
+ metrics: ReturnType<LazyPGLiteManager['getMetrics']>
571
+ hoisted: {
572
+ hasInstance: boolean
573
+ hasPendingPromise: boolean
574
+ moduleInstanceId: string
575
+ wasmLoadedAt: number | null
576
+ timeSinceLoad: number | null
577
+ }
578
+ } {
579
+ return {
580
+ initialized: this.initialized,
581
+ wasmLoaded: this.pglite !== null,
582
+ loadingState: this.loadingState,
583
+ usingHoistedInstance: this.usingHoistedInstance,
584
+ metrics: this.getMetrics(),
585
+ hoisted: getLazyHoistedPgliteDiagnostics(),
586
+ }
587
+ }
588
+ }
589
+
590
+ /**
591
+ * Factory function to create a LazyPGLiteManager.
592
+ */
593
+ export function createLazyPGLiteManager(config?: LazyPGLiteManagerConfig): LazyPGLiteManager {
594
+ return new LazyPGLiteManager(config)
595
+ }