@livequery/client 1.0.53 → 1.0.85

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.
@@ -1,4 +1,4 @@
1
- import { Subject, Observable } from 'rxjs';
1
+ import { Subject, Observable, ReplaySubject } from 'rxjs';
2
2
  import { ErrorInfo, LivequeryBaseEntity, QueryOption, Transporter, UpdatedData } from '@livequery/types';
3
3
  export type CollectionOption<T extends LivequeryBaseEntity = LivequeryBaseEntity> = {
4
4
  transporter: Transporter;
@@ -28,21 +28,32 @@ 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
+ $: ReplaySubject<CollectionStream<T>>;
33
+ constructor(ref: string | false | null | '' | undefined, collection_options: CollectionOption<T>);
32
34
  set_realtime(realtime: boolean): void;
33
- private sync;
34
35
  private fetch_data;
35
36
  reload(): void;
36
37
  reset(): void;
37
38
  fetch_more(): void;
38
39
  filter(filters: Partial<QueryOption<T>>): void;
39
- add(payload: Partial<T>): Promise<{
40
+ add<R = {
40
41
  data: {
41
42
  item: T;
42
43
  };
43
- }>;
44
- update({ id: update_payload_id, ...payload }: Partial<T>): Promise<any>;
45
- remove(remove_document_id?: string): Promise<void>;
46
- trigger<T>(name: string, payload?: object, trigger_document_id?: string): Promise<T>;
44
+ }>(payload: Partial<T>): Promise<R>;
45
+ update<R = {
46
+ data: {
47
+ item: T;
48
+ };
49
+ }>({ id: update_payload_id, ...payload }: Partial<T>): Promise<R | undefined>;
50
+ remove<R = {
51
+ data: {
52
+ item: T;
53
+ };
54
+ }>(remove_document_id?: string): Promise<void>;
55
+ trigger<R>(name: string, payload?: object, trigger_document_id?: string, query?: {
56
+ [key: string]: string | number | boolean;
57
+ }): Promise<R | undefined>;
47
58
  }
48
59
  export {};
@@ -1,30 +1,35 @@
1
- import { Subject, Observable, merge } from 'rxjs';
2
- import { get_sort_function } from './helpers/get_sort_function.js';
1
+ import { Subject, Observable, merge, ReplaySubject } from 'rxjs';
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 ReplaySubject(1);
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
  }
