@livequery/client 2.0.22 → 2.0.24

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.
@@ -15,6 +15,7 @@ export type SmartQueryItem<T> = T & {
15
15
  __adding: boolean;
16
16
  __trigger: <R extends {}>(name: string, payload?: any, query?: any) => Promise<Response<R>>;
17
17
  __ref: string;
18
+ toJson: () => T;
18
19
  };
19
20
  export type CollectionStream<T extends LivequeryBaseEntity = LivequeryBaseEntity> = {
20
21
  items: SmartQueryItem<T>[];
@@ -42,25 +42,23 @@ export class CollectionObservable extends BehaviorSubject {
42
42
  set_realtime(realtime) {
43
43
  this.collection_options.realtime = realtime;
44
44
  }
45
- #sync(stream, from_local = false, direction) {
45
+ #sync(stream, from, direction) {
46
+ if (from == 'realtime' && this.collection_options.realtime == false)
47
+ return;
46
48
  const state = this.getValue();
47
- const realtime = this.collection_options.realtime ?? true;
48
- const actions = { update: false, reindex: false };
49
+ const actions = { reindex: false };
49
50
  for (const { data, error, code, message, } of stream) {
50
- if (!from_local) {
51
+ if (from == 'request') {
51
52
  state.loading = false;
52
- actions.update = true;
53
53
  }
54
54
  // Error & paging
55
55
  if (error) {
56
56
  state.error = true;
57
57
  state.code = code;
58
58
  state.message = message;
59
- actions.update = true;
60
59
  }
61
60
  if (data?.summary) {
62
61
  state.summary = data.summary;
63
- actions.update = true;
64
62
  }
65
63
  // Sync
66
64
  for (const change of data?.changes || []) {
@@ -72,35 +70,32 @@ export class CollectionObservable extends BehaviorSubject {
72
70
  if (index == -1 && type == 'added') {
73
71
  if (
74
72
  // Is first value from HTTP query
75
- true
73
+ from == 'request'
76
74
  || (
77
75
  // Is realtime update that match filters
78
- (realtime || from_local) && Object
76
+ from == 'realtime' && Object
79
77
  .keys(state.options || {})
80
78
  .filter(key => !key.includes('_'))
81
79
  .every(key => {
80
+ const [field, expression] = key.split(':');
81
+ const a = payload[field];
82
+ const b = state.options?.[field];
83
+ const map = {
84
+ 'default': () => a == b,
85
+ eq: () => a == b,
86
+ ne: () => a != b,
87
+ lt: () => typeof a == 'number' && typeof b == 'number' && a < b,
88
+ lte: () => typeof a == 'number' && typeof b == 'number' && a <= b,
89
+ gt: () => typeof a == 'number' && typeof b == 'number' && a > b,
90
+ gte: () => typeof a == 'number' && typeof b == 'number' && a >= b,
91
+ in: () => Array.isArray(a) && a?.includes(b),
92
+ like: () => typeof a == 'string' && a.includes(`${b}`),
93
+ between: () => typeof a == 'number' && Array.isArray(b) && typeof b[0] == 'number' && b[0] <= a && typeof b[1] == 'number' && a <= b[1]
94
+ };
82
95
  try {
83
- const [field, expression] = key.split(':');
84
- const a = payload[field];
85
- const b = state.options?.[field];
86
- if (!expression)
87
- return a == b;
88
- if (expression == 'ne')
89
- return a != b;
90
- if (expression == 'lt')
91
- return typeof a == 'number' && typeof b == 'number' && a < b;
92
- if (expression == 'lte')
93
- return typeof a == 'number' && typeof b == 'number' && a <= b;
94
- if (expression == 'gt')
95
- return typeof a == 'number' && typeof b == 'number' && a > b;
96
- if (expression == 'gte')
97
- return typeof a == 'number' && typeof b == 'number' && a >= b;
98
- if (expression == 'in' || expression == 'like')
99
- return Array.isArray(a) && a?.includes(b);
100
- if (expression == 'between') {
101
- const [x, y] = b;
102
- return x <= a && a <= y;
103
- }
96
+ const fn = map[expression || 'default'];
97
+ if (fn)
98
+ return fn();
104
99
  }
105
100
  catch (e) { }
106
101
  return false;
@@ -114,16 +109,15 @@ export class CollectionObservable extends BehaviorSubject {
114
109
  __remove: () => this.remove(payload?.id),
115
110
  __trigger: (name, input = undefined, query) => this.trigger(name, input, payload?.id, query),
116
111
  __update: (input) => this.update({ ...input, id: payload?.id }),
117
- __ref: change.ref
112
+ __ref: change.ref,
113
+ toJson: () => payload
118
114
  };
119
115
  direction == 'forward' ? state.items.push(item) : state.items.unshift(item);
120
116
  actions.reindex = true;
121
- actions.update = true;
122
117
  }
123
118
  }
124
- if (index >= 0 && (realtime || from_local)) {
119
+ if (index >= 0) {
125
120
  if (type == 'added' || type == 'modified') {
126
- actions.update = true;
127
121
  const sort_key_value_updated = this.#sorters.some(({ key }) => {
128
122
  const value = payload[key];
129
123
  if (typeof value == 'string' || typeof value == 'number')
@@ -141,7 +135,6 @@ export class CollectionObservable extends BehaviorSubject {
141
135
  };
142
136
  }
143
137
  if (type == 'removed') {
144
- actions.update = true;
145
138
  state.items.splice(index, 1);
146
139
  for (const [document_id, i] of this.#IdMap) {
147
140
  i == index && this.#IdMap.delete(document_id);
@@ -205,23 +198,30 @@ export class CollectionObservable extends BehaviorSubject {
205
198
  }
206
199
  };
207
200
  }
208
- actions.update && this.next(state);
201
+ this.next(state);
209
202
  }
210
203
  fetch_data(options = {}, loading, flush = false) {
204
+ if (flush) {
205
+ this.#pages.clear();
206
+ this.#queries.forEach(s => s.unsubscribe());
207
+ this.#queries.clear();
208
+ this.#IdMap.clear();
209
+ }
211
210
  if (!this.ref)
212
211
  return;
213
212
  if (this.#refs.length == 0)
214
213
  return;
215
- if (this.getValue().loading)
214
+ if (this.getValue().loading && !flush)
216
215
  return;
217
- this.collection_options.options = options;
218
- this.#sorters = Object.keys(options).filter(k => k.endsWith(':sort')).map(k => {
219
- const key = k.split(':sort')[0];
220
- const order = options[k] == 1 ? 1 : -1;
221
- return { key, order };
216
+ const remain_data_refs = this.#refs.filter(ref => {
217
+ const paging = this.#pages.get(ref);
218
+ if (!paging)
219
+ return true;
220
+ return loading == 'forward' ? paging.has?.next : paging.has?.prev;
222
221
  });
223
- this.#sorters.every(a => a.key != 'id') && this.#sorters.push({ key: 'id', order: -1 });
224
- const state = {
222
+ if (remain_data_refs.length == 0)
223
+ return;
224
+ this.next({
225
225
  ...this.getValue(),
226
226
  items: flush ? [] : this.getValue().items,
227
227
  loading,
@@ -229,23 +229,14 @@ export class CollectionObservable extends BehaviorSubject {
229
229
  ...this.getValue().options || {},
230
230
  ...options
231
231
  }
232
- };
233
- if (flush) {
234
- this.#pages.clear();
235
- this.#queries.forEach(s => s.unsubscribe());
236
- this.#queries.clear();
237
- this.#IdMap.clear();
238
- }
239
- const remain_data_refs = this.#refs.filter(ref => {
240
- const paging = this.#pages.get(ref);
241
- if (!paging)
242
- return true;
243
- return loading == 'forward' ? paging.has?.next : paging.has?.prev;
244
232
  });
245
- const no_more_data = !flush && (remain_data_refs.length == 0 || this.getValue().loading);
246
- if (no_more_data)
247
- return;
248
- this.next(state);
233
+ this.collection_options.options = options;
234
+ this.#sorters = Object.keys(options).filter(k => k.endsWith(':sort')).map(k => {
235
+ const key = k.split(':sort')[0];
236
+ const order = options[k] == 1 ? 1 : -1;
237
+ return { key, order };
238
+ });
239
+ this.#sorters.every(a => a.key != 'id') && this.#sorters.push({ key: 'id', order: -1 });
249
240
  const queries = remain_data_refs.map((ref, index) => {
250
241
  const cursor = this.#pages.get(ref)?.cursor;
251
242
  const opts = {
@@ -261,8 +252,8 @@ export class CollectionObservable extends BehaviorSubject {
261
252
  ref
262
253
  })), share());
263
254
  });
264
- const first_values = merge(...queries.map(q => q.pipe(filter(r => !!r.data?.paging || !!r.error), first()))).pipe(toArray(), tap(list => this.#sync(list, false, loading))).subscribe();
265
- const subscription = merge(...queries.map(q => q.pipe(skip(1)))).pipe(bufferTime(this.collection_options?.sync_delay || 500), filter(list => list.length > 0), map(data => this.#sync(data, false, loading)), finalize(() => first_values.unsubscribe())).subscribe();
255
+ const first_values = merge(...queries.map(q => q.pipe(filter(r => !!r.data?.paging || !!r.error), first()))).pipe(toArray(), tap(list => this.#sync(list, 'request', loading))).subscribe();
256
+ const subscription = merge(...queries.map(q => q.pipe(skip(1)))).pipe(bufferTime(this.collection_options?.sync_delay || 500), filter(list => list.length > 0), map(data => this.#sync(data, 'realtime', loading)), finalize(() => first_values.unsubscribe())).subscribe();
266
257
  this.#queries.add(subscription);
267
258
  }
268
259
  reset() {
@@ -281,17 +272,18 @@ export class CollectionObservable extends BehaviorSubject {
281
272
  }
282
273
  #find_ref_by_id(id) {
283
274
  if (!id || !this.ref)
284
- return { ref: this.ref, collection_ref: this.ref };
275
+ return { ref: this.ref, collection_ref: this.ref, doc: null };
285
276
  const index = this.#IdMap.get(id);
286
277
  if (index == undefined)
287
278
  return {};
288
- const origin_ref = this.getValue().items[index].__ref;
279
+ const doc = this.getValue().items[index];
280
+ const origin_ref = doc.__ref;
289
281
  if (!origin_ref)
290
282
  throw 'COLLECTION_REF_NOT_FOUND';
291
283
  const refs = origin_ref.split('/');
292
284
  const collection_ref = refs.slice(0, refs.length - (refs.length % 2 == 1 ? 0 : 1)).join('/');
293
285
  const ref = `${collection_ref}/${id}`;
294
- return { ref, id, collection_ref, index };
286
+ return { ref, id, collection_ref, index, doc };
295
287
  }
296
288
  async add(payload) {
297
289
  const r = await this.collection_options.transporter.add(`${this.ref}`, payload);
@@ -301,7 +293,7 @@ export class CollectionObservable extends BehaviorSubject {
301
293
  return r;
302
294
  }
303
295
  async update({ id: update_payload_id, ...payload }) {
304
- const { id, ref } = this.#find_ref_by_id(update_payload_id);
296
+ const { id, ref, doc } = this.#find_ref_by_id(update_payload_id);
305
297
  if (!ref)
306
298
  return;
307
299
  // Trigger local update
@@ -314,7 +306,7 @@ export class CollectionObservable extends BehaviorSubject {
314
306
  type: 'modified'
315
307
  }]
316
308
  }
317
- }], true);
309
+ }], 'local');
318
310
  try {
319
311
  return await this.collection_options.transporter.update(ref, payload);
320
312
  }
@@ -323,12 +315,12 @@ export class CollectionObservable extends BehaviorSubject {
323
315
  ref,
324
316
  data: {
325
317
  changes: [{
326
- data: { id, __updating: false },
318
+ data: { ...doc || {}, id, __updating: false },
327
319
  ref,
328
320
  type: 'modified'
329
321
  }]
330
322
  }
331
- }], true);
323
+ }], 'local');
332
324
  throw e;
333
325
  }
334
326
  }
@@ -345,7 +337,7 @@ export class CollectionObservable extends BehaviorSubject {
345
337
  type: 'modified'
346
338
  }]
347
339
  }
348
- }], true);
340
+ }], 'local');
349
341
  // Trigger
350
342
  try {
351
343
  return await this.collection_options.transporter.remove(ref);
@@ -360,7 +352,7 @@ export class CollectionObservable extends BehaviorSubject {
360
352
  type: 'modified'
361
353
  }]
362
354
  }
363
- }], true);
355
+ }], 'local');
364
356
  throw e;
365
357
  }
366
358
  }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "repository": {
5
5
  "url": "https://github.com/livequery/client"
6
6
  },
7
- "version": "2.0.22",
7
+ "version": "2.0.24",
8
8
  "description": "",
9
9
  "main": "build/index.js",
10
10
  "types": "build/index.d.ts",