j-templates 7.0.85 → 7.0.87

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
@@ -174,7 +174,7 @@ function CreateChildrenScope(vnode, children, data = DefaultData) {
174
174
  if ((0, functions_1.IsAsync)(data)) {
175
175
  const asyncData = data;
176
176
  data = function () {
177
- return (0, observableScope_1.CalcScope)(async function () {
177
+ return (0, observableScope_1.InlineScope)(async function () {
178
178
  return asyncData();
179
179
  });
180
180
  };
@@ -46,38 +46,62 @@ interface IDynamicObservableScope<T> {
46
46
  */
47
47
  export type IObservableScope<T> = IStaticObservableScope<T> | IDynamicObservableScope<T>;
48
48
  /**
49
- * Creates a computed scope that acts as a gatekeeper for parent scope emissions.
50
- * If this scope's value doesn't change (=== comparison) it won't emit.
49
+ * Creates an inline computed scope registered as a dependency of the parent.
50
+ * The scope is memoized by ID and reused across reads within the same parent evaluation.
51
+ * Emits on every recomputation — no === gating.
51
52
  *
52
- * Useful for optimizing expensive operations driven by key values (e.g., sort keys).
53
- * Scopes are memoized by ID and reused across multiple reads within a single parent scope evaluation.
54
- * Defaults to "default" ID - provide custom IDs when using multiple calc scopes.
53
+ * Supports async callbacks Promise<T> is resolved and the resolved value is emitted.
55
54
  *
56
55
  * Only works within a watch context (during another scope's execution).
56
+ * Throws if called outside a watch context.
57
+ * Creates non-greedy scopes that emit immediately without === gating.
58
+ * @template T The type of value returned by the callback (extracted from Promise if async).
59
+ * @param callback The function to compute the derived value.
60
+ * @param idOverride Optional custom ID for memoization when using multiple scope calls.
61
+ * @returns The computed value, reusing existing scope if available.
62
+ * @throws Error if called outside a watch context.
63
+ */
64
+ export declare function InlineScope<T>(callback: () => T | Promise<T>, idOverride?: string): T;
65
+ /**
66
+ * Creates a gatekeeper scope that only emits when the value actually changes (===).
67
+ * Prevents unnecessary downstream re-evaluations when the derived value stays the same.
68
+ *
69
+ * Useful for optimizing expensive operations driven by frequently-changing parent state.
70
+ * Scopes are memoized by ID and reused across reads within the same parent evaluation.
71
+ *
72
+ * Supports async callbacks — Promise<T> is resolved and the resolved value is gated.
73
+ *
74
+ * Only works within a watch context (during another scope's execution).
75
+ * Throws if called outside a watch context.
57
76
  * Always creates greedy scopes that batch updates via microtask queue.
58
- * @template T The type of value returned by the callback.
77
+ * @template T The type of value returned by the callback (extracted from Promise if async).
59
78
  * @param callback The function to compute the derived value.
60
- * @param idOverride Optional custom ID for memoization when using multiple calc scopes.
79
+ * @param idOverride Optional custom ID for memoization when using multiple gate calls.
61
80
  * @returns The computed value, reusing existing scope if available.
81
+ * @throws Error if called outside a watch context.
62
82
  */
63
- export declare function CalcScope<T>(callback: () => T, idOverride?: string): T;
83
+ export declare function GateScope<T>(callback: () => T | Promise<T>, idOverride?: string): T;
64
84
  /**
65
85
  * Creates a computed scope that isn't registered as a dependency with the parent.
66
86
  * Use this function to read reactive data without subscribing to changes
67
87
  * to that data.
68
88
  *
69
- * Unlike CalcScope, PeekScope does not register itself as a dependency, meaning
89
+ * Unlike GateScope, PeekScope does not register itself as a dependency, meaning
70
90
  * changes to the data accessed within the callback will not trigger recomputation
71
91
  * of the parent scope. The scope is still memoized by ID within the watch context
72
92
  * to avoid redundant computation during the same evaluation.
73
93
  *
94
+ * Supports async callbacks — Promise<T> is resolved and the resolved value is returned.
95
+ *
74
96
  * Only works within a watch context (during another scope's execution).
75
- * @template T The type of value returned by the callback.
97
+ * Throws if called outside a watch context.
98
+ * @template T The type of value returned by the callback (extracted from Promise if async).
76
99
  * @param callback The function to compute the derived value.
77
- * @param idOverride Optional custom ID for memoization when using multiple peek scopes.
100
+ * @param idOverride Optional custom ID for memoization when using multiple peek calls.
78
101
  * @returns The computed value, reusing existing scope if available.
102
+ * @throws Error if called outside a watch context.
79
103
  */
80
- export declare function PeekScope<T>(callback: () => T, idOverride?: string): T;
104
+ export declare function PeekScope<T>(callback: () => T | Promise<T>, idOverride?: string): T;
81
105
  export declare namespace ObservableScope {
82
106
  /**
83
107
  * Creates a new observable scope from a value function.
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ObservableScope = void 0;
4
- exports.CalcScope = CalcScope;
4
+ exports.InlineScope = InlineScope;
5
+ exports.GateScope = GateScope;
5
6
  exports.PeekScope = PeekScope;
6
7
  const emitter_1 = require("../../Utils/emitter");
7
8
  const functions_1 = require("../../Utils/functions");
@@ -190,7 +191,7 @@ let watchState = null;
190
191
  * Executes a callback while tracking all scope and emitter dependencies.
191
192
  * Creates a watch context that records what was accessed during execution.
192
193
  * @param callback The function to execute while tracking dependencies.
193
- * @param currentCalc Optional map of existing calc scopes to reuse.
194
+ * @param currentCalc Optional map of existing inline scopes to reuse.
194
195
  * @returns The watch state containing tracked dependencies and result.
195
196
  */
196
197
  function WatchFunction(callback, currentCalc, initialEmitters) {
@@ -280,24 +281,53 @@ function ScopeHelper(callback, id, greedy) {
280
281
  return nextScopes[id];
281
282
  }
282
283
  /**
283
- * Creates a computed scope that acts as a gatekeeper for parent scope emissions.
284
- * If this scope's value doesn't change (=== comparison) it won't emit.
284
+ * Creates an inline computed scope registered as a dependency of the parent.
285
+ * The scope is memoized by ID and reused across reads within the same parent evaluation.
286
+ * Emits on every recomputation — no === gating.
285
287
  *
286
- * Useful for optimizing expensive operations driven by key values (e.g., sort keys).
287
- * Scopes are memoized by ID and reused across multiple reads within a single parent scope evaluation.
288
- * Defaults to "default" ID - provide custom IDs when using multiple calc scopes.
288
+ * Supports async callbacks Promise<T> is resolved and the resolved value is emitted.
289
289
  *
290
290
  * Only works within a watch context (during another scope's execution).
291
+ * Throws if called outside a watch context.
292
+ * Creates non-greedy scopes that emit immediately without === gating.
293
+ * @template T The type of value returned by the callback (extracted from Promise if async).
294
+ * @param callback The function to compute the derived value.
295
+ * @param idOverride Optional custom ID for memoization when using multiple scope calls.
296
+ * @returns The computed value, reusing existing scope if available.
297
+ * @throws Error if called outside a watch context.
298
+ */
299
+ function InlineScope(callback, idOverride) {
300
+ if (watchState === null) {
301
+ throw new Error("scope() must be called within a watch context");
302
+ }
303
+ const id = idOverride ?? "scope_default";
304
+ const scope = ScopeHelper(callback, id, false);
305
+ RegisterScope(scope);
306
+ return GetScopeValue(scope);
307
+ }
308
+ /**
309
+ * Creates a gatekeeper scope that only emits when the value actually changes (===).
310
+ * Prevents unnecessary downstream re-evaluations when the derived value stays the same.
311
+ *
312
+ * Useful for optimizing expensive operations driven by frequently-changing parent state.
313
+ * Scopes are memoized by ID and reused across reads within the same parent evaluation.
314
+ *
315
+ * Supports async callbacks — Promise<T> is resolved and the resolved value is gated.
316
+ *
317
+ * Only works within a watch context (during another scope's execution).
318
+ * Throws if called outside a watch context.
291
319
  * Always creates greedy scopes that batch updates via microtask queue.
292
- * @template T The type of value returned by the callback.
320
+ * @template T The type of value returned by the callback (extracted from Promise if async).
293
321
  * @param callback The function to compute the derived value.
294
- * @param idOverride Optional custom ID for memoization when using multiple calc scopes.
322
+ * @param idOverride Optional custom ID for memoization when using multiple gate calls.
295
323
  * @returns The computed value, reusing existing scope if available.
324
+ * @throws Error if called outside a watch context.
296
325
  */
297
- function CalcScope(callback, idOverride) {
298
- if (watchState === null)
299
- return callback();
300
- const id = idOverride ?? "calc_default";
326
+ function GateScope(callback, idOverride) {
327
+ if (watchState === null) {
328
+ throw new Error("gate() must be called within a watch context");
329
+ }
330
+ const id = idOverride ?? "gate_default";
301
331
  const scope = ScopeHelper(callback, id, true);
302
332
  RegisterScope(scope);
303
333
  return GetScopeValue(scope);
@@ -307,20 +337,25 @@ function CalcScope(callback, idOverride) {
307
337
  * Use this function to read reactive data without subscribing to changes
308
338
  * to that data.
309
339
  *
310
- * Unlike CalcScope, PeekScope does not register itself as a dependency, meaning
340
+ * Unlike GateScope, PeekScope does not register itself as a dependency, meaning
311
341
  * changes to the data accessed within the callback will not trigger recomputation
312
342
  * of the parent scope. The scope is still memoized by ID within the watch context
313
343
  * to avoid redundant computation during the same evaluation.
314
344
  *
345
+ * Supports async callbacks — Promise<T> is resolved and the resolved value is returned.
346
+ *
315
347
  * Only works within a watch context (during another scope's execution).
316
- * @template T The type of value returned by the callback.
348
+ * Throws if called outside a watch context.
349
+ * @template T The type of value returned by the callback (extracted from Promise if async).
317
350
  * @param callback The function to compute the derived value.
318
- * @param idOverride Optional custom ID for memoization when using multiple peek scopes.
351
+ * @param idOverride Optional custom ID for memoization when using multiple peek calls.
319
352
  * @returns The computed value, reusing existing scope if available.
353
+ * @throws Error if called outside a watch context.
320
354
  */
321
355
  function PeekScope(callback, idOverride) {
322
- if (watchState === null)
323
- return callback();
356
+ if (watchState === null) {
357
+ throw new Error("peek() must be called within a watch context");
358
+ }
324
359
  const id = idOverride ?? "peek_default";
325
360
  const scope = ScopeHelper(callback, id, false);
326
361
  return GetScopeValue(scope);
@@ -387,8 +422,9 @@ function DestroyScope(scope) {
387
422
  emitter_1.Emitter.Clear(scope.emitter);
388
423
  for (const key in scope.scopes)
389
424
  DestroyScope(scope.scopes[key]);
390
- for (let x = 0; x < scope.emitters.length; x++)
391
- emitter_1.Emitter.Remove(scope.emitters[x], scope.setCallback);
425
+ if (scope.emitters !== null)
426
+ for (let x = 0; x < scope.emitters.length; x++)
427
+ emitter_1.Emitter.Remove(scope.emitters[x], scope.setCallback);
392
428
  scope.value = undefined;
393
429
  scope.scopes = null;
394
430
  scope.emitters = null;
@@ -21,7 +21,7 @@
21
21
  * | getters only (expensive) | @Computed | Cached + object reuse via Store |
22
22
  * | getters only (simple) | @Scope | Cached, but new reference on update |
23
23
  * | getters only (sync, StoreAsync) | @ComputedAsync | Sync getter, StoreAsync caching + object reuse |
24
- * | async functions (direct scope) | ObservableScope.Create(async) | Direct async, new reference, initial null |
24
+ * | async functions (direct scope) | ObservableScope.Create(async) | Direct async, new reference, initial null |
25
25
  * | subscribe to changes | @Watch | Calls method when scope value changes |
26
26
  * | dependency injection | @Inject | Gets value from component injector |
27
27
  * | cleanup on destroy | @Destroy| Calls .Destroy() on component teardown |
@@ -233,7 +233,7 @@ export declare function Computed<T extends WeakKey, K extends keyof T, D extends
233
233
  * Both use synchronous getters and provide object identity preservation.
234
234
  *
235
235
  * **Comparison**:
236
- * | Aspect | @Scope | @Computed | @ComputedAsync | calc(async) + @Scope |
236
+ * | Aspect | @Scope | @Computed | @ComputedAsync | scope(async) + @Scope |
237
237
  * |--------|--------|-----------|----------------|----------------------|
238
238
  * | Caches value | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes (memoized) |
239
239
  * | Getter type | Sync | Sync | Sync | Async supported |
@@ -461,15 +461,15 @@ export declare function Value(): any;
461
461
  * - Object identity matters (array/object references used in templates)
462
462
  * - You need DOM reference preservation to avoid re-renders
463
463
  *
464
- * **Async pattern with @Scope**: Use `calc(async () => ...)` for async operations:
464
+ * **Async pattern with @Scope**: Use `scope(async () => ...)` for async operations:
465
465
  * ```typescript
466
466
  * @Scope()
467
467
  * get CurrentUser() {
468
- * return calc(async () => fetchUser(`/api/user/${this.userId}`));
468
+ * return scope(async () => fetchUser(`/api/user/${this.userId}`));
469
469
  * }
470
470
  * ```
471
- * - `calc` memoizes async operations with ID-based caching
472
- * - Returns Promise initially, resolves when complete
471
+ * - `scope` creates an inline computed scope that resolves the async operation
472
+ * - Returns the resolved value (not a Promise)
473
473
  * - Automatically batches updates via microtask queue
474
474
  * - New reference on each update (no object reuse)
475
475
  *
@@ -486,7 +486,7 @@ export declare function Value(): any;
486
486
  * @see {@link ComputedAsync} for sync getters with StoreAsync backend
487
487
  * @see {@link ObservableNode.ApplyDiff} for how @Computed maintains object identity
488
488
  * @see {@link ObservableScope} for the scope-based reactivity system
489
- * @see {@link calc} for memoized async operations within @Scope
489
+ * @see {@link scope} for inline computed scopes within @Scope
490
490
  */
491
491
  export declare function Scope(): typeof ScopeDecorator;
492
492
  /**
@@ -22,7 +22,7 @@
22
22
  * | getters only (expensive) | @Computed | Cached + object reuse via Store |
23
23
  * | getters only (simple) | @Scope | Cached, but new reference on update |
24
24
  * | getters only (sync, StoreAsync) | @ComputedAsync | Sync getter, StoreAsync caching + object reuse |
25
- * | async functions (direct scope) | ObservableScope.Create(async) | Direct async, new reference, initial null |
25
+ * | async functions (direct scope) | ObservableScope.Create(async) | Direct async, new reference, initial null |
26
26
  * | subscribe to changes | @Watch | Calls method when scope value changes |
27
27
  * | dependency injection | @Inject | Gets value from component injector |
28
28
  * | cleanup on destroy | @Destroy| Calls .Destroy() on component teardown |
@@ -136,10 +136,14 @@ function GetDestroyArrayForPrototype(prototype, create = true) {
136
136
  function CreateComputedScope(getter, store, defaultValue) {
137
137
  const getterScope = observableScope_1.ObservableScope.Create(getter, true);
138
138
  observableScope_1.ObservableScope.Watch(getterScope, (scope) => {
139
- const data = observableScope_1.ObservableScope.Value(scope);
139
+ const data = observableScope_1.ObservableScope.Peek(scope);
140
140
  store.Write(data, "root");
141
141
  });
142
- const propertyScope = observableScope_1.ObservableScope.Create(() => store.Get("root", defaultValue));
142
+ const data = observableScope_1.ObservableScope.Peek(getterScope);
143
+ store.Write(data, "root");
144
+ const propertyScope = observableScope_1.ObservableScope.Create(() => {
145
+ return store.Get("root", defaultValue);
146
+ });
143
147
  observableScope_1.ObservableScope.OnDestroyed(propertyScope, function () {
144
148
  observableScope_1.ObservableScope.Destroy(getterScope);
145
149
  if (store instanceof Store_1.StoreAsync)
@@ -356,7 +360,7 @@ function ComputedDecorator(target, prop, descriptor) {
356
360
  * Both use synchronous getters and provide object identity preservation.
357
361
  *
358
362
  * **Comparison**:
359
- * | Aspect | @Scope | @Computed | @ComputedAsync | calc(async) + @Scope |
363
+ * | Aspect | @Scope | @Computed | @ComputedAsync | scope(async) + @Scope |
360
364
  * |--------|--------|-----------|----------------|----------------------|
361
365
  * | Caches value | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes (memoized) |
362
366
  * | Getter type | Sync | Sync | Sync | Async supported |
@@ -684,15 +688,15 @@ function ValueDecorator(target, propertyKey) {
684
688
  * - Object identity matters (array/object references used in templates)
685
689
  * - You need DOM reference preservation to avoid re-renders
686
690
  *
687
- * **Async pattern with @Scope**: Use `calc(async () => ...)` for async operations:
691
+ * **Async pattern with @Scope**: Use `scope(async () => ...)` for async operations:
688
692
  * ```typescript
689
693
  * @Scope()
690
694
  * get CurrentUser() {
691
- * return calc(async () => fetchUser(`/api/user/${this.userId}`));
695
+ * return scope(async () => fetchUser(`/api/user/${this.userId}`));
692
696
  * }
693
697
  * ```
694
- * - `calc` memoizes async operations with ID-based caching
695
- * - Returns Promise initially, resolves when complete
698
+ * - `scope` creates an inline computed scope that resolves the async operation
699
+ * - Returns the resolved value (not a Promise)
696
700
  * - Automatically batches updates via microtask queue
697
701
  * - New reference on each update (no object reuse)
698
702
  *
@@ -709,7 +713,7 @@ function ValueDecorator(target, propertyKey) {
709
713
  * @see {@link ComputedAsync} for sync getters with StoreAsync backend
710
714
  * @see {@link ObservableNode.ApplyDiff} for how @Computed maintains object identity
711
715
  * @see {@link ObservableScope} for the scope-based reactivity system
712
- * @see {@link calc} for memoized async operations within @Scope
716
+ * @see {@link scope} for inline computed scopes within @Scope
713
717
  */
714
718
  function Scope() {
715
719
  return ScopeDecorator;
package/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export { Component } from './Node/component';
2
- export { CalcScope as calc, PeekScope as peek } from './Store/Tree/observableScope';
2
+ export { InlineScope as scope, GateScope as gate, PeekScope as peek } from './Store/Tree/observableScope';
package/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.peek = exports.calc = exports.Component = void 0;
3
+ exports.peek = exports.gate = exports.scope = exports.Component = void 0;
4
4
  var component_1 = require("./Node/component");
5
5
  Object.defineProperty(exports, "Component", { enumerable: true, get: function () { return component_1.Component; } });
6
6
  var observableScope_1 = require("./Store/Tree/observableScope");
7
- Object.defineProperty(exports, "calc", { enumerable: true, get: function () { return observableScope_1.CalcScope; } });
7
+ Object.defineProperty(exports, "scope", { enumerable: true, get: function () { return observableScope_1.InlineScope; } });
8
+ Object.defineProperty(exports, "gate", { enumerable: true, get: function () { return observableScope_1.GateScope; } });
8
9
  Object.defineProperty(exports, "peek", { enumerable: true, get: function () { return observableScope_1.PeekScope; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "j-templates",
3
- "version": "7.0.85",
3
+ "version": "7.0.87",
4
4
  "description": "j-templates",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/TypesInCode/jTemplates",