@uniflowed/state 0.0.0-alpha.18
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/index.js +691 -0
- package/internal/atom.js +213 -0
- package/internal/composed.js +882 -0
- package/internal/provider.js +64 -0
- package/internal/store.js +359 -0
- package/package.json +25 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
//
|
|
3
|
+
// Which store a React subtree uses.
|
|
4
|
+
//
|
|
5
|
+
// One context and one component. It is separate from the hooks because it
|
|
6
|
+
// answers a different question: the hooks ask "what is this atom's value
|
|
7
|
+
// here", and this decides what "here" means.
|
|
8
|
+
//
|
|
9
|
+
// # Why there is a default store at all
|
|
10
|
+
//
|
|
11
|
+
// Most applications have exactly one, and making every one of them wrap its
|
|
12
|
+
// tree in a provider — and every test, and every story — to get it is
|
|
13
|
+
// ceremony that buys nothing. So an unwrapped tree reads the default store,
|
|
14
|
+
// which is created on first use.
|
|
15
|
+
//
|
|
16
|
+
// A provider is what you reach for when one is not enough: a server rendering
|
|
17
|
+
// two requests in one process, a preview pane holding a draft of the state the
|
|
18
|
+
// page behind it shows, a test that wants a clean slate without reloading the
|
|
19
|
+
// module. Those are real, which is why the scoping exists, and they are not
|
|
20
|
+
// the common case, which is why it is optional.
|
|
21
|
+
//
|
|
22
|
+
// # Why the fallback store is made with useState
|
|
23
|
+
//
|
|
24
|
+
// `<Provider>` with no `store` prop owns one. Creating it in the render body
|
|
25
|
+
// would make a new store every render, and every re-render would throw the
|
|
26
|
+
// tree's state away; `useState` with a lazy initialiser creates it once and
|
|
27
|
+
// keeps it for the life of the component, without an effect that would leave
|
|
28
|
+
// the first render with no store to read.
|
|
29
|
+
|
|
30
|
+
import * as React from "@uniflowed/react";
|
|
31
|
+
import { createContext, useContext, useState } from "@uniflowed/react";
|
|
32
|
+
|
|
33
|
+
import type { StoreInstance } from "./store.js";
|
|
34
|
+
import { createStore, defaultStore } from "./store.js";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* `null` means "nobody scoped one", which is different from a store: it is
|
|
38
|
+
* what makes an unwrapped tree fall through to the default rather than throw.
|
|
39
|
+
*/
|
|
40
|
+
const StoreContext: React.Context<StoreInstance | null> = createContext(null);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Give a subtree its own store.
|
|
44
|
+
*
|
|
45
|
+
* Passing `null` asks for a fresh one owned by this component, which is what
|
|
46
|
+
* the public `Provider` does when it is given no store.
|
|
47
|
+
*/
|
|
48
|
+
export component StoreScope(store: StoreInstance | null, children: React.Node) {
|
|
49
|
+
const [owned] = useState(createStore);
|
|
50
|
+
return <StoreContext.Provider value={store ?? owned}>{children}</StoreContext.Provider>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The store a hook should use: the one it was handed, then the one the tree
|
|
55
|
+
* was scoped to, then the default.
|
|
56
|
+
*
|
|
57
|
+
* `useContext` is called unconditionally even when an override was passed,
|
|
58
|
+
* because the alternative is a conditional hook, and because a component that
|
|
59
|
+
* sometimes takes a store prop must not change its hook order when it does.
|
|
60
|
+
*/
|
|
61
|
+
export hook useStoreInstance(override?: StoreInstance): StoreInstance {
|
|
62
|
+
const scoped = useContext(StoreContext);
|
|
63
|
+
return override ?? scoped ?? defaultStore();
|
|
64
|
+
}
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
//
|
|
3
|
+
// A store: where an atom's value actually lives.
|
|
4
|
+
//
|
|
5
|
+
// One `WeakMap` from an atom definition to the cell holding its value in this
|
|
6
|
+
// store, and the four operations everything else in the package is built from
|
|
7
|
+
// — get, set, subscribe, and the React binding. There is no graph here. A
|
|
8
|
+
// derived atom's read is handed a `get` that resolves through this store, and
|
|
9
|
+
// the cell it is instantiated into does the tracking, the memoisation, the
|
|
10
|
+
// glitch-free propagation and the batching. That is the whole reason
|
|
11
|
+
// `@uniflowed/cell` is a separate package: two implementations of dependency
|
|
12
|
+
// tracking in one product is one too many.
|
|
13
|
+
//
|
|
14
|
+
// # Why a WeakMap
|
|
15
|
+
//
|
|
16
|
+
// An `atomFamily` can be keyed by anything — a row id, a date, a filter — and
|
|
17
|
+
// a long-lived store must not accumulate a cell per key ever asked for. Keying
|
|
18
|
+
// weakly means a family member that nothing references any more takes its
|
|
19
|
+
// value with it. It also means the store never has to be told an atom exists:
|
|
20
|
+
// instantiation happens on first contact, so importing a module full of atoms
|
|
21
|
+
// costs nothing until one is read.
|
|
22
|
+
//
|
|
23
|
+
// # Why instantiation during a React render is safe
|
|
24
|
+
//
|
|
25
|
+
// `useAtomValue` reads through `getSnapshot`, which React calls during render,
|
|
26
|
+
// and that read may create the cell. It is a mutation, and it is the one kind
|
|
27
|
+
// React permits: it is idempotent, keyed by an identity the caller already
|
|
28
|
+
// holds, and unobservable — two renders that race produce the same cell, and
|
|
29
|
+
// the second finds the first's. Nothing outside the store can tell whether the
|
|
30
|
+
// cell existed before the render.
|
|
31
|
+
//
|
|
32
|
+
// What is *not* done during render is mounting: `onMount` runs from
|
|
33
|
+
// `subscribe`, which React calls after commit. A render that is thrown away
|
|
34
|
+
// therefore starts nothing that would need stopping.
|
|
35
|
+
|
|
36
|
+
import type { Cell, CellOptions, Unsubscribe } from "@uniflowed/cell";
|
|
37
|
+
import {
|
|
38
|
+
batch,
|
|
39
|
+
derived,
|
|
40
|
+
peek,
|
|
41
|
+
read,
|
|
42
|
+
refresh,
|
|
43
|
+
resource,
|
|
44
|
+
state,
|
|
45
|
+
status,
|
|
46
|
+
subscribe,
|
|
47
|
+
untracked,
|
|
48
|
+
update,
|
|
49
|
+
write,
|
|
50
|
+
} from "@uniflowed/cell";
|
|
51
|
+
|
|
52
|
+
import type { AtomGetter, AtomRecord, AtomSetter, Loadable } from "./atom.js";
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The three callbacks React needs for one atom, cached so their identity never
|
|
56
|
+
* changes.
|
|
57
|
+
*
|
|
58
|
+
* `useSyncExternalStore` re-subscribes whenever the identity of `subscribe`
|
|
59
|
+
* changes, and a memoised child re-renders whenever the identity of a setter
|
|
60
|
+
* changes. A `useCallback` would keep them stable within one component;
|
|
61
|
+
* caching them on the store keeps them stable across every component reading
|
|
62
|
+
* that atom, so mounting the thousandth reader allocates nothing.
|
|
63
|
+
*/
|
|
64
|
+
export type Binding<T, A> = {
|
|
65
|
+
readonly subscribe: (listener: () => void) => Unsubscribe,
|
|
66
|
+
readonly snapshot: () => T,
|
|
67
|
+
readonly setter: (arg: A) => void,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export type StoreInstance = {
|
|
71
|
+
readonly get: AtomGetter,
|
|
72
|
+
readonly set: AtomSetter,
|
|
73
|
+
readonly sub: <V>(target: AtomRecord<V, empty>, listener: () => void) => Unsubscribe,
|
|
74
|
+
readonly bind: <V, A>(target: AtomRecord<V, A>) => Binding<V, A>,
|
|
75
|
+
readonly reload: <V>(target: AtomRecord<V, empty>) => void,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* An atom, as the thing a store's maps are keyed by.
|
|
80
|
+
*
|
|
81
|
+
* A key is an identity and nothing else: the maps are looked up by the atom
|
|
82
|
+
* the caller already holds, and no property of one is ever read through this
|
|
83
|
+
* type. So the honest key type is not "an atom record of some value type" —
|
|
84
|
+
* which is `AtomRecord<any, any>`, and is a claim that every field of one may
|
|
85
|
+
* be read and will answer `any` — it is "something with an atom's name on it".
|
|
86
|
+
* An interface is how Flow says that: `AtomRecord<V, A>` satisfies it for
|
|
87
|
+
* every `V` and `A`, and nothing that comes back out of a map has this type.
|
|
88
|
+
*/
|
|
89
|
+
type AtomIdentity = interface { readonly label: string };
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* A cell or a binding whose value type is not known here.
|
|
93
|
+
*
|
|
94
|
+
* This is the existential the key type escaped, and it does not escape: a
|
|
95
|
+
* store's maps hold every atom in the application at once, and the lookup that
|
|
96
|
+
* comes back out has to be a `Cell<V>` for the `V` of the atom it was found
|
|
97
|
+
* under. That is a *correspondence* between a key's type and its value's, and
|
|
98
|
+
* Flow has no way to write one — not an existential (`some T` loses which
|
|
99
|
+
* `T`), not variance (`Cell` is invariant, deliberately: a `Cell<Dog>` is not
|
|
100
|
+
* a `Cell<Animal>` because anything holding the second may write a `Cat`), and
|
|
101
|
+
* not `mixed` with a cast at the read, which is this same unsoundness spelled
|
|
102
|
+
* three times instead of twice.
|
|
103
|
+
*
|
|
104
|
+
* `Binding` is the near miss worth recording. Its `T` is only ever returned
|
|
105
|
+
* and its `A` only ever taken, so with the sigils that say so —
|
|
106
|
+
* `Binding<out T, in A>` — every binding really would be a
|
|
107
|
+
* `Binding<mixed, empty>` and could be *stored* as one. It is reading it back
|
|
108
|
+
* as the caller's `Binding<V, A>` that has nowhere to go, so the sigils would
|
|
109
|
+
* buy nothing here and are not added for the look of it.
|
|
110
|
+
*
|
|
111
|
+
* Every function that reaches a value is generic in its type, so nothing
|
|
112
|
+
* outside this file sees either of these.
|
|
113
|
+
*/
|
|
114
|
+
// Suppressed rather than left to fail `check:lib`: the reason above is the
|
|
115
|
+
// whole argument, and it does not end in a change anyone can make to this file.
|
|
116
|
+
// uf-lint-disable flow/unclear-type
|
|
117
|
+
type AnyCell = Cell<any>;
|
|
118
|
+
type AnyBinding = Binding<any, any>;
|
|
119
|
+
// uf-lint-enable flow/unclear-type
|
|
120
|
+
|
|
121
|
+
export function createStore(): StoreInstance {
|
|
122
|
+
const cells: WeakMap<AtomIdentity, AnyCell> = new WeakMap();
|
|
123
|
+
const bindings: WeakMap<AtomIdentity, AnyBinding> = new WeakMap();
|
|
124
|
+
/**
|
|
125
|
+
* How to make an asynchronous atom load again, per atom instantiated here.
|
|
126
|
+
*
|
|
127
|
+
* An async atom is two cells — a resource and the projection over it — and
|
|
128
|
+
* `cells` holds the projection, because that is the one an application
|
|
129
|
+
* reads. Refreshing the projection would do nothing: it is a `derived` cell,
|
|
130
|
+
* and recomputing it reads a resource that is perfectly up to date. So the
|
|
131
|
+
* resource is recorded here as it is built, next to the map that hides it.
|
|
132
|
+
*/
|
|
133
|
+
const reloads: WeakMap<AtomIdentity, () => void> = new WeakMap();
|
|
134
|
+
|
|
135
|
+
function cellFor<V, A>(target: AtomRecord<V, A>): Cell<V> {
|
|
136
|
+
const existing = cells.get(target);
|
|
137
|
+
if (existing != null) {
|
|
138
|
+
return existing;
|
|
139
|
+
}
|
|
140
|
+
const created = instantiate<V, A>(target);
|
|
141
|
+
cells.set(target, created);
|
|
142
|
+
return created;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function instantiate<V, A>(target: AtomRecord<V, A>): Cell<V> {
|
|
146
|
+
const options = cellOptions<V, A>(target);
|
|
147
|
+
return match (target.kind) {
|
|
148
|
+
"primitive" => state(target.initial, options),
|
|
149
|
+
"async" => loadable<V, A>(target, options),
|
|
150
|
+
_ => derivedAtom<V, A>(target, options),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The cell for an atom of kind `"derived"`.
|
|
156
|
+
*
|
|
157
|
+
* The atom kind and the cell constructor carry the same name one layer
|
|
158
|
+
* apart, so the local one takes the suffix: bare `derived` here is
|
|
159
|
+
* `@uniflowed/cell`'s.
|
|
160
|
+
*/
|
|
161
|
+
function derivedAtom<V, A>(target: AtomRecord<V, A>, options: CellOptions<V>): Cell<V> {
|
|
162
|
+
const reader = target.read;
|
|
163
|
+
if (reader === null) {
|
|
164
|
+
// A write-only atom. It still gets a cell, so that `useSetAtom` on one
|
|
165
|
+
// works the same way as on any other atom, but its value is a constant
|
|
166
|
+
// and nothing ever recomputes it.
|
|
167
|
+
return state(target.initial, options);
|
|
168
|
+
}
|
|
169
|
+
return derived(() => reader(get), options);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* An asynchronous atom: a resource that reloads when its inputs change,
|
|
174
|
+
* projected into the [`Loadable`] the atom's readers see.
|
|
175
|
+
*
|
|
176
|
+
* The projection is a separate cell rather than logic inside the resource
|
|
177
|
+
* because the two have different equality: the resource's value changes once
|
|
178
|
+
* per settlement, while the loadable also has to change when the *status*
|
|
179
|
+
* does — a reload that returns to `loading` is a render, even though the
|
|
180
|
+
* data it holds has not changed yet.
|
|
181
|
+
*/
|
|
182
|
+
function loadable<V, A>(target: AtomRecord<V, A>, options: CellOptions<V>): Cell<V> {
|
|
183
|
+
const loader = target.load;
|
|
184
|
+
if (loader === null) {
|
|
185
|
+
return state(target.initial, options);
|
|
186
|
+
}
|
|
187
|
+
// The load's own context is forwarded rather than rebuilt: the cell owns
|
|
188
|
+
// the signal, because the cell is what abandons the load.
|
|
189
|
+
const pending = resource((context) => loader(get, context));
|
|
190
|
+
reloads.set(target, () => {
|
|
191
|
+
refresh(pending);
|
|
192
|
+
});
|
|
193
|
+
return derived(() => {
|
|
194
|
+
try {
|
|
195
|
+
// Read first, and unconditionally: this is what makes the projection
|
|
196
|
+
// depend on the resource. A load in flight reads as `null`, and a
|
|
197
|
+
// reload that has not settled reads as the status rather than as the
|
|
198
|
+
// value it still holds — a refetch is a loading state, not stale data
|
|
199
|
+
// presented as current.
|
|
200
|
+
const settled = read(pending);
|
|
201
|
+
return status(pending) === "success" && settled != null ? settled : target.initial;
|
|
202
|
+
} catch (error) {
|
|
203
|
+
// Only a `load` that threw synchronously reaches here: a rejection is
|
|
204
|
+
// folded into the value where the atom was defined.
|
|
205
|
+
const failure: Loadable<mixed> = { state: "hasError", error };
|
|
206
|
+
return failure as $FlowFixMe;
|
|
207
|
+
}
|
|
208
|
+
}, options);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function cellOptions<V, A>(target: AtomRecord<V, A>): CellOptions<V> {
|
|
212
|
+
const equals = target.equals;
|
|
213
|
+
const onMount = target.onMount;
|
|
214
|
+
if (onMount === null) {
|
|
215
|
+
return equals === null ? {} : { equals };
|
|
216
|
+
}
|
|
217
|
+
// The mount is handed the atom bound to *this* store, so a subscription it
|
|
218
|
+
// starts feeds this store's value and no other's.
|
|
219
|
+
const mounted = (self: Cell<V>) =>
|
|
220
|
+
onMount({
|
|
221
|
+
get: () => peek(self),
|
|
222
|
+
set: (value: V) => {
|
|
223
|
+
set(target, value as $FlowFixMe);
|
|
224
|
+
},
|
|
225
|
+
subscribe: (listener) => subscribe(self, listener),
|
|
226
|
+
});
|
|
227
|
+
return equals === null ? { onMount: mounted } : { equals, onMount: mounted };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const get: AtomGetter = (target) => read(cellFor(target));
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Apply one write.
|
|
234
|
+
*
|
|
235
|
+
* A writable atom's own `write` runs untracked: a writer is not a derive,
|
|
236
|
+
* and a `get` inside one that recorded a dependency would make the atom
|
|
237
|
+
* recompute because of something a *handler* looked at.
|
|
238
|
+
*/
|
|
239
|
+
function apply<V, A>(target: AtomRecord<V, A>, argument: A): void {
|
|
240
|
+
const writer = target.write;
|
|
241
|
+
if (writer !== null) {
|
|
242
|
+
untracked(() => {
|
|
243
|
+
writer(get, set, argument);
|
|
244
|
+
});
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (target.kind !== "primitive") {
|
|
248
|
+
throw Error(`@uniflowed/state ${target.label} is read-only`);
|
|
249
|
+
}
|
|
250
|
+
const node = cellFor<V, A>(target);
|
|
251
|
+
// A function argument is a reducer, exactly as `useState` reads one — with
|
|
252
|
+
// the same consequence, that an atom holding a function must be written
|
|
253
|
+
// through a reducer returning it.
|
|
254
|
+
if (typeof argument === "function") {
|
|
255
|
+
update(node, argument as $FlowFixMe);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
write(node, argument as $FlowFixMe);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Every write is a batch, so an atom whose writer sets three others wakes
|
|
263
|
+
* each subscriber once rather than three times, and no subscriber ever runs
|
|
264
|
+
* against a store that is halfway through one logical change.
|
|
265
|
+
*/
|
|
266
|
+
const set: AtomSetter = (target, argument) => {
|
|
267
|
+
batch(() => {
|
|
268
|
+
apply(target, argument);
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const sub = <V>(target: AtomRecord<V, empty>, listener: () => void): Unsubscribe =>
|
|
273
|
+
subscribe(cellFor(target), listener);
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Start an asynchronous atom's load again with the dependencies it already
|
|
277
|
+
* has.
|
|
278
|
+
*
|
|
279
|
+
* Instantiating first is what makes this work on an atom this store has
|
|
280
|
+
* never been asked for: the resource is built as the atom is instantiated,
|
|
281
|
+
* so the entry exists by the time it is looked up.
|
|
282
|
+
*
|
|
283
|
+
* The throw is a runtime guard behind a static one. `refresh` takes an
|
|
284
|
+
* `AsyncAtom`, which only `asyncAtom` produces, so a `selector` that happens
|
|
285
|
+
* to return a `Loadable` is rejected by Flow before it gets here — but the
|
|
286
|
+
* type is erased at run time and an untyped caller deserves the name of the
|
|
287
|
+
* atom rather than a silent no-op.
|
|
288
|
+
*/
|
|
289
|
+
const reload = <V>(target: AtomRecord<V, empty>): void => {
|
|
290
|
+
cellFor(target);
|
|
291
|
+
const again = reloads.get(target);
|
|
292
|
+
if (again === undefined) {
|
|
293
|
+
throw Error(`@uniflowed/state ${target.label} is not an asynchronous atom`);
|
|
294
|
+
}
|
|
295
|
+
again();
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
function bind<V, A>(target: AtomRecord<V, A>): Binding<V, A> {
|
|
299
|
+
const existing = bindings.get(target);
|
|
300
|
+
if (existing != null) {
|
|
301
|
+
return existing;
|
|
302
|
+
}
|
|
303
|
+
const created: Binding<V, A> = {
|
|
304
|
+
subscribe: (listener) => subscribe(cellFor(target), listener),
|
|
305
|
+
// `peek`, not `read`: a snapshot taken during a React render must not
|
|
306
|
+
// become a dependency of whatever happens to be evaluating.
|
|
307
|
+
snapshot: () => peek(cellFor(target)),
|
|
308
|
+
setter: (argument) => {
|
|
309
|
+
set(target, argument);
|
|
310
|
+
},
|
|
311
|
+
};
|
|
312
|
+
bindings.set(target, created);
|
|
313
|
+
return created;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return { get, set, sub, bind, reload };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* The same three callbacks for a cell that is not an atom.
|
|
321
|
+
*
|
|
322
|
+
* `@uniflowed/cell` is the layer below this one and applications do reach it
|
|
323
|
+
* — a route loader hands out cells — so the React binding accepts one
|
|
324
|
+
* directly. There is no store involved: a cell already holds its own value,
|
|
325
|
+
* which is precisely the difference between a cell and an atom.
|
|
326
|
+
*/
|
|
327
|
+
const cellBindings: WeakMap<AnyCell, AnyBinding> = new WeakMap();
|
|
328
|
+
|
|
329
|
+
export function bindCell<T>(source: Cell<T>): Binding<T, T> {
|
|
330
|
+
const existing = cellBindings.get(source);
|
|
331
|
+
if (existing != null) {
|
|
332
|
+
return existing;
|
|
333
|
+
}
|
|
334
|
+
const created: Binding<T, T> = {
|
|
335
|
+
subscribe: (listener) => subscribe(source, listener),
|
|
336
|
+
snapshot: () => peek(source),
|
|
337
|
+
setter: (value) => {
|
|
338
|
+
write(source, value);
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
cellBindings.set(source, created);
|
|
342
|
+
return created;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* The store used by anything that does not name one.
|
|
347
|
+
*
|
|
348
|
+
* Created on first use rather than at import, so a module that imports this
|
|
349
|
+
* package and only declares atoms allocates nothing — and so the cost lands in
|
|
350
|
+
* a stack the profiler can attribute.
|
|
351
|
+
*/
|
|
352
|
+
let fallback: null | StoreInstance = null;
|
|
353
|
+
|
|
354
|
+
export function defaultStore(): StoreInstance {
|
|
355
|
+
if (fallback === null) {
|
|
356
|
+
fallback = createStore();
|
|
357
|
+
}
|
|
358
|
+
return fallback;
|
|
359
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@uniflowed/state",
|
|
3
|
+
"version": "0.0.0-alpha.18",
|
|
4
|
+
"description": "Atoms and the React binding for @uniflowed/state, part of the Unified Toolchain for Flow.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/ubugeeei-prod/uf.git",
|
|
11
|
+
"directory": "packages/state"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./index.js"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"index.js",
|
|
18
|
+
"internal",
|
|
19
|
+
"!*.test.js"
|
|
20
|
+
],
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@uniflowed/cell": "0.0.0-alpha.18",
|
|
23
|
+
"@uniflowed/react": "0.0.0-alpha.18"
|
|
24
|
+
}
|
|
25
|
+
}
|