orez 0.0.63 → 0.0.64
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 +22 -31
- package/dist/admin/server.d.ts +1 -0
- package/dist/admin/server.d.ts.map +1 -1
- package/dist/admin/server.js +12 -2
- package/dist/admin/server.js.map +1 -1
- package/dist/admin/ui.d.ts.map +1 -1
- package/dist/admin/ui.js +4 -0
- package/dist/admin/ui.js.map +1 -1
- package/dist/cli.js +92 -39
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +138 -92
- package/dist/index.js.map +1 -1
- package/dist/log.d.ts +3 -0
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +22 -0
- package/dist/log.js.map +1 -1
- package/dist/pglite-manager.d.ts.map +1 -1
- package/dist/pglite-manager.js +8 -6
- package/dist/pglite-manager.js.map +1 -1
- package/dist/replication/change-tracker.d.ts.map +1 -1
- package/dist/replication/change-tracker.js +23 -21
- package/dist/replication/change-tracker.js.map +1 -1
- package/dist/replication/handler.d.ts.map +1 -1
- package/dist/replication/handler.js +12 -8
- package/dist/replication/handler.js.map +1 -1
- package/package.json +2 -2
- package/src/admin/server.ts +14 -2
- package/src/admin/ui.ts +4 -0
- package/src/cli.ts +120 -58
- package/src/index.ts +149 -91
- package/src/integration/restore-reset.test.ts +284 -0
- package/src/log.ts +25 -0
- package/src/pglite-manager.ts +11 -9
- package/src/replication/change-tracker.test.ts +17 -0
- package/src/replication/change-tracker.ts +28 -30
- package/src/replication/handler.ts +14 -8
|
@@ -144,6 +144,23 @@ describe('change-tracker', () => {
|
|
|
144
144
|
expect(internalChanges).toHaveLength(0)
|
|
145
145
|
})
|
|
146
146
|
|
|
147
|
+
it('respects empty configured publication (tracks no public tables)', async () => {
|
|
148
|
+
const prev = process.env.ZERO_APP_PUBLICATIONS
|
|
149
|
+
process.env.ZERO_APP_PUBLICATIONS = 'zero_scope'
|
|
150
|
+
try {
|
|
151
|
+
await db.exec(`CREATE PUBLICATION "zero_scope"`)
|
|
152
|
+
await installChangeTracking(db) // reinstall picks up publication scope
|
|
153
|
+
await db.exec(`TRUNCATE public._zero_changes`)
|
|
154
|
+
|
|
155
|
+
await db.exec(`INSERT INTO public.items (name, value) VALUES ('x', 1)`)
|
|
156
|
+
const changes = await getChangesSince(db, 0)
|
|
157
|
+
expect(changes).toHaveLength(0)
|
|
158
|
+
} finally {
|
|
159
|
+
if (prev === undefined) delete process.env.ZERO_APP_PUBLICATIONS
|
|
160
|
+
else process.env.ZERO_APP_PUBLICATIONS = prev
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
|
|
147
164
|
it('handles NULL column values', async () => {
|
|
148
165
|
await db.exec(`INSERT INTO public.items (name, value) VALUES ('nulltest', NULL)`)
|
|
149
166
|
|
|
@@ -76,12 +76,10 @@ function quoteIdent(name: string): string {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
async function installTriggersOnAllTables(db: PGlite): Promise<void> {
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
const pubName = process.env.ZERO_APP_PUBLICATIONS
|
|
79
|
+
// If a publication is configured, respect it strictly. This avoids accidentally
|
|
80
|
+
// streaming private tables when publication membership is temporarily empty.
|
|
81
|
+
const pubName = process.env.ZERO_APP_PUBLICATIONS?.trim()
|
|
83
82
|
let tables: { tablename: string }[]
|
|
84
|
-
|
|
85
83
|
if (pubName) {
|
|
86
84
|
const result = await db.query<{ tablename: string }>(
|
|
87
85
|
`SELECT tablename FROM pg_publication_tables
|
|
@@ -91,38 +89,38 @@ async function installTriggersOnAllTables(db: PGlite): Promise<void> {
|
|
|
91
89
|
[pubName]
|
|
92
90
|
)
|
|
93
91
|
tables = result.rows
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// drop stale triggers from tables NOT in the publication
|
|
102
|
-
// (these may exist from a prior install before the publication was created)
|
|
103
|
-
const publishedSet = new Set(tables.map((t) => t.tablename))
|
|
104
|
-
const allTriggered = await db.query<{ event_object_table: string }>(
|
|
105
|
-
`SELECT DISTINCT event_object_table FROM information_schema.triggers
|
|
106
|
-
WHERE trigger_name = '_zero_change_trigger'
|
|
107
|
-
AND event_object_schema = 'public'`
|
|
108
|
-
)
|
|
109
|
-
for (const { event_object_table } of allTriggered.rows) {
|
|
110
|
-
if (!publishedSet.has(event_object_table)) {
|
|
111
|
-
const quoted = quoteIdent(event_object_table)
|
|
112
|
-
await db.exec(`DROP TRIGGER IF EXISTS _zero_change_trigger ON public.${quoted}`)
|
|
113
|
-
log.debug.pglite(
|
|
114
|
-
`removed stale trigger from non-published table: ${event_object_table}`
|
|
115
|
-
)
|
|
116
|
-
}
|
|
92
|
+
if (tables.length > 0) {
|
|
93
|
+
log.debug.pglite(`using publication "${pubName}" (${tables.length} tables)`)
|
|
94
|
+
} else {
|
|
95
|
+
log.pglite(`publication "${pubName}" is empty; installing no public table triggers`)
|
|
117
96
|
}
|
|
118
97
|
} else {
|
|
119
|
-
const
|
|
98
|
+
const all = await db.query<{ tablename: string }>(
|
|
120
99
|
`SELECT tablename FROM pg_tables
|
|
121
100
|
WHERE schemaname = 'public'
|
|
122
101
|
AND tablename NOT IN ('migrations', '_zero_changes')
|
|
123
102
|
AND tablename NOT LIKE '_zero_%'`
|
|
124
103
|
)
|
|
125
|
-
tables =
|
|
104
|
+
tables = all.rows
|
|
105
|
+
log.debug.pglite(`using all public tables (${tables.length})`)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// drop stale triggers from tables NOT in the publication
|
|
109
|
+
// (these may exist from a prior install before the publication was created)
|
|
110
|
+
const publishedSet = new Set(tables.map((t) => t.tablename))
|
|
111
|
+
const allTriggered = await db.query<{ event_object_table: string }>(
|
|
112
|
+
`SELECT DISTINCT event_object_table FROM information_schema.triggers
|
|
113
|
+
WHERE trigger_name = '_zero_change_trigger'
|
|
114
|
+
AND event_object_schema = 'public'`
|
|
115
|
+
)
|
|
116
|
+
for (const { event_object_table } of allTriggered.rows) {
|
|
117
|
+
if (!publishedSet.has(event_object_table)) {
|
|
118
|
+
const quoted = quoteIdent(event_object_table)
|
|
119
|
+
await db.exec(`DROP TRIGGER IF EXISTS _zero_change_trigger ON public.${quoted}`)
|
|
120
|
+
log.debug.pglite(
|
|
121
|
+
`removed stale trigger from non-published table: ${event_object_table}`
|
|
122
|
+
)
|
|
123
|
+
}
|
|
126
124
|
}
|
|
127
125
|
|
|
128
126
|
let count = 0
|
|
@@ -299,24 +299,30 @@ export async function handleStartReplication(
|
|
|
299
299
|
$$ LANGUAGE plpgsql;
|
|
300
300
|
`)
|
|
301
301
|
|
|
302
|
-
// install notify
|
|
303
|
-
|
|
302
|
+
// install notify triggers from configured publication when available.
|
|
303
|
+
// when publication is configured but empty, install none to preserve scope.
|
|
304
|
+
const pubName = process.env.ZERO_APP_PUBLICATIONS?.trim()
|
|
304
305
|
let tables: { tablename: string }[]
|
|
305
306
|
if (pubName) {
|
|
306
307
|
const result = await db.query<{ tablename: string }>(
|
|
307
308
|
`SELECT tablename FROM pg_publication_tables
|
|
308
|
-
|
|
309
|
+
WHERE pubname = $1 AND schemaname = 'public' AND tablename NOT LIKE '_zero_%'`,
|
|
309
310
|
[pubName]
|
|
310
311
|
)
|
|
311
312
|
tables = result.rows
|
|
313
|
+
if (tables.length === 0) {
|
|
314
|
+
log.proxy(
|
|
315
|
+
`publication "${pubName}" is empty; installing no public notify triggers`
|
|
316
|
+
)
|
|
317
|
+
}
|
|
312
318
|
} else {
|
|
313
|
-
const
|
|
319
|
+
const all = await db.query<{ tablename: string }>(
|
|
314
320
|
`SELECT tablename FROM pg_tables
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
321
|
+
WHERE schemaname = 'public'
|
|
322
|
+
AND tablename NOT IN ('migrations', '_zero_changes')
|
|
323
|
+
AND tablename NOT LIKE '_zero_%'`
|
|
318
324
|
)
|
|
319
|
-
tables =
|
|
325
|
+
tables = all.rows
|
|
320
326
|
}
|
|
321
327
|
|
|
322
328
|
for (const { tablename } of tables) {
|