@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/dist/index.mjs
ADDED
|
@@ -0,0 +1,1096 @@
|
|
|
1
|
+
// src/modules/registrar.ts
|
|
2
|
+
function createRegistrar(options) {
|
|
3
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
4
|
+
const warnOnOverwrite = options?.warnOnOverwrite ?? true;
|
|
5
|
+
return {
|
|
6
|
+
register(kind, id, handler) {
|
|
7
|
+
let kindMap = handlers.get(kind);
|
|
8
|
+
if (!kindMap) {
|
|
9
|
+
kindMap = /* @__PURE__ */ new Map();
|
|
10
|
+
handlers.set(kind, kindMap);
|
|
11
|
+
}
|
|
12
|
+
if (warnOnOverwrite && kindMap.has(id)) {
|
|
13
|
+
console.warn(`re-frame: overwriting ${kind} handler for: ${id}`);
|
|
14
|
+
}
|
|
15
|
+
kindMap.set(id, handler);
|
|
16
|
+
return handler;
|
|
17
|
+
},
|
|
18
|
+
get(kind, id) {
|
|
19
|
+
const kindMap = handlers.get(kind);
|
|
20
|
+
if (!kindMap) {
|
|
21
|
+
return void 0;
|
|
22
|
+
}
|
|
23
|
+
return kindMap.get(id);
|
|
24
|
+
},
|
|
25
|
+
has(kind, id) {
|
|
26
|
+
const kindMap = handlers.get(kind);
|
|
27
|
+
if (!kindMap) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
return kindMap.has(id);
|
|
31
|
+
},
|
|
32
|
+
clear(kind, id) {
|
|
33
|
+
if (kind === void 0) {
|
|
34
|
+
handlers.clear();
|
|
35
|
+
} else if (id === void 0) {
|
|
36
|
+
handlers.delete(kind);
|
|
37
|
+
} else {
|
|
38
|
+
const kindMap = handlers.get(kind);
|
|
39
|
+
if (kindMap) {
|
|
40
|
+
if (kindMap.has(id)) {
|
|
41
|
+
kindMap.delete(id);
|
|
42
|
+
} else {
|
|
43
|
+
console.warn(`re-frame: can't clear ${kind} handler for ${id}. Handler not found.`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/modules/state.ts
|
|
52
|
+
function createStateManager(initialState, onStateChange, onStateChangeForSubscriptions) {
|
|
53
|
+
let state = initialState;
|
|
54
|
+
const stateChanges = [];
|
|
55
|
+
let rafId = null;
|
|
56
|
+
const scheduleNotification = () => {
|
|
57
|
+
if (rafId !== null) return;
|
|
58
|
+
rafId = requestAnimationFrame(() => {
|
|
59
|
+
rafId = null;
|
|
60
|
+
if (stateChanges.length > 0) {
|
|
61
|
+
const latestState = stateChanges[stateChanges.length - 1];
|
|
62
|
+
stateChanges.length = 0;
|
|
63
|
+
if (onStateChange) {
|
|
64
|
+
onStateChange(latestState);
|
|
65
|
+
}
|
|
66
|
+
if (onStateChangeForSubscriptions) {
|
|
67
|
+
onStateChangeForSubscriptions(latestState);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
getState() {
|
|
74
|
+
return state;
|
|
75
|
+
},
|
|
76
|
+
setState(newState) {
|
|
77
|
+
const oldState = state;
|
|
78
|
+
if (!Object.is(oldState, newState)) {
|
|
79
|
+
state = newState;
|
|
80
|
+
stateChanges.push(newState);
|
|
81
|
+
scheduleNotification();
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
scheduleNotification
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/modules/tracing.ts
|
|
89
|
+
function createTracer(config = {}) {
|
|
90
|
+
const traceEnabled = config.enabled ?? false;
|
|
91
|
+
const traceCallbacks = /* @__PURE__ */ new Map();
|
|
92
|
+
const traces = [];
|
|
93
|
+
let traceIdCounter = 0;
|
|
94
|
+
const debounceTime = config.debounceTime ?? 50;
|
|
95
|
+
let debounceTimer = null;
|
|
96
|
+
let nextDeliveryTime = 0;
|
|
97
|
+
const deliverTraces = () => {
|
|
98
|
+
if (traces.length === 0) return;
|
|
99
|
+
const tracesToDeliver = [...traces];
|
|
100
|
+
traces.length = 0;
|
|
101
|
+
for (const [key, callback] of traceCallbacks.entries()) {
|
|
102
|
+
try {
|
|
103
|
+
callback(tracesToDeliver);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error(`Error in trace callback "${key}":`, error);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
debounceTimer = null;
|
|
109
|
+
nextDeliveryTime = 0;
|
|
110
|
+
};
|
|
111
|
+
const scheduleTraceDelivery = () => {
|
|
112
|
+
const now = Date.now();
|
|
113
|
+
if (now < nextDeliveryTime - 25) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (debounceTimer !== null) {
|
|
117
|
+
clearTimeout(debounceTimer);
|
|
118
|
+
}
|
|
119
|
+
nextDeliveryTime = now + debounceTime;
|
|
120
|
+
debounceTimer = setTimeout(() => {
|
|
121
|
+
deliverTraces();
|
|
122
|
+
}, debounceTime);
|
|
123
|
+
};
|
|
124
|
+
const emitEventTrace = (trace) => {
|
|
125
|
+
if (!traceEnabled) return;
|
|
126
|
+
const traceWithId = {
|
|
127
|
+
...trace,
|
|
128
|
+
id: ++traceIdCounter
|
|
129
|
+
};
|
|
130
|
+
traces.push(traceWithId);
|
|
131
|
+
scheduleTraceDelivery();
|
|
132
|
+
};
|
|
133
|
+
const registerTraceCallback = (key, callback) => {
|
|
134
|
+
if (!traceEnabled) {
|
|
135
|
+
console.warn("Tracing is not enabled. Set tracing.enabled to true in StoreConfig.");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
traceCallbacks.set(key, callback);
|
|
139
|
+
};
|
|
140
|
+
const removeTraceCallback = (key) => {
|
|
141
|
+
traceCallbacks.delete(key);
|
|
142
|
+
};
|
|
143
|
+
return {
|
|
144
|
+
registerTraceCallback,
|
|
145
|
+
removeTraceCallback,
|
|
146
|
+
emitEventTrace
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/modules/errorHandler.ts
|
|
151
|
+
function createErrorHandler(initialHandler, initialConfig) {
|
|
152
|
+
let errorHandler = initialHandler || defaultErrorHandler;
|
|
153
|
+
let errorHandlerConfig = {
|
|
154
|
+
rethrow: false,
|
|
155
|
+
...initialConfig
|
|
156
|
+
};
|
|
157
|
+
return {
|
|
158
|
+
register(handler, config) {
|
|
159
|
+
errorHandler = handler;
|
|
160
|
+
if (config) {
|
|
161
|
+
errorHandlerConfig = { ...errorHandlerConfig, ...config };
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
async handle(error, context) {
|
|
165
|
+
try {
|
|
166
|
+
await errorHandler(error, context, errorHandlerConfig);
|
|
167
|
+
if (errorHandlerConfig.rethrow) {
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
} catch (handlerError) {
|
|
171
|
+
console.error("Error in error handler:", handlerError);
|
|
172
|
+
if (errorHandlerConfig.rethrow) {
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function defaultErrorHandler(error, context, config) {
|
|
180
|
+
const { eventKey, phase, interceptor } = context;
|
|
181
|
+
if (phase === "interceptor" && interceptor) {
|
|
182
|
+
console.error(
|
|
183
|
+
`Error in ${interceptor.direction} phase of interceptor "${interceptor.id || "unnamed"}" while handling event "${eventKey}":`,
|
|
184
|
+
error
|
|
185
|
+
);
|
|
186
|
+
} else if (phase === "effect" && interceptor) {
|
|
187
|
+
console.error(
|
|
188
|
+
`Error executing effect "${interceptor.id}" for event "${eventKey}":`,
|
|
189
|
+
error
|
|
190
|
+
);
|
|
191
|
+
} else if (phase === "subscription") {
|
|
192
|
+
console.error(
|
|
193
|
+
`Error in subscription "${eventKey}":`,
|
|
194
|
+
error
|
|
195
|
+
);
|
|
196
|
+
} else {
|
|
197
|
+
console.error(
|
|
198
|
+
`Error handling event "${eventKey}":`,
|
|
199
|
+
error
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// src/modules/effects.ts
|
|
205
|
+
function createEffectExecutor(deps) {
|
|
206
|
+
const { registrar, stateManager, errorHandler, dispatch, deregisterEvent, registerEffect } = deps;
|
|
207
|
+
async function execute(effectMap, eventKey, payload, effectsExecuted) {
|
|
208
|
+
if (effectMap.db !== void 0) {
|
|
209
|
+
const dbHandler = registrar.get("effect", "db");
|
|
210
|
+
if (dbHandler) {
|
|
211
|
+
const effectStart = Date.now();
|
|
212
|
+
try {
|
|
213
|
+
await dbHandler(effectMap.db, deps);
|
|
214
|
+
const effectEnd = Date.now();
|
|
215
|
+
if (effectsExecuted) {
|
|
216
|
+
effectsExecuted.push({
|
|
217
|
+
effectType: "db",
|
|
218
|
+
config: effectMap.db,
|
|
219
|
+
start: effectStart,
|
|
220
|
+
end: effectEnd,
|
|
221
|
+
duration: effectEnd - effectStart
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
} catch (error) {
|
|
225
|
+
const effectEnd = Date.now();
|
|
226
|
+
if (effectsExecuted) {
|
|
227
|
+
effectsExecuted.push({
|
|
228
|
+
effectType: "db",
|
|
229
|
+
config: effectMap.db,
|
|
230
|
+
start: effectStart,
|
|
231
|
+
end: effectEnd,
|
|
232
|
+
duration: effectEnd - effectStart,
|
|
233
|
+
error
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
await errorHandler.handle(error, {
|
|
237
|
+
eventKey,
|
|
238
|
+
payload,
|
|
239
|
+
phase: "effect",
|
|
240
|
+
interceptor: {
|
|
241
|
+
id: "db",
|
|
242
|
+
direction: "after"
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const effectsWithoutDb = Object.entries(effectMap).filter(
|
|
249
|
+
([key]) => key !== "db" && key !== "fx"
|
|
250
|
+
);
|
|
251
|
+
await Promise.all(
|
|
252
|
+
effectsWithoutDb.map(async ([effectType, config]) => {
|
|
253
|
+
if (config === void 0 || config === null) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const handler = registrar.get("effect", effectType);
|
|
257
|
+
if (!handler) {
|
|
258
|
+
console.warn(`No effect handler registered for "${String(effectType)}"`);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const effectStart = Date.now();
|
|
262
|
+
try {
|
|
263
|
+
await handler(config, deps);
|
|
264
|
+
const effectEnd = Date.now();
|
|
265
|
+
if (effectsExecuted) {
|
|
266
|
+
effectsExecuted.push({
|
|
267
|
+
effectType,
|
|
268
|
+
config,
|
|
269
|
+
start: effectStart,
|
|
270
|
+
end: effectEnd,
|
|
271
|
+
duration: effectEnd - effectStart
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
} catch (error) {
|
|
275
|
+
const effectEnd = Date.now();
|
|
276
|
+
if (effectsExecuted) {
|
|
277
|
+
effectsExecuted.push({
|
|
278
|
+
effectType,
|
|
279
|
+
config,
|
|
280
|
+
start: effectStart,
|
|
281
|
+
end: effectEnd,
|
|
282
|
+
duration: effectEnd - effectStart,
|
|
283
|
+
error
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
await errorHandler.handle(error, {
|
|
287
|
+
eventKey,
|
|
288
|
+
payload,
|
|
289
|
+
phase: "effect",
|
|
290
|
+
interceptor: {
|
|
291
|
+
id: effectType,
|
|
292
|
+
direction: "after"
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
);
|
|
298
|
+
if (effectMap.fx !== void 0) {
|
|
299
|
+
const fxHandler = registrar.get("effect", "fx");
|
|
300
|
+
if (fxHandler) {
|
|
301
|
+
const effectStart = Date.now();
|
|
302
|
+
try {
|
|
303
|
+
await fxHandler(effectMap.fx, deps);
|
|
304
|
+
const effectEnd = Date.now();
|
|
305
|
+
if (effectsExecuted) {
|
|
306
|
+
effectsExecuted.push({
|
|
307
|
+
effectType: "fx",
|
|
308
|
+
config: effectMap.fx,
|
|
309
|
+
start: effectStart,
|
|
310
|
+
end: effectEnd,
|
|
311
|
+
duration: effectEnd - effectStart
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
} catch (error) {
|
|
315
|
+
const effectEnd = Date.now();
|
|
316
|
+
if (effectsExecuted) {
|
|
317
|
+
effectsExecuted.push({
|
|
318
|
+
effectType: "fx",
|
|
319
|
+
config: effectMap.fx,
|
|
320
|
+
start: effectStart,
|
|
321
|
+
end: effectEnd,
|
|
322
|
+
duration: effectEnd - effectStart,
|
|
323
|
+
error
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
await errorHandler.handle(error, {
|
|
327
|
+
eventKey,
|
|
328
|
+
payload,
|
|
329
|
+
phase: "effect",
|
|
330
|
+
interceptor: {
|
|
331
|
+
id: "fx",
|
|
332
|
+
direction: "after"
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function registerBuiltInEffects() {
|
|
340
|
+
registerEffect("db", (newState) => {
|
|
341
|
+
stateManager.setState(newState);
|
|
342
|
+
});
|
|
343
|
+
registerEffect("dispatch", async (config) => {
|
|
344
|
+
if (!config || typeof config.event !== "string") {
|
|
345
|
+
console.error("re-frame: ignoring bad :dispatch value. Expected {event: string, payload: any}, but got:", config);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
await dispatch(config.event, config.payload);
|
|
349
|
+
});
|
|
350
|
+
registerEffect("dispatch-n", async (configs) => {
|
|
351
|
+
if (!Array.isArray(configs)) {
|
|
352
|
+
console.error("re-frame: ignoring bad :dispatch-n value. Expected an array, but got:", configs);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
for (const config of configs) {
|
|
356
|
+
if (config && config.event) {
|
|
357
|
+
await dispatch(config.event, config.payload);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
registerEffect("dispatch-later", (value) => {
|
|
362
|
+
if (!Array.isArray(value)) {
|
|
363
|
+
console.error("re-frame: ignoring bad :dispatch-later value. Expected an array, but got:", value);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
for (const effect of value.filter((e) => e != null)) {
|
|
367
|
+
if (!effect || typeof effect.ms !== "number" || !effect.event) {
|
|
368
|
+
console.error("re-frame: ignoring bad :dispatch-later entry:", effect);
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
setTimeout(() => {
|
|
372
|
+
dispatch(effect.event, effect.payload);
|
|
373
|
+
}, effect.ms);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
registerEffect("fx", async (seqOfEffects) => {
|
|
377
|
+
if (!Array.isArray(seqOfEffects)) {
|
|
378
|
+
console.warn('re-frame: ":fx" effect expects an array, but was given', typeof seqOfEffects);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
for (const effectTuple of seqOfEffects) {
|
|
382
|
+
if (effectTuple == null) {
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
const [effectKey, effectValue] = effectTuple;
|
|
386
|
+
if (effectKey === "db") {
|
|
387
|
+
console.warn('re-frame: ":fx" effect should not contain a :db effect. Use top-level :db instead.');
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
const handler = registrar.get("effect", effectKey);
|
|
391
|
+
if (!handler) {
|
|
392
|
+
console.warn(`re-frame: in ":fx" effect found "${effectKey}" which has no associated handler. Ignoring.`);
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
try {
|
|
396
|
+
await handler(effectValue, deps);
|
|
397
|
+
} catch (error) {
|
|
398
|
+
await errorHandler.handle(error, {
|
|
399
|
+
eventKey: `fx:${effectKey}`,
|
|
400
|
+
payload: effectValue,
|
|
401
|
+
phase: "effect",
|
|
402
|
+
interceptor: {
|
|
403
|
+
id: effectKey,
|
|
404
|
+
direction: "after"
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
registerEffect("deregister-event-handler", (value) => {
|
|
411
|
+
if (Array.isArray(value)) {
|
|
412
|
+
for (const eventKey of value) {
|
|
413
|
+
if (typeof eventKey === "string") {
|
|
414
|
+
deregisterEvent(eventKey);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
} else if (typeof value === "string") {
|
|
418
|
+
deregisterEvent(value);
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
return {
|
|
423
|
+
execute,
|
|
424
|
+
registerBuiltInEffects
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
function mergeEffects(...effects) {
|
|
428
|
+
const result = {};
|
|
429
|
+
for (const effect of effects) {
|
|
430
|
+
for (const [key, value] of Object.entries(effect)) {
|
|
431
|
+
if (key === "dispatch-n" && Array.isArray(value)) {
|
|
432
|
+
result["dispatch-n"] = [
|
|
433
|
+
...result["dispatch-n"] || [],
|
|
434
|
+
...value
|
|
435
|
+
];
|
|
436
|
+
} else if (key === "dispatch-later" && Array.isArray(value)) {
|
|
437
|
+
result["dispatch-later"] = [
|
|
438
|
+
...result["dispatch-later"] || [],
|
|
439
|
+
...value
|
|
440
|
+
];
|
|
441
|
+
} else if (key === "fx" && Array.isArray(value)) {
|
|
442
|
+
result.fx = [
|
|
443
|
+
...result.fx || [],
|
|
444
|
+
...value
|
|
445
|
+
];
|
|
446
|
+
} else if (key === "deregister-event-handler") {
|
|
447
|
+
if (Array.isArray(value)) {
|
|
448
|
+
const existing = result["deregister-event-handler"];
|
|
449
|
+
if (Array.isArray(existing)) {
|
|
450
|
+
result["deregister-event-handler"] = [...existing, ...value];
|
|
451
|
+
} else if (typeof existing === "string") {
|
|
452
|
+
result["deregister-event-handler"] = [existing, ...value];
|
|
453
|
+
} else {
|
|
454
|
+
result["deregister-event-handler"] = value;
|
|
455
|
+
}
|
|
456
|
+
} else if (typeof value === "string") {
|
|
457
|
+
const existing = result["deregister-event-handler"];
|
|
458
|
+
if (Array.isArray(existing)) {
|
|
459
|
+
result["deregister-event-handler"] = [...existing, value];
|
|
460
|
+
} else if (typeof existing === "string") {
|
|
461
|
+
result["deregister-event-handler"] = [existing, value];
|
|
462
|
+
} else {
|
|
463
|
+
result["deregister-event-handler"] = value;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
} else {
|
|
467
|
+
result[key] = value;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return result;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// src/modules/events.ts
|
|
475
|
+
function createEventManager(deps) {
|
|
476
|
+
const { registrar, stateManager, effectExecutor, errorHandler, tracer, coeffectProviders } = deps;
|
|
477
|
+
function dbHandlerToInterceptor(handler) {
|
|
478
|
+
return {
|
|
479
|
+
id: "db-handler",
|
|
480
|
+
before: (context) => {
|
|
481
|
+
const event = context.coeffects.event;
|
|
482
|
+
const newState = handler(context.coeffects, event);
|
|
483
|
+
return {
|
|
484
|
+
...context,
|
|
485
|
+
effects: {
|
|
486
|
+
...context.effects,
|
|
487
|
+
db: newState
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
function fxHandlerToInterceptor(handler) {
|
|
494
|
+
return {
|
|
495
|
+
id: "fx-handler",
|
|
496
|
+
before: (context) => {
|
|
497
|
+
const event = context.coeffects.event;
|
|
498
|
+
const effects = handler(context.coeffects, event);
|
|
499
|
+
return {
|
|
500
|
+
...context,
|
|
501
|
+
effects: {
|
|
502
|
+
...context.effects,
|
|
503
|
+
...effects
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
function registerEventDb(eventKey, handler, interceptors) {
|
|
510
|
+
if (registrar.has("event", eventKey)) {
|
|
511
|
+
console.warn(`Event handler for "${eventKey}" is being overwritten`);
|
|
512
|
+
}
|
|
513
|
+
const handlerInterceptor = dbHandlerToInterceptor(handler);
|
|
514
|
+
const chain = interceptors ? [...interceptors, handlerInterceptor] : [handlerInterceptor];
|
|
515
|
+
registrar.register("event", eventKey, chain);
|
|
516
|
+
}
|
|
517
|
+
function registerEvent(eventKey, handler, interceptors) {
|
|
518
|
+
if (registrar.has("event", eventKey)) {
|
|
519
|
+
console.warn(`Event handler for "${eventKey}" is being overwritten`);
|
|
520
|
+
}
|
|
521
|
+
const handlerInterceptor = fxHandlerToInterceptor(handler);
|
|
522
|
+
const chain = interceptors ? [...interceptors, handlerInterceptor] : [handlerInterceptor];
|
|
523
|
+
registrar.register("event", eventKey, chain);
|
|
524
|
+
}
|
|
525
|
+
function deregisterEvent(eventKey) {
|
|
526
|
+
registrar.clear("event", eventKey);
|
|
527
|
+
}
|
|
528
|
+
async function handleEvent(eventKey, payload) {
|
|
529
|
+
const startTime = Date.now();
|
|
530
|
+
const stateBefore = stateManager.getState();
|
|
531
|
+
const interceptors = registrar.get("event", eventKey);
|
|
532
|
+
if (!interceptors || interceptors.length === 0) {
|
|
533
|
+
console.warn(`No handler registered for event "${eventKey}"`);
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
const computedCoeffects = {};
|
|
537
|
+
for (const key in coeffectProviders) {
|
|
538
|
+
const provider = coeffectProviders[key];
|
|
539
|
+
computedCoeffects[key] = provider();
|
|
540
|
+
}
|
|
541
|
+
const initialContext = {
|
|
542
|
+
db: stateManager.getState(),
|
|
543
|
+
event: payload,
|
|
544
|
+
...computedCoeffects
|
|
545
|
+
};
|
|
546
|
+
let context = {
|
|
547
|
+
coeffects: initialContext,
|
|
548
|
+
effects: {},
|
|
549
|
+
queue: [...interceptors],
|
|
550
|
+
// Copy to avoid mutation
|
|
551
|
+
stack: []
|
|
552
|
+
};
|
|
553
|
+
const effectsExecuted = [];
|
|
554
|
+
let eventError;
|
|
555
|
+
let effectMap = {};
|
|
556
|
+
try {
|
|
557
|
+
while (context.queue.length > 0) {
|
|
558
|
+
const interceptor = context.queue.shift();
|
|
559
|
+
context.stack.push(interceptor);
|
|
560
|
+
if (interceptor.before) {
|
|
561
|
+
try {
|
|
562
|
+
context = interceptor.before(context);
|
|
563
|
+
} catch (error) {
|
|
564
|
+
eventError = error;
|
|
565
|
+
await errorHandler.handle(error, {
|
|
566
|
+
eventKey,
|
|
567
|
+
payload,
|
|
568
|
+
phase: "interceptor",
|
|
569
|
+
interceptor: {
|
|
570
|
+
id: interceptor.id,
|
|
571
|
+
direction: "before"
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
if (!eventError) {
|
|
579
|
+
while (context.stack.length > 0) {
|
|
580
|
+
const interceptor = context.stack.pop();
|
|
581
|
+
if (interceptor.after) {
|
|
582
|
+
try {
|
|
583
|
+
context = interceptor.after(context);
|
|
584
|
+
} catch (error) {
|
|
585
|
+
eventError = error;
|
|
586
|
+
await errorHandler.handle(error, {
|
|
587
|
+
eventKey,
|
|
588
|
+
payload,
|
|
589
|
+
phase: "interceptor",
|
|
590
|
+
interceptor: {
|
|
591
|
+
id: interceptor.id,
|
|
592
|
+
direction: "after"
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
effectMap = context.effects;
|
|
601
|
+
if (!eventError) {
|
|
602
|
+
await effectExecutor.execute(context.effects, eventKey, payload, effectsExecuted);
|
|
603
|
+
}
|
|
604
|
+
} catch (error) {
|
|
605
|
+
eventError = error;
|
|
606
|
+
await errorHandler.handle(error, {
|
|
607
|
+
eventKey,
|
|
608
|
+
payload,
|
|
609
|
+
phase: "interceptor"
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
const stateAfter = stateManager.getState();
|
|
613
|
+
const duration = Date.now() - startTime;
|
|
614
|
+
const interceptorInfo = interceptors.map((interceptor, index) => ({
|
|
615
|
+
id: interceptor.id,
|
|
616
|
+
order: index
|
|
617
|
+
}));
|
|
618
|
+
tracer.emitEventTrace({
|
|
619
|
+
eventKey,
|
|
620
|
+
payload,
|
|
621
|
+
timestamp: startTime,
|
|
622
|
+
stateBefore,
|
|
623
|
+
stateAfter,
|
|
624
|
+
interceptors: interceptorInfo,
|
|
625
|
+
effectMap: eventError ? {} : effectMap,
|
|
626
|
+
effectsExecuted,
|
|
627
|
+
duration,
|
|
628
|
+
error: eventError
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
return {
|
|
632
|
+
registerEventDb,
|
|
633
|
+
registerEvent,
|
|
634
|
+
deregisterEvent,
|
|
635
|
+
handleEvent
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// src/modules/router.ts
|
|
640
|
+
function createRouter(deps) {
|
|
641
|
+
const { eventManager } = deps;
|
|
642
|
+
const eventQueue = [];
|
|
643
|
+
let isProcessing = false;
|
|
644
|
+
async function processEvent(eventKey, payload) {
|
|
645
|
+
await eventManager.handleEvent(eventKey, payload);
|
|
646
|
+
}
|
|
647
|
+
async function processQueue() {
|
|
648
|
+
if (isProcessing) return;
|
|
649
|
+
isProcessing = true;
|
|
650
|
+
try {
|
|
651
|
+
while (eventQueue.length > 0) {
|
|
652
|
+
const event = eventQueue.shift();
|
|
653
|
+
await processEvent(event.eventKey, event.payload);
|
|
654
|
+
}
|
|
655
|
+
} finally {
|
|
656
|
+
isProcessing = false;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
function dispatch(eventKey, payload) {
|
|
660
|
+
return new Promise((resolve, reject) => {
|
|
661
|
+
eventQueue.push({ eventKey, payload });
|
|
662
|
+
if (!isProcessing) {
|
|
663
|
+
processQueue().then(resolve).catch(reject);
|
|
664
|
+
} else {
|
|
665
|
+
resolve();
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
async function flush() {
|
|
670
|
+
await processQueue();
|
|
671
|
+
}
|
|
672
|
+
return {
|
|
673
|
+
dispatch,
|
|
674
|
+
flush
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// src/modules/subscription.ts
|
|
679
|
+
var Subscription = class {
|
|
680
|
+
constructor(key, params) {
|
|
681
|
+
this.key = key;
|
|
682
|
+
this.params = params;
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
var SubscriptionRegistry = class {
|
|
686
|
+
constructor() {
|
|
687
|
+
this.subscriptions = /* @__PURE__ */ new Map();
|
|
688
|
+
// Map of cache keys to shared Subscription objects
|
|
689
|
+
this.subscriptionCache = /* @__PURE__ */ new Map();
|
|
690
|
+
// WeakMap for computed results - automatically GC'd when Subscription is no longer referenced
|
|
691
|
+
this.resultCache = /* @__PURE__ */ new WeakMap();
|
|
692
|
+
// Reference counting: track how many active subscriptions exist for each Subscription object
|
|
693
|
+
this.refCounts = /* @__PURE__ */ new WeakMap();
|
|
694
|
+
// Listeners for each subscription
|
|
695
|
+
this.listeners = /* @__PURE__ */ new WeakMap();
|
|
696
|
+
}
|
|
697
|
+
register(key, config) {
|
|
698
|
+
this.subscriptions.set(key, config);
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Get or create a shared Subscription object for the given key+params
|
|
702
|
+
* Returns the same object for the same key+params (like re-frame)
|
|
703
|
+
*/
|
|
704
|
+
getSubscription(key, params) {
|
|
705
|
+
const cacheKey = this.getCacheKey(key, params);
|
|
706
|
+
let subscription = this.subscriptionCache.get(cacheKey);
|
|
707
|
+
if (!subscription) {
|
|
708
|
+
subscription = new Subscription(key, params);
|
|
709
|
+
this.subscriptionCache.set(cacheKey, subscription);
|
|
710
|
+
this.refCounts.set(subscription, 0);
|
|
711
|
+
this.listeners.set(subscription, /* @__PURE__ */ new Set());
|
|
712
|
+
}
|
|
713
|
+
return subscription;
|
|
714
|
+
}
|
|
715
|
+
subscribe(state, key, params, callback, onError) {
|
|
716
|
+
const subscription = this.getSubscription(key, params);
|
|
717
|
+
const currentCount = this.refCounts.get(subscription) || 0;
|
|
718
|
+
this.refCounts.set(subscription, currentCount + 1);
|
|
719
|
+
const result = this.query(state, key, params, onError);
|
|
720
|
+
callback(result);
|
|
721
|
+
const listeners = this.listeners.get(subscription);
|
|
722
|
+
listeners.add(callback);
|
|
723
|
+
return () => {
|
|
724
|
+
listeners.delete(callback);
|
|
725
|
+
const count = this.refCounts.get(subscription) || 0;
|
|
726
|
+
const newCount = Math.max(0, count - 1);
|
|
727
|
+
this.refCounts.set(subscription, newCount);
|
|
728
|
+
if (newCount <= 0 && listeners.size === 0) {
|
|
729
|
+
const cacheKey = this.getCacheKey(key, params);
|
|
730
|
+
this.subscriptionCache.delete(cacheKey);
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
query(state, key, params, onError) {
|
|
735
|
+
const config = this.subscriptions.get(key);
|
|
736
|
+
if (!config) {
|
|
737
|
+
const error = new Error(`Subscription "${key}" not registered`);
|
|
738
|
+
if (onError) {
|
|
739
|
+
onError(error, key, params);
|
|
740
|
+
return void 0;
|
|
741
|
+
}
|
|
742
|
+
console.error(error.message);
|
|
743
|
+
return void 0;
|
|
744
|
+
}
|
|
745
|
+
const subscription = this.getSubscription(key, params);
|
|
746
|
+
const cached = this.resultCache.get(subscription);
|
|
747
|
+
if (cached && cached.state === state) {
|
|
748
|
+
return cached.result;
|
|
749
|
+
}
|
|
750
|
+
let result;
|
|
751
|
+
try {
|
|
752
|
+
if (config.compute) {
|
|
753
|
+
result = config.compute(state, ...params);
|
|
754
|
+
} else if (config.deps && config.combine) {
|
|
755
|
+
const depResults = config.deps.map((depKey) => this.query(state, depKey, [], onError));
|
|
756
|
+
result = config.combine(depResults, ...params);
|
|
757
|
+
} else {
|
|
758
|
+
const error = new Error(`Invalid subscription config for "${key}"`);
|
|
759
|
+
if (onError) {
|
|
760
|
+
onError(error, key, params);
|
|
761
|
+
return void 0;
|
|
762
|
+
}
|
|
763
|
+
console.error(error.message);
|
|
764
|
+
return void 0;
|
|
765
|
+
}
|
|
766
|
+
} catch (error) {
|
|
767
|
+
if (onError) {
|
|
768
|
+
onError(error, key, params);
|
|
769
|
+
} else {
|
|
770
|
+
console.error(`Error computing subscription "${key}":`, error);
|
|
771
|
+
}
|
|
772
|
+
return cached?.result ?? void 0;
|
|
773
|
+
}
|
|
774
|
+
this.resultCache.set(subscription, { state, result });
|
|
775
|
+
return result;
|
|
776
|
+
}
|
|
777
|
+
notifyListeners(newState) {
|
|
778
|
+
for (const subscription of this.subscriptionCache.values()) {
|
|
779
|
+
const listeners = this.listeners.get(subscription);
|
|
780
|
+
if (!listeners || listeners.size === 0) {
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
const oldCached = this.resultCache.get(subscription);
|
|
784
|
+
const oldResult = oldCached?.result;
|
|
785
|
+
const newResult = this.query(newState, subscription.key, subscription.params, void 0);
|
|
786
|
+
if (!oldCached || !this.deepEqual(oldResult, newResult)) {
|
|
787
|
+
listeners.forEach((callback) => callback(newResult));
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
getCacheKey(key, params) {
|
|
792
|
+
return `${key}:${JSON.stringify(params)}`;
|
|
793
|
+
}
|
|
794
|
+
deepEqual(a, b) {
|
|
795
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
// src/modules/subscriptions.ts
|
|
800
|
+
function createSubscriptionManager(deps) {
|
|
801
|
+
const { stateManager, errorHandler } = deps;
|
|
802
|
+
const registry = new SubscriptionRegistry();
|
|
803
|
+
function createSubscriptionErrorHandler() {
|
|
804
|
+
return (error, subKey, subParams) => {
|
|
805
|
+
errorHandler.handle(error, {
|
|
806
|
+
eventKey: `subscription:${subKey}`,
|
|
807
|
+
payload: subParams,
|
|
808
|
+
phase: "subscription"
|
|
809
|
+
}).catch(() => {
|
|
810
|
+
});
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
function registerSubscription(key, config) {
|
|
814
|
+
registry.register(key, config);
|
|
815
|
+
}
|
|
816
|
+
function subscribe(key, params, callback) {
|
|
817
|
+
const onError = createSubscriptionErrorHandler();
|
|
818
|
+
return registry.subscribe(
|
|
819
|
+
stateManager.getState(),
|
|
820
|
+
key,
|
|
821
|
+
params,
|
|
822
|
+
callback,
|
|
823
|
+
onError
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
function query(key, params) {
|
|
827
|
+
const onError = createSubscriptionErrorHandler();
|
|
828
|
+
return registry.query(
|
|
829
|
+
stateManager.getState(),
|
|
830
|
+
key,
|
|
831
|
+
params,
|
|
832
|
+
onError
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
function getSubscription(key, params) {
|
|
836
|
+
return registry.getSubscription(key, params);
|
|
837
|
+
}
|
|
838
|
+
function notifyListeners(newState) {
|
|
839
|
+
registry.notifyListeners(newState);
|
|
840
|
+
}
|
|
841
|
+
return {
|
|
842
|
+
registerSubscription,
|
|
843
|
+
subscribe,
|
|
844
|
+
query,
|
|
845
|
+
getSubscription,
|
|
846
|
+
notifyListeners
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// src/modules/store.ts
|
|
851
|
+
function createStore(config) {
|
|
852
|
+
const registrar = createRegistrar();
|
|
853
|
+
const coeffectProviders = config.coeffects || {};
|
|
854
|
+
const errorHandlerManager = createErrorHandler(
|
|
855
|
+
config.errorHandler?.handler,
|
|
856
|
+
config.errorHandler?.rethrow !== void 0 ? { rethrow: config.errorHandler.rethrow } : void 0
|
|
857
|
+
);
|
|
858
|
+
let subscriptionManagerRef = null;
|
|
859
|
+
const stateManager = createStateManager(
|
|
860
|
+
config.initialState,
|
|
861
|
+
config.onStateChange,
|
|
862
|
+
(state) => {
|
|
863
|
+
if (subscriptionManagerRef) {
|
|
864
|
+
subscriptionManagerRef.notifyListeners(state);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
);
|
|
868
|
+
const subscriptionManager = createSubscriptionManager({
|
|
869
|
+
stateManager,
|
|
870
|
+
errorHandler: errorHandlerManager
|
|
871
|
+
});
|
|
872
|
+
subscriptionManagerRef = subscriptionManager;
|
|
873
|
+
const tracer = createTracer(config.tracing);
|
|
874
|
+
let dispatchFn = () => {
|
|
875
|
+
throw new Error("Router not initialized yet. This should not happen during store creation.");
|
|
876
|
+
};
|
|
877
|
+
let deregisterEventFn = () => {
|
|
878
|
+
throw new Error("Event manager not initialized yet.");
|
|
879
|
+
};
|
|
880
|
+
let registerEffectFn = () => {
|
|
881
|
+
throw new Error("Event manager not initialized yet.");
|
|
882
|
+
};
|
|
883
|
+
const effectExecutor = createEffectExecutor({
|
|
884
|
+
registrar,
|
|
885
|
+
stateManager,
|
|
886
|
+
errorHandler: errorHandlerManager,
|
|
887
|
+
dispatch: (eventKey, payload) => {
|
|
888
|
+
return dispatchFn(eventKey, payload);
|
|
889
|
+
},
|
|
890
|
+
deregisterEvent: (eventKey) => {
|
|
891
|
+
deregisterEventFn(eventKey);
|
|
892
|
+
},
|
|
893
|
+
registerEffect: (effectType, handler) => {
|
|
894
|
+
registerEffectFn(effectType, handler);
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
const eventManager = createEventManager({
|
|
898
|
+
registrar,
|
|
899
|
+
stateManager,
|
|
900
|
+
effectExecutor,
|
|
901
|
+
errorHandler: errorHandlerManager,
|
|
902
|
+
tracer,
|
|
903
|
+
coeffectProviders
|
|
904
|
+
});
|
|
905
|
+
deregisterEventFn = (eventKey) => {
|
|
906
|
+
eventManager.deregisterEvent(eventKey);
|
|
907
|
+
};
|
|
908
|
+
registerEffectFn = (effectType, handler) => {
|
|
909
|
+
registrar.register("effect", effectType, handler);
|
|
910
|
+
};
|
|
911
|
+
const router = createRouter({
|
|
912
|
+
eventManager
|
|
913
|
+
});
|
|
914
|
+
dispatchFn = (eventKey, payload) => {
|
|
915
|
+
return router.dispatch(eventKey, payload);
|
|
916
|
+
};
|
|
917
|
+
effectExecutor.registerBuiltInEffects();
|
|
918
|
+
return {
|
|
919
|
+
// Event registration
|
|
920
|
+
registerEventDb: (eventKey, handler, interceptors) => {
|
|
921
|
+
eventManager.registerEventDb(eventKey, handler, interceptors);
|
|
922
|
+
},
|
|
923
|
+
registerEvent: (eventKey, handler, interceptors) => {
|
|
924
|
+
eventManager.registerEvent(eventKey, handler, interceptors);
|
|
925
|
+
},
|
|
926
|
+
deregisterEvent: (eventKey) => {
|
|
927
|
+
eventManager.deregisterEvent(eventKey);
|
|
928
|
+
},
|
|
929
|
+
// Effect registration
|
|
930
|
+
registerEffect: (effectType, handler) => {
|
|
931
|
+
if (registrar.has("effect", effectType)) {
|
|
932
|
+
console.warn(`Effect handler for "${effectType}" is being overwritten`);
|
|
933
|
+
}
|
|
934
|
+
registrar.register("effect", effectType, handler);
|
|
935
|
+
},
|
|
936
|
+
// Event dispatching
|
|
937
|
+
dispatch: (eventKey, payload) => {
|
|
938
|
+
return router.dispatch(eventKey, payload);
|
|
939
|
+
},
|
|
940
|
+
flush: async () => {
|
|
941
|
+
await router.flush();
|
|
942
|
+
},
|
|
943
|
+
// State access
|
|
944
|
+
getState: () => {
|
|
945
|
+
return stateManager.getState();
|
|
946
|
+
},
|
|
947
|
+
// Interceptor inspection
|
|
948
|
+
getInterceptors: (eventKey) => {
|
|
949
|
+
return registrar.get("event", eventKey);
|
|
950
|
+
},
|
|
951
|
+
// Error handling
|
|
952
|
+
registerErrorHandler: (handler, config2) => {
|
|
953
|
+
errorHandlerManager.register(handler, config2);
|
|
954
|
+
},
|
|
955
|
+
// Subscriptions
|
|
956
|
+
registerSubscription: (key, config2) => {
|
|
957
|
+
subscriptionManager.registerSubscription(key, config2);
|
|
958
|
+
},
|
|
959
|
+
subscribe: (key, params, callback) => {
|
|
960
|
+
return subscriptionManager.subscribe(key, params, callback);
|
|
961
|
+
},
|
|
962
|
+
query: (key, params) => {
|
|
963
|
+
return subscriptionManager.query(key, params);
|
|
964
|
+
},
|
|
965
|
+
getSubscription: (key, params) => {
|
|
966
|
+
return subscriptionManager.getSubscription(key, params);
|
|
967
|
+
},
|
|
968
|
+
// Tracing
|
|
969
|
+
registerTraceCallback: (key, callback) => {
|
|
970
|
+
tracer.registerTraceCallback(key, callback);
|
|
971
|
+
},
|
|
972
|
+
removeTraceCallback: (key) => {
|
|
973
|
+
tracer.removeTraceCallback(key);
|
|
974
|
+
}
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// src/modules/interceptor.ts
|
|
979
|
+
function path(pathKeys) {
|
|
980
|
+
return {
|
|
981
|
+
id: `path-${pathKeys.join(".")}`,
|
|
982
|
+
before: (context) => {
|
|
983
|
+
let value = context.coeffects.db;
|
|
984
|
+
for (const key of pathKeys) {
|
|
985
|
+
value = value?.[key];
|
|
986
|
+
}
|
|
987
|
+
return {
|
|
988
|
+
...context,
|
|
989
|
+
coeffects: {
|
|
990
|
+
...context.coeffects,
|
|
991
|
+
_originalDb: context.coeffects.db,
|
|
992
|
+
_pathKeys: pathKeys,
|
|
993
|
+
db: value
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
},
|
|
997
|
+
after: (context) => {
|
|
998
|
+
const pathKeys2 = context.coeffects._pathKeys;
|
|
999
|
+
const originalDb = context.coeffects._originalDb;
|
|
1000
|
+
const focusedState = context.effects.db;
|
|
1001
|
+
if (pathKeys2 && originalDb !== void 0 && focusedState !== void 0) {
|
|
1002
|
+
let newDb = { ...originalDb };
|
|
1003
|
+
let current = newDb;
|
|
1004
|
+
for (let i = 0; i < pathKeys2.length - 1; i++) {
|
|
1005
|
+
current[pathKeys2[i]] = { ...current[pathKeys2[i]] };
|
|
1006
|
+
current = current[pathKeys2[i]];
|
|
1007
|
+
}
|
|
1008
|
+
current[pathKeys2[pathKeys2.length - 1]] = focusedState;
|
|
1009
|
+
return {
|
|
1010
|
+
...context,
|
|
1011
|
+
coeffects: {
|
|
1012
|
+
...context.coeffects,
|
|
1013
|
+
db: originalDb
|
|
1014
|
+
},
|
|
1015
|
+
effects: {
|
|
1016
|
+
...context.effects,
|
|
1017
|
+
db: newDb
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
if (pathKeys2 && originalDb !== void 0) {
|
|
1022
|
+
return {
|
|
1023
|
+
...context,
|
|
1024
|
+
coeffects: {
|
|
1025
|
+
...context.coeffects,
|
|
1026
|
+
db: originalDb
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
return context;
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
function debug() {
|
|
1035
|
+
return {
|
|
1036
|
+
id: "debug",
|
|
1037
|
+
before: (context) => {
|
|
1038
|
+
console.group("Event");
|
|
1039
|
+
console.log("Coeffects:", context.coeffects);
|
|
1040
|
+
return context;
|
|
1041
|
+
},
|
|
1042
|
+
after: (context) => {
|
|
1043
|
+
console.log("New State:", context.coeffects.db);
|
|
1044
|
+
console.log("Effects:", context.effects);
|
|
1045
|
+
console.groupEnd();
|
|
1046
|
+
return context;
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
function after(fn) {
|
|
1051
|
+
return {
|
|
1052
|
+
id: "after",
|
|
1053
|
+
after: (context) => {
|
|
1054
|
+
fn(context.coeffects.db, context.effects);
|
|
1055
|
+
return context;
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
function injectCofx(key, value) {
|
|
1060
|
+
return {
|
|
1061
|
+
id: `inject-${key}`,
|
|
1062
|
+
before: (context) => {
|
|
1063
|
+
return {
|
|
1064
|
+
...context,
|
|
1065
|
+
coeffects: {
|
|
1066
|
+
...context.coeffects,
|
|
1067
|
+
[key]: value
|
|
1068
|
+
}
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
function validate(schema) {
|
|
1074
|
+
return {
|
|
1075
|
+
id: "validate",
|
|
1076
|
+
after: (context) => {
|
|
1077
|
+
const result = schema(context.coeffects.db);
|
|
1078
|
+
if (result !== true) {
|
|
1079
|
+
console.error("State validation failed:", result);
|
|
1080
|
+
}
|
|
1081
|
+
return context;
|
|
1082
|
+
}
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
export {
|
|
1086
|
+
Subscription,
|
|
1087
|
+
SubscriptionRegistry,
|
|
1088
|
+
after,
|
|
1089
|
+
createStore,
|
|
1090
|
+
debug,
|
|
1091
|
+
defaultErrorHandler,
|
|
1092
|
+
injectCofx,
|
|
1093
|
+
mergeEffects,
|
|
1094
|
+
path,
|
|
1095
|
+
validate
|
|
1096
|
+
};
|