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.
@@ -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
- // use the configured app publication to determine which tables to track.
80
- // this avoids streaming changes for private tables (user, account, session, etc.)
81
- // that zero-cache doesn't know about.
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
- log.debug.pglite(`using publication "${pubName}" (${tables.length} tables)`)
95
- if (tables.length === 0) {
96
- log.debug.pglite(
97
- `publication "${pubName}" has no tables yet (will be populated by migrations)`
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 result = await db.query<{ tablename: string }>(
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 = result.rows
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 trigger on tracked tables (use configured publication if available)
303
- const pubName = process.env.ZERO_APP_PUBLICATIONS
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
- WHERE pubname = $1 AND schemaname = 'public' AND tablename NOT LIKE '_zero_%'`,
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 result = await db.query<{ tablename: string }>(
319
+ const all = await db.query<{ tablename: string }>(
314
320
  `SELECT tablename FROM pg_tables
315
- WHERE schemaname = 'public'
316
- AND tablename NOT IN ('migrations', '_zero_changes')
317
- AND tablename NOT LIKE '_zero_%'`
321
+ WHERE schemaname = 'public'
322
+ AND tablename NOT IN ('migrations', '_zero_changes')
323
+ AND tablename NOT LIKE '_zero_%'`
318
324
  )
319
- tables = result.rows
325
+ tables = all.rows
320
326
  }
321
327
 
322
328
  for (const { tablename } of tables) {