@dotdo/postgres 0.1.0 → 0.1.2

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 (35) hide show
  1. package/dist/backup/backup-manager.d.ts +244 -0
  2. package/dist/backup/backup-manager.d.ts.map +1 -0
  3. package/dist/backup/backup-manager.js +726 -0
  4. package/dist/backup/backup-manager.js.map +1 -0
  5. package/dist/observability/production-metrics.d.ts +318 -0
  6. package/dist/observability/production-metrics.d.ts.map +1 -0
  7. package/dist/observability/production-metrics.js +747 -0
  8. package/dist/observability/production-metrics.js.map +1 -0
  9. package/dist/pglite-assets/pglite.data +0 -0
  10. package/dist/pglite-assets/pglite.wasm +0 -0
  11. package/dist/pitr/pitr-manager.d.ts +240 -0
  12. package/dist/pitr/pitr-manager.d.ts.map +1 -0
  13. package/dist/pitr/pitr-manager.js +837 -0
  14. package/dist/pitr/pitr-manager.js.map +1 -0
  15. package/dist/streaming/cdc-iceberg-connector.d.ts +1 -1
  16. package/dist/streaming/cdc-iceberg-connector.js +1 -1
  17. package/dist/streaming/live-cdc-stream.d.ts +1 -1
  18. package/dist/streaming/live-cdc-stream.js +1 -1
  19. package/dist/worker/auth.d.ts.map +1 -1
  20. package/dist/worker/auth.js +16 -6
  21. package/dist/worker/auth.js.map +1 -1
  22. package/dist/worker/entry.d.ts.map +1 -1
  23. package/dist/worker/entry.js +108 -26
  24. package/dist/worker/entry.js.map +1 -1
  25. package/package.json +7 -6
  26. package/src/__tests__/backup.test.ts +944 -0
  27. package/src/__tests__/observability.test.ts +1089 -0
  28. package/src/__tests__/pitr.test.ts +1240 -0
  29. package/src/backup/backup-manager.ts +1006 -0
  30. package/src/observability/production-metrics.ts +1054 -0
  31. package/src/pglite-assets/pglite.data +0 -0
  32. package/src/pglite-assets/pglite.wasm +0 -0
  33. package/src/pitr/pitr-manager.ts +1136 -0
  34. package/src/worker/auth.ts +17 -6
  35. package/src/worker/entry.ts +112 -30
