Skip to content

Commit 49f564f

Browse files
committed
Switched to manageDestinationExternally option instead, escaping all internal management of the destination table.
1 parent cc187a0 commit 49f564f

4 files changed

Lines changed: 58 additions & 48 deletions

File tree

packages/common/src/client/triggers/TriggerManager.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -225,18 +225,18 @@ export interface CreateDiffTriggerOptions extends BaseCreateDiffTriggerOptions {
225225
destination: string;
226226

227227
/**
228-
* The destination table persists beyond this trigger's lifetime and is not automatically dropped when the trigger is removed.
229-
* Additionally, if the trigger already exists with the same destination, it will be reused instead of failing with a name conflict error.
228+
* When true, the diff trigger will not create or drop the destination table.
229+
* The caller is responsible for ensuring the table exists with the correct
230+
* schema before creating the trigger and for dropping it when no longer needed.
230231
*/
231-
persistDestination?: boolean;
232+
manageDestinationExternally?: boolean;
232233
}
233234

234235
/**
235236
* @experimental
236237
* Callback to drop a trigger after it has been created.
237-
* When invoked with force=true, it will also drop the destination table even if `persistDestination` was true.
238238
*/
239-
export type TriggerRemoveCallback = (force?: boolean) => Promise<void>;
239+
export type TriggerRemoveCallback = () => Promise<void>;
240240

