@powersync/common 0.0.0-dev-20260311081226 → 0.0.0-dev-20260414110516

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.
Files changed (68) hide show
  1. package/dist/bundle.cjs +775 -485
  2. package/dist/bundle.cjs.map +1 -1
  3. package/dist/bundle.mjs +769 -481
  4. package/dist/bundle.mjs.map +1 -1
  5. package/dist/bundle.node.cjs +773 -484
  6. package/dist/bundle.node.cjs.map +1 -1
  7. package/dist/bundle.node.mjs +767 -480
  8. package/dist/bundle.node.mjs.map +1 -1
  9. package/dist/index.d.cts +175 -94
  10. package/lib/attachments/AttachmentQueue.d.ts +10 -4
  11. package/lib/attachments/AttachmentQueue.js +10 -4
  12. package/lib/attachments/AttachmentQueue.js.map +1 -1
  13. package/lib/attachments/AttachmentService.js +2 -3
  14. package/lib/attachments/AttachmentService.js.map +1 -1
  15. package/lib/attachments/SyncingService.d.ts +2 -1
  16. package/lib/attachments/SyncingService.js +4 -5
  17. package/lib/attachments/SyncingService.js.map +1 -1
  18. package/lib/client/AbstractPowerSyncDatabase.d.ts +5 -1
  19. package/lib/client/AbstractPowerSyncDatabase.js +9 -5
  20. package/lib/client/AbstractPowerSyncDatabase.js.map +1 -1
  21. package/lib/client/sync/stream/AbstractRemote.d.ts +29 -8
  22. package/lib/client/sync/stream/AbstractRemote.js +154 -177
  23. package/lib/client/sync/stream/AbstractRemote.js.map +1 -1
  24. package/lib/client/sync/stream/AbstractStreamingSyncImplementation.d.ts +1 -0
  25. package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +69 -88
  26. package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js.map +1 -1
  27. package/lib/client/triggers/TriggerManager.d.ts +12 -1
  28. package/lib/client/triggers/TriggerManagerImpl.d.ts +2 -2
  29. package/lib/client/triggers/TriggerManagerImpl.js +3 -2
  30. package/lib/client/triggers/TriggerManagerImpl.js.map +1 -1
  31. package/lib/db/DBAdapter.d.ts +55 -9
  32. package/lib/db/DBAdapter.js +126 -0
  33. package/lib/db/DBAdapter.js.map +1 -1
  34. package/lib/index.d.ts +1 -1
  35. package/lib/index.js +0 -1
  36. package/lib/index.js.map +1 -1
  37. package/lib/utils/async.d.ts +0 -9
  38. package/lib/utils/async.js +0 -9
  39. package/lib/utils/async.js.map +1 -1
  40. package/lib/utils/mutex.d.ts +47 -5
  41. package/lib/utils/mutex.js +146 -21
  42. package/lib/utils/mutex.js.map +1 -1
  43. package/lib/utils/queue.d.ts +16 -0
  44. package/lib/utils/queue.js +42 -0
  45. package/lib/utils/queue.js.map +1 -0
  46. package/lib/utils/stream_transform.d.ts +39 -0
  47. package/lib/utils/stream_transform.js +206 -0
  48. package/lib/utils/stream_transform.js.map +1 -0
  49. package/package.json +9 -8
  50. package/src/attachments/AttachmentQueue.ts +10 -4
  51. package/src/attachments/AttachmentService.ts +2 -3
  52. package/src/attachments/README.md +6 -4
  53. package/src/attachments/SyncingService.ts +4 -5
  54. package/src/client/AbstractPowerSyncDatabase.ts +9 -5
  55. package/src/client/sync/stream/AbstractRemote.ts +182 -206
  56. package/src/client/sync/stream/AbstractStreamingSyncImplementation.ts +82 -83
  57. package/src/client/triggers/TriggerManager.ts +13 -1
  58. package/src/client/triggers/TriggerManagerImpl.ts +4 -2
  59. package/src/db/DBAdapter.ts +167 -9
  60. package/src/index.ts +1 -1
  61. package/src/utils/async.ts +0 -11
  62. package/src/utils/mutex.ts +184 -26
  63. package/src/utils/queue.ts +48 -0
  64. package/src/utils/stream_transform.ts +252 -0
  65. package/lib/utils/DataStream.d.ts +0 -62
  66. package/lib/utils/DataStream.js +0 -169
  67. package/lib/utils/DataStream.js.map +0 -1
  68. package/src/utils/DataStream.ts +0 -222
