@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.
- package/dist/backup/backup-manager.d.ts +244 -0
- package/dist/backup/backup-manager.d.ts.map +1 -0
- package/dist/backup/backup-manager.js +726 -0
- package/dist/backup/backup-manager.js.map +1 -0
- package/dist/observability/production-metrics.d.ts +318 -0
- package/dist/observability/production-metrics.d.ts.map +1 -0
- package/dist/observability/production-metrics.js +747 -0
- package/dist/observability/production-metrics.js.map +1 -0
- package/dist/pglite-assets/pglite.data +0 -0
- package/dist/pglite-assets/pglite.wasm +0 -0
- package/dist/pitr/pitr-manager.d.ts +240 -0
- package/dist/pitr/pitr-manager.d.ts.map +1 -0
- package/dist/pitr/pitr-manager.js +837 -0
- package/dist/pitr/pitr-manager.js.map +1 -0
- package/dist/streaming/cdc-iceberg-connector.d.ts +1 -1
- package/dist/streaming/cdc-iceberg-connector.js +1 -1
- package/dist/streaming/live-cdc-stream.d.ts +1 -1
- package/dist/streaming/live-cdc-stream.js +1 -1
- package/dist/worker/auth.d.ts.map +1 -1
- package/dist/worker/auth.js +16 -6
- package/dist/worker/auth.js.map +1 -1
- package/dist/worker/entry.d.ts.map +1 -1
- package/dist/worker/entry.js +108 -26
- package/dist/worker/entry.js.map +1 -1
- package/package.json +7 -6
- package/src/__tests__/backup.test.ts +944 -0
- package/src/__tests__/observability.test.ts +1089 -0
- package/src/__tests__/pitr.test.ts +1240 -0
- package/src/backup/backup-manager.ts +1006 -0
- package/src/observability/production-metrics.ts +1054 -0
- package/src/pglite-assets/pglite.data +0 -0
- package/src/pglite-assets/pglite.wasm +0 -0
- package/src/pitr/pitr-manager.ts +1136 -0
- package/src/worker/auth.ts +17 -6
- package/src/worker/entry.ts +112 -30
package/src/worker/auth.ts
CHANGED
|
@@ -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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
924
|
+
cache.set(token, validationResult, tokenCacheTTL)
|
|
914
925
|
}
|
|
915
926
|
}
|
|
916
927
|
|
package/src/worker/entry.ts
CHANGED
|
@@ -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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
387
|
-
|
|
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',
|