@powersync/common 1.45.0 → 1.46.0
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/dist/bundle.cjs +412 -2067
- package/dist/bundle.cjs.map +1 -1
- package/dist/bundle.mjs +410 -2068
- package/dist/bundle.mjs.map +1 -1
- package/dist/bundle.node.cjs +237 -63
- package/dist/bundle.node.cjs.map +1 -1
- package/dist/bundle.node.mjs +235 -64
- package/dist/bundle.node.mjs.map +1 -1
- package/dist/index.d.cts +94 -15
- package/lib/client/AbstractPowerSyncDatabase.d.ts +9 -2
- package/lib/client/AbstractPowerSyncDatabase.js +18 -5
- package/lib/client/AbstractPowerSyncDatabase.js.map +1 -1
- package/lib/client/sync/stream/AbstractRemote.js +41 -32
- package/lib/client/sync/stream/AbstractRemote.js.map +1 -1
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.d.ts +7 -12
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +10 -12
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js.map +1 -1
- package/lib/client/triggers/MemoryTriggerClaimManager.d.ts +6 -0
- package/lib/client/triggers/MemoryTriggerClaimManager.js +21 -0
- package/lib/client/triggers/MemoryTriggerClaimManager.js.map +1 -0
- package/lib/client/triggers/TriggerManager.d.ts +37 -0
- package/lib/client/triggers/TriggerManagerImpl.d.ts +24 -3
- package/lib/client/triggers/TriggerManagerImpl.js +133 -11
- package/lib/client/triggers/TriggerManagerImpl.js.map +1 -1
- package/lib/index.d.ts +3 -0
- package/lib/index.js +3 -0
- package/lib/index.js.map +1 -1
- package/lib/utils/DataStream.js +11 -2
- package/lib/utils/DataStream.js.map +1 -1
- package/package.json +4 -3
- package/src/client/AbstractPowerSyncDatabase.ts +21 -6
- package/src/client/sync/stream/AbstractRemote.ts +47 -35
- package/src/client/sync/stream/AbstractStreamingSyncImplementation.ts +11 -14
- package/src/client/triggers/MemoryTriggerClaimManager.ts +25 -0
- package/src/client/triggers/TriggerManager.ts +50 -6
- package/src/client/triggers/TriggerManagerImpl.ts +177 -13
- package/src/index.ts +3 -0
- package/src/utils/DataStream.ts +13 -2
package/dist/bundle.node.cjs
CHANGED
|
@@ -6722,7 +6722,7 @@ function requireDist () {
|
|
|
6722
6722
|
|
|
6723
6723
|
var distExports = requireDist();
|
|
6724
6724
|
|
|
6725
|
-
var version = "1.
|
|
6725
|
+
var version = "1.46.0";
|
|
6726
6726
|
var PACKAGE = {
|
|
6727
6727
|
version: version};
|
|
6728
6728
|
|
|
@@ -6793,6 +6793,16 @@ class DataStream extends BaseObserver {
|
|
|
6793
6793
|
* @returns a Data payload or Null if the stream closed.
|
|
6794
6794
|
*/
|
|
6795
6795
|
async read() {
|
|
6796
|
+
if (this.closed) {
|
|
6797
|
+
return null;
|
|
6798
|
+
}
|
|
6799
|
+
// Wait for any pending processing to complete first.
|
|
6800
|
+
// This ensures we register our listener before calling processQueue(),
|
|
6801
|
+
// avoiding a race where processQueue() sees no reader and returns early.
|
|
6802
|
+
if (this.processingPromise) {
|
|
6803
|
+
await this.processingPromise;
|
|
6804
|
+
}
|
|
6805
|
+
// Re-check after await - stream may have closed while we were waiting
|
|
6796
6806
|
if (this.closed) {
|
|
6797
6807
|
return null;
|
|
6798
6808
|
}
|
|
@@ -6832,7 +6842,7 @@ class DataStream extends BaseObserver {
|
|
|
6832
6842
|
}
|
|
6833
6843
|
const promise = (this.processingPromise = this._processQueue());
|
|
6834
6844
|
promise.finally(() => {
|
|
6835
|
-
|
|
6845
|
+
this.processingPromise = null;
|
|
6836
6846
|
});
|
|
6837
6847
|
return promise;
|
|
6838
6848
|
}
|
|
@@ -6864,7 +6874,6 @@ class DataStream extends BaseObserver {
|
|
|
6864
6874
|
this.notifyDataAdded = null;
|
|
6865
6875
|
}
|
|
6866
6876
|
if (this.dataQueue.length > 0) {
|
|
6867
|
-
// Next tick
|
|
6868
6877
|
setTimeout(() => this.processQueue());
|
|
6869
6878
|
}
|
|
6870
6879
|
}
|
|
@@ -7484,7 +7493,11 @@ class AbstractRemote {
|
|
|
7484
7493
|
};
|
|
7485
7494
|
const stream = new DataStream({
|
|
7486
7495
|
logger: this.logger,
|
|
7487
|
-
mapLine: mapLine
|
|
7496
|
+
mapLine: mapLine,
|
|
7497
|
+
pressure: {
|
|
7498
|
+
highWaterMark: 20,
|
|
7499
|
+
lowWaterMark: 10
|
|
7500
|
+
}
|
|
7488
7501
|
});
|
|
7489
7502
|
abortSignal?.addEventListener('abort', () => {
|
|
7490
7503
|
closeReader();
|
|
@@ -7492,42 +7505,47 @@ class AbstractRemote {
|
|
|
7492
7505
|
});
|
|
7493
7506
|
const decoder = this.createTextDecoder();
|
|
7494
7507
|
let buffer = '';
|
|
7495
|
-
const
|
|
7496
|
-
|
|
7497
|
-
|
|
7508
|
+
const consumeStream = async () => {
|
|
7509
|
+
while (!stream.closed && !abortSignal?.aborted && !readerReleased) {
|
|
7510
|
+
const { done, value } = await reader.read();
|
|
7511
|
+
if (done) {
|
|
7512
|
+
const remaining = buffer.trim();
|
|
7513
|
+
if (remaining.length != 0) {
|
|
7514
|
+
stream.enqueueData(remaining);
|
|
7515
|
+
}
|
|
7516
|
+
stream.close();
|
|
7517
|
+
await closeReader();
|
|
7498
7518
|
return;
|
|
7499
7519
|
}
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
stream.enqueueData(remaining);
|
|
7508
|
-
}
|
|
7509
|
-
stream.close();
|
|
7510
|
-
await closeReader();
|
|
7511
|
-
return;
|
|
7512
|
-
}
|
|
7513
|
-
const data = decoder.decode(value, { stream: true });
|
|
7514
|
-
buffer += data;
|
|
7515
|
-
const lines = buffer.split('\n');
|
|
7516
|
-
for (var i = 0; i < lines.length - 1; i++) {
|
|
7517
|
-
var l = lines[i].trim();
|
|
7518
|
-
if (l.length > 0) {
|
|
7519
|
-
stream.enqueueData(l);
|
|
7520
|
-
didCompleteLine = true;
|
|
7521
|
-
}
|
|
7522
|
-
}
|
|
7523
|
-
buffer = lines[lines.length - 1];
|
|
7520
|
+
const data = decoder.decode(value, { stream: true });
|
|
7521
|
+
buffer += data;
|
|
7522
|
+
const lines = buffer.split('\n');
|
|
7523
|
+
for (var i = 0; i < lines.length - 1; i++) {
|
|
7524
|
+
var l = lines[i].trim();
|
|
7525
|
+
if (l.length > 0) {
|
|
7526
|
+
stream.enqueueData(l);
|
|
7524
7527
|
}
|
|
7525
7528
|
}
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7529
|
+
buffer = lines[lines.length - 1];
|
|
7530
|
+
// Implement backpressure by waiting for the low water mark to be reached
|
|
7531
|
+
if (stream.dataQueue.length > stream.highWatermark) {
|
|
7532
|
+
await new Promise((resolve) => {
|
|
7533
|
+
const dispose = stream.registerListener({
|
|
7534
|
+
lowWater: async () => {
|
|
7535
|
+
resolve();
|
|
7536
|
+
dispose();
|
|
7537
|
+
},
|
|
7538
|
+
closed: () => {
|
|
7539
|
+
resolve();
|
|
7540
|
+
dispose();
|
|
7541
|
+
}
|
|
7542
|
+
});
|
|
7543
|
+
});
|
|
7529
7544
|
}
|
|
7530
|
-
}
|
|
7545
|
+
}
|
|
7546
|
+
};
|
|
7547
|
+
consumeStream().catch(ex => this.logger.error('Error consuming stream', ex));
|
|
7548
|
+
const l = stream.registerListener({
|
|
7531
7549
|
closed: () => {
|
|
7532
7550
|
closeReader();
|
|
7533
7551
|
l?.();
|
|
@@ -7606,18 +7624,17 @@ exports.SyncClientImplementation = void 0;
|
|
|
7606
7624
|
*
|
|
7607
7625
|
* This is the default option.
|
|
7608
7626
|
*
|
|
7609
|
-
* @deprecated
|
|
7610
|
-
*
|
|
7611
|
-
*
|
|
7627
|
+
* @deprecated We recommend the {@link RUST} client implementation for all apps. If you have issues with
|
|
7628
|
+
* the Rust client, please file an issue or reach out to us. The JavaScript client will be removed in a future
|
|
7629
|
+
* version of the PowerSync SDK.
|
|
7612
7630
|
*/
|
|
7613
7631
|
SyncClientImplementation["JAVASCRIPT"] = "js";
|
|
7614
7632
|
/**
|
|
7615
7633
|
* This implementation offloads the sync line decoding and handling into the PowerSync
|
|
7616
7634
|
* core extension.
|
|
7617
7635
|
*
|
|
7618
|
-
* @
|
|
7619
|
-
*
|
|
7620
|
-
* it has seen less real-world testing and is marked as __experimental__ at the moment.
|
|
7636
|
+
* This option is more performant than the {@link JAVASCRIPT} client, enabled by default and the
|
|
7637
|
+
* recommended client implementation for all apps.
|
|
7621
7638
|
*
|
|
7622
7639
|
* ## Compatibility warning
|
|
7623
7640
|
*
|
|
@@ -7635,13 +7652,9 @@ exports.SyncClientImplementation = void 0;
|
|
|
7635
7652
|
SyncClientImplementation["RUST"] = "rust";
|
|
7636
7653
|
})(exports.SyncClientImplementation || (exports.SyncClientImplementation = {}));
|
|
7637
7654
|
/**
|
|
7638
|
-
* The default {@link SyncClientImplementation} to use.
|
|
7639
|
-
*
|
|
7640
|
-
* Please use this field instead of {@link SyncClientImplementation.JAVASCRIPT} directly. A future version
|
|
7641
|
-
* of the PowerSync SDK will enable {@link SyncClientImplementation.RUST} by default and remove the JavaScript
|
|
7642
|
-
* option.
|
|
7655
|
+
* The default {@link SyncClientImplementation} to use, {@link SyncClientImplementation.RUST}.
|
|
7643
7656
|
*/
|
|
7644
|
-
const DEFAULT_SYNC_CLIENT_IMPLEMENTATION = exports.SyncClientImplementation.
|
|
7657
|
+
const DEFAULT_SYNC_CLIENT_IMPLEMENTATION = exports.SyncClientImplementation.RUST;
|
|
7645
7658
|
const DEFAULT_CRUD_UPLOAD_THROTTLE_MS = 1000;
|
|
7646
7659
|
const DEFAULT_RETRY_DELAY_MS = 5000;
|
|
7647
7660
|
const DEFAULT_STREAMING_SYNC_OPTIONS = {
|
|
@@ -8051,6 +8064,9 @@ The next upload iteration will be delayed.`);
|
|
|
8051
8064
|
if (rawTables != null && rawTables.length) {
|
|
8052
8065
|
this.logger.warn('Raw tables require the Rust-based sync client. The JS client will ignore them.');
|
|
8053
8066
|
}
|
|
8067
|
+
if (this.activeStreams.length) {
|
|
8068
|
+
this.logger.error('Sync streams require `clientImplementation: SyncClientImplementation.RUST` when connecting.');
|
|
8069
|
+
}
|
|
8054
8070
|
this.logger.debug('Streaming sync iteration started');
|
|
8055
8071
|
this.options.adapter.startSession();
|
|
8056
8072
|
let [req, bucketMap] = await this.collectLocalBucketState();
|
|
@@ -8566,6 +8582,27 @@ The next upload iteration will be delayed.`);
|
|
|
8566
8582
|
}
|
|
8567
8583
|
}
|
|
8568
8584
|
|
|
8585
|
+
const CLAIM_STORE = new Map();
|
|
8586
|
+
/**
|
|
8587
|
+
* @internal
|
|
8588
|
+
* @experimental
|
|
8589
|
+
*/
|
|
8590
|
+
const MEMORY_TRIGGER_CLAIM_MANAGER = {
|
|
8591
|
+
async obtainClaim(identifier) {
|
|
8592
|
+
if (CLAIM_STORE.has(identifier)) {
|
|
8593
|
+
throw new Error(`A claim is already present for ${identifier}`);
|
|
8594
|
+
}
|
|
8595
|
+
const release = async () => {
|
|
8596
|
+
CLAIM_STORE.delete(identifier);
|
|
8597
|
+
};
|
|
8598
|
+
CLAIM_STORE.set(identifier, release);
|
|
8599
|
+
return release;
|
|
8600
|
+
},
|
|
8601
|
+
async checkClaim(identifier) {
|
|
8602
|
+
return CLAIM_STORE.has(identifier);
|
|
8603
|
+
}
|
|
8604
|
+
};
|
|
8605
|
+
|
|
8569
8606
|
/**
|
|
8570
8607
|
* SQLite operations to track changes for with {@link TriggerManager}
|
|
8571
8608
|
* @experimental
|
|
@@ -8577,9 +8614,20 @@ exports.DiffTriggerOperation = void 0;
|
|
|
8577
8614
|
DiffTriggerOperation["DELETE"] = "DELETE";
|
|
8578
8615
|
})(exports.DiffTriggerOperation || (exports.DiffTriggerOperation = {}));
|
|
8579
8616
|
|
|
8617
|
+
const DEFAULT_TRIGGER_MANAGER_CONFIGURATION = {
|
|
8618
|
+
useStorageByDefault: false
|
|
8619
|
+
};
|
|
8620
|
+
const TRIGGER_CLEANUP_INTERVAL_MS = 120_000; // 2 minutes
|
|
8621
|
+
/**
|
|
8622
|
+
* @internal
|
|
8623
|
+
* @experimental
|
|
8624
|
+
*/
|
|
8580
8625
|
class TriggerManagerImpl {
|
|
8581
8626
|
options;
|
|
8582
8627
|
schema;
|
|
8628
|
+
defaultConfig;
|
|
8629
|
+
cleanupTimeout;
|
|
8630
|
+
isDisposed;
|
|
8583
8631
|
constructor(options) {
|
|
8584
8632
|
this.options = options;
|
|
8585
8633
|
this.schema = options.schema;
|
|
@@ -8588,6 +8636,33 @@ class TriggerManagerImpl {
|
|
|
8588
8636
|
this.schema = schema;
|
|
8589
8637
|
}
|
|
8590
8638
|
});
|
|
8639
|
+
this.isDisposed = false;
|
|
8640
|
+
/**
|
|
8641
|
+
* Configure a cleanup to run on an interval.
|
|
8642
|
+
* The interval is configured using setTimeout to take the async
|
|
8643
|
+
* execution time of the callback into account.
|
|
8644
|
+
*/
|
|
8645
|
+
this.defaultConfig = DEFAULT_TRIGGER_MANAGER_CONFIGURATION;
|
|
8646
|
+
const cleanupCallback = async () => {
|
|
8647
|
+
this.cleanupTimeout = null;
|
|
8648
|
+
if (this.isDisposed) {
|
|
8649
|
+
return;
|
|
8650
|
+
}
|
|
8651
|
+
try {
|
|
8652
|
+
await this.cleanupResources();
|
|
8653
|
+
}
|
|
8654
|
+
catch (ex) {
|
|
8655
|
+
this.db.logger.error(`Caught error while attempting to cleanup triggers`, ex);
|
|
8656
|
+
}
|
|
8657
|
+
finally {
|
|
8658
|
+
// if not closed, set another timeout
|
|
8659
|
+
if (this.isDisposed) {
|
|
8660
|
+
return;
|
|
8661
|
+
}
|
|
8662
|
+
this.cleanupTimeout = setTimeout(cleanupCallback, TRIGGER_CLEANUP_INTERVAL_MS);
|
|
8663
|
+
}
|
|
8664
|
+
};
|
|
8665
|
+
this.cleanupTimeout = setTimeout(cleanupCallback, TRIGGER_CLEANUP_INTERVAL_MS);
|
|
8591
8666
|
}
|
|
8592
8667
|
get db() {
|
|
8593
8668
|
return this.options.db;
|
|
@@ -8605,13 +8680,95 @@ class TriggerManagerImpl {
|
|
|
8605
8680
|
await tx.execute(/* sql */ `DROP TRIGGER IF EXISTS ${triggerId}; `);
|
|
8606
8681
|
}
|
|
8607
8682
|
}
|
|
8683
|
+
dispose() {
|
|
8684
|
+
this.isDisposed = true;
|
|
8685
|
+
if (this.cleanupTimeout) {
|
|
8686
|
+
clearTimeout(this.cleanupTimeout);
|
|
8687
|
+
}
|
|
8688
|
+
}
|
|
8689
|
+
/**
|
|
8690
|
+
* Updates default config settings for platform specific use-cases.
|
|
8691
|
+
*/
|
|
8692
|
+
updateDefaults(config) {
|
|
8693
|
+
this.defaultConfig = {
|
|
8694
|
+
...this.defaultConfig,
|
|
8695
|
+
...config
|
|
8696
|
+
};
|
|
8697
|
+
}
|
|
8698
|
+
generateTriggerName(operation, destinationTable, triggerId) {
|
|
8699
|
+
return `__ps_temp_trigger_${operation.toLowerCase()}__${destinationTable}__${triggerId}`;
|
|
8700
|
+
}
|
|
8701
|
+
/**
|
|
8702
|
+
* Cleanup any SQLite triggers or tables that are no longer in use.
|
|
8703
|
+
*/
|
|
8704
|
+
async cleanupResources() {
|
|
8705
|
+
// we use the database here since cleanupResources is called during the PowerSyncDatabase initialization
|
|
8706
|
+
await this.db.database.writeLock(async (ctx) => {
|
|
8707
|
+
/**
|
|
8708
|
+
* Note: We only cleanup persisted triggers. These are tracked in the sqlite_master table.
|
|
8709
|
+
* temporary triggers will not be affected by this.
|
|
8710
|
+
* Query all triggers that match our naming pattern
|
|
8711
|
+
*/
|
|
8712
|
+
const triggers = await ctx.getAll(/* sql */ `
|
|
8713
|
+
SELECT
|
|
8714
|
+
name
|
|
8715
|
+
FROM
|
|
8716
|
+
sqlite_master
|
|
8717
|
+
WHERE
|
|
8718
|
+
type = 'trigger'
|
|
8719
|
+
AND name LIKE '__ps_temp_trigger_%'
|
|
8720
|
+
`);
|
|
8721
|
+
/** Use regex to extract table names and IDs from trigger names
|
|
8722
|
+
* Trigger naming convention: __ps_temp_trigger_<operation>__<destination_table>__<id>
|
|
8723
|
+
*/
|
|
8724
|
+
const triggerPattern = /^__ps_temp_trigger_(?:insert|update|delete)__(.+)__([a-f0-9_]{36})$/i;
|
|
8725
|
+
const trackedItems = new Map();
|
|
8726
|
+
for (const trigger of triggers) {
|
|
8727
|
+
const match = trigger.name.match(triggerPattern);
|
|
8728
|
+
if (match) {
|
|
8729
|
+
const [, table, id] = match;
|
|
8730
|
+
// Collect all trigger names for each id combo
|
|
8731
|
+
const existing = trackedItems.get(id);
|
|
8732
|
+
if (existing) {
|
|
8733
|
+
existing.triggerNames.push(trigger.name);
|
|
8734
|
+
}
|
|
8735
|
+
else {
|
|
8736
|
+
trackedItems.set(id, { table, id, triggerNames: [trigger.name] });
|
|
8737
|
+
}
|
|
8738
|
+
}
|
|
8739
|
+
}
|
|
8740
|
+
for (const trackedItem of trackedItems.values()) {
|
|
8741
|
+
// check if there is anything holding on to this item
|
|
8742
|
+
const hasClaim = await this.options.claimManager.checkClaim(trackedItem.id);
|
|
8743
|
+
if (hasClaim) {
|
|
8744
|
+
// This does not require cleanup
|
|
8745
|
+
continue;
|
|
8746
|
+
}
|
|
8747
|
+
this.db.logger.debug(`Clearing resources for trigger ${trackedItem.id} with table ${trackedItem.table}`);
|
|
8748
|
+
// We need to delete the triggers and table
|
|
8749
|
+
for (const triggerName of trackedItem.triggerNames) {
|
|
8750
|
+
await ctx.execute(`DROP TRIGGER IF EXISTS ${triggerName}`);
|
|
8751
|
+
}
|
|
8752
|
+
await ctx.execute(`DROP TABLE IF EXISTS ${trackedItem.table}`);
|
|
8753
|
+
}
|
|
8754
|
+
});
|
|
8755
|
+
}
|
|
8608
8756
|
async createDiffTrigger(options) {
|
|
8609
8757
|
await this.db.waitForReady();
|
|
8610
|
-
const { source, destination, columns, when, hooks
|
|
8758
|
+
const { source, destination, columns, when, hooks,
|
|
8759
|
+
// Fall back to the provided default if not given on this level
|
|
8760
|
+
useStorage = this.defaultConfig.useStorageByDefault } = options;
|
|
8611
8761
|
const operations = Object.keys(when);
|
|
8612
8762
|
if (operations.length == 0) {
|
|
8613
8763
|
throw new Error('At least one WHEN operation must be specified for the trigger.');
|
|
8614
8764
|
}
|
|
8765
|
+
/**
|
|
8766
|
+
* The clause to use when executing
|
|
8767
|
+
* CREATE ${tableTriggerTypeClause} TABLE
|
|
8768
|
+
* OR
|
|
8769
|
+
* CREATE ${tableTriggerTypeClause} TRIGGER
|
|
8770
|
+
*/
|
|
8771
|
+
const tableTriggerTypeClause = !useStorage ? 'TEMP' : '';
|
|
8615
8772
|
const whenClauses = Object.fromEntries(Object.entries(when).map(([operation, filter]) => [operation, `WHEN ${filter}`]));
|
|
8616
8773
|
/**
|
|
8617
8774
|
* Allow specifying the View name as the source.
|
|
@@ -8625,6 +8782,7 @@ class TriggerManagerImpl {
|
|
|
8625
8782
|
const internalSource = sourceDefinition.internalName;
|
|
8626
8783
|
const triggerIds = [];
|
|
8627
8784
|
const id = await this.getUUID();
|
|
8785
|
+
const releaseStorageClaim = useStorage ? await this.options.claimManager.obtainClaim(id) : null;
|
|
8628
8786
|
/**
|
|
8629
8787
|
* We default to replicating all columns if no columns array is provided.
|
|
8630
8788
|
*/
|
|
@@ -8657,26 +8815,27 @@ class TriggerManagerImpl {
|
|
|
8657
8815
|
return this.db.writeLock(async (tx) => {
|
|
8658
8816
|
await this.removeTriggers(tx, triggerIds);
|
|
8659
8817
|
await tx.execute(/* sql */ `DROP TABLE IF EXISTS ${destination};`);
|
|
8818
|
+
await releaseStorageClaim?.();
|
|
8660
8819
|
});
|
|
8661
8820
|
};
|
|
8662
8821
|
const setup = async (tx) => {
|
|
8663
8822
|
// Allow user code to execute in this lock context before the trigger is created.
|
|
8664
8823
|
await hooks?.beforeCreate?.(tx);
|
|
8665
8824
|
await tx.execute(/* sql */ `
|
|
8666
|
-
CREATE
|
|
8825
|
+
CREATE ${tableTriggerTypeClause} TABLE ${destination} (
|
|
8667
8826
|
operation_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
8668
8827
|
id TEXT,
|
|
8669
8828
|
operation TEXT,
|
|
8670
8829
|
timestamp TEXT,
|
|
8671
8830
|
value TEXT,
|
|
8672
8831
|
previous_value TEXT
|
|
8673
|
-
)
|
|
8832
|
+
)
|
|
8674
8833
|
`);
|
|
8675
8834
|
if (operations.includes(exports.DiffTriggerOperation.INSERT)) {
|
|
8676
|
-
const insertTriggerId =
|
|
8835
|
+
const insertTriggerId = this.generateTriggerName(exports.DiffTriggerOperation.INSERT, destination, id);
|
|
8677
8836
|
triggerIds.push(insertTriggerId);
|
|
8678
8837
|
await tx.execute(/* sql */ `
|
|
8679
|
-
CREATE
|
|
8838
|
+
CREATE ${tableTriggerTypeClause} TRIGGER ${insertTriggerId} AFTER INSERT ON ${internalSource} ${whenClauses[exports.DiffTriggerOperation.INSERT]} BEGIN
|
|
8680
8839
|
INSERT INTO
|
|
8681
8840
|
${destination} (id, operation, timestamp, value)
|
|
8682
8841
|
VALUES
|
|
@@ -8687,14 +8846,14 @@ class TriggerManagerImpl {
|
|
|
8687
8846
|
${jsonFragment('NEW')}
|
|
8688
8847
|
);
|
|
8689
8848
|
|
|
8690
|
-
END
|
|
8849
|
+
END
|
|
8691
8850
|
`);
|
|
8692
8851
|
}
|
|
8693
8852
|
if (operations.includes(exports.DiffTriggerOperation.UPDATE)) {
|
|
8694
|
-
const updateTriggerId =
|
|
8853
|
+
const updateTriggerId = this.generateTriggerName(exports.DiffTriggerOperation.UPDATE, destination, id);
|
|
8695
8854
|
triggerIds.push(updateTriggerId);
|
|
8696
8855
|
await tx.execute(/* sql */ `
|
|
8697
|
-
CREATE
|
|
8856
|
+
CREATE ${tableTriggerTypeClause} TRIGGER ${updateTriggerId} AFTER
|
|
8698
8857
|
UPDATE ON ${internalSource} ${whenClauses[exports.DiffTriggerOperation.UPDATE]} BEGIN
|
|
8699
8858
|
INSERT INTO
|
|
8700
8859
|
${destination} (id, operation, timestamp, value, previous_value)
|
|
@@ -8711,11 +8870,11 @@ class TriggerManagerImpl {
|
|
|
8711
8870
|
`);
|
|
8712
8871
|
}
|
|
8713
8872
|
if (operations.includes(exports.DiffTriggerOperation.DELETE)) {
|
|
8714
|
-
const deleteTriggerId =
|
|
8873
|
+
const deleteTriggerId = this.generateTriggerName(exports.DiffTriggerOperation.DELETE, destination, id);
|
|
8715
8874
|
triggerIds.push(deleteTriggerId);
|
|
8716
8875
|
// Create delete trigger for basic JSON
|
|
8717
8876
|
await tx.execute(/* sql */ `
|
|
8718
|
-
CREATE
|
|
8877
|
+
CREATE ${tableTriggerTypeClause} TRIGGER ${deleteTriggerId} AFTER DELETE ON ${internalSource} ${whenClauses[exports.DiffTriggerOperation.DELETE]} BEGIN
|
|
8719
8878
|
INSERT INTO
|
|
8720
8879
|
${destination} (id, operation, timestamp, value)
|
|
8721
8880
|
VALUES
|
|
@@ -8759,7 +8918,7 @@ class TriggerManagerImpl {
|
|
|
8759
8918
|
// If no array is provided, we use all columns from the source table.
|
|
8760
8919
|
const contextColumns = columns ?? sourceDefinition.columns.map((col) => col.name);
|
|
8761
8920
|
const id = await this.getUUID();
|
|
8762
|
-
const destination = `
|
|
8921
|
+
const destination = `__ps_temp_track_${source}_${id}`;
|
|
8763
8922
|
// register an onChange before the trigger is created
|
|
8764
8923
|
const abortController = new AbortController();
|
|
8765
8924
|
const abortOnChange = () => abortController.abort();
|
|
@@ -8914,6 +9073,7 @@ class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
8914
9073
|
* Allows creating SQLite triggers which can be used to track various operations on SQLite tables.
|
|
8915
9074
|
*/
|
|
8916
9075
|
triggers;
|
|
9076
|
+
triggersImpl;
|
|
8917
9077
|
logger;
|
|
8918
9078
|
constructor(options) {
|
|
8919
9079
|
super();
|
|
@@ -8977,9 +9137,10 @@ class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
8977
9137
|
logger: this.logger
|
|
8978
9138
|
});
|
|
8979
9139
|
this._isReadyPromise = this.initialize();
|
|
8980
|
-
this.triggers = new TriggerManagerImpl({
|
|
9140
|
+
this.triggers = this.triggersImpl = new TriggerManagerImpl({
|
|
8981
9141
|
db: this,
|
|
8982
|
-
schema: this.schema
|
|
9142
|
+
schema: this.schema,
|
|
9143
|
+
...this.generateTriggerManagerConfig()
|
|
8983
9144
|
});
|
|
8984
9145
|
}
|
|
8985
9146
|
/**
|
|
@@ -9005,6 +9166,15 @@ class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
9005
9166
|
get connecting() {
|
|
9006
9167
|
return this.currentStatus?.connecting || false;
|
|
9007
9168
|
}
|
|
9169
|
+
/**
|
|
9170
|
+
* Generates a base configuration for {@link TriggerManagerImpl}.
|
|
9171
|
+
* Implementations should override this if necessary.
|
|
9172
|
+
*/
|
|
9173
|
+
generateTriggerManagerConfig() {
|
|
9174
|
+
return {
|
|
9175
|
+
claimManager: MEMORY_TRIGGER_CLAIM_MANAGER
|
|
9176
|
+
};
|
|
9177
|
+
}
|
|
9008
9178
|
/**
|
|
9009
9179
|
* @returns A promise which will resolve once initialization is completed.
|
|
9010
9180
|
*/
|
|
@@ -9065,14 +9235,15 @@ class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
9065
9235
|
async initialize() {
|
|
9066
9236
|
await this._initialize();
|
|
9067
9237
|
await this.bucketStorageAdapter.init();
|
|
9068
|
-
await this.
|
|
9238
|
+
await this.loadVersion();
|
|
9069
9239
|
await this.updateSchema(this.options.schema);
|
|
9070
9240
|
await this.resolveOfflineSyncStatus();
|
|
9071
9241
|
await this.database.execute('PRAGMA RECURSIVE_TRIGGERS=TRUE');
|
|
9242
|
+
await this.triggersImpl.cleanupResources();
|
|
9072
9243
|
this.ready = true;
|
|
9073
9244
|
this.iterateListeners((cb) => cb.initialized?.());
|
|
9074
9245
|
}
|
|
9075
|
-
async
|
|
9246
|
+
async loadVersion() {
|
|
9076
9247
|
try {
|
|
9077
9248
|
const { version } = await this.database.get('SELECT powersync_rs_version() as version');
|
|
9078
9249
|
this.sdkVersion = version;
|
|
@@ -9188,7 +9359,6 @@ class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
9188
9359
|
await this.disconnect();
|
|
9189
9360
|
await this.waitForReady();
|
|
9190
9361
|
const { clearLocal } = options;
|
|
9191
|
-
// TODO DB name, verify this is necessary with extension
|
|
9192
9362
|
await this.database.writeTransaction(async (tx) => {
|
|
9193
9363
|
await tx.execute('SELECT powersync_clear(?)', [clearLocal ? 1 : 0]);
|
|
9194
9364
|
});
|
|
@@ -9220,6 +9390,7 @@ class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
9220
9390
|
if (this.closed) {
|
|
9221
9391
|
return;
|
|
9222
9392
|
}
|
|
9393
|
+
this.triggersImpl.dispose();
|
|
9223
9394
|
await this.iterateAsyncListeners(async (cb) => cb.closing?.());
|
|
9224
9395
|
const { disconnect } = options;
|
|
9225
9396
|
if (disconnect) {
|
|
@@ -10831,9 +11002,11 @@ exports.InvalidSQLCharacters = InvalidSQLCharacters;
|
|
|
10831
11002
|
exports.LogLevel = LogLevel;
|
|
10832
11003
|
exports.MAX_AMOUNT_OF_COLUMNS = MAX_AMOUNT_OF_COLUMNS;
|
|
10833
11004
|
exports.MAX_OP_ID = MAX_OP_ID;
|
|
11005
|
+
exports.MEMORY_TRIGGER_CLAIM_MANAGER = MEMORY_TRIGGER_CLAIM_MANAGER;
|
|
10834
11006
|
exports.OnChangeQueryProcessor = OnChangeQueryProcessor;
|
|
10835
11007
|
exports.OpType = OpType;
|
|
10836
11008
|
exports.OplogEntry = OplogEntry;
|
|
11009
|
+
exports.RawTable = RawTable;
|
|
10837
11010
|
exports.Schema = Schema;
|
|
10838
11011
|
exports.SqliteBucketStorage = SqliteBucketStorage;
|
|
10839
11012
|
exports.SyncDataBatch = SyncDataBatch;
|
|
@@ -10842,6 +11015,7 @@ exports.SyncProgress = SyncProgress;
|
|
|
10842
11015
|
exports.SyncStatus = SyncStatus;
|
|
10843
11016
|
exports.Table = Table;
|
|
10844
11017
|
exports.TableV2 = TableV2;
|
|
11018
|
+
exports.TriggerManagerImpl = TriggerManagerImpl;
|
|
10845
11019
|
exports.UploadQueueStats = UploadQueueStats;
|
|
10846
11020
|
exports.column = column;
|
|
10847
11021
|
exports.compilableQueryWatch = compilableQueryWatch;
|