@sylphx/lens-signals 1.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/dist/index.d.ts +327 -0
- package/dist/index.js +528 -0
- package/package.json +44 -0
- package/src/index.ts +58 -0
- package/src/reactive-store.ts +805 -0
- package/src/signal.ts +159 -0
- package/src/store-types.ts +78 -0
package/src/signal.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sylphx/lens-client - Signal Implementation
|
|
3
|
+
*
|
|
4
|
+
* Re-exports Preact Signals with Lens-specific types and utilities.
|
|
5
|
+
* @see https://preactjs.com/guide/v10/signals/
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
batch as preactBatch,
|
|
10
|
+
computed as preactComputed,
|
|
11
|
+
effect as preactEffect,
|
|
12
|
+
signal as preactSignal,
|
|
13
|
+
} from "@preact/signals-core";
|
|
14
|
+
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// Types
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
/** Subscription callback */
|
|
20
|
+
export type Subscriber<T> = (value: T) => void;
|
|
21
|
+
|
|
22
|
+
/** Unsubscribe function */
|
|
23
|
+
export type Unsubscribe = () => void;
|
|
24
|
+
|
|
25
|
+
/** Read-only signal interface */
|
|
26
|
+
export interface Signal<T> {
|
|
27
|
+
/** Current value (read-only) */
|
|
28
|
+
readonly value: T;
|
|
29
|
+
|
|
30
|
+
/** Subscribe to value changes */
|
|
31
|
+
subscribe(fn: Subscriber<T>): Unsubscribe;
|
|
32
|
+
|
|
33
|
+
/** Get value without tracking */
|
|
34
|
+
peek(): T;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Writable signal interface */
|
|
38
|
+
export interface WritableSignal<T> extends Signal<T> {
|
|
39
|
+
/** Current value (read-write) */
|
|
40
|
+
value: T;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// Factory Functions (Re-exports with our types)
|
|
45
|
+
// =============================================================================
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Create a writable signal
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* const count = signal(0);
|
|
53
|
+
* console.log(count.value); // 0
|
|
54
|
+
*
|
|
55
|
+
* count.value = 1;
|
|
56
|
+
* console.log(count.value); // 1
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export function signal<T>(initial: T): WritableSignal<T> {
|
|
60
|
+
return preactSignal(initial) as WritableSignal<T>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Create a computed signal
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* const count = signal(0);
|
|
69
|
+
* const doubled = computed(() => count.value * 2);
|
|
70
|
+
*
|
|
71
|
+
* console.log(doubled.value); // 0
|
|
72
|
+
*
|
|
73
|
+
* count.value = 5;
|
|
74
|
+
* console.log(doubled.value); // 10
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export function computed<T>(compute: () => T): Signal<T> {
|
|
78
|
+
return preactComputed(compute) as Signal<T>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Run a function whenever dependencies change
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* const count = signal(0);
|
|
87
|
+
* const dispose = effect(() => {
|
|
88
|
+
* console.log('Count:', count.value);
|
|
89
|
+
* });
|
|
90
|
+
*
|
|
91
|
+
* count.value = 1; // Logs: "Count: 1"
|
|
92
|
+
* dispose(); // Stop watching
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export function effect(fn: () => void | (() => void)): Unsubscribe {
|
|
96
|
+
return preactEffect(fn);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Batch multiple signal updates
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```typescript
|
|
104
|
+
* const a = signal(1);
|
|
105
|
+
* const b = signal(2);
|
|
106
|
+
*
|
|
107
|
+
* batch(() => {
|
|
108
|
+
* a.value = 10;
|
|
109
|
+
* b.value = 20;
|
|
110
|
+
* });
|
|
111
|
+
* // Subscribers notified only once
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export function batch<T>(fn: () => T): T {
|
|
115
|
+
return preactBatch(fn);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// =============================================================================
|
|
119
|
+
// Utilities
|
|
120
|
+
// =============================================================================
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Check if a value is a signal
|
|
124
|
+
*/
|
|
125
|
+
export function isSignal(value: unknown): value is Signal<unknown> {
|
|
126
|
+
return (
|
|
127
|
+
value !== null &&
|
|
128
|
+
typeof value === "object" &&
|
|
129
|
+
"value" in value &&
|
|
130
|
+
"peek" in value &&
|
|
131
|
+
"subscribe" in value
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Convert a signal to a promise that resolves when the signal changes
|
|
137
|
+
*/
|
|
138
|
+
export function toPromise<T>(sig: Signal<T>): Promise<T> {
|
|
139
|
+
return new Promise((resolve) => {
|
|
140
|
+
let isFirst = true;
|
|
141
|
+
let unsub: Unsubscribe;
|
|
142
|
+
unsub = sig.subscribe((value) => {
|
|
143
|
+
// Skip the initial value (subscribe fires immediately)
|
|
144
|
+
if (isFirst) {
|
|
145
|
+
isFirst = false;
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
unsub();
|
|
149
|
+
resolve(value);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Create a signal that derives from multiple signals
|
|
156
|
+
*/
|
|
157
|
+
export function derive<T, U>(signals: Signal<T>[], fn: (values: T[]) => U): Signal<U> {
|
|
158
|
+
return computed(() => fn(signals.map((s) => s.value)));
|
|
159
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sylphx/lens-signals - Store Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for ReactiveStore.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PipelineResult } from "@sylphx/reify";
|
|
8
|
+
|
|
9
|
+
/** Entity state with metadata */
|
|
10
|
+
export interface EntityState<T = unknown> {
|
|
11
|
+
/** The entity data */
|
|
12
|
+
data: T | null;
|
|
13
|
+
/** Loading state */
|
|
14
|
+
loading: boolean;
|
|
15
|
+
/** Error state */
|
|
16
|
+
error: Error | null;
|
|
17
|
+
/** Whether data is stale */
|
|
18
|
+
stale: boolean;
|
|
19
|
+
/** Subscription reference count */
|
|
20
|
+
refCount: number;
|
|
21
|
+
/** Cache timestamp */
|
|
22
|
+
cachedAt?: number | undefined;
|
|
23
|
+
/** Cache tags for invalidation */
|
|
24
|
+
tags?: string[] | undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Optimistic update entry */
|
|
28
|
+
export interface OptimisticEntry {
|
|
29
|
+
id: string;
|
|
30
|
+
entityName: string;
|
|
31
|
+
entityId: string;
|
|
32
|
+
type: "create" | "update" | "delete";
|
|
33
|
+
originalData: unknown;
|
|
34
|
+
optimisticData: unknown;
|
|
35
|
+
timestamp: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Multi-entity optimistic transaction */
|
|
39
|
+
export interface OptimisticTransaction {
|
|
40
|
+
id: string;
|
|
41
|
+
/** Pipeline results from Reify execution */
|
|
42
|
+
results: PipelineResult;
|
|
43
|
+
/** Original data for each entity (for rollback) */
|
|
44
|
+
originalData: Map<string, unknown>;
|
|
45
|
+
timestamp: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Store configuration */
|
|
49
|
+
export interface StoreConfig {
|
|
50
|
+
/** Enable optimistic updates (default: true) */
|
|
51
|
+
optimistic?: boolean;
|
|
52
|
+
/** Cache TTL in milliseconds (default: 5 minutes) */
|
|
53
|
+
cacheTTL?: number;
|
|
54
|
+
/** Maximum cache size (default: 1000) */
|
|
55
|
+
maxCacheSize?: number;
|
|
56
|
+
/** Cascade invalidation rules */
|
|
57
|
+
cascadeRules?: CascadeRule[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Cascade invalidation rule */
|
|
61
|
+
export interface CascadeRule {
|
|
62
|
+
/** Source entity type that triggers invalidation */
|
|
63
|
+
source: string;
|
|
64
|
+
/** Operation types that trigger cascade (default: all) */
|
|
65
|
+
operations?: ("create" | "update" | "delete")[];
|
|
66
|
+
/** Target entities to invalidate */
|
|
67
|
+
targets: string[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Invalidation options */
|
|
71
|
+
export interface InvalidationOptions {
|
|
72
|
+
/** Invalidate by tag */
|
|
73
|
+
tags?: string[];
|
|
74
|
+
/** Invalidate by entity type pattern (glob-like) */
|
|
75
|
+
pattern?: string;
|
|
76
|
+
/** Cascade to related entities */
|
|
77
|
+
cascade?: boolean;
|
|
78
|
+
}
|