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.
- package/dist/cf-do/worker.d.ts.map +1 -1
- package/dist/cf-do/worker.js +9 -1
- package/dist/cf-do/worker.js.map +1 -1
- package/dist/pg-proxy-do-backend.d.ts +2 -0
- package/dist/pg-proxy-do-backend.d.ts.map +1 -1
- package/dist/pg-proxy-do-backend.js +49 -7
- package/dist/pg-proxy-do-backend.js.map +1 -1
- package/dist/pg-sqlite-compiler/catalog/seed.d.ts +67 -0
- package/dist/pg-sqlite-compiler/catalog/seed.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/catalog/seed.js +436 -0
- package/dist/pg-sqlite-compiler/catalog/seed.js.map +1 -0
- package/dist/pg-sqlite-compiler/index.d.ts +12 -0
- package/dist/pg-sqlite-compiler/index.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/index.js +59 -0
- package/dist/pg-sqlite-compiler/index.js.map +1 -0
- package/dist/pg-sqlite-compiler/passes/ast-utils.d.ts +48 -0
- package/dist/pg-sqlite-compiler/passes/ast-utils.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/passes/ast-utils.js +93 -0
- package/dist/pg-sqlite-compiler/passes/ast-utils.js.map +1 -0
- package/dist/pg-sqlite-compiler/passes/catalog.d.ts +34 -0
- package/dist/pg-sqlite-compiler/passes/catalog.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/passes/catalog.js +30 -0
- package/dist/pg-sqlite-compiler/passes/catalog.js.map +1 -0
- package/dist/pg-sqlite-compiler/passes/datetime.d.ts +21 -0
- package/dist/pg-sqlite-compiler/passes/datetime.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/passes/datetime.js +53 -0
- package/dist/pg-sqlite-compiler/passes/datetime.js.map +1 -0
- package/dist/pg-sqlite-compiler/passes/index.d.ts +21 -0
- package/dist/pg-sqlite-compiler/passes/index.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/passes/index.js +39 -0
- package/dist/pg-sqlite-compiler/passes/index.js.map +1 -0
- package/dist/pg-sqlite-compiler/passes/types.d.ts +41 -0
- package/dist/pg-sqlite-compiler/passes/types.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/passes/types.js +103 -0
- package/dist/pg-sqlite-compiler/passes/types.js.map +1 -0
- package/dist/pg-sqlite-compiler/test/oracle.d.ts +34 -0
- package/dist/pg-sqlite-compiler/test/oracle.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/test/oracle.js +204 -0
- package/dist/pg-sqlite-compiler/test/oracle.js.map +1 -0
- package/dist/pg-sqlite-compiler/types.d.ts +55 -0
- package/dist/pg-sqlite-compiler/types.d.ts.map +1 -0
- package/dist/pg-sqlite-compiler/types.js +2 -0
- package/dist/pg-sqlite-compiler/types.js.map +1 -0
- package/package.json +8 -4
- package/src/admin/admin-data.test.ts +0 -348
- package/src/admin/http-proxy.ts +0 -252
- package/src/admin/log-store.ts +0 -192
- package/src/admin/server.ts +0 -471
- package/src/admin/ui.ts +0 -1322
- package/src/bench/proxy-throughput.bench.ts +0 -343
- package/src/bench/serial-mutations.bench.ts +0 -270
- package/src/browser.ts +0 -203
- package/src/cf-do/.wrangler/cache/cf.json +0 -1
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm +0 -0
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/0f0f3bdf0abda097eb6f1246db4657d9fc622081362d894d82c1a1ce067b05b6.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/1ddd3a4a48a11b51658444f5458a1fb175194b1d5b6a5bda20ef3fe3205b900c.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/204a39120310d37e972c5914cfd71ad55c151bdb9e8ed289a5f8c5b052dd60e4.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/3835f242df9728adba3d127a238793fd054ed3e51df3f60749ee744c469bf2a2.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/4aa9c80eb716cf55b8995ccf7afab0b36c683e6da07d7c37a3f9c570136036df.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/533e2fd1d6ea46e7a9a0017916ef341802d438d72583462755f2c1f8225e9bf2.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/5ffa1aced1225ecaeac6366f7586aa3de92761cdff8711d81fbd81f248076abd.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/686c3a9f0d7e59ed2ab607efd4b76d779c97cafeb3818380033bf7c7eb86c819.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/6e8214e8dcfadd0deb52d64e5e9ca85c6b329ace11193909845995396914c473.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/78d9ec9ff873d3fe3507ff53c2a6f6dfc408b4268eb0db3f2a146c0678965366.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/7eff9f0ed7e27ad0d3f9d923de0682fab1928591172c1ba336c5f79a134a5d85.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/836cda5b995b25867d722ed4f4c2292167e80351a3c6038db626648eb247dd8b.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/91ef63b112209ab30172763acd8a0935106c248f7f1bcae5545ce37a9f201551.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/a66ea4293a5f5938bc6d116edfa2522bb85bc37aea3541fbc09c3b613b9b32c0.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/ceb2ab26b80590840b65651deb6e948d3bf81565c6751f3a58752cf4bf4aecae.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-shm +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-wal +0 -0
- package/src/cf-do/ARCHITECTURE.md +0 -83
- package/src/cf-do/watermark.test.ts +0 -103
- package/src/cf-do/watermark.ts +0 -118
- package/src/cf-do/worker.ts +0 -1033
- package/src/cf-do/wrangler.toml +0 -11
- package/src/cf-pglite/README.md +0 -19
- package/src/change-tracking.ts +0 -25
- package/src/child-process.test.ts +0 -147
- package/src/child-process.ts +0 -90
- package/src/cli-entry.ts +0 -72
- package/src/cli.test.ts +0 -38
- package/src/cli.ts +0 -1214
- package/src/config.ts +0 -150
- package/src/do-sql-tracking.test.ts +0 -19
- package/src/do-sql-tracking.ts +0 -19
- package/src/index.ts +0 -1215
- package/src/integration/integration.test.ts +0 -517
- package/src/integration/native-binary.guard.test.ts +0 -13
- package/src/integration/native-startup.test.ts +0 -44
- package/src/integration/replication-latency.test.ts +0 -428
- package/src/integration/restore-live-stress.test.ts +0 -433
- package/src/integration/restore-reset.test.ts +0 -400
- package/src/integration/restore.test.ts +0 -274
- package/src/integration/test-permissions.ts +0 -147
- package/src/load-config.ts +0 -46
- package/src/log.ts +0 -96
- package/src/mutex.ts +0 -47
- package/src/pg-proxy-browser.singledb.test.ts +0 -233
- package/src/pg-proxy-browser.ts +0 -2022
- package/src/pg-proxy-do-backend.test.ts +0 -3890
- package/src/pg-proxy-do-backend.ts +0 -7157
- package/src/pg-proxy.ts +0 -1087
- package/src/pglite-ipc.test.ts +0 -116
- package/src/pglite-ipc.ts +0 -266
- package/src/pglite-manager.ts +0 -557
- package/src/pglite-web-proxy.test.ts +0 -57
- package/src/pglite-web-proxy.ts +0 -221
- package/src/pglite-web-worker.ts +0 -152
- package/src/pglite-worker-thread.ts +0 -253
- package/src/port.ts +0 -25
- package/src/process-title.ts +0 -9
- package/src/recovery.ts +0 -155
- package/src/replication/change-tracker.test.ts +0 -357
- package/src/replication/change-tracker.ts +0 -279
- package/src/replication/handler.test.ts +0 -511
- package/src/replication/handler.ts +0 -1190
- package/src/replication/pgoutput-encoder.test.ts +0 -697
- package/src/replication/pgoutput-encoder.ts +0 -373
- package/src/replication/tcp-replication.test.ts +0 -876
- package/src/replication/zero-compat.test.ts +0 -1150
- package/src/restore-stress.test.ts +0 -188
- package/src/s3-local.ts +0 -203
- package/src/shim/hooks.mjs +0 -120
- package/src/shim/register.mjs +0 -4
- package/src/sqlite-mode/apply-mode.ts +0 -224
- package/src/sqlite-mode/index.ts +0 -15
- package/src/sqlite-mode/native-binary.ts +0 -89
- package/src/sqlite-mode/package-resolve.ts +0 -17
- package/src/sqlite-mode/resolve-mode.ts +0 -80
- package/src/sqlite-mode/shim-template.ts +0 -159
- package/src/sqlite-mode/sqlite-mode.test.ts +0 -427
- package/src/sqlite-mode/types.ts +0 -30
- package/src/vite-plugin.ts +0 -67
- package/src/wasm-sqlite.test.ts +0 -537
- package/src/worker/browser-admin.ts +0 -52
- package/src/worker/browser-build-config.test.ts +0 -71
- package/src/worker/browser-build-config.ts +0 -109
- package/src/worker/browser-embed-admin.test.ts +0 -75
- package/src/worker/browser-embed.ts +0 -345
- package/src/worker/cf-patches.ts +0 -384
- package/src/worker/embed-integration.test.ts +0 -321
- package/src/worker/index.ts +0 -138
- package/src/worker/shims/fastify.test.ts +0 -255
- package/src/worker/shims/fastify.ts +0 -306
- package/src/worker/shims/http-service.test.ts +0 -355
- package/src/worker/shims/http-service.ts +0 -293
- package/src/worker/shims/node-stub.ts +0 -290
- package/src/worker/shims/oxfmt.ts +0 -3
- package/src/worker/shims/postgres-browser.ts +0 -59
- package/src/worker/shims/postgres-socket.test.ts +0 -576
- package/src/worker/shims/postgres-socket.ts +0 -310
- package/src/worker/shims/postgres.test.ts +0 -364
- package/src/worker/shims/postgres.ts +0 -1454
- package/src/worker/shims/sqlite-browser.test.ts +0 -233
- package/src/worker/shims/sqlite-browser.ts +0 -175
- package/src/worker/shims/sqlite.test.ts +0 -786
- package/src/worker/shims/sqlite.ts +0 -978
- package/src/worker/shims/stream-browser.ts +0 -15
- package/src/worker/shims/ws-browser.test.ts +0 -205
- package/src/worker/shims/ws-browser.ts +0 -248
- package/src/worker/shims/ws.test.ts +0 -288
- package/src/worker/shims/ws.ts +0 -467
- package/src/worker/shims/zero-process-env.ts +0 -11
- package/src/worker/types.ts +0 -75
- package/src/worker/worker-integration.test.ts +0 -223
- package/src/worker/worker.test.ts +0 -136
- package/src/worker/zero-cache-embed-cf.ts +0 -463
- package/src/worker/zero-cache-embed.ts +0 -277
package/src/admin/server.ts
DELETED
|
@@ -1,471 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs'
|
|
2
|
-
import {
|
|
3
|
-
createServer,
|
|
4
|
-
type Server,
|
|
5
|
-
type IncomingMessage,
|
|
6
|
-
type ServerResponse,
|
|
7
|
-
} from 'node:http'
|
|
8
|
-
import { resolve } from 'node:path'
|
|
9
|
-
|
|
10
|
-
import { log } from '../log.js'
|
|
11
|
-
import { getAdminHtml } from './ui.js'
|
|
12
|
-
|
|
13
|
-
import type { ZeroLiteConfig } from '../config.js'
|
|
14
|
-
import type { HttpLogStore } from './http-proxy.js'
|
|
15
|
-
import type { LogStore } from './log-store.js'
|
|
16
|
-
import type { PGlite } from '@electric-sql/pglite'
|
|
17
|
-
|
|
18
|
-
export interface AdminActions {
|
|
19
|
-
restartZero?: () => Promise<void>
|
|
20
|
-
stopZero?: () => Promise<void>
|
|
21
|
-
resetZero?: () => Promise<void>
|
|
22
|
-
resetZeroFull?: () => Promise<void>
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface AdminDbInstances {
|
|
26
|
-
postgres: PGlite
|
|
27
|
-
cvr: PGlite
|
|
28
|
-
cdb: PGlite
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface AdminServerOpts {
|
|
32
|
-
port: number
|
|
33
|
-
logStore: LogStore
|
|
34
|
-
config: ZeroLiteConfig
|
|
35
|
-
zeroEnv: Record<string, string>
|
|
36
|
-
actions?: AdminActions
|
|
37
|
-
startTime: number
|
|
38
|
-
httpLog?: HttpLogStore
|
|
39
|
-
db?: AdminDbInstances
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const CORS_HEADERS: Record<string, string> = {
|
|
43
|
-
'Access-Control-Allow-Origin': '*',
|
|
44
|
-
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
45
|
-
'Access-Control-Allow-Headers': '*',
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const JSON_HEADERS: Record<string, string> = {
|
|
49
|
-
...CORS_HEADERS,
|
|
50
|
-
'Content-Type': 'application/json',
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function json(res: ServerResponse, data: unknown, status = 200) {
|
|
54
|
-
res.writeHead(status, JSON_HEADERS)
|
|
55
|
-
res.end(JSON.stringify(data))
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const UI_PATHS = new Set([
|
|
59
|
-
'/',
|
|
60
|
-
'/all',
|
|
61
|
-
'/data',
|
|
62
|
-
'/zero',
|
|
63
|
-
'/pglite',
|
|
64
|
-
'/proxy',
|
|
65
|
-
'/orez',
|
|
66
|
-
'/s3',
|
|
67
|
-
'/http',
|
|
68
|
-
'/env',
|
|
69
|
-
])
|
|
70
|
-
|
|
71
|
-
export function startAdminServer(opts: AdminServerOpts): Promise<Server> {
|
|
72
|
-
const { logStore, config, zeroEnv, actions, startTime } = opts
|
|
73
|
-
const html = getAdminHtml()
|
|
74
|
-
|
|
75
|
-
const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
|
|
76
|
-
if (req.method === 'OPTIONS') {
|
|
77
|
-
res.writeHead(200, CORS_HEADERS)
|
|
78
|
-
res.end()
|
|
79
|
-
return
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const url = new URL(req.url || '/', 'http://localhost:' + opts.port)
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
if (req.method === 'GET' && UI_PATHS.has(url.pathname)) {
|
|
86
|
-
res.writeHead(200, { ...CORS_HEADERS, 'Content-Type': 'text/html' })
|
|
87
|
-
res.end(html)
|
|
88
|
-
return
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (req.method === 'GET' && url.pathname === '/api/logs') {
|
|
92
|
-
const source = url.searchParams.get('source') || undefined
|
|
93
|
-
const level = url.searchParams.get('level') || undefined
|
|
94
|
-
const sinceStr = url.searchParams.get('since')
|
|
95
|
-
const limitStr = url.searchParams.get('limit')
|
|
96
|
-
const since = sinceStr ? Number(sinceStr) : undefined
|
|
97
|
-
const limit = limitStr ? Number(limitStr) : undefined
|
|
98
|
-
json(res, logStore.query({ source, level, since, limit }))
|
|
99
|
-
return
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (req.method === 'GET' && url.pathname === '/api/env') {
|
|
103
|
-
const filtered = Object.entries(zeroEnv)
|
|
104
|
-
.filter(
|
|
105
|
-
([k]) => k.startsWith('ZERO_') || k === 'NODE_ENV' || k === 'NODE_OPTIONS'
|
|
106
|
-
)
|
|
107
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
108
|
-
json(res, { env: Object.fromEntries(filtered) })
|
|
109
|
-
return
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (req.method === 'GET' && url.pathname === '/api/status') {
|
|
113
|
-
json(res, {
|
|
114
|
-
pgPort: config.pgPort,
|
|
115
|
-
zeroPort: config.zeroPort,
|
|
116
|
-
adminPort: opts.port,
|
|
117
|
-
uptime: Math.floor((Date.now() - startTime) / 1000),
|
|
118
|
-
logLevel: config.logLevel,
|
|
119
|
-
skipZeroCache: config.skipZeroCache,
|
|
120
|
-
sqliteMode: config.disableWasmSqlite ? 'native' : 'wasm',
|
|
121
|
-
})
|
|
122
|
-
return
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (req.method === 'POST' && url.pathname === '/api/actions/restart-zero') {
|
|
126
|
-
if (!actions?.restartZero) {
|
|
127
|
-
json(res, { ok: false, message: 'zero-cache not running' }, 400)
|
|
128
|
-
return
|
|
129
|
-
}
|
|
130
|
-
log.orez('admin: restarting zero-cache')
|
|
131
|
-
await actions.restartZero()
|
|
132
|
-
json(res, { ok: true, message: 'zero-cache restarted' })
|
|
133
|
-
return
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (req.method === 'POST' && url.pathname === '/api/actions/stop-zero') {
|
|
137
|
-
if (!actions?.stopZero) {
|
|
138
|
-
json(res, { ok: false, message: 'zero-cache not running' }, 400)
|
|
139
|
-
return
|
|
140
|
-
}
|
|
141
|
-
log.orez('admin: stopping zero-cache for restore')
|
|
142
|
-
await actions.stopZero()
|
|
143
|
-
json(res, { ok: true, message: 'zero-cache stopped' })
|
|
144
|
-
return
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (req.method === 'POST' && url.pathname === '/api/actions/reset-zero') {
|
|
148
|
-
if (!actions?.resetZero) {
|
|
149
|
-
json(res, { ok: false, message: 'zero-cache not running' }, 400)
|
|
150
|
-
return
|
|
151
|
-
}
|
|
152
|
-
log.orez('admin: resetting zero-cache (cache-only)')
|
|
153
|
-
await actions.resetZero()
|
|
154
|
-
json(res, { ok: true, message: 'zero-cache reset (cache-only) and restarted' })
|
|
155
|
-
return
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (req.method === 'POST' && url.pathname === '/api/actions/reset-zero-full') {
|
|
159
|
-
if (!actions?.resetZeroFull) {
|
|
160
|
-
json(res, { ok: false, message: 'zero-cache not running' }, 400)
|
|
161
|
-
return
|
|
162
|
-
}
|
|
163
|
-
log.orez('admin: resetting zero-cache (full)')
|
|
164
|
-
await actions.resetZeroFull()
|
|
165
|
-
json(res, { ok: true, message: 'zero-cache reset (full) and restarted' })
|
|
166
|
-
return
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (req.method === 'POST' && url.pathname === '/api/actions/clear-logs') {
|
|
170
|
-
logStore.clear()
|
|
171
|
-
json(res, { ok: true, message: 'logs cleared' })
|
|
172
|
-
return
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (req.method === 'GET' && url.pathname === '/api/http-log') {
|
|
176
|
-
const sinceStr = url.searchParams.get('since')
|
|
177
|
-
const path = url.searchParams.get('path') || undefined
|
|
178
|
-
const since = sinceStr ? Number(sinceStr) : undefined
|
|
179
|
-
json(res, opts.httpLog?.query({ since, path }) || { entries: [], cursor: 0 })
|
|
180
|
-
return
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (req.method === 'POST' && url.pathname === '/api/actions/clear-http') {
|
|
184
|
-
opts.httpLog?.clear()
|
|
185
|
-
json(res, { ok: true, message: 'http log cleared' })
|
|
186
|
-
return
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// db explorer endpoints
|
|
190
|
-
if (opts.db && req.method === 'GET' && url.pathname === '/api/db/tables') {
|
|
191
|
-
const dbName = url.searchParams.get('db') || 'postgres'
|
|
192
|
-
const instance = getDbInstance(opts.db, dbName)
|
|
193
|
-
if (!instance) {
|
|
194
|
-
json(res, { error: 'unknown db: ' + dbName }, 400)
|
|
195
|
-
return
|
|
196
|
-
}
|
|
197
|
-
try {
|
|
198
|
-
const result = await instance.query(
|
|
199
|
-
`SELECT table_schema, table_name, pg_total_relation_size(quote_ident(table_schema) || '.' || quote_ident(table_name)) as size_bytes
|
|
200
|
-
FROM information_schema.tables
|
|
201
|
-
WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
|
|
202
|
-
ORDER BY table_schema, table_name`
|
|
203
|
-
)
|
|
204
|
-
json(res, { tables: result.rows })
|
|
205
|
-
} catch (err: any) {
|
|
206
|
-
json(res, { error: err?.message ?? 'query failed' }, 500)
|
|
207
|
-
}
|
|
208
|
-
return
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (opts.db && req.method === 'GET' && url.pathname === '/api/db/table-data') {
|
|
212
|
-
const dbName = url.searchParams.get('db') || 'postgres'
|
|
213
|
-
const table = url.searchParams.get('table')
|
|
214
|
-
if (!table) {
|
|
215
|
-
json(res, { error: 'missing table param' }, 400)
|
|
216
|
-
return
|
|
217
|
-
}
|
|
218
|
-
const instance = getDbInstance(opts.db, dbName)
|
|
219
|
-
if (!instance) {
|
|
220
|
-
json(res, { error: 'unknown db: ' + dbName }, 400)
|
|
221
|
-
return
|
|
222
|
-
}
|
|
223
|
-
const search = url.searchParams.get('search') || ''
|
|
224
|
-
const offset = Number(url.searchParams.get('offset') || '0')
|
|
225
|
-
const limit = Number(url.searchParams.get('limit') || '100')
|
|
226
|
-
try {
|
|
227
|
-
// get columns first
|
|
228
|
-
const colResult = await instance.query(
|
|
229
|
-
`SELECT column_name, data_type FROM information_schema.columns
|
|
230
|
-
WHERE table_schema || '.' || table_name = $1 OR table_name = $1
|
|
231
|
-
ORDER BY ordinal_position`,
|
|
232
|
-
[table]
|
|
233
|
-
)
|
|
234
|
-
const columns = colResult.rows.map((r: any) => ({
|
|
235
|
-
name: r.column_name,
|
|
236
|
-
type: r.data_type,
|
|
237
|
-
}))
|
|
238
|
-
// build query with optional search
|
|
239
|
-
let sql = `SELECT * FROM ${quoteIdentPg(table)}`
|
|
240
|
-
const params: any[] = []
|
|
241
|
-
if (search) {
|
|
242
|
-
// search across all text-castable columns
|
|
243
|
-
const conds = columns.map(
|
|
244
|
-
(_: any, i: number) =>
|
|
245
|
-
`${quoteIdentPg(columns[i].name)}::text ILIKE $${params.length + 1}`
|
|
246
|
-
)
|
|
247
|
-
if (conds.length > 0) {
|
|
248
|
-
params.push('%' + search + '%')
|
|
249
|
-
sql += ' WHERE ' + conds.join(' OR ')
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
// get total count
|
|
253
|
-
const countResult = await instance.query(
|
|
254
|
-
`SELECT count(*)::int as total FROM (${sql}) _c`,
|
|
255
|
-
params
|
|
256
|
-
)
|
|
257
|
-
const total = (countResult.rows[0] as any)?.total ?? 0
|
|
258
|
-
sql += ` LIMIT ${limit} OFFSET ${offset}`
|
|
259
|
-
const result = await instance.query(sql, params)
|
|
260
|
-
json(res, {
|
|
261
|
-
columns,
|
|
262
|
-
rows: result.rows,
|
|
263
|
-
total,
|
|
264
|
-
offset,
|
|
265
|
-
limit,
|
|
266
|
-
})
|
|
267
|
-
} catch (err: any) {
|
|
268
|
-
json(res, { error: err?.message ?? 'query failed' }, 500)
|
|
269
|
-
}
|
|
270
|
-
return
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
if (opts.db && req.method === 'POST' && url.pathname === '/api/db/query') {
|
|
274
|
-
const body = await readBody(req)
|
|
275
|
-
let parsed: { db?: string; sql?: string }
|
|
276
|
-
try {
|
|
277
|
-
parsed = JSON.parse(body)
|
|
278
|
-
} catch {
|
|
279
|
-
json(res, { error: 'invalid json body' }, 400)
|
|
280
|
-
return
|
|
281
|
-
}
|
|
282
|
-
const dbName = parsed.db || 'postgres'
|
|
283
|
-
const sql = parsed.sql
|
|
284
|
-
if (!sql) {
|
|
285
|
-
json(res, { error: 'missing sql' }, 400)
|
|
286
|
-
return
|
|
287
|
-
}
|
|
288
|
-
const instance = getDbInstance(opts.db, dbName)
|
|
289
|
-
if (!instance) {
|
|
290
|
-
json(res, { error: 'unknown db: ' + dbName }, 400)
|
|
291
|
-
return
|
|
292
|
-
}
|
|
293
|
-
try {
|
|
294
|
-
const start = performance.now()
|
|
295
|
-
const result = await instance.query(sql)
|
|
296
|
-
const durationMs = Math.round((performance.now() - start) * 100) / 100
|
|
297
|
-
json(res, {
|
|
298
|
-
fields: (result.fields || []).map((f: any) => f.name),
|
|
299
|
-
rows: result.rows,
|
|
300
|
-
rowCount: result.rows.length,
|
|
301
|
-
durationMs,
|
|
302
|
-
})
|
|
303
|
-
} catch (err: any) {
|
|
304
|
-
json(res, { error: err?.message ?? 'query failed' }, 400)
|
|
305
|
-
}
|
|
306
|
-
return
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// sqlite replica endpoints
|
|
310
|
-
if (req.method === 'GET' && url.pathname === '/api/sqlite/tables') {
|
|
311
|
-
const sqliteDb = await openSqliteReplica(opts.config.dataDir)
|
|
312
|
-
if (!sqliteDb) {
|
|
313
|
-
json(res, { error: 'sqlite replica not found' }, 404)
|
|
314
|
-
return
|
|
315
|
-
}
|
|
316
|
-
try {
|
|
317
|
-
const tables = sqliteDb
|
|
318
|
-
.prepare(
|
|
319
|
-
`SELECT name, (SELECT count(*) FROM pragma_table_info(m.name)) as col_count
|
|
320
|
-
FROM sqlite_master m WHERE type='table' AND name NOT LIKE 'sqlite_%'
|
|
321
|
-
ORDER BY name`
|
|
322
|
-
)
|
|
323
|
-
.all()
|
|
324
|
-
json(res, { tables })
|
|
325
|
-
} catch (err: any) {
|
|
326
|
-
json(res, { error: err?.message ?? 'query failed' }, 500)
|
|
327
|
-
} finally {
|
|
328
|
-
sqliteDb.close()
|
|
329
|
-
}
|
|
330
|
-
return
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
if (req.method === 'GET' && url.pathname === '/api/sqlite/table-data') {
|
|
334
|
-
const table = url.searchParams.get('table')
|
|
335
|
-
if (!table) {
|
|
336
|
-
json(res, { error: 'missing table param' }, 400)
|
|
337
|
-
return
|
|
338
|
-
}
|
|
339
|
-
const sqliteDb = await openSqliteReplica(opts.config.dataDir)
|
|
340
|
-
if (!sqliteDb) {
|
|
341
|
-
json(res, { error: 'sqlite replica not found' }, 404)
|
|
342
|
-
return
|
|
343
|
-
}
|
|
344
|
-
const search = url.searchParams.get('search') || ''
|
|
345
|
-
const offset = Number(url.searchParams.get('offset') || '0')
|
|
346
|
-
const limit = Number(url.searchParams.get('limit') || '100')
|
|
347
|
-
try {
|
|
348
|
-
const columns = sqliteDb
|
|
349
|
-
.prepare(`SELECT name, type FROM pragma_table_info(?)`)
|
|
350
|
-
.all(table)
|
|
351
|
-
const quotedTable = '"' + table.replace(/"/g, '""') + '"'
|
|
352
|
-
let sql = `SELECT * FROM ${quotedTable}`
|
|
353
|
-
const params: any[] = []
|
|
354
|
-
if (search) {
|
|
355
|
-
const conds = columns.map(
|
|
356
|
-
(c: any) => `"${c.name.replace(/"/g, '""')}" LIKE ?`
|
|
357
|
-
)
|
|
358
|
-
if (conds.length > 0) {
|
|
359
|
-
params.push(...conds.map(() => '%' + search + '%'))
|
|
360
|
-
sql += ' WHERE ' + conds.join(' OR ')
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
const countRow = sqliteDb
|
|
364
|
-
.prepare(`SELECT count(*) as total FROM (${sql})`)
|
|
365
|
-
.get(...params)
|
|
366
|
-
const total = (countRow as any)?.total ?? 0
|
|
367
|
-
sql += ` LIMIT ? OFFSET ?`
|
|
368
|
-
params.push(limit, offset)
|
|
369
|
-
const stmt = sqliteDb.prepare(sql)
|
|
370
|
-
const rows = stmt.all(...params)
|
|
371
|
-
json(res, { columns, rows, total, offset, limit })
|
|
372
|
-
} catch (err: any) {
|
|
373
|
-
json(res, { error: err?.message ?? 'query failed' }, 500)
|
|
374
|
-
} finally {
|
|
375
|
-
sqliteDb.close()
|
|
376
|
-
}
|
|
377
|
-
return
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
if (req.method === 'POST' && url.pathname === '/api/sqlite/query') {
|
|
381
|
-
const body = await readBody(req)
|
|
382
|
-
let parsed: { sql?: string }
|
|
383
|
-
try {
|
|
384
|
-
parsed = JSON.parse(body)
|
|
385
|
-
} catch {
|
|
386
|
-
json(res, { error: 'invalid json body' }, 400)
|
|
387
|
-
return
|
|
388
|
-
}
|
|
389
|
-
const sql = parsed.sql
|
|
390
|
-
if (!sql) {
|
|
391
|
-
json(res, { error: 'missing sql' }, 400)
|
|
392
|
-
return
|
|
393
|
-
}
|
|
394
|
-
const sqliteDb = await openSqliteReplica(opts.config.dataDir)
|
|
395
|
-
if (!sqliteDb) {
|
|
396
|
-
json(res, { error: 'sqlite replica not found' }, 404)
|
|
397
|
-
return
|
|
398
|
-
}
|
|
399
|
-
try {
|
|
400
|
-
const start = performance.now()
|
|
401
|
-
const stmt = sqliteDb.prepare(sql)
|
|
402
|
-
const fields = stmt.columns().map((c: any) => c.name)
|
|
403
|
-
const rows = stmt.all()
|
|
404
|
-
const durationMs = Math.round((performance.now() - start) * 100) / 100
|
|
405
|
-
json(res, { fields, rows, rowCount: rows.length, durationMs })
|
|
406
|
-
} catch (err: any) {
|
|
407
|
-
json(res, { error: err?.message ?? 'query failed' }, 400)
|
|
408
|
-
} finally {
|
|
409
|
-
sqliteDb.close()
|
|
410
|
-
}
|
|
411
|
-
return
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
res.writeHead(404, CORS_HEADERS)
|
|
415
|
-
res.end('not found')
|
|
416
|
-
} catch (err: any) {
|
|
417
|
-
json(res, { error: err?.message ?? 'internal error' }, 500)
|
|
418
|
-
}
|
|
419
|
-
})
|
|
420
|
-
|
|
421
|
-
return new Promise((resolve, reject) => {
|
|
422
|
-
server.listen(opts.port, '127.0.0.1', () => {
|
|
423
|
-
resolve(server)
|
|
424
|
-
})
|
|
425
|
-
server.on('error', reject)
|
|
426
|
-
})
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
function getDbInstance(db: AdminDbInstances, name: string): PGlite | null {
|
|
430
|
-
if (name === 'postgres' || name === 'main') return db.postgres
|
|
431
|
-
if (name === 'cvr') return db.cvr
|
|
432
|
-
if (name === 'cdb') return db.cdb
|
|
433
|
-
return null
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
function readBody(req: IncomingMessage): Promise<string> {
|
|
437
|
-
return new Promise((resolve, reject) => {
|
|
438
|
-
const chunks: Buffer[] = []
|
|
439
|
-
req.on('data', (c) => chunks.push(c))
|
|
440
|
-
req.on('end', () => resolve(Buffer.concat(chunks).toString()))
|
|
441
|
-
req.on('error', reject)
|
|
442
|
-
})
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
function quoteIdentPg(name: string): string {
|
|
446
|
-
if (name.includes('.')) {
|
|
447
|
-
return name
|
|
448
|
-
.split('.')
|
|
449
|
-
.map((p) => '"' + p.replace(/"/g, '""') + '"')
|
|
450
|
-
.join('.')
|
|
451
|
-
}
|
|
452
|
-
if (/^[a-z_][a-z0-9_]*$/.test(name)) return name
|
|
453
|
-
return '"' + name.replace(/"/g, '""') + '"'
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
let cachedDatabaseCtor: any | null = null
|
|
457
|
-
|
|
458
|
-
async function openSqliteReplica(dataDir: string): Promise<any | null> {
|
|
459
|
-
const replicaPath = resolve(dataDir, 'zero-replica.db')
|
|
460
|
-
if (!existsSync(replicaPath)) return null
|
|
461
|
-
try {
|
|
462
|
-
if (!cachedDatabaseCtor) {
|
|
463
|
-
const mod: any = await import('bedrock-sqlite')
|
|
464
|
-
cachedDatabaseCtor = mod.Database || mod.default?.Database || mod.default || mod
|
|
465
|
-
}
|
|
466
|
-
return new cachedDatabaseCtor(replicaPath, { readonly: true })
|
|
467
|
-
} catch (err: any) {
|
|
468
|
-
log.debug.orez('admin: sqlite replica open failed: ' + (err?.message ?? err))
|
|
469
|
-
return null
|
|
470
|
-
}
|
|
471
|
-
}
|