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.
Files changed (172) hide show
  1. package/dist/cf-do/worker.d.ts.map +1 -1
  2. package/dist/cf-do/worker.js +9 -1
  3. package/dist/cf-do/worker.js.map +1 -1
  4. package/dist/pg-proxy-do-backend.d.ts +2 -0
  5. package/dist/pg-proxy-do-backend.d.ts.map +1 -1
  6. package/dist/pg-proxy-do-backend.js +49 -7
  7. package/dist/pg-proxy-do-backend.js.map +1 -1
  8. package/dist/pg-sqlite-compiler/catalog/seed.d.ts +67 -0
  9. package/dist/pg-sqlite-compiler/catalog/seed.d.ts.map +1 -0
  10. package/dist/pg-sqlite-compiler/catalog/seed.js +436 -0
  11. package/dist/pg-sqlite-compiler/catalog/seed.js.map +1 -0
  12. package/dist/pg-sqlite-compiler/index.d.ts +12 -0
  13. package/dist/pg-sqlite-compiler/index.d.ts.map +1 -0
  14. package/dist/pg-sqlite-compiler/index.js +59 -0
  15. package/dist/pg-sqlite-compiler/index.js.map +1 -0
  16. package/dist/pg-sqlite-compiler/passes/ast-utils.d.ts +48 -0
  17. package/dist/pg-sqlite-compiler/passes/ast-utils.d.ts.map +1 -0
  18. package/dist/pg-sqlite-compiler/passes/ast-utils.js +93 -0
  19. package/dist/pg-sqlite-compiler/passes/ast-utils.js.map +1 -0
  20. package/dist/pg-sqlite-compiler/passes/catalog.d.ts +34 -0
  21. package/dist/pg-sqlite-compiler/passes/catalog.d.ts.map +1 -0
  22. package/dist/pg-sqlite-compiler/passes/catalog.js +30 -0
  23. package/dist/pg-sqlite-compiler/passes/catalog.js.map +1 -0
  24. package/dist/pg-sqlite-compiler/passes/datetime.d.ts +21 -0
  25. package/dist/pg-sqlite-compiler/passes/datetime.d.ts.map +1 -0
  26. package/dist/pg-sqlite-compiler/passes/datetime.js +53 -0
  27. package/dist/pg-sqlite-compiler/passes/datetime.js.map +1 -0
  28. package/dist/pg-sqlite-compiler/passes/index.d.ts +21 -0
  29. package/dist/pg-sqlite-compiler/passes/index.d.ts.map +1 -0
  30. package/dist/pg-sqlite-compiler/passes/index.js +39 -0
  31. package/dist/pg-sqlite-compiler/passes/index.js.map +1 -0
  32. package/dist/pg-sqlite-compiler/passes/types.d.ts +41 -0
  33. package/dist/pg-sqlite-compiler/passes/types.d.ts.map +1 -0
  34. package/dist/pg-sqlite-compiler/passes/types.js +103 -0
  35. package/dist/pg-sqlite-compiler/passes/types.js.map +1 -0
  36. package/dist/pg-sqlite-compiler/test/oracle.d.ts +34 -0
  37. package/dist/pg-sqlite-compiler/test/oracle.d.ts.map +1 -0
  38. package/dist/pg-sqlite-compiler/test/oracle.js +204 -0
  39. package/dist/pg-sqlite-compiler/test/oracle.js.map +1 -0
  40. package/dist/pg-sqlite-compiler/types.d.ts +55 -0
  41. package/dist/pg-sqlite-compiler/types.d.ts.map +1 -0
  42. package/dist/pg-sqlite-compiler/types.js +2 -0
  43. package/dist/pg-sqlite-compiler/types.js.map +1 -0
  44. package/package.json +8 -4
  45. package/src/admin/admin-data.test.ts +0 -348
  46. package/src/admin/http-proxy.ts +0 -252
  47. package/src/admin/log-store.ts +0 -192
  48. package/src/admin/server.ts +0 -471
  49. package/src/admin/ui.ts +0 -1322
  50. package/src/bench/proxy-throughput.bench.ts +0 -343
  51. package/src/bench/serial-mutations.bench.ts +0 -270
  52. package/src/browser.ts +0 -203
  53. package/src/cf-do/.wrangler/cache/cf.json +0 -1
  54. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite +0 -0
  55. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm +0 -0
  56. package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal +0 -0
  57. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/0f0f3bdf0abda097eb6f1246db4657d9fc622081362d894d82c1a1ce067b05b6.sqlite +0 -0
  58. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/1ddd3a4a48a11b51658444f5458a1fb175194b1d5b6a5bda20ef3fe3205b900c.sqlite +0 -0
  59. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/204a39120310d37e972c5914cfd71ad55c151bdb9e8ed289a5f8c5b052dd60e4.sqlite +0 -0
  60. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/3835f242df9728adba3d127a238793fd054ed3e51df3f60749ee744c469bf2a2.sqlite +0 -0
  61. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/4aa9c80eb716cf55b8995ccf7afab0b36c683e6da07d7c37a3f9c570136036df.sqlite +0 -0
  62. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/533e2fd1d6ea46e7a9a0017916ef341802d438d72583462755f2c1f8225e9bf2.sqlite +0 -0
  63. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/5ffa1aced1225ecaeac6366f7586aa3de92761cdff8711d81fbd81f248076abd.sqlite +0 -0
  64. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/686c3a9f0d7e59ed2ab607efd4b76d779c97cafeb3818380033bf7c7eb86c819.sqlite +0 -0
  65. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/6e8214e8dcfadd0deb52d64e5e9ca85c6b329ace11193909845995396914c473.sqlite +0 -0
  66. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/78d9ec9ff873d3fe3507ff53c2a6f6dfc408b4268eb0db3f2a146c0678965366.sqlite +0 -0
  67. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/7eff9f0ed7e27ad0d3f9d923de0682fab1928591172c1ba336c5f79a134a5d85.sqlite +0 -0
  68. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/836cda5b995b25867d722ed4f4c2292167e80351a3c6038db626648eb247dd8b.sqlite +0 -0
  69. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/91ef63b112209ab30172763acd8a0935106c248f7f1bcae5545ce37a9f201551.sqlite +0 -0
  70. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/a66ea4293a5f5938bc6d116edfa2522bb85bc37aea3541fbc09c3b613b9b32c0.sqlite +0 -0
  71. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/ceb2ab26b80590840b65651deb6e948d3bf81565c6751f3a58752cf4bf4aecae.sqlite +0 -0
  72. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite +0 -0
  73. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-shm +0 -0
  74. package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-wal +0 -0
  75. package/src/cf-do/ARCHITECTURE.md +0 -83
  76. package/src/cf-do/watermark.test.ts +0 -103
  77. package/src/cf-do/watermark.ts +0 -118
  78. package/src/cf-do/worker.ts +0 -1033
  79. package/src/cf-do/wrangler.toml +0 -11
  80. package/src/cf-pglite/README.md +0 -19
  81. package/src/change-tracking.ts +0 -25
  82. package/src/child-process.test.ts +0 -147
  83. package/src/child-process.ts +0 -90
  84. package/src/cli-entry.ts +0 -72
  85. package/src/cli.test.ts +0 -38
  86. package/src/cli.ts +0 -1214
  87. package/src/config.ts +0 -150
  88. package/src/do-sql-tracking.test.ts +0 -19
  89. package/src/do-sql-tracking.ts +0 -19
  90. package/src/index.ts +0 -1215
  91. package/src/integration/integration.test.ts +0 -517
  92. package/src/integration/native-binary.guard.test.ts +0 -13
  93. package/src/integration/native-startup.test.ts +0 -44
  94. package/src/integration/replication-latency.test.ts +0 -428
  95. package/src/integration/restore-live-stress.test.ts +0 -433
  96. package/src/integration/restore-reset.test.ts +0 -400
  97. package/src/integration/restore.test.ts +0 -274
  98. package/src/integration/test-permissions.ts +0 -147
  99. package/src/load-config.ts +0 -46
  100. package/src/log.ts +0 -96
  101. package/src/mutex.ts +0 -47
  102. package/src/pg-proxy-browser.singledb.test.ts +0 -233
  103. package/src/pg-proxy-browser.ts +0 -2022
  104. package/src/pg-proxy-do-backend.test.ts +0 -3890
  105. package/src/pg-proxy-do-backend.ts +0 -7157
  106. package/src/pg-proxy.ts +0 -1087
  107. package/src/pglite-ipc.test.ts +0 -116
  108. package/src/pglite-ipc.ts +0 -266
  109. package/src/pglite-manager.ts +0 -557
  110. package/src/pglite-web-proxy.test.ts +0 -57
  111. package/src/pglite-web-proxy.ts +0 -221
  112. package/src/pglite-web-worker.ts +0 -152
  113. package/src/pglite-worker-thread.ts +0 -253
  114. package/src/port.ts +0 -25
  115. package/src/process-title.ts +0 -9
  116. package/src/recovery.ts +0 -155
  117. package/src/replication/change-tracker.test.ts +0 -357
  118. package/src/replication/change-tracker.ts +0 -279
  119. package/src/replication/handler.test.ts +0 -511
  120. package/src/replication/handler.ts +0 -1190
  121. package/src/replication/pgoutput-encoder.test.ts +0 -697
  122. package/src/replication/pgoutput-encoder.ts +0 -373
  123. package/src/replication/tcp-replication.test.ts +0 -876
  124. package/src/replication/zero-compat.test.ts +0 -1150
  125. package/src/restore-stress.test.ts +0 -188
  126. package/src/s3-local.ts +0 -203
  127. package/src/shim/hooks.mjs +0 -120
  128. package/src/shim/register.mjs +0 -4
  129. package/src/sqlite-mode/apply-mode.ts +0 -224
  130. package/src/sqlite-mode/index.ts +0 -15
  131. package/src/sqlite-mode/native-binary.ts +0 -89
  132. package/src/sqlite-mode/package-resolve.ts +0 -17
  133. package/src/sqlite-mode/resolve-mode.ts +0 -80
  134. package/src/sqlite-mode/shim-template.ts +0 -159
  135. package/src/sqlite-mode/sqlite-mode.test.ts +0 -427
  136. package/src/sqlite-mode/types.ts +0 -30
  137. package/src/vite-plugin.ts +0 -67
  138. package/src/wasm-sqlite.test.ts +0 -537
  139. package/src/worker/browser-admin.ts +0 -52
  140. package/src/worker/browser-build-config.test.ts +0 -71
  141. package/src/worker/browser-build-config.ts +0 -109
  142. package/src/worker/browser-embed-admin.test.ts +0 -75
  143. package/src/worker/browser-embed.ts +0 -345
  144. package/src/worker/cf-patches.ts +0 -384
  145. package/src/worker/embed-integration.test.ts +0 -321
  146. package/src/worker/index.ts +0 -138
  147. package/src/worker/shims/fastify.test.ts +0 -255
  148. package/src/worker/shims/fastify.ts +0 -306
  149. package/src/worker/shims/http-service.test.ts +0 -355
  150. package/src/worker/shims/http-service.ts +0 -293
  151. package/src/worker/shims/node-stub.ts +0 -290
  152. package/src/worker/shims/oxfmt.ts +0 -3
  153. package/src/worker/shims/postgres-browser.ts +0 -59
  154. package/src/worker/shims/postgres-socket.test.ts +0 -576
  155. package/src/worker/shims/postgres-socket.ts +0 -310
  156. package/src/worker/shims/postgres.test.ts +0 -364
  157. package/src/worker/shims/postgres.ts +0 -1454
  158. package/src/worker/shims/sqlite-browser.test.ts +0 -233
  159. package/src/worker/shims/sqlite-browser.ts +0 -175
  160. package/src/worker/shims/sqlite.test.ts +0 -786
  161. package/src/worker/shims/sqlite.ts +0 -978
  162. package/src/worker/shims/stream-browser.ts +0 -15
  163. package/src/worker/shims/ws-browser.test.ts +0 -205
  164. package/src/worker/shims/ws-browser.ts +0 -248
  165. package/src/worker/shims/ws.test.ts +0 -288
  166. package/src/worker/shims/ws.ts +0 -467
  167. package/src/worker/shims/zero-process-env.ts +0 -11
  168. package/src/worker/types.ts +0 -75
  169. package/src/worker/worker-integration.test.ts +0 -223
  170. package/src/worker/worker.test.ts +0 -136
  171. package/src/worker/zero-cache-embed-cf.ts +0 -463
  172. package/src/worker/zero-cache-embed.ts +0 -277
@@ -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
- }