@powersync/common 0.0.0-dev-20241107150304 → 0.0.0-dev-20250207081035

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.
@@ -10,25 +10,18 @@ import { PowerSyncBackendConnector } from './connection/PowerSyncBackendConnecto
10
10
  import { BucketStorageAdapter } from './sync/bucket/BucketStorageAdapter.js';
11
11
  import { CrudBatch } from './sync/bucket/CrudBatch.js';
12
12
  import { CrudTransaction } from './sync/bucket/CrudTransaction.js';
13
- import { PowerSyncConnectionOptions, StreamingSyncImplementation, StreamingSyncImplementationListener } from './sync/stream/AbstractStreamingSyncImplementation.js';
13
+ import { type AdditionalConnectionOptions, type PowerSyncConnectionOptions, StreamingSyncImplementation, StreamingSyncImplementationListener, type RequiredAdditionalConnectionOptions } from './sync/stream/AbstractStreamingSyncImplementation.js';
14
14
  export interface DisconnectAndClearOptions {
15
15
  /** When set to false, data in local-only tables is preserved. */
16
16
  clearLocal?: boolean;
17
17
  }
18
- export interface BasePowerSyncDatabaseOptions {
18
+ export interface BasePowerSyncDatabaseOptions extends AdditionalConnectionOptions {
19
19
  /** Schema used for the local database. */
20
20
  schema: Schema;
21
21
  /**
22
- * Delay for retrying sync streaming operations
23
- * from the PowerSync backend after an error occurs.
22
+ * @deprecated Use {@link retryDelayMs} instead as this will be removed in future releases.
24
23
  */
25
24
  retryDelay?: number;
26
- /**
27
- * Backend Connector CRUD operations are throttled
28
- * to occur at most every `crudUploadThrottleMs`
29
- * milliseconds.
30
- */
31
- crudUploadThrottleMs?: number;
32
25
  logger?: ILogger;
33
26
  }
34
27
  export interface PowerSyncDatabaseOptions extends BasePowerSyncDatabaseOptions {
@@ -76,6 +69,7 @@ export interface WatchOnChangeHandler {
76
69
  }
77
70
  export interface PowerSyncDBListener extends StreamingSyncImplementationListener {
78
71
  initialized: () => void;
72
+ schemaChanged: (schema: Schema) => void;
79
73
  }
80
74
  export interface PowerSyncCloseOptions {
81
75
  /**
@@ -88,7 +82,7 @@ export interface PowerSyncCloseOptions {
88
82
  export declare const DEFAULT_POWERSYNC_CLOSE_OPTIONS: PowerSyncCloseOptions;
89
83
  export declare const DEFAULT_WATCH_THROTTLE_MS = 30;
90
84
  export declare const DEFAULT_POWERSYNC_DB_OPTIONS: {
91
- retryDelay: number;
85
+ retryDelayMs: number;
92
86
  logger: Logger.ILogger;
93
87
  crudUploadThrottleMs: number;
94
88
  };
@@ -147,11 +141,12 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
147
141
  * Whether a connection to the PowerSync service is currently open.
148
142
  */
149
143
  get connected(): boolean;
144
+ get connecting(): boolean;
150
145
  /**
151
146
  * Opens the DBAdapter given open options using a default open factory
152
147
  */
153
148
  protected abstract openDBAdapter(options: PowerSyncDatabaseOptionsWithSettings): DBAdapter;
154
- protected abstract generateSyncStreamImplementation(connector: PowerSyncBackendConnector): StreamingSyncImplementation;
149
+ protected abstract generateSyncStreamImplementation(connector: PowerSyncBackendConnector, options: RequiredAdditionalConnectionOptions): StreamingSyncImplementation;
155
150
  protected abstract generateBucketStorageAdapter(): BucketStorageAdapter;
156
151
  /**
157
152
  * @returns A promise which will resolve once initialization is completed.
@@ -184,6 +179,7 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
184
179
  * While initializing is automatic, this helps to catch and report initialization errors.
185
180
  */
186
181
  init(): Promise<void>;
182
+ resolvedConnectionOptions(options?: PowerSyncConnectionOptions): RequiredAdditionalConnectionOptions;
187
183
  /**
188
184
  * Connects to stream of events from the PowerSync instance.
189
185
  */
@@ -13,7 +13,8 @@ import { PSInternalTable } from './sync/bucket/BucketStorageAdapter.js';
13
13
  import { CrudBatch } from './sync/bucket/CrudBatch.js';
14
14
  import { CrudEntry } from './sync/bucket/CrudEntry.js';
15
15
  import { CrudTransaction } from './sync/bucket/CrudTransaction.js';
16
- import { DEFAULT_CRUD_UPLOAD_THROTTLE_MS } from './sync/stream/AbstractStreamingSyncImplementation.js';
16
+ import { DEFAULT_CRUD_UPLOAD_THROTTLE_MS, DEFAULT_RETRY_DELAY_MS } from './sync/stream/AbstractStreamingSyncImplementation.js';
17
+ import { runOnSchemaChange } from './runOnSchemaChange.js';
17
18
  const POWERSYNC_TABLE_MATCH = /(^ps_data__|^ps_data_local__)/;
18
19
  const DEFAULT_DISCONNECT_CLEAR_OPTIONS = {
19
20
  clearLocal: true
@@ -23,7 +24,7 @@ export const DEFAULT_POWERSYNC_CLOSE_OPTIONS = {
23
24
  };
24
25
  export const DEFAULT_WATCH_THROTTLE_MS = 30;
25
26
  export const DEFAULT_POWERSYNC_DB_OPTIONS = {
26
- retryDelay: 5000,
27
+ retryDelayMs: 5000,
27
28
  logger: Logger.get('PowerSyncDatabase'),
28
29
  crudUploadThrottleMs: DEFAULT_CRUD_UPLOAD_THROTTLE_MS
29
30
  };
@@ -113,6 +114,9 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
113
114
  get connected() {
114
115
  return this.currentStatus?.connected || false;
115
116
  }
117
+ get connecting() {
118
+ return this.currentStatus?.connecting || false;
119
+ }
116
120
  /**
117
121
  * @returns A promise which will resolve once initialization is completed.
118
122
  */
@@ -211,6 +215,8 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
211
215
  }
212
216
  this._schema = schema;
213
217
  await this.database.execute('SELECT powersync_replace_schema(?)', [JSON.stringify(this.schema.toJSON())]);
218
+ await this.database.refreshSchema();
219
+ this.iterateListeners(async (cb) => cb.schemaChanged?.(schema));
214
220
  }
215
221
  /**
216
222
  * Wait for initialization to complete.
@@ -219,6 +225,13 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
219
225
  async init() {
220
226
  return this.waitForReady();
221
227
  }
228
+ // Use the options passed in during connect, or fallback to the options set during database creation or fallback to the default options
229
+ resolvedConnectionOptions(options) {
230
+ return {
231
+ retryDelayMs: options?.retryDelayMs ?? this.options.retryDelayMs ?? this.options.retryDelay ?? DEFAULT_RETRY_DELAY_MS,
232
+ crudUploadThrottleMs: options?.crudUploadThrottleMs ?? this.options.crudUploadThrottleMs ?? DEFAULT_CRUD_UPLOAD_THROTTLE_MS
233
+ };
234
+ }
222
235
  /**
223
236
  * Connects to stream of events from the PowerSync instance.
224
237
  */
@@ -229,7 +242,11 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
229
242
  if (this.closed) {
230
243
  throw new Error('Cannot connect using a closed client');
231
244
  }
232
- this.syncStreamImplementation = this.generateSyncStreamImplementation(connector);
245
+ const { retryDelayMs, crudUploadThrottleMs } = this.resolvedConnectionOptions(options);
246
+ this.syncStreamImplementation = this.generateSyncStreamImplementation(connector, {
247
+ retryDelayMs,
248
+ crudUploadThrottleMs,
249
+ });
233
250
  this.syncStatusListenerDisposer = this.syncStreamImplementation.registerListener({
234
251
  statusChanged: (status) => {
235
252
  this.currentStatus = new SyncStatus({
@@ -387,13 +404,13 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
387
404
  if (writeCheckpoint) {
388
405
  const check = await tx.execute(`SELECT 1 FROM ${PSInternalTable.CRUD} LIMIT 1`);
389
406
  if (!check.rows?.length) {
390
- await tx.execute(`UPDATE ${PSInternalTable.BUCKETS} SET target_op = ? WHERE name='$local'`, [
407
+ await tx.execute(`UPDATE ${PSInternalTable.BUCKETS} SET target_op = CAST(? as INTEGER) WHERE name='$local'`, [
391
408
  writeCheckpoint
392
409
  ]);
393
410
  }
394
411
  }
395
412
  else {
396
- await tx.execute(`UPDATE ${PSInternalTable.BUCKETS} SET target_op = ? WHERE name='$local'`, [
413
+ await tx.execute(`UPDATE ${PSInternalTable.BUCKETS} SET target_op = CAST(? as INTEGER) WHERE name='$local'`, [
397
414
  this.bucketStorageAdapter.getMaxOpId()
398
415
  ]);
399
416
  }
@@ -503,7 +520,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
503
520
  if (!onResult) {
504
521
  throw new Error('onResult is required');
505
522
  }
506
- (async () => {
523
+ const watchQuery = async (abortSignal) => {
507
524
  try {
508
525
  const resolvedTables = await this.resolveTables(sql, parameters, options);
509
526
  // Fetch initial data
@@ -522,13 +539,16 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
522
539
  onError
523
540
  }, {
524
541
  ...(options ?? {}),
525
- tables: resolvedTables
542
+ tables: resolvedTables,
543
+ // Override the abort signal since we intercept it
544
+ signal: abortSignal
526
545
  });
527
546
  }
528
547
  catch (error) {
529
548
  onError?.(error);
530
549
  }
531
- })();
550
+ };
551
+ runOnSchemaChange(watchQuery, this, options);
532
552
  }
533
553
  /**
534
554
  * Execute a read query every time the source tables are modified.
@@ -537,17 +557,18 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
537
557
  */
538
558
  watchWithAsyncGenerator(sql, parameters, options) {
539
559
  return new EventIterator((eventOptions) => {
540
- (async () => {
541
- const resolvedTables = await this.resolveTables(sql, parameters, options);
542
- // Fetch initial data
543
- eventOptions.push(await this.executeReadOnly(sql, parameters));
544
- for await (const event of this.onChangeWithAsyncGenerator({
545
- ...(options ?? {}),
546
- tables: resolvedTables
547
- })) {
548
- eventOptions.push(await this.executeReadOnly(sql, parameters));
560
+ const handler = {
561
+ onResult: (result) => {
562
+ eventOptions.push(result);
563
+ },
564
+ onError: (error) => {
565
+ eventOptions.fail(error);
549
566
  }
550
- })();
567
+ };
568
+ this.watchWithCallback(sql, parameters, handler, options);
569
+ options?.signal?.addEventListener('abort', () => {
570
+ eventOptions.stop();
571
+ });
551
572
  });
552
573
  }
553
574
  async resolveTables(sql, parameters, options) {
@@ -0,0 +1,7 @@
1
+ import { CompilableQuery } from './../types/types.js';
2
+ import { AbstractPowerSyncDatabase, SQLWatchOptions } from './AbstractPowerSyncDatabase.js';
3
+ export interface CompilableQueryWatchHandler<T> {
4
+ onResult: (results: T[]) => void;
5
+ onError?: (error: Error) => void;
6
+ }
7
+ export declare function compilableQueryWatch<T>(db: AbstractPowerSyncDatabase, query: CompilableQuery<T>, handler: CompilableQueryWatchHandler<T>, options?: SQLWatchOptions): void;
@@ -0,0 +1,37 @@
1
+ import { runOnSchemaChange } from './runOnSchemaChange.js';
2
+ export function compilableQueryWatch(db, query, handler, options) {
3
+ const { onResult, onError = (e) => { } } = handler ?? {};
4
+ if (!onResult) {
5
+ throw new Error('onResult is required');
6
+ }
7
+ const watchQuery = async (abortSignal) => {
8
+ try {
9
+ const toSql = query.compile();
10
+ const resolvedTables = await db.resolveTables(toSql.sql, toSql.parameters, options);
11
+ // Fetch initial data
12
+ const result = await query.execute();
13
+ onResult(result);
14
+ db.onChangeWithCallback({
15
+ onChange: async () => {
16
+ try {
17
+ const result = await query.execute();
18
+ onResult(result);
19
+ }
20
+ catch (error) {
21
+ onError(error);
22
+ }
23
+ },
24
+ onError
25
+ }, {
26
+ ...(options ?? {}),
27
+ tables: resolvedTables,
28
+ // Override the abort signal since we intercept it
29
+ signal: abortSignal
30
+ });
31
+ }
32
+ catch (error) {
33
+ onError(error);
34
+ }
35
+ };
36
+ runOnSchemaChange(watchQuery, db, options);
37
+ }
@@ -0,0 +1,2 @@
1
+ import { AbstractPowerSyncDatabase, SQLWatchOptions } from './AbstractPowerSyncDatabase.js';
2
+ export declare function runOnSchemaChange(callback: (signal: AbortSignal) => void, db: AbstractPowerSyncDatabase, options?: SQLWatchOptions): void;
@@ -0,0 +1,23 @@
1
+ export function runOnSchemaChange(callback, db, options) {
2
+ const triggerWatchedQuery = () => {
3
+ const abortController = new AbortController();
4
+ let disposeSchemaListener = null;
5
+ const stopWatching = () => {
6
+ abortController.abort('Abort triggered');
7
+ disposeSchemaListener?.();
8
+ disposeSchemaListener = null;
9
+ // Stop listening to upstream abort for this watch
10
+ options?.signal?.removeEventListener('abort', stopWatching);
11
+ };
12
+ options?.signal?.addEventListener('abort', stopWatching);
13
+ disposeSchemaListener = db.registerListener({
14
+ schemaChanged: async () => {
15
+ stopWatching();
16
+ // Re trigger the watched query (recursively), setTimeout ensures that we don't modify the list of listeners while iterating through them
17
+ setTimeout(() => triggerWatchedQuery(), 0);
18
+ }
19
+ });
20
+ callback(abortController.signal);
21
+ };
22
+ triggerWatchedQuery();
23
+ }
@@ -63,7 +63,7 @@ export class SqliteBucketStorage extends BaseObserver {
63
63
  */
64
64
  startSession() { }
65
65
  async getBucketStates() {
66
- const result = await this.db.getAll('SELECT name as bucket, cast(last_op as TEXT) as op_id FROM ps_buckets WHERE pending_delete = 0');
66
+ const result = await this.db.getAll("SELECT name as bucket, cast(last_op as TEXT) as op_id FROM ps_buckets WHERE pending_delete = 0 AND name != '$local'");
67
67
  return result;
68
68
  }
69
69
  async saveSyncData(batch) {
@@ -204,9 +204,7 @@ export class SqliteBucketStorage extends BaseObserver {
204
204
  this.compactCounter = 0;
205
205
  }
206
206
  async updateLocalTarget(cb) {
207
- const rs1 = await this.db.getAll("SELECT target_op FROM ps_buckets WHERE name = '$local' AND target_op = ?", [
208
- MAX_OP_ID
209
- ]);
207
+ const rs1 = await this.db.getAll("SELECT target_op FROM ps_buckets WHERE name = '$local' AND target_op = CAST(? as INTEGER)", [MAX_OP_ID]);
210
208
  if (!rs1.length) {
211
209
  // Nothing to update
212
210
  return false;
@@ -9,12 +9,12 @@ import { version as POWERSYNC_JS_VERSION } from '../../../../package.json';
9
9
  const POWERSYNC_TRAILING_SLASH_MATCH = /\/+$/;
10
10
  // Refresh at least 30 sec before it expires
11
11
  const REFRESH_CREDENTIALS_SAFETY_PERIOD_MS = 30_000;
12
- const SYNC_QUEUE_REQUEST_N = 10;
12
+ const SYNC_QUEUE_REQUEST_N = 1;
13
13
  const SYNC_QUEUE_REQUEST_LOW_WATER = 5;
14
14
  // Keep alive message is sent every period
15
- const KEEP_ALIVE_MS = 20_000;
15
+ const KEEP_ALIVE_MS = 60_000;
16
16
  // The ACK must be received in this period
17
- const KEEP_ALIVE_LIFETIME_MS = 30_000;
17
+ const KEEP_ALIVE_LIFETIME_MS = 90_000;
18
18
  export const DEFAULT_REMOTE_LOGGER = Logger.get('PowerSyncRemote');
19
19
  /**
20
20
  * Class wrapper for providing a fetch implementation.
@@ -20,10 +20,9 @@ export interface LockOptions<T> {
20
20
  type: LockType;
21
21
  signal?: AbortSignal;
22
22
  }
23
- export interface AbstractStreamingSyncImplementationOptions {
23
+ export interface AbstractStreamingSyncImplementationOptions extends AdditionalConnectionOptions {
24
24
  adapter: BucketStorageAdapter;
25
25
  uploadCrud: () => Promise<void>;
26
- crudUploadThrottleMs?: number;
27
26
  /**
28
27
  * An identifier for which PowerSync DB this sync implementation is
29
28
  * linked to. Most commonly DB name, but not restricted to DB name.
@@ -31,7 +30,6 @@ export interface AbstractStreamingSyncImplementationOptions {
31
30
  identifier?: string;
32
31
  logger?: ILogger;
33
32
  remote: AbstractRemote;
34
- retryDelayMs?: number;
35
33
  }
36
34
  export interface StreamingSyncImplementationListener extends BaseListener {
37
35
  /**
@@ -48,7 +46,10 @@ export interface StreamingSyncImplementationListener extends BaseListener {
48
46
  * Configurable options to be used when connecting to the PowerSync
49
47
  * backend instance.
50
48
  */
51
- export interface PowerSyncConnectionOptions {
49
+ export interface PowerSyncConnectionOptions extends BaseConnectionOptions, AdditionalConnectionOptions {
50
+ }
51
+ /** @internal */
52
+ export interface BaseConnectionOptions {
52
53
  /**
53
54
  * The connection method to use when streaming updates from
54
55
  * the PowerSync backend instance.
@@ -60,6 +61,22 @@ export interface PowerSyncConnectionOptions {
60
61
  */
61
62
  params?: Record<string, StreamingSyncRequestParameterType>;
62
63
  }
64
+ /** @internal */
65
+ export interface AdditionalConnectionOptions {
66
+ /**
67
+ * Delay for retrying sync streaming operations
68
+ * from the PowerSync backend after an error occurs.
69
+ */
70
+ retryDelayMs?: number;
71
+ /**
72
+ * Backend Connector CRUD operations are throttled
73
+ * to occur at most every `crudUploadThrottleMs`
74
+ * milliseconds.
75
+ */
76
+ crudUploadThrottleMs?: number;
77
+ }
78
+ /** @internal */
79
+ export type RequiredAdditionalConnectionOptions = Required<AdditionalConnectionOptions>;
63
80
  export interface StreamingSyncImplementation extends BaseObserver<StreamingSyncImplementationListener>, Disposable {
64
81
  /**
65
82
  * Connects to the sync service
@@ -80,12 +97,14 @@ export interface StreamingSyncImplementation extends BaseObserver<StreamingSyncI
80
97
  waitForStatus(status: SyncStatusOptions): Promise<void>;
81
98
  }
82
99
  export declare const DEFAULT_CRUD_UPLOAD_THROTTLE_MS = 1000;
100
+ export declare const DEFAULT_RETRY_DELAY_MS = 5000;
83
101
  export declare const DEFAULT_STREAMING_SYNC_OPTIONS: {
84
102
  retryDelayMs: number;
85
103
  logger: Logger.ILogger;
86
104
  crudUploadThrottleMs: number;
87
105
  };
88
- export declare const DEFAULT_STREAM_CONNECTION_OPTIONS: Required<PowerSyncConnectionOptions>;
106
+ export type RequiredPowerSyncConnectionOptions = Required<BaseConnectionOptions>;
107
+ export declare const DEFAULT_STREAM_CONNECTION_OPTIONS: RequiredPowerSyncConnectionOptions;
89
108
  export declare abstract class AbstractStreamingSyncImplementation extends BaseObserver<StreamingSyncImplementationListener> implements StreamingSyncImplementation {
90
109
  protected _lastSyncedAt: Date | null;
91
110
  protected options: AbstractStreamingSyncImplementationOptions;
@@ -16,8 +16,9 @@ export var SyncStreamConnectionMethod;
16
16
  SyncStreamConnectionMethod["WEB_SOCKET"] = "web-socket";
17
17
  })(SyncStreamConnectionMethod || (SyncStreamConnectionMethod = {}));
18
18
  export const DEFAULT_CRUD_UPLOAD_THROTTLE_MS = 1000;
19
+ export const DEFAULT_RETRY_DELAY_MS = 5000;
19
20
  export const DEFAULT_STREAMING_SYNC_OPTIONS = {
20
- retryDelayMs: 5000,
21
+ retryDelayMs: DEFAULT_RETRY_DELAY_MS,
21
22
  logger: Logger.get('PowerSyncStream'),
22
23
  crudUploadThrottleMs: DEFAULT_CRUD_UPLOAD_THROTTLE_MS
23
24
  };
@@ -38,6 +39,7 @@ export class AbstractStreamingSyncImplementation extends BaseObserver {
38
39
  this.options = { ...DEFAULT_STREAMING_SYNC_OPTIONS, ...options };
39
40
  this.syncStatus = new SyncStatus({
40
41
  connected: false,
42
+ connecting: false,
41
43
  lastSyncedAt: undefined,
42
44
  dataFlow: {
43
45
  uploading: false,
@@ -207,7 +209,7 @@ The next upload iteration will be delayed.`);
207
209
  }
208
210
  this.streamingSyncPromise = undefined;
209
211
  this.abortController = null;
210
- this.updateSyncStatus({ connected: false });
212
+ this.updateSyncStatus({ connected: false, connecting: false });
211
213
  }
212
214
  /**
213
215
  * @deprecated use [connect instead]
@@ -238,6 +240,7 @@ The next upload iteration will be delayed.`);
238
240
  this.crudUpdateListener = undefined;
239
241
  this.updateSyncStatus({
240
242
  connected: false,
243
+ connecting: false,
241
244
  dataFlow: {
242
245
  downloading: false
243
246
  }
@@ -250,6 +253,7 @@ The next upload iteration will be delayed.`);
250
253
  * - Close any sync stream ReadableStreams (which will also close any established network requests)
251
254
  */
252
255
  while (true) {
256
+ this.updateSyncStatus({ connecting: true });
253
257
  try {
254
258
  if (signal?.aborted) {
255
259
  break;
@@ -280,6 +284,7 @@ The next upload iteration will be delayed.`);
280
284
  else {
281
285
  this.logger.error(ex);
282
286
  }
287
+ // On error, wait a little before retrying
283
288
  await this.delayRetry();
284
289
  }
285
290
  finally {
@@ -288,13 +293,13 @@ The next upload iteration will be delayed.`);
288
293
  nestedAbortController = new AbortController();
289
294
  }
290
295
  this.updateSyncStatus({
291
- connected: false
296
+ connected: false,
297
+ connecting: true // May be unnecessary
292
298
  });
293
- // On error, wait a little before retrying
294
299
  }
295
300
  }
296
301
  // Mark as disconnected if here
297
- this.updateSyncStatus({ connected: false });
302
+ this.updateSyncStatus({ connected: false, connecting: false });
298
303
  }
299
304
  async streamingSyncIteration(signal, options) {
300
305
  return await this.obtainLock({
@@ -489,6 +494,7 @@ The next upload iteration will be delayed.`);
489
494
  updateSyncStatus(options) {
490
495
  const updatedStatus = new SyncStatus({
491
496
  connected: options.connected ?? this.syncStatus.connected,
497
+ connecting: !options.connected && (options.connecting ?? this.syncStatus.connecting),
492
498
  lastSyncedAt: options.lastSyncedAt ?? this.syncStatus.lastSyncedAt,
493
499
  dataFlow: {
494
500
  ...this.syncStatus.dataFlowStatus,
@@ -90,6 +90,10 @@ export interface DBAdapter extends BaseObserverInterface<DBAdapterListener>, DBG
90
90
  readTransaction: <T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions) => Promise<T>;
91
91
  writeLock: <T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions) => Promise<T>;
92
92
  writeTransaction: <T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions) => Promise<T>;
93
+ /**
94
+ * This method refreshes the schema information across all connections. This is for advanced use cases, and should generally not be needed.
95
+ */
96
+ refreshSchema: () => Promise<void>;
93
97
  }
94
98
  export declare function isBatchedUpdateNotification(update: BatchedUpdateNotification | UpdateNotification): update is BatchedUpdateNotification;
95
99
  export declare function extractTableUpdates(update: BatchedUpdateNotification | UpdateNotification): string[];
@@ -4,6 +4,7 @@ export type SyncDataFlowStatus = Partial<{
4
4
  }>;
5
5
  export type SyncStatusOptions = {
6
6
  connected?: boolean;
7
+ connecting?: boolean;
7
8
  dataFlow?: SyncDataFlowStatus;
8
9
  lastSyncedAt?: Date;
9
10
  hasSynced?: boolean;
@@ -15,6 +16,7 @@ export declare class SyncStatus {
15
16
  * true if currently connected.
16
17
  */
17
18
  get connected(): boolean;
19
+ get connecting(): boolean;
18
20
  /**
19
21
  * Time that a last sync has fully completed, if any.
20
22
  * Currently this is reset to null after a restart.
@@ -9,6 +9,9 @@ export class SyncStatus {
9
9
  get connected() {
10
10
  return this.options.connected ?? false;
11
11
  }
12
+ get connecting() {
13
+ return this.options.connecting ?? false;
14
+ }
12
15
  /**
13
16
  * Time that a last sync has fully completed, if any.
14
17
  * Currently this is reset to null after a restart.
@@ -44,11 +47,12 @@ export class SyncStatus {
44
47
  }
45
48
  getMessage() {
46
49
  const dataFlow = this.dataFlowStatus;
47
- return `SyncStatus<connected: ${this.connected} lastSyncedAt: ${this.lastSyncedAt} hasSynced: ${this.hasSynced}. Downloading: ${dataFlow.downloading}. Uploading: ${dataFlow.uploading}`;
50
+ return `SyncStatus<connected: ${this.connected} connecting: ${this.connecting} lastSyncedAt: ${this.lastSyncedAt} hasSynced: ${this.hasSynced}. Downloading: ${dataFlow.downloading}. Uploading: ${dataFlow.uploading}`;
48
51
  }
49
52
  toJSON() {
50
53
  return {
51
54
  connected: this.connected,
55
+ connecting: this.connecting,
52
56
  dataFlow: this.dataFlowStatus,
53
57
  lastSyncedAt: this.lastSyncedAt,
54
58
  hasSynced: this.hasSynced
@@ -12,7 +12,7 @@ export type BaseColumnType<T extends number | string | null> = {
12
12
  };
13
13
  export type ColumnsType = Record<string, BaseColumnType<any>>;
14
14
  export type ExtractColumnValueType<T extends BaseColumnType<any>> = T extends BaseColumnType<infer R> ? R : unknown;
15
- export declare const MAX_AMOUNT_OF_COLUMNS = 63;
15
+ export declare const MAX_AMOUNT_OF_COLUMNS = 1999;
16
16
  export declare const column: {
17
17
  text: BaseColumnType<string | null>;
18
18
  integer: BaseColumnType<number | null>;
@@ -14,9 +14,9 @@ const integer = {
14
14
  const real = {
15
15
  type: ColumnType.REAL
16
16
  };
17
- // There is maximum of 127 arguments for any function in SQLite. Currently we use json_object which uses 1 arg per key (column name)
18
- // and one per value, which limits it to 63 arguments.
19
- export const MAX_AMOUNT_OF_COLUMNS = 63;
17
+ // powersync-sqlite-core limits the number of column per table to 1999, due to internal SQLite limits.
18
+ // In earlier versions this was limited to 63.
19
+ export const MAX_AMOUNT_OF_COLUMNS = 1999;
20
20
  export const column = {
21
21
  text,
22
22
  integer,
@@ -1,6 +1,6 @@
1
1
  import { RowType, Table } from './Table.js';
2
2
  type SchemaType = Record<string, Table<any>>;
3
- type SchemaTableType<S extends SchemaType> = {
3
+ export type SchemaTableType<S extends SchemaType> = {
4
4
  [K in keyof S]: RowType<S[K]>;
5
5
  };
6
6
  /**
@@ -11,6 +11,16 @@ export class Schema {
11
11
  tables;
12
12
  constructor(tables) {
13
13
  if (Array.isArray(tables)) {
14
+ /*
15
+ We need to validate that the tables have a name here because a user could pass in an array
16
+ of Tables that don't have a name because they are using the V2 syntax.
17
+ Therefore, 'convertToClassicTables' won't be called on the tables resulting in a runtime error.
18
+ */
19
+ for (const table of tables) {
20
+ if (table.name === '') {
21
+ throw new Error("It appears you are trying to create a new Schema with an array instead of an object. Passing in an object instead of an array into 'new Schema()' may resolve your issue.");
22
+ }
23
+ }
14
24
  this.tables = tables;
15
25
  }
16
26
  else {
package/lib/index.d.ts CHANGED
@@ -4,6 +4,8 @@ export * from './client/SQLOpenFactory.js';
4
4
  export * from './client/connection/PowerSyncBackendConnector.js';
5
5
  export * from './client/connection/PowerSyncCredentials.js';
6
6
  export * from './client/sync/bucket/BucketStorageAdapter.js';
7
+ export { runOnSchemaChange } from './client/runOnSchemaChange.js';
8
+ export { CompilableQueryWatchHandler, compilableQueryWatch } from './client/compilableQueryWatch.js';
7
9
  export { UpdateType, CrudEntry, OpId } from './client/sync/bucket/CrudEntry.js';
8
10
  export * from './client/sync/bucket/SqliteBucketStorage.js';
9
11
  export * from './client/sync/bucket/CrudBatch.js';
package/lib/index.js CHANGED
@@ -4,6 +4,8 @@ export * from './client/SQLOpenFactory.js';
4
4
  export * from './client/connection/PowerSyncBackendConnector.js';
5
5
  export * from './client/connection/PowerSyncCredentials.js';
6
6
  export * from './client/sync/bucket/BucketStorageAdapter.js';
7
+ export { runOnSchemaChange } from './client/runOnSchemaChange.js';
8
+ export { compilableQueryWatch } from './client/compilableQueryWatch.js';
7
9
  export { UpdateType, CrudEntry } from './client/sync/bucket/CrudEntry.js';
8
10
  export * from './client/sync/bucket/SqliteBucketStorage.js';
9
11
  export * from './client/sync/bucket/CrudBatch.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powersync/common",
3
- "version": "0.0.0-dev-20241107150304",
3
+ "version": "0.0.0-dev-20250207081035",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"
@@ -59,7 +59,7 @@
59
59
  "scripts": {
60
60
  "build": "tsc -b && rollup -c rollup.config.mjs",
61
61
  "build:prod": "tsc -b --sourceMap false && rollup -c rollup.config.mjs --sourceMap false",
62
- "clean": "rm -rf lib dist tsconfig.tsbuildinfo",
62
+ "clean": "rm -rf lib dist tsconfig.tsbuildinfo node_modules",
63
63
  "test": "vitest"
64
64
  }
65
65
  }