@powersync/common 1.33.2 → 1.34.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.
@@ -1,5 +1,5 @@
1
1
  import { Mutex } from 'async-mutex';
2
- import Logger, { ILogger } from 'js-logger';
2
+ import { ILogger } from 'js-logger';
3
3
  import { DBAdapter, QueryResult, Transaction } from '../db/DBAdapter.js';
4
4
  import { SyncStatus } from '../db/crud/SyncStatus.js';
5
5
  import { UploadQueueStats } from '../db/crud/UploadQueueStatus.js';
@@ -84,7 +84,6 @@ export declare const DEFAULT_POWERSYNC_CLOSE_OPTIONS: PowerSyncCloseOptions;
84
84
  export declare const DEFAULT_WATCH_THROTTLE_MS = 30;
85
85
  export declare const DEFAULT_POWERSYNC_DB_OPTIONS: {
86
86
  retryDelayMs: number;
87
- logger: Logger.ILogger;
88
87
  crudUploadThrottleMs: number;
89
88
  };
90
89
  export declare const DEFAULT_CRUD_BATCH_LIMIT = 100;
@@ -101,11 +100,6 @@ export declare const DEFAULT_LOCK_TIMEOUT_MS = 120000;
101
100
  export declare const isPowerSyncDatabaseOptionsWithSettings: (test: any) => test is PowerSyncDatabaseOptionsWithSettings;
102
101
  export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDBListener> {
103
102
  protected options: PowerSyncDatabaseOptions;
104
- /**
105
- * Transactions should be queued in the DBAdapter, but we also want to prevent
106
- * calls to `.execute` while an async transaction is running.
107
- */
108
- protected static transactionMutex: Mutex;
109
103
  /**
110
104
  * Returns true if the connection is closed.
111
105
  */
@@ -123,6 +117,7 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
123
117
  protected _schema: Schema;
124
118
  private _database;
125
119
  protected runExclusiveMutex: Mutex;
120
+ logger: ILogger;
126
121
  constructor(options: PowerSyncDatabaseOptionsWithDBAdapter);
127
122
  constructor(options: PowerSyncDatabaseOptionsWithOpenFactory);
128
123
  constructor(options: PowerSyncDatabaseOptionsWithSettings);
@@ -185,7 +180,6 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
185
180
  * Cannot be used while connected - this should only be called before {@link AbstractPowerSyncDatabase.connect}.
186
181
  */
187
182
  updateSchema(schema: Schema): Promise<void>;
188
- get logger(): Logger.ILogger;
189
183
  /**
190
184
  * Wait for initialization to complete.
191
185
  * While initializing is automatic, this helps to catch and report initialization errors.
@@ -8,7 +8,6 @@ import { UploadQueueStats } from '../db/crud/UploadQueueStatus.js';
8
8
  import { BaseObserver } from '../utils/BaseObserver.js';
9
9
  import { ControlledExecutor } from '../utils/ControlledExecutor.js';
10
10
  import { throttleTrailing } from '../utils/async.js';
11
- import { mutexRunExclusive } from '../utils/mutex.js';
12
11
  import { ConnectionManager } from './ConnectionManager.js';
13
12
  import { isDBAdapter, isSQLOpenFactory, isSQLOpenOptions } from './SQLOpenFactory.js';
14
13
  import { runOnSchemaChange } from './runOnSchemaChange.js';
@@ -27,7 +26,6 @@ export const DEFAULT_POWERSYNC_CLOSE_OPTIONS = {
27
26
  export const DEFAULT_WATCH_THROTTLE_MS = 30;
28
27
  export const DEFAULT_POWERSYNC_DB_OPTIONS = {
29
28
  retryDelayMs: 5000,
30
- logger: Logger.get('PowerSyncDatabase'),
31
29
  crudUploadThrottleMs: DEFAULT_CRUD_UPLOAD_THROTTLE_MS
32
30
  };
33
31
  export const DEFAULT_CRUD_BATCH_LIMIT = 100;
@@ -46,11 +44,6 @@ export const isPowerSyncDatabaseOptionsWithSettings = (test) => {
46
44
  };
47
45
  export class AbstractPowerSyncDatabase extends BaseObserver {
48
46
  options;
49
- /**
50
- * Transactions should be queued in the DBAdapter, but we also want to prevent
51
- * calls to `.execute` while an async transaction is running.
52
- */
53
- static transactionMutex = new Mutex();
54
47
  /**
55
48
  * Returns true if the connection is closed.
56
49
  */
@@ -70,6 +63,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
70
63
  _schema;
71
64
  _database;
72
65
  runExclusiveMutex;
66
+ logger;
73
67
  constructor(options) {
74
68
  super();
75
69
  this.options = options;
@@ -89,6 +83,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
89
83
  else {
90
84
  throw new Error('The provided `database` option is invalid.');
91
85
  }
86
+ this.logger = options.logger ?? Logger.get(`PowerSyncDatabase[${this._database.name}]`);
92
87
  this.bucketStorageAdapter = this.generateBucketStorageAdapter();
93
88
  this.closed = false;
94
89
  this.currentStatus = new SyncStatus({});
@@ -268,16 +263,13 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
268
263
  schema.validate();
269
264
  }
270
265
  catch (ex) {
271
- this.options.logger?.warn('Schema validation failed. Unexpected behaviour could occur', ex);
266
+ this.logger.warn('Schema validation failed. Unexpected behaviour could occur', ex);
272
267
  }
273
268
  this._schema = schema;
274
269
  await this.database.execute('SELECT powersync_replace_schema(?)', [JSON.stringify(this.schema.toJSON())]);
275
270
  await this.database.refreshSchema();
276
271
  this.iterateListeners(async (cb) => cb.schemaChanged?.(schema));
277
272
  }
