raf-effect-atom 0.0.0-stage → 0.1.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.
- package/CHANGELOG.md +111 -0
- package/LICENSE +21 -0
- package/README.md +65 -2
- package/dist/Handle.d.mts +90 -0
- package/dist/Hooks.d.mts +89 -0
- package/dist/Hooks.mjs +135 -0
- package/dist/Hydration.d.mts +93 -0
- package/dist/Hydration.mjs +134 -0
- package/dist/Loader.d.mts +192 -0
- package/dist/Loader.mjs +286 -0
- package/dist/Next.d.mts +65 -0
- package/dist/Next.mjs +64 -0
- package/dist/NextClient.d.mts +32 -0
- package/dist/NextClient.mjs +44 -0
- package/dist/ReactHydration.d.mts +52 -0
- package/dist/ReactHydration.mjs +71 -0
- package/dist/ReactRouter.d.mts +63 -0
- package/dist/ReactRouter.mjs +116 -0
- package/dist/RegistryProvider.d.mts +40 -0
- package/dist/RegistryProvider.mjs +55 -0
- package/dist/RequestRegistry.d.mts +54 -0
- package/dist/RequestRegistry.mjs +76 -0
- package/dist/TanStackRouter.d.mts +84 -0
- package/dist/TanStackRouter.mjs +134 -0
- package/dist/Waterfalls.d.mts +99 -0
- package/dist/Waterfalls.mjs +111 -0
- package/dist/_virtual/_rolldown/runtime.mjs +12 -0
- package/dist/index.d.mts +8 -0
- package/dist/index.mjs +7 -0
- package/dist/internal/development.mjs +6 -0
- package/dist/internal/handles.mjs +22 -0
- package/dist/internal/leases.mjs +39 -0
- package/dist/internal/next.mjs +16 -0
- package/dist/internal/pending.mjs +75 -0
- package/dist/internal/registry.mjs +16 -0
- package/dist/internal/sources.mjs +14 -0
- package/dist/internal/waterfalls.d.mts +5 -0
- package/dist/internal/waterfalls.mjs +83 -0
- package/package.json +80 -3
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { keyOf } from "./leases.mjs";
|
|
2
|
+
import * as Predicate from "effect/Predicate";
|
|
3
|
+
import * as Option from "effect/Option";
|
|
4
|
+
import * as AsyncResult from "effect/reactivity/AsyncResult";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
// src/internal/waterfalls.ts
|
|
7
|
+
const ModeContext = React.createContext("warn");
|
|
8
|
+
const docs = "https://raf-effect-atom.katt.dev/docs/request-waterfalls";
|
|
9
|
+
/** Whether the registry has `atom`'s node, or a hydrated value for it. */
|
|
10
|
+
const isStarted = (registry, atom) => {
|
|
11
|
+
const key = keyOf(atom);
|
|
12
|
+
return registry.getNodes().has(key) || Predicate.isString(key) && registry.preloadedSerializable.has(key);
|
|
13
|
+
};
|
|
14
|
+
const isLoading = (node) => {
|
|
15
|
+
const value = Option.getOrUndefined(node.valueOption());
|
|
16
|
+
return AsyncResult.isAsyncResult(value) && AsyncResult.isInitial(value);
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* The nodes `registry.get` created that load on their own: `Initial`, with
|
|
20
|
+
* no `Initial` parent. A derived atom over a source that is loading or
|
|
21
|
+
* loaded isn't one, so reading through `Atom.map` or a wrapper over an atom
|
|
22
|
+
* a loader started reports nothing.
|
|
23
|
+
*/
|
|
24
|
+
const startedLoading = (registry, atom, before) => {
|
|
25
|
+
const root = registry.getNodes().get(keyOf(atom));
|
|
26
|
+
const started = [];
|
|
27
|
+
const seen = /* @__PURE__ */ new Set();
|
|
28
|
+
const queue = root === void 0 ? [] : [root];
|
|
29
|
+
for (const node of queue) {
|
|
30
|
+
if (seen.has(node) || before.has(keyOf(node.atom))) continue;
|
|
31
|
+
seen.add(node);
|
|
32
|
+
if (isLoading(node) && !Array.from(node.parents).some(isLoading)) started.push(node);
|
|
33
|
+
queue.push(...node.parents);
|
|
34
|
+
}
|
|
35
|
+
return started;
|
|
36
|
+
};
|
|
37
|
+
const nameOf = (atom) => {
|
|
38
|
+
const key = keyOf(atom);
|
|
39
|
+
return atom.label?.[0] ?? (Predicate.isString(key) ? key : "an atom");
|
|
40
|
+
};
|
|
41
|
+
const message = (read, started) => `raf-effect-atom: rendering ${read === started ? `read ${read}` : `read ${read}, which started ${started}`} before anything else had started it, so its request waits for this render: a request waterfall. Start it before render and pass its handle down, from the route's loader or the server component (atoms.prefetch(source, ...args) or atoms.ensure). If the state that picks it, such as a page, a filter or an open dialog, isn't in the URL, put it there so the loader can. As escape hatches for state that can't be in the URL: start it in the event handler that changes the state (Loader.make(registry).prefetch), or, if its input only exists in render, read it with useAtomResult(source, useWaterfall(source, ...args)). ${docs}`;
|
|
42
|
+
const warned = /* @__PURE__ */ new WeakMap();
|
|
43
|
+
/**
|
|
44
|
+
* The nodes a read started in throw mode, with the error. React renders a
|
|
45
|
+
* component that threw once more before it gives up, and that render finds
|
|
46
|
+
* the node, so the error stays with the node.
|
|
47
|
+
*/
|
|
48
|
+
const thrown = /* @__PURE__ */ new WeakMap();
|
|
49
|
+
/**
|
|
50
|
+
* Reads `atom`, reporting by `mode` when the read starts an atom no loader,
|
|
51
|
+
* server component, event handler or `useWaterfall` started. Development
|
|
52
|
+
* only: callers skip it in production builds.
|
|
53
|
+
*/
|
|
54
|
+
const check = (registry, atom, mode) => {
|
|
55
|
+
if (mode === "off") return;
|
|
56
|
+
if (isStarted(registry, atom)) {
|
|
57
|
+
const node = registry.getNodes().get(keyOf(atom));
|
|
58
|
+
const error = mode === "throw" && node ? thrown.get(node) : void 0;
|
|
59
|
+
if (error !== void 0) throw error;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const before = new Set(registry.getNodes().keys());
|
|
63
|
+
registry.get(atom);
|
|
64
|
+
const first = startedLoading(registry, atom, before)[0];
|
|
65
|
+
if (first === void 0) return;
|
|
66
|
+
const text = message(nameOf(atom), nameOf(first.atom));
|
|
67
|
+
if (mode === "throw") {
|
|
68
|
+
const error = new Error(text);
|
|
69
|
+
const node = registry.getNodes().get(keyOf(atom));
|
|
70
|
+
if (node !== void 0) thrown.set(node, error);
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
let seen = warned.get(registry);
|
|
74
|
+
if (seen === void 0) {
|
|
75
|
+
seen = /* @__PURE__ */ new Set();
|
|
76
|
+
warned.set(registry, seen);
|
|
77
|
+
}
|
|
78
|
+
if (!seen.has(text)) {
|
|
79
|
+
seen.add(text);
|
|
80
|
+
console.warn(text);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
export { ModeContext, check, isStarted };
|
package/package.json
CHANGED
|
@@ -1,6 +1,83 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "raf-effect-atom",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Render-as-you-fetch for Effect Atom: start atoms from route loaders and stream them into the browser, typed failures intact",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"effect",
|
|
7
|
+
"effect-atom",
|
|
8
|
+
"react",
|
|
9
|
+
"render-as-you-fetch",
|
|
10
|
+
"ssr",
|
|
11
|
+
"suspense"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": {
|
|
15
|
+
"name": "KATT",
|
|
16
|
+
"email": "alexander@n1s.se"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/KATT/raf-effect-atom.git",
|
|
21
|
+
"directory": "packages/raf-effect-atom"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://raf-effect-atom.katt.dev",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/KATT/raf-effect-atom/issues"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist/",
|
|
29
|
+
"CHANGELOG.md",
|
|
30
|
+
"LICENSE",
|
|
31
|
+
"README.md"
|
|
32
|
+
],
|
|
33
|
+
"type": "module",
|
|
34
|
+
"sideEffects": false,
|
|
35
|
+
"exports": {
|
|
36
|
+
".": "./dist/index.mjs",
|
|
37
|
+
"./next": "./dist/Next.mjs",
|
|
38
|
+
"./react-router": "./dist/ReactRouter.mjs",
|
|
39
|
+
"./tanstack-router": "./dist/TanStackRouter.mjs"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@effect/atom-react": "^4.0.6",
|
|
43
|
+
"@tanstack/react-router": "^1.170.46",
|
|
44
|
+
"effect": "^4.0.6",
|
|
45
|
+
"react": "^19.2.0"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"@tanstack/react-router": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@effect/atom-react": "4.0.6",
|
|
54
|
+
"@effect/vitest": "4.0.6",
|
|
55
|
+
"@tanstack/react-router": "1.170.46",
|
|
56
|
+
"@testing-library/react": "16.3.3",
|
|
57
|
+
"@types/react": "19.3.0",
|
|
58
|
+
"@types/react-dom": "19.3.0",
|
|
59
|
+
"@vitest/coverage-v8": "5.0.5",
|
|
60
|
+
"effect": "4.0.6",
|
|
61
|
+
"effect-atom-boundary": "0.1.0",
|
|
62
|
+
"happy-dom": "21.0.4",
|
|
63
|
+
"react": "19.3.1",
|
|
64
|
+
"react-dom": "19.3.1",
|
|
65
|
+
"react-error-boundary": "6.1.6",
|
|
66
|
+
"react-router": "8.5.0",
|
|
67
|
+
"react-server-dom-webpack": "19.3.1",
|
|
68
|
+
"tstyche": "7.2.5",
|
|
69
|
+
"typescript": "7.0.2",
|
|
70
|
+
"vite-plus": "1.4.0"
|
|
71
|
+
},
|
|
72
|
+
"publishConfig": {
|
|
73
|
+
"access": "public",
|
|
74
|
+
"provenance": true
|
|
75
|
+
},
|
|
76
|
+
"scripts": {
|
|
77
|
+
"build": "vp pack",
|
|
78
|
+
"test": "vp test && pnpm test-types",
|
|
79
|
+
"test-types": "tstyche",
|
|
80
|
+
"typecheck": "tsc",
|
|
81
|
+
"publish:npm": "pnpm pack --out package.tgz && npm publish package.tgz --access public"
|
|
82
|
+
}
|
|
6
83
|
}
|