@@ -4,7 +4,6 @@ import { InternalProgressInformation } from '../../../db/crud/SyncProgress.js';
4
4
  import { SyncStatus, SyncStatusOptions } from '../../../db/crud/SyncStatus.js';
5
5
  import { AbortOperation } from '../../../utils/AbortOperation.js';
6
6
  import { BaseListener, BaseObserver, BaseObserverInterface, Disposable } from '../../../utils/BaseObserver.js';
7
- import { DataStream } from '../../../utils/DataStream.js';
8
7
  import { throttleLeadingTrailing } from '../../../utils/async.js';
9
8
  import {
10
9
  BucketChecksum,
@@ -19,7 +18,6 @@ import { AbstractRemote, FetchStrategy, SyncStreamOptions } from './AbstractRemo
19
18
  import { EstablishSyncStream, Instruction, coreStatusToJs } from './core-instruction.js';
20
19
  import {
21
20
  BucketRequest,
22
- CrudUploadNotification,
23
21
  StreamingSyncLine,
24
22
  StreamingSyncLineOrCrudUploadComplete,
25
23
  StreamingSyncRequestParameterType,
@@ -30,6 +28,15 @@ import {
30
28
  isStreamingSyncCheckpointPartiallyComplete,
31
29
  isStreamingSyncData
32
30
  } from './streaming-sync-types.js';
31
+ import {
32
+ extractBsonObjects,
33
+ extractJsonLines,
34
+ injectable,
35
+ InjectableIterator,
36
+ map,
37
+ SimpleAsyncIterator
38
+ } from '../../../utils/stream_transform.js';
39
+ import type { BSON } from 'bson';
33
40
 
34
41
  export enum LockType {
35
42
  CRUD = 'crud',
@@ -682,6 +689,27 @@ The next upload iteration will be delayed.`);
682
689
  });
683
690
  }
684
691
 
692
+ private async receiveSyncLines(data: {
693
+ options: SyncStreamOptions;
694
+ connection: RequiredPowerSyncConnectionOptions;
695
+ bson?: typeof BSON;
696
+ }): Promise<SimpleAsyncIterator<Uint8Array | string>> {
697
+ const { options, connection, bson } = data;
698
+ const remote = this.options.remote;
699
+
700
+ if (connection.connectionMethod == SyncStreamConnectionMethod.HTTP) {
701
+ return await remote.fetchStream(options);
702
+ } else {
703
+ return await this.options.remote.socketStreamRaw(
704
+ {
705
+ ...options,
706
+ ...{ fetchStrategy: connection.fetchStrategy }
707
+ },
708
+ bson
709
+ );
710
+ }
711
+ }
712
+
685
713
  private async legacyStreamingSyncIteration(signal: AbortSignal, resolvedOptions: RequiredPowerSyncConnectionOptions) {
686
714
  const rawTables = resolvedOptions.serializedSchema?.raw_tables;
687
715
  if (rawTables != null && rawTables.length) {
@@ -717,46 +745,31 @@ The next upload iteration will be delayed.`);
717
745
  }
718
746
  };
719
747
 
720
- let stream: DataStream<StreamingSyncLineOrCrudUploadComplete>;
721
- if (resolvedOptions?.connectionMethod == SyncStreamConnectionMethod.HTTP) {
722
- stream = await this.options.remote.postStreamRaw(syncOptions, (line: string | CrudUploadNotification) => {
748
+ const bson = await this.options.remote.getBSON();
749
+ const source = await this.receiveSyncLines({
750
+ options: syncOptions,
751
+ connection: resolvedOptions,
752
+ bson
753
+ });
754
+ const stream: InjectableIterator<StreamingSyncLineOrCrudUploadComplete> = injectable(
755
+ map(source, (line) => {
723
756
  if (typeof line == 'string') {
724
757
  return JSON.parse(line) as StreamingSyncLine;
725
758
  } else {
726
- // Directly enqueued by us
727
- return line;
759
+ return bson.deserialize(line) as StreamingSyncLine;
728
760
  }
729
- });
730
- } else {
731
- const bson = await this.options.remote.getBSON();
732
- stream = await this.options.remote.socketStreamRaw(
733
- {
734
- ...syncOptions,
735
- ...{ fetchStrategy: resolvedOptions.fetchStrategy }
736
- },
737
- (payload: Uint8Array | CrudUploadNotification) => {
738
- if (payload instanceof Uint8Array) {
739
- return bson.deserialize(payload) as StreamingSyncLine;
740
- } else {
741
- // Directly enqueued by us
742
- return payload;
743
- }
744
- },
745
- bson
746
- );
747
- }
761
+ })
762
+ );
748
763
 
