@octanejs/jotai 0.1.49 → 0.1.51
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.upstream +21 -0
- package/README.md +31 -72
- package/UPSTREAM.md +69 -0
- package/package.json +26 -9
- package/src/index.ts +1 -18
- package/src/react/Provider.tsrx +2 -2
- package/src/react/Provider.tsrx.d.ts +2 -1
- package/src/react/continuablePromise.ts +54 -0
- package/src/react/useAtomValue.ts +12 -145
- package/src/react/useAtomValueRaw.ts +53 -0
- package/src/react/useAtomValueRawSync.ts +47 -0
- package/src/react/utils.ts +0 -2
- package/src/react.ts +2 -5
- package/src/vanilla/utils.ts +2 -2
package/LICENSE.upstream
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Poimandres
|
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @octanejs/jotai
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Jotai 3 atoms and stores for Octane. The package imports Jotai's vanilla core and ports its React hooks and provider to Octane.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -9,89 +9,48 @@ npm install @octanejs/jotai
|
|
|
9
9
|
pnpm add @octanejs/jotai
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
jotai separates a framework-agnostic **vanilla core** (`atom`, `createStore`,
|
|
13
|
-
`getDefaultStore` + all of `vanilla/utils`) from a small **React binding**
|
|
14
|
-
(`Provider`, `useStore`, `useAtom`, `useAtomValue`, `useSetAtom`). This package
|
|
15
|
-
reuses the vanilla core unchanged (re-exported verbatim from `jotai/vanilla`) and
|
|
16
|
-
reimplements only the binding on octane's hooks — deliberately preserving
|
|
17
|
-
upstream's implementation shape (a force-update `useReducer` + effect
|
|
18
|
-
subscription, not `useSyncExternalStore`), so re-render behavior matches jotai on
|
|
19
|
-
React. The public surface matches jotai 1:1 — existing jotai code works by
|
|
20
|
-
changing the import.
|
|
21
|
-
|
|
22
12
|
```tsx
|
|
23
|
-
// before
|
|
24
|
-
import { atom, useAtom } from 'jotai';
|
|
25
|
-
// after
|
|
26
13
|
import { atom, useAtom } from '@octanejs/jotai';
|
|
27
14
|
|
|
28
15
|
const countAtom = atom(0);
|
|
29
16
|
|
|
30
17
|
function Counter() @{
|
|
31
18
|
const [count, setCount] = useAtom(countAtom);
|
|
32
|
-
<button onClick={() => setCount((
|
|
19
|
+
<button onClick={() => setCount((value) => value + 1)}>{count as string}</button>
|
|
33
20
|
}
|
|
34
21
|
```
|
|
35
22
|
|
|
36
23
|
## Entry points
|
|
37
24
|
|
|
38
|
-
|
|
|
39
|
-
| --- | --- |
|
|
40
|
-
| `@octanejs/jotai` |
|
|
41
|
-
| `@octanejs/jotai/
|
|
42
|
-
| `@octanejs/jotai/
|
|
43
|
-
| `@octanejs/jotai/vanilla
|
|
44
|
-
| `@octanejs/jotai/
|
|
45
|
-
| `@octanejs/jotai/
|
|
46
|
-
| `@octanejs/jotai/utils` |
|
|
47
|
-
|
|
48
|
-
`jotai/babel/*` (React-specific compile-time plugins) is not shipped.
|
|
49
|
-
|
|
50
|
-
## How it works
|
|
51
|
-
|
|
52
|
-
octane keys hooks by a compiler-injected per-call-site `Symbol`, appended as the
|
|
53
|
-
last argument of every `use*` call. The hooks here **forward** that slot to the
|
|
54
|
-
base hooks they compose (deriving a stable sub-slot per composed base hook), so
|
|
55
|
-
`useAtom(a)` and `useAtom(b)` in one component — or the same atom used twice —
|
|
56
|
-
stay independent, exactly like distinct call sites in React.
|
|
57
|
-
|
|
58
|
-
The binding is a line-for-line port of `jotai/react`: a reader holds a
|
|
59
|
-
`[value, store, atom]` tuple in a force-update reducer and subscribes to the
|
|
60
|
-
store in an effect. That means the same observable behavior as jotai on React,
|
|
61
|
-
including:
|
|
62
|
-
|
|
63
|
-
- **`useSetAtom` never re-renders the writer.** A component that only writes an
|
|
64
|
-
atom doesn't subscribe to it.
|
|
65
|
-
- **Derived atoms bail out.** A dependency write that recomputes to an
|
|
66
|
-
`Object.is`-equal value never notifies readers.
|
|
67
|
-
- **Readers mount with two renders** (the subscription effect re-checks the
|
|
68
|
-
value after subscribing) — same as upstream.
|
|
69
|
-
|
|
70
|
-
## Async atoms + Suspense
|
|
71
|
-
|
|
72
|
-
An atom whose value is a promise suspends the reader through octane's `use()`
|
|
73
|
-
(React-19 parity) on jotai's identity-stable *continuable promise*. Use a
|
|
74
|
-
suspense boundary (`@try { } @pending { } @catch (e) { }` or `<Suspense>`), or
|
|
75
|
-
skip suspending entirely with the vanilla `loadable`/`unwrap` escape hatches:
|
|
25
|
+
| Import | Surface |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| `@octanejs/jotai` | Vanilla atoms/stores, `Provider`, `useStore`, `useAtom`, `useSetAtom`, `useAtomValue`, `useAtomValueRaw`, `useAtomValueRawSync` |
|
|
28
|
+
| `@octanejs/jotai/react` | Provider and hooks |
|
|
29
|
+
| `@octanejs/jotai/react/utils` | `useResetAtom`, `useReducerAtom`, `useAtomCallback`, `useHydrateAtoms` |
|
|
30
|
+
| `@octanejs/jotai/vanilla` | Direct re-exports of `jotai/vanilla` |
|
|
31
|
+
| `@octanejs/jotai/vanilla/utils` | Direct re-exports of `jotai/vanilla/utils` |
|
|
32
|
+
| `@octanejs/jotai/vanilla/internals` | Upstream's unstable store building blocks |
|
|
33
|
+
| `@octanejs/jotai/utils` | Vanilla utilities and hook utilities |
|
|
76
34
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
35
|
+
Framework-neutral callers can import directly from `jotai/vanilla` and `jotai/vanilla/utils`. These imports share the same atoms and stores with the Octane binding.
|
|
36
|
+
|
|
37
|
+
## Jotai 3 migration
|
|
38
|
+
|
|
39
|
+
- `useAtomValueRaw` returns the atom value without unwrapping promises or suspending. It subscribes through an effect.
|
|
40
|
+
- `useAtomValueRawSync` also returns the raw value and uses `useSyncExternalStore` for synchronous store consistency.
|
|
41
|
+
- `useAtomValue` and `useAtom` retain Suspense integration. Their obsolete `delay` option is removed.
|
|
42
|
+
- `loadable` is removed. Use the retained `unwrap` utility or read raw promise values for non-suspending reads.
|
|
43
|
+
- `atomFamily` moves to `jotai-family`; install that package and import `atomFamily` directly from it.
|
|
44
|
+
- The atom read function's `setSelf` option is removed. Its replacement depends on the use case; see the [upstream migration guide](https://github.com/pmndrs/jotai/blob/89d4fddd1949628e50952fc8ac1b09786248dfca/docs/guides/migrating-to-v3.mdx).
|
|
45
|
+
- Upstream's internal store API advances from Rev3 to Rev4. It remains unstable.
|
|
46
|
+
- The existing `INTERNAL_InferAtomTuples` type remains available from both utility entry points for compatibility.
|
|
47
|
+
|
|
48
|
+
Readers no longer force a second render immediately after subscribing. `useSetAtom` still does not subscribe the writer. Hook slots are forwarded to keep multiple atom hooks in one component independent.
|
|
49
|
+
|
|
50
|
+
## Async atoms and server rendering
|
|
51
|
+
|
|
52
|
+
`useAtomValue` unwraps Jotai's stable continuable promises through Octane's `use()`. Use a Suspense boundary for pending values. Both raw hooks preserve the promise as a value.
|
|
92
53
|
|
|
93
|
-
|
|
54
|
+
Create a store per server request and pass it to `Provider`. Server rendering does not mount atom subscriptions. The hydration conformance test checks reuse of the server DOM, live client updates, and subscription teardown.
|
|
94
55
|
|
|
95
|
-
|
|
96
|
-
generated [bindings status table](../../docs/bindings-status.md), sourced from
|
|
97
|
-
this package's [`status.json`](./status.json).
|
|
56
|
+
See [UPSTREAM.md](./UPSTREAM.md) for the immutable source pin, API crosswalk, test adaptation, and recorded evidence.
|
package/UPSTREAM.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Jotai upstream
|
|
2
|
+
|
|
3
|
+
`@octanejs/jotai` ports the provider and React hooks from Jotai 3.0.0 and imports the framework-neutral core from `jotai/vanilla`, `/vanilla/utils`, and `/vanilla/internals`.
|
|
4
|
+
|
|
5
|
+
## Immutable identity and license
|
|
6
|
+
|
|
7
|
+
- Release: `jotai@3.0.0`
|
|
8
|
+
- Commit: `89d4fddd1949628e50952fc8ac1b09786248dfca`
|
|
9
|
+
- Source: https://github.com/pmndrs/jotai/tree/89d4fddd1949628e50952fc8ac1b09786248dfca
|
|
10
|
+
- MIT license: exact upstream bytes in packaged `LICENSE.upstream`; SHA-256 `0530d5d58026f4bb73367d195946f6a516a648ea62dd8b84763318f64d3cb3e2`.
|
|
11
|
+
- npm integrity: `sha512-KxhbmsJUp/tmrv6aZgO+nzB3H9K8C0xmk5i3aGkwy/EANg73DVBkggv3zVHaAi5PlNlQgTgP4uiVepo+JC+wSA==`.
|
|
12
|
+
- npm archive SHA-256: `dc9cfb4c901424eea4be448048a274c56472ab0956ee4e4e7d1623bc98e4ef00`.
|
|
13
|
+
- Signed npm provenance resolves the release tag to this commit; npm does not publish a `gitHead` for this release.
|
|
14
|
+
- Tested dependency: 3.0.0; supported catalog range: `^3.0.0`.
|
|
15
|
+
- React runtime oracle: React and React DOM 19.2.7. Original type compiler: TypeScript 6.0.3, with React types 19.2.18 and React DOM types 19.2.7.
|
|
16
|
+
|
|
17
|
+
`audit/upstream.lock.json` authenticates the byte-exact repository source and tests under `upstream/`. The npm artifact is retained for published-declaration verification. Neither tree is published. Vanilla source appears only in the pristine test environment; runtime code imports the installed upstream dependency.
|
|
18
|
+
|
|
19
|
+
## Complete export crosswalk
|
|
20
|
+
|
|
21
|
+
The table includes runtime and type exports from every published runtime entry. Root and vanilla utilities import the same upstream implementation. Provider/hooks and the four hook utilities are adapted to Octane. All seven entries have exact runtime export-set checks and positive public type assertions in `tests/types/public.ts`, including upstream's unstable `INTERNAL_*` exports. Package metadata describes the Octane package itself.
|
|
22
|
+
|
|
23
|
+
| Entry | Exports | Disposition |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| `@octanejs/jotai` | `Atom`, `ExtractAtomArgs`, `ExtractAtomResult`, `ExtractAtomValue`, `Getter`, `INTERNAL_overrideCreateStore`, `PrimitiveAtom`, `Provider`, `SetStateAction`, `Setter`, `WritableAtom`, `atom`, `createStore`, `getDefaultStore`, `useAtom`, `useAtomValue`, `useAtomValueRaw`, `useAtomValueRawSync`, `useSetAtom`, `useStore` | Imported vanilla exports plus ported hooks/provider. |
|
|
26
|
+
| `@octanejs/jotai/utils` | `RESET`, `atomWithDefault`, `atomWithLazy`, `atomWithObservable`, `atomWithReducer`, `atomWithRefresh`, `atomWithReset`, `atomWithStorage`, `createJSONStorage`, `freezeAtom`, `freezeAtomCreator`, `selectAtom`, `splitAtom`, `unstable_withStorageValidator`, `unwrap`, `useAtomCallback`, `useHydrateAtoms`, `useReducerAtom`, `useResetAtom` | Imported vanilla exports plus ported hooks/provider. |
|
|
27
|
+
| `@octanejs/jotai/vanilla` | `Atom`, `ExtractAtomArgs`, `ExtractAtomResult`, `ExtractAtomValue`, `Getter`, `INTERNAL_overrideCreateStore`, `PrimitiveAtom`, `SetStateAction`, `Setter`, `WritableAtom`, `atom`, `createStore`, `getDefaultStore` | Imported from the matching Jotai vanilla entry. |
|
|
28
|
+
| `@octanejs/jotai/vanilla/utils` | `RESET`, `atomWithDefault`, `atomWithLazy`, `atomWithObservable`, `atomWithReducer`, `atomWithRefresh`, `atomWithReset`, `atomWithStorage`, `createJSONStorage`, `freezeAtom`, `freezeAtomCreator`, `selectAtom`, `splitAtom`, `unstable_withStorageValidator`, `unwrap` | Imported from the matching Jotai vanilla entry. |
|
|
29
|
+
| `@octanejs/jotai/vanilla/internals` | `INTERNAL_AtomOnInit`, `INTERNAL_AtomOnMount`, `INTERNAL_AtomRead`, `INTERNAL_AtomState`, `INTERNAL_AtomStateMap`, `INTERNAL_AtomWrite`, `INTERNAL_BuildingBlocks`, `INTERNAL_Callbacks`, `INTERNAL_ChangedAtoms`, `INTERNAL_EnsureAtomState`, `INTERNAL_FlushCallbacks`, `INTERNAL_InvalidateDependents`, `INTERNAL_InvalidatedAtoms`, `INTERNAL_KEY_abortHandlersMap`, `INTERNAL_KEY_abortPromise`, `INTERNAL_KEY_atomOnInit`, `INTERNAL_KEY_atomOnMount`, `INTERNAL_KEY_atomRead`, `INTERNAL_KEY_atomStateMap`, `INTERNAL_KEY_atomWrite`, `INTERNAL_KEY_changedAtoms`, `INTERNAL_KEY_enhanceBuildingBlocks`, `INTERNAL_KEY_ensureAtomState`, `INTERNAL_KEY_flushCallbacks`, `INTERNAL_KEY_invalidateDependents`, `INTERNAL_KEY_invalidatedAtoms`, `INTERNAL_KEY_mountAtom`, `INTERNAL_KEY_mountCallbacks`, `INTERNAL_KEY_mountDependencies`, `INTERNAL_KEY_mountedMap`, `INTERNAL_KEY_readAtomState`, `INTERNAL_KEY_recomputeInvalidatedAtoms`, `INTERNAL_KEY_registerAbortHandler`, `INTERNAL_KEY_setAtomStateValueOrPromise`, `INTERNAL_KEY_storeEpochHolder`, `INTERNAL_KEY_storeGet`, `INTERNAL_KEY_storeHooks`, `INTERNAL_KEY_storeSet`, `INTERNAL_KEY_storeSub`, `INTERNAL_KEY_unmountAtom`, `INTERNAL_KEY_unmountCallbacks`, `INTERNAL_KEY_writeAtomState`, `INTERNAL_MountAtom`, `INTERNAL_MountDependencies`, `INTERNAL_Mounted`, `INTERNAL_MountedMap`, `INTERNAL_ReadAtomState`, `INTERNAL_RecomputeInvalidatedAtoms`, `INTERNAL_Store`, `INTERNAL_StoreHooks`, `INTERNAL_UnmountAtom`, `INTERNAL_WriteAtomState`, `INTERNAL_addPendingPromiseToDependency`, `INTERNAL_buildStoreRev4`, `INTERNAL_getBuildingBlocksRev4`, `INTERNAL_getMountedOrPendingDependents`, `INTERNAL_hasInitialValue`, `INTERNAL_initializeStoreHooksRev4`, `INTERNAL_isActuallyWritableAtom`, `INTERNAL_isAtomStateInitialized`, `INTERNAL_isPromiseLike`, `INTERNAL_returnAtomValue`, `INTERNAL_shouldThrowSynchronously` | Imported from the matching Jotai vanilla entry. |
|
|
30
|
+
| `@octanejs/jotai/react` | `Provider`, `useAtom`, `useAtomValue`, `useAtomValueRaw`, `useAtomValueRawSync`, `useSetAtom`, `useStore` | Ported hooks/provider. |
|
|
31
|
+
| `@octanejs/jotai/react/utils` | `useAtomCallback`, `useHydrateAtoms`, `useReducerAtom`, `useResetAtom` | Ported hooks/provider. |
|
|
32
|
+
|
|
33
|
+
## Source boundary
|
|
34
|
+
|
|
35
|
+
`src/react/Provider.tsrx` and `src/react/store.ts` correspond to upstream `src/react/Provider.ts`. `src/react/utils.ts` retains the four modules under upstream `src/react/utils/`. Other hook and continuable-promise modules preserve upstream layout. `src/internal.ts` supplies the Octane call-site slot adaptation. `audit/source-ledger.json` records hashes for the complete shipped closure and identifies the adapted boundary.
|
|
36
|
+
|
|
37
|
+
Octane's `use()` handles Suspense; React 18's fallback is unnecessary. Raw hook subscriptions and the Rev4 abort-handler protocol follow Jotai 3. Provider context is deduplicated by renderer identity, retaining the existing Octane binding contract. Providers render one stable JSX shape when the store prop changes.
|
|
38
|
+
|
|
39
|
+
The migration removes `delay`, `loadable`, `atomFamily`, the atom-read `setSelf` option, and Rev3 internals, as upstream did. The existing `INTERNAL_InferAtomTuples` utility export is retained for compatibility and checked against Jotai's published `dist/react/utils/useHydrateAtoms.d.ts` declaration. See README for consumer guidance.
|
|
40
|
+
|
|
41
|
+
## Complete upstream test crosswalk
|
|
42
|
+
|
|
43
|
+
`audit/registrations.json` preserves all 404 immutable registrations across 44 test files. `audit/crosswalk.json` maps each registration to its generated Octane test file. Both pristine and adapted runtime suites execute all 404 registrations without skips. Eleven registrations in the four explicit type-test files are additionally compiled in separate pristine and adapted programs; the hydration utility suite's negative type controls are also compiled.
|
|
44
|
+
|
|
45
|
+
The committed lock generates ignored `tests/upstream/`. Import repointing and the Octane JSX pragma are mechanical rewrites. Committed patches contain only these test adaptations:
|
|
46
|
+
|
|
47
|
+
- React class error boundaries become functional Octane error boundaries, preserving the error, fallback and retry assertions.
|
|
48
|
+
- Commit-count hooks move from JSX child expressions into component setup so they observe the owning component. Their effects use explicit `null` dependencies for the original every-render observation.
|
|
49
|
+
- The observable error-boundary fixture moves to module scope for Octane compilation.
|
|
50
|
+
|
|
51
|
+
The pristine type lane preserves upstream's `noUncheckedIndexedAccess` and `exactOptionalPropertyTypes`. The adapted lane uses the repository's strict configuration without these additional flags because imported Octane runtime source does not satisfy them. Both lanes retain `strict: true`, `skipLibCheck: false`, every upstream positive assertion, and every negative control.
|
|
52
|
+
|
|
53
|
+
`audit/react-parity.json` registers pristine/adapted runtime and type lanes, the four existing React/Octane differential scenarios, and the SSR/hydration conformance scenario. The latter proves no server subscriptions, existing DOM adoption, click/external-store updates, and teardown. Conformance checks additionally cover slot identity, provider scope/swap, subscription cleanup, async rejection/resolution and utilities.
|
|
54
|
+
|
|
55
|
+
## Reproduce evidence
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
node scripts/react-port/materialize.mjs run --package-dir packages/jotai
|
|
59
|
+
node scripts/react-parity/verify-provenance.mjs --package-dir packages/jotai
|
|
60
|
+
pnpm --dir packages/jotai test
|
|
61
|
+
node scripts/react-parity/harness.mjs run-required --manifest packages/jotai/audit/react-parity.json
|
|
62
|
+
./packages/jotai/node_modules/.bin/tsc --noEmit -p packages/jotai/tsconfig.pristine.json
|
|
63
|
+
pnpm exec tsrx-tsc --noEmit -p packages/jotai/tsconfig.adapted.json
|
|
64
|
+
pnpm exec tsrx-tsc --noEmit -p packages/jotai/tsconfig.json
|
|
65
|
+
pnpm exec tsrx-tsc --noEmit -p packages/jotai/tests/types/tsconfig.json
|
|
66
|
+
pnpm packages:pack:check
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The shared port evidence gate records the actual commands and verifies the package contract, licenses, crosswalk, and shipped closure. The declared imported surfaces retain dependency, exports, public types and consumer checks; copied React surfaces retain their full upstream suite obligations. The ownership declaration does not remove prior evidence.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octanejs/jotai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.51",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -31,7 +31,10 @@
|
|
|
31
31
|
"types": "src/index.ts",
|
|
32
32
|
"files": [
|
|
33
33
|
"src",
|
|
34
|
-
"README.md"
|
|
34
|
+
"README.md",
|
|
35
|
+
"UPSTREAM.md",
|
|
36
|
+
"LICENSE",
|
|
37
|
+
"LICENSE.upstream"
|
|
35
38
|
],
|
|
36
39
|
"exports": {
|
|
37
40
|
".": "./src/index.ts",
|
|
@@ -43,20 +46,34 @@
|
|
|
43
46
|
"./utils": "./src/utils.ts"
|
|
44
47
|
},
|
|
45
48
|
"dependencies": {
|
|
46
|
-
"jotai": "^
|
|
49
|
+
"jotai": "^3.0.0"
|
|
47
50
|
},
|
|
48
51
|
"peerDependencies": {
|
|
49
|
-
"octane": "^0.1.51 || ^0.2.0"
|
|
52
|
+
"octane": "^0.1.51 || ^0.2.0 || ^0.3.0"
|
|
50
53
|
},
|
|
51
54
|
"devDependencies": {
|
|
52
|
-
"@tsrx/react": "^0.
|
|
55
|
+
"@tsrx/react": "^0.3.0",
|
|
53
56
|
"esbuild": "^0.28.1",
|
|
54
|
-
"react": "
|
|
55
|
-
"react-dom": "
|
|
57
|
+
"react": "19.2.7",
|
|
58
|
+
"react-dom": "19.2.7",
|
|
56
59
|
"vitest": "^4.1.10",
|
|
57
|
-
"
|
|
60
|
+
"@testing-library/react": "^16.3.2",
|
|
61
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
62
|
+
"rxjs": "^7.8.2",
|
|
63
|
+
"wonka": "6.3.6",
|
|
64
|
+
"jest-leak-detector": "30.5.1",
|
|
65
|
+
"@types/node": "^24.13.3",
|
|
66
|
+
"vite": "^8.1.5",
|
|
67
|
+
"typescript": "6.0.3",
|
|
68
|
+
"@types/react": "19.2.18",
|
|
69
|
+
"@types/react-dom": "19.2.7",
|
|
70
|
+
"@octanejs/testing-library": "0.1.52",
|
|
71
|
+
"octane": "0.3.0"
|
|
58
72
|
},
|
|
59
73
|
"scripts": {
|
|
60
|
-
"test": "vitest run"
|
|
74
|
+
"test": "vitest run --root ../.. --config vitest.config.js --project jotai --project jotai-differential --project jotai-pristine --project jotai-hydration",
|
|
75
|
+
"upstream:check": "node ../../scripts/react-port/materialize.mjs run --check --package-dir .",
|
|
76
|
+
"upstream:test": "node ../../scripts/react-parity/run-pristine.mjs jotai",
|
|
77
|
+
"upstream:verify": "node ../../scripts/react-parity/verify-provenance.mjs --package-dir ."
|
|
61
78
|
}
|
|
62
79
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// jotai cleanly separates a framework-agnostic vanilla core (`atom`,
|
|
4
|
-
// `createStore`, `getDefaultStore` + all of vanilla/utils) from a small React
|
|
5
|
-
// binding (`Provider`, `useStore`, `useAtom`, `useAtomValue`, `useSetAtom`).
|
|
6
|
-
// This package reuses the vanilla core UNCHANGED (re-exported verbatim from
|
|
7
|
-
// `jotai/vanilla`) and reimplements only the binding on octane's hooks —
|
|
8
|
-
// preserving upstream's useReducer-force-update implementation rather than
|
|
9
|
-
// rewriting it on useSyncExternalStore, so re-render behavior matches jotai on
|
|
10
|
-
// React. The public surface matches jotai 1:1: existing jotai code works by
|
|
11
|
-
// changing the import from `jotai` to `@octanejs/jotai`.
|
|
12
|
-
//
|
|
13
|
-
// The one octane-specific detail is hook slots: octane keys hooks by a
|
|
14
|
-
// compiler-injected per-call-site Symbol, appended as the LAST argument of
|
|
15
|
-
// every `use*` call. The hooks here FORWARD that slot (deriving stable
|
|
16
|
-
// sub-slots when one hook composes several base hooks — see internal.ts), so
|
|
17
|
-
// `useAtom(a)` and `useAtom(b)` in one component stay independent, just like
|
|
18
|
-
// in React.
|
|
1
|
+
// Jotai 3 vanilla exports and Octane provider/hooks.
|
|
19
2
|
export * from './vanilla';
|
|
20
3
|
export * from './react';
|
package/src/react/Provider.tsrx
CHANGED
|
@@ -7,7 +7,7 @@ import { useRef, type OctaneNode } from 'octane';
|
|
|
7
7
|
import { createStore } from 'jotai/vanilla';
|
|
8
8
|
import { StoreContext, type Store } from './store.ts';
|
|
9
9
|
|
|
10
|
-
export function Provider(props: { children?: OctaneNode; store?: Store })
|
|
10
|
+
export function Provider(props: { children?: OctaneNode; store?: Store }) {
|
|
11
11
|
const { children, store } = props;
|
|
12
12
|
|
|
13
13
|
const storeRef = useRef<Store | null>(null);
|
|
@@ -15,5 +15,5 @@ export function Provider(props: { children?: OctaneNode; store?: Store }) @{
|
|
|
15
15
|
storeRef.current = createStore();
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
<StoreContext
|
|
18
|
+
return <StoreContext value={store || storeRef.current!}>{children}</StoreContext>;
|
|
19
19
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Type declaration for the .tsrx component (resolved by relative path).
|
|
2
2
|
import type { Store } from './store';
|
|
3
|
+
import type { JSX, OctaneNode } from 'octane';
|
|
3
4
|
|
|
4
|
-
export declare const Provider: (props: { children?:
|
|
5
|
+
export declare const Provider: (props: { children?: OctaneNode; store?: Store }) => JSX.Element;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {
|
|
2
|
+
INTERNAL_getBuildingBlocksRev4 as INTERNAL_getBuildingBlocks,
|
|
3
|
+
INTERNAL_KEY_registerAbortHandler as KEY_registerAbortHandler,
|
|
4
|
+
} from 'jotai/vanilla/internals';
|
|
5
|
+
import type { INTERNAL_Store as Store } from 'jotai/vanilla/internals';
|
|
6
|
+
|
|
7
|
+
export const isPromiseLike = (x: unknown): x is PromiseLike<unknown> =>
|
|
8
|
+
typeof (x as PromiseLike<unknown>)?.then === 'function';
|
|
9
|
+
|
|
10
|
+
const continuablePromiseMap = new WeakMap<PromiseLike<unknown>, Promise<unknown>>();
|
|
11
|
+
|
|
12
|
+
export const createContinuablePromise = <T>(
|
|
13
|
+
store: Store,
|
|
14
|
+
promise: PromiseLike<T>,
|
|
15
|
+
getValue: () => PromiseLike<T> | T,
|
|
16
|
+
): Promise<unknown> => {
|
|
17
|
+
const buildingBlocks = INTERNAL_getBuildingBlocks(store);
|
|
18
|
+
const registerAbortHandler = buildingBlocks[KEY_registerAbortHandler];
|
|
19
|
+
let continuablePromise = continuablePromiseMap.get(promise);
|
|
20
|
+
if (!continuablePromise) {
|
|
21
|
+
continuablePromise = new Promise<T>((resolve, reject) => {
|
|
22
|
+
let curr = promise;
|
|
23
|
+
const onFulfilled = (me: PromiseLike<T>) => (v: T) => {
|
|
24
|
+
if (curr === me) {
|
|
25
|
+
resolve(v);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const onRejected = (me: PromiseLike<T>) => (e: unknown) => {
|
|
29
|
+
if (curr === me) {
|
|
30
|
+
reject(e);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const onAbort = () => {
|
|
34
|
+
try {
|
|
35
|
+
const nextValue = getValue();
|
|
36
|
+
if (isPromiseLike(nextValue)) {
|
|
37
|
+
continuablePromiseMap.set(nextValue, continuablePromise!);
|
|
38
|
+
curr = nextValue;
|
|
39
|
+
nextValue.then(onFulfilled(nextValue), onRejected(nextValue));
|
|
40
|
+
registerAbortHandler(buildingBlocks, store, nextValue, onAbort);
|
|
41
|
+
} else {
|
|
42
|
+
resolve(nextValue);
|
|
43
|
+
}
|
|
44
|
+
} catch (e) {
|
|
45
|
+
reject(e);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
promise.then(onFulfilled(promise), onRejected(promise));
|
|
49
|
+
registerAbortHandler(buildingBlocks, store, promise, onAbort);
|
|
50
|
+
});
|
|
51
|
+
continuablePromiseMap.set(promise, continuablePromise);
|
|
52
|
+
}
|
|
53
|
+
return continuablePromise;
|
|
54
|
+
};
|
|
@@ -1,37 +1,10 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
// and subscribes in an effect (re-render on store notify; an unconditional
|
|
4
|
-
// post-subscribe rerender catches updates that raced between render and
|
|
5
|
-
// effect — so mounting renders twice, in React and here alike). The port keeps
|
|
6
|
-
// that shape exactly: octane's `useReducer` has React's semantics (lazy init;
|
|
7
|
-
// a no-op dispatch still renders once with children bailing), and a
|
|
8
|
-
// render-phase `rerender()` on store/atom swap mutates the reducer state
|
|
9
|
-
// synchronously and schedules a self-quiescing re-render while THIS render
|
|
10
|
-
// uses the locally-computed value.
|
|
11
|
-
//
|
|
12
|
-
// Async atom values suspend through octane's `use()` on a "continuable"
|
|
13
|
-
// promise: a wrapper whose identity is WeakMap-stable across atom
|
|
14
|
-
// recomputations (aborted fetches chain into it via the store's abort-handler
|
|
15
|
-
// registry). That stability is what lets octane's thenable replay see the SAME
|
|
16
|
-
// promise on every re-render until it settles — do not simplify it away.
|
|
17
|
-
import { use, useDebugValue, useEffect, useReducer } from 'octane';
|
|
18
|
-
import { INTERNAL_getBuildingBlocksRev3 as INTERNAL_getBuildingBlocks } from 'jotai/vanilla/internals';
|
|
1
|
+
// Jotai 3 separates the concurrent raw subscription from Suspense unwrapping.
|
|
2
|
+
import { use } from 'octane';
|
|
19
3
|
import type { Atom, ExtractAtomValue } from 'jotai/vanilla';
|
|
20
|
-
import {
|
|
4
|
+
import { isPromiseLike } from './continuablePromise';
|
|
5
|
+
import { useAtomValueRaw } from './useAtomValueRaw';
|
|
21
6
|
import { splitSlot, subSlot } from '../internal';
|
|
22
7
|
|
|
23
|
-
// The consumer's bundler substitutes the whole `process.env.NODE_ENV` expression
|
|
24
|
-
// below, so it must stay written out literally. Declared module-locally — never
|
|
25
|
-
// `declare global`, which would ship in the tarball — so this file type-checks in
|
|
26
|
-
// a browser app that has no `@types/node`.
|
|
27
|
-
declare const process: { env: { NODE_ENV?: string } };
|
|
28
|
-
|
|
29
|
-
const isPromiseLike = (x: unknown): x is PromiseLike<unknown> =>
|
|
30
|
-
typeof (x as PromiseLike<unknown>)?.then === 'function';
|
|
31
|
-
|
|
32
|
-
// Opt-in (`unstable_promiseStatus`) decoration of the suspended promise with
|
|
33
|
-
// React 19's `status`/`value`/`reason` convention. Upstream defaults this to
|
|
34
|
-
// "React.use is missing"; octane always has `use`, so it defaults to false.
|
|
35
8
|
const attachPromiseStatus = <T>(
|
|
36
9
|
promise: PromiseLike<T> & {
|
|
37
10
|
status?: 'pending' | 'fulfilled' | 'rejected';
|
|
@@ -54,136 +27,30 @@ const attachPromiseStatus = <T>(
|
|
|
54
27
|
}
|
|
55
28
|
};
|
|
56
29
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const createContinuablePromise = <T>(
|
|
60
|
-
store: Store,
|
|
61
|
-
promise: PromiseLike<T>,
|
|
62
|
-
getValue: () => PromiseLike<T> | T,
|
|
63
|
-
) => {
|
|
64
|
-
const buildingBlocks = INTERNAL_getBuildingBlocks(store);
|
|
65
|
-
const registerAbortHandler = buildingBlocks[26];
|
|
66
|
-
let continuablePromise = continuablePromiseMap.get(promise);
|
|
67
|
-
if (!continuablePromise) {
|
|
68
|
-
continuablePromise = new Promise<T>((resolve, reject) => {
|
|
69
|
-
let curr = promise;
|
|
70
|
-
const onFulfilled = (me: PromiseLike<T>) => (v: T) => {
|
|
71
|
-
if (curr === me) {
|
|
72
|
-
resolve(v);
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
const onRejected = (me: PromiseLike<T>) => (e: unknown) => {
|
|
76
|
-
if (curr === me) {
|
|
77
|
-
reject(e);
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
const onAbort = () => {
|
|
81
|
-
try {
|
|
82
|
-
const nextValue = getValue();
|
|
83
|
-
if (isPromiseLike(nextValue)) {
|
|
84
|
-
continuablePromiseMap.set(nextValue, continuablePromise!);
|
|
85
|
-
curr = nextValue;
|
|
86
|
-
nextValue.then(onFulfilled(nextValue), onRejected(nextValue));
|
|
87
|
-
registerAbortHandler(buildingBlocks, store, nextValue, onAbort);
|
|
88
|
-
} else {
|
|
89
|
-
resolve(nextValue);
|
|
90
|
-
}
|
|
91
|
-
} catch (e) {
|
|
92
|
-
reject(e);
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
promise.then(onFulfilled(promise), onRejected(promise));
|
|
96
|
-
registerAbortHandler(buildingBlocks, store, promise, onAbort);
|
|
97
|
-
});
|
|
98
|
-
continuablePromiseMap.set(promise, continuablePromise);
|
|
99
|
-
}
|
|
100
|
-
return continuablePromise;
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
type Options = Parameters<typeof useStore>[0] & {
|
|
104
|
-
/** @deprecated delay option is deprecated and will be removed in v3. https://github.com/pmndrs/jotai/pull/3264 */
|
|
105
|
-
delay?: number;
|
|
30
|
+
type Options = Parameters<typeof useAtomValueRaw>[1] & {
|
|
106
31
|
unstable_promiseStatus?: boolean;
|
|
107
32
|
};
|
|
108
33
|
|
|
109
34
|
export function useAtomValue<Value>(atom: Atom<Value>, options?: Options): Awaited<Value>;
|
|
110
|
-
|
|
111
35
|
export function useAtomValue<AtomType extends Atom<unknown>>(
|
|
112
36
|
atom: AtomType,
|
|
113
37
|
options?: Options,
|
|
114
38
|
): Awaited<ExtractAtomValue<AtomType>>;
|
|
115
|
-
|
|
116
39
|
export function useAtomValue<Value>(
|
|
117
40
|
atom: Atom<Value>,
|
|
118
41
|
...rest: [options?: Options, slot?: symbol]
|
|
119
42
|
) {
|
|
120
43
|
const [user, slot] = splitSlot(rest);
|
|
121
44
|
const options = user[0] as Options | undefined;
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
readonly [Value, Store, Atom<Value>],
|
|
127
|
-
void,
|
|
128
|
-
undefined
|
|
129
|
-
>(
|
|
130
|
-
(prev) => {
|
|
131
|
-
const nextValue = store.get(atom);
|
|
132
|
-
if (Object.is(prev[0], nextValue) && prev[1] === store && prev[2] === atom) {
|
|
133
|
-
return prev;
|
|
134
|
-
}
|
|
135
|
-
return [nextValue, store, atom];
|
|
136
|
-
},
|
|
137
|
-
undefined,
|
|
138
|
-
() => [store.get(atom), store, atom],
|
|
139
|
-
subSlot(slot, 'uav:r'),
|
|
45
|
+
const value = (useAtomValueRaw as <T>(a: Atom<T>, o?: Options, s?: symbol) => T)(
|
|
46
|
+
atom,
|
|
47
|
+
options,
|
|
48
|
+
subSlot(slot, 'value:raw'),
|
|
140
49
|
);
|
|
141
|
-
|
|
142
|
-
let value = valueFromReducer;
|
|
143
|
-
if (storeFromReducer !== store || atomFromReducer !== atom) {
|
|
144
|
-
rerender();
|
|
145
|
-
value = store.get(atom);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
useEffect(
|
|
149
|
-
() => {
|
|
150
|
-
const unsub = store.sub(atom, () => {
|
|
151
|
-
if (promiseStatus) {
|
|
152
|
-
try {
|
|
153
|
-
const value = store.get(atom);
|
|
154
|
-
if (isPromiseLike(value)) {
|
|
155
|
-
attachPromiseStatus(createContinuablePromise(store, value, () => store.get(atom)));
|
|
156
|
-
}
|
|
157
|
-
} catch {
|
|
158
|
-
// ignore
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
if (typeof delay === 'number') {
|
|
162
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
163
|
-
console.warn(
|
|
164
|
-
'[DEPRECATED] delay option is deprecated and will be removed in v3. https://github.com/pmndrs/jotai/pull/3264',
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
// delay rerendering to wait a promise possibly to resolve
|
|
168
|
-
setTimeout(rerender, delay);
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
rerender();
|
|
172
|
-
});
|
|
173
|
-
rerender();
|
|
174
|
-
return unsub;
|
|
175
|
-
},
|
|
176
|
-
[store, atom, delay, promiseStatus],
|
|
177
|
-
subSlot(slot, 'uav:e'),
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
useDebugValue(value);
|
|
181
50
|
if (isPromiseLike(value)) {
|
|
182
|
-
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
return use(promise);
|
|
51
|
+
// Octane always provides use(); upstream's React 18 fallback is unnecessary.
|
|
52
|
+
if (options?.unstable_promiseStatus) attachPromiseStatus(value);
|
|
53
|
+
return use(value);
|
|
187
54
|
}
|
|
188
55
|
return value as Awaited<Value>;
|
|
189
56
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { useDebugValue, useEffect, useReducer } from 'octane';
|
|
2
|
+
import type { Atom, ExtractAtomValue } from 'jotai/vanilla';
|
|
3
|
+
import { createContinuablePromise, isPromiseLike } from './continuablePromise';
|
|
4
|
+
import { useStore } from './store';
|
|
5
|
+
|
|
6
|
+
import { splitSlot, subSlot } from '../internal';
|
|
7
|
+
|
|
8
|
+
type Store = ReturnType<typeof useStore>;
|
|
9
|
+
|
|
10
|
+
type Options = Parameters<typeof useStore>[0];
|
|
11
|
+
|
|
12
|
+
export function useAtomValueRaw<Value>(atom: Atom<Value>, options?: Options): Value;
|
|
13
|
+
|
|
14
|
+
export function useAtomValueRaw<AtomType extends Atom<unknown>>(
|
|
15
|
+
atom: AtomType,
|
|
16
|
+
options?: Options,
|
|
17
|
+
): ExtractAtomValue<AtomType>;
|
|
18
|
+
|
|
19
|
+
export function useAtomValueRaw<Value>(
|
|
20
|
+
atom: Atom<Value>,
|
|
21
|
+
...rest: [options?: Options, slot?: symbol]
|
|
22
|
+
) {
|
|
23
|
+
const [user, slot] = splitSlot(rest);
|
|
24
|
+
const options = user[0] as Options | undefined;
|
|
25
|
+
const store = useStore(options);
|
|
26
|
+
const [[valueFromReducer, storeFromReducer, atomFromReducer], rerender] = useReducer<
|
|
27
|
+
readonly [Value, Store, typeof atom],
|
|
28
|
+
void,
|
|
29
|
+
undefined
|
|
30
|
+
>(
|
|
31
|
+
(prev) => {
|
|
32
|
+
const nextValue = store.get(atom);
|
|
33
|
+
if (Object.is(prev[0], nextValue) && prev[1] === store && prev[2] === atom) {
|
|
34
|
+
return prev;
|
|
35
|
+
}
|
|
36
|
+
return [nextValue, store, atom];
|
|
37
|
+
},
|
|
38
|
+
undefined,
|
|
39
|
+
() => [store.get(atom), store, atom],
|
|
40
|
+
subSlot(slot, 'raw:reducer'),
|
|
41
|
+
);
|
|
42
|
+
let value = valueFromReducer;
|
|
43
|
+
if (storeFromReducer !== store || atomFromReducer !== atom) {
|
|
44
|
+
rerender();
|
|
45
|
+
value = store.get(atom);
|
|
46
|
+
}
|
|
47
|
+
useEffect(() => store.sub(atom, rerender), [store, atom], subSlot(slot, 'raw:effect'));
|
|
48
|
+
useDebugValue(value);
|
|
49
|
+
if (isPromiseLike(value)) {
|
|
50
|
+
return createContinuablePromise(store, value, () => store.get(atom));
|
|
51
|
+
}
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useCallback, useDebugValue, useSyncExternalStore } from 'octane';
|
|
2
|
+
import type { Atom, ExtractAtomValue } from 'jotai/vanilla';
|
|
3
|
+
import { createContinuablePromise, isPromiseLike } from './continuablePromise';
|
|
4
|
+
import { useStore } from './store';
|
|
5
|
+
|
|
6
|
+
import { splitSlot, subSlot } from '../internal';
|
|
7
|
+
|
|
8
|
+
type Options = Parameters<typeof useStore>[0];
|
|
9
|
+
|
|
10
|
+
export function useAtomValueRawSync<Value>(atom: Atom<Value>, options?: Options): Value;
|
|
11
|
+
|
|
12
|
+
export function useAtomValueRawSync<AtomType extends Atom<unknown>>(
|
|
13
|
+
atom: AtomType,
|
|
14
|
+
options?: Options,
|
|
15
|
+
): ExtractAtomValue<AtomType>;
|
|
16
|
+
|
|
17
|
+
export function useAtomValueRawSync<Value>(
|
|
18
|
+
atom: Atom<Value>,
|
|
19
|
+
...rest: [options?: Options, slot?: symbol]
|
|
20
|
+
) {
|
|
21
|
+
const [user, slot] = splitSlot(rest);
|
|
22
|
+
const options = user[0] as Options | undefined;
|
|
23
|
+
const store = useStore(options);
|
|
24
|
+
const getSnapshot = useCallback(
|
|
25
|
+
() => {
|
|
26
|
+
const value = store.get(atom);
|
|
27
|
+
if (isPromiseLike(value)) {
|
|
28
|
+
return createContinuablePromise(store, value, () => store.get(atom));
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
},
|
|
32
|
+
[store, atom],
|
|
33
|
+
subSlot(slot, 'sync:snapshot'),
|
|
34
|
+
);
|
|
35
|
+
const value = useSyncExternalStore(
|
|
36
|
+
useCallback(
|
|
37
|
+
(callback: () => void) => store.sub(atom, callback),
|
|
38
|
+
[store, atom],
|
|
39
|
+
subSlot(slot, 'sync:subscribe'),
|
|
40
|
+
),
|
|
41
|
+
getSnapshot,
|
|
42
|
+
getSnapshot,
|
|
43
|
+
subSlot(slot, 'sync:store'),
|
|
44
|
+
);
|
|
45
|
+
useDebugValue(value);
|
|
46
|
+
return value;
|
|
47
|
+
}
|
package/src/react/utils.ts
CHANGED
|
@@ -128,8 +128,6 @@ type InferAtomTuples<T> = {
|
|
|
128
128
|
: never;
|
|
129
129
|
};
|
|
130
130
|
|
|
131
|
-
// For internal use only
|
|
132
|
-
// This can be changed without notice.
|
|
133
131
|
export type INTERNAL_InferAtomTuples<T> = InferAtomTuples<T>;
|
|
134
132
|
|
|
135
133
|
const hydratedMap: WeakMap<Store, WeakSet<AnyWritableAtom>> = new WeakMap();
|
package/src/react.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
// `@octanejs/jotai/react` — the binding layer, ported from jotai's react.ts.
|
|
2
|
-
// Upstream's implementation shape is kept deliberately (a force-update
|
|
3
|
-
// useReducer + effect subscription, NOT useSyncExternalStore) so behavior —
|
|
4
|
-
// including re-render timing and write-only non-re-rendering — matches jotai
|
|
5
|
-
// on React.
|
|
6
1
|
export { Provider } from './react/Provider.tsrx';
|
|
7
2
|
export { useStore } from './react/store';
|
|
8
3
|
export { useAtomValue } from './react/useAtomValue';
|
|
9
4
|
export { useSetAtom } from './react/useSetAtom';
|
|
10
5
|
export { useAtom } from './react/useAtom';
|
|
6
|
+
export { useAtomValueRaw } from './react/useAtomValueRaw';
|
|
7
|
+
export { useAtomValueRawSync } from './react/useAtomValueRawSync';
|
package/src/vanilla/utils.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// `@octanejs/jotai/vanilla/utils` — re-exported verbatim from jotai.
|
|
2
2
|
//
|
|
3
3
|
// Every vanilla util (RESET, atomWithReset, atomWithDefault, atomWithStorage,
|
|
4
|
-
// atomWithReducer, atomWithRefresh, atomWithLazy, atomWithObservable,
|
|
5
|
-
// selectAtom, splitAtom,
|
|
4
|
+
// atomWithReducer, atomWithRefresh, atomWithLazy, atomWithObservable,
|
|
5
|
+
// selectAtom, splitAtom, unwrap, freezeAtom, …) is a framework-agnostic
|
|
6
6
|
// atom factory or store helper — it composes with octane's `useAtom`/`useAtomValue`
|
|
7
7
|
// unchanged, so we re-export it as-is (mirroring src/vanilla.ts).
|
|
8
8
|
export * from 'jotai/vanilla/utils';
|