orez 0.0.37 → 0.0.39

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.
@@ -67,6 +67,35 @@ export async function installChangeTracking(db: PGlite): Promise<void> {
67
67
  $$ LANGUAGE plpgsql;
68
68
  `)
69
69
 
70
+ // auto-install change tracking on tables created after startup (e.g. via restore
71
+ // or wire protocol). uses a DDL event trigger that fires on CREATE TABLE.
72
+ await db.exec(`
73
+ CREATE OR REPLACE FUNCTION public._zero_auto_track() RETURNS event_trigger AS $$
74
+ DECLARE
75
+ obj record;
76
+ BEGIN
77
+ FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands()
78
+ WHERE command_tag = 'CREATE TABLE'
79
+ LOOP
80
+ IF obj.schema_name = 'public'
81
+ AND obj.object_identity NOT LIKE '%._zero_%'
82
+ AND obj.object_identity NOT LIKE '%.migrations'
83
+ THEN
84
+ EXECUTE format(
85
+ 'CREATE TRIGGER _zero_change_trigger AFTER INSERT OR UPDATE OR DELETE ON %s FOR EACH ROW EXECUTE FUNCTION public._zero_track_change()',
86
+ obj.object_identity
87
+ );
88
+ END IF;
89
+ END LOOP;
90
+ END;
91
+ $$ LANGUAGE plpgsql;
92
+
93
+ DROP EVENT TRIGGER IF EXISTS _zero_auto_track_trigger;
94
+ CREATE EVENT TRIGGER _zero_auto_track_trigger ON ddl_command_end
95
+ WHEN TAG IN ('CREATE TABLE')
96
+ EXECUTE FUNCTION public._zero_auto_track();
97
+ `)
98
+
70
99
  // install triggers on all public tables
71
100
  await installTriggersOnAllTables(db)
72
101
  }
@@ -140,6 +169,58 @@ async function installTriggersOnAllTables(db: PGlite): Promise<void> {
140
169
  log.debug.pglite(`installed change tracking triggers on ${count} tables`)
141
170
  }
142
171
 
172
+ /**
173
+ * re-install change tracking triggers on any public tables that don't have them.
174
+ * catches tables created between startup and replication start.
175
+ */
176
+ export async function ensureChangeTrackingOnAllTables(db: PGlite): Promise<void> {
177
+ const pubName = process.env.ZERO_APP_PUBLICATIONS
178
+ let tables: { tablename: string }[]
179
+
180
+ if (pubName) {
181
+ const result = await db.query<{ tablename: string }>(
182
+ `SELECT tablename FROM pg_publication_tables
183
+ WHERE pubname = $1
184
+ AND schemaname = 'public'
185
+ AND tablename NOT LIKE '_zero_%'`,
186
+ [pubName]
187
+ )
188
+ tables = result.rows
189
+ } else {
190
+ const result = await db.query<{ tablename: string }>(
191
+ `SELECT tablename FROM pg_tables
192
+ WHERE schemaname = 'public'
193
+ AND tablename NOT IN ('migrations', '_zero_changes')
194
+ AND tablename NOT LIKE '_zero_%'`
195
+ )
196
+ tables = result.rows
197
+ }
198
+
199
+ // find tables missing the change trigger
200
+ const triggered = await db.query<{ event_object_table: string }>(
201
+ `SELECT DISTINCT event_object_table FROM information_schema.triggers
202
+ WHERE trigger_name = '_zero_change_trigger'
203
+ AND event_object_schema = 'public'`
204
+ )
205
+ const hasTracker = new Set(triggered.rows.map((r) => r.event_object_table))
206
+
207
+ let count = 0
208
+ for (const { tablename } of tables) {
209
+ if (hasTracker.has(tablename)) continue
210
+ const quoted = quoteIdent(tablename)
211
+ await db.exec(`
212
+ CREATE TRIGGER _zero_change_trigger
213
+ AFTER INSERT OR UPDATE OR DELETE ON public.${quoted}
214
+ FOR EACH ROW EXECUTE FUNCTION public._zero_track_change();
215
+ `)
216
+ count++
217
+ }
218
+
219
+ if (count > 0) {
220
+ log.debug.pglite(`installed change tracking on ${count} new tables`)
221
+ }
222
+ }
223
+
143
224
  /**
144
225
  * install change tracking triggers on tables in shard schemas.
145
226
  * zero-cache creates shard schemas (e.g. chat_0) with clients/mutations
@@ -11,6 +11,7 @@ import {
11
11
  getChangesSince,
12
12
  getCurrentWatermark,
13
13
  installTriggersOnShardTables,
14
+ ensureChangeTrackingOnAllTables,
14
15
  type ChangeRecord,
15
16
  } from './change-tracker.js'
16
17
  import {
@@ -284,6 +285,9 @@ export async function handleStartReplication(
284
285
  // "already in transaction" errors when they interleave.
285
286
  await mutex.acquire()
286
287
  try {
288
+ // install change tracking triggers on any tables created after startup
289
+ await ensureChangeTrackingOnAllTables(db)
290
+
287
291
  // install change tracking triggers on shard schema tables (e.g. chat_0.clients)
288
292
  // these track zero-cache's lastMutationID for .server promise resolution
289
293
  await installTriggersOnShardTables(db)