@livequery/client 1.0.52 → 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,37 +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
- async add(payload) {
199
- if (this.ref.includes('.'))
200
- throw 'INVAILD_COLLECTION_REF_FOR_ADDING';
201
- return await this.collection_options.transporter.add(`${this.ref}`, payload);
202
- }
203
208
  #find_ref_by_id(id) {
204
- if (!id)
209
+ if (!id || !this.ref)
205
210
  return { ref: this.ref, collection_ref: this.ref };
206
- const ref = this.#state.items[this.#IdMap.get(id)].__ref;
207
- if (!ref)
211
+ const index = this.#IdMap.get(id);
212
+ if (index == undefined)
213
+ return {};
214
+ const origin_ref = this.value.items[index].__ref;
215
+ if (!origin_ref)
208
216
  throw 'COLLECTION_REF_NOT_FOUND';
209
- const refs = ref.split('/');
217
+ const refs = origin_ref.split('/');
210
218
  const collection_ref = refs.slice(0, refs.length - (refs.length % 2 == 1 ? 0 : 1)).join('/');
211
- return { ref, id, collection_ref };
219
+ const ref = `${collection_ref}/${id}`;
220
+ return { ref, id, collection_ref, index };
221
+ }
222
+ async add(payload) {
223
+ return await this.collection_options.transporter.add(`${this.ref}`, payload);
212
224
  }
213
225
  async update({ id: update_payload_id, ...payload }) {
214
226
  const { id, ref } = this.#find_ref_by_id(update_payload_id);
227
+ if (!ref)
228
+ return;
215
229
  // Trigger local update
216
230
  this.sync([{
217
231
  ref,
@@ -242,6 +256,8 @@ export class CollectionObservable extends Observable {
242
256
  }
243
257
  async remove(remove_document_id) {
244
258
  const { id, ref } = this.#find_ref_by_id(remove_document_id);
259
+ if (!ref)
260
+ return;
245
261
  this.sync([{
246
262
  ref,
247
263
  data: {
@@ -272,6 +288,8 @@ export class CollectionObservable extends Observable {
272
288
  }
273
289
  async trigger(name, payload, trigger_document_id) {
274
290
  const { ref } = this.#find_ref_by_id(trigger_document_id);
291
+ if (!ref)
292
+ return;
275
293
  return await this.collection_options.transporter.trigger(ref, name, {}, payload);
276
294
  }
277
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.52",
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
- }