j-templates 7.0.70 → 7.0.71

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
  }
@@ -48,6 +48,10 @@ function CreateStaticScope(initialValue) {
48
48
  };
49
49
  }
50
50
  let scopeQueue = [];
51
+ /**
52
+ * Processes all queued scopes, recomputing dirty scopes and emitting changes.
53
+ * Executed as a microtask to batch updates efficiently.
54
+ */
51
55
  function ProcessScopeQueue() {
52
56
  const queue = scopeQueue;
53
57
  scopeQueue = [];
@@ -61,6 +65,11 @@ function ProcessScopeQueue() {
61
65
  }
62
66
  }
63
67
  }
68
+ /**
69
+ * Queues a scope for batched update processing.
70
+ * Schedules ProcessScopeQueue as a microtask if the queue was empty.
71
+ * @param scope The scope to queue for update.
72
+ */
64
73
  function OnSetQueued(scope) {
65
74
  if (scopeQueue.length === 0)
66
75
  queueMicrotask(ProcessScopeQueue);
@@ -84,6 +93,13 @@ function OnSet(scope) {
84
93
  emitter_1.Emitter.Emit(scope.emitter, scope);
85
94
  return false;
86
95
  }
96
+ /**
97
+ * Registers an emitter using SAME_STRATEGY.
98
+ * If the emitter is already registered at the current index, increments the index.
99
+ * Otherwise, switches to PUSH_STRATEGY and appends the emitter.
100
+ * @param state The current watch state.
101
+ * @param emitter The emitter to register.
102
+ */
87
103
  function RegisterSame(state, emitter) {
88
104
  if (state.emitterIndex < state.emitters.length &&
89
105
  state.emitters[state.emitterIndex] === emitter) {
@@ -91,10 +107,30 @@ function RegisterSame(state, emitter) {
91
107
  return;
92
108
  }
93
109
  state.emitters = state.emitters.slice(0, state.emitterIndex);
94
- state.emitters.push(emitter);
95
- state.emitterIndex = -1;
96
- state.strategy = PUSH_STRATEGY;
110
+ state.strategy = SORTED_STRATEGY;
111
+ RegisterSorted(state, emitter);
97
112
  }
113
+ /**
114
+ * Registers an emitter while maintaining sorted order by emitter ID.
115
+ * If insertion would break sort order, falls back to PUSH_STRATEGY.
116
+ * @param state The current watch state.
117
+ * @param emitter The emitter to register.
118
+ */
119
+ function RegisterSorted(state, emitter) {
120
+ if (state.emitters.length === 0 ||
121
+ state.emitters[state.emitters.length - 1][0] < emitter[0])
122
+ state.emitters.push(emitter);
123
+ else {
124
+ state.strategy = PUSH_STRATEGY;
125
+ RegisterPush(state, emitter);
126
+ }
127
+ }
128
+ /**
129
+ * Registers an emitter using PUSH_STRATEGY.
130
+ * Switches to DISTINCT_STRATEGY when the emitter count exceeds 50 to optimize memory.
131
+ * @param state The current watch state.
132
+ * @param emitter The emitter to register.
133
+ */
98
134
  function RegisterPush(state, emitter) {
99
135
  state.emitters.push(emitter);
100
136
  if (state.emitters.length > 50) {
@@ -112,12 +148,23 @@ function RegisterPush(state, emitter) {
112
148
  state.strategy = DISTINCT_STRATEGY;
113
149
  }
114
150
  }
151
+ /**
152
+ * Registers an emitter using DISTINCT_STRATEGY.
153
+ * Only adds emitters that haven't been registered yet, using an ID set for O(1) lookup.
154
+ * @param state The current watch state.
155
+ * @param emitter The emitter to register.
156
+ */
115
157
  function RegisterDistinct(state, emitter) {
116
158
  if (!state.emitterIds.has(emitter[0])) {
117
159
  state.emitters.push(emitter);
118
160
  state.emitterIds.add(emitter[0]);
119
161
  }
120
162
  }
163
+ /**
164
+ * Routes an emitter to the appropriate registration strategy based on current watch state.
165
+ * Does nothing if not within a watch context.
166
+ * @param emitter The emitter to register.
167
+ */
121
168
  function RegisterEmitter(emitter) {
122
169
  if (watchState === null)
123
170
  return;
@@ -125,6 +172,9 @@ function RegisterEmitter(emitter) {
125
172
  case SAME_STRATEGY:
126
173
  RegisterSame(watchState, emitter);
127
174
  break;
175
+ case SORTED_STRATEGY:
176
+ RegisterSorted(watchState, emitter);
177
+ break;
128
178
  case PUSH_STRATEGY:
129
179
  RegisterPush(watchState, emitter);
130
180
  break;
@@ -154,12 +204,22 @@ function GetScopeValue(scope) {
154
204
  ExecuteScope(scope);
155
205
  return scope.value;
156
206
  }
207
+ /**
208
+ * Strategy constants for optimizing emitter registration during watch operations.
209
+ * Each strategy represents a different approach to tracking and deduplicating dependencies.
210
+ */
157
211
  const SAME_STRATEGY = 1;
158
212
  const PUSH_STRATEGY = 2;
159
- const DISTINCT_STRATEGY = 3;
160
- const SHRINK_STRATEGY = 4;
213
+ const SORTED_STRATEGY = 3;
214
+ const DISTINCT_STRATEGY = 4;
215
+ const SHRINK_STRATEGY = 5;
161
216
  let watchState = null;
162
217
  const watchPool = list_1.List.Create();
218
+ /**
219
+ * Creates a new watch state object, reusing from pool if available.
220
+ * Object pooling reduces GC pressure during frequent watch operations.
221
+ * @returns A new or recycled watch state object.
222
+ */
163
223
  function CreateWatchState() {
164
224
  return (list_1.List.Pop(watchPool) ?? {
165
225
  emitterIndex: 0,
@@ -171,6 +231,11 @@ function CreateWatchState() {
171
231
  strategy: PUSH_STRATEGY,
172
232
  });
173
233
  }
234
+ /**
235
+ * Returns a watch state object to the pool for reuse.
236
+ * Resets all fields to their default values before pooling.
237
+ * @param state The watch state to return to the pool.
238
+ */
174
239
  function ReturnWatchState(state) {
175
240
  state.emitterIndex = 0;
176
241
  state.value = null;
@@ -178,7 +243,7 @@ function ReturnWatchState(state) {
178
243
  state.emitterIds = null;
179
244
  state.currentCalc = null;
180
245
  state.nextCalc = null;
181
- state.strategy = PUSH_STRATEGY;
246
+ state.strategy = SORTED_STRATEGY;
182
247
  list_1.List.Push(watchPool, state);
183
248
  }
184
249
  /**
@@ -304,6 +369,7 @@ function UpdateEmitters(scope, right, strategy) {
304
369
  strategy === PUSH_STRATEGY
305
370
  ? emitter_1.Emitter.Distinct(right)
306
371
  : emitter_1.Emitter.Sort(right);
372
+ case SORTED_STRATEGY:
307
373
  if (scope.emitters === null || scope.emitters.length === 0) {
308
374
  for (let x = 0; x < right.length; x++)
309
375
  emitter_1.Emitter.On(right[x], scope.setCallback);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "j-templates",
3
- "version": "7.0.70",
3
+ "version": "7.0.71",
4
4
  "description": "j-templates",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/TypesInCode/jTemplates",