@powersync/common 1.32.0 → 1.33.1

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.
@@ -0,0 +1,53 @@
1
+ import { StreamingSyncRequest } from './streaming-sync-types.js';
2
+ /**
3
+ * An internal instruction emitted by the sync client in the core extension in response to the JS
4
+ * SDK passing sync data into the extension.
5
+ */
6
+ export type Instruction = {
7
+ LogLine: LogLine;
8
+ } | {
9
+ UpdateSyncStatus: UpdateSyncStatus;
10
+ } | {
11
+ EstablishSyncStream: EstablishSyncStream;
12
+ } | {
13
+ FetchCredentials: FetchCredentials;
14
+ } | {
15
+ CloseSyncStream: any;
16
+ } | {
17
+ FlushFileSystem: any;
18
+ } | {
19
+ DidCompleteSync: any;
20
+ };
21
+ export interface LogLine {
22
+ severity: 'DEBUG' | 'INFO' | 'WARNING';
23
+ line: string;
24
+ }
25
+ export interface EstablishSyncStream {
26
+ request: StreamingSyncRequest;
27
+ }
28
+ export interface UpdateSyncStatus {
29
+ status: CoreSyncStatus;
30
+ }
31
+ export interface CoreSyncStatus {
32
+ connected: boolean;
33
+ connecting: boolean;
34
+ priority_status: SyncPriorityStatus[];
35
+ downloading: DownloadProgress | null;
36
+ }
37
+ export interface SyncPriorityStatus {
38
+ priority: number;
39
+ last_synced_at: number | number;
40
+ has_synced: boolean | null;
41
+ }
42
+ export interface DownloadProgress {
43
+ buckets: Record<string, BucketProgress>;
44
+ }
45
+ export interface BucketProgress {
46
+ priority: number;
47
+ at_last: number;
48
+ since_last: number;
49
+ target_count: number;
50
+ }
51
+ export interface FetchCredentials {
52
+ did_expire: boolean;
53
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,10 +1,6 @@
1
+ import { BucketProgress } from 'src/client/sync/stream/core-instruction.js';
1
2
  /** @internal */
2
- export type InternalProgressInformation = Record<string, {
3
- priority: number;
4
- atLast: number;
5
- sinceLast: number;
6
- targetCount: number;
7
- }>;
3
+ export type InternalProgressInformation = Record<string, BucketProgress>;
8
4
  /**
9
5
  * @internal The priority used by the core extension to indicate that a full sync was completed.
10
6
  */
@@ -46,8 +46,8 @@ export class SyncProgress {
46
46
  for (const progress of Object.values(this.internal)) {
47
47
  // Include higher-priority buckets, which are represented by lower numbers.
48
48
  if (progress.priority <= priority) {
49
- downloaded += progress.sinceLast;
50
- total += progress.targetCount - progress.atLast;
49
+ downloaded += progress.since_last;
50
+ total += progress.target_count - progress.at_last;
51
51
  }
52
52
  }
53
53
  let progress = total == 0 ? 0.0 : downloaded / total;
@@ -1,6 +1,7 @@
1
1
  import { ILogger } from 'js-logger';
2
2
  import { BaseListener, BaseObserver } from './BaseObserver.js';
3
- export type DataStreamOptions = {
3
+ export type DataStreamOptions<ParsedData, SourceData> = {
4
+ mapLine?: (line: SourceData) => ParsedData;
4
5
  /**
5
6
  * Close the stream if any consumer throws an error
6
7
  */
@@ -28,13 +29,15 @@ export declare const DEFAULT_PRESSURE_LIMITS: {
28
29
  * native JS streams or async iterators.
29
30
  * This is handy for environments such as React Native which need polyfills for the above.
30
31
  */
31
- export declare class DataStream<Data extends any = any> extends BaseObserver<DataStreamListener<Data>> {
32
- protected options?: DataStreamOptions | undefined;
33
- dataQueue: Data[];
32
+ export declare class DataStream<ParsedData, SourceData = any> extends BaseObserver<DataStreamListener<ParsedData>> {
33
+ protected options?: DataStreamOptions<ParsedData, SourceData> | undefined;
34
+ dataQueue: SourceData[];
34
35
  protected isClosed: boolean;
35
36
  protected processingPromise: Promise<void> | null;
37
+ protected notifyDataAdded: (() => void) | null;
36
38
  protected logger: ILogger;
37
- constructor(options?: DataStreamOptions | undefined);
39
+ protected mapLine: (line: SourceData) => ParsedData;
40
+ constructor(options?: DataStreamOptions<ParsedData, SourceData> | undefined);
38
41
  get highWatermark(): number;
39
42
  get lowWatermark(): number;
40
43
  get closed(): boolean;
@@ -42,22 +45,18 @@ export declare class DataStream<Data extends any = any> extends BaseObserver<Dat
42
45
  /**
43
46
  * Enqueues data for the consumers to read
44
47
  */
45
- enqueueData(data: Data): void;
48
+ enqueueData(data: SourceData): void;
46
49
  /**
47
50
  * Reads data once from the data stream
48
51
  * @returns a Data payload or Null if the stream closed.
49
52
  */
50
- read(): Promise<Data | null>;
53
+ read(): Promise<ParsedData | null>;
51
54
  /**
52
55
  * Executes a callback for each data item in the stream
53
56
  */
54
- forEach(callback: DataStreamCallback<Data>): () => void;
55
- protected processQueue(): Promise<void>;
56
- /**
57
- * Creates a new data stream which is a map of the original
58
- */
59
- map<ReturnData>(callback: (data: Data) => ReturnData): DataStream<ReturnData>;
57
+ forEach(callback: DataStreamCallback<ParsedData>): () => void;
58
+ protected processQueue(): Promise<void> | undefined;
60
59
  protected hasDataReader(): boolean;
61
60
  protected _processQueue(): Promise<void>;
62
- protected iterateAsyncErrored(cb: (l: BaseListener) => Promise<void>): Promise<void>;
61
+ protected iterateAsyncErrored(cb: (l: Partial<DataStreamListener<ParsedData>>) => Promise<void>): Promise<void>;
63
62
  }
@@ -14,13 +14,16 @@ export class DataStream extends BaseObserver {
14
14
  dataQueue;
15
15
  isClosed;
16
16
  processingPromise;
17
+ notifyDataAdded;
17
18
  logger;
19
+ mapLine;
18
20
  constructor(options) {
19
21
  super();
20
22
  this.options = options;
21
23
  this.processingPromise = null;
22
24
  this.isClosed = false;
23
25
  this.dataQueue = [];
26
+ this.mapLine = options?.mapLine ?? ((line) => line);
24
27
  this.logger = options?.logger ?? Logger.get('DataStream');
25
28
  if (options?.closeOnError) {
26
29
  const l = this.registerListener({
@@ -56,6 +59,7 @@ export class DataStream extends BaseObserver {
56
59
  throw new Error('Cannot enqueue data into closed stream.');
57
60
  }
58
61
  this.dataQueue.push(data);
62
+ this.notifyDataAdded?.();
59
63
  this.processQueue();
60
64
  }
61
65
  /**
@@ -96,10 +100,20 @@ export class DataStream extends BaseObserver {
96
100
  data: callback
97
101
  });
98
102
  }
99
- async processQueue() {
103
+ processQueue() {
100
104
  if (this.processingPromise) {
101
105
  return;
102
106
  }
107
+ const promise = (this.processingPromise = this._processQueue());
108
+ promise.finally(() => {
109
+ return (this.processingPromise = null);
110
+ });
111
+ return promise;
112
+ }
113
+ hasDataReader() {
114
+ return Array.from(this.listeners.values()).some((l) => !!l.data);
115
+ }
116
+ async _processQueue() {
103
117
  /**
104
118
  * Allow listeners to mutate the queue before processing.
105
119
  * This allows for operations such as dropping or compressing data
@@ -108,47 +122,31 @@ export class DataStream extends BaseObserver {
108
122
  if (this.dataQueue.length >= this.highWatermark) {
109
123
  await this.iterateAsyncErrored(async (l) => l.highWater?.());
110
124
  }
111
- return (this.processingPromise = this._processQueue());
112
- }
113
- /**
114
- * Creates a new data stream which is a map of the original
115
- */
116
- map(callback) {
117
- const stream = new DataStream(this.options);
118
- const l = this.registerListener({
119
- data: async (data) => {
120
- stream.enqueueData(callback(data));
121
- },
122
- closed: () => {
123
- stream.close();
124
- l?.();
125
- }
126
- });
127
- return stream;
128
- }
129
- hasDataReader() {
130
- return Array.from(this.listeners.values()).some((l) => !!l.data);
131
- }
132
- async _processQueue() {
133
125
  if (this.isClosed || !this.hasDataReader()) {
134
- Promise.resolve().then(() => (this.processingPromise = null));
135
126
  return;
136
127
  }
137
128
  if (this.dataQueue.length) {
138
129
  const data = this.dataQueue.shift();
139
- await this.iterateAsyncErrored(async (l) => l.data?.(data));
130
+ const mapped = this.mapLine(data);
131
+ await this.iterateAsyncErrored(async (l) => l.data?.(mapped));
140
132
  }
141
133
  if (this.dataQueue.length <= this.lowWatermark) {
142
- await this.iterateAsyncErrored(async (l) => l.lowWater?.());
134
+ const dataAdded = new Promise((resolve) => {
135
+ this.notifyDataAdded = resolve;
136
+ });
137
+ await Promise.race([this.iterateAsyncErrored(async (l) => l.lowWater?.()), dataAdded]);
138
+ this.notifyDataAdded = null;
143
139
  }
144
- this.processingPromise = null;
145
- if (this.dataQueue.length) {
140
+ if (this.dataQueue.length > 0) {
146
141
  // Next tick
147
142
  setTimeout(() => this.processQueue());
148
143
  }
149
144
  }
150
145
  async iterateAsyncErrored(cb) {
151
- for (let i of Array.from(this.listeners.values())) {
146
+ // Important: We need to copy the listeners, as calling a listener could result in adding another
147
+ // listener, resulting in infinite loops.
148
+ const listeners = Array.from(this.listeners.values());
149
+ for (let i of listeners) {
152
150
  try {
153
151
  await cb(i);
154
152
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powersync/common",
3
- "version": "1.32.0",
3
+ "version": "1.33.1",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"
@@ -13,8 +13,9 @@
13
13
  "exports": {
14
14
  ".": {
15
15
  "import": "./dist/bundle.mjs",
16
- "default": "./dist/bundle.mjs",
17
- "types": "./lib/index.d.ts"
16
+ "require": "./dist/bundle.cjs",
17
+ "types": "./lib/index.d.ts",
18
+ "default": "./dist/bundle.mjs"
18
19
  }
19
20
  },
20
21
  "author": "JOURNEYAPPS",
@@ -45,7 +46,6 @@
45
46
  "async-mutex": "^0.4.0",
46
47
  "bson": "^6.6.0",
47
48
  "buffer": "^6.0.3",
48
- "can-ndjson-stream": "^1.0.2",
49
49
  "cross-fetch": "^4.0.0",
50
50
  "event-iterator": "^2.0.0",
51
51
  "rollup": "4.14.3",