opshot 0.1.1 → 0.3.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 +224 -89
- package/dist/index.d.ts +173 -35
- package/dist/index.js +2262 -110
- package/package.json +74 -70
- package/dist/react.d.ts +0 -9
- package/dist/react.js +0 -267
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matt Cavender
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -2,12 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# opshot
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- **Self-contained**: a state carries its data, its methods, and its own subscription; pass it around like any object.
|
|
8
|
-
- **Reactive reads**: components read plain properties, and reads are tracked per component: a component re-renders only when a property it read changes.
|
|
9
|
-
- **Safe mutation**: write by mutating the actual object directly inside `mutate`.
|
|
10
|
-
- **Ops events**: every mutation emits its changes as `{ do, undo }` JSON Patch pairs, ready for history, sync, and persistence.
|
|
5
|
+
Mutable state for React, with re-render for only the components that read what changed. (It's like [valtio](https://github.com/pmndrs/valtio), but not a footgun.)
|
|
11
6
|
|
|
12
7
|
## Install
|
|
13
8
|
|
|
@@ -15,109 +10,248 @@ Plain-object state for React: mutate it directly, re-render only the components
|
|
|
15
10
|
npm install opshot
|
|
16
11
|
```
|
|
17
12
|
|
|
18
|
-
##
|
|
13
|
+
## Mutable state
|
|
14
|
+
|
|
15
|
+
React state is immutable: changing one field means spreading the old object into a new one.
|
|
19
16
|
|
|
20
17
|
```tsx
|
|
21
|
-
|
|
18
|
+
const [user, setUser] = useState({ name: "Ada", age: 36 });
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
setUser((prev) => ({ ...prev, age: 37 }));
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**opshot** state is a live mutable object: you assign the field.
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
const user = useMutableState({ name: "Ada", age: 36 });
|
|
27
|
+
|
|
28
|
+
user.age = 37;
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Bounded re-renders
|
|
32
|
+
|
|
33
|
+
React re-renders a component and its children when its state changes.
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
interface User {
|
|
37
|
+
name: string;
|
|
38
|
+
age: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const Parent = () => {
|
|
42
|
+
const [user, setUser] = useState<User>({ name: "Ada", age: 36 });
|
|
43
|
+
|
|
44
|
+
const birthday = () => setUser((prev) => ({ ...prev, age: prev.age + 1 }));
|
|
45
|
+
|
|
46
|
+
// A click re-renders Parent and Child.
|
|
47
|
+
return (
|
|
48
|
+
<>
|
|
49
|
+
<button onClick={birthday}>+</button>
|
|
50
|
+
<Child user={user} />
|
|
51
|
+
</>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const Child = ({ user }: { user: User }) => <p>{user.age}</p>;
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**opshot** re-renders only what read the change. Wrap a child in `scope` and it subscribes to the fields it reads. **Where the mutation happens doesn't matter** — here Parent writes, and only Child re-renders, because renders follow reads, not writes.
|
|
25
59
|
|
|
26
|
-
|
|
27
|
-
|
|
60
|
+
```tsx
|
|
61
|
+
const Parent = () => {
|
|
62
|
+
const user = useMutableState<User>({ name: "Ada", age: 36 });
|
|
63
|
+
|
|
64
|
+
const birthday = () => {
|
|
65
|
+
user.age++;
|
|
66
|
+
};
|
|
28
67
|
|
|
29
|
-
|
|
68
|
+
// A click re-renders only Child.
|
|
69
|
+
return (
|
|
70
|
+
<>
|
|
71
|
+
<button onClick={birthday}>+</button>
|
|
72
|
+
<Child user={user} />
|
|
73
|
+
</>
|
|
74
|
+
);
|
|
30
75
|
};
|
|
76
|
+
|
|
77
|
+
const Child = scope<{ user: User }>(({ user }) => <p>{user.age}</p>);
|
|
31
78
|
```
|
|
32
79
|
|
|
33
|
-
|
|
80
|
+
This is how you optimize re-rendering across your component tree: place `scope` boundaries where you want re-renders contained, and each boundary re-renders only when a field it read changes. `useMutableState` is a boundary itself.
|
|
81
|
+
|
|
82
|
+
## Creating State
|
|
34
83
|
|
|
35
84
|
```tsx
|
|
36
|
-
import {
|
|
37
|
-
|
|
85
|
+
import { ignore, unsafeTrack, useMutableState, type Ignored, type UnsafeTracked } from "opshot";
|
|
86
|
+
|
|
87
|
+
interface PlayerState {
|
|
88
|
+
position: number;
|
|
89
|
+
element: Ignored<HTMLAudioElement>;
|
|
90
|
+
queue: UnsafeTracked<Playlist>;
|
|
91
|
+
seek: (position: number) => void;
|
|
92
|
+
}
|
|
38
93
|
|
|
39
94
|
const Player = () => {
|
|
40
|
-
const player =
|
|
95
|
+
const player = useMutableState<PlayerState>({
|
|
41
96
|
position: 0,
|
|
42
97
|
|
|
43
|
-
//
|
|
44
|
-
element:
|
|
98
|
+
// ignore() keeps a value out of reactivity and ops.
|
|
99
|
+
element: ignore(new Audio()),
|
|
45
100
|
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
get().element.currentTime = position;
|
|
101
|
+
// unsafeTrack() tracks all the values it can, even if there is weird behaviour
|
|
102
|
+
queue: unsafeTrack(new Playlist()),
|
|
49
103
|
|
|
50
|
-
|
|
104
|
+
seek(position: number) {
|
|
105
|
+
this.element.currentTime = position;
|
|
106
|
+
|
|
107
|
+
if (this.position === position) return;
|
|
108
|
+
|
|
109
|
+
this.position = position;
|
|
51
110
|
},
|
|
52
|
-
})
|
|
111
|
+
});
|
|
53
112
|
|
|
54
113
|
// ...
|
|
55
114
|
};
|
|
56
115
|
```
|
|
57
116
|
|
|
58
|
-
##
|
|
117
|
+
## Constraints
|
|
118
|
+
|
|
119
|
+
opshot tracks plain data.
|
|
59
120
|
|
|
60
|
-
|
|
121
|
+
It can't track:
|
|
122
|
+
|
|
123
|
+
- Internal slots (language level features like in Map)
|
|
124
|
+
- #private fields (hidden at the language level)
|
|
125
|
+
- Arrow methods on classes (they write to the original object, not the tracked one)
|
|
126
|
+
- Array subclasses (the prototype is lost when copied)
|
|
127
|
+
|
|
128
|
+
Use `ignore` or `unsafeTrack` when dealing with these.
|
|
129
|
+
|
|
130
|
+
## Tracked collections
|
|
131
|
+
|
|
132
|
+
`TrackedMap`, `TrackedSet`, and `TrackedDate` stand in for the built-ins opshot rejects. They have the exact same API as their counterparts.
|
|
61
133
|
|
|
62
134
|
```ts
|
|
63
|
-
|
|
64
|
-
counter.op.mutate((proxy) => {}, { transactionKey: "drag" });
|
|
135
|
+
import { TrackedMap, useMutableState } from "opshot";
|
|
65
136
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
137
|
+
const state = useMutableState({ index: new TrackedMap<string, number>() });
|
|
138
|
+
|
|
139
|
+
state.index.set("a", 1);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Subscribe
|
|
70
143
|
|
|
71
|
-
|
|
72
|
-
counter.op.isSameState(other);
|
|
144
|
+
`subscribe` hears every change to a state.
|
|
73
145
|
|
|
74
|
-
|
|
75
|
-
|
|
146
|
+
```tsx
|
|
147
|
+
import { useEffect } from "react";
|
|
148
|
+
import { subscribe, useMutableState } from "opshot";
|
|
76
149
|
|
|
77
|
-
|
|
78
|
-
counter
|
|
150
|
+
const Counter = () => {
|
|
151
|
+
const counter = useMutableState({ count: 0 });
|
|
79
152
|
|
|
80
|
-
|
|
81
|
-
|
|
153
|
+
useEffect(
|
|
154
|
+
() =>
|
|
155
|
+
subscribe(counter, (ops, meta) => {
|
|
156
|
+
// ops: [{
|
|
157
|
+
// do: { op: "replace", path: ["count"], value: 1 },
|
|
158
|
+
// undo: { op: "replace", path: ["count"], value: 0 },
|
|
159
|
+
// }]
|
|
160
|
+
// meta: whatever the writer passed, or undefined for bare writes
|
|
161
|
+
}),
|
|
162
|
+
[counter],
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
// ...
|
|
166
|
+
};
|
|
82
167
|
```
|
|
83
168
|
|
|
84
169
|
## Ops
|
|
85
170
|
|
|
171
|
+
An op is an invertible pair of `Operation` halves. Every half uses one of three verbs:
|
|
172
|
+
|
|
86
173
|
```ts
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
174
|
+
type OperationPath = ReadonlyArray<string | number>;
|
|
175
|
+
|
|
176
|
+
type Operation =
|
|
177
|
+
| { readonly op: "add"; readonly path: OperationPath; readonly value: unknown }
|
|
178
|
+
| { readonly op: "replace"; readonly path: OperationPath; readonly value: unknown }
|
|
179
|
+
| { readonly op: "remove"; readonly path: OperationPath };
|
|
180
|
+
|
|
181
|
+
interface Op {
|
|
182
|
+
readonly do: Operation;
|
|
183
|
+
readonly undo: Operation;
|
|
184
|
+
}
|
|
94
185
|
```
|
|
95
186
|
|
|
96
|
-
|
|
187
|
+
`applyOps` puts them back on a state, so a history is a list of ops and an undo is their `undo` halves in reverse.
|
|
97
188
|
|
|
98
|
-
|
|
189
|
+
```tsx
|
|
190
|
+
import { useEffect, useRef } from "react";
|
|
191
|
+
import { applyOps, subscribe, useMutableState, type Op } from "opshot";
|
|
192
|
+
|
|
193
|
+
const replay = {};
|
|
99
194
|
|
|
100
|
-
|
|
195
|
+
const Counter = () => {
|
|
196
|
+
const counter = useMutableState({ count: 0 });
|
|
197
|
+
const history = useRef<Array<ReadonlyArray<Op>>>([]);
|
|
198
|
+
|
|
199
|
+
useEffect(
|
|
200
|
+
() =>
|
|
201
|
+
subscribe(counter, (ops, meta) => {
|
|
202
|
+
// Skip our own replays, so undo doesn't record itself.
|
|
203
|
+
if (meta === replay) return;
|
|
204
|
+
|
|
205
|
+
history.current.push(ops);
|
|
206
|
+
}),
|
|
207
|
+
[counter],
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const undo = () => {
|
|
211
|
+
const ops = history.current.pop();
|
|
212
|
+
|
|
213
|
+
if (!ops) return;
|
|
214
|
+
|
|
215
|
+
applyOps(
|
|
216
|
+
counter,
|
|
217
|
+
[...ops].reverse().map((op) => op.undo),
|
|
218
|
+
replay,
|
|
219
|
+
);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
return (
|
|
223
|
+
<>
|
|
224
|
+
<button onClick={() => counter.count++}>+</button>
|
|
225
|
+
<button onClick={undo}>Undo</button>
|
|
226
|
+
</>
|
|
227
|
+
);
|
|
228
|
+
};
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Replay is exact for anything opshot can see: plain data. State behind a constraint is the exception.
|
|
232
|
+
|
|
233
|
+
If your state is JSON serializable, **then ops are too**.
|
|
101
234
|
|
|
102
235
|
## Groups
|
|
103
236
|
|
|
104
|
-
A group creates states and hears every op from the states it created: one stream for history, sync,
|
|
237
|
+
A group creates states and hears every op from the states it created: one stream for history, sync, persistence, etc.
|
|
105
238
|
|
|
106
239
|
```tsx
|
|
107
240
|
import { useEffect } from "react";
|
|
108
|
-
import {
|
|
241
|
+
import { subscribe, useGroup, useMutableState } from "opshot";
|
|
109
242
|
|
|
110
243
|
const Editor = () => {
|
|
111
|
-
|
|
112
|
-
const group = useCreateGroup();
|
|
244
|
+
const group = useGroup();
|
|
113
245
|
|
|
114
|
-
// Created through the group, so
|
|
115
|
-
const doc =
|
|
246
|
+
// Created through the group, so their ops reach the group's subscribers.
|
|
247
|
+
const doc = useMutableState({ items: new Array<string>() }, group);
|
|
248
|
+
const selection = useMutableState({ index: 0 }, group);
|
|
116
249
|
|
|
117
250
|
useEffect(
|
|
118
251
|
() =>
|
|
119
|
-
// Fires for doc and every other state the group created.
|
|
120
|
-
|
|
252
|
+
// Fires for doc, selection, and every other state the group created.
|
|
253
|
+
// state is whichever one changed.
|
|
254
|
+
subscribe(group, (state, ops, meta) => {
|
|
121
255
|
// ...
|
|
122
256
|
}),
|
|
123
257
|
[group],
|
|
@@ -127,47 +261,48 @@ const Editor = () => {
|
|
|
127
261
|
};
|
|
128
262
|
```
|
|
129
263
|
|
|
130
|
-
##
|
|
264
|
+
## Channels
|
|
131
265
|
|
|
132
|
-
A
|
|
133
|
-
|
|
134
|
-
This is a lever for bounding re-renders. Here `CounterButton` is plain, so its `count` read belongs to `App`, and every click re-renders `App` and everything under it:
|
|
266
|
+
A channel binds `transact`, `subscribe`, and `applyOps` to a typed meta convention, so a listener can tell its own writes from everyone else's.
|
|
135
267
|
|
|
136
268
|
```tsx
|
|
137
|
-
import
|
|
138
|
-
import {
|
|
269
|
+
import { useEffect } from "react";
|
|
270
|
+
import { createChannel, useMutableState } from "opshot";
|
|
139
271
|
|
|
140
|
-
interface
|
|
141
|
-
|
|
142
|
-
|
|
272
|
+
interface DocumentMeta {
|
|
273
|
+
replay?: boolean;
|
|
274
|
+
source?: string;
|
|
143
275
|
}
|
|
144
276
|
|
|
145
|
-
|
|
146
|
-
const App = () => {
|
|
147
|
-
const counter = useCreateState<Counter>({ title: "Hits", count: 0 });
|
|
277
|
+
const docChannel = createChannel<DocumentMeta>({ source: "editor" }); // set defaults
|
|
148
278
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
<h1>{counter.title}</h1>
|
|
152
|
-
<CounterButton counter={counter} />
|
|
153
|
-
</>
|
|
154
|
-
);
|
|
155
|
-
};
|
|
279
|
+
const TitleBar = () => {
|
|
280
|
+
const doc = useMutableState({ title: "Untitled" });
|
|
156
281
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
)
|
|
160
|
-
|
|
282
|
+
useEffect(
|
|
283
|
+
() =>
|
|
284
|
+
docChannel.subscribe(doc, (ops, context) => {
|
|
285
|
+
// A bare write, or a transact from another channel: meta is unknown.
|
|
286
|
+
if (!context.isTransaction) return;
|
|
161
287
|
|
|
162
|
-
|
|
288
|
+
// Own-channel transaction: meta is typed, with defaults merged.
|
|
289
|
+
if (context.meta.replay) return;
|
|
163
290
|
|
|
164
|
-
|
|
165
|
-
|
|
291
|
+
// ...
|
|
292
|
+
}),
|
|
293
|
+
[doc],
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
const rename = () => {
|
|
297
|
+
docChannel.transact(doc, () => {
|
|
298
|
+
doc.title = "Draft";
|
|
299
|
+
});
|
|
300
|
+
};
|
|
166
301
|
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
<button onClick={() => counter.op.mutate((proxy) => proxy.count++)}>{counter.count}</button>
|
|
170
|
-
));
|
|
302
|
+
// ...
|
|
303
|
+
};
|
|
171
304
|
```
|
|
172
305
|
|
|
173
|
-
|
|
306
|
+
## License
|
|
307
|
+
|
|
308
|
+
[MIT](LICENSE)
|
package/dist/index.d.ts
CHANGED
|
@@ -1,47 +1,185 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { Snapshot, ref } from 'valtio/vanilla';
|
|
1
|
+
import { ComponentType, FC } from 'react';
|
|
3
2
|
|
|
4
|
-
type
|
|
3
|
+
type OperationPath = ReadonlyArray<string | number>;
|
|
4
|
+
|
|
5
|
+
interface AddOperation {
|
|
5
6
|
readonly op: "add";
|
|
6
|
-
readonly path:
|
|
7
|
+
readonly path: OperationPath;
|
|
7
8
|
readonly value: unknown;
|
|
8
|
-
}
|
|
9
|
+
}
|
|
10
|
+
interface ReplaceOperation {
|
|
9
11
|
readonly op: "replace";
|
|
10
|
-
readonly path:
|
|
12
|
+
readonly path: OperationPath;
|
|
11
13
|
readonly value: unknown;
|
|
12
|
-
}
|
|
14
|
+
}
|
|
15
|
+
interface RemoveOperation {
|
|
13
16
|
readonly op: "remove";
|
|
14
|
-
readonly path:
|
|
15
|
-
}
|
|
17
|
+
readonly path: OperationPath;
|
|
18
|
+
}
|
|
19
|
+
type Operation = AddOperation | ReplaceOperation | RemoveOperation;
|
|
16
20
|
interface Op {
|
|
17
|
-
readonly do:
|
|
18
|
-
readonly undo:
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
type
|
|
23
|
-
type Mutate<T extends object> = (callback: (proxy: T) => void, options?: MutateOptions) => void;
|
|
24
|
-
type StateListener<T extends object> = (state: State<T>, ops: Array<Op>, options: MutateOptions) => void;
|
|
25
|
-
interface OpshotHandle<T extends object> {
|
|
26
|
-
readonly proxy: object;
|
|
27
|
-
readonly isMutating: boolean;
|
|
28
|
-
readonly mutate: Mutate<T>;
|
|
29
|
-
readonly subscribe: (listener: StateListener<T>) => () => void;
|
|
30
|
-
readonly isSameState: (other: unknown) => boolean;
|
|
31
|
-
readonly unwrap: () => Snapshot<T>;
|
|
32
|
-
}
|
|
33
|
-
type State<T extends object> = Snapshot<T> & {
|
|
34
|
-
readonly op: OpshotHandle<T>;
|
|
35
|
-
};
|
|
36
|
-
type DefineCallback<T extends object> = (mutate: Mutate<T>, get: () => State<T>) => T;
|
|
37
|
-
type Define<T extends object> = DefineCallback<T> | T;
|
|
38
|
-
declare function createState<T extends object>(define: Define<T>): State<T>;
|
|
39
|
-
declare function isState(value: unknown): value is State<object>;
|
|
21
|
+
readonly do: Operation;
|
|
22
|
+
readonly undo: Operation;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type StateListener = (ops: ReadonlyArray<Op>, meta: unknown) => void;
|
|
26
|
+
type GroupListener = (state: object, ops: ReadonlyArray<Op>, meta: unknown) => void;
|
|
40
27
|
|
|
41
28
|
interface Group {
|
|
42
|
-
|
|
43
|
-
subscribe(listener: StateListener<object>): () => void;
|
|
29
|
+
createMutableState<T extends object>(properties: T): T;
|
|
44
30
|
}
|
|
45
31
|
declare function createGroup(): Group;
|
|
46
32
|
|
|
47
|
-
|
|
33
|
+
type Context<M> = {
|
|
34
|
+
readonly isTransaction: true;
|
|
35
|
+
readonly meta: M;
|
|
36
|
+
} | {
|
|
37
|
+
readonly isTransaction: false;
|
|
38
|
+
readonly meta: unknown;
|
|
39
|
+
};
|
|
40
|
+
declare function subscribe(group: Group, listener: GroupListener): () => void;
|
|
41
|
+
declare function subscribe(state: object, listener: StateListener): () => void;
|
|
42
|
+
|
|
43
|
+
interface Channel<M extends object> {
|
|
44
|
+
transact(state: object, mutate: () => void, meta?: Partial<M>): void;
|
|
45
|
+
subscribe(group: Group, listener: (state: object, ops: ReadonlyArray<Op>, context: Context<M>) => void): () => void;
|
|
46
|
+
subscribe(state: object, listener: (ops: ReadonlyArray<Op>, context: Context<M>) => void): () => void;
|
|
47
|
+
applyOps(state: object, operations: ReadonlyArray<Operation>, meta?: Partial<M>): void;
|
|
48
|
+
}
|
|
49
|
+
declare function createChannel<M extends object>(defaults?: M): Channel<M>;
|
|
50
|
+
|
|
51
|
+
declare function createMutableState<T extends object>(properties: T, group?: Group): T;
|
|
52
|
+
|
|
53
|
+
declare function identify(value: object): object;
|
|
54
|
+
declare function isSameIdentity(first: object, second: object): boolean;
|
|
55
|
+
|
|
56
|
+
declare function isState(value: unknown): value is object;
|
|
57
|
+
|
|
58
|
+
declare function applyOps(state: object, operations: ReadonlyArray<Operation>, meta?: unknown): void;
|
|
59
|
+
|
|
60
|
+
declare function diffSnapshots(before: object, after: object): Array<Op>;
|
|
61
|
+
|
|
62
|
+
declare const ignoredMarker: unique symbol;
|
|
63
|
+
type Ignored<T extends object> = T & {
|
|
64
|
+
readonly [ignoredMarker]: true;
|
|
65
|
+
};
|
|
66
|
+
declare const ignore: <T extends object>(value: T) => Ignored<T>;
|
|
67
|
+
|
|
68
|
+
declare const unsafeTrackedBrand: unique symbol;
|
|
69
|
+
type UnsafeTracked<T extends object> = T & {
|
|
70
|
+
readonly [unsafeTrackedBrand]: true;
|
|
71
|
+
};
|
|
72
|
+
declare function unsafeTrack<T extends object>(value: T): UnsafeTracked<T>;
|
|
73
|
+
|
|
74
|
+
type DateConstructorArgs = [] | [value: number | string] | [
|
|
75
|
+
year: number,
|
|
76
|
+
monthIndex: number,
|
|
77
|
+
date?: number,
|
|
78
|
+
hours?: number,
|
|
79
|
+
minutes?: number,
|
|
80
|
+
seconds?: number,
|
|
81
|
+
milliseconds?: number
|
|
82
|
+
];
|
|
83
|
+
declare class TrackedDate {
|
|
84
|
+
private epochMs;
|
|
85
|
+
constructor(...args: DateConstructorArgs);
|
|
86
|
+
private readDate;
|
|
87
|
+
private write;
|
|
88
|
+
toString(): string;
|
|
89
|
+
toDateString(): string;
|
|
90
|
+
toTimeString(): string;
|
|
91
|
+
toLocaleString(locales?: Intl.LocalesArgument, options?: Intl.DateTimeFormatOptions): string;
|
|
92
|
+
toLocaleDateString(locales?: Intl.LocalesArgument, options?: Intl.DateTimeFormatOptions): string;
|
|
93
|
+
toLocaleTimeString(locales?: Intl.LocalesArgument, options?: Intl.DateTimeFormatOptions): string;
|
|
94
|
+
valueOf(): number;
|
|
95
|
+
getTime(): number;
|
|
96
|
+
getFullYear(): number;
|
|
97
|
+
getYear(): number;
|
|
98
|
+
getUTCFullYear(): number;
|
|
99
|
+
getMonth(): number;
|
|
100
|
+
getUTCMonth(): number;
|
|
101
|
+
getDate(): number;
|
|
102
|
+
getUTCDate(): number;
|
|
103
|
+
getDay(): number;
|
|
104
|
+
getUTCDay(): number;
|
|
105
|
+
getHours(): number;
|
|
106
|
+
getUTCHours(): number;
|
|
107
|
+
getMinutes(): number;
|
|
108
|
+
getUTCMinutes(): number;
|
|
109
|
+
getSeconds(): number;
|
|
110
|
+
getUTCSeconds(): number;
|
|
111
|
+
getMilliseconds(): number;
|
|
112
|
+
getUTCMilliseconds(): number;
|
|
113
|
+
getTimezoneOffset(): number;
|
|
114
|
+
setYear(year: number): number;
|
|
115
|
+
setTime(...args: [time: number]): number;
|
|
116
|
+
setMilliseconds(...args: [milliseconds: number]): number;
|
|
117
|
+
setUTCMilliseconds(...args: [milliseconds: number]): number;
|
|
118
|
+
setSeconds(...args: [seconds: number, milliseconds?: number]): number;
|
|
119
|
+
setUTCSeconds(...args: [seconds: number, milliseconds?: number]): number;
|
|
120
|
+
setMinutes(...args: [minutes: number, seconds?: number, milliseconds?: number]): number;
|
|
121
|
+
setUTCMinutes(...args: [minutes: number, seconds?: number, milliseconds?: number]): number;
|
|
122
|
+
setHours(...args: [hours: number, minutes?: number, seconds?: number, milliseconds?: number]): number;
|
|
123
|
+
setUTCHours(...args: [hours: number, minutes?: number, seconds?: number, milliseconds?: number]): number;
|
|
124
|
+
setDate(...args: [dateValue: number]): number;
|
|
125
|
+
setUTCDate(...args: [dateValue: number]): number;
|
|
126
|
+
setMonth(...args: [month: number, dateValue?: number]): number;
|
|
127
|
+
setUTCMonth(...args: [month: number, dateValue?: number]): number;
|
|
128
|
+
setFullYear(...args: [year: number, month?: number, dateValue?: number]): number;
|
|
129
|
+
setUTCFullYear(...args: [year: number, month?: number, dateValue?: number]): number;
|
|
130
|
+
toUTCString(): string;
|
|
131
|
+
toGMTString(): string;
|
|
132
|
+
toISOString(): string;
|
|
133
|
+
[Symbol.toPrimitive](hint: "default" | "string" | "number"): string | number;
|
|
134
|
+
readonly [Symbol.toStringTag]: "TrackedDate";
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
declare class TrackedMap<K, V> {
|
|
138
|
+
private slots;
|
|
139
|
+
private index;
|
|
140
|
+
private count;
|
|
141
|
+
constructor(entries?: Iterable<readonly [K, V]>);
|
|
142
|
+
get size(): number;
|
|
143
|
+
has(key: K): boolean;
|
|
144
|
+
get(key: K): V | undefined;
|
|
145
|
+
set(key: K, value: V): this;
|
|
146
|
+
delete(key: K): boolean;
|
|
147
|
+
clear(): void;
|
|
148
|
+
entries(): IterableIterator<[K, V]>;
|
|
149
|
+
keys(): IterableIterator<K>;
|
|
150
|
+
values(): IterableIterator<V>;
|
|
151
|
+
forEach(callback: (value: V, key: K, map: TrackedMap<K, V>) => void): void;
|
|
152
|
+
[Symbol.iterator](): IterableIterator<[K, V]>;
|
|
153
|
+
readonly [Symbol.toStringTag]: "TrackedMap";
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
declare class TrackedSet<T> {
|
|
157
|
+
private slots;
|
|
158
|
+
private index;
|
|
159
|
+
private count;
|
|
160
|
+
constructor(values?: Iterable<T>);
|
|
161
|
+
get size(): number;
|
|
162
|
+
has(value: T): boolean;
|
|
163
|
+
add(value: T): this;
|
|
164
|
+
delete(value: T): boolean;
|
|
165
|
+
clear(): void;
|
|
166
|
+
entries(): IterableIterator<[T, T]>;
|
|
167
|
+
keys(): IterableIterator<T>;
|
|
168
|
+
values(): IterableIterator<T>;
|
|
169
|
+
forEach(callback: (value: T, key: T, set: TrackedSet<T>) => void): void;
|
|
170
|
+
[Symbol.iterator](): IterableIterator<T>;
|
|
171
|
+
readonly [Symbol.toStringTag]: "TrackedSet";
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
declare function transact(state: object, mutate: () => void, meta?: unknown): void;
|
|
175
|
+
|
|
176
|
+
interface ScopeOptions {
|
|
177
|
+
readonly maxDepth?: number;
|
|
178
|
+
}
|
|
179
|
+
declare function scope<P extends object>(Component: ComponentType<P>, options?: ScopeOptions): FC<P>;
|
|
180
|
+
|
|
181
|
+
declare function useGroup(): Group;
|
|
182
|
+
|
|
183
|
+
declare function useMutableState<T extends object>(properties: T, group?: Group): T;
|
|
184
|
+
|
|
185
|
+
export { type AddOperation, type Channel, type Context, type Group, type GroupListener, type Ignored, type Op, type Operation, type OperationPath, type RemoveOperation, type ReplaceOperation, type ScopeOptions, type StateListener, TrackedDate, TrackedMap, TrackedSet, type UnsafeTracked, applyOps, createChannel, createGroup, createMutableState, diffSnapshots, identify, ignore, isSameIdentity, isState, scope, subscribe, transact, unsafeTrack, useGroup, useMutableState };
|