@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.
package/dist/index.d.cts CHANGED
@@ -3037,16 +3037,45 @@ declare class TriggerManagerImpl implements TriggerManager {
3037
3037
 
3038
3038
  type UnlockFn = () => void;
3039
3039
  /**
3040
- * An asynchronous mutex implementation.
3040
+ * An asynchronous semaphore implementation with associated items per lease.
3041
3041
  *
3042
3042
  * @internal This class is meant to be used in PowerSync SDKs only, and is not part of the public API.
3043
3043
  */
3044
- declare class Mutex {
3045
- private inCriticalSection;
3044
+ declare class Semaphore<T> {
3045
+ private readonly available;
3046
+ readonly size: number;
3046
3047
  private firstWaiter?;
3047
3048
  private lastWaiter?;
3049
+ constructor(elements: Iterable<T>);
3048
3050
  private addWaiter;
3049
3051
  private deactivateWaiter;
3052
+ private requestPermits;
3053
+ /**
3054
+ * Requests a single item from the pool.
3055
+ *
3056
+ * The returned `release` callback must be invoked to return the item into the pool.
3057
+ */
3058
+ requestOne(abort?: AbortSignal): Promise<{
3059
+ item: T;
3060
+ release: UnlockFn;
3061
+ }>;
3062
+ /**
3063
+ * Requests access to all items from the pool.
3064
+ *
3065
+ * The returned `release` callback must be invoked to return items into the pool.
3066
+ */
3067
+ requestAll(abort?: AbortSignal): Promise<{
3068
+ items: T[];
3069
+ release: UnlockFn;
3070
+ }>;
3071
+ }
3072
+ /**
3073
+ * An asynchronous mutex implementation.
3074
+ *
3075
+ * @internal This class is meant to be used in PowerSync SDKs only, and is not part of the public API.
3076
+ */
3077
+ declare class Mutex {
3078
+ private inner;
3050
3079
  acquire(abort?: AbortSignal): Promise<UnlockFn>;
3051
3080
  runExclusive<T>(fn: () => PromiseLike<T> | T, abort?: AbortSignal): Promise<T>;
3052
3081
  }
@@ -4395,5 +4424,5 @@ interface ParsedQuery {
4395
4424
  }
4396
4425
  declare const parseQuery: <T>(query: string | CompilableQuery<T>, parameters: any[]) => ParsedQuery;
4397
4426
 
4398
- 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 };
4427
+ 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 };
4399
4428
  export type { AbstractQueryProcessorOptions, AbstractRemoteOptions, AbstractStreamingSyncImplementationOptions, AdditionalConnectionOptions, ArrayComparatorOptions, ArrayQueryDefinition, AttachmentData, AttachmentErrorHandler, AttachmentRecord, AttachmentTableOptions, BSONImplementation, BaseColumnType, BaseConnectionOptions, BaseListener, BaseObserverInterface, BasePowerSyncDatabaseOptions, BaseTriggerDiffRecord, BatchedUpdateNotification, BucketChecksum, BucketDescription, BucketOperationProgress, BucketRequest, BucketState, BucketStorageAdapter, BucketStorageListener, Checkpoint, ChecksumCache, ColumnOptions, ColumnsType, CompilableQuery, CompilableQueryWatchHandler, CompiledQuery, ConnectionManagerListener, ConnectionManagerOptions, ConnectionManagerSyncImplementationResult, ConnectionPool, ContinueCheckpointRequest, ControlledExecutorOptions, CreateDiffTriggerOptions, CreateLoggerOptions, CreateSyncImplementationOptions, CrudRequest, CrudResponse, CrudUploadNotification, DBAdapter, DBAdapterListener, DBGetUtils, DBLockOptions, DataStreamCallback, DataStreamListener, DataStreamOptions, DifferentialQueryProcessorOptions, DifferentialWatchedQuery, DifferentialWatchedQueryComparator, DifferentialWatchedQueryListener, DifferentialWatchedQueryOptions, DifferentialWatchedQuerySettings, DisconnectAndClearOptions, Disposable, ExtractColumnValueType, ExtractedTriggerDiffRecord, FetchImplementation, GetAllQueryOptions, IndexColumnOptions, IndexOptions, IndexShorthand, InternalConnectionOptions, InternalSubscriptionAdapter, LinkQueryOptions, LocalStorageAdapter, LockContext, LockOptions, MutableWatchedQueryState, OnChangeQueryProcessorOptions, OpId, OpTypeJSON, OplogEntryJSON, ParsedQuery, PendingStatement, PendingStatementParameter, PowerSyncBackendConnector, PowerSyncCloseOptions, PowerSyncConnectionOptions, PowerSyncCredentials, PowerSyncDBListener, PowerSyncDatabaseOptions, PowerSyncDatabaseOptionsWithDBAdapter, PowerSyncDatabaseOptionsWithOpenFactory, PowerSyncDatabaseOptionsWithSettings, PowerSyncOpenFactoryOptions, ProgressWithOperations, Query, QueryParam, QueryResult, RawTableType, RemoteConnector, RemoteStorageAdapter, RequiredAdditionalConnectionOptions, RequiredPowerSyncConnectionOptions, RowType, SQLOnChangeOptions, SQLOpenFactory, SQLOpenOptions, SQLWatchOptions, SavedProgress, SchemaTableType, SocketSyncStreamOptions, SqlExecutor, StandardWatchedQuery, StandardWatchedQueryOptions, StreamingSyncCheckpoint, StreamingSyncCheckpointComplete, StreamingSyncCheckpointDiff, StreamingSyncCheckpointPartiallyComplete, StreamingSyncDataJSON, StreamingSyncImplementation, StreamingSyncImplementationListener, StreamingSyncKeepalive, StreamingSyncLine, StreamingSyncLineOrCrudUploadComplete, StreamingSyncRequest, StreamingSyncRequestParameterType, SubscribedStream, SyncDataBucketJSON, SyncDataFlowStatus, SyncLocalDatabaseResult, SyncNewCheckpointRequest, SyncPriorityStatus, SyncRequest, SyncResponse, SyncStatusOptions, SyncStream, SyncStreamDescription, SyncStreamOptions, SyncStreamStatus, SyncStreamSubscribeOptions, SyncStreamSubscription, SyncSubscriptionDescription, TableOptions, TableOrRawTableOptions, TableUpdateOperation, TableV2Options, TrackDiffOptions, TrackPreviousOptions, Transaction, TriggerClaimManager, TriggerCreationHooks, TriggerDiffDeleteRecord, TriggerDiffHandlerContext, TriggerDiffInsertRecord, TriggerDiffRecord, TriggerDiffUpdateRecord, TriggerManager, TriggerManagerConfig, TriggerRemoveCallback, TriggerRemoveCallbackOptions, UnlockFn, UpdateNotification, WatchCompatibleQuery, WatchExecuteOptions, WatchHandler, WatchOnChangeEvent, WatchOnChangeHandler, WatchedAttachmentItem, WatchedQuery, WatchedQueryComparator, WatchedQueryDifferential, WatchedQueryListener, WatchedQueryOptions, WatchedQueryRowDifferential, WatchedQuerySettings, WatchedQueryState, WithDiffOptions };
