orez 0.0.52 → 0.0.53
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/README.md +47 -27
- package/dist/admin/ui.d.ts.map +1 -1
- package/dist/admin/ui.js +48 -40
- package/dist/admin/ui.js.map +1 -1
- package/dist/cli.js +35 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +36 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/admin/ui.ts +48 -40
- package/src/cli.ts +36 -1
- package/src/index.ts +42 -3
- package/src/replication/tcp-replication.test.ts +23 -14
package/src/index.ts
CHANGED
|
@@ -101,6 +101,10 @@ export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
|
|
|
101
101
|
|
|
102
102
|
mkdirSync(config.dataDir, { recursive: true })
|
|
103
103
|
|
|
104
|
+
// write pid file for IPC (pg_restore uses this to signal restart)
|
|
105
|
+
const pidFile = resolve(config.dataDir, 'orez.pid')
|
|
106
|
+
writeFileSync(pidFile, String(process.pid))
|
|
107
|
+
|
|
104
108
|
// start pglite (separate instances for postgres, zero_cvr, zero_cdb)
|
|
105
109
|
const instances = await createPGliteInstances(config)
|
|
106
110
|
const db = instances.postgres
|
|
@@ -166,6 +170,15 @@ export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
|
|
|
166
170
|
})
|
|
167
171
|
}
|
|
168
172
|
|
|
173
|
+
// handle SIGUSR1 to restart zero-cache (sent by pg_restore after clearing replica)
|
|
174
|
+
if (!config.skipZeroCache) {
|
|
175
|
+
process.on('SIGUSR1', async () => {
|
|
176
|
+
log.orez('received SIGUSR1, restarting zero-cache...')
|
|
177
|
+
await restartZeroCache(false) // replica already cleared by pg_restore
|
|
178
|
+
log.zero(`ready ${port(config.zeroPort, 'magenta')}`)
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
|
|
169
182
|
const killZeroCache = async () => {
|
|
170
183
|
if (zeroCacheProcess && !zeroCacheProcess.killed) {
|
|
171
184
|
zeroCacheProcess.kill('SIGTERM')
|
|
@@ -202,6 +215,9 @@ export async function startZeroLite(overrides: Partial<ZeroLiteConfig> = {}) {
|
|
|
202
215
|
instances.cvr.close(),
|
|
203
216
|
instances.cdb.close(),
|
|
204
217
|
])
|
|
218
|
+
try {
|
|
219
|
+
unlinkSync(pidFile)
|
|
220
|
+
} catch {}
|
|
205
221
|
log.debug.orez('stopped')
|
|
206
222
|
}
|
|
207
223
|
|
|
@@ -353,9 +369,13 @@ async function startZeroCache(
|
|
|
353
369
|
const cdbUrl = getConnectionString(config, 'zero_cdb')
|
|
354
370
|
|
|
355
371
|
// defaults that can be overridden by user env
|
|
372
|
+
// when admin is enabled and user hasn't set ZERO_LOG_LEVEL, capture debug
|
|
373
|
+
// logs for the admin UI while still respecting --log-level for console output
|
|
374
|
+
const zeroLogLevel =
|
|
375
|
+
config.adminPort > 0 && !process.env.ZERO_LOG_LEVEL ? 'debug' : config.logLevel
|
|
356
376
|
const defaults: Record<string, string> = {
|
|
357
377
|
NODE_ENV: 'development',
|
|
358
|
-
ZERO_LOG_LEVEL:
|
|
378
|
+
ZERO_LOG_LEVEL: zeroLogLevel,
|
|
359
379
|
ZERO_NUM_SYNC_WORKERS: '1',
|
|
360
380
|
// disable query planner — it relies on scanStatus which causes infinite
|
|
361
381
|
// loops with wasm sqlite and has caused freezes with native too.
|
|
@@ -402,11 +422,30 @@ async function startZeroCache(
|
|
|
402
422
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
403
423
|
})
|
|
404
424
|
|
|
425
|
+
// detect log level from zero-cache output
|
|
426
|
+
const detectLevel = (line: string, fallback: string): string => {
|
|
427
|
+
const lower = line.toLowerCase()
|
|
428
|
+
if (
|
|
429
|
+
lower.includes('"level":"error"') ||
|
|
430
|
+
lower.includes(' error ') ||
|
|
431
|
+
lower.includes('error:')
|
|
432
|
+
)
|
|
433
|
+
return 'error'
|
|
434
|
+
if (
|
|
435
|
+
lower.includes('"level":"warn"') ||
|
|
436
|
+
lower.includes(' warn ') ||
|
|
437
|
+
lower.includes('warning:')
|
|
438
|
+
)
|
|
439
|
+
return 'warn'
|
|
440
|
+
if (lower.includes('"level":"debug"') || lower.includes(' debug ')) return 'debug'
|
|
441
|
+
return fallback
|
|
442
|
+
}
|
|
443
|
+
|
|
405
444
|
child.stdout?.on('data', (data: Buffer) => {
|
|
406
445
|
const lines = data.toString().trim().split('\n')
|
|
407
446
|
for (const line of lines) {
|
|
408
447
|
log.debug.zero(line)
|
|
409
|
-
logStore?.push('zero', 'info', line)
|
|
448
|
+
logStore?.push('zero', detectLevel(line, 'info'), line)
|
|
410
449
|
}
|
|
411
450
|
})
|
|
412
451
|
|
|
@@ -414,7 +453,7 @@ async function startZeroCache(
|
|
|
414
453
|
const lines = data.toString().trim().split('\n')
|
|
415
454
|
for (const line of lines) {
|
|
416
455
|
log.debug.zero(line)
|
|
417
|
-
logStore?.push('zero', 'error', line)
|
|
456
|
+
logStore?.push('zero', detectLevel(line, 'error'), line)
|
|
418
457
|
}
|
|
419
458
|
})
|
|
420
459
|
|
|
@@ -692,8 +692,8 @@ describe('tcp replication', () => {
|
|
|
692
692
|
}, 15_000)
|
|
693
693
|
|
|
694
694
|
it('handles multiple tables in same stream', async () => {
|
|
695
|
-
|
|
696
|
-
await db.exec(`CREATE
|
|
695
|
+
// create a second table and re-install tracking for both
|
|
696
|
+
await db.exec(`CREATE TABLE public.products (id SERIAL PRIMARY KEY, label TEXT)`)
|
|
697
697
|
await installChangeTracking(db)
|
|
698
698
|
|
|
699
699
|
const replClient = new TestPgClient(port)
|
|
@@ -707,28 +707,37 @@ describe('tcp replication', () => {
|
|
|
707
707
|
'CREATE_REPLICATION_SLOT "multi_test" TEMPORARY LOGICAL pgoutput NOEXPORT_SNAPSHOT'
|
|
708
708
|
)
|
|
709
709
|
await replClient.startReplication(
|
|
710
|
-
"START_REPLICATION SLOT \"multi_test\" LOGICAL 0/0 (proto_version '1', publication_names '
|
|
710
|
+
"START_REPLICATION SLOT \"multi_test\" LOGICAL 0/0 (proto_version '1', publication_names 'zero_takeout')"
|
|
711
711
|
)
|
|
712
712
|
|
|
713
713
|
await replClient.collectStream(200)
|
|
714
714
|
|
|
715
|
-
|
|
716
|
-
await db.exec(`INSERT INTO public.
|
|
715
|
+
// insert into both tables
|
|
716
|
+
await db.exec(`INSERT INTO public.items (name, value) VALUES ('multi1', 1)`)
|
|
717
|
+
await db.exec(`INSERT INTO public.products (label) VALUES ('multi2')`)
|
|
717
718
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
const
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
if (
|
|
719
|
+
// collect until we see both relations (with timeout)
|
|
720
|
+
const allDecoded: PgOutputMessage[] = []
|
|
721
|
+
const deadline = Date.now() + 8000
|
|
722
|
+
while (Date.now() < deadline) {
|
|
723
|
+
const stream = await replClient.collectStream(500)
|
|
724
|
+
for (const msg of stream) {
|
|
725
|
+
if (msg.type === 0x64) {
|
|
726
|
+
const result = decodeCopyData(new Uint8Array(msg.data))
|
|
727
|
+
if (result) allDecoded.push(result)
|
|
728
|
+
}
|
|
725
729
|
}
|
|
730
|
+
const relations = allDecoded.filter(
|
|
731
|
+
(m) => m.type === 'Relation'
|
|
732
|
+
) as RelationMessage[]
|
|
733
|
+
const tableNames = relations.map((r) => r.tableName)
|
|
734
|
+
if (tableNames.includes('items') && tableNames.includes('products')) break
|
|
726
735
|
}
|
|
727
736
|
|
|
728
|
-
const relations =
|
|
737
|
+
const relations = allDecoded.filter((m) => m.type === 'Relation') as RelationMessage[]
|
|
729
738
|
const tableNames = relations.map((r) => r.tableName)
|
|
730
739
|
expect(tableNames).toContain('items')
|
|
731
|
-
expect(tableNames).toContain('
|
|
740
|
+
expect(tableNames).toContain('products')
|
|
732
741
|
|
|
733
742
|
replClient.close()
|
|
734
743
|
}, 15_000)
|