278
- get logger() {
279
- return this.options.logger;
280
- }
281
273
  /**
282
274
  * Wait for initialization to complete.
283
275
  * While initializing is automatic, this helps to catch and report initialization errors.
@@ -304,7 +296,9 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
304
296
  * Connects to stream of events from the PowerSync instance.
305
297
  */
306
298
  async connect(connector, options) {
307
- return this.connectionManager.connect(connector, options);
299
+ const resolvedOptions = options ?? {};
300
+ resolvedOptions.serializedSchema = this.schema.toJSON();
301
+ return this.connectionManager.connect(connector, resolvedOptions);
308
302
  }
309
303
  /**
310
304
  * Close the sync connection.
@@ -477,8 +471,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
477
471
  * @returns The query result as an object with structured key-value pairs
478
472
  */
479
473
  async execute(sql, parameters) {
480
- await this.waitForReady();
481
- return this.database.execute(sql, parameters);
474
+ return this.writeLock((tx) => tx.execute(sql, parameters));
482
475
  }
483
476
  /**
484
477
  * Execute a SQL write (INSERT/UPDATE/DELETE) query directly on the database without any PowerSync processing.
@@ -546,7 +539,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
546
539
  */
547
540
  async readLock(callback) {
548
541
  await this.waitForReady();
549
- return mutexRunExclusive(AbstractPowerSyncDatabase.transactionMutex, () => callback(this.database));
542
+ return this.database.readLock(callback);
550
543
  }
551
544
  /**
552
545
  * Takes a global lock, without starting a transaction.
@@ -554,10 +547,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
554
547
  */
555
548
  async writeLock(callback) {
556
549
  await this.waitForReady();
557
- return mutexRunExclusive(AbstractPowerSyncDatabase.transactionMutex, async () => {
558
- const res = await callback(this.database);
559
- return res;
560
- });
550
+ return this.database.writeLock(callback);
561
551
  }
562
552
  /**
563
553
  * Open a read-only transaction.
@@ -617,7 +607,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
617
607
  * @param options Options for configuring watch behavior
618
608
  */
