native-document 1.0.257 → 1.0.259
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/package.json +1 -1
- package/src/core/data/Observable.js +13 -1
- package/src/core/data/ObservableArray.js +10 -2
- package/src/core/data/ObservableQuery.js +157 -0
- package/src/core/data/ObservableRelationQuery.js +5 -0
- package/types/observable-query.d.ts +67 -0
- package/types/observable.d.ts +7 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "native-document",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.259",
|
|
4
4
|
"description": "A reactive JavaScript framework that preserves native DOM simplicity without sacrificing modern features",
|
|
5
5
|
"author": "AfroCodeur <https://github.com/afrocodeur>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -12,6 +12,7 @@ import './observable-helpers/observable.prototypes';
|
|
|
12
12
|
import ObservableResource from './ObservableResource';
|
|
13
13
|
import {Formatters} from '../utils/formatters';
|
|
14
14
|
import Ref from './Ref';
|
|
15
|
+
import ObservableQuery from './ObservableQuery';
|
|
15
16
|
/**
|
|
16
17
|
*
|
|
17
18
|
* @param {*} value
|
|
@@ -188,9 +189,16 @@ Observable.auto = (value, options = null) => {
|
|
|
188
189
|
if(value == null) {
|
|
189
190
|
return new ObservableItem(value, options);
|
|
190
191
|
}
|
|
192
|
+
if(value.__$Observable) {
|
|
193
|
+
return value;
|
|
194
|
+
}
|
|
191
195
|
if(Validator.isArray(value)) {
|
|
192
196
|
return new ObservableArray(value, options);
|
|
193
|
-
}
|
|
197
|
+
}
|
|
198
|
+
else if(typeof value === 'object') {
|
|
199
|
+
if(!Validator.isJson(value)) {
|
|
200
|
+
return value;
|
|
201
|
+
}
|
|
194
202
|
return new ObservableObject(value, options);
|
|
195
203
|
}
|
|
196
204
|
return new ObservableItem(value, options);
|
|
@@ -252,6 +260,10 @@ Observable.collection = function() {
|
|
|
252
260
|
return new Ref(true);
|
|
253
261
|
};
|
|
254
262
|
|
|
263
|
+
Observable.from = function(target) {
|
|
264
|
+
return new ObservableQuery(target);
|
|
265
|
+
};
|
|
266
|
+
|
|
255
267
|
ObservableItem.prototype.resolve = function () {
|
|
256
268
|
return Observable.value(this);
|
|
257
269
|
};
|
|
@@ -6,7 +6,7 @@ import NativeDocumentError from '../errors/NativeDocumentError.js';
|
|
|
6
6
|
import {nextTick} from '../utils/helpers';
|
|
7
7
|
|
|
8
8
|
export const mutationMethods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice'];
|
|
9
|
-
export const noMutationMethods = ['map', 'forEach', 'filter', 'reduce', 'some', 'every', 'find', 'findIndex', 'concat', 'includes', 'indexOf'];
|
|
9
|
+
export const noMutationMethods = ['map', 'forEach', 'filter', 'reduce', 'some', 'every', 'find', 'findIndex', 'concat', 'includes', 'indexOf', 'at', 'lastIndexOf', 'findLastIndex'];
|
|
10
10
|
|
|
11
11
|
const toObservable = (item) => item?.__$Observable ? item : ObservableArray.auto(item);
|
|
12
12
|
|
|
@@ -30,7 +30,7 @@ const ObservableArray = function (target, configs = { propagation: false, deep:
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
if(configs?.deep) {
|
|
33
|
+
if(configs?.deep !== false) {
|
|
34
34
|
target = target.map(toObservable);
|
|
35
35
|
this.interceptMutations((data, operations) => {
|
|
36
36
|
if(!operations.action || ['push', 'unshift', 'set', 'splice'].includes(operations.action)) {
|
|
@@ -578,4 +578,12 @@ ObservableArray.prototype.fnClear = function () {
|
|
|
578
578
|
// return result;
|
|
579
579
|
// };
|
|
580
580
|
|
|
581
|
+
ObservableArray.prototype.first = function () {
|
|
582
|
+
return this.$currentValue[0];
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
ObservableArray.prototype.last = function () {
|
|
586
|
+
return this.$currentValue.at(-1);
|
|
587
|
+
};
|
|
588
|
+
|
|
581
589
|
export default ObservableArray;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
export default function ObservableQuery($target) {
|
|
4
|
+
if(!(this instanceof ObservableQuery)) {
|
|
5
|
+
return new ObservableQuery($target);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if(!$target?.__$isObservableArray) {
|
|
9
|
+
throw new Error('ObservableQuery target must be a ObservableArray');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
this.$target = $target;
|
|
13
|
+
this.$parentMap = new Map([$target]);
|
|
14
|
+
this.$currentResultData = $target;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
ObservableQuery.prototype.flush = function () {
|
|
18
|
+
this.$currentResultData = this.$target;
|
|
19
|
+
return this;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const predicateFns = {
|
|
23
|
+
gt: (source, target) => source.valueOf() > target.valueOf(),
|
|
24
|
+
gte: (source, target) => source.valueOf() >= target.valueOf(),
|
|
25
|
+
lt: (source, target) => source.valueOf() < target.valueOf(),
|
|
26
|
+
lte: (source, target) => source.valueOf() <= target.valueOf(),
|
|
27
|
+
eq: (source, target) => source.valueOf() === target.valueOf(),
|
|
28
|
+
neq: (source, target) => source.valueOf() !== target.valueOf(),
|
|
29
|
+
in: (source, target) => target.includes(source.valueOf()),
|
|
30
|
+
nin: (source, target) => !target.includes(source.valueOf()),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const getPredicateFn = (column, predicate) => {
|
|
34
|
+
if(typeof column === 'function') {
|
|
35
|
+
return column;
|
|
36
|
+
}
|
|
37
|
+
if(predicate == null) {
|
|
38
|
+
return (element) => {
|
|
39
|
+
return element[column] == null;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if(typeof predicate?.valueOf === 'function') {
|
|
43
|
+
return (element) => {
|
|
44
|
+
return element[column]?.valueOf() === predicate?.valueOf();
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const operator = Object.keys(predicate).at(0);
|
|
48
|
+
if(!operator) {
|
|
49
|
+
return () => true;
|
|
50
|
+
}
|
|
51
|
+
const value = Object.values(predicate).at(0);
|
|
52
|
+
const operation = predicateFns[operator];
|
|
53
|
+
if(!operation) {
|
|
54
|
+
throw new Error('Unknown predicate operator '+operator);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (element) => operation(element[column], value);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// --------------------------------------------------------------------------------------------------------------------
|
|
61
|
+
ObservableQuery.prototype.where = function(column, predicate) {
|
|
62
|
+
const predicateFn = getPredicateFn(column, predicate);
|
|
63
|
+
this.$currentResultData = this.$currentResultData.valueOf().flatMap((item) => {
|
|
64
|
+
if(item?.__$isObservableArray || Array.isArray(item)) {
|
|
65
|
+
const values = item.filter(predicateFn).filter(Boolean);
|
|
66
|
+
values.forEach((value) => {
|
|
67
|
+
this.$parentMap.set(value, item);
|
|
68
|
+
});
|
|
69
|
+
return values;
|
|
70
|
+
}
|
|
71
|
+
return predicateFn(item) ? [item] : [];
|
|
72
|
+
});
|
|
73
|
+
return this;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
ObservableQuery.prototype.in = function(relation) {
|
|
77
|
+
|
|
78
|
+
this.$currentResultData = this.$currentResultData
|
|
79
|
+
.map((parentElement) => {
|
|
80
|
+
const childCollection = typeof relation === 'function' ? relation(parentElement) : parentElement[relation];
|
|
81
|
+
if(childCollection) {
|
|
82
|
+
this.$parentMap.set(childCollection, parentElement);
|
|
83
|
+
}
|
|
84
|
+
return childCollection;
|
|
85
|
+
})
|
|
86
|
+
.filter(Boolean);
|
|
87
|
+
|
|
88
|
+
return this;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
ObservableQuery.prototype.first = function() {
|
|
92
|
+
|
|
93
|
+
return this.$currentResultData[0] ?? null;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
ObservableQuery.prototype.all = function() {
|
|
97
|
+
|
|
98
|
+
return this.$currentResultData;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
// --------------------------------------------------------------------------------------------------------------------
|
|
103
|
+
ObservableQuery.prototype.count = function() {
|
|
104
|
+
return this.$currentResultData.length;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
ObservableQuery.prototype.exists = function() {
|
|
108
|
+
return this.$currentResultData.length > 0;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
ObservableQuery.prototype.sum = function(key) {
|
|
112
|
+
return this.$currentResultData.reduce((acc, element) => {
|
|
113
|
+
return acc + element[key]?.valueOf();
|
|
114
|
+
}, 0);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
ObservableQuery.prototype.map = function(fn) {
|
|
118
|
+
return this.$currentResultData.map(fn);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
// --------------------------------------------------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
ObservableQuery.prototype.set = function(dataset) {
|
|
125
|
+
this.$currentResultData.forEach((element) => {
|
|
126
|
+
if(typeof element.set === 'function') {
|
|
127
|
+
element.set(dataset);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
for(const key in dataset) {
|
|
131
|
+
const prop = element[key];
|
|
132
|
+
const value = dataset[key];
|
|
133
|
+
if(prop?.__$isObservable) {
|
|
134
|
+
prop.set(value);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
element[key] = value;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
return this.flush();
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
ObservableQuery.prototype.delete = function() {
|
|
144
|
+
this.$currentResultData.forEach((element) => {
|
|
145
|
+
const parent = this.$parentMap.get(element) ?? this.$target;
|
|
146
|
+
const indexOfElement = parent.indexOf(element);
|
|
147
|
+
parent.splice(indexOfElement, 1);
|
|
148
|
+
});
|
|
149
|
+
return this.flush();
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
ObservableQuery.prototype.push = function(value) {
|
|
153
|
+
this.$currentResultData.forEach((element) => {
|
|
154
|
+
element.push && element.push(value);
|
|
155
|
+
});
|
|
156
|
+
return this.flush();
|
|
157
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ObservableArray, ObservableItem } from './observable';
|
|
2
|
+
|
|
3
|
+
type PredicateOperator = 'gt' | 'gte' | 'lt' | 'lte' | 'eq' | 'neq' | 'in' | 'nin';
|
|
4
|
+
|
|
5
|
+
type PredicateFn<T> = (element: T) => boolean;
|
|
6
|
+
|
|
7
|
+
type Predicate<T> =
|
|
8
|
+
| null
|
|
9
|
+
| { [K in PredicateOperator]?: any }
|
|
10
|
+
| PredicateFn<T>;
|
|
11
|
+
|
|
12
|
+
type Dataset<T> = Partial<{
|
|
13
|
+
[K in keyof T]: T[K] extends ObservableItem<infer V> ? V : T[K];
|
|
14
|
+
}>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Fluent query builder for ObservableArray collections.
|
|
18
|
+
* Supports filtering, navigation into nested relations, aggregation and mutations.
|
|
19
|
+
*
|
|
20
|
+
* @template T - Type of items in the target ObservableArray
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* // Select
|
|
24
|
+
* $.from($events).where('id', 1).first();
|
|
25
|
+
*
|
|
26
|
+
* // Navigate into relation
|
|
27
|
+
* $.from($events).where('id', 1).in('tickets').where('type', 'VIP').all();
|
|
28
|
+
*
|
|
29
|
+
* // Delete from relation
|
|
30
|
+
* $.from($events).in('tickets').where('type', 'VIP').delete();
|
|
31
|
+
*
|
|
32
|
+
* // Insert into relation
|
|
33
|
+
* $.from($events).where('id', 1).in('tickets').push(newTicket);
|
|
34
|
+
*
|
|
35
|
+
* // Update
|
|
36
|
+
* $.from($events).where('id', 1).set({ name: 'New name' });
|
|
37
|
+
*
|
|
38
|
+
* // Aggregation
|
|
39
|
+
* $.from($events).in('tickets').sum('price');
|
|
40
|
+
* $.from($events).where('active', true).count();
|
|
41
|
+
*/
|
|
42
|
+
export declare function ObservableQuery<T = any>(
|
|
43
|
+
target: ObservableArray<T>
|
|
44
|
+
): ObservableQueryInstance<T>;
|
|
45
|
+
|
|
46
|
+
export interface ObservableQueryInstance<T = any> {
|
|
47
|
+
|
|
48
|
+
// -- Navigation / filtering -----------------------------------------------
|
|
49
|
+
flush(): this;
|
|
50
|
+
where(column: keyof T | PredicateFn<T>, predicate?: Predicate<T>): this;
|
|
51
|
+
in(relation: keyof T | ((element: T) => any)): ObservableQueryInstance<any>;
|
|
52
|
+
|
|
53
|
+
// -- Read -----------------------------------------------------------------
|
|
54
|
+
first(): T | null;
|
|
55
|
+
all(): T[];
|
|
56
|
+
|
|
57
|
+
// -- Aggregation ----------------------------------------------------------
|
|
58
|
+
count(): number;
|
|
59
|
+
exists(): boolean;
|
|
60
|
+
sum(key: keyof T): number;
|
|
61
|
+
map<U>(fn: (element: T) => U): U[];
|
|
62
|
+
|
|
63
|
+
// -- Mutations ------------------------------------------------------------
|
|
64
|
+
set(dataset: Dataset<T>): this;
|
|
65
|
+
delete(): this;
|
|
66
|
+
push(value: any): this;
|
|
67
|
+
}
|
package/types/observable.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FilterResult, PredicateMap } from './filters';
|
|
2
2
|
import Ref, {RefItem} from './ref';
|
|
3
|
+
import {ObservableQueryInstance} from "./observable-query";
|
|
3
4
|
|
|
4
5
|
export type Unsubscribe = () => void;
|
|
5
6
|
|
|
@@ -216,9 +217,14 @@ export interface ObservableArray<T> extends ObservableItem<T[]> {
|
|
|
216
217
|
find(callback: (value: T, index: number, array: T[]) => boolean): T | undefined;
|
|
217
218
|
at(index: number): T | undefined;
|
|
218
219
|
findIndex(callback: (value: T, index: number, array: T[]) => boolean): number;
|
|
220
|
+
findLastIndex(callback: (value: T, index: number, array: T[]) => boolean): number;
|
|
219
221
|
concat(...items: (T | T[])[]): T[];
|
|
220
222
|
indexOf(item: T): number;
|
|
223
|
+
lastIndexOf(item: T): number;
|
|
221
224
|
includes(item: T): boolean;
|
|
225
|
+
at(index: number): T | undefined;
|
|
226
|
+
first(): T | undefined;
|
|
227
|
+
last(): T | undefined;
|
|
222
228
|
|
|
223
229
|
where(predicates: PredicateMap<T> | ((item: T) => boolean)): ObservableArray<T>;
|
|
224
230
|
whereSome<K extends keyof T>(fields: K[], filter: FilterResult<T[K]>): ObservableArray<T>;
|
|
@@ -340,6 +346,7 @@ export interface ObservableStatic {
|
|
|
340
346
|
json<T extends Record<string, any>>(value: T, configs?: ObservableConfig | null): ObservableObject<T>;
|
|
341
347
|
|
|
342
348
|
auto<T>(value: T): ObservableArray<T>|ObservableObject<T>|ObservableItem<T>;
|
|
349
|
+
from<T>(value: ObservableArray<T>): ObservableQueryInstance<T>;
|
|
343
350
|
|
|
344
351
|
resource<T = any>(
|
|
345
352
|
fn: (...args: any[]) => Promise<T>,
|