@signaltree/core 6.3.0 → 6.4.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/dist/index.js +1 -0
- package/dist/lib/internals/merge-derived.js +59 -0
- package/dist/lib/markers/derived.js +12 -0
- package/dist/lib/signal-tree.js +107 -2
- package/package.json +1 -1
- package/src/index.d.ts +3 -0
- package/src/lib/internals/builder-types.d.ts +13 -0
- package/src/lib/internals/derived-types.d.ts +10 -0
- package/src/lib/internals/merge-derived.d.ts +4 -0
- package/src/lib/markers/derived.d.ts +10 -0
- package/src/lib/markers/index.d.ts +1 -0
- package/src/lib/signal-tree.d.ts +5 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { signalTree } from './lib/signal-tree.js';
|
|
2
2
|
export { ENHANCER_META, entityMap } from './lib/types.js';
|
|
3
|
+
export { derived, isDerivedMarker } from './lib/markers/derived.js';
|
|
3
4
|
export { composeEnhancers, createLazySignalTree, isAnySignal, isNodeAccessor, toWritableSignal } from './lib/utils.js';
|
|
4
5
|
export { createEditSession } from './lib/edit-session.js';
|
|
5
6
|
export { getPathNotifier } from './lib/path-notifier.js';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { computed, isSignal } from '@angular/core';
|
|
2
|
+
import { isDerivedMarker } from '../markers/derived.js';
|
|
3
|
+
|
|
4
|
+
function isSignalLike(value) {
|
|
5
|
+
return isSignal(value);
|
|
6
|
+
}
|
|
7
|
+
function ensurePathAndGetTarget($, path) {
|
|
8
|
+
if (!path) return $;
|
|
9
|
+
const parts = path.split('.');
|
|
10
|
+
let current = $;
|
|
11
|
+
for (const part of parts) {
|
|
12
|
+
if (!(part in current)) {
|
|
13
|
+
current[part] = {};
|
|
14
|
+
}
|
|
15
|
+
current = current[part];
|
|
16
|
+
}
|
|
17
|
+
return current;
|
|
18
|
+
}
|
|
19
|
+
function mergeDerivedState($, derivedDef, path = '') {
|
|
20
|
+
if (!derivedDef || typeof derivedDef !== 'object') {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
for (const [key, value] of Object.entries(derivedDef)) {
|
|
24
|
+
const currentPath = path ? `${path}.${key}` : key;
|
|
25
|
+
if (isDerivedMarker(value)) {
|
|
26
|
+
const target = ensurePathAndGetTarget($, path);
|
|
27
|
+
if (key in target && isSignalLike(target[key])) {
|
|
28
|
+
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
|
29
|
+
console.warn(`SignalTree: Derived "${currentPath}" overwrites source signal. ` + `Consider using a different key to avoid confusion.`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
target[key] = computed(value.factory);
|
|
33
|
+
} else if (isSignalLike(value)) {
|
|
34
|
+
const target = ensurePathAndGetTarget($, path);
|
|
35
|
+
if (key in target && isSignalLike(target[key])) {
|
|
36
|
+
if (typeof ngDevMode === 'undefined' || ngDevMode) {
|
|
37
|
+
console.warn(`SignalTree: Derived signal "${currentPath}" overwrites source signal. ` + `Consider using a different key to avoid confusion.`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
target[key] = value;
|
|
41
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
42
|
+
const target = ensurePathAndGetTarget($, path);
|
|
43
|
+
if (!(key in target)) {
|
|
44
|
+
target[key] = {};
|
|
45
|
+
} else if (isSignalLike(target[key])) {
|
|
46
|
+
throw new Error(`SignalTree: Cannot merge derived object into "${currentPath}" ` + `because source is a signal. Either make source an object or use a different key.`);
|
|
47
|
+
}
|
|
48
|
+
mergeDerivedState($, value, currentPath);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function applyDerivedFactories($, factories) {
|
|
53
|
+
for (const factory of factories) {
|
|
54
|
+
const derivedDef = factory($);
|
|
55
|
+
mergeDerivedState($, derivedDef);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { applyDerivedFactories, mergeDerivedState };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const DERIVED_MARKER = Symbol.for('signaltree:derived');
|
|
2
|
+
function derived(factory) {
|
|
3
|
+
return {
|
|
4
|
+
[DERIVED_MARKER]: true,
|
|
5
|
+
factory
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
function isDerivedMarker(value) {
|
|
9
|
+
return value !== null && typeof value === 'object' && DERIVED_MARKER in value && value[DERIVED_MARKER] === true;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { derived, isDerivedMarker };
|
package/dist/lib/signal-tree.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { signal, isSignal } from '@angular/core';
|
|
2
2
|
import { SIGNAL_TREE_MESSAGES, SIGNAL_TREE_CONSTANTS } from './constants.js';
|
|
3
|
+
import { applyDerivedFactories } from './internals/merge-derived.js';
|
|
3
4
|
import { SignalMemoryManager } from './memory/memory-manager.js';
|
|
4
5
|
import { getPathNotifier } from './path-notifier.js';
|
|
5
6
|
import { SecurityValidator } from './security/security-validator.js';
|
|
@@ -275,8 +276,112 @@ function create(initialState, config) {
|
|
|
275
276
|
}
|
|
276
277
|
return tree;
|
|
277
278
|
}
|
|
278
|
-
function signalTree(initialState,
|
|
279
|
-
|
|
279
|
+
function signalTree(initialState, configOrDerived) {
|
|
280
|
+
const isFactory = typeof configOrDerived === 'function';
|
|
281
|
+
const config = isFactory ? {} : configOrDerived ?? {};
|
|
282
|
+
const baseTree = create(initialState, config);
|
|
283
|
+
const builder = createBuilder(baseTree);
|
|
284
|
+
if (isFactory) {
|
|
285
|
+
return builder.derived(configOrDerived);
|
|
286
|
+
}
|
|
287
|
+
return builder;
|
|
288
|
+
}
|
|
289
|
+
function createBuilder(baseTree) {
|
|
290
|
+
const derivedQueue = [];
|
|
291
|
+
let isFinalized = false;
|
|
292
|
+
const finalize = () => {
|
|
293
|
+
if (isFinalized) return;
|
|
294
|
+
isFinalized = true;
|
|
295
|
+
if (derivedQueue.length > 0) {
|
|
296
|
+
applyDerivedFactories(baseTree.$, derivedQueue);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
const builder = function (arg) {
|
|
300
|
+
if (arguments.length === 0) {
|
|
301
|
+
return baseTree();
|
|
302
|
+
}
|
|
303
|
+
return baseTree(arg);
|
|
304
|
+
};
|
|
305
|
+
builder[NODE_ACCESSOR_SYMBOL] = true;
|
|
306
|
+
Object.defineProperty(builder, 'state', {
|
|
307
|
+
get() {
|
|
308
|
+
finalize();
|
|
309
|
+
return baseTree.state;
|
|
310
|
+
},
|
|
311
|
+
enumerable: false,
|
|
312
|
+
configurable: true
|
|
313
|
+
});
|
|
314
|
+
Object.defineProperty(builder, '$', {
|
|
315
|
+
get() {
|
|
316
|
+
finalize();
|
|
317
|
+
return baseTree.$;
|
|
318
|
+
},
|
|
319
|
+
enumerable: false,
|
|
320
|
+
configurable: true
|
|
321
|
+
});
|
|
322
|
+
Object.defineProperty(builder, 'with', {
|
|
323
|
+
value: function (enhancer) {
|
|
324
|
+
const enhanced = baseTree.with(enhancer);
|
|
325
|
+
const newBuilder = createBuilder(enhanced);
|
|
326
|
+
for (const key of Object.keys(enhanced)) {
|
|
327
|
+
if (key !== '$' && key !== 'state' && key !== 'with' && key !== 'bind' && key !== 'destroy' && key !== 'derived') {
|
|
328
|
+
try {
|
|
329
|
+
newBuilder[key] = enhanced[key];
|
|
330
|
+
} catch {}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
for (const factory of derivedQueue) {
|
|
334
|
+
newBuilder.derived(factory);
|
|
335
|
+
}
|
|
336
|
+
return newBuilder;
|
|
337
|
+
},
|
|
338
|
+
enumerable: false,
|
|
339
|
+
writable: false,
|
|
340
|
+
configurable: true
|
|
341
|
+
});
|
|
342
|
+
if (typeof baseTree.bind === 'function') {
|
|
343
|
+
Object.defineProperty(builder, 'bind', {
|
|
344
|
+
value: baseTree.bind.bind(baseTree),
|
|
345
|
+
enumerable: false,
|
|
346
|
+
writable: false,
|
|
347
|
+
configurable: true
|
|
348
|
+
});
|
|
349
|
+
} else {
|
|
350
|
+
Object.defineProperty(builder, 'bind', {
|
|
351
|
+
value: () => builder,
|
|
352
|
+
enumerable: false,
|
|
353
|
+
writable: false,
|
|
354
|
+
configurable: true
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
if (typeof baseTree.destroy === 'function') {
|
|
358
|
+
Object.defineProperty(builder, 'destroy', {
|
|
359
|
+
value: baseTree.destroy.bind(baseTree),
|
|
360
|
+
enumerable: false,
|
|
361
|
+
writable: true,
|
|
362
|
+
configurable: true
|
|
363
|
+
});
|
|
364
|
+
} else {
|
|
365
|
+
Object.defineProperty(builder, 'destroy', {
|
|
366
|
+
value: () => {},
|
|
367
|
+
enumerable: false,
|
|
368
|
+
writable: true,
|
|
369
|
+
configurable: true
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
Object.defineProperty(builder, 'derived', {
|
|
373
|
+
value: function (factory) {
|
|
374
|
+
if (isFinalized) {
|
|
375
|
+
throw new Error('SignalTree: Cannot add derived() after tree.$ has been accessed. ' + 'Chain all .derived() calls before accessing $.');
|
|
376
|
+
}
|
|
377
|
+
derivedQueue.push(factory);
|
|
378
|
+
return builder;
|
|
379
|
+
},
|
|
380
|
+
enumerable: false,
|
|
381
|
+
writable: false,
|
|
382
|
+
configurable: true
|
|
383
|
+
});
|
|
384
|
+
return builder;
|
|
280
385
|
}
|
|
281
386
|
|
|
282
387
|
export { isNodeAccessor, signalTree };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signaltree/core",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.4.0",
|
|
4
4
|
"description": "Lightweight, type-safe signal-based state management for Angular. Core package providing hierarchical signal trees, basic entity management, and async actions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
package/src/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export { signalTree } from './lib/signal-tree';
|
|
2
2
|
export type { ISignalTree, SignalTree, SignalTreeBase, FullSignalTree, ProdSignalTree, TreeNode, CallableWritableSignal, AccessibleNode, NodeAccessor, Primitive, NotFn, TreeConfig, TreePreset, Enhancer, EnhancerMeta, EnhancerWithMeta, EntitySignal, EntityMapMarker, EntityConfig, MutationOptions, AddOptions, AddManyOptions, TimeTravelEntry, TimeTravelMethods, } from './lib/types';
|
|
3
3
|
export { entityMap } from './lib/types';
|
|
4
|
+
export type { ProcessDerived, DeepMergeTree, DerivedFactory, } from './lib/internals/derived-types';
|
|
5
|
+
export type { SignalTreeBuilder } from './lib/internals/builder-types';
|
|
6
|
+
export { derived, isDerivedMarker, type DerivedMarker, type DerivedType, } from './lib/markers/derived';
|
|
4
7
|
export { equal, deepEqual, isNodeAccessor, isAnySignal, toWritableSignal, parsePath, composeEnhancers, isBuiltInObject, createLazySignalTree, } from './lib/utils';
|
|
5
8
|
export { createEditSession, type EditSession, type UndoRedoHistory, } from './lib/edit-session';
|
|
6
9
|
export { getPathNotifier } from './lib/path-notifier';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ProcessDerived } from './derived-types';
|
|
2
|
+
import type { ISignalTree, TreeNode } from '../types';
|
|
3
|
+
export interface SignalTreeBuilder<TSource, TAccum = TreeNode<TSource>> {
|
|
4
|
+
(): TSource;
|
|
5
|
+
(value: TSource): void;
|
|
6
|
+
(updater: (current: TSource) => TSource): void;
|
|
7
|
+
readonly $: TAccum;
|
|
8
|
+
readonly state: TAccum;
|
|
9
|
+
with<TAdded>(enhancer: (tree: ISignalTree<TSource>) => ISignalTree<TSource> & TAdded): SignalTreeBuilder<TSource, TAccum> & TAdded;
|
|
10
|
+
bind(thisArg?: unknown): (value?: TSource) => TSource | void;
|
|
11
|
+
destroy(): void;
|
|
12
|
+
derived<TDerived extends object>(factory: ($: TAccum) => TDerived): SignalTreeBuilder<TSource, TAccum & ProcessDerived<TDerived>>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Signal } from '@angular/core';
|
|
2
|
+
import type { DerivedMarker } from '../markers/derived';
|
|
3
|
+
import type { TreeNode } from '../types';
|
|
4
|
+
export type ProcessDerived<T> = T extends DerivedMarker<infer R> ? Signal<R> : T extends Signal<infer S> ? Signal<S> : T extends object ? {
|
|
5
|
+
[P in keyof T]: ProcessDerived<T[P]>;
|
|
6
|
+
} : never;
|
|
7
|
+
export type DeepMergeTree<TSource, TDerived> = {
|
|
8
|
+
[K in keyof TSource | keyof TDerived]: K extends keyof TSource ? K extends keyof TDerived ? TSource[K] extends object ? TDerived[K] extends object ? TDerived[K] extends DerivedMarker<infer R> ? Signal<R> : TSource[K] & DeepMergeTree<TSource[K], ProcessDerived<TDerived[K]>> : TSource[K] : ProcessDerived<TDerived[K]> : TSource[K] : K extends keyof TDerived ? ProcessDerived<TDerived[K]> : never;
|
|
9
|
+
};
|
|
10
|
+
export type DerivedFactory<TSource, TDerived> = ($: TreeNode<TSource>) => TDerived;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
declare const DERIVED_MARKER: unique symbol;
|
|
2
|
+
export interface DerivedMarker<T> {
|
|
3
|
+
readonly [DERIVED_MARKER]: true;
|
|
4
|
+
readonly factory: () => T;
|
|
5
|
+
readonly __type?: T;
|
|
6
|
+
}
|
|
7
|
+
export type DerivedType<T> = T extends DerivedMarker<infer R> ? R : never;
|
|
8
|
+
export declare function derived<T>(factory: () => T): DerivedMarker<T>;
|
|
9
|
+
export declare function isDerivedMarker(value: unknown): value is DerivedMarker<unknown>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { derived, isDerivedMarker, getDerivedMarkerSymbol, type DerivedMarker, type DerivedType, } from './derived';
|
package/src/lib/signal-tree.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { SignalTreeBuilder } from './internals/builder-types';
|
|
2
|
+
import { ProcessDerived } from './internals/derived-types';
|
|
3
|
+
import type { TreeNode, TreeConfig, NodeAccessor } from './types';
|
|
2
4
|
export declare function isNodeAccessor(value: unknown): value is NodeAccessor<unknown>;
|
|
3
|
-
export declare function signalTree<T extends object>(initialState: T,
|
|
5
|
+
export declare function signalTree<T extends object, TDerived extends object>(initialState: T, derivedFactory: ($: TreeNode<T>) => TDerived): SignalTreeBuilder<T, TreeNode<T> & ProcessDerived<TDerived>>;
|
|
6
|
+
export declare function signalTree<T extends object>(initialState: T, config?: TreeConfig): SignalTreeBuilder<T, TreeNode<T>>;
|