j-templates 7.0.104 → 8.0.0

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/Node/vNode.js CHANGED
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.vNode = void 0;
4
4
  const Store_1 = require("../Store");
5
5
  const observableScope_1 = require("../Store/Tree/observableScope");
6
+ const array_1 = require("../Utils/array");
6
7
  const emitter_1 = require("../Utils/emitter");
7
8
  const functions_1 = require("../Utils/functions");
8
9
  const injector_1 = require("../Utils/injector");
@@ -152,8 +153,10 @@ function Children(vnode, children, data) {
152
153
  Store_1.ObservableScope.Watch(childrenScope, CreateScheduledCallback(function (scope) {
153
154
  if (vnode.destroyed)
154
155
  return;
156
+ const oldChildrenLength = vnode.children.length;
155
157
  vnode.children = Store_1.ObservableScope.Value(scope);
156
- UpdateChildren(vnode);
158
+ if (oldChildrenLength !== 0 || vnode.children.length !== 0)
159
+ UpdateChildren(vnode);
157
160
  }));
158
161
  vnode.children = Store_1.ObservableScope.Value(childrenScope);
159
162
  }
@@ -204,32 +207,18 @@ function ToArray(result) {
204
207
  return result;
205
208
  return [result];
206
209
  }
207
- function IsFragmentOrNull(vnode) {
208
- return vnode === null || vnode.type === vNode_types_1.FRAGMENT_NODE;
209
- }
210
- function GetNode(vnode) {
211
- return vnode.node;
212
- }
213
- function FlattenFragmentsRecursive(children, startIndex, result) {
214
- for (let x = startIndex; x < children.length; x++) {
215
- const vnode = children[x];
216
- switch (vnode.type) {
217
- case vNode_types_1.FRAGMENT_NODE:
218
- FlattenFragmentsRecursive(vnode.children, 0, result);
219
- default:
220
- if (vnode.node !== null)
221
- result.push(vnode.node);
222
- }
210
+ function MapNode(vnode) {
211
+ switch (vnode.type) {
212
+ case vNode_types_1.FRAGMENT_NODE:
213
+ return vnode.children.flatMap(MapNode);
214
+ default:
215
+ return vnode.node;
223
216
  }
224
- return result;
225
217
  }
226
218
  function CleanupChildrenArray(children) {
227
- const index = children.findIndex(IsFragmentOrNull);
228
- if (index < 0)
229
- return children;
230
- const result = children.slice(0, index).map(GetNode);
231
- FlattenFragmentsRecursive(children, index, result);
232
- return result;
219
+ const nodes = children.flatMap(MapNode);
220
+ (0, array_1.RemoveNulls)(nodes);
221
+ return nodes;
233
222
  }
234
223
  function UpdateChildren(vnode, init = false, skipInit = false) {
235
224
  if (!vnode.children)
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Removes all null values from an array starting from a specified index.
3
+ * This function modifies the array in-place by shifting non-null elements
4
+ * to fill the gaps left by removed null values, then truncating the array.
5
+ *
6
+ * @param array - The array from which to remove null values. Can contain mixed types including null.
7
+ * @param startIndex - The index to start removing null values from (default: 0).
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const arr = [1, null, 2, null, 3, null, 4];
12
+ * RemoveNulls(arr); // Removes all null values, result: [1, 2, 3, 4]
13
+ *
14
+ * const arr2 = [null, null, 1, null, 2];
15
+ * RemoveNulls(arr2, 2); // Starts from index 2, result: [null, null, 1, 2]
16
+ * ```
17
+ */
18
+ export declare function RemoveNulls(array: (unknown | null)[], startIndex?: number): void;
package/Utils/array.js ADDED
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RemoveNulls = RemoveNulls;
4
+ /**
5
+ * Removes all null values from an array starting from a specified index.
6
+ * This function modifies the array in-place by shifting non-null elements
7
+ * to fill the gaps left by removed null values, then truncating the array.
8
+ *
9
+ * @param array - The array from which to remove null values. Can contain mixed types including null.
10
+ * @param startIndex - The index to start removing null values from (default: 0).
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const arr = [1, null, 2, null, 3, null, 4];
15
+ * RemoveNulls(arr); // Removes all null values, result: [1, 2, 3, 4]
16
+ *
17
+ * const arr2 = [null, null, 1, null, 2];
18
+ * RemoveNulls(arr2, 2); // Starts from index 2, result: [null, null, 1, 2]
19
+ * ```
20
+ */
21
+ function RemoveNulls(array, startIndex = 0) {
22
+ let nullIndex = startIndex;
23
+ for (; nullIndex < array.length && array[nullIndex] !== null; nullIndex++) { }
24
+ let notNullIndex = nullIndex + 1;
25
+ for (; notNullIndex < array.length && array[notNullIndex] === null; notNullIndex++) { }
26
+ while (notNullIndex < array.length) {
27
+ array[nullIndex] = array[notNullIndex];
28
+ nullIndex++;
29
+ notNullIndex++;
30
+ for (; notNullIndex < array.length && array[notNullIndex] === null; notNullIndex++) { }
31
+ }
32
+ if (nullIndex < array.length)
33
+ array.splice(nullIndex);
34
+ }
@@ -88,7 +88,7 @@ import { Injector } from "./injector";
88
88
  * @Value()
89
89
  * lastName: string = "Doe";
90
90
  *
91
- * @Computed() // Overhead: creates StoreSync, watch cycle, diff computation
91
+ * @Computed() // Overhead: observable node, watch cycle, diff computation
92
92
  * get fullName(): string {
93
93
  * return this.firstName + " " + this.lastName; // Cheap string concat
94
94
  * }
@@ -99,15 +99,18 @@ import { Injector } from "./injector";
99
99
  * }
100
100
  * ```
101
101
  *
102
- * @param defaultValue The default value to be used if the computed property is not defined.
103
102
  * @returns A property decorator that can be applied to a getter method.
104
103
  * @throws Will throw an error if the property is not a getter or if it has a setter.
105
104
  * @remarks
106
- * The @Computed decorator uses a two-phase caching system with diff-based updates:
107
- * 1. Getter scope: Computes value and writes to StoreSync when dependencies change
108
- * 2. StoreSync: Computes diff between old and new values
109
- * 3. ObservableNode.ApplyDiff: Updates the EXISTING object with only changed properties
110
- * 4. Property scope: Reads the updated (but same reference) value from StoreSync
105
+ * The @Computed decorator uses a Gated getter scope driving a persistent observable node:
106
+ * 1. Getter scope (Gated): Re-evaluates the getter when dependencies change, batched via
107
+ * the microtask queue. If the result deep-equals the previous value, no update is emitted.
108
+ * 2. ObservableNode.Apply: Diffs the fresh value against the node's current root and applies
109
+ * only the changed paths in-place, preserving the node's object identity.
110
+ * 3. Downstream notification: Per-property scopes are fired for each changed path, so scopes
111
+ * reading sub-properties update directly. If the diff collapses to a full root replacement
112
+ * (type change or removed keys), the root value gets a new identity and the Gated scope
113
+ * emits, notifying top-level subscribers.
111
114
  *
112
115
  * **Initialization**: @Computed uses lazy initialization - the scopes are created on first access:
113
116
  * ```typescript
@@ -152,8 +155,9 @@ import { Injector } from "./injector";
152
155
  * - The diff computation overhead is justified when you need object reuse
153
156
  * @see {@link Scope} for simple getter-based reactive properties (caches but new reference)
154
157
  * @see {@link ComputedAsync} for async computed properties
155
- * @see {@link ObservableNode.ApplyDiff} for how diffs are applied to maintain object identity
156
- * @see {@link StoreSync} for sync store implementation
158
+ * @see {@link ObservableNode.Apply} for how diffs are applied in-place to maintain object identity
159
+ * @see {@link ObservableScope} for the Gated scope mechanism (batched, ===-gated updates)
160
+ * @see {@link StoreSync} for the store-backed variant used by @ComputedAsync
157
161
  */
158
162
  export declare function Computed<T extends WeakKey, K extends keyof T, D extends T[K]>(): (target: T, propertyKey: K, descriptor: PropertyDescriptor) => PropertyDescriptor;
159
163
  /**
@@ -133,7 +133,7 @@ function GetDestroyArrayForPrototype(prototype, create = true) {
133
133
  }
134
134
  return array;
135
135
  }
136
- function CreateComputedScope(getter, store, defaultValue) {
136
+ function CreateStoreScope(getter, store, defaultValue) {
137
137
  const getterScope = observableScope_1.ObservableScope.Gated(function () {
138
138
  const value = getter();
139
139
  return observableNode_1.ObservableNode.Clone(value);
@@ -154,6 +154,31 @@ function CreateComputedScope(getter, store, defaultValue) {
154
154
  });
155
155
  return propertyScope;
156
156
  }
157
+ /**
158
+ * Creates a Gated observable scope that exposes a getter's value through a persistent
159
+ * observable node, preserving the value's object identity across updates.
160
+ *
161
+ * On each re-evaluation the getter's value is cloned and merged into the node in-place
162
+ * via ObservableNode.Apply:
163
+ * - Content changes (same structure, changed properties) fire only the affected
164
+ * per-property scopes - the top-level scope does not emit.
165
+ * - Structural changes (type change or removed keys) replace the root value with a new
166
+ * identity, which the Gated scope detects and emits for.
167
+ * - Deep-equal results are suppressed entirely by the Gated ===-gating.
168
+ *
169
+ * @param getter Function computing the value. May read reactive dependencies.
170
+ * @returns The Gated scope whose value is the observable root node.
171
+ */
172
+ function CreateNodeScope(getter) {
173
+ const observableNode = observableNode_1.ObservableNode.Create({ root: null });
174
+ const getterScope = observableScope_1.ObservableScope.Gated(function () {
175
+ const value = getter();
176
+ const clonedValue = observableNode_1.ObservableNode.Clone(value);
177
+ observableNode_1.ObservableNode.Apply(observableNode, { root: clonedValue });
178
+ return observableNode.root;
179
+ });
180
+ return getterScope;
181
+ }
157
182
  /**
158
183
  * Computed decorator factory for creating synchronous computed properties with caching and object reuse.
159
184
  * A computed property is derived from other properties and automatically updates when its dependencies change.
@@ -184,7 +209,7 @@ function CreateComputedScope(getter, store, defaultValue) {
184
209
  * @Value()
185
210
  * lastName: string = "Doe";
186
211
  *
187
- * @Computed() // Overhead: creates StoreSync, watch cycle, diff computation
212
+ * @Computed() // Overhead: observable node, watch cycle, diff computation
188
213
  * get fullName(): string {
189
214
  * return this.firstName + " " + this.lastName; // Cheap string concat
190
215
  * }
@@ -195,15 +220,18 @@ function CreateComputedScope(getter, store, defaultValue) {
195
220
  * }
196
221
  * ```
197
222
  *
198
- * @param defaultValue The default value to be used if the computed property is not defined.
199
223
  * @returns A property decorator that can be applied to a getter method.
200
224
  * @throws Will throw an error if the property is not a getter or if it has a setter.
201
225
  * @remarks
202
- * The @Computed decorator uses a two-phase caching system with diff-based updates:
203
- * 1. Getter scope: Computes value and writes to StoreSync when dependencies change
204
- * 2. StoreSync: Computes diff between old and new values
205
- * 3. ObservableNode.ApplyDiff: Updates the EXISTING object with only changed properties
206
- * 4. Property scope: Reads the updated (but same reference) value from StoreSync
226
+ * The @Computed decorator uses a Gated getter scope driving a persistent observable node:
227
+ * 1. Getter scope (Gated): Re-evaluates the getter when dependencies change, batched via
228
+ * the microtask queue. If the result deep-equals the previous value, no update is emitted.
229
+ * 2. ObservableNode.Apply: Diffs the fresh value against the node's current root and applies
230
+ * only the changed paths in-place, preserving the node's object identity.
231
+ * 3. Downstream notification: Per-property scopes are fired for each changed path, so scopes
232
+ * reading sub-properties update directly. If the diff collapses to a full root replacement
233
+ * (type change or removed keys), the root value gets a new identity and the Gated scope
234
+ * emits, notifying top-level subscribers.
207
235
  *
208
236
  * **Initialization**: @Computed uses lazy initialization - the scopes are created on first access:
209
237
  * ```typescript
@@ -248,8 +276,9 @@ function CreateComputedScope(getter, store, defaultValue) {
248
276
  * - The diff computation overhead is justified when you need object reuse
249
277
  * @see {@link Scope} for simple getter-based reactive properties (caches but new reference)
250
278
  * @see {@link ComputedAsync} for async computed properties
251
- * @see {@link ObservableNode.ApplyDiff} for how diffs are applied to maintain object identity
252
- * @see {@link StoreSync} for sync store implementation
279
+ * @see {@link ObservableNode.Apply} for how diffs are applied in-place to maintain object identity
280
+ * @see {@link ObservableScope} for the Gated scope mechanism (batched, ===-gated updates)
281
+ * @see {@link StoreSync} for the store-backed variant used by @ComputedAsync
253
282
  */
254
283
  function Computed() {
255
284
  return function (target, propertyKey, descriptor) {
@@ -262,7 +291,6 @@ function Computed() {
262
291
  * @param target The target object.
263
292
  * @param prop The property key.
264
293
  * @param descriptor The property descriptor.
265
- * @param defaultValue The default value to be used if the computed property is not defined.
266
294
  * @returns A property descriptor that replaces the original descriptor with a computed implementation.
267
295
  * @throws Will throw an error if the property is not a getter or if it has a setter.
268
296
  */
@@ -279,7 +307,7 @@ function ComputedDecorator(target, prop, descriptor) {
279
307
  get: function () {
280
308
  const scopeMap = GetScopeMapForInstance(this);
281
309
  if (scopeMap[propertyKey] === undefined) {
282
- const propertyScope = CreateComputedScope(getter.bind(this), new Store_1.StoreSync());
310
+ const propertyScope = CreateNodeScope(getter.bind(this));
283
311
  scopeMap[propertyKey] = [propertyScope, undefined];
284
312
  }
285
313
  return observableScope_1.ObservableScope.Value(scopeMap[propertyKey][0]);
@@ -405,7 +433,7 @@ function ComputedAsyncDecorator(target, prop, descriptor, defaultValue) {
405
433
  get: function () {
406
434
  const scopeMap = GetScopeMapForInstance(this);
407
435
  if (scopeMap[propertyKey] === undefined) {
408
- const propertyScope = CreateComputedScope(getter.bind(this), new Store_1.StoreAsync(), defaultValue);
436
+ const propertyScope = CreateStoreScope(getter.bind(this), new Store_1.StoreAsync(), defaultValue);
409
437
  scopeMap[propertyKey] = [propertyScope, undefined];
410
438
  }
411
439
  return observableScope_1.ObservableScope.Value(scopeMap[propertyKey][0]);
@@ -1,21 +1,3 @@
1
- /**
2
- * Removes all null values from an array starting from a specified index.
3
- * This function modifies the array in-place by shifting non-null elements
4
- * to fill the gaps left by removed null values, then truncating the array.
5
- *
6
- * @param array - The array from which to remove null values. Can contain mixed types including null.
7
- * @param startIndex - The index to start removing null values from (default: 0).
8
- *
9
- * @example
10
- * ```typescript
11
- * const arr = [1, null, 2, null, 3, null, 4];
12
- * RemoveNulls(arr); // Removes all null values, result: [1, 2, 3, 4]
13
- *
14
- * const arr2 = [null, null, 1, null, 2];
15
- * RemoveNulls(arr2, 2); // Starts from index 2, result: [null, null, 1, 2]
16
- * ```
17
- */
18
- export declare function RemoveNulls(array: (unknown | null)[], startIndex?: number): void;
19
1
  export declare function ArrayDiff(source: any[], target: any[]): boolean;
20
2
  /**
21
3
  * Reconciles two sorted arrays by applying add and remove operations to transition
@@ -1,40 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RemoveNulls = RemoveNulls;
4
3
  exports.ArrayDiff = ArrayDiff;
5
4
  exports.ReconcileSortedEmitters = ReconcileSortedEmitters;
6
5
  exports.InsertionSortTuples = InsertionSortTuples;
7
6
  exports.ReconcileSortedArrays = ReconcileSortedArrays;
8
- /**
9
- * Removes all null values from an array starting from a specified index.
10
- * This function modifies the array in-place by shifting non-null elements
11
- * to fill the gaps left by removed null values, then truncating the array.
12
- *
13
- * @param array - The array from which to remove null values. Can contain mixed types including null.
14
- * @param startIndex - The index to start removing null values from (default: 0).
15
- *
16
- * @example
17
- * ```typescript
18
- * const arr = [1, null, 2, null, 3, null, 4];
19
- * RemoveNulls(arr); // Removes all null values, result: [1, 2, 3, 4]
20
- *
21
- * const arr2 = [null, null, 1, null, 2];
22
- * RemoveNulls(arr2, 2); // Starts from index 2, result: [null, null, 1, 2]
23
- * ```
24
- */
25
- function RemoveNulls(array, startIndex = 0) {
26
- let nullIndex = startIndex;
27
- for (; nullIndex < array.length && array[nullIndex] !== null; nullIndex++) { }
28
- let notNullIndex = nullIndex + 1;
29
- for (; notNullIndex < array.length && array[notNullIndex] === null; notNullIndex++) { }
30
- while (notNullIndex < array.length) {
31
- array[nullIndex] = array[notNullIndex];
32
- nullIndex++;
33
- notNullIndex++;
34
- for (; notNullIndex < array.length && array[notNullIndex] === null; notNullIndex++) { }
35
- }
36
- array.splice(nullIndex);
37
- }
38
7
  function ArrayDiff(source, target) {
39
8
  if (source === target)
40
9
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "j-templates",
3
- "version": "7.0.104",
3
+ "version": "8.0.0",
4
4
  "description": "j-templates",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/TypesInCode/jTemplates",