@livequery/client 1.0.53 → 1.0.80

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.
@@ -28,7 +28,9 @@ export declare class CollectionObservable<T extends LivequeryBaseEntity = Livequ
28
28
  private ref;
29
29
  private collection_options;
30
30
  readonly $changes: Subject<UpdatedData<T>>;
31
- constructor(ref: string, collection_options: CollectionOption<T>);
31
+ value: CollectionStream<T>;
32
+ $: Subject<CollectionStream<T>>;
33
+ constructor(ref: string | false | null | '' | undefined, collection_options: CollectionOption<T>);
32
34
  set_realtime(realtime: boolean): void;
33
35
  private sync;
34
36
  private fetch_data;
@@ -43,6 +45,6 @@ export declare class CollectionObservable<T extends LivequeryBaseEntity = Livequ
43
45
  }>;
44
46
  update({ id: update_payload_id, ...payload }: Partial<T>): Promise<any>;
45
47
  remove(remove_document_id?: string): Promise<void>;
46
- trigger<T>(name: string, payload?: object, trigger_document_id?: string): Promise<T>;
48
+ trigger<T>(name: string, payload?: object, trigger_document_id?: string): Promise<T | undefined>;
47
49
  }
48
50
  export {};
@@ -1,30 +1,35 @@
1
1
  import { Subject, Observable, merge } from 'rxjs';
2
- import { get_sort_function } from './helpers/get_sort_function.js';
3
2
  import { bufferTime, filter, map } from 'rxjs/operators';
