j-templates 7.0.70 → 7.0.72

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,12 +1,56 @@
1
1
  import { JsonDiffResult } from "../../Utils/json";
2
- export declare const IS_OBSERVABLE_NODE = "____isObservableNode";
3
- export declare const GET_OBSERVABLE_VALUE = "____getObservableValue";
4
- export declare const GET_TO_JSON = "toJSON";
2
+ /**
3
+ * Symbol to identify observable nodes.
4
+ * Used to check if a value is an observable node proxy.
5
+ */
6
+ export declare const IS_OBSERVABLE_NODE: unique symbol;
7
+ /**
8
+ * Symbol to get the raw underlying value from an observable node.
9
+ * Returns the unwrapped, non-proxied value.
10
+ */
11
+ export declare const GET_OBSERVABLE_VALUE: unique symbol;
12
+ /**
13
+ * Symbol for the toJSON method on observable nodes.
14
+ * Used to serialize observable nodes to plain JSON.
15
+ */
16
+ export declare const GET_TO_JSON: unique symbol;
5
17
  export declare namespace ObservableNode {
6
- function BypassProxy(value: boolean): void;
18
+ /**
19
+ * Unwraps an observable node to get the raw underlying value.
20
+ * Recursively unwraps nested objects and arrays.
21
+ * @template T The type of value to unwrap.
22
+ * @param value The value to unwrap, which may be an observable node or plain value.
23
+ * @returns The unwrapped raw value without proxy wrappers.
24
+ */
7
25
  function Unwrap<T>(value: T): T;
26
+ /**
27
+ * Creates an observable node from a plain value.
28
+ * Wraps the value in a proxy that tracks changes and enables reactive updates.
29
+ * @template T The type of value to wrap.
30
+ * @param value The plain value (object, array, or primitive) to make observable.
31
+ * @returns A proxied version of the value that emits change events.
32
+ */
8
33
  function Create<T>(value: T): T;
34
+ /**
35
+ * Marks an observable node or its property as changed, triggering reactive updates.
36
+ * Used internally to notify dependencies that a value has been modified.
37
+ * @param value The observable node to touch.
38
+ * @param prop Optional property name or index to touch a specific nested property.
39
+ */
9
40
  function Touch(value: unknown, prop?: string | number): void;
41
+ /**
42
+ * Applies a JSON diff result to an observable node, efficiently updating only changed properties.
43
+ * Optimizes nested object updates by computing paths incrementally and touching modified properties.
44
+ * @param rootNode The observable node to apply the diff to.
45
+ * @param diffResult The diff result from JsonDiff containing path-value pairs of changes.
46
+ */
10
47
  function ApplyDiff(rootNode: any, diffResult: JsonDiffResult): void;
48
+ /**
49
+ * Creates a factory function for making values observable with optional aliasing.
50
+ * The alias function transforms values before creating the observable proxy,
51
+ * useful for read-only views or value transformations.
52
+ * @param alias Optional function to transform values before making them observable.
53
+ * @returns A function that creates observable nodes from plain values.
54
+ */
11
55
  function CreateFactory(alias?: (value: any) => any | undefined): <T>(value: T) => T;
12
56
  }
@@ -4,10 +4,21 @@ exports.ObservableNode = exports.GET_TO_JSON = exports.GET_OBSERVABLE_VALUE = ex
4
4
  const json_1 = require("../../Utils/json");
5
5
  const jsonType_1 = require("../../Utils/jsonType");
6
6
  const observableScope_1 = require("./observableScope");
7
- let bypassProxy = false;
8
- exports.IS_OBSERVABLE_NODE = "____isObservableNode";
9
- exports.GET_OBSERVABLE_VALUE = "____getObservableValue";
10
- exports.GET_TO_JSON = "toJSON";
7
+ /**
8
+ * Symbol to identify observable nodes.
9
+ * Used to check if a value is an observable node proxy.
10
+ */
11
+ exports.IS_OBSERVABLE_NODE = Symbol("isObservableNode");
12
+ /**
13
+ * Symbol to get the raw underlying value from an observable node.
14
+ * Returns the unwrapped, non-proxied value.
15
+ */
16
+ exports.GET_OBSERVABLE_VALUE = Symbol("getObservableValue");
17
+ /**
18
+ * Symbol for the toJSON method on observable nodes.
19
+ * Used to serialize observable nodes to plain JSON.
20
+ */
21
+ exports.GET_TO_JSON = Symbol("toJSON");
11
22
  const proxyCache = new WeakMap();
