@electric-sql/client 0.2.2

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,243 @@
1
+ type Value = string | number | boolean | bigint | null | Value[] | {
2
+ [key: string]: Value;
3
+ };
4
+ type Offset = `-1` | `${number}_${number}`;
5
+ interface Header {
6
+ [key: string]: Value;
7
+ }
8
+ type ControlMessage = {
9
+ headers: Header;
10
+ };
11
+ type ChangeMessage<T> = {
12
+ key: string;
13
+ value: T;
14
+ headers: Header & {
15
+ action: `insert` | `update` | `delete`;
16
+ };
17
+ offset: Offset;
18
+ };
19
+ type Message<T extends Value = {
20
+ [key: string]: Value;
21
+ }> = ControlMessage | ChangeMessage<T>;
22
+ type RegularColumn = {
23
+ type: string;
24
+ dims: number;
25
+ };
26
+ type VarcharColumn = {
27
+ type: `varchar`;
28
+ dims: number;
29
+ max_length?: number;
30
+ };
31
+ type BpcharColumn = {
32
+ type: `bpchar`;
33
+ dims: number;
34
+ length?: number;
35
+ };
36
+ type TimeColumn = {
37
+ type: `time` | `timetz` | `timestamp` | `timestamptz`;
38
+ dims: number;
39
+ precision?: number;
40
+ };
41
+ type IntervalColumn = {
42
+ type: `interval`;
43
+ dims: number;
44
+ fields?: `YEAR` | `MONTH` | `DAY` | `HOUR` | `MINUTE` | `YEAR TO MONTH` | `DAY TO HOUR` | `DAY TO MINUTE` | `DAY TO SECOND` | `HOUR TO MINUTE` | `HOUR TO SECOND` | `MINUTE TO SECOND`;
45
+ };
46
+ type IntervalColumnWithPrecision = {
47
+ type: `interval`;
48
+ dims: number;
49
+ precision?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
50
+ fields?: `SECOND`;
51
+ };
52
+ type BitColumn = {
53
+ type: `bit`;
54
+ dims: number;
55
+ length: number;
56
+ };
57
+ type NumericColumn = {
58
+ type: `numeric`;
59
+ dims: number;
60
+ precision?: number;
61
+ scale?: number;
62
+ };
63
+ type ColumnInfo = RegularColumn | VarcharColumn | BpcharColumn | TimeColumn | IntervalColumn | IntervalColumnWithPrecision | BitColumn | NumericColumn;
64
+ type Schema = {
65
+ [key: string]: ColumnInfo;
66
+ };
67
+ type TypedMessages<T extends Value = {
68
+ [key: string]: Value;
69
+ }> = {
70
+ messages: Array<Message<T>>;
71
+ schema: ColumnInfo;
72
+ };
73
+
74
+ type ParseFunction = (value: string, additionalInfo?: Omit<ColumnInfo, `type` | `dims`>) => Value;
75
+ type Parser = {
76
+ [key: string]: ParseFunction;
77
+ };
78
+
79
+ type ShapeData = Map<string, {
80
+ [key: string]: Value;
81
+ }>;
82
+ type ShapeChangedCallback = (value: ShapeData) => void;
83
+ interface BackoffOptions {
84
+ initialDelay: number;
85
+ maxDelay: number;
86
+ multiplier: number;
87
+ }
88
+ declare const BackoffDefaults: {
89
+ initialDelay: number;
90
+ maxDelay: number;
91
+ multiplier: number;
92
+ };
93
+ /**
94
+ * Options for constructing a ShapeStream.
95
+ */
96
+ interface ShapeStreamOptions {
97
+ /**
98
+ * The full URL to where the Shape is hosted. This can either be the Electric server
99
+ * directly or a proxy. E.g. for a local Electric instance, you might set `http://localhost:3000/v1/shape/foo`
100
+ */
101
+ url: string;
102
+ /**
103
+ * where clauses for the shape.
104
+ */
105
+ where?: string;
106
+ /**
107
+ * The "offset" on the shape log. This is typically not set as the ShapeStream
108
+ * will handle this automatically. A common scenario where you might pass an offset
109
+ * is if you're maintaining a local cache of the log. If you've gone offline
110
+ * and are re-starting a ShapeStream to catch-up to the latest state of the Shape,
111
+ * you'd pass in the last offset and shapeId you'd seen from the Electric server
112
+ * so it knows at what point in the shape to catch you up from.
113
+ */
114
+ offset?: Offset;
115
+ /**
116
+ * Similar to `offset`, this isn't typically used unless you're maintaining
117
+ * a cache of the shape log.
118
+ */
119
+ shapeId?: string;
120
+ backoffOptions?: BackoffOptions;
121
+ /**
122
+ * Automatically fetch updates to the Shape. If you just want to sync the current
123
+ * shape and stop, pass false.
124
+ */
125
+ subscribe?: boolean;
126
+ signal?: AbortSignal;
127
+ fetchClient?: typeof fetch;
128
+ parser?: Parser;
129
+ }
130
+ declare class FetchError extends Error {
131
+ url: string;
132
+ status: number;
133
+ text?: string;
134
+ json?: object;
135
+ headers: Record<string, string>;
136
+ constructor(status: number, text: string | undefined, json: object | undefined, headers: Record<string, string>, url: string, message?: string);
137
+ static fromResponse(response: Response, url: string): Promise<FetchError>;
138
+ }
139
+ /**
140
+ * Reads updates to a shape from Electric using HTTP requests and long polling. Notifies subscribers
141
+ * when new messages come in. Doesn't maintain any history of the
142
+ * log but does keep track of the offset position and is the best way
143
+ * to consume the HTTP `GET /v1/shape` api.
144
+ *
145
+ * @constructor
146
+ * @param {ShapeStreamOptions} options
147
+ *
148
+ * Register a callback function to subscribe to the messages.
149
+ *
150
+ * const stream = new ShapeStream(options)
151
+ * stream.subscribe(messages => {
152
+ * // messages is 1 or more row updates
153
+ * })
154
+ *
155
+ * To abort the stream, abort the `signal`
156
+ * passed in via the `ShapeStreamOptions`.
157
+ *
158
+ * const aborter = new AbortController()
159
+ * const issueStream = new ShapeStream({
160
+ * url: `${BASE_URL}/${table}`
161
+ * subscribe: true,
162
+ * signal: aborter.signal,
163
+ * })
164
+ * // Later...
165
+ * aborter.abort()
166
+ */
167
+ declare class ShapeStream {
168
+ private options;
169
+ private backoffOptions;
170
+ private fetchClient;
171
+ private schema?;
172
+ private subscribers;
173
+ private upToDateSubscribers;
174
+ private lastOffset;
175
+ private messageParser;
176
+ isUpToDate: boolean;
177
+ shapeId?: string;
178
+ constructor(options: ShapeStreamOptions);
179
+ start(): Promise<void>;
180
+ subscribe(callback: (messages: Message[]) => void | Promise<void>, onError?: (error: FetchError | Error) => void): () => void;
181
+ unsubscribeAll(): void;
182
+ private publish;
183
+ private sendErrorToSubscribers;
184
+ subscribeOnceToUpToDate(callback: () => void | Promise<void>, error: (err: FetchError | Error) => void): () => void;
185
+ unsubscribeAllUpToDateSubscribers(): void;
186
+ private notifyUpToDateSubscribers;
187
+ private sendErrorToUpToDateSubscribers;
188
+ /**
189
+ * Resets the state of the stream, optionally with a provided
190
+ * shape ID
191
+ */
192
+ private reset;
193
+ private validateOptions;
194
+ private fetchWithBackoff;
195
+ }
196
+ /**
197
+ * A Shape is an object that subscribes to a shape log,
198
+ * keeps a materialised shape `.value` in memory and
199
+ * notifies subscribers when the value has changed.
200
+ *
201
+ * It can be used without a framework and as a primitive
202
+ * to simplify developing framework hooks.
203
+ *
204
+ * @constructor
205
+ * @param {Shape}
206
+ *
207
+ * const shapeStream = new ShapeStream(url: 'http://localhost:3000/v1/shape/foo'})
208
+ * const shape = new Shape(shapeStream)
209
+ *
210
+ * `value` returns a promise that resolves the Shape data once the Shape has been
211
+ * fully loaded (and when resuming from being offline):
212
+ *
213
+ * const value = await shape.value
214
+ *
215
+ * `valueSync` returns the current data synchronously:
216
+ *
217
+ * const value = shape.valueSync
218
+ *
219
+ * Subscribe to updates. Called whenever the shape updates in Postgres.
220
+ *
221
+ * shape.subscribe(shapeData => {
222
+ * console.log(shapeData)
223
+ * })
224
+ */
225
+ declare class Shape {
226
+ private stream;
227
+ private data;
228
+ private subscribers;
229
+ error: FetchError | false;
230
+ private hasNotifiedSubscribersUpToDate;
231
+ constructor(stream: ShapeStream);
232
+ get isUpToDate(): boolean;
233
+ get value(): Promise<ShapeData>;
234
+ get valueSync(): ShapeData;
235
+ subscribe(callback: ShapeChangedCallback): () => void;
236
+ unsubscribeAll(): void;
237
+ get numSubscribers(): number;
238
+ private process;
239
+ private handleError;
240
+ private notify;
241
+ }
242
+
243
+ export { BackoffDefaults, type BackoffOptions, type BitColumn, type BpcharColumn, type ChangeMessage, type ColumnInfo, type ControlMessage, FetchError, type IntervalColumn, type IntervalColumnWithPrecision, type Message, type NumericColumn, type Offset, type RegularColumn, type Schema, Shape, type ShapeChangedCallback, type ShapeData, ShapeStream, type ShapeStreamOptions, type TimeColumn, type TypedMessages, type Value, type VarcharColumn };
@@ -0,0 +1,450 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
3
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
4
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
+ var __spreadValues = (a, b) => {
7
+ for (var prop in b || (b = {}))
8
+ if (__hasOwnProp.call(b, prop))
9
+ __defNormalProp(a, prop, b[prop]);
10
+ if (__getOwnPropSymbols)
11
+ for (var prop of __getOwnPropSymbols(b)) {
12
+ if (__propIsEnum.call(b, prop))
13
+ __defNormalProp(a, prop, b[prop]);
14
+ }
15
+ return a;
16
+ };
17
+ var __objRest = (source, exclude) => {
18
+ var target = {};
19
+ for (var prop in source)
20
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
21
+ target[prop] = source[prop];
22
+ if (source != null && __getOwnPropSymbols)
23
+ for (var prop of __getOwnPropSymbols(source)) {
24
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
25
+ target[prop] = source[prop];
26
+ }
27
+ return target;
28
+ };
29
+
30
+ // src/parser.ts
31
+ var parseNumber = (value) => Number(value);
32
+ var parseBool = (value) => value === `true` || value === `t`;
33
+ var parseBigInt = (value) => BigInt(value);
34
+ var parseJson = (value) => JSON.parse(value);
35
+ var defaultParser = {
36
+ int2: parseNumber,
37
+ int4: parseNumber,
38
+ int8: parseBigInt,
39
+ bool: parseBool,
40
+ float4: parseNumber,
41
+ float8: parseNumber,
42
+ json: parseJson,
43
+ jsonb: parseJson
44
+ };
45
+ function pgArrayParser(value, parser) {
46
+ let i = 0;
47
+ let char = null;
48
+ let str = ``;
49
+ let quoted = false;
50
+ let last = 0;
51
+ let p = void 0;
52
+ function loop(x) {
53
+ const xs = [];
54
+ for (; i < x.length; i++) {
55
+ char = x[i];
56
+ if (quoted) {
57
+ if (char === `\\`) {
58
+ str += x[++i];
59
+ } else if (char === `"`) {
60
+ xs.push(parser ? parser(str) : str);
61
+ str = ``;
62
+ quoted = x[i + 1] === `"`;
63
+ last = i + 2;
64
+ } else {
65
+ str += char;
66
+ }
67
+ } else if (char === `"`) {
68
+ quoted = true;
69
+ } else if (char === `{`) {
70
+ last = ++i;
71
+ xs.push(loop(x));
72
+ } else if (char === `}`) {
73
+ quoted = false;
74
+ last < i && xs.push(parser ? parser(x.slice(last, i)) : x.slice(last, i));
75
+ last = i + 1;
76
+ break;
77
+ } else if (char === `,` && p !== `}` && p !== `"`) {
78
+ xs.push(parser ? parser(x.slice(last, i)) : x.slice(last, i));
79
+ last = i + 1;
80
+ }
81
+ p = char;
82
+ }
83
+ last < i && xs.push(parser ? parser(x.slice(last, i + 1)) : x.slice(last, i + 1));
84
+ return xs;
85
+ }
86
+ return loop(value)[0];
87
+ }
88
+ var MessageParser = class {
89
+ constructor(parser) {
90
+ this.parser = __spreadValues(__spreadValues({}, defaultParser), parser);
91
+ }
92
+ parse(messages, schema) {
93
+ return JSON.parse(messages, (key, value) => {
94
+ if (key === `value` && typeof value === `object`) {
95
+ const row = value;
96
+ Object.keys(row).forEach((key2) => {
97
+ row[key2] = this.parseRow(key2, row[key2], schema);
98
+ });
99
+ }
100
+ return value;
101
+ });
102
+ }
103
+ // Parses the message values using the provided parser based on the schema information
104
+ parseRow(key, value, schema) {
105
+ const columnInfo = schema[key];
106
+ if (!columnInfo) {
107
+ return value;
108
+ }
109
+ const parser = this.parser[columnInfo.type];
110
+ const _a = columnInfo, { type: _typ, dims: dimensions } = _a, additionalInfo = __objRest(_a, ["type", "dims"]);
111
+ if (dimensions > 0) {
112
+ const identityParser = (v) => v;
113
+ return pgArrayParser(value, parser != null ? parser : identityParser);
114
+ }
115
+ if (!parser) {
116
+ return value;
117
+ }
118
+ return parser(value, additionalInfo);
119
+ }
120
+ };
121
+
122
+ // src/client.ts
123
+ var BackoffDefaults = {
124
+ initialDelay: 100,
125
+ maxDelay: 1e4,
126
+ multiplier: 1.3
127
+ };
128
+ var MessageProcessor = class {
129
+ constructor(callback) {
130
+ this.messageQueue = [];
131
+ this.isProcessing = false;
132
+ this.callback = callback;
133
+ }
134
+ process(messages) {
135
+ this.messageQueue.push(messages);
136
+ if (!this.isProcessing) {
137
+ this.processQueue();
138
+ }
139
+ }
140
+ async processQueue() {
141
+ this.isProcessing = true;
142
+ while (this.messageQueue.length > 0) {
143
+ const messages = this.messageQueue.shift();
144
+ await this.callback(messages);
145
+ }
146
+ this.isProcessing = false;
147
+ }
148
+ };
149
+ var FetchError = class _FetchError extends Error {
150
+ constructor(status, text, json, headers, url, message) {
151
+ super(
152
+ message || `HTTP Error ${status} at ${url}: ${text != null ? text : JSON.stringify(json)}`
153
+ );
154
+ this.url = url;
155
+ this.name = `FetchError`;
156
+ this.status = status;
157
+ this.text = text;
158
+ this.json = json;
159
+ this.headers = headers;
160
+ }
161
+ static async fromResponse(response, url) {
162
+ const status = response.status;
163
+ const headers = Object.fromEntries([...response.headers.entries()]);
164
+ let text = void 0;
165
+ let json = void 0;
166
+ const contentType = response.headers.get(`content-type`);
167
+ if (contentType && contentType.includes(`application/json`)) {
168
+ json = await response.json();
169
+ } else {
170
+ text = await response.text();
171
+ }
172
+ return new _FetchError(status, text, json, headers, url);
173
+ }
174
+ };
175
+ var ShapeStream = class {
176
+ constructor(options) {
177
+ this.subscribers = /* @__PURE__ */ new Map();
178
+ this.upToDateSubscribers = /* @__PURE__ */ new Map();
179
+ this.isUpToDate = false;
180
+ var _a, _b, _c;
181
+ this.validateOptions(options);
182
+ this.options = __spreadValues({ subscribe: true }, options);
183
+ this.lastOffset = (_a = this.options.offset) != null ? _a : `-1`;
184
+ this.shapeId = this.options.shapeId;
185
+ this.messageParser = new MessageParser(options.parser);
186
+ this.backoffOptions = (_b = options.backoffOptions) != null ? _b : BackoffDefaults;
187
+ this.fetchClient = (_c = options.fetchClient) != null ? _c : (...args) => fetch(...args);
188
+ this.start();
189
+ }
190
+ async start() {
191
+ var _a, _b;
192
+ this.isUpToDate = false;
193
+ const { url, where, signal } = this.options;
194
+ while (!(signal == null ? void 0 : signal.aborted) && !this.isUpToDate || this.options.subscribe) {
195
+ const fetchUrl = new URL(url);
196
+ if (where) fetchUrl.searchParams.set(`where`, where);
197
+ fetchUrl.searchParams.set(`offset`, this.lastOffset);
198
+ if (this.isUpToDate) {
199
+ fetchUrl.searchParams.set(`live`, `true`);
200
+ }
201
+ if (this.shapeId) {
202
+ fetchUrl.searchParams.set(`shape_id`, this.shapeId);
203
+ }
204
+ let response;
205
+ try {
206
+ const maybeResponse = await this.fetchWithBackoff(fetchUrl);
207
+ if (maybeResponse) response = maybeResponse;
208
+ else break;
209
+ } catch (e) {
210
+ if (!(e instanceof FetchError)) throw e;
211
+ if (e.status == 409) {
212
+ const newShapeId = e.headers[`x-electric-shape-id`];
213
+ this.reset(newShapeId);
214
+ this.publish(e.json);
215
+ continue;
216
+ } else if (e.status >= 400 && e.status < 500) {
217
+ this.sendErrorToUpToDateSubscribers(e);
218
+ this.sendErrorToSubscribers(e);
219
+ throw e;
220
+ }
221
+ }
222
+ const { headers, status } = response;
223
+ const shapeId = headers.get(`X-Electric-Shape-Id`);
224
+ if (shapeId) {
225
+ this.shapeId = shapeId;
226
+ }
227
+ const lastOffset = headers.get(`X-Electric-Chunk-Last-Offset`);
228
+ if (lastOffset) {
229
+ this.lastOffset = lastOffset;
230
+ }
231
+ const getSchema = () => {
232
+ const schemaHeader = headers.get(`X-Electric-Schema`);
233
+ return schemaHeader ? JSON.parse(schemaHeader) : {};
234
+ };
235
+ this.schema = (_a = this.schema) != null ? _a : getSchema();
236
+ const messages = status === 204 ? `[]` : await response.text();
237
+ const batch = this.messageParser.parse(messages, this.schema);
238
+ if (batch.length > 0) {
239
+ const lastMessage = batch[batch.length - 1];
240
+ if (((_b = lastMessage.headers) == null ? void 0 : _b[`control`]) === `up-to-date` && !this.isUpToDate) {
241
+ this.isUpToDate = true;
242
+ this.notifyUpToDateSubscribers();
243
+ }
244
+ this.publish(batch);
245
+ }
246
+ }
247
+ }
248
+ subscribe(callback, onError) {
249
+ const subscriptionId = Math.random();
250
+ const subscriber = new MessageProcessor(callback);
251
+ this.subscribers.set(subscriptionId, [subscriber, onError]);
252
+ return () => {
253
+ this.subscribers.delete(subscriptionId);
254
+ };
255
+ }
256
+ unsubscribeAll() {
257
+ this.subscribers.clear();
258
+ }
259
+ publish(messages) {
260
+ this.subscribers.forEach(([subscriber, _]) => {
261
+ subscriber.process(messages);
262
+ });
263
+ }
264
+ sendErrorToSubscribers(error) {
265
+ this.subscribers.forEach(([_, errorFn]) => {
266
+ errorFn == null ? void 0 : errorFn(error);
267
+ });
268
+ }
269
+ subscribeOnceToUpToDate(callback, error) {
270
+ const subscriptionId = Math.random();
271
+ this.upToDateSubscribers.set(subscriptionId, [callback, error]);
272
+ return () => {
273
+ this.upToDateSubscribers.delete(subscriptionId);
274
+ };
275
+ }
276
+ unsubscribeAllUpToDateSubscribers() {
277
+ this.upToDateSubscribers.clear();
278
+ }
279
+ notifyUpToDateSubscribers() {
280
+ this.upToDateSubscribers.forEach(([callback]) => {
281
+ callback();
282
+ });
283
+ }
284
+ sendErrorToUpToDateSubscribers(error) {
285
+ this.upToDateSubscribers.forEach(
286
+ ([_, errorCallback]) => errorCallback(error)
287
+ );
288
+ }
289
+ /**
290
+ * Resets the state of the stream, optionally with a provided
291
+ * shape ID
292
+ */
293
+ reset(shapeId) {
294
+ this.lastOffset = `-1`;
295
+ this.shapeId = shapeId;
296
+ this.isUpToDate = false;
297
+ this.schema = void 0;
298
+ }
299
+ validateOptions(options) {
300
+ if (!options.url) {
301
+ throw new Error(`Invalid shape option. It must provide the url`);
302
+ }
303
+ if (options.signal && !(options.signal instanceof AbortSignal)) {
304
+ throw new Error(
305
+ `Invalid signal option. It must be an instance of AbortSignal.`
306
+ );
307
+ }
308
+ if (options.offset !== void 0 && options.offset !== `-1` && !options.shapeId) {
309
+ throw new Error(
310
+ `shapeId is required if this isn't an initial fetch (i.e. offset > -1)`
311
+ );
312
+ }
313
+ }
314
+ async fetchWithBackoff(url) {
315
+ const { initialDelay, maxDelay, multiplier } = this.backoffOptions;
316
+ const signal = this.options.signal;
317
+ let delay = initialDelay;
318
+ let attempt = 0;
319
+ while (true) {
320
+ try {
321
+ const result = await this.fetchClient(url.toString(), { signal });
322
+ if (result.ok) return result;
323
+ else throw await FetchError.fromResponse(result, url.toString());
324
+ } catch (e) {
325
+ if (signal == null ? void 0 : signal.aborted) {
326
+ return void 0;
327
+ } else if (e instanceof FetchError && e.status >= 400 && e.status < 500) {
328
+ throw e;
329
+ } else {
330
+ await new Promise((resolve) => setTimeout(resolve, delay));
331
+ delay = Math.min(delay * multiplier, maxDelay);
332
+ attempt++;
333
+ console.log(`Retry attempt #${attempt} after ${delay}ms`);
334
+ }
335
+ }
336
+ }
337
+ }
338
+ };
339
+ var Shape = class {
340
+ constructor(stream) {
341
+ this.data = /* @__PURE__ */ new Map();
342
+ this.subscribers = /* @__PURE__ */ new Map();
343
+ this.error = false;
344
+ this.hasNotifiedSubscribersUpToDate = false;
345
+ this.stream = stream;
346
+ this.stream.subscribe(this.process.bind(this), this.handleError.bind(this));
347
+ const unsubscribe = this.stream.subscribeOnceToUpToDate(
348
+ () => {
349
+ unsubscribe();
350
+ },
351
+ (e) => {
352
+ this.handleError(e);
353
+ throw e;
354
+ }
355
+ );
356
+ }
357
+ get isUpToDate() {
358
+ return this.stream.isUpToDate;
359
+ }
360
+ get value() {
361
+ return new Promise((resolve) => {
362
+ if (this.stream.isUpToDate) {
363
+ resolve(this.valueSync);
364
+ } else {
365
+ const unsubscribe = this.stream.subscribeOnceToUpToDate(
366
+ () => {
367
+ unsubscribe();
368
+ resolve(this.valueSync);
369
+ },
370
+ (e) => {
371
+ throw e;
372
+ }
373
+ );
374
+ }
375
+ });
376
+ }
377
+ get valueSync() {
378
+ return this.data;
379
+ }
380
+ subscribe(callback) {
381
+ const subscriptionId = Math.random();
382
+ this.subscribers.set(subscriptionId, callback);
383
+ return () => {
384
+ this.subscribers.delete(subscriptionId);
385
+ };
386
+ }
387
+ unsubscribeAll() {
388
+ this.subscribers.clear();
389
+ }
390
+ get numSubscribers() {
391
+ return this.subscribers.size;
392
+ }
393
+ process(messages) {
394
+ let dataMayHaveChanged = false;
395
+ let isUpToDate = false;
396
+ let newlyUpToDate = false;
397
+ messages.forEach((message) => {
398
+ var _a, _b;
399
+ if (`key` in message) {
400
+ dataMayHaveChanged = [`insert`, `update`, `delete`].includes(
401
+ message.headers.action
402
+ );
403
+ switch (message.headers.action) {
404
+ case `insert`:
405
+ this.data.set(message.key, message.value);
406
+ break;
407
+ case `update`:
408
+ this.data.set(message.key, __spreadValues(__spreadValues({}, this.data.get(message.key)), message.value));
409
+ break;
410
+ case `delete`:
411
+ this.data.delete(message.key);
412
+ break;
413
+ }
414
+ }
415
+ if (((_a = message.headers) == null ? void 0 : _a[`control`]) === `up-to-date`) {
416
+ isUpToDate = true;
417
+ if (!this.hasNotifiedSubscribersUpToDate) {
418
+ newlyUpToDate = true;
419
+ }
420
+ }
421
+ if (((_b = message.headers) == null ? void 0 : _b[`control`]) === `must-refetch`) {
422
+ this.data.clear();
423
+ this.error = false;
424
+ isUpToDate = false;
425
+ newlyUpToDate = false;
426
+ }
427
+ });
428
+ if (newlyUpToDate || isUpToDate && dataMayHaveChanged) {
429
+ this.hasNotifiedSubscribersUpToDate = true;
430
+ this.notify();
431
+ }
432
+ }
433
+ handleError(e) {
434
+ if (e instanceof FetchError) {
435
+ this.error = e;
436
+ }
437
+ }
438
+ notify() {
439
+ this.subscribers.forEach((callback) => {
440
+ callback(this.valueSync);
441
+ });
442
+ }
443
+ };
444
+ export {
445
+ BackoffDefaults,
446
+ FetchError,
447
+ Shape,
448
+ ShapeStream
449
+ };
450
+ //# sourceMappingURL=index.legacy-esm.js.map