@@ -737,9 +737,17 @@ const DEFAULT_CACHE_CONFIG: TokenCacheConfig = {
737
737
 
738
738
  /**
739
739
  * Global token validation cache instance.
740
+ * Lazy-initialized to avoid global scope async operations in Cloudflare Workers.
740
741
  * @internal
741
742
  */
742
- let tokenCache = new TokenCache(DEFAULT_CACHE_CONFIG)
743
+ let tokenCache: TokenCache | null = null
744
+
745
+ function getTokenCache(): TokenCache {
746
+ if (!tokenCache) {
747
+ tokenCache = new TokenCache(DEFAULT_CACHE_CONFIG)
748
+ }
749
+ return tokenCache
750
+ }
743
751
 
744
752
  /**
745
753
  * Reconfigures the global token cache with new settings.
@@ -756,7 +764,9 @@ let tokenCache = new TokenCache(DEFAULT_CACHE_CONFIG)
756
764
  * ```
757
765
  */
758
766
  export function configureTokenCache(config: TokenCacheConfig): void {
759
- tokenCache.dispose()
767
+ if (tokenCache) {
768
+ tokenCache.dispose()
769
+ }
760
770
  tokenCache = new TokenCache({
761
771
  ...DEFAULT_CACHE_CONFIG,
762
772
  ...config,
@@ -769,7 +779,7 @@ export function configureTokenCache(config: TokenCacheConfig): void {
769
779
  * @returns Cache stats including hits, misses, and size
770
780
  */
771
781
  export function getTokenCacheStats(): CacheStats {
772
- return tokenCache.stats
782
+ return getTokenCache().stats
773
783
  }
774
784
 
775
785
  /**
@@ -779,7 +789,7 @@ export function getTokenCacheStats(): CacheStats {
779
789
  * a thundering herd to the OAuth provider.
780
790
  */
781
791
  export function clearTokenCache(): void {
782
- tokenCache.clear()
792
+ getTokenCache().clear()
783
793
  }
784
794
 
785
795
  export { extractBearerToken }
@@ -902,7 +912,8 @@ export function createAuthMiddleware(config: AuthConfig = {}): MiddlewareHandler
902
912
  )
903
913
  }
904
914
 
905
- let validationResult = tokenCache.get(token)
915
+ const cache = getTokenCache()
916
+ let validationResult = cache.get(token)
906
917
 
907
918
  if (!validationResult) {
908
919
  validationResult = validateToken
@@ -910,7 +921,7 @@ export function createAuthMiddleware(config: AuthConfig = {}): MiddlewareHandler
910
921
  : await sharedValidateToken(token, { oauthUrl })
911
922
 
912
923
  if (validationResult.valid) {
913
- tokenCache.set(token, validationResult, tokenCacheTTL)
924
+ cache.set(token, validationResult, tokenCacheTTL)
914
925
  }
915
926
  }
916
927
 
@@ -152,42 +152,44 @@ function createApp() {
152
152
  app.post('/query', async (c) => {
153
153
  const dbId = c.req.query('db') || 'default'
154
154
 
155
- // Get the Durable Object stub
156
- const doId = c.env.POSTGRES_DO.idFromName(dbId)
157
- const stub = c.env.POSTGRES_DO.get(doId)
158
-
159
- // Forward to DO's /query endpoint
160
- const url = new URL(c.req.url)
161
- url.pathname = '/query'
162
- url.searchParams.delete('db')
163
-
164
- const doRequest = new Request(url.toString(), {
165
- method: 'POST',
166
- headers: c.req.raw.headers,
167
- body: c.req.raw.body,
168
- })
155
+ try {
156
+ const body = await c.req.json<{ sql: string; params?: unknown[] }>()
157
+ if (!body.sql) {
158
+ return c.json({ error: true, code: 'INVALID_REQUEST', message: 'Missing sql field' }, 400)
159
+ }
169
160
 
170
- return stub.fetch(doRequest)
161
+ // Get the Durable Object stub and call RPC method directly
162
+ const doId = c.env.POSTGRES_DO.idFromName(dbId)
163
+ const stub = c.env.POSTGRES_DO.get(doId) as DurableObjectStub<PostgresDO>
164
+ const result = await stub.rpcQuery(body.sql, body.params)
165
+ return c.json(result)
166
+ } catch (error) {
167
+ const message = error instanceof Error ? error.message : 'Unknown error'
168
+ return c.json({ error: true, code: 'QUERY_ERROR', message }, 500)
169
+ }
171
170
  })
172
171
 
173
172
  // Batch queries
174
173
  app.post('/batch', async (c) => {
175
174
  const dbId = c.req.query('db') || 'default'
176
175
 
177
- const doId = c.env.POSTGRES_DO.idFromName(dbId)
178
- const stub = c.env.POSTGRES_DO.get(doId)
179
-
180
- const url = new URL(c.req.url)
181
- url.pathname = '/batch'
182
- url.searchParams.delete('db')
183
-
184
- const doRequest = new Request(url.toString(), {
185
- method: 'POST',
186
- headers: c.req.raw.headers,
187
- body: c.req.raw.body,
188
- })
176
+ try {
177
+ const body = await c.req.json<{ queries: Array<{ sql: string; params?: unknown[] }>; transaction?: boolean }>()
178
+ if (!body.queries || !Array.isArray(body.queries)) {
179
+ return c.json({ error: true, code: 'INVALID_REQUEST', message: 'Missing queries array' }, 400)
180
+ }
189
181
 
190
- return stub.fetch(doRequest)
182
+ // Get the Durable Object stub and call RPC method directly
183
+ const doId = c.env.POSTGRES_DO.idFromName(dbId)
184
+ const stub = c.env.POSTGRES_DO.get(doId) as DurableObjectStub<PostgresDO>
185
+ const result = body.transaction
186
+ ? await stub.rpcBatchTransaction(body.queries)
187
+ : await stub.rpcBatch(body.queries)
188
+ return c.json(result)
189
+ } catch (error) {
190
+ const message = error instanceof Error ? error.message : 'Unknown error'
191
+ return c.json({ error: true, code: 'BATCH_ERROR', message }, 500)
192
+ }
191
193
  })
192
194
 
193
195
  // ==========================================================================
@@ -383,8 +385,88 @@ function createApp() {
383
385
  }
384
386
  })
385
387
 
386
- // 404 handler
387
- app.notFound((c) => {
388
+ // ==========================================================================
389
+ // Dynamic database routes (for postgres.do client compatibility)
390
+ // ==========================================================================
391
+ // This handles URLs like https://db.example.com.ai/neon-example/query
392
+ // where the first path segment is the database ID
393
+ // We use notFound to handle these since Hono matches more specific routes first
394
+
395
+ const RESERVED_PATHS = new Set(['ping', 'health', 'user', 'db', 'rpc', 'query', 'batch'])
396
+
397
+ // 404 handler - also handles dynamic database routes
398
+ // Supports postgres.do client URLs like https://db.api.qa/mydb/query
399
+ app.notFound(async (c) => {
400
+ // Try to handle as dynamic database route: /:dbId/...
401
+ const pathParts = c.req.path.split('/').filter(Boolean)
402
+ if (pathParts.length >= 1) {
403
+ const dbId = pathParts[0]
404
+
405
+ // Skip if it's a reserved path
406
+ if (!RESERVED_PATHS.has(dbId)) {
407
+ const remainingPath = '/' + pathParts.slice(1).join('/')
408
+
409
+ // Get the Durable Object stub
410
+ const doId = c.env.POSTGRES_DO.idFromName(dbId)
411
+ const stub = c.env.POSTGRES_DO.get(doId) as DurableObjectStub<PostgresDO>
412
+
413
+ // Handle specific paths via RPC
414
+ if (remainingPath === '/query' && c.req.method === 'POST') {
415
+ try {
416
+ const body = await c.req.json<{ sql: string; params?: unknown[] }>()
417
+ if (!body.sql) {
418
+ return c.json({ error: true, code: 'INVALID_REQUEST', message: 'Missing sql field' }, 400)
419
+ }
420
+ const result = await stub.rpcQuery(body.sql, body.params)
421
+ return c.json(result)
422
+ } catch (error) {
423
+ const message = error instanceof Error ? error.message : 'Unknown error'
424
+ return c.json({ error: true, code: 'QUERY_ERROR', message }, 500)
425
+ }
426
+ }
427
+
428
+ if (remainingPath === '/batch' && c.req.method === 'POST') {
429
+ try {
430
+ const body = await c.req.json<{ queries: Array<{ sql: string; params?: unknown[] }>; transaction?: boolean }>()
431
+ if (!body.queries || !Array.isArray(body.queries)) {
432
+ return c.json({ error: true, code: 'INVALID_REQUEST', message: 'Missing queries array' }, 400)
433
+ }
434
+ const result = body.transaction
435
+ ? await stub.rpcBatchTransaction(body.queries)
436
+ : await stub.rpcBatch(body.queries)
437
+ return c.json(result)
438
+ } catch (error) {
439
+ const message = error instanceof Error ? error.message : 'Unknown error'
440
+ return c.json({ error: true, code: 'BATCH_ERROR', message }, 500)
441
+ }
442
+ }
443
+
444
+ if (remainingPath === '/transaction' && c.req.method === 'POST') {
445
+ try {
446
+ const body = await c.req.json<{ queries: Array<{ sql: string; params?: unknown[] }>; options?: unknown }>()
447
+ if (!body.queries || !Array.isArray(body.queries)) {
448
+ return c.json({ error: true, code: 'INVALID_REQUEST', message: 'Missing queries array' }, 400)
449
+ }
450
+ const result = await stub.rpcBatchTransaction(body.queries)
451
+ return c.json(result)
452
+ } catch (error) {
453
+ const message = error instanceof Error ? error.message : 'Unknown error'
454
+ return c.json({ error: true, code: 'TRANSACTION_ERROR', message }, 500)
455
+ }
456
+ }
457
+
458
+ // For other paths, return database info
459
+ if (remainingPath === '/' || remainingPath === '') {
460
+ return c.json({
461
+ service: 'postgres.do',
462
+ database: dbId,
463
+ status: 'ready',
464
+ timestamp: new Date().toISOString(),
465
+ })
466
+ }
467
+ }
468
+ }
469
+
388
470
  return c.json({
389
471
  error: true,
390
472
  code: 'NOT_FOUND',