@rplx/core 0.2.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/README.md +132 -0
- package/dist/index.d.mts +237 -0
- package/dist/index.d.ts +237 -0
- package/dist/index.js +1132 -0
- package/dist/index.mjs +1096 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# @rplx/core
|
|
2
|
+
|
|
3
|
+
A re-frame inspired state management library for TypeScript.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @rplx/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createStore } from '@rplx/core'
|
|
15
|
+
|
|
16
|
+
// Define your state
|
|
17
|
+
interface AppState {
|
|
18
|
+
count: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Define your coeffects (optional)
|
|
22
|
+
interface AppCoeffects {
|
|
23
|
+
uuid: string
|
|
24
|
+
now: Date
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Create store with coeffect providers
|
|
28
|
+
const store = createStore<AppState, AppCoeffects>({
|
|
29
|
+
initialState: { count: 0 },
|
|
30
|
+
coeffects: {
|
|
31
|
+
uuid: () => crypto.randomUUID(),
|
|
32
|
+
now: () => new Date()
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
// Register event handlers - fully type-safe!
|
|
37
|
+
store.registerEvent('increment', (context, payload) => {
|
|
38
|
+
// context.db is AppState
|
|
39
|
+
// context.uuid is string
|
|
40
|
+
// context.now is Date
|
|
41
|
+
return [
|
|
42
|
+
{ count: context.db.count + 1 },
|
|
43
|
+
{} // no effects
|
|
44
|
+
]
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
// Dispatch events
|
|
48
|
+
await store.dispatch('increment')
|
|
49
|
+
|
|
50
|
+
// Get state
|
|
51
|
+
const state = store.getState()
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Core Concepts
|
|
55
|
+
|
|
56
|
+
### Events
|
|
57
|
+
Events are dispatched to trigger state changes. Each event has a handler that receives the current state (via context) and a payload, and returns a new state and effects.
|
|
58
|
+
|
|
59
|
+
### Coeffects (Context)
|
|
60
|
+
Coeffects are the **inputs** to event handlers. Define your coeffect types and provide functions to compute them:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
interface MyCoeffects {
|
|
64
|
+
uuid: string
|
|
65
|
+
now: Date
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const store = createStore<AppState, MyCoeffects>({
|
|
69
|
+
initialState,
|
|
70
|
+
coeffects: {
|
|
71
|
+
uuid: () => crypto.randomUUID(),
|
|
72
|
+
now: () => new Date()
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// Handlers get type-safe access
|
|
77
|
+
store.registerEvent('create', (context, data) => {
|
|
78
|
+
const { db, uuid, now } = context // ✅ Fully typed!
|
|
79
|
+
return [newState, effects]
|
|
80
|
+
})
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Benefits:**
|
|
84
|
+
- ✅ Full type safety
|
|
85
|
+
- ✅ Easy to test (mock coeffect providers)
|
|
86
|
+
- ✅ Lazy evaluation
|
|
87
|
+
|
|
88
|
+
### Effects
|
|
89
|
+
Effects are side effects that can be triggered by event handlers. Common effects include:
|
|
90
|
+
- `dispatch` - Dispatch another event
|
|
91
|
+
- `dispatch-n` - Dispatch multiple events
|
|
92
|
+
- Custom effects (HTTP requests, localStorage, etc.)
|
|
93
|
+
|
|
94
|
+
### Interceptors
|
|
95
|
+
Interceptors wrap event handlers to add cross-cutting concerns. They have `:before` and `:after` phases that run in opposite order (like middleware):
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { path, debug } from '@rplx/core'
|
|
99
|
+
|
|
100
|
+
store.registerEventWithInterceptors(
|
|
101
|
+
'update-todo',
|
|
102
|
+
[path(['todos']), debug()],
|
|
103
|
+
handler
|
|
104
|
+
)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## API
|
|
108
|
+
|
|
109
|
+
### `createStore<State, Cofx>(config)`
|
|
110
|
+
|
|
111
|
+
Factory function to create a store instance. Generic over State and Coeffects.
|
|
112
|
+
|
|
113
|
+
**Parameters:**
|
|
114
|
+
- `config.initialState` - Initial application state
|
|
115
|
+
- `config.coeffects` - Optional coeffect providers
|
|
116
|
+
- `config.tracing` - Optional tracing configuration
|
|
117
|
+
|
|
118
|
+
**Returns:** A store instance with the following methods:
|
|
119
|
+
- `registerEventDb<Payload>(eventKey, handler, interceptors?)` - Register an event handler that returns state
|
|
120
|
+
- `registerEvent<Payload>(eventKey, handler, interceptors?)` - Register an event handler that returns effects
|
|
121
|
+
- `registerEffect<Config>(effectType, handler)` - Register an effect handler
|
|
122
|
+
- `dispatch<Payload>(eventKey, payload)` - Dispatch an event
|
|
123
|
+
- `getState()` - Get current state
|
|
124
|
+
- `flush()` - Flush event queue (for testing)
|
|
125
|
+
- `registerSubscription(key, config)` - Register a subscription
|
|
126
|
+
- `subscribe(key, params, callback)` - Subscribe to state changes
|
|
127
|
+
- `query(key, params)` - Query a subscription once
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT
|
|
132
|
+
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
interface EffectMap {
|
|
2
|
+
db?: any;
|
|
3
|
+
dispatch?: {
|
|
4
|
+
event: string;
|
|
5
|
+
payload: any;
|
|
6
|
+
};
|
|
7
|
+
'dispatch-n'?: Array<{
|
|
8
|
+
event: string;
|
|
9
|
+
payload: any;
|
|
10
|
+
}>;
|
|
11
|
+
'dispatch-later'?: Array<{
|
|
12
|
+
ms: number;
|
|
13
|
+
event: string;
|
|
14
|
+
payload: any;
|
|
15
|
+
}>;
|
|
16
|
+
fx?: Array<[string, any] | null>;
|
|
17
|
+
'deregister-event-handler'?: string | string[];
|
|
18
|
+
}
|
|
19
|
+
type Context<State, Cofx = {}> = {
|
|
20
|
+
db: State;
|
|
21
|
+
event?: any;
|
|
22
|
+
} & Cofx;
|
|
23
|
+
type EventHandlerDb<State, Cofx = {}, Payload = any> = (context: Context<State, Cofx>, payload: Payload) => State;
|
|
24
|
+
type EventHandlerFx<State, Cofx = {}, Payload = any> = (context: Context<State, Cofx>, payload: Payload) => EffectMap;
|
|
25
|
+
interface ErrorContext {
|
|
26
|
+
eventKey: string;
|
|
27
|
+
payload: any;
|
|
28
|
+
phase: 'interceptor' | 'effect' | 'subscription';
|
|
29
|
+
interceptor?: {
|
|
30
|
+
id?: string;
|
|
31
|
+
direction: 'before' | 'after';
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
interface ErrorHandlerConfig {
|
|
35
|
+
/** Whether to re-throw the error after handling (default: false) */
|
|
36
|
+
rethrow?: boolean;
|
|
37
|
+
}
|
|
38
|
+
type ErrorHandler = (error: Error, context: ErrorContext, config: ErrorHandlerConfig) => void | Promise<void>;
|
|
39
|
+
type EffectHandler<Config = any> = (config: Config, store: any) => Promise<void> | void;
|
|
40
|
+
type CoeffectProviders<Cofx> = {
|
|
41
|
+
[K in keyof Cofx]: () => Cofx[K];
|
|
42
|
+
};
|
|
43
|
+
interface EffectExecutionTrace {
|
|
44
|
+
effectType: string;
|
|
45
|
+
config: any;
|
|
46
|
+
start: number;
|
|
47
|
+
end: number;
|
|
48
|
+
duration: number;
|
|
49
|
+
error?: Error;
|
|
50
|
+
}
|
|
51
|
+
interface EventTrace<State = any> {
|
|
52
|
+
id: number;
|
|
53
|
+
eventKey: string;
|
|
54
|
+
payload: any;
|
|
55
|
+
timestamp: number;
|
|
56
|
+
stateBefore: State;
|
|
57
|
+
stateAfter: State;
|
|
58
|
+
interceptors: Array<{
|
|
59
|
+
id?: string;
|
|
60
|
+
order: number;
|
|
61
|
+
}>;
|
|
62
|
+
effectMap: EffectMap;
|
|
63
|
+
effectsExecuted: EffectExecutionTrace[];
|
|
64
|
+
duration: number;
|
|
65
|
+
error?: Error;
|
|
66
|
+
}
|
|
67
|
+
type TraceCallback<State = any> = (traces: EventTrace<State>[]) => void;
|
|
68
|
+
interface StoreConfig<State, Cofx = {}> {
|
|
69
|
+
initialState: State;
|
|
70
|
+
coeffects?: CoeffectProviders<Cofx>;
|
|
71
|
+
onStateChange?: (state: State) => void;
|
|
72
|
+
/** Error handler configuration */
|
|
73
|
+
errorHandler?: {
|
|
74
|
+
handler?: ErrorHandler;
|
|
75
|
+
rethrow?: boolean;
|
|
76
|
+
};
|
|
77
|
+
/** Tracing configuration */
|
|
78
|
+
tracing?: {
|
|
79
|
+
enabled?: boolean;
|
|
80
|
+
debounceTime?: number;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
interface QueuedEvent {
|
|
84
|
+
eventKey: string;
|
|
85
|
+
payload: any;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface InterceptorContext<State, Cofx = {}> {
|
|
89
|
+
coeffects: Context<State, Cofx>;
|
|
90
|
+
effects: EffectMap;
|
|
91
|
+
queue: Interceptor<State, Cofx>[];
|
|
92
|
+
stack: Interceptor<State, Cofx>[];
|
|
93
|
+
}
|
|
94
|
+
interface Interceptor<State, Cofx = {}> {
|
|
95
|
+
id?: string;
|
|
96
|
+
before?: (context: InterceptorContext<State, Cofx>) => InterceptorContext<State, Cofx>;
|
|
97
|
+
after?: (context: InterceptorContext<State, Cofx>) => InterceptorContext<State, Cofx>;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Path interceptor - focus handler on a path in state
|
|
101
|
+
*/
|
|
102
|
+
declare function path<State, Cofx = {}>(pathKeys: (keyof State)[]): Interceptor<State, Cofx>;
|
|
103
|
+
/**
|
|
104
|
+
* Debug interceptor - log events
|
|
105
|
+
*/
|
|
106
|
+
declare function debug<State, Cofx = {}>(): Interceptor<State, Cofx>;
|
|
107
|
+
/**
|
|
108
|
+
* After interceptor - run side effect after handler
|
|
109
|
+
*/
|
|
110
|
+
declare function after<State, Cofx = {}>(fn: (db: State, effects: EffectMap) => void): Interceptor<State, Cofx>;
|
|
111
|
+
/**
|
|
112
|
+
* Inject coeffect interceptor - adds a dynamic coeffect value
|
|
113
|
+
* Note: With the new coeffect provider system, this is rarely needed
|
|
114
|
+
* Use it only for one-off dynamic values that aren't in your Cofx type
|
|
115
|
+
*/
|
|
116
|
+
declare function injectCofx<State, Cofx = {}>(key: string, value: any): Interceptor<State, Cofx>;
|
|
117
|
+
/**
|
|
118
|
+
* Validation interceptor
|
|
119
|
+
*/
|
|
120
|
+
declare function validate<State, Cofx = {}>(schema: (state: State) => boolean | string): Interceptor<State, Cofx>;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Subscription system
|
|
124
|
+
*/
|
|
125
|
+
type SubscriptionFn<State, Result, Params extends any[] = []> = (state: State, ...params: Params) => Result;
|
|
126
|
+
type SubscriptionConfig<State, Result, Params extends any[] = [], Deps extends any[] = any[]> = {
|
|
127
|
+
compute?: SubscriptionFn<State, Result, Params>;
|
|
128
|
+
deps?: string[];
|
|
129
|
+
combine?: (deps: Deps, ...params: Params) => Result;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Shared subscription object - same instance for same key+params
|
|
133
|
+
* This enables React memoization and matches re-frame behavior
|
|
134
|
+
*/
|
|
135
|
+
declare class Subscription<State = any, Result = any, Params extends any[] = []> {
|
|
136
|
+
readonly key: string;
|
|
137
|
+
readonly params: Params;
|
|
138
|
+
constructor(key: string, params: Params);
|
|
139
|
+
}
|
|
140
|
+
type SubscriptionErrorHandler = (error: Error, key: string, params: any[]) => void;
|
|
141
|
+
declare class SubscriptionRegistry<State> {
|
|
142
|
+
private subscriptions;
|
|
143
|
+
private subscriptionCache;
|
|
144
|
+
private resultCache;
|
|
145
|
+
private refCounts;
|
|
146
|
+
private listeners;
|
|
147
|
+
register<Result, Params extends any[] = [], Deps extends any[] = any[]>(key: string, config: SubscriptionConfig<State, Result, Params, Deps>): void;
|
|
148
|
+
/**
|
|
149
|
+
* Get or create a shared Subscription object for the given key+params
|
|
150
|
+
* Returns the same object for the same key+params (like re-frame)
|
|
151
|
+
*/
|
|
152
|
+
getSubscription<Result, Params extends any[]>(key: string, params: Params): Subscription<State, Result, Params>;
|
|
153
|
+
subscribe<Result, Params extends any[]>(state: State, key: string, params: Params, callback: (result: Result) => void, onError?: SubscriptionErrorHandler): () => void;
|
|
154
|
+
query<Result, Params extends any[]>(state: State, key: string, params: Params, onError?: SubscriptionErrorHandler): Result;
|
|
155
|
+
notifyListeners(newState: State): void;
|
|
156
|
+
private getCacheKey;
|
|
157
|
+
private deepEqual;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Store Factory Module
|
|
162
|
+
* Creates a store instance by composing all modules together
|
|
163
|
+
*
|
|
164
|
+
* This is the primary API for creating stores (replaces the Store class)
|
|
165
|
+
*/
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Store API interface - all public methods available on a store instance
|
|
169
|
+
*/
|
|
170
|
+
interface StoreAPI<State, Cofx = {}> {
|
|
171
|
+
registerEventDb<Payload = any>(eventKey: string, handler: EventHandlerDb<State, Cofx, Payload>, interceptors?: Interceptor<State, Cofx>[]): void;
|
|
172
|
+
registerEvent<Payload = any>(eventKey: string, handler: EventHandlerFx<State, Cofx, Payload>, interceptors?: Interceptor<State, Cofx>[]): void;
|
|
173
|
+
deregisterEvent(eventKey: string): void;
|
|
174
|
+
registerEffect<Config = any>(effectType: string, handler: EffectHandler<Config>): void;
|
|
175
|
+
dispatch<Payload = any>(eventKey: string, payload: Payload): Promise<void>;
|
|
176
|
+
flush(): Promise<void>;
|
|
177
|
+
getState(): Readonly<State>;
|
|
178
|
+
getInterceptors(eventKey: string): Interceptor<State, Cofx>[] | undefined;
|
|
179
|
+
registerErrorHandler(handler: ErrorHandler, config?: ErrorHandlerConfig): void;
|
|
180
|
+
registerSubscription<Result, Params extends any[] = [], Deps extends any[] = any[]>(key: string, config: SubscriptionConfig<State, Result, Params, Deps>): void;
|
|
181
|
+
subscribe<Result, Params extends any[]>(key: string, params: Params, callback: (result: Result) => void): () => void;
|
|
182
|
+
query<Result, Params extends any[]>(key: string, params: Params): Result;
|
|
183
|
+
getSubscription<Result, Params extends any[]>(key: string, params: Params): any;
|
|
184
|
+
registerTraceCallback(key: string, callback: TraceCallback<State>): void;
|
|
185
|
+
removeTraceCallback(key: string): void;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Create a new store instance by composing all modules
|
|
189
|
+
*
|
|
190
|
+
* This is the recommended way to create a store (replaces `new Store()`)
|
|
191
|
+
*
|
|
192
|
+
* @param config - Store configuration
|
|
193
|
+
* @returns Store API instance
|
|
194
|
+
*/
|
|
195
|
+
declare function createStore<State, Cofx = {}>(config: StoreConfig<State, Cofx>): StoreAPI<State, Cofx>;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Error Handler Module
|
|
199
|
+
* Handles error registration and execution
|
|
200
|
+
*/
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Default error handler
|
|
204
|
+
* Logs error details to console
|
|
205
|
+
*/
|
|
206
|
+
declare function defaultErrorHandler(error: Error, context: ErrorContext, config: ErrorHandlerConfig): void;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Effects Module
|
|
210
|
+
* Handles effect execution and built-in effects
|
|
211
|
+
* Inspired by re-frame's fx.cljc
|
|
212
|
+
*/
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Merge multiple effect maps into a single effect map
|
|
216
|
+
*
|
|
217
|
+
* Handles special merging rules for:
|
|
218
|
+
* - `dispatch-n`: Arrays are concatenated
|
|
219
|
+
* - `dispatch-later`: Arrays are concatenated
|
|
220
|
+
* - `fx`: Arrays are concatenated
|
|
221
|
+
* - `deregister-event-handler`: Arrays or strings are merged
|
|
222
|
+
* - Other effects: Last one wins
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* ```typescript
|
|
226
|
+
* import { mergeEffects } from '@rplx/core'
|
|
227
|
+
*
|
|
228
|
+
* const effects = mergeEffects(
|
|
229
|
+
* { db: newState },
|
|
230
|
+
* { dispatch: { event: 'save', payload: {} } },
|
|
231
|
+
* { 'dispatch-n': [{ event: 'log', payload: {} }] }
|
|
232
|
+
* )
|
|
233
|
+
* ```
|
|
234
|
+
*/
|
|
235
|
+
declare function mergeEffects(...effects: EffectMap[]): EffectMap;
|
|
236
|
+
|
|
237
|
+
export { type CoeffectProviders, type Context, type EffectExecutionTrace, type EffectHandler, type EffectMap, type ErrorContext, type ErrorHandler, type ErrorHandlerConfig, type EventHandlerDb, type EventHandlerFx, type EventTrace, type Interceptor, type InterceptorContext, type QueuedEvent, type StoreAPI, type StoreConfig, Subscription, type SubscriptionConfig, type SubscriptionErrorHandler, type SubscriptionFn, SubscriptionRegistry, type TraceCallback, after, createStore, debug, defaultErrorHandler, injectCofx, mergeEffects, path, validate };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
interface EffectMap {
|
|
2
|
+
db?: any;
|
|
3
|
+
dispatch?: {
|
|
4
|
+
event: string;
|
|
5
|
+
payload: any;
|
|
6
|
+
};
|
|
7
|
+
'dispatch-n'?: Array<{
|
|
8
|
+
event: string;
|
|
9
|
+
payload: any;
|
|
10
|
+
}>;
|
|
11
|
+
'dispatch-later'?: Array<{
|
|
12
|
+
ms: number;
|
|
13
|
+
event: string;
|
|
14
|
+
payload: any;
|
|
15
|
+
}>;
|
|
16
|
+
fx?: Array<[string, any] | null>;
|
|
17
|
+
'deregister-event-handler'?: string | string[];
|
|
18
|
+
}
|
|
19
|
+
type Context<State, Cofx = {}> = {
|
|
20
|
+
db: State;
|
|
21
|
+
event?: any;
|
|
22
|
+
} & Cofx;
|
|
23
|
+
type EventHandlerDb<State, Cofx = {}, Payload = any> = (context: Context<State, Cofx>, payload: Payload) => State;
|
|
24
|
+
type EventHandlerFx<State, Cofx = {}, Payload = any> = (context: Context<State, Cofx>, payload: Payload) => EffectMap;
|
|
25
|
+
interface ErrorContext {
|
|
26
|
+
eventKey: string;
|
|
27
|
+
payload: any;
|
|
28
|
+
phase: 'interceptor' | 'effect' | 'subscription';
|
|
29
|
+
interceptor?: {
|
|
30
|
+
id?: string;
|
|
31
|
+
direction: 'before' | 'after';
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
interface ErrorHandlerConfig {
|
|
35
|
+
/** Whether to re-throw the error after handling (default: false) */
|
|
36
|
+
rethrow?: boolean;
|
|
37
|
+
}
|
|
38
|
+
type ErrorHandler = (error: Error, context: ErrorContext, config: ErrorHandlerConfig) => void | Promise<void>;
|
|
39
|
+
type EffectHandler<Config = any> = (config: Config, store: any) => Promise<void> | void;
|
|
40
|
+
type CoeffectProviders<Cofx> = {
|
|
41
|
+
[K in keyof Cofx]: () => Cofx[K];
|
|
42
|
+
};
|
|
43
|
+
interface EffectExecutionTrace {
|
|
44
|
+
effectType: string;
|
|
45
|
+
config: any;
|
|
46
|
+
start: number;
|
|
47
|
+
end: number;
|
|
48
|
+
duration: number;
|
|
49
|
+
error?: Error;
|
|
50
|
+
}
|
|
51
|
+
interface EventTrace<State = any> {
|
|
52
|
+
id: number;
|
|
53
|
+
eventKey: string;
|
|
54
|
+
payload: any;
|
|
55
|
+
timestamp: number;
|
|
56
|
+
stateBefore: State;
|
|
57
|
+
stateAfter: State;
|
|
58
|
+
interceptors: Array<{
|
|
59
|
+
id?: string;
|
|
60
|
+
order: number;
|
|
61
|
+
}>;
|
|
62
|
+
effectMap: EffectMap;
|
|
63
|
+
effectsExecuted: EffectExecutionTrace[];
|
|
64
|
+
duration: number;
|
|
65
|
+
error?: Error;
|
|
66
|
+
}
|
|
67
|
+
type TraceCallback<State = any> = (traces: EventTrace<State>[]) => void;
|
|
68
|
+
interface StoreConfig<State, Cofx = {}> {
|
|
69
|
+
initialState: State;
|
|
70
|
+
coeffects?: CoeffectProviders<Cofx>;
|
|
71
|
+
onStateChange?: (state: State) => void;
|
|
72
|
+
/** Error handler configuration */
|
|
73
|
+
errorHandler?: {
|
|
74
|
+
handler?: ErrorHandler;
|
|
75
|
+
rethrow?: boolean;
|
|
76
|
+
};
|
|
77
|
+
/** Tracing configuration */
|
|
78
|
+
tracing?: {
|
|
79
|
+
enabled?: boolean;
|
|
80
|
+
debounceTime?: number;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
interface QueuedEvent {
|
|
84
|
+
eventKey: string;
|
|
85
|
+
payload: any;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface InterceptorContext<State, Cofx = {}> {
|
|
89
|
+
coeffects: Context<State, Cofx>;
|
|
90
|
+
effects: EffectMap;
|
|
91
|
+
queue: Interceptor<State, Cofx>[];
|
|
92
|
+
stack: Interceptor<State, Cofx>[];
|
|
93
|
+
}
|
|
94
|
+
interface Interceptor<State, Cofx = {}> {
|
|
95
|
+
id?: string;
|
|
96
|
+
before?: (context: InterceptorContext<State, Cofx>) => InterceptorContext<State, Cofx>;
|
|
97
|
+
after?: (context: InterceptorContext<State, Cofx>) => InterceptorContext<State, Cofx>;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Path interceptor - focus handler on a path in state
|
|
101
|
+
*/
|
|
102
|
+
declare function path<State, Cofx = {}>(pathKeys: (keyof State)[]): Interceptor<State, Cofx>;
|
|
103
|
+
/**
|
|
104
|
+
* Debug interceptor - log events
|
|
105
|
+
*/
|
|
106
|
+
declare function debug<State, Cofx = {}>(): Interceptor<State, Cofx>;
|
|
107
|
+
/**
|
|
108
|
+
* After interceptor - run side effect after handler
|
|
109
|
+
*/
|
|
110
|
+
declare function after<State, Cofx = {}>(fn: (db: State, effects: EffectMap) => void): Interceptor<State, Cofx>;
|
|
111
|
+
/**
|
|
112
|
+
* Inject coeffect interceptor - adds a dynamic coeffect value
|
|
113
|
+
* Note: With the new coeffect provider system, this is rarely needed
|
|
114
|
+
* Use it only for one-off dynamic values that aren't in your Cofx type
|
|
115
|
+
*/
|
|
116
|
+
declare function injectCofx<State, Cofx = {}>(key: string, value: any): Interceptor<State, Cofx>;
|
|
117
|
+
/**
|
|
118
|
+
* Validation interceptor
|
|
119
|
+
*/
|
|
120
|
+
declare function validate<State, Cofx = {}>(schema: (state: State) => boolean | string): Interceptor<State, Cofx>;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Subscription system
|
|
124
|
+
*/
|
|
125
|
+
type SubscriptionFn<State, Result, Params extends any[] = []> = (state: State, ...params: Params) => Result;
|
|
126
|
+
type SubscriptionConfig<State, Result, Params extends any[] = [], Deps extends any[] = any[]> = {
|
|
127
|
+
compute?: SubscriptionFn<State, Result, Params>;
|
|
128
|
+
deps?: string[];
|
|
129
|
+
combine?: (deps: Deps, ...params: Params) => Result;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Shared subscription object - same instance for same key+params
|
|
133
|
+
* This enables React memoization and matches re-frame behavior
|
|
134
|
+
*/
|
|
135
|
+
declare class Subscription<State = any, Result = any, Params extends any[] = []> {
|
|
136
|
+
readonly key: string;
|
|
137
|
+
readonly params: Params;
|
|
138
|
+
constructor(key: string, params: Params);
|
|
139
|
+
}
|
|
140
|
+
type SubscriptionErrorHandler = (error: Error, key: string, params: any[]) => void;
|
|
141
|
+
declare class SubscriptionRegistry<State> {
|
|
142
|
+
private subscriptions;
|
|
143
|
+
private subscriptionCache;
|
|
144
|
+
private resultCache;
|
|
145
|
+
private refCounts;
|
|
146
|
+
private listeners;
|
|
147
|
+
register<Result, Params extends any[] = [], Deps extends any[] = any[]>(key: string, config: SubscriptionConfig<State, Result, Params, Deps>): void;
|
|
148
|
+
/**
|
|
149
|
+
* Get or create a shared Subscription object for the given key+params
|
|
150
|
+
* Returns the same object for the same key+params (like re-frame)
|
|
151
|
+
*/
|
|
152
|
+
getSubscription<Result, Params extends any[]>(key: string, params: Params): Subscription<State, Result, Params>;
|
|
153
|
+
subscribe<Result, Params extends any[]>(state: State, key: string, params: Params, callback: (result: Result) => void, onError?: SubscriptionErrorHandler): () => void;
|
|
154
|
+
query<Result, Params extends any[]>(state: State, key: string, params: Params, onError?: SubscriptionErrorHandler): Result;
|
|
155
|
+
notifyListeners(newState: State): void;
|
|
156
|
+
private getCacheKey;
|
|
157
|
+
private deepEqual;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Store Factory Module
|
|
162
|
+
* Creates a store instance by composing all modules together
|
|
163
|
+
*
|
|
164
|
+
* This is the primary API for creating stores (replaces the Store class)
|
|
165
|
+
*/
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Store API interface - all public methods available on a store instance
|
|
169
|
+
*/
|
|
170
|
+
interface StoreAPI<State, Cofx = {}> {
|
|
171
|
+
registerEventDb<Payload = any>(eventKey: string, handler: EventHandlerDb<State, Cofx, Payload>, interceptors?: Interceptor<State, Cofx>[]): void;
|
|
172
|
+
registerEvent<Payload = any>(eventKey: string, handler: EventHandlerFx<State, Cofx, Payload>, interceptors?: Interceptor<State, Cofx>[]): void;
|
|
173
|
+
deregisterEvent(eventKey: string): void;
|
|
174
|
+
registerEffect<Config = any>(effectType: string, handler: EffectHandler<Config>): void;
|
|
175
|
+
dispatch<Payload = any>(eventKey: string, payload: Payload): Promise<void>;
|
|
176
|
+
flush(): Promise<void>;
|
|
177
|
+
getState(): Readonly<State>;
|
|
178
|
+
getInterceptors(eventKey: string): Interceptor<State, Cofx>[] | undefined;
|
|
179
|
+
registerErrorHandler(handler: ErrorHandler, config?: ErrorHandlerConfig): void;
|
|
180
|
+
registerSubscription<Result, Params extends any[] = [], Deps extends any[] = any[]>(key: string, config: SubscriptionConfig<State, Result, Params, Deps>): void;
|
|
181
|
+
subscribe<Result, Params extends any[]>(key: string, params: Params, callback: (result: Result) => void): () => void;
|
|
182
|
+
query<Result, Params extends any[]>(key: string, params: Params): Result;
|
|
183
|
+
getSubscription<Result, Params extends any[]>(key: string, params: Params): any;
|
|
184
|
+
registerTraceCallback(key: string, callback: TraceCallback<State>): void;
|
|
185
|
+
removeTraceCallback(key: string): void;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Create a new store instance by composing all modules
|
|
189
|
+
*
|
|
190
|
+
* This is the recommended way to create a store (replaces `new Store()`)
|
|
191
|
+
*
|
|
192
|
+
* @param config - Store configuration
|
|
193
|
+
* @returns Store API instance
|
|
194
|
+
*/
|
|
195
|
+
declare function createStore<State, Cofx = {}>(config: StoreConfig<State, Cofx>): StoreAPI<State, Cofx>;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Error Handler Module
|
|
199
|
+
* Handles error registration and execution
|
|
200
|
+
*/
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Default error handler
|
|
204
|
+
* Logs error details to console
|
|
205
|
+
*/
|
|
206
|
+
declare function defaultErrorHandler(error: Error, context: ErrorContext, config: ErrorHandlerConfig): void;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Effects Module
|
|
210
|
+
* Handles effect execution and built-in effects
|
|
211
|
+
* Inspired by re-frame's fx.cljc
|
|
212
|
+
*/
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Merge multiple effect maps into a single effect map
|
|
216
|
+
*
|
|
217
|
+
* Handles special merging rules for:
|
|
218
|
+
* - `dispatch-n`: Arrays are concatenated
|
|
219
|
+
* - `dispatch-later`: Arrays are concatenated
|
|
220
|
+
* - `fx`: Arrays are concatenated
|
|
221
|
+
* - `deregister-event-handler`: Arrays or strings are merged
|
|
222
|
+
* - Other effects: Last one wins
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* ```typescript
|
|
226
|
+
* import { mergeEffects } from '@rplx/core'
|
|
227
|
+
*
|
|
228
|
+
* const effects = mergeEffects(
|
|
229
|
+
* { db: newState },
|
|
230
|
+
* { dispatch: { event: 'save', payload: {} } },
|
|
231
|
+
* { 'dispatch-n': [{ event: 'log', payload: {} }] }
|
|
232
|
+
* )
|
|
233
|
+
* ```
|
|
234
|
+
*/
|
|
235
|
+
declare function mergeEffects(...effects: EffectMap[]): EffectMap;
|
|
236
|
+
|
|
237
|
+
export { type CoeffectProviders, type Context, type EffectExecutionTrace, type EffectHandler, type EffectMap, type ErrorContext, type ErrorHandler, type ErrorHandlerConfig, type EventHandlerDb, type EventHandlerFx, type EventTrace, type Interceptor, type InterceptorContext, type QueuedEvent, type StoreAPI, type StoreConfig, Subscription, type SubscriptionConfig, type SubscriptionErrorHandler, type SubscriptionFn, SubscriptionRegistry, type TraceCallback, after, createStore, debug, defaultErrorHandler, injectCofx, mergeEffects, path, validate };
|