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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "native-document",
3
- "version": "1.0.257",
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
- } else if(typeof value === 'object') {
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,5 @@
1
+
2
+
3
+ export default function ObservableRelationQuery($parent, relation) {
4
+
5
+ }
@@ -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
+ }
@@ -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>,