749
764
  this.logger.debug('Stream established. Processing events');
750
765
 
751
766
  this.notifyCompletedUploads = () => {
752
- if (!stream.closed) {
753
- stream.enqueueData({ crud_upload_completed: null });
754
- }
767
+ stream.inject({ crud_upload_completed: null });
755
768
  };
756
769
 
757
- while (!stream.closed) {
758
- const line = await stream.read();
759
- if (!line) {
770
+ while (true) {
771
+ const { value: line, done } = await stream.next();
772
+ if (done) {
760
773
  // The stream has closed while waiting
761
774
  return;
762
775
  }
@@ -942,6 +955,11 @@ The next upload iteration will be delayed.`);
942
955
  const syncImplementation = this;
943
956
  const adapter = this.options.adapter;
944
957
  const remote = this.options.remote;
958
+ const controller = new AbortController();
959
+ const abort = () => {
960
+ return controller.abort(signal.reason);
961
+ };
962
+ signal.addEventListener('abort', abort);
945
963
  let receivingLines: Promise<void> | null = null;
946
964
  let hadSyncLine = false;
947
965
  let hideDisconnectOnRestart = false;
@@ -949,64 +967,53 @@ The next upload iteration will be delayed.`);
949
967
  if (signal.aborted) {
950
968
  throw new AbortOperation('Connection request has been aborted');
951
969
  }
952
- const abortController = new AbortController();
953
- signal.addEventListener('abort', () => abortController.abort());
954
970
 
955
971
  // Pending sync lines received from the service, as well as local events that trigger a powersync_control
956
972
  // invocation (local events include refreshed tokens and completed uploads).
957
973
  // This is a single data stream so that we can handle all control calls from a single place.
958
- let controlInvocations: DataStream<EnqueuedCommand, Uint8Array | EnqueuedCommand> | null = null;
974
+ let controlInvocations: InjectableIterator<EnqueuedCommand> | null = null;
959
975
 
960
976
  async function connect(instr: EstablishSyncStream) {
961
977
  const syncOptions: SyncStreamOptions = {
962
978
  path: '/sync/stream',
963
- abortSignal: abortController.signal,
979
+ abortSignal: controller.signal,
964
980
  data: instr.request
965
981
  };
966
982
 
967
- if (resolvedOptions.connectionMethod == SyncStreamConnectionMethod.HTTP) {
968
- controlInvocations = await remote.postStreamRaw(syncOptions, (line: string | EnqueuedCommand) => {
969
- if (typeof line == 'string') {
970
- return {
971
- command: PowerSyncControlCommand.PROCESS_TEXT_LINE,
972
- payload: line
973
- };
974
- } else {
975
- // Directly enqueued by us
976
- return line;
977
- }
978
- });
979
- } else {
980
- controlInvocations = await remote.socketStreamRaw(
981
- {
982
- ...syncOptions,
983
- fetchStrategy: resolvedOptions.fetchStrategy
984
- },
985
- (payload: Uint8Array | EnqueuedCommand) => {
986
- if (payload instanceof Uint8Array) {
983
+ controlInvocations = injectable(
984
+ map(
985
+ await syncImplementation.receiveSyncLines({
986
+ options: syncOptions,
987
+ connection: resolvedOptions
988
+ }),
989
+ (line) => {
990
+ if (typeof line == 'string') {
987
991
  return {
988
- command: PowerSyncControlCommand.PROCESS_BSON_LINE,
989
- payload: payload
992
+ command: PowerSyncControlCommand.PROCESS_TEXT_LINE,
993
+ payload: line
990
994
  };
991
995
  } else {
992
- // Directly enqueued by us
993
- return payload;
996
+ return {
997
+ command: PowerSyncControlCommand.PROCESS_BSON_LINE,
998
+ payload: line
999
+ };
994
1000
  }
995
1001
  }
996
- );
997
- }
1002
+ )
1003
+ );
998
1004
 
999
1005
  // The rust client will set connected: true after the first sync line because that's when it gets invoked, but
1000
1006
  // we're already connected here and can report that.
1001
1007
  syncImplementation.updateSyncStatus({ connected: true });
1002
1008
 
1003
1009
  try {
1004
- while (!controlInvocations.closed) {
1005
- const line = await controlInvocations.read();
1006
- if (line == null) {
1007
- return;
1010
+ while (true) {
1011
+ let event = await controlInvocations.next();
1012
+ if (event.done) {
1013
+ break;
1008
1014
  }
1009
1015
 
1016
+ const line = event.value;
1010
1017
  await control(line.command, line.payload);
1011
1018
 
1012
1019
  if (!hadSyncLine) {
@@ -1015,12 +1022,8 @@ The next upload iteration will be delayed.`);
1015
1022
  }
1016
1023
  }
1017
1024
  } finally {
1018
- const activeInstructions = controlInvocations;
1019
- // We concurrently add events to the active data stream when e.g. a CRUD upload is completed or a token is
1020
- // refreshed. That would throw after closing (and we can't handle those events either way), so set this back
1021
- // to null.
1022
- controlInvocations = null;
1023
- await activeInstructions.close();
1025
+ abort();
1026
+ signal.removeEventListener('abort', abort);
1024
1027
  }
1025
1028
  }
1026
1029
 
@@ -1072,7 +1075,7 @@ The next upload iteration will be delayed.`);
1072
1075
  // Restart iteration after the credentials have been refreshed.
1073
1076
  remote.fetchCredentials().then(
1074
1077
  (_) => {
1075
- controlInvocations?.enqueueData({ command: PowerSyncControlCommand.NOTIFY_TOKEN_REFRESHED });
1078
+ controlInvocations?.inject({ command: PowerSyncControlCommand.NOTIFY_TOKEN_REFRESHED });
1076
1079
  },
1077
1080
  (err) => {
1078
1081
  syncImplementation.logger.warn('Could not prefetch credentials', err);
@@ -1080,7 +1083,7 @@ The next upload iteration will be delayed.`);
1080
1083
  );
1081
1084
  }
1082
1085
  } else if ('CloseSyncStream' in instruction) {
1083
- abortController.abort();
1086
+ controller.abort();
1084
1087
  hideDisconnectOnRestart = instruction.CloseSyncStream.hide_disconnect;
1085
1088
  } else if ('FlushFileSystem' in instruction) {
1086
1089
  // Not necessary on JS platforms.
@@ -1113,17 +1116,13 @@ The next upload iteration will be delayed.`);
1113
1116
  await control(PowerSyncControlCommand.START, JSON.stringify(options));
1114
1117
 
1115
1118
  this.notifyCompletedUploads = () => {
1116
- if (controlInvocations && !controlInvocations?.closed) {
1117
- controlInvocations.enqueueData({ command: PowerSyncControlCommand.NOTIFY_CRUD_UPLOAD_COMPLETED });
1118
- }
1119
+ controlInvocations?.inject({ command: PowerSyncControlCommand.NOTIFY_CRUD_UPLOAD_COMPLETED });
1119
1120
  };
1120
1121
  this.handleActiveStreamsChange = () => {
1121
- if (controlInvocations && !controlInvocations?.closed) {
1122
- controlInvocations.enqueueData({
1123
- command: PowerSyncControlCommand.UPDATE_SUBSCRIPTIONS,
1124
- payload: JSON.stringify(this.activeStreams)
1125
- });
1126
- }
1122
+ controlInvocations?.inject({
1123
+ command: PowerSyncControlCommand.UPDATE_SUBSCRIPTIONS,
1124
+ payload: JSON.stringify(this.activeStreams)
1125
+ });
1127
1126
  };
1128
1127
  await receivingLines;
1129
1128
  } finally {
@@ -224,14 +224,26 @@ export interface CreateDiffTriggerOptions extends BaseCreateDiffTriggerOptions {
224
224
  */
225
225
  destination: string;
226
226
 
227
+ /**
228
+ * Context to use for the setup operation.
229
+ * This is useful for when the setup operation needs to be executed in a specific context.
230
+ */
227
231
  setupContext?: LockContext;
228
232
  }
229
233
 
234
+ /**
235
+ * @experimental
236
+ * Options for {@link TriggerRemoveCallback}.
237
+ */
238
+ export interface TriggerRemoveCallbackOptions {
239
+ context?: LockContext;
240
+ }
241
+
230
242
  /**
231
243
  * @experimental
232
244
  * Callback to drop a trigger after it has been created.
233
245
  */
234
- export type TriggerRemoveCallback = (context?: LockContext) => Promise<void>;
246
+ export type TriggerRemoveCallback = (options?: TriggerRemoveCallbackOptions) => Promise<void>;
235
247
  /**
236
248
  * @experimental
237
249
  * Options for {@link TriggerDiffHandlerContext#withDiff}.
@@ -9,6 +9,7 @@ import {
9
9
  TriggerManager,
10
10
  TriggerManagerConfig,
11
11
  TriggerRemoveCallback,
12
+ TriggerRemoveCallbackOptions,
12
13
  WithDiffOptions
13
14
  } from './TriggerManager.js';
14
15
 
@@ -269,7 +270,8 @@ export class TriggerManagerImpl implements TriggerManager {
269
270
  * we need to ensure we can cleanup the created resources.
270
271
  * We unfortunately cannot rely on transaction rollback.
271
272
  */
272
- const cleanup = async (context?: LockContext) => {
273
+ const cleanup = async (options?: TriggerRemoveCallbackOptions) => {
274
+ const { context } = options ?? {};
273
275
  disposeWarningListener();
274
276
  const doCleanup = async (tx: LockContext) => {
275
277
  await this.removeTriggers(tx, triggerIds);
@@ -374,7 +376,7 @@ export class TriggerManagerImpl implements TriggerManager {
374
376
  return cleanup;
375
377
  } catch (error) {
376
378
  try {
377
- await cleanup();
379
+ await cleanup(setupContext ? { context: setupContext } : undefined);
378
380
  } catch (cleanupError) {
379
381
  throw new AggregateError([error, cleanupError], 'Error during operation and cleanup');
380
382
  }
@@ -16,7 +16,13 @@ import { BaseListener, BaseObserverInterface } from '../utils/BaseObserver.js';
16
16
  export type QueryResult = {
17
17
  /** Represents the auto-generated row id if applicable. */
18
18
  insertId?: number;
19
- /** Number of affected rows if result of a update query. */
19
+ /**
20
+ * Number of affected rows reported by SQLite for a write query.
21
+ *
22
+ * When using the default client-side [JSON-based view system](https://docs.powersync.com/architecture/client-architecture#client-side-schema-and-sqlite-database-structure),
23
+ * `rowsAffected` may be `0` for successful `UPDATE` and `DELETE` statements.
24
+ * Use a `RETURNING` clause and inspect `rows` when you need to confirm which rows changed.
25
+ */
20
26
  rowsAffected: number;
21
27
  /** if status is undefined or 0 this object will contain the query results */
22
28
  rows?: {
@@ -41,7 +47,7 @@ export interface DBGetUtils {
41
47
  get<T>(sql: string, parameters?: any[]): Promise<T>;
42
48
  }
43
49
 
44
- export interface LockContext extends DBGetUtils {
50
+ export interface SqlExecutor {
45
51
  /** Execute a single write statement. */
46
52
  execute: (query: string, params?: any[] | undefined) => Promise<QueryResult>;
47
53
  /**
@@ -59,6 +65,61 @@ export interface LockContext extends DBGetUtils {
59
65
  * ```[ { id: '33', name: 'list 1', content: 'Post content', list_id: '1' } ]```
60
66
  */
61
67
  executeRaw: (query: string, params?: any[] | undefined) => Promise<any[][]>;
68
+
69
+ executeBatch: (query: string, params?: any[][]) => Promise<QueryResult>;
70
+ }
71
+
72
+ export interface LockContext extends SqlExecutor, DBGetUtils {}
73
+
74
+ /**
75
+ * Implements {@link DBGetUtils} on a {@link SqlRunner}.
76
+ */
77
+ export function DBGetUtilsDefaultMixin<TBase extends new (...args: any[]) => Omit<SqlExecutor, 'executeBatch'>>(
78
+ Base: TBase
79
+ ) {
80
+ return class extends Base implements DBGetUtils, SqlExecutor {
81
+ async getAll<T>(sql: string, parameters?: any[]): Promise<T[]> {
82
+ const res = await this.execute(sql, parameters);
83
+ return res.rows?._array ?? [];
84
+ }
85
+
86
+ async getOptional<T>(sql: string, parameters?: any[]): Promise<T | null> {
87
+ const res = await this.execute(sql, parameters);
88
+ return res.rows?.item(0) ?? null;
89
+ }
90
+
91
+ async get<T>(sql: string, parameters?: any[]): Promise<T> {
92
+ const res = await this.execute(sql, parameters);
93
+ const first = res.rows?.item(0);
94
+ if (!first) {
95
+ throw new Error('Result set is empty');
96
+ }
97
+ return first;
98
+ }
99
+
100
+ async executeBatch(query: string, params: any[][] = []): Promise<QueryResult> {
101
+ // If this context can run batch statements natively, use that.
102
+ // @ts-ignore
103
+ if (super.executeBatch) {
104
+ // @ts-ignore
105
+ return super.executeBatch(query, params);
106
+ }
107
+
108
+ // Emulate executeBatch by running statements individually.
109
+ let lastInsertId: number | undefined;
110
+ let rowsAffected = 0;
111
+ for (const set of params) {
112
+ const result = await this.execute(query, set);
113
+ lastInsertId = result.insertId;
114
+ rowsAffected += result.rowsAffected;
115
+ }
116
+
117
+ return {
118
+ rowsAffected,
119
+ insertId: lastInsertId
120
+ };
121
+ }
122
+ };
62
123
  }
63
124
 
64
125
  export interface Transaction extends LockContext {
@@ -107,22 +168,119 @@ export interface DBLockOptions {
107
168
  timeoutMs?: number;
108
169
  }
109
170
 
110
- export interface DBAdapter extends BaseObserverInterface<DBAdapterListener>, DBGetUtils {
111
- close: () => void | Promise<void>;
112
- execute: (query: string, params?: any[]) => Promise<QueryResult>;
113
- executeRaw: (query: string, params?: any[]) => Promise<any[][]>;
114
- executeBatch: (query: string, params?: any[][]) => Promise<QueryResult>;
171
+ export interface ConnectionPool extends BaseObserverInterface<DBAdapterListener> {
115
172
  name: string;
173
+ close: () => void | Promise<void>;
116
174
  readLock: <T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions) => Promise<T>;
117
- readTransaction: <T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions) => Promise<T>;
118
175
  writeLock: <T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions) => Promise<T>;
119
- writeTransaction: <T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions) => Promise<T>;
176
+
120
177
  /**
121
178
  * This method refreshes the schema information across all connections. This is for advanced use cases, and should generally not be needed.
122
179
  */
123
180
  refreshSchema: () => Promise<void>;
124
181
  }
125
182
 
183
+ export interface DBAdapter extends ConnectionPool, SqlExecutor, DBGetUtils {
184
+ readTransaction: <T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions) => Promise<T>;
185
+ writeTransaction: <T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions) => Promise<T>;
186
+ }
187
+
188
+ /**
189
+ * A mixin to implement {@link DBAdapter} by delegating to {@link ConnectionPool.readLock} and
190
+ * {@link ConnectionPool.writeLock}.
191
+ */
192
+ export function DBAdapterDefaultMixin<TBase extends new (...args: any[]) => ConnectionPool>(Base: TBase) {
193
+ return class extends Base implements DBAdapter {
194
+ readTransaction<T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions): Promise<T> {
195
+ return this.readLock((ctx) => TransactionImplementation.runWith(ctx, fn), options);
196
+ }
197
+
198
+ writeTransaction<T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions): Promise<T> {
199
+ return this.writeLock((ctx) => TransactionImplementation.runWith(ctx, fn), options);
200
+ }
201
+
202
+ getAll<T>(sql: string, parameters?: any[]): Promise<T[]> {
203
+ return this.readLock((ctx) => ctx.getAll(sql, parameters));
204
+ }
205
+
206
+ getOptional<T>(sql: string, parameters?: any[]): Promise<T | null> {
207
+ return this.readLock((ctx) => ctx.getOptional(sql, parameters));
208
+ }
209
+
210
+ get<T>(sql: string, parameters?: any[]): Promise<T> {
211
+ return this.readLock((ctx) => ctx.get(sql, parameters));
212
+ }
213
+
214
+ execute(query: string, params?: any[]): Promise<QueryResult> {
215
+ return this.writeLock((ctx) => ctx.execute(query, params));
216
+ }
217
+
218
+ executeRaw(query: string, params?: any[]): Promise<any[][]> {
219
+ return this.writeLock((ctx) => ctx.executeRaw(query, params));
220
+ }
221
+
222
+ executeBatch(query: string, params?: any[][]): Promise<QueryResult> {
223
+ return this.writeTransaction((tx) => tx.executeBatch(query, params));
224
+ }
225
+ };
226
+ }
227
+
228
+ class BaseTransaction implements SqlExecutor {
229
+ private finalized = false;
230
+
231
+ constructor(private inner: SqlExecutor) {}
232
+
233
+ async commit(): Promise<QueryResult> {
234
+ if (this.finalized) {
235
+ return { rowsAffected: 0 };
236
+ }
237
+ this.finalized = true;
238
+ return this.inner.execute('COMMIT');
239
+ }
240
+
241
+ async rollback(): Promise<QueryResult> {
242
+ if (this.finalized) {
243
+ return { rowsAffected: 0 };
244
+ }
245
+ this.finalized = true;
246
+ return this.inner.execute('ROLLBACK');
247
+ }
248
+
249
+ execute(query: string, params?: any[] | undefined): Promise<QueryResult> {
250
+ return this.inner.execute(query, params);
251
+ }
252
+
253
+ executeRaw(query: string, params?: any[] | undefined): Promise<any[][]> {
254
+ return this.inner.executeRaw(query, params);
255
+ }
256
+
257
+ executeBatch(query: string, params?: any[][]): Promise<QueryResult> {
258
+ return this.inner.executeBatch(query, params);
259
+ }
260
+ }
261
+
262
+ class TransactionImplementation extends DBGetUtilsDefaultMixin(BaseTransaction) {
263
+ static async runWith<T>(ctx: LockContext, fn: (tx: Transaction) => Promise<T>): Promise<T> {
264
+ let tx = new TransactionImplementation(ctx);
265
+
266
+ try {
267
+ await ctx.execute('BEGIN IMMEDIATE');
268
+
269
+ const result = await fn(tx);
270
+ await tx.commit();
271
+ return result;
272
+ } catch (ex) {
273
+ try {
274
+ await tx.rollback();
275
+ } catch (ex2) {
276
+ // In rare cases, a rollback may fail.
277
+ // Safe to ignore.
278
+ }
279
+ throw ex;
280
+ }
281
+ }
282
+ }
283
+
126
284
  export function isBatchedUpdateNotification(
127
285
  update: BatchedUpdateNotification | UpdateNotification
128
286
  ): update is BatchedUpdateNotification {
package/src/index.ts CHANGED
@@ -59,9 +59,9 @@ export * from './client/watched/WatchedQuery.js';
59
59
  export * from './utils/AbortOperation.js';
60
60
  export * from './utils/BaseObserver.js';
61
61
  export * from './utils/ControlledExecutor.js';
62
- export * from './utils/DataStream.js';
63
62
  export * from './utils/Logger.js';
64
63
  export * from './utils/mutex.js';
65
64
  export * from './utils/parseQuery.js';
65
+ export type { SimpleAsyncIterator } from './utils/stream_transform.js';
66
66
 
67
67
  export * from './types/types.js';
@@ -1,14 +1,3 @@
1
- /**
2
- * A ponyfill for `Symbol.asyncIterator` that is compatible with the
3
- * [recommended polyfill](https://github.com/Azure/azure-sdk-for-js/blob/%40azure/core-asynciterator-polyfill_1.0.2/sdk/core/core-asynciterator-polyfill/src/index.ts#L4-L6)
4
- * we recommend for React Native.
5
- *
6
- * As long as we use this symbol (instead of `for await` and `async *`) in this package, we can be compatible with async
7
- * iterators without requiring them.
8
- */
9
- export const symbolAsyncIterator: typeof Symbol.asyncIterator =
10
- Symbol.asyncIterator ?? Symbol.for('Symbol.asyncIterator');
11
-
12
1
  /**
13
2
  * Throttle a function to be called at most once every "wait" milliseconds,
14
3
  * on the trailing edge.