4
3
  export class CollectionObservable extends Observable {
5
4
  ref;
6
5
  collection_options;
7
6
  $changes = new Subject();
8
- #$state = new Subject();
9
- #subscriptions = new Set();
10
- #state;
7
+ #queries = new Set();
11
8
  #next_cursor = {};
12
9
  #IdMap = new Map();
13
10
  #refs = [];
11
+ value = {
12
+ has_more: false,
13
+ items: [],
14
+ options: {},
15
+ loading: false
16
+ };
17
+ $ = new Subject();
14
18
  constructor(ref, collection_options) {
15
19
  super(o => {
16
- this.#state = { items: [], options: collection_options.filters, has_more: false };
17
- const subscription = this.#$state.subscribe(o);
20
+ const subscription = this.$.subscribe(o);
18
21
  const auto_reload_interval = collection_options.reload_interval && setInterval(() => this.reload(), collection_options.reload_interval);
19
22
  return () => {
20
- this.#subscriptions.forEach(s => s.unsubscribe());
21
23
  subscription.unsubscribe();
24
+ this.#queries.forEach(s => s.unsubscribe());
22
25
  clearInterval(auto_reload_interval);
23
26
  };
24
27
  });
25
28
  this.ref = ref;
26
29
  this.collection_options = collection_options;
27
- if (ref.startsWith('/') || ref.endsWith('/'))
30
+ if (collection_options.filters)
31
+ this.value.options = collection_options.filters;
32
+ if (ref && (ref.startsWith('/') || ref.endsWith('/')))
28
33
  throw 'INVAILD_REF_FORMAT';
29
34
  this.#refs = this.#ref_parser(ref);
30
35
  }
@@ -50,13 +55,13 @@ export class CollectionObservable extends Observable {
50
55
  for (const { data, error, ref } of stream) {
51
56
  // Error & paging
52
57
  if (error) {
53
- this.#state.error = error;
58
+ this.value.error = error;
54
59
  actions.update = true;
55
60
  }
56
61
  if (data?.paging?.n == 0) {
57
62
  this.#next_cursor[ref] = data?.paging?.next_cursor;
58
- this.#state.has_more = Object.values(this.#next_cursor).some(v => v && v != '#');
59
- this.#state.loading = false;
63
+ this.value.has_more = Object.values(this.#next_cursor).some(v => v && v != '#');
64
+ this.value.loading = false;
60
65
  actions.update = true;
61
66
  }
62
67
  // Sync
@@ -73,40 +78,34 @@ export class CollectionObservable extends Observable {
73
78
  || (
74
79
  // Is realtime update that match filters
75
80
  (realtime || from_local) && Object
76
- .keys(this.#state.options || {})
81
+ .keys(this.value.options || {})
77
82
  .filter(key => !key.includes('_'))
78
83
  .every(key => {
79
84
  try {
80
85
  const [field, expression] = key.split(':');
81
86
  const a = payload[field];
82
- const b = this.#state.options?.[field];
87
+ const b = this.value.options?.[field];
83
88
  if (!expression)
84
89
  return a == b;
85
90
  if (expression == 'ne')
86
91
  return a != b;
87
92
  if (expression == 'lt')
88
- return a < b;
93
+ return typeof a == 'number' && typeof b == 'number' && a < b;
89
94
  if (expression == 'lte')
90
- return a <= b;
95
+ return typeof a == 'number' && typeof b == 'number' && a <= b;
91
96
  if (expression == 'gt')
92
- return a > b;
97
+ return typeof a == 'number' && typeof b == 'number' && a > b;
93
98
  if (expression == 'gte')
94
- return a >= b;
95
- if (expression == 'in-array')
96
- return a?.includes(b);
97
- if (expression == 'contains')
98
- return a?.some(e => b?.includes(e));
99
- if (expression == 'not-contains')
100
- return a?.every(e => !b?.includes(e));
99
+ return typeof a == 'number' && typeof b == 'number' && a >= b;
100
+ if (expression == 'in' || expression == 'like')
101
+ return Array.isArray(a) && a?.includes(b);
101
102
  if (expression == 'between')
102
103
  return (b[0] <= a && a <= b[1]);
103
- if (expression == 'like')
104
- return a.includes(b);
105
104
  }
106
105
  catch (e) { }
107
106
  return false;
108
107
  }))) {
109
- this.#state.items.push({
108
+ this.value.items.push({
110
109
  ...payload,
111
110
  __adding: false,
112
111
  __updating: false,
@@ -126,8 +125,8 @@ export class CollectionObservable extends Observable {
126
125
  if (Object.keys(payload).some(key => ['created_at', this.collection_options?.filters?._order_by].includes(key))) {
127
126
  actions.reindex = true;
128
127
  }
129
- this.#state.items[index] = {
130
- ...this.#state.items[index],
128
+ this.value.items[index] = {
129
+ ...this.value.items[index],
131
130
  __adding: false,
132
131
  __updating: false,
133
132
  __removing: false,
@@ -137,7 +136,7 @@ export class CollectionObservable extends Observable {
137
136
  if (type == 'removed') {
138
137
  actions.reindex = true;
139
138
  actions.update = true;
140
- this.#state.items.splice(index, 1);
139
+ this.value.items.splice(index, 1);
141
140
  for (const [document_id, i] of this.#IdMap) {
142
141
  i == index && this.#IdMap.delete(document_id);
143
142
  i > index && this.#IdMap.set(document_id, i - 1);
@@ -147,33 +146,44 @@ export class CollectionObservable extends Observable {
147
146
  }
148
147
  }
149
148
  if (actions.reindex) {
150
- this.#state.items = this.#state.items.sort(get_sort_function(this.#state.items[0], this.collection_options?.filters?._order_by || 'created_at', this.collection_options?.filters?._sort));
149
+ const _sort = this.collection_options?.filters?._sort || 'desc';
150
+ const _order_by = this.collection_options?.filters?._order_by || 'created_at';
151
+ this.value.items = this.value.items.sort((a, b) => {
152
+ const ka = a[_order_by];
153
+ const kb = b[_order_by];
154
+ if (typeof ka == 'string' && typeof kb == 'string')
155
+ return ka.localeCompare(ka) * (_sort == 'desc' ? -1 : 1);
156
+ if ((typeof ka == 'number' || typeof ka == 'number')
157
+ && (typeof kb == 'number' || typeof kb == 'number'))
158
+ return (ka - kb) * (_sort == 'desc' ? -1 : 1);
159
+ return 1;
160
+ });
151
161
  this.#IdMap.clear();
152
- this.#state.items.map((item, index) => this.#IdMap.set(item.id, index));
162
+ this.value.items.map((item, index) => this.#IdMap.set(item.id, index));
153
163
  }
154
- actions.update && this.#$state.next(this.#state);
164
+ actions.update && this.$.next(this.value);
155
165
  }
156
166
  fetch_data(options = {}, flush = false) {
157
167
  if (this.#refs.length == 0)
158
168
  return;
159
169
  if (flush) {
160
170
  this.#next_cursor = {};
161
- this.#subscriptions.forEach(s => s.unsubscribe());
162
- this.#subscriptions.clear();
171
+ this.#queries.forEach(s => s.unsubscribe());
172
+ this.#queries.clear();
163
173
  this.#IdMap.clear();
164
174
  }
165
175
  const has_more_data_refs = this.#refs.filter(ref => this.#next_cursor[ref] === undefined || (this.#next_cursor[ref] && this.#next_cursor[ref] != '#'));
166
176
  // Load more but no more data || loading
167
- if (!flush && (has_more_data_refs.length == 0 || this.#state.loading))
177
+ if (!flush && (has_more_data_refs.length == 0 || this.value.loading))
168
178
  return;
169
- this.#state = {
170
- ...this.#state,
171
- items: flush ? [] : this.#state.items,
172
- error: null,
179
+ this.value = {
180
+ ...this.value,
181
+ items: flush ? [] : this.value.items,
182
+ error: undefined,
173
183
  loading: true,
174
184
  options
175
185
  };
176
- this.#$state.next(this.#state);
186
+ this.$.next(this.value);
177
187
  const queries = has_more_data_refs.map(ref => (this
178
188
  .collection_options
179
189
  .transporter
@@ -181,36 +191,41 @@ export class CollectionObservable extends Observable {
181
191
  const reload = () => queries.map(q => q.reload());
182
192
  const $ = merge(...queries.map((q, index) => q.pipe(map(data => ({ ...data, ref: has_more_data_refs[index] }))))).pipe(bufferTime(500), filter(list => list.length > 0), map(data => this.sync(data)));
183
193
  const subscription = Object.assign($.subscribe(), { reload });
184
- this.#subscriptions.add(subscription);
194
+ this.#queries.add(subscription);
185
195
  }
186
196
  reload() {
187
- this.#subscriptions.forEach(s => s.reload());
197
+ this.#queries.forEach(s => s.reload());
188
198
  }
189
199
  reset() {
190
200
  this.fetch_data({}, true);
191
201
  }
192
202
  fetch_more() {
193
- this.fetch_data(this.#state?.options);
203
+ this.fetch_data(this.value?.options);
194
204
  }
195
205
  filter(filters) {
196
206
  this.fetch_data(filters, true);
197
207
  }
198
208
  #find_ref_by_id(id) {
199
- if (!id)
209
+ if (!id || !this.ref)
200
210
  return { ref: this.ref, collection_ref: this.ref };
201
- const origin_ref = this.#state.items[this.#IdMap.get(id)].__ref;
211
+ const index = this.#IdMap.get(id);
212
+ if (index == undefined)
213
+ return {};
214
+ const origin_ref = this.value.items[index].__ref;
202
215
  if (!origin_ref)
203
216
  throw 'COLLECTION_REF_NOT_FOUND';
204
217
  const refs = origin_ref.split('/');
205
218
  const collection_ref = refs.slice(0, refs.length - (refs.length % 2 == 1 ? 0 : 1)).join('/');
206
219
  const ref = `${collection_ref}/${id}`;
207
- return { ref, id, collection_ref };
220
+ return { ref, id, collection_ref, index };
208
221
  }
209
222
  async add(payload) {
210
223
  return await this.collection_options.transporter.add(`${this.ref}`, payload);
211
224
  }
212
225
  async update({ id: update_payload_id, ...payload }) {
213
226
  const { id, ref } = this.#find_ref_by_id(update_payload_id);
227
+ if (!ref)
228
+ return;
214
229
  // Trigger local update
215
230
  this.sync([{
216
231
  ref,
@@ -241,6 +256,8 @@ export class CollectionObservable extends Observable {
241
256
  }
242
257
  async remove(remove_document_id) {
243
258
  const { id, ref } = this.#find_ref_by_id(remove_document_id);
259
+ if (!ref)
260
+ return;
244
261
  this.sync([{
245
262
  ref,
246
263
  data: {
@@ -271,6 +288,8 @@ export class CollectionObservable extends Observable {
271
288
  }
272
289
  async trigger(name, payload, trigger_document_id) {
273
290
  const { ref } = this.#find_ref_by_id(trigger_document_id);
291
+ if (!ref)
292
+ return;
274
293
  return await this.collection_options.transporter.trigger(ref, name, {}, payload);
275
294
  }
276
295
  }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "repository": {
5
5
  "url": "https://github.com/livequery/client"
6
6
  },
7
- "version": "1.0.53",
7
+ "version": "1.0.80",
8
8
  "description": "",
9
9
  "main": "build/index.js",
10
10
  "types": "build/index.d.ts",
@@ -1 +0,0 @@
1
- export declare function get_sort_function(data: any, key: string, order?: 'asc' | 'desc'): (a: any, b: any) => number;
@@ -1,16 +0,0 @@
1
- export function get_sort_function(data, key, order = 'desc') {
2
- const type = typeof data?.[key];
3
- if (type == 'bigint' || type == 'number') {
4
- if (order == 'asc')
5
- return (a, b) => a[key] - b[key] || ((a.created_at || 1) - (b.created_at || 0));
6
- if (order == 'desc')
7
- return (a, b) => b[key] - a[key] || ((a.created_at || 1) - (b.created_at || 0));
8
- }
9
- if (type == 'string') {
10
- if (order == 'asc')
11
- return (a, b) => a[key].localeCompare(b[key]) || ((a.created_at || 1) - (b.created_at || 0));
12
- if (order == 'desc')
13
- return (a, b) => b[key].localeCompare(a[key]) || ((a.created_at || 1) - (b.created_at || 0));
14
- }
15
- return () => 1;
16
- }