619
609
  watchWithCallback(sql, parameters, handler, options) {
620
- const { onResult, onError = (e) => this.options.logger?.error(e) } = handler ?? {};
610
+ const { onResult, onError = (e) => this.logger.error(e) } = handler ?? {};
621
611
  if (!onResult) {
622
612
  throw new Error('onResult is required');
623
613
  }
@@ -723,7 +713,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
723
713
  * @returns A dispose function to stop watching for changes
724
714
  */
725
715
  onChangeWithCallback(handler, options) {
726
- const { onChange, onError = (e) => this.options.logger?.error(e) } = handler ?? {};
716
+ const { onChange, onError = (e) => this.logger.error(e) } = handler ?? {};
727
717
  if (!onChange) {
728
718
  throw new Error('onChange is required');
729
719
  }
@@ -1,7 +1,7 @@
1
1
  import { ILogger } from 'js-logger';
2
2
  import { BaseListener, BaseObserver } from '../utils/BaseObserver.js';
3
3
  import { PowerSyncBackendConnector } from './connection/PowerSyncBackendConnector.js';
4
- import { PowerSyncConnectionOptions, StreamingSyncImplementation } from './sync/stream/AbstractStreamingSyncImplementation.js';
4
+ import { InternalConnectionOptions, StreamingSyncImplementation } from './sync/stream/AbstractStreamingSyncImplementation.js';
5
5
  /**
6
6
  * @internal
7
7
  */
@@ -17,12 +17,12 @@ export interface ConnectionManagerSyncImplementationResult {
17
17
  * @internal
18
18
  */
19
19
  export interface ConnectionManagerOptions {
20
- createSyncImplementation(connector: PowerSyncBackendConnector, options: PowerSyncConnectionOptions): Promise<ConnectionManagerSyncImplementationResult>;
20
+ createSyncImplementation(connector: PowerSyncBackendConnector, options: InternalConnectionOptions): Promise<ConnectionManagerSyncImplementationResult>;
21
21
  logger: ILogger;
22
22
  }
23
23
  type StoredConnectionOptions = {
24
24
  connector: PowerSyncBackendConnector;
25
- options: PowerSyncConnectionOptions;
25
+ options: InternalConnectionOptions;
26
26
  };
27
27
  /**
28
28
  * @internal
@@ -66,7 +66,7 @@ export declare class ConnectionManager extends BaseObserver<ConnectionManagerLis
66
66
  constructor(options: ConnectionManagerOptions);
67
67
  get logger(): ILogger;
68
68
  close(): Promise<void>;
69
- connect(connector: PowerSyncBackendConnector, options?: PowerSyncConnectionOptions): Promise<void>;
69
+ connect(connector: PowerSyncBackendConnector, options: InternalConnectionOptions): Promise<void>;
70
70
  protected connectInternal(): Promise<void>;
71
71
  /**
72
72
  * Close the sync connection.
@@ -1,4 +1,3 @@
1
- import { Mutex } from 'async-mutex';
2
1
  import { ILogger } from 'js-logger';
3
2
  import { DBAdapter, Transaction } from '../../../db/DBAdapter.js';
4
3
  import { BaseObserver } from '../../../utils/BaseObserver.js';
@@ -8,13 +7,12 @@ import { CrudEntry } from './CrudEntry.js';
8
7
  import { SyncDataBatch } from './SyncDataBatch.js';
9
8
  export declare class SqliteBucketStorage extends BaseObserver<BucketStorageListener> implements BucketStorageAdapter {
10
9
  private db;
11
- private mutex;
12
10
  private logger;
13
11
  tableNames: Set<string>;
14
12
  private _hasCompletedSync;
15
13
  private updateListener;
16
14
  private _clientId?;
17
- constructor(db: DBAdapter, mutex: Mutex, logger?: ILogger);
15
+ constructor(db: DBAdapter, logger?: ILogger);
18
16
  init(): Promise<void>;
19
17
  dispose(): Promise<void>;
20
18
  _getClientId(): Promise<string>;
@@ -6,16 +6,14 @@ import { PSInternalTable } from './BucketStorageAdapter.js';
6
6
  import { CrudEntry } from './CrudEntry.js';
7
7
  export class SqliteBucketStorage extends BaseObserver {
8
8
  db;
9
- mutex;
10
9
  logger;
11
10
  tableNames;
12
11
  _hasCompletedSync;
13
12
  updateListener;
14
13
  _clientId;
15
- constructor(db, mutex, logger = Logger.get('SqliteBucketStorage')) {
14
+ constructor(db, logger = Logger.get('SqliteBucketStorage')) {
16
15
  super();
17
16
  this.db = db;
18
- this.mutex = mutex;
19
17
  this.logger = logger;
20
18
  this._hasCompletedSync = false;
21
19
  this.tableNames = new Set();
@@ -66,11 +64,11 @@ export class SqliteBucketStorage extends BaseObserver {
66
64
  async saveSyncData(batch, fixedKeyFormat = false) {
67
65
  await this.writeTransaction(async (tx) => {
68
66
  for (const b of batch.buckets) {
69
- const result = await tx.execute('INSERT INTO powersync_operations(op, data) VALUES(?, ?)', [
67
+ await tx.execute('INSERT INTO powersync_operations(op, data) VALUES(?, ?)', [
70
68
  'save',
71
69
  JSON.stringify({ buckets: [b.toJSON(fixedKeyFormat)] })
72
70
  ]);
73
- this.logger.debug('saveSyncData', JSON.stringify(result));
71
+ this.logger.debug(`Saved batch of data for bucket: ${b.bucket}, operations: ${b.data.length}`);
74
72
  }
75
73
  });
76
74
  }
@@ -86,7 +84,7 @@ export class SqliteBucketStorage extends BaseObserver {
86
84
  await this.writeTransaction(async (tx) => {
87
85
  await tx.execute('INSERT INTO powersync_operations(op, data) VALUES(?, ?)', ['delete_bucket', bucket]);
88
86
  });
89
- this.logger.debug('done deleting bucket');
87
+ this.logger.debug(`Done deleting bucket ${bucket}`);
90
88
  }
91
89
  async hasCompletedSync() {
92
90
  if (this._hasCompletedSync) {
@@ -108,6 +106,12 @@ export class SqliteBucketStorage extends BaseObserver {
108
106
  }
109
107
  return { ready: false, checkpointValid: false, checkpointFailures: r.checkpointFailures };
110
108
  }
109
+ if (priority == null) {
110
+ this.logger.debug(`Validated checksums checkpoint ${checkpoint.last_op_id}`);
111
+ }
112
+ else {
113
+ this.logger.debug(`Validated checksums for partial checkpoint ${checkpoint.last_op_id}, priority ${priority}`);
114
+ }
111
115
  let buckets = checkpoint.buckets;
112
116
  if (priority !== undefined) {
113
117
  buckets = buckets.filter((b) => hasMatchingPriority(priority, b));
@@ -124,7 +128,6 @@ export class SqliteBucketStorage extends BaseObserver {
124
128
  });
125
129
  const valid = await this.updateObjectsFromBuckets(checkpoint, priority);
126
130
  if (!valid) {
127
- this.logger.debug('Not at a consistent checkpoint - cannot update local db');
128
131
  return { ready: false, checkpointValid: true };
129
132
  }
130
133
  return {
@@ -177,7 +180,6 @@ export class SqliteBucketStorage extends BaseObserver {
177
180
  JSON.stringify({ ...checkpoint })
178
181
  ]);
179
182
  const resultItem = rs.rows?.item(0);
180
- this.logger.debug('validateChecksums priority, checkpoint, result item', priority, checkpoint, resultItem);
181
183
  if (!resultItem) {
182
184
  return {
183
185
  checkpointValid: false,
@@ -210,30 +212,26 @@ export class SqliteBucketStorage extends BaseObserver {
210
212
  }
211
213
  const seqBefore = rs[0]['seq'];
212
214
  const opId = await cb();
213
- this.logger.debug(`[updateLocalTarget] Updating target to checkpoint ${opId}`);
214
215
  return this.writeTransaction(async (tx) => {
215
216
  const anyData = await tx.execute('SELECT 1 FROM ps_crud LIMIT 1');
216
217
  if (anyData.rows?.length) {
217
218
  // if isNotEmpty
218
- this.logger.debug('updateLocalTarget', 'ps crud is not empty');
219
+ this.logger.debug(`New data uploaded since write checkpoint ${opId} - need new write checkpoint`);
219
220
  return false;
220
221
  }
221
222
  const rs = await tx.execute("SELECT seq FROM sqlite_sequence WHERE name = 'ps_crud'");
222
223
  if (!rs.rows?.length) {
223
224
  // assert isNotEmpty
224
- throw new Error('SQlite Sequence should not be empty');
225
+ throw new Error('SQLite Sequence should not be empty');
225
226
  }
226
227
  const seqAfter = rs.rows?.item(0)['seq'];
227
- this.logger.debug('seqAfter', JSON.stringify(rs.rows?.item(0)));
228
228
  if (seqAfter != seqBefore) {
229
- this.logger.debug('seqAfter != seqBefore', seqAfter, seqBefore);
229
+ this.logger.debug(`New data uploaded since write checpoint ${opId} - need new write checkpoint (sequence updated)`);
230
230
  // New crud data may have been uploaded since we got the checkpoint. Abort.
231
231
  return false;
232
232
  }
233
- const response = await tx.execute("UPDATE ps_buckets SET target_op = CAST(? as INTEGER) WHERE name='$local'", [
234
- opId
235
- ]);
236
- this.logger.debug(['[updateLocalTarget] Response from updating target_op ', JSON.stringify(response)]);
233
+ this.logger.debug(`Updating target write checkpoint to ${opId}`);
234
+ await tx.execute("UPDATE ps_buckets SET target_op = CAST(? as INTEGER) WHERE name='$local'", [opId]);
237
235
  return true;
238
236
  });
239
237
  }
@@ -3,7 +3,7 @@ import { type fetch } from 'cross-fetch';
3
3
  import Logger, { ILogger } from 'js-logger';
4
4
  import { DataStream } from '../../../utils/DataStream.js';
5
5
  import { PowerSyncCredentials } from '../../connection/PowerSyncCredentials.js';
6
- import { StreamingSyncLine, StreamingSyncRequest } from './streaming-sync-types.js';
6
+ import { StreamingSyncRequest } from './streaming-sync-types.js';
7
7
  export type BSONImplementation = typeof BSON;
8
8
  export type RemoteConnector = {
9
9
  fetchCredentials: () => Promise<PowerSyncCredentials | null>;
@@ -120,11 +120,6 @@ export declare abstract class AbstractRemote {
120
120
  */
121
121
  abstract getBSON(): Promise<BSONImplementation>;
122
122
  protected createSocket(url: string): WebSocket;
123
- /**
124
- * Connects to the sync/stream websocket endpoint and delivers sync lines by decoding the BSON events
125
- * sent by the server.
126
- */
127
- socketStream(options: SocketSyncStreamOptions): Promise<DataStream<StreamingSyncLine>>;
128
123
  /**
129
124
  * Returns a data stream of sync line data.
130
125
  *
@@ -133,10 +128,6 @@ export declare abstract class AbstractRemote {
133
128
  * (required for compatibility with older sync services).
134
129
  */
135
130
  socketStreamRaw<T>(options: SocketSyncStreamOptions, map: (buffer: Uint8Array) => T, bson?: typeof BSON): Promise<DataStream<T>>;
136
- /**
137
- * Connects to the sync/stream http endpoint, parsing lines as JSON.
138
- */
139
- postStream(options: SyncStreamOptions): Promise<DataStream<StreamingSyncLine>>;
140
131
  /**
141
132
  * Connects to the sync/stream http endpoint, mapping and emitting each received string line.
142
133
  */
@@ -178,14 +178,6 @@ export class AbstractRemote {
178
178
  createSocket(url) {
179
179
  return new WebSocket(url);
180
180
  }
181
- /**
182
- * Connects to the sync/stream websocket endpoint and delivers sync lines by decoding the BSON events
183
- * sent by the server.
184
- */
185
- async socketStream(options) {
186
- const bson = await this.getBSON();
187
- return await this.socketStreamRaw(options, (data) => bson.deserialize(data), bson);
188
- }
189
181
  /**
190
182
  * Returns a data stream of sync line data.
191
183
  *
@@ -363,14 +355,6 @@ export class AbstractRemote {
363
355
  }
364
356
  return stream;
365
357
  }
366
- /**
367
- * Connects to the sync/stream http endpoint, parsing lines as JSON.
368
- */
369
- async postStream(options) {
370
- return await this.postStreamRaw(options, (line) => {
371
- return JSON.parse(line);
372
- });
373
- }
374
358
  /**
375
359
  * Connects to the sync/stream http endpoint, mapping and emitting each received string line.
376
360
  */
@@ -1,4 +1,4 @@
1
- import Logger, { ILogger } from 'js-logger';
1
+ import { ILogger } from 'js-logger';
2
2
  import { SyncStatus, SyncStatusOptions } from '../../../db/crud/SyncStatus.js';
3
3
  import { BaseListener, BaseObserver, Disposable } from '../../../utils/BaseObserver.js';
4
4
  import { BucketStorageAdapter } from '../bucket/BucketStorageAdapter.js';
@@ -88,7 +88,8 @@ export interface StreamingSyncImplementationListener extends BaseListener {
88
88
  * Configurable options to be used when connecting to the PowerSync
89
89
  * backend instance.
90
90
  */
91
- export interface PowerSyncConnectionOptions extends BaseConnectionOptions, AdditionalConnectionOptions {
91
+ export type PowerSyncConnectionOptions = Omit<InternalConnectionOptions, 'serializedSchema'>;
92
+ export interface InternalConnectionOptions extends BaseConnectionOptions, AdditionalConnectionOptions {
92
93
  }
93
94
  /** @internal */
94
95
  export interface BaseConnectionOptions {
@@ -114,6 +115,10 @@ export interface BaseConnectionOptions {
114
115
  * These parameters are passed to the sync rules, and will be available under the`user_parameters` object.
115
116
  */
116
117
  params?: Record<string, StreamingSyncRequestParameterType>;
118
+ /**
119
+ * The serialized schema - mainly used to forward information about raw tables to the sync client.
120
+ */
121
+ serializedSchema?: any;
117
122
  }
118
123
  /** @internal */
119
124
  export interface AdditionalConnectionOptions {
@@ -135,7 +140,7 @@ export interface StreamingSyncImplementation extends BaseObserver<StreamingSyncI
135
140
  /**
136
141
  * Connects to the sync service
137
142
  */
138
- connect(options?: PowerSyncConnectionOptions): Promise<void>;
143
+ connect(options?: InternalConnectionOptions): Promise<void>;
139
144
  /**
140
145
  * Disconnects from the sync services.
141
146
  * @throws if not connected or if abort is not controlled internally
@@ -155,7 +160,6 @@ export declare const DEFAULT_CRUD_UPLOAD_THROTTLE_MS = 1000;
155
160
  export declare const DEFAULT_RETRY_DELAY_MS = 5000;
156
161
  export declare const DEFAULT_STREAMING_SYNC_OPTIONS: {
157
162
  retryDelayMs: number;
158
- logger: Logger.ILogger;
159
163
  crudUploadThrottleMs: number;
160
164
  };
161
165
  export type RequiredPowerSyncConnectionOptions = Required<BaseConnectionOptions>;
@@ -166,7 +170,8 @@ export declare abstract class AbstractStreamingSyncImplementation extends BaseOb
166
170
  protected abortController: AbortController | null;
167
171
  protected crudUpdateListener?: () => void;
168
172
  protected streamingSyncPromise?: Promise<void>;
169
- private pendingCrudUpload?;
173
+ protected logger: ILogger;
174
+ private isUploadingCrud;
170
175
  private notifyCompletedUploads?;
171
176
  syncStatus: SyncStatus;
172
177
  triggerCrudUpload: () => void;
@@ -176,7 +181,6 @@ export declare abstract class AbstractStreamingSyncImplementation extends BaseOb
176
181
  waitUntilStatusMatches(predicate: (status: SyncStatus) => boolean): Promise<void>;
177
182
  get lastSyncedAt(): Date | undefined;
178
183
  get isConnected(): boolean;
179
- protected get logger(): Logger.ILogger;
180
184
  dispose(): Promise<void>;
181
185
  abstract obtainLock<T>(lockOptions: LockOptions<T>): Promise<T>;
182
186
  hasCompletedSync(): Promise<boolean>;