1+ import { LockContext } from 'src/db/DBAdapter.js' ;
2+
13export enum DiffTriggerOperation {
24 INSERT = 'INSERT' ,
35 UPDATE = 'UPDATE' ,
46 DELETE = 'DELETE'
57}
68
7- export type TriggerResultUpdate = {
8- operation : 'UPDATE' ;
9+ export interface BaseTriggerDiffRecord {
910 id : string ;
11+ operation : DiffTriggerOperation ;
12+ /**
13+ * Time the change operation was recorded.
14+ * This is in ISO 8601 format, e.g. `2023-10-01T12:00:00.000Z`.
15+ */
16+ timestamp : string ;
17+ }
18+
19+ export interface TriggerDiffUpdateRecord extends BaseTriggerDiffRecord {
20+ operation : DiffTriggerOperation . UPDATE ;
1021 change : string ;
11- } ;
22+ }
1223
13- export type TriggerResultInsert = {
14- operation : 'INSERT' ;
15- id : string ;
24+ export interface TriggerDiffInsertRecord extends BaseTriggerDiffRecord {
25+ operation : DiffTriggerOperation . INSERT ;
1626 change : string ;
17- } ;
27+ }
1828
19- export type TriggerResultDelete = {
20- id : string ;
21- operation : 'DELETE' ;
22- } ;
29+ export interface TriggerDiffDeleteRecord extends BaseTriggerDiffRecord {
30+ operation : DiffTriggerOperation . DELETE ;
31+ }
2332
24- export type TriggerDiffResult = TriggerResultUpdate | TriggerResultInsert | TriggerResultDelete ;
33+ export type TriggerDiffRecord = TriggerDiffUpdateRecord | TriggerDiffInsertRecord | TriggerDiffDeleteRecord ;
2534
26- export type CreateDiffTriggerOptions = {
35+ export interface TriggerCreationHooks {
36+ /**
37+ * Executed inside the write lock before the trigger is created.
38+ */
39+ beforeCreate ?: ( context : LockContext ) => Promise < void > ;
40+ }
41+
42+ export interface CreateDiffTriggerOptions {
2743 /**
2844 * Source table to trigger and track changes from.
2945 */
@@ -51,13 +67,59 @@ export type CreateDiffTriggerOptions = {
5167 * This is useful for only triggering on specific conditions.
5268 * For example, you can use it to only trigger on certain values in the NEW row.
5369 * Note that for PowerSync the data is stored in a JSON column named `data`.
54- * @example `NEW.data.status = 'active' AND length(NEW.data.name) > 3`
70+ * @example
71+ * [`NEW.data.status = 'active' AND length(NEW.data.name) > 3`]
5572 */
5673 when ?: string ;
57- } ;
74+
75+ /**
76+ * Optional context to create the triggers in.
77+ * This can be useful to synchronize the current state and fetch all changes after the current state.
78+ */
79+ hooks ?: TriggerCreationHooks ;
80+ }
5881
5982export type TriggerRemoveCallback = ( ) => Promise < void > ;
6083
84+ export interface TriggerDiffHandlerContext {
85+ /**
86+ * Allows querying the database with access to the table containing diff records.
87+ * The diff table is accessible via the `diff` accessor.
88+ * The `DIFF` table is of the form:
89+ *
90+ * ```sql
91+ * CREATE TEMP TABLE ${destination} (
92+ * id TEXT,
93+ * operation TEXT,
94+ * change TEXT,
95+ * timestamp TEXT
96+ * );
97+ * ```
98+ * The `change` column contains the JSON representation of the change. This column is NULL for
99+ * {@link DiffTriggerOperation#DELETE} operations.
100+ * For {@link DiffTriggerOperation#UPDATE} operations the `change` column is a JSON object containing both the `new` and `old` values:
101+ *
102+ * @example
103+ * ```sql
104+ * SELECT
105+ * todos.*
106+ * FROM
107+ * DIFF
108+ * JOIN todos ON DIFF.id = todos.id
109+ * ```
110+ */
111+ getAll : < T = any > ( query : string , params ?: any [ ] ) => Promise < T [ ] > ;
112+ }
113+
114+ export interface TrackDiffOptions {
115+ source : string ;
116+ filter ?: string ;
117+ columns ?: string [ ] ;
118+ operations : DiffTriggerOperation [ ] ;
119+ onChange : ( context : TriggerDiffHandlerContext ) => Promise < void > ;
120+ hooks ?: TriggerCreationHooks ;
121+ }
122+
61123export interface TriggerManager {
62124 /**
63125 * Creates a temporary trigger which tracks changes to a source table
@@ -66,4 +128,53 @@ export interface TriggerManager {
66128 * @returns A callback to remove the trigger and drop the destination table.
67129 */
68130 createDiffTrigger ( options : CreateDiffTriggerOptions ) : Promise < TriggerRemoveCallback > ;
131+
132+ /**
133+ * Tracks changes for a table. Triggering a provided handler on changes.
134+ * Uses {@link createDiffTrigger} internally to create a temporary destination table.
135+ * @returns A callback to cleanup the trigger and stop tracking changes.
136+ *
137+ * @example
138+ * ```javascript
139+ * database.triggers.trackTableDiff({
140+ * source: 'ps_data__todos',
141+ * columns: ['list_id'],
142+ * operations: [DiffTriggerOperation.INSERT],
143+ * onChange: async (context) => {
144+ * console.log('Change detected, fetching new todos');
145+ * // Fetches the todo records that were inserted during this diff
146+ * const newTodos = await context.getAll<Database['todos']>("
147+ * SELECT
148+ * todos.*
149+ * FROM
150+ * DIFF
151+ * JOIN todos ON DIFF.id = todos.id
152+ * ");
153+ * todos.push(...newTodos);
154+ * },
155+ * hooks: {
156+ * beforeCreate: async (lockContext) => {
157+ * // This hook is executed inside the write lock before the trigger is created.
158+ * // It can be used to synchronize the current state and fetch all changes after the current state.
159+ * // Read the current state of the todos table
160+ * const currentTodos = await lockContext.getAll<Database['todos']>(
161+ * "
162+ * SELECT
163+ * *
164+ * FROM
165+ * todos
166+ * WHERE
167+ * list_id = ?
168+ * ",
169+ * [firstList.id]
170+ * );
171+ *
172+ * // Example code could process the current todos if necessary
173+ * todos.push(...currentTodos);
174+ * }
175+ * }
176+ * });
177+ * ```
178+ */
179+ trackTableDiff ( options : TrackDiffOptions ) : Promise < TriggerRemoveCallback > ;
69180}
0 commit comments