@pyreon/state-tree 0.0.1
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/LICENSE +21 -0
- package/README.md +249 -0
- package/lib/analysis/devtools.js.html +5406 -0
- package/lib/analysis/index.js.html +5406 -0
- package/lib/devtools.js +111 -0
- package/lib/devtools.js.map +1 -0
- package/lib/index.js +353 -0
- package/lib/index.js.map +1 -0
- package/lib/types/devtools.d.ts +104 -0
- package/lib/types/devtools.d.ts.map +1 -0
- package/lib/types/devtools2.d.ts +40 -0
- package/lib/types/devtools2.d.ts.map +1 -0
- package/lib/types/index.d.ts +316 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index2.d.ts +198 -0
- package/lib/types/index2.d.ts.map +1 -0
- package/package.json +54 -0
- package/src/devtools.ts +87 -0
- package/src/index.ts +29 -0
- package/src/instance.ts +128 -0
- package/src/middleware.ts +57 -0
- package/src/model.ts +117 -0
- package/src/patch.ts +173 -0
- package/src/registry.ts +16 -0
- package/src/snapshot.ts +66 -0
- package/src/tests/devtools.test.ts +163 -0
- package/src/tests/model.test.ts +718 -0
- package/src/types.ts +98 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//#region src/devtools.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* @pyreon/state-tree devtools introspection API.
|
|
4
|
+
* Import: `import { ... } from "@pyreon/state-tree/devtools"`
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Register a model instance for devtools inspection.
|
|
8
|
+
* Call this when creating instances you want visible in devtools.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const counter = Counter.create()
|
|
12
|
+
* registerInstance("app-counter", counter)
|
|
13
|
+
*/
|
|
14
|
+
declare function registerInstance(name: string, instance: object): void;
|
|
15
|
+
/**
|
|
16
|
+
* Unregister a model instance.
|
|
17
|
+
*/
|
|
18
|
+
declare function unregisterInstance(name: string): void;
|
|
19
|
+
/**
|
|
20
|
+
* Get all registered model instance names.
|
|
21
|
+
* Automatically cleans up garbage-collected instances.
|
|
22
|
+
*/
|
|
23
|
+
declare function getActiveModels(): string[];
|
|
24
|
+
/**
|
|
25
|
+
* Get a model instance by name (or undefined if GC'd or not registered).
|
|
26
|
+
*/
|
|
27
|
+
declare function getModelInstance(name: string): object | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Get a snapshot of a registered model instance.
|
|
30
|
+
*/
|
|
31
|
+
declare function getModelSnapshot(name: string): Record<string, unknown> | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Subscribe to model registry changes. Returns unsubscribe function.
|
|
34
|
+
*/
|
|
35
|
+
declare function onModelChange(listener: () => void): () => void;
|
|
36
|
+
/** @internal — reset devtools registry (for tests). */
|
|
37
|
+
declare function _resetDevtools(): void;
|
|
38
|
+
//#endregion
|
|
39
|
+
export { _resetDevtools, getActiveModels, getModelInstance, getModelSnapshot, onModelChange, registerInstance, unregisterInstance };
|
|
40
|
+
//# sourceMappingURL=devtools2.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devtools2.d.ts","names":[],"sources":["../../src/devtools.ts"],"mappings":";;AAuBA;;;;;AAQA;;;;;AASA;iBAjBgB,gBAAA,CAAiB,IAAA,UAAc,QAAA;;;;iBAQ/B,kBAAA,CAAmB,IAAA;;;;;iBASnB,eAAA,CAAA;;;;iBAUA,gBAAA,CAAiB,IAAA;AAyBjC;;;AAAA,iBAXgB,gBAAA,CACd,IAAA,WACC,MAAA;;AAiBH;;iBARgB,aAAA,CAAc,QAAA;;iBAQd,cAAA,CAAA"}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { batch, signal } from "@pyreon/reactivity";
|
|
2
|
+
|
|
3
|
+
//#region src/registry.ts
|
|
4
|
+
/**
|
|
5
|
+
* WeakMap from every model instance object → its internal metadata.
|
|
6
|
+
* Shared across patch, middleware, and snapshot modules.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Returns true when a value is a model instance (has metadata registered). */
|
|
10
|
+
function isModelInstance(value) {
|
|
11
|
+
return value != null && typeof value === "object" && instanceMeta.has(value);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/middleware.ts
|
|
16
|
+
/**
|
|
17
|
+
* Run an action through the middleware chain registered on `meta`.
|
|
18
|
+
* Each middleware receives the call descriptor and a `next` function.
|
|
19
|
+
* If no middlewares, the action runs directly.
|
|
20
|
+
*/
|
|
21
|
+
function runAction(meta, name, fn, args) {
|
|
22
|
+
const call = {
|
|
23
|
+
name,
|
|
24
|
+
args,
|
|
25
|
+
path: `/${name}`
|
|
26
|
+
};
|
|
27
|
+
const dispatch = (idx, c) => {
|
|
28
|
+
if (idx >= meta.middlewares.length) return fn(...c.args);
|
|
29
|
+
const mw = meta.middlewares[idx];
|
|
30
|
+
if (!mw) return fn(...c.args);
|
|
31
|
+
return mw(c, nextCall => dispatch(idx + 1, nextCall));
|
|
32
|
+
};
|
|
33
|
+
return dispatch(0, call);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Intercept every action call on `instance`.
|
|
37
|
+
* Middlewares run in registration order — call `next(call)` to continue.
|
|
38
|
+
*
|
|
39
|
+
* Returns an unsubscribe function.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* const unsub = addMiddleware(counter, (call, next) => {
|
|
43
|
+
* console.log(`> ${call.name}(${call.args})`)
|
|
44
|
+
* const result = next(call)
|
|
45
|
+
* console.log(`< ${call.name}`)
|
|
46
|
+
* return result
|
|
47
|
+
* })
|
|
48
|
+
*/
|
|
49
|
+
function addMiddleware(instance, middleware) {
|
|
50
|
+
const meta = instanceMeta.get(instance);
|
|
51
|
+
if (!meta) throw new Error("[@pyreon/state-tree] addMiddleware: not a model instance");
|
|
52
|
+
meta.middlewares.push(middleware);
|
|
53
|
+
return () => {
|
|
54
|
+
const idx = meta.middlewares.indexOf(middleware);
|
|
55
|
+
if (idx !== -1) meta.middlewares.splice(idx, 1);
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/patch.ts
|
|
61
|
+
/** Property names that must never be used as patch path segments. */
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Wraps a signal so that every write emits a JSON patch via `emitPatch`.
|
|
65
|
+
* Reads are pass-through — no overhead on hot reactive paths.
|
|
66
|
+
*
|
|
67
|
+
* @param hasListeners Optional predicate — when provided, patch object allocation
|
|
68
|
+
* and snapshotting are skipped entirely when no listeners are registered.
|
|
69
|
+
*/
|
|
70
|
+
function trackedSignal(inner, path, emitPatch, hasListeners) {
|
|
71
|
+
const read = () => inner();
|
|
72
|
+
read.peek = () => inner.peek();
|
|
73
|
+
read.subscribe = listener => inner.subscribe(listener);
|
|
74
|
+
read.set = newValue => {
|
|
75
|
+
const prev = inner.peek();
|
|
76
|
+
inner.set(newValue);
|
|
77
|
+
if (!Object.is(prev, newValue) && (!hasListeners || hasListeners())) emitPatch({
|
|
78
|
+
op: "replace",
|
|
79
|
+
path,
|
|
80
|
+
value: isModelInstance(newValue) ? snapshotValue(newValue) : newValue
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
read.update = fn => {
|
|
84
|
+
read.set(fn(inner.peek()));
|
|
85
|
+
};
|
|
86
|
+
return read;
|
|
87
|
+
}
|
|
88
|
+
/** Shallow snapshot helper (avoids importing snapshot.ts to prevent circular deps). */
|
|
89
|
+
function snapshotValue(instance) {
|
|
90
|
+
const meta = instanceMeta.get(instance);
|
|
91
|
+
if (!meta) return instance;
|
|
92
|
+
const out = {};
|
|
93
|
+
for (const key of meta.stateKeys) {
|
|
94
|
+
const sig = instance[key];
|
|
95
|
+
if (!sig) continue;
|
|
96
|
+
const val = sig.peek();
|
|
97
|
+
out[key] = isModelInstance(val) ? snapshotValue(val) : val;
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Subscribe to every state mutation in `instance` as a JSON patch.
|
|
103
|
+
* Also captures mutations in nested model instances (path is prefixed).
|
|
104
|
+
*
|
|
105
|
+
* Returns an unsubscribe function.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* const unsub = onPatch(counter, patch => {
|
|
109
|
+
* // { op: "replace", path: "/count", value: 6 }
|
|
110
|
+
* })
|
|
111
|
+
*/
|
|
112
|
+
function onPatch(instance, listener) {
|
|
113
|
+
const meta = instanceMeta.get(instance);
|
|
114
|
+
if (!meta) throw new Error("[@pyreon/state-tree] onPatch: not a model instance");
|
|
115
|
+
meta.patchListeners.add(listener);
|
|
116
|
+
return () => meta.patchListeners.delete(listener);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Apply a JSON patch (or array of patches) to a model instance.
|
|
120
|
+
* Only "replace" operations are supported (matching the patches emitted by `onPatch`).
|
|
121
|
+
*
|
|
122
|
+
* Paths use JSON pointer format: `"/count"` for top-level, `"/profile/name"` for nested.
|
|
123
|
+
* Nested model instances are resolved automatically.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* applyPatch(counter, { op: "replace", path: "/count", value: 10 })
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* // Replay patches recorded from onPatch (undo/redo, time-travel)
|
|
130
|
+
* applyPatch(counter, [
|
|
131
|
+
* { op: "replace", path: "/count", value: 1 },
|
|
132
|
+
* { op: "replace", path: "/count", value: 2 },
|
|
133
|
+
* ])
|
|
134
|
+
*/
|
|
135
|
+
function applyPatch(instance, patch) {
|
|
136
|
+
const patches = Array.isArray(patch) ? patch : [patch];
|
|
137
|
+
batch(() => {
|
|
138
|
+
for (const p of patches) {
|
|
139
|
+
if (p.op !== "replace") throw new Error(`[@pyreon/state-tree] applyPatch: unsupported op "${p.op}"`);
|
|
140
|
+
const segments = p.path.split("/").filter(Boolean);
|
|
141
|
+
if (segments.length === 0) throw new Error("[@pyreon/state-tree] applyPatch: empty path");
|
|
142
|
+
let target = instance;
|
|
143
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
144
|
+
const segment = segments[i];
|
|
145
|
+
if (RESERVED_KEYS.has(segment)) throw new Error(`[@pyreon/state-tree] applyPatch: reserved property name "${segment}"`);
|
|
146
|
+
if (!instanceMeta.get(target)) throw new Error(`[@pyreon/state-tree] applyPatch: not a model instance at "${segment}"`);
|
|
147
|
+
const sig = target[segment];
|
|
148
|
+
if (!sig || typeof sig.peek !== "function") throw new Error(`[@pyreon/state-tree] applyPatch: unknown state key "${segment}"`);
|
|
149
|
+
const nested = sig.peek();
|
|
150
|
+
if (!nested || typeof nested !== "object" || !isModelInstance(nested)) throw new Error(`[@pyreon/state-tree] applyPatch: "${segment}" is not a nested model instance`);
|
|
151
|
+
target = nested;
|
|
152
|
+
}
|
|
153
|
+
const lastKey = segments[segments.length - 1];
|
|
154
|
+
if (RESERVED_KEYS.has(lastKey)) throw new Error(`[@pyreon/state-tree] applyPatch: reserved property name "${lastKey}"`);
|
|
155
|
+
const meta = instanceMeta.get(target);
|
|
156
|
+
if (!meta) throw new Error("[@pyreon/state-tree] applyPatch: not a model instance");
|
|
157
|
+
if (!meta.stateKeys.includes(lastKey)) throw new Error(`[@pyreon/state-tree] applyPatch: unknown state key "${lastKey}"`);
|
|
158
|
+
const sig = target[lastKey];
|
|
159
|
+
if (sig && typeof sig.set === "function") sig.set(p.value);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
//#endregion
|
|
165
|
+
//#region src/types.ts
|
|
166
|
+
/** Property key stamped on every ModelDefinition to distinguish it from plain objects. */
|
|
167
|
+
|
|
168
|
+
//#endregion
|
|
169
|
+
//#region src/instance.ts
|
|
170
|
+
function isModelDef(v) {
|
|
171
|
+
if (v == null || typeof v !== "object") return false;
|
|
172
|
+
return v[MODEL_BRAND] === true;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Create a live model instance from a config + optional initial snapshot.
|
|
176
|
+
* Called by `ModelDefinition.create()`.
|
|
177
|
+
*/
|
|
178
|
+
function createInstance(config, initial) {
|
|
179
|
+
const instance = {};
|
|
180
|
+
const meta = {
|
|
181
|
+
stateKeys: [],
|
|
182
|
+
patchListeners: /* @__PURE__ */new Set(),
|
|
183
|
+
middlewares: [],
|
|
184
|
+
emitPatch(patch) {
|
|
185
|
+
if (this.patchListeners.size === 0) return;
|
|
186
|
+
for (const listener of this.patchListeners) listener(patch);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
instanceMeta.set(instance, meta);
|
|
190
|
+
const self = new Proxy(instance, {
|
|
191
|
+
get(_, k) {
|
|
192
|
+
return instance[k];
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
for (const [key, defaultValue] of Object.entries(config.state)) {
|
|
196
|
+
meta.stateKeys.push(key);
|
|
197
|
+
const path = `/${key}`;
|
|
198
|
+
const initValue = key in initial ? initial[key] : void 0;
|
|
199
|
+
let rawSig;
|
|
200
|
+
if (isModelDef(defaultValue)) {
|
|
201
|
+
const nestedInstance = createInstance(defaultValue._config, initValue ?? {});
|
|
202
|
+
rawSig = signal(nestedInstance);
|
|
203
|
+
onPatch(nestedInstance, patch => {
|
|
204
|
+
meta.emitPatch({
|
|
205
|
+
...patch,
|
|
206
|
+
path: path + patch.path
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
} else rawSig = signal(initValue !== void 0 ? initValue : defaultValue);
|
|
210
|
+
instance[key] = trackedSignal(rawSig, path, p => meta.emitPatch(p), () => meta.patchListeners.size > 0);
|
|
211
|
+
}
|
|
212
|
+
if (config.views) {
|
|
213
|
+
const views = config.views(self);
|
|
214
|
+
for (const [key, view] of Object.entries(views)) instance[key] = view;
|
|
215
|
+
}
|
|
216
|
+
if (config.actions) {
|
|
217
|
+
const rawActions = config.actions(self);
|
|
218
|
+
for (const [key, actionFn] of Object.entries(rawActions)) instance[key] = (...args) => runAction(meta, key, actionFn, args);
|
|
219
|
+
}
|
|
220
|
+
return instance;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
//#endregion
|
|
224
|
+
//#region src/model.ts
|
|
225
|
+
|
|
226
|
+
/** Destroy a hook singleton by id so next call re-creates the instance. */
|
|
227
|
+
function resetHook(id) {
|
|
228
|
+
_hookRegistry.delete(id);
|
|
229
|
+
}
|
|
230
|
+
/** Destroy all hook singletons. */
|
|
231
|
+
function resetAllHooks() {
|
|
232
|
+
_hookRegistry.clear();
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Returned by `model()`. Call `.create()` for instances or `.asHook(id)` for
|
|
236
|
+
* a Zustand-style singleton hook.
|
|
237
|
+
*/
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Define a reactive model with state, views, and actions.
|
|
241
|
+
*
|
|
242
|
+
* - **state** — plain JS object; each key becomes a `Signal<T>` on the instance.
|
|
243
|
+
* - **views** — factory receiving `self`; return computed signals for derived state.
|
|
244
|
+
* - **actions** — factory receiving `self`; return functions that mutate state.
|
|
245
|
+
*
|
|
246
|
+
* Use nested `ModelDefinition` values in `state` to compose models.
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* const Counter = model({
|
|
250
|
+
* state: { count: 0 },
|
|
251
|
+
* views: (self) => ({
|
|
252
|
+
* doubled: computed(() => self.count() * 2),
|
|
253
|
+
* }),
|
|
254
|
+
* actions: (self) => ({
|
|
255
|
+
* inc: () => self.count.update(c => c + 1),
|
|
256
|
+
* reset: () => self.count.set(0),
|
|
257
|
+
* }),
|
|
258
|
+
* })
|
|
259
|
+
*
|
|
260
|
+
* const c = Counter.create({ count: 5 })
|
|
261
|
+
* c.count() // 5
|
|
262
|
+
* c.inc()
|
|
263
|
+
* c.doubled() // 12
|
|
264
|
+
*/
|
|
265
|
+
function model(config) {
|
|
266
|
+
return new ModelDefinition(config);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
//#endregion
|
|
270
|
+
//#region src/snapshot.ts
|
|
271
|
+
/**
|
|
272
|
+
* Serialize a model instance to a plain JS object (no signals, no functions).
|
|
273
|
+
* Nested model instances are recursively serialized.
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* getSnapshot(counter) // { count: 6 }
|
|
277
|
+
* getSnapshot(app) // { profile: { name: "Alice" }, title: "My App" }
|
|
278
|
+
*/
|
|
279
|
+
function getSnapshot(instance) {
|
|
280
|
+
const meta = instanceMeta.get(instance);
|
|
281
|
+
if (!meta) throw new Error("[@pyreon/state-tree] getSnapshot: not a model instance");
|
|
282
|
+
const out = {};
|
|
283
|
+
for (const key of meta.stateKeys) {
|
|
284
|
+
const sig = instance[key];
|
|
285
|
+
if (!sig) continue;
|
|
286
|
+
const val = sig.peek();
|
|
287
|
+
out[key] = isModelInstance(val) ? getSnapshot(val) : val;
|
|
288
|
+
}
|
|
289
|
+
return out;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Restore a model instance from a plain-object snapshot.
|
|
293
|
+
* All signal writes are coalesced via `batch()` for a single reactive flush.
|
|
294
|
+
* Keys absent from the snapshot are left unchanged.
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* applySnapshot(counter, { count: 0 })
|
|
298
|
+
*/
|
|
299
|
+
function applySnapshot(instance, snapshot) {
|
|
300
|
+
const meta = instanceMeta.get(instance);
|
|
301
|
+
if (!meta) throw new Error("[@pyreon/state-tree] applySnapshot: not a model instance");
|
|
302
|
+
batch(() => {
|
|
303
|
+
for (const key of meta.stateKeys) {
|
|
304
|
+
if (!(key in snapshot)) continue;
|
|
305
|
+
const sig = instance[key];
|
|
306
|
+
if (!sig) continue;
|
|
307
|
+
const val = snapshot[key];
|
|
308
|
+
const current = sig.peek();
|
|
309
|
+
if (isModelInstance(current)) applySnapshot(current, val);else sig.set(val);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
//#endregion
|
|
315
|
+
export { addMiddleware, applyPatch, applySnapshot, getSnapshot, model, onPatch, resetAllHooks, resetHook };
|
|
316
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/registry.ts","../../src/middleware.ts","../../src/patch.ts","../../src/types.ts","../../src/instance.ts","../../src/model.ts","../../src/snapshot.ts"],"mappings":"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"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { Computed, Signal } from "@pyreon/reactivity";
|
|
2
|
+
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
/** Property key stamped on every ModelDefinition to distinguish it from plain objects. */
|
|
5
|
+
declare const MODEL_BRAND: "__pyreonMod";
|
|
6
|
+
type StateShape = Record<string, unknown>;
|
|
7
|
+
/**
|
|
8
|
+
* Resolve a state field type:
|
|
9
|
+
* - ModelDefinition → the instance type it produces
|
|
10
|
+
* - Anything else → as-is
|
|
11
|
+
*/
|
|
12
|
+
type ResolveField<T> = T extends {
|
|
13
|
+
readonly __pyreonMod: true;
|
|
14
|
+
create(initial?: any): infer I;
|
|
15
|
+
} ? I : T;
|
|
16
|
+
/** Map state shape to per-field signals. */
|
|
17
|
+
type StateSignals<TState extends StateShape> = { readonly [K in keyof TState]: Signal<ResolveField<TState[K]>> };
|
|
18
|
+
/**
|
|
19
|
+
* `self` type inside actions / views:
|
|
20
|
+
* strongly typed for state signals, `any` for actions and views so that
|
|
21
|
+
* actions can call each other without circular type issues.
|
|
22
|
+
*/
|
|
23
|
+
type ModelSelf<TState extends StateShape> = StateSignals<TState> & Record<string, any>;
|
|
24
|
+
/** The public instance type returned by `.create()` and hooks. */
|
|
25
|
+
type ModelInstance<TState extends StateShape, TActions extends Record<string, (...args: any[]) => any>, TViews extends Record<string, Signal<any> | Computed<any>>> = StateSignals<TState> & TActions & TViews;
|
|
26
|
+
/**
|
|
27
|
+
* Extract the state type from a ModelDefinition.
|
|
28
|
+
* Used by Snapshot to recursively resolve nested model types.
|
|
29
|
+
*/
|
|
30
|
+
type ExtractModelState<T> = T extends {
|
|
31
|
+
readonly __pyreonMod: true;
|
|
32
|
+
readonly _config: {
|
|
33
|
+
state: infer S extends StateShape;
|
|
34
|
+
};
|
|
35
|
+
} ? S : never;
|
|
36
|
+
/**
|
|
37
|
+
* Snapshot type: plain JS values (no signals, no model instances).
|
|
38
|
+
* Nested model fields recursively produce their own typed snapshot.
|
|
39
|
+
*/
|
|
40
|
+
type Snapshot<TState extends StateShape> = { [K in keyof TState]: TState[K] extends {
|
|
41
|
+
readonly __pyreonMod: true;
|
|
42
|
+
} ? Snapshot<ExtractModelState<TState[K]>> : TState[K] };
|
|
43
|
+
interface Patch {
|
|
44
|
+
op: 'replace';
|
|
45
|
+
path: string;
|
|
46
|
+
value: unknown;
|
|
47
|
+
}
|
|
48
|
+
type PatchListener = (patch: Patch) => void;
|
|
49
|
+
interface ActionCall {
|
|
50
|
+
/** Action name. */
|
|
51
|
+
name: string;
|
|
52
|
+
/** Arguments passed to the action. */
|
|
53
|
+
args: unknown[];
|
|
54
|
+
/** JSON-pointer-style path, e.g. `"/inc"`. */
|
|
55
|
+
path: string;
|
|
56
|
+
}
|
|
57
|
+
type MiddlewareFn = (call: ActionCall, next: (nextCall: ActionCall) => unknown) => unknown;
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/instance.d.ts
|
|
60
|
+
interface ModelConfig<TState extends StateShape, TActions, TViews> {
|
|
61
|
+
state: TState;
|
|
62
|
+
views?: (self: any) => TViews;
|
|
63
|
+
actions?: (self: any) => TActions;
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/model.d.ts
|
|
67
|
+
/** Destroy a hook singleton by id so next call re-creates the instance. */
|
|
68
|
+
declare function resetHook(id: string): void;
|
|
69
|
+
/** Destroy all hook singletons. */
|
|
70
|
+
declare function resetAllHooks(): void;
|
|
71
|
+
/**
|
|
72
|
+
* Returned by `model()`. Call `.create()` for instances or `.asHook(id)` for
|
|
73
|
+
* a Zustand-style singleton hook.
|
|
74
|
+
*/
|
|
75
|
+
declare class ModelDefinition<TState extends StateShape, TActions extends Record<string, (...args: any[]) => any>, TViews extends Record<string, Signal<any> | Computed<any>>> {
|
|
76
|
+
/** Brand used to identify ModelDefinition objects at runtime (without instanceof). */
|
|
77
|
+
readonly [MODEL_BRAND]: true;
|
|
78
|
+
/** @internal — exposed so nested instance creation can read it. */
|
|
79
|
+
readonly _config: ModelConfig<TState, TActions, TViews>;
|
|
80
|
+
constructor(config: ModelConfig<TState, TActions, TViews>);
|
|
81
|
+
/**
|
|
82
|
+
* Create a new independent model instance.
|
|
83
|
+
* Pass a partial snapshot to override defaults.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* const counter = Counter.create({ count: 5 })
|
|
87
|
+
*/
|
|
88
|
+
create(initial?: Partial<Snapshot<TState>>): ModelInstance<TState, TActions, TViews>;
|
|
89
|
+
/**
|
|
90
|
+
* Returns a hook function that always returns the same singleton instance
|
|
91
|
+
* for the given `id` — Zustand / Pinia style.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* const useCounter = Counter.asHook("app-counter")
|
|
95
|
+
* // Any call to useCounter() returns the same instance.
|
|
96
|
+
* const store = useCounter()
|
|
97
|
+
*/
|
|
98
|
+
asHook(id: string): () => ModelInstance<TState, TActions, TViews>;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Define a reactive model with state, views, and actions.
|
|
102
|
+
*
|
|
103
|
+
* - **state** — plain JS object; each key becomes a `Signal<T>` on the instance.
|
|
104
|
+
* - **views** — factory receiving `self`; return computed signals for derived state.
|
|
105
|
+
* - **actions** — factory receiving `self`; return functions that mutate state.
|
|
106
|
+
*
|
|
107
|
+
* Use nested `ModelDefinition` values in `state` to compose models.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* const Counter = model({
|
|
111
|
+
* state: { count: 0 },
|
|
112
|
+
* views: (self) => ({
|
|
113
|
+
* doubled: computed(() => self.count() * 2),
|
|
114
|
+
* }),
|
|
115
|
+
* actions: (self) => ({
|
|
116
|
+
* inc: () => self.count.update(c => c + 1),
|
|
117
|
+
* reset: () => self.count.set(0),
|
|
118
|
+
* }),
|
|
119
|
+
* })
|
|
120
|
+
*
|
|
121
|
+
* const c = Counter.create({ count: 5 })
|
|
122
|
+
* c.count() // 5
|
|
123
|
+
* c.inc()
|
|
124
|
+
* c.doubled() // 12
|
|
125
|
+
*/
|
|
126
|
+
declare function model<TState extends StateShape, TActions extends Record<string, (...args: any[]) => any> = Record<never, never>, TViews extends Record<string, Signal<any> | Computed<any>> = Record<never, never>>(config: ModelConfig<TState, TActions, TViews>): ModelDefinition<TState, TActions, TViews>;
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/snapshot.d.ts
|
|
129
|
+
/**
|
|
130
|
+
* Serialize a model instance to a plain JS object (no signals, no functions).
|
|
131
|
+
* Nested model instances are recursively serialized.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* getSnapshot(counter) // { count: 6 }
|
|
135
|
+
* getSnapshot(app) // { profile: { name: "Alice" }, title: "My App" }
|
|
136
|
+
*/
|
|
137
|
+
declare function getSnapshot<TState extends StateShape>(instance: object): Snapshot<TState>;
|
|
138
|
+
/**
|
|
139
|
+
* Restore a model instance from a plain-object snapshot.
|
|
140
|
+
* All signal writes are coalesced via `batch()` for a single reactive flush.
|
|
141
|
+
* Keys absent from the snapshot are left unchanged.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* applySnapshot(counter, { count: 0 })
|
|
145
|
+
*/
|
|
146
|
+
declare function applySnapshot<TState extends StateShape>(instance: object, snapshot: Partial<Snapshot<TState>>): void;
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/patch.d.ts
|
|
149
|
+
/**
|
|
150
|
+
* Subscribe to every state mutation in `instance` as a JSON patch.
|
|
151
|
+
* Also captures mutations in nested model instances (path is prefixed).
|
|
152
|
+
*
|
|
153
|
+
* Returns an unsubscribe function.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* const unsub = onPatch(counter, patch => {
|
|
157
|
+
* // { op: "replace", path: "/count", value: 6 }
|
|
158
|
+
* })
|
|
159
|
+
*/
|
|
160
|
+
declare function onPatch(instance: object, listener: PatchListener): () => void;
|
|
161
|
+
/**
|
|
162
|
+
* Apply a JSON patch (or array of patches) to a model instance.
|
|
163
|
+
* Only "replace" operations are supported (matching the patches emitted by `onPatch`).
|
|
164
|
+
*
|
|
165
|
+
* Paths use JSON pointer format: `"/count"` for top-level, `"/profile/name"` for nested.
|
|
166
|
+
* Nested model instances are resolved automatically.
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* applyPatch(counter, { op: "replace", path: "/count", value: 10 })
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* // Replay patches recorded from onPatch (undo/redo, time-travel)
|
|
173
|
+
* applyPatch(counter, [
|
|
174
|
+
* { op: "replace", path: "/count", value: 1 },
|
|
175
|
+
* { op: "replace", path: "/count", value: 2 },
|
|
176
|
+
* ])
|
|
177
|
+
*/
|
|
178
|
+
declare function applyPatch(instance: object, patch: Patch | Patch[]): void;
|
|
179
|
+
//#endregion
|
|
180
|
+
//#region src/middleware.d.ts
|
|
181
|
+
/**
|
|
182
|
+
* Intercept every action call on `instance`.
|
|
183
|
+
* Middlewares run in registration order — call `next(call)` to continue.
|
|
184
|
+
*
|
|
185
|
+
* Returns an unsubscribe function.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* const unsub = addMiddleware(counter, (call, next) => {
|
|
189
|
+
* console.log(`> ${call.name}(${call.args})`)
|
|
190
|
+
* const result = next(call)
|
|
191
|
+
* console.log(`< ${call.name}`)
|
|
192
|
+
* return result
|
|
193
|
+
* })
|
|
194
|
+
*/
|
|
195
|
+
declare function addMiddleware(instance: object, middleware: MiddlewareFn): () => void;
|
|
196
|
+
//#endregion
|
|
197
|
+
export { type ActionCall, type MiddlewareFn, type ModelDefinition, type ModelInstance, type ModelSelf, type Patch, type PatchListener, type Snapshot, type StateShape, addMiddleware, applyPatch, applySnapshot, getSnapshot, model, onPatch, resetAllHooks, resetHook };
|
|
198
|
+
//# sourceMappingURL=index2.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index2.d.ts","names":[],"sources":["../../src/types.ts","../../src/instance.ts","../../src/model.ts","../../src/snapshot.ts","../../src/patch.ts","../../src/middleware.ts"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pyreon/state-tree",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Structured reactive state tree — composable models with snapshots, patches, and middleware",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/pyreon/fundamentals.git",
|
|
9
|
+
"directory": "packages/state-tree"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/pyreon/fundamentals/tree/main/packages/state-tree#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/pyreon/fundamentals/issues"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"lib",
|
|
20
|
+
"src",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "./lib/index.js",
|
|
26
|
+
"module": "./lib/index.js",
|
|
27
|
+
"types": "./lib/types/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"bun": "./src/index.ts",
|
|
31
|
+
"import": "./lib/index.js",
|
|
32
|
+
"types": "./lib/types/index.d.ts"
|
|
33
|
+
},
|
|
34
|
+
"./devtools": {
|
|
35
|
+
"bun": "./src/devtools.ts",
|
|
36
|
+
"import": "./lib/devtools.js",
|
|
37
|
+
"types": "./lib/types/devtools.d.ts"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"sideEffects": false,
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "vl_rolldown_build",
|
|
43
|
+
"dev": "vl_rolldown_build-watch",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"typecheck": "tsc --noEmit"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"@pyreon/reactivity": "^0.2.1"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@happy-dom/global-registrator": "^20.8.3",
|
|
52
|
+
"@pyreon/reactivity": "^0.2.1"
|
|
53
|
+
}
|
|
54
|
+
}
|