@@ -1,15 +1,44 @@
1
1
  export type UnlockFn = () => void;
2
2
  /**
3
- * An asynchronous mutex implementation.
3
+ * An asynchronous semaphore implementation with associated items per lease.
4
4
  *
5
5
  * @internal This class is meant to be used in PowerSync SDKs only, and is not part of the public API.
6
6
  */
7
- export declare class Mutex {
8
- private inCriticalSection;
7
+ export declare class Semaphore<T> {
8
+ private readonly available;
9
+ readonly size: number;
9
10
  private firstWaiter?;
10
11
  private lastWaiter?;
12
+ constructor(elements: Iterable<T>);
11
13
  private addWaiter;
12
14
  private deactivateWaiter;
15
+ private requestPermits;
16
+ /**
17
+ * Requests a single item from the pool.
18
+ *
19
+ * The returned `release` callback must be invoked to return the item into the pool.
20
+ */
21
+ requestOne(abort?: AbortSignal): Promise<{
22
+ item: T;
23
+ release: UnlockFn;
24
+ }>;
25
+ /**
26
+ * Requests access to all items from the pool.
27
+ *
28
+ * The returned `release` callback must be invoked to return items into the pool.
29
+ */
30
+ requestAll(abort?: AbortSignal): Promise<{
31
+ items: T[];
32
+ release: UnlockFn;
33
+ }>;
34
+ }
35
+ /**
36
+ * An asynchronous mutex implementation.
37
+ *
38
+ * @internal This class is meant to be used in PowerSync SDKs only, and is not part of the public API.
39
+ */
40
+ export declare class Mutex {
41
+ private inner;
13
42
  acquire(abort?: AbortSignal): Promise<UnlockFn>;
14
43
  runExclusive<T>(fn: () => PromiseLike<T> | T, abort?: AbortSignal): Promise<T>;
15
44
  }
@@ -1,17 +1,26 @@
1
+ import { Queue } from './queue.js';
1
2
  /**
2
- * An asynchronous mutex implementation.
3
+ * An asynchronous semaphore implementation with associated items per lease.
3
4
  *
4
5
  * @internal This class is meant to be used in PowerSync SDKs only, and is not part of the public API.
5
6
  */
6
- export class Mutex {
7
- inCriticalSection = false;
7
+ export class Semaphore {
8
+ // Available items that are not currently assigned to a waiter.
9
+ available;
10
+ size;
8
11
  // Linked list of waiters. We don't expect the wait list to become particularly large, and this allows removing
9
12
  // aborted waiters from the middle of the list efficiently.
10
13
  firstWaiter;
11
14
  lastWaiter;
12
- addWaiter(onAcquire) {
15
+ constructor(elements) {
16
+ this.available = new Queue(elements);
17
+ this.size = this.available.length;
18
+ }
19
+ addWaiter(requestedItems, onAcquire) {
13
20
  const node = {
14
21
  isActive: true,
22
+ acquiredItems: [],
23
+ remainingItems: requestedItems,
15
24
  onAcquire,
16
25
  prev: this.lastWaiter
17
26
  };
@@ -37,52 +46,92 @@ export class Mutex {
37
46
  if (waiter == this.lastWaiter)
38
47
  this.lastWaiter = prev;
39
48
  }
40
- acquire(abort) {
49
+ requestPermits(amount, abort) {
50
+ if (amount <= 0 || amount > this.size) {
51
+ throw new Error(`Invalid amount of items requested (${amount}), must be between 1 and ${this.size}`);
52
+ }
41
53
  return new Promise((resolve, reject) => {
42
54
  function rejectAborted() {
43
- reject(abort?.reason ?? new Error('Mutex acquire aborted'));
55
+ reject(abort?.reason ?? new Error('Semaphore acquire aborted'));
44
56
  }
45
57
  if (abort?.aborted) {
46
58
  return rejectAborted();
47
59
  }
48
- let holdsMutex = false;
60
+ let waiter;
49
61
  const markCompleted = () => {
50
- if (!holdsMutex)
51
- return;
52
- holdsMutex = false;
53
- const waiter = this.firstWaiter;
54
- if (waiter) {
55
- this.deactivateWaiter(waiter);
56
- // Still in critical section, but owned by next waiter now.
57
- waiter.onAcquire();
62
+ const items = waiter.acquiredItems;
63
+ waiter.acquiredItems = []; // Avoid releasing items twice.
64
+ for (const element of items) {
65
+ // Give to next waiter, if possible.
66
+ const nextWaiter = this.firstWaiter;
67
+ if (nextWaiter) {
68
+ nextWaiter.acquiredItems.push(element);
69
+ nextWaiter.remainingItems--;
70
+ if (nextWaiter.remainingItems == 0) {
71
+ nextWaiter.onAcquire();
72
+ }
73
+ }
74
+ else {
75
+ // No pending waiter, return lease into pool.
76
+ this.available.addLast(element);
77
+ }
58
78
  }
59
- else {
60
- this.inCriticalSection = false;
79
+ };
80
+ const onAbort = () => {
81
+ abort?.removeEventListener('abort', onAbort);
82
+ if (waiter.isActive) {
83
+ this.deactivateWaiter(waiter);
84
+ rejectAborted();
61
85
  }
62
86
  };
63
- if (!this.inCriticalSection) {
64
- this.inCriticalSection = true;
65
- holdsMutex = true;
66
- return resolve(markCompleted);
87
+ const resolvePromise = () => {
88
+ this.deactivateWaiter(waiter);
89
+ abort?.removeEventListener('abort', onAbort);
90
+ const items = waiter.acquiredItems;
91
+ resolve({ items, release: markCompleted });
92
+ };
93
+ waiter = this.addWaiter(amount, resolvePromise);
94
+ // If there are items in the pool that haven't been assigned, we can pull them into this waiter. Note that this is
95
+ // only the case if we're the first waiter (otherwise, items would have been assigned to an earlier waiter).
96
+ while (!this.available.isEmpty && waiter.remainingItems > 0) {
97
+ waiter.acquiredItems.push(this.available.removeFirst());
98
+ waiter.remainingItems--;
67
99
  }
68
- else {
69
- let node;
70
- const onAbort = () => {
71
- abort?.removeEventListener('abort', onAbort);
72
- if (node.isActive) {
73
- this.deactivateWaiter(node);
74
- rejectAborted();
75
- }
76
- };
77
- node = this.addWaiter(() => {
78
- abort?.removeEventListener('abort', onAbort);
79
- holdsMutex = true;
80
- resolve(markCompleted);
81
- });
82
- abort?.addEventListener('abort', onAbort);
100
+ if (waiter.remainingItems == 0) {
101
+ return resolvePromise();
83
102
  }
103
+ abort?.addEventListener('abort', onAbort);
84
104
  });
85
105
  }
106
+ /**
107
+ * Requests a single item from the pool.
108
+ *
109
+ * The returned `release` callback must be invoked to return the item into the pool.
110
+ */
111
+ async requestOne(abort) {
112
+ const { items, release } = await this.requestPermits(1, abort);
113
+ return { release, item: items[0] };
114
+ }
115
+ /**
116
+ * Requests access to all items from the pool.
117
+ *
118
+ * The returned `release` callback must be invoked to return items into the pool.
119
+ */
120
+ requestAll(abort) {
121
+ return this.requestPermits(this.size, abort);
122
+ }
123
+ }
124
+ /**
125
+ * An asynchronous mutex implementation.
126
+ *
127
+ * @internal This class is meant to be used in PowerSync SDKs only, and is not part of the public API.
128
+ */
129
+ export class Mutex {
130
+ inner = new Semaphore([null]);
131
+ async acquire(abort) {
132
+ const { release } = await this.inner.requestOne(abort);
133
+ return release;
134
+ }
86
135
  async runExclusive(fn, abort) {
87
136
  const returnMutex = await this.acquire(abort);
88
137
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"mutex.js","sourceRoot":"","sources":["../../src/utils/mutex.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mutex.js","sourceRoot":"","sources":["../../src/utils/mutex.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * A simple fixed-capacity queue implementation.
3
+ *
4
+ * Unlike a naive queue implemented by `array.push()` and `array.shift()`, this avoids moving array elements around
5
+ * and is `O(1)` for {@link addLast} and {@link removeFirst}.
6
+ */
7
+ export declare class Queue<T> {
8
+ private table;
9
+ private head;
10
+ private _length;
11
+ constructor(initialItems: Iterable<T>);
12
+ get isEmpty(): boolean;
13
+ get length(): number;
14
+ removeFirst(): T;
15
+ addLast(element: T): void;
16
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * A simple fixed-capacity queue implementation.
3
+ *
4
+ * Unlike a naive queue implemented by `array.push()` and `array.shift()`, this avoids moving array elements around
5
+ * and is `O(1)` for {@link addLast} and {@link removeFirst}.
6
+ */
7
+ export class Queue {
8
+ table;
9
+ // Index of the first element in the table.
10
+ head;
11
+ // Amount of items currently in the queue.
12
+ _length;
13
+ constructor(initialItems) {
14
+ this.table = [...initialItems];
15
+ this.head = 0;
16
+ this._length = this.table.length;
17
+ }
18
+ get isEmpty() {
19
+ return this.length == 0;
20
+ }
21
+ get length() {
22
+ return this._length;
23
+ }
24
+ removeFirst() {
25
+ if (this.isEmpty) {
26
+ throw new Error('Queue is empty');
27
+ }
28
+ const result = this.table[this.head];
29
+ this._length--;
30
+ this.table[this.head] = undefined;
31
+ this.head = (this.head + 1) % this.table.length;
32
+ return result;
33
+ }
34
+ addLast(element) {
35
+ if (this.length == this.table.length) {
36
+ throw new Error('Queue is full');
37
+ }
38
+ this.table[(this.head + this._length) % this.table.length] = element;
39
+ this._length++;
40
+ }
41
+ }
42
+ //# sourceMappingURL=queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.js","sourceRoot":"","sources":["../../src/utils/queue.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,OAAO,KAAK;IACR,KAAK,CAAoB;IACjC,2CAA2C;IACnC,IAAI,CAAS;IACrB,0CAA0C;IAClC,OAAO,CAAS;IAExB,YAAY,YAAyB;QACnC,IAAI,CAAC,KAAK,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;QACd,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;IACnC,CAAC;IAED,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,WAAW;QACT,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC;QACpC,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAM,CAAC;QAC1C,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC;QAClC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;QAChD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,CAAC,OAAU;QAChB,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC;QACnC,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC;QACrE,IAAI,CAAC,OAAO,EAAE,CAAC;IACjB,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powersync/common",
3
- "version": "1.50.0",
3
+ "version": "1.51.0",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"
@@ -1,21 +1,32 @@
1
+ import { Queue } from './queue.js';
2
+
1
3
  export type UnlockFn = () => void;
2
4
 
3
5
  /**
4
- * An asynchronous mutex implementation.
6
+ * An asynchronous semaphore implementation with associated items per lease.
5
7
  *
6
8
  * @internal This class is meant to be used in PowerSync SDKs only, and is not part of the public API.
7
9
  */
8
- export class Mutex {
9
- private inCriticalSection = false;
10
+ export class Semaphore<T> {
11
+ // Available items that are not currently assigned to a waiter.
12
+ private readonly available: Queue<T>;
10
13
 
14
+ readonly size: number;
11
15
  // Linked list of waiters. We don't expect the wait list to become particularly large, and this allows removing
12
16
  // aborted waiters from the middle of the list efficiently.
13
- private firstWaiter?: MutexWaitNode;
14
- private lastWaiter?: MutexWaitNode;
17
+ private firstWaiter?: SemaphoreWaitNode<T>;
18
+ private lastWaiter?: SemaphoreWaitNode<T>;
15
19
 
16
- private addWaiter(onAcquire: () => void): MutexWaitNode {
17
- const node: MutexWaitNode = {
20
+ constructor(elements: Iterable<T>) {
21
+ this.available = new Queue(elements);
22
+ this.size = this.available.length;
23
+ }
24
+
25
+ private addWaiter(requestedItems: number, onAcquire: () => void): SemaphoreWaitNode<T> {
26
+ const node: SemaphoreWaitNode<T> = {
18
27
  isActive: true,
28
+ acquiredItems: [],
29
+ remainingItems: requestedItems,
19
30
  onAcquire,
20
31
  prev: this.lastWaiter
21
32
  };
@@ -30,7 +41,7 @@ export class Mutex {
30
41
  return node;
31
42
  }
32
43
 
33
- private deactivateWaiter(waiter: MutexWaitNode) {
44
+ private deactivateWaiter(waiter: SemaphoreWaitNode<T>) {
34
45
  const { prev, next } = waiter;
35
46
  waiter.isActive = false;
36
47
 
@@ -40,77 +51,129 @@ export class Mutex {
40
51
  if (waiter == this.lastWaiter) this.lastWaiter = prev;
41
52
  }
42
53
 
43
- acquire(abort?: AbortSignal): Promise<UnlockFn> {
54
+ private requestPermits(amount: number, abort?: AbortSignal): Promise<{ items: T[]; release: UnlockFn }> {
55
+ if (amount <= 0 || amount > this.size) {
56
+ throw new Error(`Invalid amount of items requested (${amount}), must be between 1 and ${this.size}`);
57
+ }
58
+
44
59
  return new Promise((resolve, reject) => {
45
60
  function rejectAborted() {
46
- reject(abort?.reason ?? new Error('Mutex acquire aborted'));
61
+ reject(abort?.reason ?? new Error('Semaphore acquire aborted'));
47
62
  }
48
63
  if (abort?.aborted) {
49
64
  return rejectAborted();
50
65
  }
51
66
 
52
- let holdsMutex = false;
67
+ let waiter: SemaphoreWaitNode<T>;
53
68
 
54
69
  const markCompleted = () => {
55
- if (!holdsMutex) return;
56
- holdsMutex = false;
70
+ const items = waiter.acquiredItems;
71
+ waiter.acquiredItems = []; // Avoid releasing items twice.
72
+
73
+ for (const element of items) {
74
+ // Give to next waiter, if possible.
75
+ const nextWaiter = this.firstWaiter;
76
+ if (nextWaiter) {
77
+ nextWaiter.acquiredItems.push(element);
78
+ nextWaiter.remainingItems--;
79
+ if (nextWaiter.remainingItems == 0) {
80
+ nextWaiter.onAcquire();
81
+ }
82
+ } else {
83
+ // No pending waiter, return lease into pool.
84
+ this.available.addLast(element);
85
+ }
86
+ }
87
+ };
88
+
89
+ const onAbort = () => {
90
+ abort?.removeEventListener('abort', onAbort);
57
91
 
58
- const waiter = this.firstWaiter;
59
- if (waiter) {
92
+ if (waiter.isActive) {
60
93
  this.deactivateWaiter(waiter);
61
- // Still in critical section, but owned by next waiter now.
62
- waiter.onAcquire();
63
- } else {
64
- this.inCriticalSection = false;
94
+ rejectAborted();
65
95
  }
66
96
  };
67
97
 
68
- if (!this.inCriticalSection) {
69
- this.inCriticalSection = true;
70
- holdsMutex = true;
71
- return resolve(markCompleted);
72
- } else {
73
- let node: MutexWaitNode;
98
+ const resolvePromise = () => {
99
+ this.deactivateWaiter(waiter);
100
+ abort?.removeEventListener('abort', onAbort);
74
101
 
75
- const onAbort = () => {
76
- abort?.removeEventListener('abort', onAbort);
102
+ const items = waiter.acquiredItems;
103
+ resolve({ items, release: markCompleted });
104
+ };
77
105
 
78
- if (node.isActive) {
79
- this.deactivateWaiter(node);
80
- rejectAborted();
81
- }
82
- };
106
+ waiter = this.addWaiter(amount, resolvePromise);
83
107
 
84
- node = this.addWaiter(() => {
85
- abort?.removeEventListener('abort', onAbort);
86
- holdsMutex = true;
87
- resolve(markCompleted);
88
- });
108
+ // If there are items in the pool that haven't been assigned, we can pull them into this waiter. Note that this is
109
+ // only the case if we're the first waiter (otherwise, items would have been assigned to an earlier waiter).
110
+ while (!this.available.isEmpty && waiter.remainingItems > 0) {
111
+ waiter.acquiredItems.push(this.available.removeFirst());
112
+ waiter.remainingItems--;
113
+ }
89
114
 
90
- abort?.addEventListener('abort', onAbort);
115
+ if (waiter.remainingItems == 0) {
116
+ return resolvePromise();
91
117
  }
118
+
119
+ abort?.addEventListener('abort', onAbort);
92
120
  });
93
121
  }
94
122
 
95
- async runExclusive<T>(fn: () => PromiseLike<T> | T, abort?: AbortSignal): Promise<T> {
96
- const returnMutex = await this.acquire(abort);
123
+ /**
124
+ * Requests a single item from the pool.
125
+ *
126
+ * The returned `release` callback must be invoked to return the item into the pool.
127
+ */
128
+ async requestOne(abort?: AbortSignal): Promise<{ item: T; release: UnlockFn }> {
129
+ const { items, release } = await this.requestPermits(1, abort);
130
+ return { release, item: items[0] };
131
+ }
97
132
 
98
- try {
99
- return await fn();
100
- } finally {
101
- returnMutex();
102
- }
133
+ /**
134
+ * Requests access to all items from the pool.
135
+ *
136
+ * The returned `release` callback must be invoked to return items into the pool.
137
+ */
138
+ requestAll(abort?: AbortSignal): Promise<{ items: T[]; release: UnlockFn }> {
139
+ return this.requestPermits(this.size, abort);
103
140
  }
104
141
  }
105
142
 
106
- interface MutexWaitNode {
143
+ interface SemaphoreWaitNode<T> {
107
144
  /**
108
145
  * Whether the waiter is currently active (not aborted and not fullfilled).
109
146
  */
110
147
  isActive: boolean;
148
+ acquiredItems: T[];
149
+ remainingItems: number;
111
150
  onAcquire: () => void;
112
- prev?: MutexWaitNode;
113
- next?: MutexWaitNode;
151
+ prev?: SemaphoreWaitNode<T>;
152
+ next?: SemaphoreWaitNode<T>;
153
+ }
154
+
155
+ /**
156
+ * An asynchronous mutex implementation.
157
+ *
158
+ * @internal This class is meant to be used in PowerSync SDKs only, and is not part of the public API.
159
+ */
160
+ export class Mutex {
161
+ private inner = new Semaphore([null]);
162
+
163
+ async acquire(abort?: AbortSignal): Promise<UnlockFn> {
164
+ const { release } = await this.inner.requestOne(abort);
165
+ return release;
166
+ }
167
+
168
+ async runExclusive<T>(fn: () => PromiseLike<T> | T, abort?: AbortSignal): Promise<T> {
169
+ const returnMutex = await this.acquire(abort);
170
+
171
+ try {
172
+ return await fn();
173
+ } finally {
174
+ returnMutex();
175
+ }
176
+ }
114
177
  }
115
178
 
116
179
  /**
@@ -0,0 +1,48 @@
1
+ /**
2
+ * A simple fixed-capacity queue implementation.
3
+ *
4
+ * Unlike a naive queue implemented by `array.push()` and `array.shift()`, this avoids moving array elements around
5
+ * and is `O(1)` for {@link addLast} and {@link removeFirst}.
6
+ */
7
+ export class Queue<T> {
8
+ private table: (T | undefined)[];
9
+ // Index of the first element in the table.
10
+ private head: number;
11
+ // Amount of items currently in the queue.
12
+ private _length: number;
13
+
14
+ constructor(initialItems: Iterable<T>) {
15
+ this.table = [...initialItems];
16
+ this.head = 0;
17
+ this._length = this.table.length;
18
+ }
19
+
20
+ get isEmpty(): boolean {
21
+ return this.length == 0;
22
+ }
23
+
24
+ get length(): number {
25
+ return this._length;
26
+ }
27
+
28
+ removeFirst(): T {
29
+ if (this.isEmpty) {
30
+ throw new Error('Queue is empty');
31
+ }
32
+
33
+ const result = this.table[this.head] as T;
34
+ this._length--;
35
+ this.table[this.head] = undefined;
36
+ this.head = (this.head + 1) % this.table.length;
37
+ return result;
38
+ }
39
+
40
+ addLast(element: T) {
41
+ if (this.length == this.table.length) {
42
+ throw new Error('Queue is full');
43
+ }
44
+
45
+ this.table[(this.head + this._length) % this.table.length] = element;
46
+ this._length++;
47
+ }
48
+ }