12
23
  const scopeCache = new WeakMap();
13
24
  const leafScopeCache = new WeakMap();
@@ -40,7 +51,7 @@ function ownKeysArray(value) {
40
51
  function UnwrapProxy(value, type = (0, jsonType_1.JsonType)(value)) {
41
52
  if (type === "value")
42
53
  return value;
43
- if (value[exports.IS_OBSERVABLE_NODE] === true)
54
+ if (value[exports.IS_OBSERVABLE_NODE])
44
55
  return value[exports.GET_OBSERVABLE_VALUE];
45
56
  switch (type) {
46
57
  case "object": {
@@ -114,8 +125,6 @@ function CreateProxyFactory(alias) {
114
125
  function ArrayProxySetter(array, prop, value) {
115
126
  if (readOnly)
116
127
  throw `Object is readonly`;
117
- if (prop === exports.IS_OBSERVABLE_NODE)
118
- throw `Cannot assign read-only property: ${exports.IS_OBSERVABLE_NODE}`;
119
128
  value = UnwrapProxy(value);
120
129
  array[prop] = value;
121
130
  const scope = scopeCache.get(array);
@@ -147,8 +156,6 @@ function CreateProxyFactory(alias) {
147
156
  const scope = scopeCache.get(array);
148
157
  array = observableScope_1.ObservableScope.Value(scope);
149
158
  const arrayValue = array[prop];
150
- if (bypassProxy)
151
- return arrayValue;
152
159
  if (typeof prop === "symbol")
153
160
  return arrayValue;
154
161
  if (typeof arrayValue === "function")
@@ -188,8 +195,6 @@ function CreateProxyFactory(alias) {
188
195
  function ObjectProxySetter(object, prop, value) {
189
196
  if (readOnly)
190
197
  throw `Object is readonly`;
191
- if (prop === exports.IS_OBSERVABLE_NODE)
192
- throw `Cannot assign read-only property: ${exports.IS_OBSERVABLE_NODE}`;
193
198
  const jsonType = (0, jsonType_1.JsonType)(value);
194
199
  if (jsonType === "value") {
195
200
  value !== object[prop] && SetPropertyValue(object, prop, value);
@@ -224,8 +229,6 @@ function CreateProxyFactory(alias) {
224
229
  case exports.GET_OBSERVABLE_VALUE:
225
230
  return object;
226
231
  default: {
227
- if (bypassProxy)
228
- return object[prop];
229
232
  return GetAccessorValue(object, prop);
230
233
  }
231
234
  }
@@ -264,18 +267,34 @@ function CreateProxyFactory(alias) {
264
267
  const DefaultCreateProxy = CreateProxyFactory();
265
268
  var ObservableNode;
266
269
  (function (ObservableNode) {
267
- function BypassProxy(value) {
268
- bypassProxy = value;
269
- }
270
- ObservableNode.BypassProxy = BypassProxy;
270
+ /**
271
+ * Unwraps an observable node to get the raw underlying value.
272
+ * Recursively unwraps nested objects and arrays.
273
+ * @template T The type of value to unwrap.
274
+ * @param value The value to unwrap, which may be an observable node or plain value.
275
+ * @returns The unwrapped raw value without proxy wrappers.
276
+ */
271
277
  function Unwrap(value) {
272
278
  return UnwrapProxy(value);
273
279
  }
274
280
  ObservableNode.Unwrap = Unwrap;
281
+ /**
282
+ * Creates an observable node from a plain value.
283
+ * Wraps the value in a proxy that tracks changes and enables reactive updates.
284
+ * @template T The type of value to wrap.
285
+ * @param value The plain value (object, array, or primitive) to make observable.
286
+ * @returns A proxied version of the value that emits change events.
287
+ */
275
288
  function Create(value) {
276
289
  return DefaultCreateProxy(value);
277
290
  }
278
291
  ObservableNode.Create = Create;
292
+ /**
293
+ * Marks an observable node or its property as changed, triggering reactive updates.
294
+ * Used internally to notify dependencies that a value has been modified.
295
+ * @param value The observable node to touch.
296
+ * @param prop Optional property name or index to touch a specific nested property.
297
+ */
279
298
  function Touch(value, prop) {
280
299
  let scope;
281
300
  if (prop !== undefined) {
@@ -286,6 +305,12 @@ var ObservableNode;
286
305
  observableScope_1.ObservableScope.Update(scope);
287
306
  }
288
307
  ObservableNode.Touch = Touch;
308
+ /**
309
+ * Applies a JSON diff result to an observable node, efficiently updating only changed properties.
310
+ * Optimizes nested object updates by computing paths incrementally and touching modified properties.
311
+ * @param rootNode The observable node to apply the diff to.
312
+ * @param diffResult The diff result from JsonDiff containing path-value pairs of changes.
313
+ */
289
314
  function ApplyDiff(rootNode, diffResult) {
290
315
  const root = rootNode[exports.GET_OBSERVABLE_VALUE];
291
316
  const pathTuples = [["", root]];
@@ -312,6 +337,13 @@ var ObservableNode;
312
337
  }
313
338
  }
314
339
  ObservableNode.ApplyDiff = ApplyDiff;
340
+ /**
341
+ * Creates a factory function for making values observable with optional aliasing.
342
+ * The alias function transforms values before creating the observable proxy,
343
+ * useful for read-only views or value transformations.
344
+ * @param alias Optional function to transform values before making them observable.
345
+ * @returns A function that creates observable nodes from plain values.
346
+ */
315
347
  function CreateFactory(alias) {
316
348
  return CreateProxyFactory(alias);
317
349
  }
@@ -23,15 +23,14 @@ function CreateDynamicScope(getFunction, greedy, value) {
23
23
  dirty: false,
24
24
  destroyed: false,
25
25
  getFunction,
26
- setCallback: function () {
27
- return OnSet(scope);
28
- },
26
+ setCallback: null,
29
27
  value,
30
28
  emitter: emitter_1.Emitter.Create(),
31
29
  emitters: null,
32
30
  onDestroyed: null,
33
31
  calcScopes: null,
34
32
  };
33
+ scope.setCallback = OnSet.bind(scope);
35
34
  return scope;
36
35
  }
37
36
  /**
@@ -48,6 +47,10 @@ function CreateStaticScope(initialValue) {
48
47
  };
49
48
  }
50
49
  let scopeQueue = [];
50
+ /**
51
+ * Processes all queued scopes, recomputing dirty scopes and emitting changes.
52
+ * Executed as a microtask to batch updates efficiently.
53
+ */
51
54
  function ProcessScopeQueue() {
52
55
  const queue = scopeQueue;
53
56
  scopeQueue = [];
@@ -61,6 +64,11 @@ function ProcessScopeQueue() {
61
64
  }
62
65
  }
63
66
  }
67
+ /**
68
+ * Queues a scope for batched update processing.
69
+ * Schedules ProcessScopeQueue as a microtask if the queue was empty.
70
+ * @param scope The scope to queue for update.
71
+ */
64
72
  function OnSetQueued(scope) {
65
73
  if (scopeQueue.length === 0)
66
74
  queueMicrotask(ProcessScopeQueue);
@@ -71,19 +79,23 @@ function OnSetQueued(scope) {
71
79
  * @param scope The scope to mark for update.
72
80
  * @returns true if the scope is static or destroyed, false if queued or non-greedy scope emitted.
73
81
  */
74
- function OnSet(scope) {
75
- if (scope.type === "static" || scope.destroyed)
76
- return true;
77
- if (scope.dirty)
78
- return false;
79
- scope.dirty = true;
80
- if (scope.greedy) {
81
- OnSetQueued(scope);
82
+ function OnSet() {
83
+ if (this.dirty)
84
+ return;
85
+ this.dirty = true;
86
+ if (this.greedy) {
87
+ OnSetQueued(this);
82
88
  return;
83
89
  }
84
- emitter_1.Emitter.Emit(scope.emitter, scope);
85
- return false;
90
+ emitter_1.Emitter.Emit(this.emitter, this);
86
91
  }
92
+ /**
93
+ * Registers an emitter using SAME_STRATEGY.
94
+ * If the emitter is already registered at the current index, increments the index.
95
+ * Otherwise, switches to PUSH_STRATEGY and appends the emitter.
96
+ * @param state The current watch state.
97
+ * @param emitter The emitter to register.
98
+ */
87
99
  function RegisterSame(state, emitter) {
88
100
  if (state.emitterIndex < state.emitters.length &&
89
101
  state.emitters[state.emitterIndex] === emitter) {
@@ -91,10 +103,30 @@ function RegisterSame(state, emitter) {
91
103
  return;
92
104
  }
93
105
  state.emitters = state.emitters.slice(0, state.emitterIndex);
94
- state.emitters.push(emitter);
95
- state.emitterIndex = -1;
96
- state.strategy = PUSH_STRATEGY;
106
+ state.strategy = SORTED_STRATEGY;
107
+ RegisterSorted(state, emitter);
97
108
  }
109
+ /**
110
+ * Registers an emitter while maintaining sorted order by emitter ID.
111
+ * If insertion would break sort order, falls back to PUSH_STRATEGY.
112
+ * @param state The current watch state.
113
+ * @param emitter The emitter to register.
114
+ */
115
+ function RegisterSorted(state, emitter) {
116
+ if (state.emitters.length === 0 ||
117
+ state.emitters[state.emitters.length - 1][0] < emitter[0])
118
+ state.emitters.push(emitter);
119
+ else {
120
+ state.strategy = PUSH_STRATEGY;
121
+ RegisterPush(state, emitter);
122
+ }
123
+ }
124
+ /**
125
+ * Registers an emitter using PUSH_STRATEGY.
126
+ * Switches to DISTINCT_STRATEGY when the emitter count exceeds 50 to optimize memory.
127
+ * @param state The current watch state.
128
+ * @param emitter The emitter to register.
129
+ */
98
130
  function RegisterPush(state, emitter) {
99
131
  state.emitters.push(emitter);
100
132
  if (state.emitters.length > 50) {
@@ -112,12 +144,23 @@ function RegisterPush(state, emitter) {
112
144
  state.strategy = DISTINCT_STRATEGY;
113
145
  }
114
146
  }
147
+ /**
148
+ * Registers an emitter using DISTINCT_STRATEGY.
149
+ * Only adds emitters that haven't been registered yet, using an ID set for O(1) lookup.
150
+ * @param state The current watch state.
151
+ * @param emitter The emitter to register.
152
+ */
115
153
  function RegisterDistinct(state, emitter) {
116
154
  if (!state.emitterIds.has(emitter[0])) {
117
155
  state.emitters.push(emitter);
118
156
  state.emitterIds.add(emitter[0]);
119
157
  }
120
158
  }
159
+ /**
160
+ * Routes an emitter to the appropriate registration strategy based on current watch state.
161
+ * Does nothing if not within a watch context.
162
+ * @param emitter The emitter to register.
163
+ */
121
164
  function RegisterEmitter(emitter) {
122
165
  if (watchState === null)
123
166
  return;
@@ -125,6 +168,9 @@ function RegisterEmitter(emitter) {
125
168
  case SAME_STRATEGY:
126
169
  RegisterSame(watchState, emitter);
127
170
  break;
171
+ case SORTED_STRATEGY:
172
+ RegisterSorted(watchState, emitter);
173
+ break;
128
174
  case PUSH_STRATEGY:
129
175
  RegisterPush(watchState, emitter);
130
176
  break;
@@ -154,12 +200,22 @@ function GetScopeValue(scope) {
154
200
  ExecuteScope(scope);
155
201
  return scope.value;
156
202
  }
203
+ /**
204
+ * Strategy constants for optimizing emitter registration during watch operations.
205
+ * Each strategy represents a different approach to tracking and deduplicating dependencies.
206
+ */
157
207
  const SAME_STRATEGY = 1;
158
208
  const PUSH_STRATEGY = 2;
159
- const DISTINCT_STRATEGY = 3;
160
- const SHRINK_STRATEGY = 4;
209
+ const SORTED_STRATEGY = 3;
210
+ const DISTINCT_STRATEGY = 4;
211
+ const SHRINK_STRATEGY = 5;
161
212
  let watchState = null;
162
213
  const watchPool = list_1.List.Create();
214
+ /**
215
+ * Creates a new watch state object, reusing from pool if available.
216
+ * Object pooling reduces GC pressure during frequent watch operations.
217
+ * @returns A new or recycled watch state object.
218
+ */
163
219
  function CreateWatchState() {
164
220
  return (list_1.List.Pop(watchPool) ?? {
165
221
  emitterIndex: 0,
@@ -171,6 +227,11 @@ function CreateWatchState() {
171
227
  strategy: PUSH_STRATEGY,
172
228
  });
173
229
  }
230
+ /**
231
+ * Returns a watch state object to the pool for reuse.
232
+ * Resets all fields to their default values before pooling.
233
+ * @param state The watch state to return to the pool.
234
+ */
174
235
  function ReturnWatchState(state) {
175
236
  state.emitterIndex = 0;
176
237
  state.value = null;
@@ -178,7 +239,7 @@ function ReturnWatchState(state) {
178
239
  state.emitterIds = null;
179
240
  state.currentCalc = null;
180
241
  state.nextCalc = null;
181
- state.strategy = PUSH_STRATEGY;
242
+ state.strategy = SORTED_STRATEGY;
182
243
  list_1.List.Push(watchPool, state);
183
244
  }
184
245
  /**
@@ -304,6 +365,7 @@ function UpdateEmitters(scope, right, strategy) {
304
365
  strategy === PUSH_STRATEGY
305
366
  ? emitter_1.Emitter.Distinct(right)
306
367
  : emitter_1.Emitter.Sort(right);
368
+ case SORTED_STRATEGY:
307
369
  if (scope.emitters === null || scope.emitters.length === 0) {
308
370
  for (let x = 0; x < right.length; x++)
309
371
  emitter_1.Emitter.On(right[x], scope.setCallback);
@@ -335,9 +397,9 @@ function DestroyAllScopes(scopes) {
335
397
  function DestroyScope(scope) {
336
398
  if (!scope || scope.type === "static")
337
399
  return;
338
- emitter_1.Emitter.Clear(scope.emitter);
339
- const scopes = scope.calcScopes && Object.values(scope.calcScopes);
340
- scopes && DestroyAllScopes(scopes);
400
+ emitter_1.Emitter.Destroy(scope.emitter);
401
+ for (const key in scope.calcScopes)
402
+ DestroyScope(scope.calcScopes[key]);
341
403
  scope.calcScopes = null;
342
404
  for (let x = 0; x < scope.emitters.length; x++)
343
405
  emitter_1.Emitter.Remove(scope.emitters[x], scope.setCallback);
@@ -447,9 +509,10 @@ var ObservableScope;
447
509
  * @param scope The scope to mark for update.
448
510
  */
449
511
  function Update(scope) {
450
- if (!scope)
512
+ if (!scope || scope.type === "static")
451
513
  return;
452
- OnSet(scope);
514
+ // OnSet(scope);
515
+ scope.setCallback();
453
516
  }
454
517
  ObservableScope.Update = Update;
455
518
  /**
@@ -1,10 +1,11 @@
1
- export type EmitterCallback<T extends readonly any[] = any[]> = (...args: T) => boolean | void;
1
+ export type EmitterCallback<T extends readonly any[] = any[]> = (...args: T) => void;
2
2
  export type Emitter = [number, ...EmitterCallback[]];
3
3
  export declare namespace Emitter {
4
4
  function Create(): Emitter;
5
5
  function GetId(emitter: Emitter): number;
6
6
  function On(emitter: Emitter, callback: EmitterCallback): void;
7
7
  function Emit(emitter: Emitter, ...args: any[]): void;
8
+ function Destroy(emitter: Emitter): void;
8
9
  function Remove(emitter: Emitter, callback: EmitterCallback): void;
9
10
  function Clear(emitter: Emitter): void;
10
11
  function Distinct(emitters: Emitter[]): void;
package/Utils/emitter.js CHANGED
@@ -19,17 +19,26 @@ var Emitter;
19
19
  }
20
20
  Emitter.On = On;
21
21
  function Emit(emitter, ...args) {
22
+ if (emitter[0] === -1)
23
+ return;
22
24
  let writePos = 1;
23
25
  for (let x = 1; x < emitter.length; x++) {
24
- if (emitter[x] !== null &&
25
- emitter[x](...args) !== true)
26
+ if (emitter[x] !== null) {
27
+ emitter[x](...args);
26
28
  emitter[writePos++] = emitter[x];
29
+ }
27
30
  }
28
31
  if (writePos < emitter.length)
29
32
  emitter.splice(writePos);
30
33
  }
31
34
  Emitter.Emit = Emit;
35
+ function Destroy(emitter) {
36
+ emitter[0] = -1;
37
+ }
38
+ Emitter.Destroy = Destroy;
32
39
  function Remove(emitter, callback) {
40
+ if (emitter[0] === -1)
41
+ return;
33
42
  const index = emitter.indexOf(callback);
34
43
  if (index >= 1)
35
44
  emitter[index] = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "j-templates",
3
- "version": "7.0.70",
3
+ "version": "7.0.72",
4
4
  "description": "j-templates",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/TypesInCode/jTemplates",