@powersync/common 1.50.0 → 1.51.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.
@@ -783,19 +783,69 @@ class SyncingService {
783
783
  }
784
784
 
785
785
  /**
786
- * An asynchronous mutex implementation.
786
+ * A simple fixed-capacity queue implementation.
787
+ *
788
+ * Unlike a naive queue implemented by `array.push()` and `array.shift()`, this avoids moving array elements around
789
+ * and is `O(1)` for {@link addLast} and {@link removeFirst}.
790
+ */
791
+ class Queue {
792
+ table;
793
+ // Index of the first element in the table.
794
+ head;
795
+ // Amount of items currently in the queue.
796
+ _length;
797
+ constructor(initialItems) {
798
+ this.table = [...initialItems];
799
+ this.head = 0;
800
+ this._length = this.table.length;
801
+ }
802
+ get isEmpty() {
803
+ return this.length == 0;
804
+ }
805
+ get length() {
806
+ return this._length;
807
+ }
808
+ removeFirst() {
809
+ if (this.isEmpty) {
810
+ throw new Error('Queue is empty');
811
+ }
812
+ const result = this.table[this.head];
813
+ this._length--;
814
+ this.table[this.head] = undefined;
815
+ this.head = (this.head + 1) % this.table.length;
816
+ return result;
817
+ }
818
+ addLast(element) {
819
+ if (this.length == this.table.length) {
820
+ throw new Error('Queue is full');
821
+ }
822
+ this.table[(this.head + this._length) % this.table.length] = element;
823
+ this._length++;
824
+ }
825
+ }
826
+
827
+ /**
828
+ * An asynchronous semaphore implementation with associated items per lease.
787
829
  *
788
830
  * @internal This class is meant to be used in PowerSync SDKs only, and is not part of the public API.
789
831
  */
