opshot 0.3.4 → 0.4.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.
Files changed (4) hide show
  1. package/README.md +33 -39
  2. package/dist/index.d.ts +435 -42
  3. package/dist/index.js +2993 -1241
  4. package/package.json +20 -10
package/README.md CHANGED
@@ -28,12 +28,6 @@ const user = useMutableState({ name: "Ada", age: 36 });
28
28
  user.age = 37;
29
29
  ```
30
30
 
31
- The state is created once, on the first render. Pass a function to build the properties once as well, exactly as `useState` does:
32
-
33
- ```tsx
34
- const navigation = useMutableState(createNavigation);
35
- ```
36
-
37
31
  ## Bounded re-renders
38
32
 
39
33
  React re-renders a component and its children when its state changes.
@@ -88,23 +82,23 @@ This is how you optimize re-rendering across your component tree: place `scope`
88
82
  ## Creating State
89
83
 
90
84
  ```tsx
91
- import { ignore, unsafeTrack, useMutableState, type Ignored, type UnsafeTracked } from "opshot";
85
+ import { ignore, unsafeTrack, useMutableState } from "opshot";
92
86
 
93
87
  interface PlayerState {
94
88
  position: number;
95
- element: Ignored<HTMLAudioElement>;
96
- queue: UnsafeTracked<Playlist>;
89
+ element: HTMLAudioElement;
90
+ queue: Playlist;
97
91
  seek: (position: number) => void;
98
92
  }
99
93
 
100
94
  const Player = () => {
101
- const player = useMutableState<PlayerState>({
95
+ const player: PlayerState = useMutableState({
102
96
  position: 0,
103
97
 
104
- // ignore() keeps a value out of reactivity and ops.
98
+ // ignore() on a value in the factory argument makes the edge at that path untracked.
105
99
  element: ignore(new Audio()),
106
100
 
107
- // unsafeTrack() tracks all the values it can, even if there is weird behaviour
101
+ // unsafeTrack() on a value in the factory argument disables strict at and under that path.
108
102
  queue: unsafeTrack(new Playlist()),
109
103
 
110
104
  seek(position: number) {
@@ -126,13 +120,14 @@ opshot tracks plain data.
126
120
 
127
121
  It can't track:
128
122
 
129
- - Internal slots (language level features like in Map)
130
- - #private fields (hidden at the language level)
131
- - Array subclasses (the prototype is lost when copied)
123
+ - Hidden stores (language-level features like in Map)
124
+ - #private fields
125
+ - Own function properties on class instances
126
+ - Non-writable properties that hold an object
132
127
 
133
- And `this` for arrow methods on classes refers to the original and **not** the tracked state.
128
+ `strict: true` throws at a dangerous edge, at the cause.
134
129
 
135
- Use `ignore` or `unsafeTrack` when dealing with these.
130
+ Use `ignore` on a value in the factory argument to make the edge at that path untracked. Use `unsafeTrack` on a value in the factory argument to disable strict at and under that path.
136
131
 
137
132
  ## Tracked collections
138
133
 
@@ -161,8 +156,8 @@ const Counter = () => {
161
156
  () =>
162
157
  subscribe(counter, (ops, meta) => {
163
158
  // ops: [{
164
- // do: { op: "replace", path: ["count"], value: 1 },
165
- // undo: { op: "replace", path: ["count"], value: 0 },
159
+ // do: { verb: "assign", path: ["count"], value: 1 },
160
+ // undo: { verb: "assign", path: ["count"], value: 0 },
166
161
  // }]
167
162
  // meta: whatever the writer passed, or undefined for bare writes
168
163
  }),
@@ -175,33 +170,36 @@ const Counter = () => {
175
170
 
176
171
  ## Ops
177
172
 
178
- An op is an invertible pair of `Operation` halves. Every half uses one of three verbs:
173
+ An op is an invertible pair of halves. Every half uses one of three verbs:
179
174
 
180
175
  ```ts
181
176
  type OperationPath = ReadonlyArray<string | number>;
182
177
 
183
- type Operation =
184
- | { readonly op: "add"; readonly path: OperationPath; readonly value: unknown }
185
- | { readonly op: "replace"; readonly path: OperationPath; readonly value: unknown }
186
- | { readonly op: "remove"; readonly path: OperationPath };
187
-
188
- interface Op {
189
- readonly do: Operation;
190
- readonly undo: Operation;
178
+ interface Operation {
179
+ readonly do:
180
+ | {
181
+ readonly verb: "assign";
182
+ readonly path: OperationPath;
183
+ readonly value: unknown;
184
+ readonly ids?: ReadonlyArray<number>;
185
+ }
186
+ | { readonly verb: "delete"; readonly path: OperationPath }
187
+ | { readonly verb: "link"; readonly path: OperationPath; readonly ref: number };
188
+ readonly undo: Operation["do"];
191
189
  }
192
190
  ```
193
191
 
194
- `applyOps` puts them back on a state, so a history is a list of ops and an undo is their `undo` halves in reverse.
192
+ Ids vend in admission-walk order over the emitted artifact; a departure's undo assign may carry `ids` to rebind that walk, the one naming fact construction cannot re-derive. `applyOperations` puts them back on a state, so a history is a list of ops and an undo is `applyOperations` with `"undo"`.
195
193
 
196
194
  ```tsx
197
195
  import { useEffect, useRef } from "react";
198
- import { applyOps, subscribe, useMutableState, type Op } from "opshot";
196
+ import { applyOperations, subscribe, useMutableState, type Operation } from "opshot";
199
197
 
200
198
  const replay = {};
201
199
 
202
200
  const Counter = () => {
203
201
  const counter = useMutableState({ count: 0 });
204
- const history = useRef<Array<ReadonlyArray<Op>>>([]);
202
+ const history = useRef<Array<ReadonlyArray<Operation>>>([]);
205
203
 
206
204
  useEffect(
207
205
  () =>
@@ -219,11 +217,7 @@ const Counter = () => {
219
217
 
220
218
  if (!ops) return;
221
219
 
222
- applyOps(
223
- counter,
224
- [...ops].reverse().map((op) => op.undo),
225
- replay,
226
- );
220
+ applyOperations(counter, ops, "undo", replay);
227
221
  };
228
222
 
229
223
  return (
@@ -251,8 +245,8 @@ const Editor = () => {
251
245
  const group = useGroup();
252
246
 
253
247
  // Created through the group, so their ops reach the group's subscribers.
254
- const doc = useMutableState({ items: new Array<string>() }, group);
255
- const selection = useMutableState({ index: 0 }, group);
248
+ const doc = useMutableState({ items: new Array<string>() }, { group });
249
+ const selection = useMutableState({ index: 0 }, { group });
256
250
 
257
251
  useEffect(
258
252
  () =>
@@ -270,7 +264,7 @@ const Editor = () => {
270
264
 
271
265
  ## Channels
272
266
 
273
- A channel binds `transact`, `subscribe`, and `applyOps` to a typed meta convention, so a listener can tell its own writes from everyone else's.
267
+ A channel binds `transact`, `subscribe`, and `applyOperations` to a typed meta convention, so a listener can tell its own writes from everyone else's.
274
268
 
275
269
  ```tsx
276
270
  import { useEffect } from "react";