@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.
package/build/Collection.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
40
|
+
add<R = {
|
|
40
41
|
data: {
|
|
41
42
|
item: T;
|
|
42
43
|
};
|
|
43
|
-
}>;
|
|
44
|
-
update
|
|
45
|
-
|
|
46
|
-
|
|
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 {};
|
package/build/Collection.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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
|
|
59
|
-
this
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
130
|
-
...this
|
|
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
|
|
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
|
-
|
|
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
|
|
162
|
+
this.value.items.map((item, index) => this.#IdMap.set(item.id, index));
|
|
153
163
|
}
|
|
154
|
-
actions.update && this
|
|
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.#
|
|
162
|
-
this.#
|
|
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
|
-
|
|
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
|
|
170
|
-
...this
|
|
171
|
-
items: flush ? [] : this
|
|
172
|
-
error:
|
|
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
|
|
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
|
|
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.#
|
|
195
|
+
this.#queries.add(subscription);
|
|
185
196
|
}
|
|
186
197
|
reload() {
|
|
187
|
-
this.#
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
@@ -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
|
-
}
|