790
- class Mutex {
791
- inCriticalSection = false;
832
+ class Semaphore {
833
+ // Available items that are not currently assigned to a waiter.
834
+ available;
835
+ size;
792
836
  // Linked list of waiters. We don't expect the wait list to become particularly large, and this allows removing
793
837
  // aborted waiters from the middle of the list efficiently.
794
838
  firstWaiter;
795
839
  lastWaiter;
796
- addWaiter(onAcquire) {
840
+ constructor(elements) {
841
+ this.available = new Queue(elements);
842
+ this.size = this.available.length;
843
+ }
844
+ addWaiter(requestedItems, onAcquire) {
797
845
  const node = {
798
846
  isActive: true,
847
+ acquiredItems: [],
848
+ remainingItems: requestedItems,
799
849
  onAcquire,
800
850
  prev: this.lastWaiter
801
851
  };
@@ -821,52 +871,92 @@ class Mutex {
821
871
  if (waiter == this.lastWaiter)
822
872
  this.lastWaiter = prev;
823
873
  }
824
- acquire(abort) {
874
+ requestPermits(amount, abort) {
875
+ if (amount <= 0 || amount > this.size) {
876
+ throw new Error(`Invalid amount of items requested (${amount}), must be between 1 and ${this.size}`);
877
+ }
825
878
  return new Promise((resolve, reject) => {
826
879
  function rejectAborted() {
827
- reject(abort?.reason ?? new Error('Mutex acquire aborted'));
880
+ reject(abort?.reason ?? new Error('Semaphore acquire aborted'));
828
881
  }
829
882
  if (abort?.aborted) {
830
883
  return rejectAborted();
831
884
  }
832
- let holdsMutex = false;
885
+ let waiter;
833
886
  const markCompleted = () => {
834
- if (!holdsMutex)
835
- return;
836
- holdsMutex = false;
837
- const waiter = this.firstWaiter;
838
- if (waiter) {
839
- this.deactivateWaiter(waiter);
840
- // Still in critical section, but owned by next waiter now.
841
- waiter.onAcquire();
887
+ const items = waiter.acquiredItems;
888
+ waiter.acquiredItems = []; // Avoid releasing items twice.
889
+ for (const element of items) {
890
+ // Give to next waiter, if possible.
891
+ const nextWaiter = this.firstWaiter;
892
+ if (nextWaiter) {
893
+ nextWaiter.acquiredItems.push(element);
894
+ nextWaiter.remainingItems--;
895
+ if (nextWaiter.remainingItems == 0) {
896
+ nextWaiter.onAcquire();
897
+ }
898
+ }
899
+ else {
900
+ // No pending waiter, return lease into pool.
901
+ this.available.addLast(element);
902
+ }
842
903
  }
843
- else {
844
- this.inCriticalSection = false;
904
+ };
905
+ const onAbort = () => {
906
+ abort?.removeEventListener('abort', onAbort);
907
+ if (waiter.isActive) {
908
+ this.deactivateWaiter(waiter);
909
+ rejectAborted();
845
910
  }
846
911
  };
847
- if (!this.inCriticalSection) {
848
- this.inCriticalSection = true;
849
- holdsMutex = true;
850
- return resolve(markCompleted);
912
+ const resolvePromise = () => {
913
+ this.deactivateWaiter(waiter);
914
+ abort?.removeEventListener('abort', onAbort);
915
+ const items = waiter.acquiredItems;
916
+ resolve({ items, release: markCompleted });
917
+ };
918
+ waiter = this.addWaiter(amount, resolvePromise);
919
+ // If there are items in the pool that haven't been assigned, we can pull them into this waiter. Note that this is
920
+ // only the case if we're the first waiter (otherwise, items would have been assigned to an earlier waiter).
921
+ while (!this.available.isEmpty && waiter.remainingItems > 0) {
922
+ waiter.acquiredItems.push(this.available.removeFirst());
923
+ waiter.remainingItems--;
851
924
  }
852
- else {
853
- let node;
854
- const onAbort = () => {
855
- abort?.removeEventListener('abort', onAbort);
856
- if (node.isActive) {
857
- this.deactivateWaiter(node);
858
- rejectAborted();
859
- }
860
- };
861
- node = this.addWaiter(() => {
862
- abort?.removeEventListener('abort', onAbort);
863
- holdsMutex = true;
864
- resolve(markCompleted);
865
- });
866
- abort?.addEventListener('abort', onAbort);
925
+ if (waiter.remainingItems == 0) {
926
+ return resolvePromise();
867
927
  }
928
+ abort?.addEventListener('abort', onAbort);
868
929
  });
869
930
  }
931
+ /**
932
+ * Requests a single item from the pool.
933
+ *
934
+ * The returned `release` callback must be invoked to return the item into the pool.
935
+ */
936
+ async requestOne(abort) {
937
+ const { items, release } = await this.requestPermits(1, abort);
938
+ return { release, item: items[0] };
939
+ }
940
+ /**
941
+ * Requests access to all items from the pool.
942
+ *
943
+ * The returned `release` callback must be invoked to return items into the pool.
944
+ */
945
+ requestAll(abort) {
946
+ return this.requestPermits(this.size, abort);
947
+ }
948
+ }
949
+ /**
950
+ * An asynchronous mutex implementation.
951
+ *
952
+ * @internal This class is meant to be used in PowerSync SDKs only, and is not part of the public API.
953
+ */
954
+ class Mutex {
955
+ inner = new Semaphore([null]);
956
+ async acquire(abort) {
957
+ const { release } = await this.inner.requestOne(abort);
958
+ return release;
959
+ }
870
960
  async runExclusive(fn, abort) {
871
961
  const returnMutex = await this.acquire(abort);
872
962
  try {
@@ -8139,7 +8229,7 @@ function requireDist () {
8139
8229
 
8140
8230
  var distExports = requireDist();
8141
8231
 
8142
- var version = "1.50.0";
8232
+ var version = "1.51.0";
8143
8233
  var PACKAGE = {
8144
8234
  version: version};
8145
8235
 
@@ -12073,5 +12163,5 @@ const parseQuery = (query, parameters) => {
12073
12163
  return { sqlStatement, parameters: parameters };
12074
12164
  };
12075
12165
 
12076
- export { ATTACHMENT_TABLE, AbortOperation, AbstractPowerSyncDatabase, AbstractPowerSyncDatabaseOpenFactory, AbstractQueryProcessor, AbstractRemote, AbstractStreamingSyncImplementation, ArrayComparator, AttachmentContext, AttachmentQueue, AttachmentService, AttachmentState, AttachmentTable, BaseObserver, Column, ColumnType, ConnectionClosedError, ConnectionManager, ControlledExecutor, CrudBatch, CrudEntry, CrudTransaction, DBAdapterDefaultMixin, DBGetUtilsDefaultMixin, DEFAULT_CRUD_BATCH_LIMIT, DEFAULT_CRUD_UPLOAD_THROTTLE_MS, DEFAULT_INDEX_COLUMN_OPTIONS, DEFAULT_INDEX_OPTIONS, DEFAULT_LOCK_TIMEOUT_MS, DEFAULT_POWERSYNC_CLOSE_OPTIONS, DEFAULT_POWERSYNC_DB_OPTIONS, DEFAULT_PRESSURE_LIMITS, DEFAULT_REMOTE_LOGGER, DEFAULT_REMOTE_OPTIONS, DEFAULT_RETRY_DELAY_MS, DEFAULT_ROW_COMPARATOR, DEFAULT_STREAMING_SYNC_OPTIONS, DEFAULT_STREAM_CONNECTION_OPTIONS, DEFAULT_SYNC_CLIENT_IMPLEMENTATION, DEFAULT_TABLE_OPTIONS, DEFAULT_WATCH_QUERY_OPTIONS, DEFAULT_WATCH_THROTTLE_MS, DataStream, DiffTriggerOperation, DifferentialQueryProcessor, EMPTY_DIFFERENTIAL, EncodingType, FalsyComparator, FetchImplementationProvider, FetchStrategy, GetAllQuery, Index, IndexedColumn, InvalidSQLCharacters, LockType, LogLevel, MAX_AMOUNT_OF_COLUMNS, MAX_OP_ID, MEMORY_TRIGGER_CLAIM_MANAGER, Mutex, OnChangeQueryProcessor, OpType, OpTypeEnum, OplogEntry, PSInternalTable, PowerSyncControlCommand, RowUpdateType, Schema, SqliteBucketStorage, SyncClientImplementation, SyncDataBatch, SyncDataBucket, SyncProgress, SyncStatus, SyncStreamConnectionMethod, SyncingService, Table, TableV2, TriggerManagerImpl, UpdateType, UploadQueueStats, WatchedQueryListenerEvent, attachmentFromSql, column, compilableQueryWatch, createBaseLogger, createLogger, extractTableUpdates, isBatchedUpdateNotification, isContinueCheckpointRequest, isDBAdapter, isPowerSyncDatabaseOptionsWithSettings, isSQLOpenFactory, isSQLOpenOptions, isStreamingKeepalive, isStreamingSyncCheckpoint, isStreamingSyncCheckpointComplete, isStreamingSyncCheckpointDiff, isStreamingSyncCheckpointPartiallyComplete, isStreamingSyncData, isSyncNewCheckpointRequest, parseQuery, runOnSchemaChange, sanitizeSQL, sanitizeUUID, timeoutSignal };
12166
+ export { ATTACHMENT_TABLE, AbortOperation, AbstractPowerSyncDatabase, AbstractPowerSyncDatabaseOpenFactory, AbstractQueryProcessor, AbstractRemote, AbstractStreamingSyncImplementation, ArrayComparator, AttachmentContext, AttachmentQueue, AttachmentService, AttachmentState, AttachmentTable, BaseObserver, Column, ColumnType, ConnectionClosedError, ConnectionManager, ControlledExecutor, CrudBatch, CrudEntry, CrudTransaction, DBAdapterDefaultMixin, DBGetUtilsDefaultMixin, DEFAULT_CRUD_BATCH_LIMIT, DEFAULT_CRUD_UPLOAD_THROTTLE_MS, DEFAULT_INDEX_COLUMN_OPTIONS, DEFAULT_INDEX_OPTIONS, DEFAULT_LOCK_TIMEOUT_MS, DEFAULT_POWERSYNC_CLOSE_OPTIONS, DEFAULT_POWERSYNC_DB_OPTIONS, DEFAULT_PRESSURE_LIMITS, DEFAULT_REMOTE_LOGGER, DEFAULT_REMOTE_OPTIONS, DEFAULT_RETRY_DELAY_MS, DEFAULT_ROW_COMPARATOR, DEFAULT_STREAMING_SYNC_OPTIONS, DEFAULT_STREAM_CONNECTION_OPTIONS, DEFAULT_SYNC_CLIENT_IMPLEMENTATION, DEFAULT_TABLE_OPTIONS, DEFAULT_WATCH_QUERY_OPTIONS, DEFAULT_WATCH_THROTTLE_MS, DataStream, DiffTriggerOperation, DifferentialQueryProcessor, EMPTY_DIFFERENTIAL, EncodingType, FalsyComparator, FetchImplementationProvider, FetchStrategy, GetAllQuery, Index, IndexedColumn, InvalidSQLCharacters, LockType, LogLevel, MAX_AMOUNT_OF_COLUMNS, MAX_OP_ID, MEMORY_TRIGGER_CLAIM_MANAGER, Mutex, OnChangeQueryProcessor, OpType, OpTypeEnum, OplogEntry, PSInternalTable, PowerSyncControlCommand, RowUpdateType, Schema, Semaphore, SqliteBucketStorage, SyncClientImplementation, SyncDataBatch, SyncDataBucket, SyncProgress, SyncStatus, SyncStreamConnectionMethod, SyncingService, Table, TableV2, TriggerManagerImpl, UpdateType, UploadQueueStats, WatchedQueryListenerEvent, attachmentFromSql, column, compilableQueryWatch, createBaseLogger, createLogger, extractTableUpdates, isBatchedUpdateNotification, isContinueCheckpointRequest, isDBAdapter, isPowerSyncDatabaseOptionsWithSettings, isSQLOpenFactory, isSQLOpenOptions, isStreamingKeepalive, isStreamingSyncCheckpoint, isStreamingSyncCheckpointComplete, isStreamingSyncCheckpointDiff, isStreamingSyncCheckpointPartiallyComplete, isStreamingSyncData, isSyncNewCheckpointRequest, parseQuery, runOnSchemaChange, sanitizeSQL, sanitizeUUID, timeoutSignal };
12077
12167
  //# sourceMappingURL=bundle.node.mjs.map