@effector-tanstack-query/core 0.1.0 → 0.2.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/LICENSE +21 -0
- package/dist/index.cjs +5 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/prefetchQueries.cjs +14 -0
- package/dist/prefetchQueries.cjs.map +1 -0
- package/dist/prefetchQueries.d.cts +59 -0
- package/dist/prefetchQueries.d.ts +59 -0
- package/dist/prefetchQueries.js +12 -0
- package/dist/prefetchQueries.js.map +1 -0
- package/package.json +8 -8
- package/src/index.ts +5 -0
- package/src/prefetchQueries.ts +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ilya Agarkov
|
|
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/dist/index.cjs
CHANGED
|
@@ -5,6 +5,7 @@ var createInfiniteQuery_cjs = require('./createInfiniteQuery.cjs');
|
|
|
5
5
|
var createMutation_cjs = require('./createMutation.cjs');
|
|
6
6
|
var createInvalidate_cjs = require('./createInvalidate.cjs');
|
|
7
7
|
var queryClient_cjs = require('./queryClient.cjs');
|
|
8
|
+
var prefetchQueries_cjs = require('./prefetchQueries.cjs');
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
|
|
@@ -32,5 +33,9 @@ Object.defineProperty(exports, "setQueryClient", {
|
|
|
32
33
|
enumerable: true,
|
|
33
34
|
get: function () { return queryClient_cjs.setQueryClient; }
|
|
34
35
|
});
|
|
36
|
+
Object.defineProperty(exports, "prefetchQueries", {
|
|
37
|
+
enumerable: true,
|
|
38
|
+
get: function () { return prefetchQueries_cjs.prefetchQueries; }
|
|
39
|
+
});
|
|
35
40
|
//# sourceMappingURL=index.cjs.map
|
|
36
41
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.d.cts
CHANGED
|
@@ -3,6 +3,7 @@ export { createInfiniteQuery } from './createInfiniteQuery.cjs';
|
|
|
3
3
|
export { createMutation } from './createMutation.cjs';
|
|
4
4
|
export { CreateInvalidateOptions, createInvalidate } from './createInvalidate.cjs';
|
|
5
5
|
export { $queryClient, setQueryClient } from './queryClient.cjs';
|
|
6
|
+
export { PrefetchQueriesConfig, PrefetchableQuery, prefetchQueries } from './prefetchQueries.cjs';
|
|
6
7
|
export { CreateInfiniteQueryOptions, CreateMutationOptions, CreateQueryOptions, EffectorQueryKey, InfiniteQueryResult, MutationResult, MutationStatus, QueryResult, StoreOrValue } from './types.cjs';
|
|
7
8
|
import '@tanstack/query-core';
|
|
8
9
|
import 'effector';
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { createInfiniteQuery } from './createInfiniteQuery.js';
|
|
|
3
3
|
export { createMutation } from './createMutation.js';
|
|
4
4
|
export { CreateInvalidateOptions, createInvalidate } from './createInvalidate.js';
|
|
5
5
|
export { $queryClient, setQueryClient } from './queryClient.js';
|
|
6
|
+
export { PrefetchQueriesConfig, PrefetchableQuery, prefetchQueries } from './prefetchQueries.js';
|
|
6
7
|
export { CreateInfiniteQueryOptions, CreateMutationOptions, CreateQueryOptions, EffectorQueryKey, InfiniteQueryResult, MutationResult, MutationStatus, QueryResult, StoreOrValue } from './types.js';
|
|
7
8
|
import '@tanstack/query-core';
|
|
8
9
|
import 'effector';
|
package/dist/index.js
CHANGED
|
@@ -3,5 +3,6 @@ export { createInfiniteQuery } from './createInfiniteQuery.js';
|
|
|
3
3
|
export { createMutation } from './createMutation.js';
|
|
4
4
|
export { createInvalidate } from './createInvalidate.js';
|
|
5
5
|
export { $queryClient, setQueryClient } from './queryClient.js';
|
|
6
|
+
export { prefetchQueries } from './prefetchQueries.js';
|
|
6
7
|
//# sourceMappingURL=index.js.map
|
|
7
8
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var effector = require('effector');
|
|
4
|
+
|
|
5
|
+
// src/prefetchQueries.ts
|
|
6
|
+
async function prefetchQueries(queries, config) {
|
|
7
|
+
const { scope } = config;
|
|
8
|
+
await Promise.all(queries.map((q) => effector.allSettled(q.prefetch, { scope })));
|
|
9
|
+
await Promise.all(queries.map((q) => effector.allSettled(q.mounted, { scope })));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
exports.prefetchQueries = prefetchQueries;
|
|
13
|
+
//# sourceMappingURL=prefetchQueries.cjs.map
|
|
14
|
+
//# sourceMappingURL=prefetchQueries.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/prefetchQueries.ts"],"names":["allSettled"],"mappings":";;;;;AA2DA,eAAsB,eAAA,CACpB,SACA,MAAA,EACe;AACf,EAAA,MAAM,EAAE,OAAM,GAAI,MAAA;AAClB,EAAA,MAAM,OAAA,CAAQ,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,KAAMA,mBAAA,CAAW,CAAA,CAAE,QAAA,EAAU,EAAE,KAAA,EAAO,CAAC,CAAC,CAAA;AACvE,EAAA,MAAM,OAAA,CAAQ,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,KAAMA,mBAAA,CAAW,CAAA,CAAE,OAAA,EAAS,EAAE,KAAA,EAAO,CAAC,CAAC,CAAA;AACxE","file":"prefetchQueries.cjs","sourcesContent":["import { allSettled } from 'effector'\nimport type { EventCallable, Scope } from 'effector'\n\n/**\n * Structural shape of any factory result that exposes the two SSR\n * lifecycle events. Both `QueryResult` and `InfiniteQueryResult` satisfy\n * this without forcing the caller to widen their `TData` / `TError` type\n * parameters.\n */\nexport interface PrefetchableQuery {\n prefetch: EventCallable<void>\n mounted: EventCallable<void>\n}\n\nexport interface PrefetchQueriesConfig {\n /** Scope to run the lifecycle events in. Required — `allSettled` cannot\n * await unit triggers outside of a scope. */\n scope: Scope\n}\n\n/**\n * Server-side helper that fills **both** SSR layers for a set of queries:\n *\n * 1. `await allSettled(q.prefetch, { scope })` for each query — awaits\n * `queryClient.fetchQuery(...)` so by the time the promise resolves\n * the cache holds the data.\n * 2. `await allSettled(q.mounted, { scope })` for each query — creates\n * the per-scope observer; its synchronous `getCurrentResult()`\n * dispatch reads from the now-populated cache and writes into the\n * effector stores (`$data`, `$status`, …). This is what makes\n * `serialize(scope)` ship populated stores, which in turn lets the\n * server-rendered HTML show data on first paint.\n *\n * Skipping step 2 (calling only `prefetch`) leaves the effector stores at\n * their defaults — `serialize(scope)` returns nothing useful, the\n * server-rendered HTML shows loading, and the user sees a flash on\n * hydration. This helper exists so that bug is impossible to write.\n *\n * Both phases run in parallel within themselves (`Promise.all`) but\n * sequentially across phases (`mounted` must see a populated cache).\n *\n * Snapshot the layers yourself afterwards — `dehydrate(queryClient)` for\n * the cache and `serialize(scope)` for the stores — and ship both to the\n * client (e.g. via `<HydrationBoundary state={...}>` + `<EffectorNext\n * values={...}>` or `fork({ values: ... })`).\n *\n * @example\n * ```ts\n * const queryClient = new QueryClient()\n * const scope = fork({ values: [[$queryClient, queryClient]] })\n *\n * await prefetchQueries([listQuery, pokemonQuery], { scope })\n *\n * return {\n * dehydratedQueryClient: dehydrate(queryClient),\n * serializedScope: serialize(scope),\n * }\n * ```\n */\nexport async function prefetchQueries(\n queries: ReadonlyArray<PrefetchableQuery>,\n config: PrefetchQueriesConfig,\n): Promise<void> {\n const { scope } = config\n await Promise.all(queries.map((q) => allSettled(q.prefetch, { scope })))\n await Promise.all(queries.map((q) => allSettled(q.mounted, { scope })))\n}\n"]}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Scope, EventCallable } from 'effector';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Structural shape of any factory result that exposes the two SSR
|
|
5
|
+
* lifecycle events. Both `QueryResult` and `InfiniteQueryResult` satisfy
|
|
6
|
+
* this without forcing the caller to widen their `TData` / `TError` type
|
|
7
|
+
* parameters.
|
|
8
|
+
*/
|
|
9
|
+
interface PrefetchableQuery {
|
|
10
|
+
prefetch: EventCallable<void>;
|
|
11
|
+
mounted: EventCallable<void>;
|
|
12
|
+
}
|
|
13
|
+
interface PrefetchQueriesConfig {
|
|
14
|
+
/** Scope to run the lifecycle events in. Required — `allSettled` cannot
|
|
15
|
+
* await unit triggers outside of a scope. */
|
|
16
|
+
scope: Scope;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Server-side helper that fills **both** SSR layers for a set of queries:
|
|
20
|
+
*
|
|
21
|
+
* 1. `await allSettled(q.prefetch, { scope })` for each query — awaits
|
|
22
|
+
* `queryClient.fetchQuery(...)` so by the time the promise resolves
|
|
23
|
+
* the cache holds the data.
|
|
24
|
+
* 2. `await allSettled(q.mounted, { scope })` for each query — creates
|
|
25
|
+
* the per-scope observer; its synchronous `getCurrentResult()`
|
|
26
|
+
* dispatch reads from the now-populated cache and writes into the
|
|
27
|
+
* effector stores (`$data`, `$status`, …). This is what makes
|
|
28
|
+
* `serialize(scope)` ship populated stores, which in turn lets the
|
|
29
|
+
* server-rendered HTML show data on first paint.
|
|
30
|
+
*
|
|
31
|
+
* Skipping step 2 (calling only `prefetch`) leaves the effector stores at
|
|
32
|
+
* their defaults — `serialize(scope)` returns nothing useful, the
|
|
33
|
+
* server-rendered HTML shows loading, and the user sees a flash on
|
|
34
|
+
* hydration. This helper exists so that bug is impossible to write.
|
|
35
|
+
*
|
|
36
|
+
* Both phases run in parallel within themselves (`Promise.all`) but
|
|
37
|
+
* sequentially across phases (`mounted` must see a populated cache).
|
|
38
|
+
*
|
|
39
|
+
* Snapshot the layers yourself afterwards — `dehydrate(queryClient)` for
|
|
40
|
+
* the cache and `serialize(scope)` for the stores — and ship both to the
|
|
41
|
+
* client (e.g. via `<HydrationBoundary state={...}>` + `<EffectorNext
|
|
42
|
+
* values={...}>` or `fork({ values: ... })`).
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const queryClient = new QueryClient()
|
|
47
|
+
* const scope = fork({ values: [[$queryClient, queryClient]] })
|
|
48
|
+
*
|
|
49
|
+
* await prefetchQueries([listQuery, pokemonQuery], { scope })
|
|
50
|
+
*
|
|
51
|
+
* return {
|
|
52
|
+
* dehydratedQueryClient: dehydrate(queryClient),
|
|
53
|
+
* serializedScope: serialize(scope),
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
declare function prefetchQueries(queries: ReadonlyArray<PrefetchableQuery>, config: PrefetchQueriesConfig): Promise<void>;
|
|
58
|
+
|
|
59
|
+
export { type PrefetchQueriesConfig, type PrefetchableQuery, prefetchQueries };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Scope, EventCallable } from 'effector';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Structural shape of any factory result that exposes the two SSR
|
|
5
|
+
* lifecycle events. Both `QueryResult` and `InfiniteQueryResult` satisfy
|
|
6
|
+
* this without forcing the caller to widen their `TData` / `TError` type
|
|
7
|
+
* parameters.
|
|
8
|
+
*/
|
|
9
|
+
interface PrefetchableQuery {
|
|
10
|
+
prefetch: EventCallable<void>;
|
|
11
|
+
mounted: EventCallable<void>;
|
|
12
|
+
}
|
|
13
|
+
interface PrefetchQueriesConfig {
|
|
14
|
+
/** Scope to run the lifecycle events in. Required — `allSettled` cannot
|
|
15
|
+
* await unit triggers outside of a scope. */
|
|
16
|
+
scope: Scope;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Server-side helper that fills **both** SSR layers for a set of queries:
|
|
20
|
+
*
|
|
21
|
+
* 1. `await allSettled(q.prefetch, { scope })` for each query — awaits
|
|
22
|
+
* `queryClient.fetchQuery(...)` so by the time the promise resolves
|
|
23
|
+
* the cache holds the data.
|
|
24
|
+
* 2. `await allSettled(q.mounted, { scope })` for each query — creates
|
|
25
|
+
* the per-scope observer; its synchronous `getCurrentResult()`
|
|
26
|
+
* dispatch reads from the now-populated cache and writes into the
|
|
27
|
+
* effector stores (`$data`, `$status`, …). This is what makes
|
|
28
|
+
* `serialize(scope)` ship populated stores, which in turn lets the
|
|
29
|
+
* server-rendered HTML show data on first paint.
|
|
30
|
+
*
|
|
31
|
+
* Skipping step 2 (calling only `prefetch`) leaves the effector stores at
|
|
32
|
+
* their defaults — `serialize(scope)` returns nothing useful, the
|
|
33
|
+
* server-rendered HTML shows loading, and the user sees a flash on
|
|
34
|
+
* hydration. This helper exists so that bug is impossible to write.
|
|
35
|
+
*
|
|
36
|
+
* Both phases run in parallel within themselves (`Promise.all`) but
|
|
37
|
+
* sequentially across phases (`mounted` must see a populated cache).
|
|
38
|
+
*
|
|
39
|
+
* Snapshot the layers yourself afterwards — `dehydrate(queryClient)` for
|
|
40
|
+
* the cache and `serialize(scope)` for the stores — and ship both to the
|
|
41
|
+
* client (e.g. via `<HydrationBoundary state={...}>` + `<EffectorNext
|
|
42
|
+
* values={...}>` or `fork({ values: ... })`).
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const queryClient = new QueryClient()
|
|
47
|
+
* const scope = fork({ values: [[$queryClient, queryClient]] })
|
|
48
|
+
*
|
|
49
|
+
* await prefetchQueries([listQuery, pokemonQuery], { scope })
|
|
50
|
+
*
|
|
51
|
+
* return {
|
|
52
|
+
* dehydratedQueryClient: dehydrate(queryClient),
|
|
53
|
+
* serializedScope: serialize(scope),
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
declare function prefetchQueries(queries: ReadonlyArray<PrefetchableQuery>, config: PrefetchQueriesConfig): Promise<void>;
|
|
58
|
+
|
|
59
|
+
export { type PrefetchQueriesConfig, type PrefetchableQuery, prefetchQueries };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { allSettled } from 'effector';
|
|
2
|
+
|
|
3
|
+
// src/prefetchQueries.ts
|
|
4
|
+
async function prefetchQueries(queries, config) {
|
|
5
|
+
const { scope } = config;
|
|
6
|
+
await Promise.all(queries.map((q) => allSettled(q.prefetch, { scope })));
|
|
7
|
+
await Promise.all(queries.map((q) => allSettled(q.mounted, { scope })));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export { prefetchQueries };
|
|
11
|
+
//# sourceMappingURL=prefetchQueries.js.map
|
|
12
|
+
//# sourceMappingURL=prefetchQueries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/prefetchQueries.ts"],"names":[],"mappings":";;;AA2DA,eAAsB,eAAA,CACpB,SACA,MAAA,EACe;AACf,EAAA,MAAM,EAAE,OAAM,GAAI,MAAA;AAClB,EAAA,MAAM,OAAA,CAAQ,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,KAAM,UAAA,CAAW,CAAA,CAAE,QAAA,EAAU,EAAE,KAAA,EAAO,CAAC,CAAC,CAAA;AACvE,EAAA,MAAM,OAAA,CAAQ,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,KAAM,UAAA,CAAW,CAAA,CAAE,OAAA,EAAS,EAAE,KAAA,EAAO,CAAC,CAAC,CAAA;AACxE","file":"prefetchQueries.js","sourcesContent":["import { allSettled } from 'effector'\nimport type { EventCallable, Scope } from 'effector'\n\n/**\n * Structural shape of any factory result that exposes the two SSR\n * lifecycle events. Both `QueryResult` and `InfiniteQueryResult` satisfy\n * this without forcing the caller to widen their `TData` / `TError` type\n * parameters.\n */\nexport interface PrefetchableQuery {\n prefetch: EventCallable<void>\n mounted: EventCallable<void>\n}\n\nexport interface PrefetchQueriesConfig {\n /** Scope to run the lifecycle events in. Required — `allSettled` cannot\n * await unit triggers outside of a scope. */\n scope: Scope\n}\n\n/**\n * Server-side helper that fills **both** SSR layers for a set of queries:\n *\n * 1. `await allSettled(q.prefetch, { scope })` for each query — awaits\n * `queryClient.fetchQuery(...)` so by the time the promise resolves\n * the cache holds the data.\n * 2. `await allSettled(q.mounted, { scope })` for each query — creates\n * the per-scope observer; its synchronous `getCurrentResult()`\n * dispatch reads from the now-populated cache and writes into the\n * effector stores (`$data`, `$status`, …). This is what makes\n * `serialize(scope)` ship populated stores, which in turn lets the\n * server-rendered HTML show data on first paint.\n *\n * Skipping step 2 (calling only `prefetch`) leaves the effector stores at\n * their defaults — `serialize(scope)` returns nothing useful, the\n * server-rendered HTML shows loading, and the user sees a flash on\n * hydration. This helper exists so that bug is impossible to write.\n *\n * Both phases run in parallel within themselves (`Promise.all`) but\n * sequentially across phases (`mounted` must see a populated cache).\n *\n * Snapshot the layers yourself afterwards — `dehydrate(queryClient)` for\n * the cache and `serialize(scope)` for the stores — and ship both to the\n * client (e.g. via `<HydrationBoundary state={...}>` + `<EffectorNext\n * values={...}>` or `fork({ values: ... })`).\n *\n * @example\n * ```ts\n * const queryClient = new QueryClient()\n * const scope = fork({ values: [[$queryClient, queryClient]] })\n *\n * await prefetchQueries([listQuery, pokemonQuery], { scope })\n *\n * return {\n * dehydratedQueryClient: dehydrate(queryClient),\n * serializedScope: serialize(scope),\n * }\n * ```\n */\nexport async function prefetchQueries(\n queries: ReadonlyArray<PrefetchableQuery>,\n config: PrefetchQueriesConfig,\n): Promise<void> {\n const { scope } = config\n await Promise.all(queries.map((q) => allSettled(q.prefetch, { scope })))\n await Promise.all(queries.map((q) => allSettled(q.mounted, { scope })))\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effector-tanstack-query/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Effector bindings for TanStack Query — core factories (createQuery, createMutation, createInfiniteQuery)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ilya Agarkov <ilya.al.ag@gmail.com>",
|
|
@@ -43,6 +43,12 @@
|
|
|
43
43
|
"!src/__tests__"
|
|
44
44
|
],
|
|
45
45
|
"sideEffects": false,
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@tanstack/query-core": "^5.0.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"effector": ">=23.0.0"
|
|
51
|
+
},
|
|
46
52
|
"scripts": {
|
|
47
53
|
"build": "tsup",
|
|
48
54
|
"build:watch": "tsup --watch",
|
|
@@ -50,11 +56,5 @@
|
|
|
50
56
|
"test": "vitest --run",
|
|
51
57
|
"test:watch": "vitest",
|
|
52
58
|
"test:types": "tsc --noEmit"
|
|
53
|
-
},
|
|
54
|
-
"dependencies": {
|
|
55
|
-
"@tanstack/query-core": "^5.0.0"
|
|
56
|
-
},
|
|
57
|
-
"peerDependencies": {
|
|
58
|
-
"effector": ">=23.0.0"
|
|
59
59
|
}
|
|
60
|
-
}
|
|
60
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,11 @@ export { createMutation } from './createMutation'
|
|
|
4
4
|
export { createInvalidate } from './createInvalidate'
|
|
5
5
|
export type { CreateInvalidateOptions } from './createInvalidate'
|
|
6
6
|
export { $queryClient, setQueryClient } from './queryClient'
|
|
7
|
+
export { prefetchQueries } from './prefetchQueries'
|
|
8
|
+
export type {
|
|
9
|
+
PrefetchableQuery,
|
|
10
|
+
PrefetchQueriesConfig,
|
|
11
|
+
} from './prefetchQueries'
|
|
7
12
|
export type {
|
|
8
13
|
CreateInfiniteQueryOptions,
|
|
9
14
|
CreateMutationOptions,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { allSettled } from 'effector'
|
|
2
|
+
import type { EventCallable, Scope } from 'effector'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Structural shape of any factory result that exposes the two SSR
|
|
6
|
+
* lifecycle events. Both `QueryResult` and `InfiniteQueryResult` satisfy
|
|
7
|
+
* this without forcing the caller to widen their `TData` / `TError` type
|
|
8
|
+
* parameters.
|
|
9
|
+
*/
|
|
10
|
+
export interface PrefetchableQuery {
|
|
11
|
+
prefetch: EventCallable<void>
|
|
12
|
+
mounted: EventCallable<void>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface PrefetchQueriesConfig {
|
|
16
|
+
/** Scope to run the lifecycle events in. Required — `allSettled` cannot
|
|
17
|
+
* await unit triggers outside of a scope. */
|
|
18
|
+
scope: Scope
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Server-side helper that fills **both** SSR layers for a set of queries:
|
|
23
|
+
*
|
|
24
|
+
* 1. `await allSettled(q.prefetch, { scope })` for each query — awaits
|
|
25
|
+
* `queryClient.fetchQuery(...)` so by the time the promise resolves
|
|
26
|
+
* the cache holds the data.
|
|
27
|
+
* 2. `await allSettled(q.mounted, { scope })` for each query — creates
|
|
28
|
+
* the per-scope observer; its synchronous `getCurrentResult()`
|
|
29
|
+
* dispatch reads from the now-populated cache and writes into the
|
|
30
|
+
* effector stores (`$data`, `$status`, …). This is what makes
|
|
31
|
+
* `serialize(scope)` ship populated stores, which in turn lets the
|
|
32
|
+
* server-rendered HTML show data on first paint.
|
|
33
|
+
*
|
|
34
|
+
* Skipping step 2 (calling only `prefetch`) leaves the effector stores at
|
|
35
|
+
* their defaults — `serialize(scope)` returns nothing useful, the
|
|
36
|
+
* server-rendered HTML shows loading, and the user sees a flash on
|
|
37
|
+
* hydration. This helper exists so that bug is impossible to write.
|
|
38
|
+
*
|
|
39
|
+
* Both phases run in parallel within themselves (`Promise.all`) but
|
|
40
|
+
* sequentially across phases (`mounted` must see a populated cache).
|
|
41
|
+
*
|
|
42
|
+
* Snapshot the layers yourself afterwards — `dehydrate(queryClient)` for
|
|
43
|
+
* the cache and `serialize(scope)` for the stores — and ship both to the
|
|
44
|
+
* client (e.g. via `<HydrationBoundary state={...}>` + `<EffectorNext
|
|
45
|
+
* values={...}>` or `fork({ values: ... })`).
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* const queryClient = new QueryClient()
|
|
50
|
+
* const scope = fork({ values: [[$queryClient, queryClient]] })
|
|
51
|
+
*
|
|
52
|
+
* await prefetchQueries([listQuery, pokemonQuery], { scope })
|
|
53
|
+
*
|
|
54
|
+
* return {
|
|
55
|
+
* dehydratedQueryClient: dehydrate(queryClient),
|
|
56
|
+
* serializedScope: serialize(scope),
|
|
57
|
+
* }
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export async function prefetchQueries(
|
|
61
|
+
queries: ReadonlyArray<PrefetchableQuery>,
|
|
62
|
+
config: PrefetchQueriesConfig,
|
|
63
|
+
): Promise<void> {
|
|
64
|
+
const { scope } = config
|
|
65
|
+
await Promise.all(queries.map((q) => allSettled(q.prefetch, { scope })))
|
|
66
|
+
await Promise.all(queries.map((q) => allSettled(q.mounted, { scope })))
|
|
67
|
+
}
|