@@ -44,19 +49,19 @@ export class CollectionObservable extends Observable {
44
49
  set_realtime(realtime) {
45
50
  this.collection_options.realtime = realtime;
46
51
  }
47
- sync(stream, from_local = false) {
52
+ #sync(stream, from_local = false) {
48
53
  const realtime = this.collection_options.realtime ?? true;
49
54
  const actions = { update: false, reindex: false };
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,72 +146,89 @@ 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) {
167
+ if (!this.ref)
168
+ return;
157
169
  if (this.#refs.length == 0)
158
170
  return;
159
171
  if (flush) {
160
172
  this.#next_cursor = {};
161
- this.#subscriptions.forEach(s => s.unsubscribe());
162
- this.#subscriptions.clear();
173
+ this.#queries.forEach(s => s.unsubscribe());
174
+ this.#queries.clear();
163
175
  this.#IdMap.clear();
164
176
  }
165
177
  const has_more_data_refs = this.#refs.filter(ref => this.#next_cursor[ref] === undefined || (this.#next_cursor[ref] && this.#next_cursor[ref] != '#'));
166
- // Load more but no more data || loading
167
- if (!flush && (has_more_data_refs.length == 0 || this.#state.loading))
178
+ if (!flush && (has_more_data_refs.length == 0 || this.value.loading))
168
179
  return;
169
- this.#state = {
170
- ...this.#state,
171
- items: flush ? [] : this.#state.items,
172
- error: null,
180
+ this.value = {
181
+ ...this.value,
182
+ items: flush ? [] : this.value.items,
183
+ error: undefined,
173
184
  loading: true,
174
185
  options
175
186
  };
176
- this.#$state.next(this.#state);
187
+ this.$.next(this.value);
177
188
  const queries = has_more_data_refs.map(ref => (this
178
189
  .collection_options
179
190
  .transporter
180
191
  .query(ref, { ...options, _cursor: this.#next_cursor[ref] })));
181
192
  const reload = () => queries.map(q => q.reload());
182
- 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)));
193
+ 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
194
  const subscription = Object.assign($.subscribe(), { reload });
184
- this.#subscriptions.add(subscription);
195
+ this.#queries.add(subscription);
185
196
  }
186
197
  reload() {
187
- this.#subscriptions.forEach(s => s.reload());
198
+ this.#queries.forEach(s => s.reload());
188
199
  }
189
200
  reset() {
190
201
  this.fetch_data({}, true);
191
202
  }
192
203
  fetch_more() {
193
- this.fetch_data(this.#state?.options);
204
+ this.fetch_data(this.value?.options);
194
205
  }
195
206
  filter(filters) {
196
207
  this.fetch_data(filters, true);
197
208
  }
198
209
  #find_ref_by_id(id) {
199
- if (!id)
210
+ if (!id || !this.ref)
200
211
  return { ref: this.ref, collection_ref: this.ref };
201
- const origin_ref = this.#state.items[this.#IdMap.get(id)].__ref;
212
+ const index = this.#IdMap.get(id);
213
+ if (index == undefined)
214
+ return {};
215
+ const origin_ref = this.value.items[index].__ref;
202
216
  if (!origin_ref)
203
217
  throw 'COLLECTION_REF_NOT_FOUND';
204
218
  const refs = origin_ref.split('/');
205
219
  const collection_ref = refs.slice(0, refs.length - (refs.length % 2 == 1 ? 0 : 1)).join('/');
206
220
  const ref = `${collection_ref}/${id}`;
207
- return { ref, id, collection_ref };
221
+ return { ref, id, collection_ref, index };
208
222
  }
209
223
  async add(payload) {
210
224
  return await this.collection_options.transporter.add(`${this.ref}`, payload);
211
225
  }
212
226
  async update({ id: update_payload_id, ...payload }) {
213
227
  const { id, ref } = this.#find_ref_by_id(update_payload_id);
228
+ if (!ref)
229
+ return;
214
230
  // Trigger local update
215
- this.sync([{
231
+ this.#sync([{
216
232
  ref,
217
233
  data: {
218
234
  changes: [{
@@ -226,7 +242,7 @@ export class CollectionObservable extends Observable {
226
242
  return await this.collection_options.transporter.update(ref, payload);
227
243
  }
228
244
  catch (e) {
229
- this.sync([{
245
+ this.#sync([{
230
246
  ref,
231
247
  data: {
232
248
  changes: [{
@@ -241,7 +257,9 @@ export class CollectionObservable extends Observable {
241
257
  }
242
258
  async remove(remove_document_id) {
243
259
  const { id, ref } = this.#find_ref_by_id(remove_document_id);
244
- this.sync([{
260
+ if (!ref)
261
+ return;
262
+ this.#sync([{
245
263
  ref,
246
264
  data: {
247
265
  changes: [{
@@ -256,7 +274,7 @@ export class CollectionObservable extends Observable {
256
274
  return await this.collection_options.transporter.remove(ref);
257
275
  }
258
276
  catch (e) {
259
- this.sync([{
277
+ this.#sync([{
260
278
  ref,
261
279
  data: {
262
280
  changes: [{
@@ -269,8 +287,10 @@ export class CollectionObservable extends Observable {
269
287
  throw e;
270
288
  }
271
289
  }
272
- async trigger(name, payload, trigger_document_id) {
290
+ async trigger(name, payload, trigger_document_id, query = {}) {
273
291
  const { ref } = this.#find_ref_by_id(trigger_document_id);
274
- return await this.collection_options.transporter.trigger(ref, name, {}, payload);
292
+ if (!ref)
293
+ return;
294
+ return await this.collection_options.transporter.trigger(ref, name, query, payload);
275
295
  }
276
296
  }
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.85",
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
- }