241241
/**
242242
* @experimental

packages/common/src/client/triggers/TriggerManagerImpl.ts

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ export class TriggerManagerImpl implements TriggerManager {
201201
columns,
202202
when,
203203
hooks,
204-
persistDestination = false,
204+
manageDestinationExternally = false,
205205
// Fall back to the provided default if not given on this level
206206
useStorage = this.defaultConfig.useStorageByDefault
207207
} = options;
@@ -269,11 +269,11 @@ export class TriggerManagerImpl implements TriggerManager {
269269
* we need to ensure we can cleanup the created resources.
270270
* We unfortunately cannot rely on transaction rollback.
271271
*/
272-
const cleanup = async (force?: boolean) => {
272+
const cleanup = async () => {
273273
disposeWarningListener();
274274
return this.db.writeLock(async (tx) => {
275275
await this.removeTriggers(tx, triggerIds);
276-
if (!persistDestination || force) {
276+
if (!manageDestinationExternally) {
277277
await tx.execute(/* sql */ `DROP TABLE IF EXISTS ${destination};`);
278278
}
279279
await releaseStorageClaim?.();
@@ -283,16 +283,18 @@ export class TriggerManagerImpl implements TriggerManager {
283283
const setup = async (tx: LockContext) => {
284284
// Allow user code to execute in this lock context before the trigger is created.
285285
await hooks?.beforeCreate?.(tx);
286-
await tx.execute(/* sql */ `
287-
CREATE ${tableTriggerTypeClause} TABLE ${persistDestination ? 'IF NOT EXISTS ' : ''}${destination} (
288-
operation_id INTEGER PRIMARY KEY AUTOINCREMENT,
289-
id TEXT,
290-
operation TEXT,
291-
timestamp TEXT,
292-
value TEXT,
293-
previous_value TEXT
294-
)
295-
`);
286+
if (!manageDestinationExternally) {
287+
await tx.execute(/* sql */ `
288+
CREATE ${tableTriggerTypeClause} TABLE ${destination} (
289+
operation_id INTEGER PRIMARY KEY AUTOINCREMENT,
290+
id TEXT,
291+
operation TEXT,
292+
timestamp TEXT,
293+
value TEXT,
294+
previous_value TEXT
295+
)
296+
`);
297+
}
296298

297299
if (operations.includes(DiffTriggerOperation.INSERT)) {
298300
const insertTriggerId = this.generateTriggerName(DiffTriggerOperation.INSERT, destination, id);
@@ -471,9 +473,9 @@ export class TriggerManagerImpl implements TriggerManager {
471473
hooks
472474
});
473475

474-
return async (force?: boolean) => {
476+
return async () => {
475477
abortOnChange();
476-
await removeTrigger(force);
478+
await removeTrigger();
477479
};
478480
} catch (error) {
479481
try {

packages/node/tests/trigger.test.ts

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -616,12 +616,24 @@ describe('Triggers', () => {
616616
databaseTest('persistDestination: should not drop destination table on dispose', async ({ database }) => {
617617
const table = 'persist_dest_dispose_test';
618618

619+
// Manually create the destination table (simulates a table that persisted from a prior session)
620+
await database.execute(`
621+
CREATE TABLE ${table} (
622+
operation_id INTEGER PRIMARY KEY AUTOINCREMENT,
623+
id TEXT,
624+
operation TEXT,
625+
timestamp TEXT,
626+
value TEXT,
627+
previous_value TEXT
628+
)
629+
`);
630+
619631
const dispose = await database.triggers.createDiffTrigger({
620632
source: 'todos',
621633
destination: table,
622634
when: { [DiffTriggerOperation.INSERT]: 'TRUE' },
623635
useStorage: true, // persistent table so we can verify via sqlite_master
624-
persistDestination: true
636+
manageDestinationExternally: true
625637
});
626638

627639
// Table must exist before dispose
@@ -634,23 +646,20 @@ describe('Triggers', () => {
634646
await dispose();
635647

636648
// Table must STILL exist — currently FAILS (impl drops the table unconditionally)
637-
rows = await database.getAll<{ name: string }>(
638-
`SELECT name FROM sqlite_master WHERE type='table' AND name = ?`,
639-
[table]
640-
);
649+
rows = await database.getAll<{ name: string }>(`SELECT name FROM sqlite_master WHERE type='table' AND name = ?`, [
650+
table
651+
]);
641652
expect(rows.length).toEqual(1);
642653

643654
// Manual cleanup so the test doesn't leak
644655
await database.execute(`DROP TABLE IF EXISTS ${table}`);
645656
});
646657

647-
databaseTest(
648-
'persistDestination: should allow reusing an existing destination table',
649-
async ({ database }) => {
650-
const table = 'persist_dest_reuse_test';
658+
databaseTest('persistDestination: should allow reusing an existing destination table', async ({ database }) => {
659+
const table = 'persist_dest_reuse_test';
651660

652-
// Manually create the destination table (simulates a table that persisted from a prior session)
653-
await database.execute(`
661+
// Manually create the destination table (simulates a table that persisted from a prior session)
662+
await database.execute(`
654663
CREATE TABLE ${table} (
655664
operation_id INTEGER PRIMARY KEY AUTOINCREMENT,
656665
id TEXT,
@@ -661,22 +670,21 @@ describe('Triggers', () => {
661670
)
662671
`);
663672

664-
// Must NOT throw even though the table already exists.
665-
// Currently FAILS — impl runs bare CREATE TABLE which SQLite rejects with "table already exists".
666-
const dispose = await database.triggers.createDiffTrigger({
667-
source: 'todos',
668-
destination: table,
669-
when: { [DiffTriggerOperation.INSERT]: 'TRUE' },
670-
useStorage: true,
671-
persistDestination: true
672-
});
673+
// Must NOT throw even though the table already exists.
674+
// Currently FAILS — impl runs bare CREATE TABLE which SQLite rejects with "table already exists".
675+
const dispose = await database.triggers.createDiffTrigger({
676+
source: 'todos',
677+
destination: table,
678+
when: { [DiffTriggerOperation.INSERT]: 'TRUE' },
679+
useStorage: true,
680+
manageDestinationExternally: true
681+
});
673682

674-
await dispose();
683+
await dispose();
675684

676-
// Manual cleanup
677-
await database.execute(`DROP TABLE IF EXISTS ${table}`);
678-
}
679-
);
685+
// Manual cleanup
686+
await database.execute(`DROP TABLE IF EXISTS ${table}`);
687+
});
680688

681689
databaseTest('Should cast operation_id as string with withDiff option', async ({ database }) => {
682690
const results: TriggerDiffRecord<string>[] = [];

packages/web/tests/triggers.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ describe('Triggers', () => {
5555
}
5656
});
5757

58-
onTestFinished(() => disposeTrigger());
58+
onTestFinished(disposeTrigger);
5959

6060
await db.execute("INSERT INTO customers (id, name) VALUES (uuid(), 'test')");
6161

@@ -103,7 +103,7 @@ describe('Triggers', () => {
103103
}
104104
});
105105

106-
onTestFinished(() => disposeTrigger());
106+
onTestFinished(disposeTrigger);
107107

108108
await db.execute("INSERT INTO customers (id, name) VALUES (uuid(), 'test')");
109109

@@ -221,7 +221,7 @@ describe('Triggers', () => {
221221
}
222222
});
223223

224-
onTestFinished(() => disposeTrigger());
224+
onTestFinished(disposeTrigger);
225225

226226
// Perform an insert from client B
227227
await dbB.execute("INSERT INTO customers (id, name) VALUES (uuid(), 'from-client-b')");

0 commit comments

Comments
 (0)