@btst/stack 3.0.0-rc.1 → 3.0.0-rc.2
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/README.md +4 -8
- package/dist/next/index.d.cts +13 -7
- package/dist/next/index.d.mts +13 -7
- package/dist/next/index.d.ts +13 -7
- package/dist/packages/stack/src/next/page.cjs +8 -6
- package/dist/packages/stack/src/next/page.mjs +8 -6
- package/dist/packages/stack/src/plugins/comments/client/components/comment-thread.cjs +1 -1
- package/dist/packages/stack/src/plugins/comments/client/components/comment-thread.mjs +1 -1
- package/dist/packages/stack/src/react-router/page.cjs +17 -11
- package/dist/packages/stack/src/react-router/page.mjs +17 -11
- package/dist/packages/stack/src/tanstack/page.cjs +5 -3
- package/dist/packages/stack/src/tanstack/page.mjs +5 -3
- package/dist/plugins/blog/api/index.d.cts +2 -2
- package/dist/plugins/blog/api/index.d.mts +2 -2
- package/dist/plugins/blog/api/index.d.ts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
- package/dist/plugins/blog/client/index.d.cts +2 -2
- package/dist/plugins/blog/client/index.d.mts +2 -2
- package/dist/plugins/blog/client/index.d.ts +2 -2
- package/dist/plugins/blog/query-keys.d.cts +2 -2
- package/dist/plugins/blog/query-keys.d.mts +2 -2
- package/dist/plugins/blog/query-keys.d.ts +2 -2
- package/dist/plugins/cms/api/index.d.cts +1 -1
- package/dist/plugins/cms/api/index.d.mts +1 -1
- package/dist/plugins/cms/api/index.d.ts +1 -1
- package/dist/plugins/cms/query-keys.d.cts +1 -1
- package/dist/plugins/cms/query-keys.d.mts +1 -1
- package/dist/plugins/cms/query-keys.d.ts +1 -1
- package/dist/plugins/comments/client/components/index.d.cts +5 -0
- package/dist/plugins/comments/client/components/index.d.mts +5 -0
- package/dist/plugins/comments/client/components/index.d.ts +5 -0
- package/dist/plugins/kanban/api/index.d.cts +1 -1
- package/dist/plugins/kanban/api/index.d.mts +1 -1
- package/dist/plugins/kanban/api/index.d.ts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.cts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.mts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.ts +1 -1
- package/dist/plugins/kanban/query-keys.d.cts +1 -1
- package/dist/plugins/kanban/query-keys.d.mts +1 -1
- package/dist/plugins/kanban/query-keys.d.ts +1 -1
- package/dist/plugins/ui-builder/client/hooks/index.d.cts +1 -1
- package/dist/plugins/ui-builder/client/hooks/index.d.mts +1 -1
- package/dist/plugins/ui-builder/client/hooks/index.d.ts +1 -1
- package/dist/plugins/ui-builder/client/index.d.cts +1 -1
- package/dist/plugins/ui-builder/client/index.d.mts +1 -1
- package/dist/plugins/ui-builder/client/index.d.ts +1 -1
- package/dist/plugins/ui-builder/query-keys.d.cts +1 -1
- package/dist/plugins/ui-builder/query-keys.d.mts +1 -1
- package/dist/plugins/ui-builder/query-keys.d.ts +1 -1
- package/dist/react-router/index.d.cts +14 -4
- package/dist/react-router/index.d.mts +14 -4
- package/dist/react-router/index.d.ts +14 -4
- package/dist/shared/{stack.H5B8miAV.d.cts → stack.CKGjOI3Z.d.cts} +11 -11
- package/dist/shared/{stack.80-y2TTR.d.mts → stack.DZHEHd-S.d.mts} +11 -11
- package/dist/shared/{stack.CRelKJfH.d.ts → stack.DseBJmGF.d.ts} +4 -4
- package/dist/shared/{stack.INBa9-TO.d.cts → stack.DzwZw-Ck.d.cts} +4 -4
- package/dist/shared/{stack.DrUBX-QE.d.mts → stack.EOPVnJrF.d.cts} +6 -1
- package/dist/shared/{stack.DrUBX-QE.d.ts → stack.EOPVnJrF.d.mts} +6 -1
- package/dist/shared/{stack.DrUBX-QE.d.cts → stack.EOPVnJrF.d.ts} +6 -1
- package/dist/shared/{stack.Bk5UB_BT.d.ts → stack.WELCDuMs.d.ts} +11 -11
- package/dist/shared/{stack.3di7vQs6.d.mts → stack.oVIdV7XS.d.mts} +4 -4
- package/dist/tanstack/index.d.cts +20 -12
- package/dist/tanstack/index.d.mts +20 -12
- package/dist/tanstack/index.d.ts +20 -12
- package/package.json +6 -6
- package/src/__tests__/entry-factories.test.tsx +104 -4
- package/src/__tests__/package-metadata.test.ts +21 -0
- package/src/next/index.tsx +2 -0
- package/src/next/page.tsx +17 -9
- package/src/plugins/comments/__tests__/client-sweep.test.tsx +23 -0
- package/src/plugins/comments/client/components/comment-thread.tsx +6 -2
- package/src/react-router/index.tsx +1 -0
- package/src/react-router/page.tsx +34 -12
- package/src/shared/entry-factories.ts +9 -0
- package/src/tanstack/index.tsx +2 -0
- package/src/tanstack/page.tsx +28 -11
- package/dist/shared/{stack.BvxHSZYT.d.cts → stack.BsmDN8r9.d.cts} +3 -3
- package/dist/shared/{stack.eq5eg1yt.d.cts → stack.CuI_-z-_.d.cts} +22 -22
- package/dist/shared/{stack.B0muOwUF.d.mts → stack.D18Dq3ve.d.cts} +7 -7
- package/dist/shared/{stack.B0muOwUF.d.ts → stack.D18Dq3ve.d.mts} +7 -7
- package/dist/shared/{stack.B0muOwUF.d.cts → stack.D18Dq3ve.d.ts} +7 -7
- package/dist/shared/{stack.Dj04W2c3.d.mts → stack.D2RVkQD8.d.mts} +22 -22
- package/dist/shared/{stack.B-hEIoUw.d.ts → stack.DOzsgzkY.d.ts} +3 -3
- package/dist/shared/{stack.CMbX8Q5C.d.ts → stack.DXrOtuek.d.ts} +22 -22
- package/dist/shared/{stack.f8Vke-eH.d.mts → stack.NGKtwA6S.d.mts} +3 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as StackRequestHandler, G as GetStackClient } from '../shared/stack.
|
|
1
|
+
import { S as StackRequestHandler, G as GetStackClient, R as ResolveStackClient } from '../shared/stack.EOPVnJrF.cjs';
|
|
2
2
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
3
|
import { QueryClient } from '@tanstack/react-query';
|
|
4
4
|
import { S as StackRouterConfig } from '../shared/stack.aqVZ6PEW.cjs';
|
|
@@ -38,9 +38,23 @@ declare function toTanStackHandlers(handler: StackRequestHandler): {
|
|
|
38
38
|
}) => Response | Promise<Response>;
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
interface
|
|
41
|
+
interface TanStackPageLoaderArgs<TContext = unknown> {
|
|
42
|
+
/** Parameters for the TanStack catch-all route. */
|
|
43
|
+
params: {
|
|
44
|
+
_splat?: string;
|
|
45
|
+
};
|
|
46
|
+
/** The current TanStack router context. */
|
|
47
|
+
context: TContext;
|
|
48
|
+
}
|
|
49
|
+
interface CreateTanStackPageOptions<TContext = unknown> {
|
|
42
50
|
/** Returns the stack client for a given QueryClient (`lib/stack-client`). */
|
|
43
51
|
getStackClient: GetStackClient;
|
|
52
|
+
/**
|
|
53
|
+
* Resolves the stack client for the isomorphic route loader. Use an
|
|
54
|
+
* isomorphic adapter when request-aware setup differs on the server and
|
|
55
|
+
* client. Defaults to `getStackClient`.
|
|
56
|
+
*/
|
|
57
|
+
getLoaderStackClient?: ResolveStackClient<TanStackPageLoaderArgs<TContext>>;
|
|
44
58
|
/**
|
|
45
59
|
* Returns the QueryClient for the current context. Defaults to the
|
|
46
60
|
* `queryClient` from the router context (set up by
|
|
@@ -48,12 +62,6 @@ interface CreateTanStackPageOptions {
|
|
|
48
62
|
*/
|
|
49
63
|
getQueryClient?: () => QueryClient;
|
|
50
64
|
}
|
|
51
|
-
interface TanStackPageLoaderArgs {
|
|
52
|
-
params: {
|
|
53
|
-
_splat?: string;
|
|
54
|
-
};
|
|
55
|
-
context?: unknown;
|
|
56
|
-
}
|
|
57
65
|
/**
|
|
58
66
|
* Creates the route options for the TanStack Start catch-all page, to spread
|
|
59
67
|
* into `createFileRoute("/pages/$")(...)`: SSR prefetch via `route.loader()`,
|
|
@@ -73,10 +81,10 @@ interface TanStackPageLoaderArgs {
|
|
|
73
81
|
* );
|
|
74
82
|
* ```
|
|
75
83
|
*/
|
|
76
|
-
declare function createTanStackPageOptions(options: CreateTanStackPageOptions): {
|
|
84
|
+
declare function createTanStackPageOptions<TContext = unknown>(options: CreateTanStackPageOptions<TContext>): {
|
|
77
85
|
readonly ssr: true;
|
|
78
86
|
readonly component: () => react_jsx_runtime.JSX.Element;
|
|
79
|
-
readonly loader: (
|
|
87
|
+
readonly loader: (loaderArgs: TanStackPageLoaderArgs<TContext>) => Promise<{
|
|
80
88
|
meta: any;
|
|
81
89
|
}>;
|
|
82
90
|
readonly head: ({ loaderData }: {
|
|
@@ -106,5 +114,5 @@ declare function createTanStackPageOptions(options: CreateTanStackPageOptions):
|
|
|
106
114
|
*/
|
|
107
115
|
declare function tanstackRouter(): StackRouterConfig;
|
|
108
116
|
|
|
109
|
-
export { GetStackClient, StackRequestHandler, createTanStackPageOptions, tanstackRouter, toTanStackHandlers };
|
|
110
|
-
export type { CreateTanStackPageOptions };
|
|
117
|
+
export { GetStackClient, ResolveStackClient, StackRequestHandler, createTanStackPageOptions, tanstackRouter, toTanStackHandlers };
|
|
118
|
+
export type { CreateTanStackPageOptions, TanStackPageLoaderArgs };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as StackRequestHandler, G as GetStackClient } from '../shared/stack.
|
|
1
|
+
import { S as StackRequestHandler, G as GetStackClient, R as ResolveStackClient } from '../shared/stack.EOPVnJrF.mjs';
|
|
2
2
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
3
|
import { QueryClient } from '@tanstack/react-query';
|
|
4
4
|
import { S as StackRouterConfig } from '../shared/stack.aqVZ6PEW.mjs';
|
|
@@ -38,9 +38,23 @@ declare function toTanStackHandlers(handler: StackRequestHandler): {
|
|
|
38
38
|
}) => Response | Promise<Response>;
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
interface
|
|
41
|
+
interface TanStackPageLoaderArgs<TContext = unknown> {
|
|
42
|
+
/** Parameters for the TanStack catch-all route. */
|
|
43
|
+
params: {
|
|
44
|
+
_splat?: string;
|
|
45
|
+
};
|
|
46
|
+
/** The current TanStack router context. */
|
|
47
|
+
context: TContext;
|
|
48
|
+
}
|
|
49
|
+
interface CreateTanStackPageOptions<TContext = unknown> {
|
|
42
50
|
/** Returns the stack client for a given QueryClient (`lib/stack-client`). */
|
|
43
51
|
getStackClient: GetStackClient;
|
|
52
|
+
/**
|
|
53
|
+
* Resolves the stack client for the isomorphic route loader. Use an
|
|
54
|
+
* isomorphic adapter when request-aware setup differs on the server and
|
|
55
|
+
* client. Defaults to `getStackClient`.
|
|
56
|
+
*/
|
|
57
|
+
getLoaderStackClient?: ResolveStackClient<TanStackPageLoaderArgs<TContext>>;
|
|
44
58
|
/**
|
|
45
59
|
* Returns the QueryClient for the current context. Defaults to the
|
|
46
60
|
* `queryClient` from the router context (set up by
|
|
@@ -48,12 +62,6 @@ interface CreateTanStackPageOptions {
|
|
|
48
62
|
*/
|
|
49
63
|
getQueryClient?: () => QueryClient;
|
|
50
64
|
}
|
|
51
|
-
interface TanStackPageLoaderArgs {
|
|
52
|
-
params: {
|
|
53
|
-
_splat?: string;
|
|
54
|
-
};
|
|
55
|
-
context?: unknown;
|
|
56
|
-
}
|
|
57
65
|
/**
|
|
58
66
|
* Creates the route options for the TanStack Start catch-all page, to spread
|
|
59
67
|
* into `createFileRoute("/pages/$")(...)`: SSR prefetch via `route.loader()`,
|
|
@@ -73,10 +81,10 @@ interface TanStackPageLoaderArgs {
|
|
|
73
81
|
* );
|
|
74
82
|
* ```
|
|
75
83
|
*/
|
|
76
|
-
declare function createTanStackPageOptions(options: CreateTanStackPageOptions): {
|
|
84
|
+
declare function createTanStackPageOptions<TContext = unknown>(options: CreateTanStackPageOptions<TContext>): {
|
|
77
85
|
readonly ssr: true;
|
|
78
86
|
readonly component: () => react_jsx_runtime.JSX.Element;
|
|
79
|
-
readonly loader: (
|
|
87
|
+
readonly loader: (loaderArgs: TanStackPageLoaderArgs<TContext>) => Promise<{
|
|
80
88
|
meta: any;
|
|
81
89
|
}>;
|
|
82
90
|
readonly head: ({ loaderData }: {
|
|
@@ -106,5 +114,5 @@ declare function createTanStackPageOptions(options: CreateTanStackPageOptions):
|
|
|
106
114
|
*/
|
|
107
115
|
declare function tanstackRouter(): StackRouterConfig;
|
|
108
116
|
|
|
109
|
-
export { GetStackClient, StackRequestHandler, createTanStackPageOptions, tanstackRouter, toTanStackHandlers };
|
|
110
|
-
export type { CreateTanStackPageOptions };
|
|
117
|
+
export { GetStackClient, ResolveStackClient, StackRequestHandler, createTanStackPageOptions, tanstackRouter, toTanStackHandlers };
|
|
118
|
+
export type { CreateTanStackPageOptions, TanStackPageLoaderArgs };
|
package/dist/tanstack/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as StackRequestHandler, G as GetStackClient } from '../shared/stack.
|
|
1
|
+
import { S as StackRequestHandler, G as GetStackClient, R as ResolveStackClient } from '../shared/stack.EOPVnJrF.js';
|
|
2
2
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
3
|
import { QueryClient } from '@tanstack/react-query';
|
|
4
4
|
import { S as StackRouterConfig } from '../shared/stack.aqVZ6PEW.js';
|
|
@@ -38,9 +38,23 @@ declare function toTanStackHandlers(handler: StackRequestHandler): {
|
|
|
38
38
|
}) => Response | Promise<Response>;
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
interface
|
|
41
|
+
interface TanStackPageLoaderArgs<TContext = unknown> {
|
|
42
|
+
/** Parameters for the TanStack catch-all route. */
|
|
43
|
+
params: {
|
|
44
|
+
_splat?: string;
|
|
45
|
+
};
|
|
46
|
+
/** The current TanStack router context. */
|
|
47
|
+
context: TContext;
|
|
48
|
+
}
|
|
49
|
+
interface CreateTanStackPageOptions<TContext = unknown> {
|
|
42
50
|
/** Returns the stack client for a given QueryClient (`lib/stack-client`). */
|
|
43
51
|
getStackClient: GetStackClient;
|
|
52
|
+
/**
|
|
53
|
+
* Resolves the stack client for the isomorphic route loader. Use an
|
|
54
|
+
* isomorphic adapter when request-aware setup differs on the server and
|
|
55
|
+
* client. Defaults to `getStackClient`.
|
|
56
|
+
*/
|
|
57
|
+
getLoaderStackClient?: ResolveStackClient<TanStackPageLoaderArgs<TContext>>;
|
|
44
58
|
/**
|
|
45
59
|
* Returns the QueryClient for the current context. Defaults to the
|
|
46
60
|
* `queryClient` from the router context (set up by
|
|
@@ -48,12 +62,6 @@ interface CreateTanStackPageOptions {
|
|
|
48
62
|
*/
|
|
49
63
|
getQueryClient?: () => QueryClient;
|
|
50
64
|
}
|
|
51
|
-
interface TanStackPageLoaderArgs {
|
|
52
|
-
params: {
|
|
53
|
-
_splat?: string;
|
|
54
|
-
};
|
|
55
|
-
context?: unknown;
|
|
56
|
-
}
|
|
57
65
|
/**
|
|
58
66
|
* Creates the route options for the TanStack Start catch-all page, to spread
|
|
59
67
|
* into `createFileRoute("/pages/$")(...)`: SSR prefetch via `route.loader()`,
|
|
@@ -73,10 +81,10 @@ interface TanStackPageLoaderArgs {
|
|
|
73
81
|
* );
|
|
74
82
|
* ```
|
|
75
83
|
*/
|
|
76
|
-
declare function createTanStackPageOptions(options: CreateTanStackPageOptions): {
|
|
84
|
+
declare function createTanStackPageOptions<TContext = unknown>(options: CreateTanStackPageOptions<TContext>): {
|
|
77
85
|
readonly ssr: true;
|
|
78
86
|
readonly component: () => react_jsx_runtime.JSX.Element;
|
|
79
|
-
readonly loader: (
|
|
87
|
+
readonly loader: (loaderArgs: TanStackPageLoaderArgs<TContext>) => Promise<{
|
|
80
88
|
meta: any;
|
|
81
89
|
}>;
|
|
82
90
|
readonly head: ({ loaderData }: {
|
|
@@ -106,5 +114,5 @@ declare function createTanStackPageOptions(options: CreateTanStackPageOptions):
|
|
|
106
114
|
*/
|
|
107
115
|
declare function tanstackRouter(): StackRouterConfig;
|
|
108
116
|
|
|
109
|
-
export { GetStackClient, StackRequestHandler, createTanStackPageOptions, tanstackRouter, toTanStackHandlers };
|
|
110
|
-
export type { CreateTanStackPageOptions };
|
|
117
|
+
export { GetStackClient, ResolveStackClient, StackRequestHandler, createTanStackPageOptions, tanstackRouter, toTanStackHandlers };
|
|
118
|
+
export type { CreateTanStackPageOptions, TanStackPageLoaderArgs };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btst/stack",
|
|
3
|
-
"version": "3.0.0-rc.
|
|
3
|
+
"version": "3.0.0-rc.2",
|
|
4
4
|
"description": "A composable, plugin-based library for building full-stack applications.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -833,7 +833,7 @@
|
|
|
833
833
|
}
|
|
834
834
|
},
|
|
835
835
|
"dependencies": {
|
|
836
|
-
"@btst/db": "2.2.
|
|
836
|
+
"@btst/db": "2.2.3",
|
|
837
837
|
"@milkdown/crepe": "^7.17.1",
|
|
838
838
|
"@milkdown/kit": "^7.17.1",
|
|
839
839
|
"remend": "^1.0.1",
|
|
@@ -843,7 +843,7 @@
|
|
|
843
843
|
"@ai-sdk/react": ">=2.0.0",
|
|
844
844
|
"@aws-sdk/client-s3": ">=3.0.0",
|
|
845
845
|
"@aws-sdk/s3-request-presigner": ">=3.0.0",
|
|
846
|
-
"@btst/yar": ">=1.3.
|
|
846
|
+
"@btst/yar": ">=1.3.2",
|
|
847
847
|
"@hookform/resolvers": ">=5.0.0",
|
|
848
848
|
"@radix-ui/react-dialog": ">=1.1.0",
|
|
849
849
|
"@radix-ui/react-label": ">=2.1.0",
|
|
@@ -854,7 +854,7 @@
|
|
|
854
854
|
"@tanstack/react-router": ">=1.0.0",
|
|
855
855
|
"@vercel/blob": ">=0.14.0",
|
|
856
856
|
"ai": ">=5.0.0",
|
|
857
|
-
"better-call": "
|
|
857
|
+
"better-call": "1.3.6",
|
|
858
858
|
"class-variance-authority": ">=0.7.0",
|
|
859
859
|
"clsx": ">=2.1.0",
|
|
860
860
|
"cmdk": ">=1.1.0",
|
|
@@ -903,8 +903,8 @@
|
|
|
903
903
|
"@ai-sdk/react": "^2.0.94",
|
|
904
904
|
"@aws-sdk/client-s3": "^3.1011.0",
|
|
905
905
|
"@aws-sdk/s3-request-presigner": "^3.1011.0",
|
|
906
|
-
"@btst/adapter-memory": "2.2.
|
|
907
|
-
"@btst/yar": "1.3.
|
|
906
|
+
"@btst/adapter-memory": "2.2.3",
|
|
907
|
+
"@btst/yar": "1.3.2",
|
|
908
908
|
"@tanstack/react-router": "1.168.10",
|
|
909
909
|
"@types/react": "^19.0.0",
|
|
910
910
|
"@types/react-dom": "^19.0.0",
|
|
@@ -23,6 +23,14 @@ function makeQueryClient() {
|
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
function reactRouterLoaderArgs(splat: string) {
|
|
27
|
+
return {
|
|
28
|
+
request: new Request(`http://test.local/${splat}`),
|
|
29
|
+
params: { "*": splat },
|
|
30
|
+
context: undefined,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
26
34
|
const okHandler = async (request: Request) =>
|
|
27
35
|
new Response(`ok:${request.method}`);
|
|
28
36
|
|
|
@@ -96,6 +104,28 @@ describe("createNextPage", () => {
|
|
|
96
104
|
expect(html).toContain("blog page");
|
|
97
105
|
});
|
|
98
106
|
|
|
107
|
+
it("supports an async request-aware stack client when rendering", async () => {
|
|
108
|
+
const queryClient = makeQueryClient();
|
|
109
|
+
const pageProps = { params: params(["account"]) };
|
|
110
|
+
const page = createNextPage({
|
|
111
|
+
getStackClient: async (currentQueryClient, currentPageProps) => {
|
|
112
|
+
const { all } = await currentPageProps.params;
|
|
113
|
+
return makeStackClient({
|
|
114
|
+
[`/${all?.join("/")}`]: {
|
|
115
|
+
PageComponent: () => <div>request-aware page</div>,
|
|
116
|
+
},
|
|
117
|
+
})(currentQueryClient);
|
|
118
|
+
},
|
|
119
|
+
getQueryClient: () => queryClient,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const element = await page.Page(pageProps);
|
|
123
|
+
const html = renderToString(
|
|
124
|
+
<QueryClientProvider client={queryClient}>{element}</QueryClientProvider>,
|
|
125
|
+
);
|
|
126
|
+
expect(html).toContain("request-aware page");
|
|
127
|
+
});
|
|
128
|
+
|
|
99
129
|
it("calls notFound when no route matches", async () => {
|
|
100
130
|
const notFound = vi.fn(() => {
|
|
101
131
|
throw new Error("NEXT_NOT_FOUND_TEST");
|
|
@@ -140,6 +170,26 @@ describe("createNextPage", () => {
|
|
|
140
170
|
});
|
|
141
171
|
});
|
|
142
172
|
|
|
173
|
+
it("generateMetadata awaits a request-aware stack client", async () => {
|
|
174
|
+
const page = createNextPage({
|
|
175
|
+
getStackClient: async (queryClient, { params: pageParams }) => {
|
|
176
|
+
const { all } = await pageParams;
|
|
177
|
+
return makeStackClient({
|
|
178
|
+
[`/${all?.join("/")}`]: {
|
|
179
|
+
meta: () => [{ name: "title", content: "Request Metadata" }],
|
|
180
|
+
},
|
|
181
|
+
})(queryClient);
|
|
182
|
+
},
|
|
183
|
+
getQueryClient: makeQueryClient,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const metadata = await page.generateMetadata({
|
|
187
|
+
params: params(["account"]),
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
expect(metadata).toMatchObject({ title: "Request Metadata" });
|
|
191
|
+
});
|
|
192
|
+
|
|
143
193
|
it("generateMetadata returns {} without running the loader when the route has no meta", async () => {
|
|
144
194
|
const loader = vi.fn();
|
|
145
195
|
const page = createNextPage({
|
|
@@ -170,6 +220,35 @@ describe("createNextPage", () => {
|
|
|
170
220
|
});
|
|
171
221
|
|
|
172
222
|
describe("createReactRouterPage", () => {
|
|
223
|
+
it("creates an async request-aware loader without changing the render client", async () => {
|
|
224
|
+
const queryClient = makeQueryClient();
|
|
225
|
+
const page = createReactRouterPage({
|
|
226
|
+
getStackClient: makeStackClient({}),
|
|
227
|
+
getQueryClient: () => queryClient,
|
|
228
|
+
});
|
|
229
|
+
const loader = page.createLoader<{ role: string }>(
|
|
230
|
+
async (currentQueryClient, { request, context }) => {
|
|
231
|
+
const role = context.role;
|
|
232
|
+
const requestRole = request.headers.get("x-role");
|
|
233
|
+
return makeStackClient({
|
|
234
|
+
"/account": {
|
|
235
|
+
meta: () => [{ name: "viewer", content: `${role}:${requestRole}` }],
|
|
236
|
+
},
|
|
237
|
+
})(currentQueryClient);
|
|
238
|
+
},
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const data = await loader({
|
|
242
|
+
params: { "*": "account" },
|
|
243
|
+
request: new Request("http://test.local/account", {
|
|
244
|
+
headers: { "x-role": "member" },
|
|
245
|
+
}),
|
|
246
|
+
context: { role: "admin" },
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
expect(data.meta).toEqual([{ name: "viewer", content: "admin:member" }]);
|
|
250
|
+
});
|
|
251
|
+
|
|
173
252
|
it("loader prefetches and returns path, dehydratedState and meta", async () => {
|
|
174
253
|
const queryClient = makeQueryClient();
|
|
175
254
|
const page = createReactRouterPage({
|
|
@@ -187,7 +266,7 @@ describe("createReactRouterPage", () => {
|
|
|
187
266
|
getQueryClient: () => queryClient,
|
|
188
267
|
});
|
|
189
268
|
|
|
190
|
-
const data = await page.loader(
|
|
269
|
+
const data = await page.loader(reactRouterLoaderArgs("blog"));
|
|
191
270
|
expect(data.path).toBe("/blog");
|
|
192
271
|
expect(data.meta).toEqual([{ name: "title", content: "Blog" }]);
|
|
193
272
|
expect(data.dehydratedState.queries).toHaveLength(1);
|
|
@@ -215,7 +294,7 @@ describe("createReactRouterPage", () => {
|
|
|
215
294
|
getQueryClient: () => queryClient,
|
|
216
295
|
});
|
|
217
296
|
|
|
218
|
-
const data = await page.loader(
|
|
297
|
+
const data = await page.loader(reactRouterLoaderArgs("broken"));
|
|
219
298
|
const errored = data.dehydratedState.queries.find(
|
|
220
299
|
(q) => q.state.status === "error",
|
|
221
300
|
);
|
|
@@ -244,12 +323,33 @@ describe("createReactRouterPage", () => {
|
|
|
244
323
|
});
|
|
245
324
|
|
|
246
325
|
expect(page.ErrorBoundary).toBe(CustomErrorBoundary);
|
|
247
|
-
const data = await page.loader(
|
|
326
|
+
const data = await page.loader(reactRouterLoaderArgs("broken"));
|
|
248
327
|
expect(data.dehydratedState.queries).toHaveLength(0);
|
|
249
328
|
});
|
|
250
329
|
});
|
|
251
330
|
|
|
252
331
|
describe("createTanStackPageOptions", () => {
|
|
332
|
+
it("awaits a context-aware stack client for isomorphic loaders", async () => {
|
|
333
|
+
const queryClient = makeQueryClient();
|
|
334
|
+
const options = createTanStackPageOptions<{ role: string }>({
|
|
335
|
+
getStackClient: makeStackClient({}),
|
|
336
|
+
getQueryClient: () => queryClient,
|
|
337
|
+
getLoaderStackClient: async (currentQueryClient, { context }) =>
|
|
338
|
+
makeStackClient({
|
|
339
|
+
"/account": {
|
|
340
|
+
meta: () => [{ title: `Account for ${context.role}` }],
|
|
341
|
+
},
|
|
342
|
+
})(currentQueryClient),
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
const data = await options.loader({
|
|
346
|
+
params: { _splat: "account" },
|
|
347
|
+
context: { role: "admin" },
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
expect(data).toEqual({ meta: [{ title: "Account for admin" }] });
|
|
351
|
+
});
|
|
352
|
+
|
|
253
353
|
it("loader throws notFound() when no route matches", async () => {
|
|
254
354
|
const options = createTanStackPageOptions({
|
|
255
355
|
getStackClient: makeStackClient({}),
|
|
@@ -257,7 +357,7 @@ describe("createTanStackPageOptions", () => {
|
|
|
257
357
|
});
|
|
258
358
|
|
|
259
359
|
await expect(
|
|
260
|
-
options.loader({ params: { _splat: "missing" } }),
|
|
360
|
+
options.loader({ params: { _splat: "missing" }, context: undefined }),
|
|
261
361
|
).rejects.toMatchObject({ isNotFound: true });
|
|
262
362
|
});
|
|
263
363
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
|
|
5
|
+
describe("published dependency alignment", () => {
|
|
6
|
+
it("uses the Better DB release that shares the Better Auth 1.6.16 cohort", async () => {
|
|
7
|
+
const manifest = JSON.parse(
|
|
8
|
+
await readFile(resolve("package.json"), "utf8"),
|
|
9
|
+
) as {
|
|
10
|
+
dependencies?: Record<string, string>;
|
|
11
|
+
devDependencies?: Record<string, string>;
|
|
12
|
+
peerDependencies?: Record<string, string>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
expect(manifest.dependencies?.["@btst/db"]).toBe("2.2.3");
|
|
16
|
+
expect(manifest.devDependencies?.["@btst/adapter-memory"]).toBe("2.2.3");
|
|
17
|
+
expect(manifest.devDependencies?.["@btst/yar"]).toBe("1.3.2");
|
|
18
|
+
expect(manifest.peerDependencies?.["@btst/yar"]).toBe(">=1.3.2");
|
|
19
|
+
expect(manifest.peerDependencies?.["better-call"]).toBe("1.3.6");
|
|
20
|
+
});
|
|
21
|
+
});
|
package/src/next/index.tsx
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
export { toNextRouteHandlers } from "./handlers";
|
|
2
2
|
export {
|
|
3
3
|
type CreateNextPageOptions,
|
|
4
|
+
type GetNextStackClient,
|
|
4
5
|
type NextPageProps,
|
|
5
6
|
createNextPage,
|
|
6
7
|
} from "./page";
|
|
7
8
|
export { nextRouter } from "./router";
|
|
8
9
|
export type {
|
|
9
10
|
GetStackClient,
|
|
11
|
+
ResolveStackClient,
|
|
10
12
|
StackRequestHandler,
|
|
11
13
|
} from "../shared/entry-factories";
|
package/src/next/page.tsx
CHANGED
|
@@ -6,7 +6,7 @@ import type { ReactNode } from "react";
|
|
|
6
6
|
import { metaElementsToObject } from "../client/meta-utils";
|
|
7
7
|
import { normalizePath } from "../client/path-utils";
|
|
8
8
|
import {
|
|
9
|
-
type
|
|
9
|
+
type ResolveStackClient,
|
|
10
10
|
stackDehydrateOptions,
|
|
11
11
|
} from "../shared/entry-factories";
|
|
12
12
|
|
|
@@ -14,9 +14,15 @@ export interface NextPageProps {
|
|
|
14
14
|
params: Promise<{ all?: string[] }>;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/** Resolves the stack client for a Next.js page or metadata request. */
|
|
18
|
+
export type GetNextStackClient = ResolveStackClient<NextPageProps>;
|
|
19
|
+
|
|
17
20
|
export interface CreateNextPageOptions {
|
|
18
|
-
/**
|
|
19
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Returns the stack client for the current request. May be async and receives
|
|
23
|
+
* the page props so request-aware clients can resolve request-local state.
|
|
24
|
+
*/
|
|
25
|
+
getStackClient: GetNextStackClient;
|
|
20
26
|
/** Returns the QueryClient for the current context (`lib/query-client`). */
|
|
21
27
|
getQueryClient: () => QueryClient;
|
|
22
28
|
/**
|
|
@@ -63,11 +69,13 @@ export function createNextPage(options: CreateNextPageOptions) {
|
|
|
63
69
|
dehydrateOptions = stackDehydrateOptions,
|
|
64
70
|
} = options;
|
|
65
71
|
|
|
66
|
-
async function Page(
|
|
72
|
+
async function Page(pageProps: NextPageProps) {
|
|
73
|
+
const { params } = pageProps;
|
|
67
74
|
const pathParams = await params;
|
|
68
75
|
const path = normalizePath(pathParams?.all);
|
|
69
76
|
const queryClient = getQueryClient();
|
|
70
|
-
const
|
|
77
|
+
const stackClient = await getStackClient(queryClient, pageProps);
|
|
78
|
+
const route = stackClient.router.getRoute(path);
|
|
71
79
|
|
|
72
80
|
if (route?.loader) {
|
|
73
81
|
await route.loader();
|
|
@@ -82,13 +90,13 @@ export function createNextPage(options: CreateNextPageOptions) {
|
|
|
82
90
|
);
|
|
83
91
|
}
|
|
84
92
|
|
|
85
|
-
async function generateMetadata({
|
|
86
|
-
params
|
|
87
|
-
}: NextPageProps): Promise<Metadata> {
|
|
93
|
+
async function generateMetadata(pageProps: NextPageProps): Promise<Metadata> {
|
|
94
|
+
const { params } = pageProps;
|
|
88
95
|
const pathParams = await params;
|
|
89
96
|
const path = normalizePath(pathParams?.all);
|
|
90
97
|
const queryClient = getQueryClient();
|
|
91
|
-
const
|
|
98
|
+
const stackClient = await getStackClient(queryClient, pageProps);
|
|
99
|
+
const route = stackClient.router.getRoute(path);
|
|
92
100
|
|
|
93
101
|
if (!route) {
|
|
94
102
|
return notFound();
|
|
@@ -496,6 +496,29 @@ describe("CommentThread provider wiring", () => {
|
|
|
496
496
|
?.getAttribute("href"),
|
|
497
497
|
).toBe("/sign-in");
|
|
498
498
|
});
|
|
499
|
+
|
|
500
|
+
it("prefers the per-thread login href when unauthenticated", async () => {
|
|
501
|
+
await render(
|
|
502
|
+
<StackProvider
|
|
503
|
+
basePath="/pages"
|
|
504
|
+
api={{ baseURL: "http://provider.local", basePath: "/api/stack" }}
|
|
505
|
+
auth={{ getIdentity: () => null, loginPath: "/sign-in" }}
|
|
506
|
+
>
|
|
507
|
+
<CommentThread
|
|
508
|
+
resourceId="post-1"
|
|
509
|
+
resourceType="blog-post"
|
|
510
|
+
loginHref="/sign-in?redirectTo=%2Fblog%2Fpost-1%23comments"
|
|
511
|
+
/>
|
|
512
|
+
</StackProvider>,
|
|
513
|
+
);
|
|
514
|
+
await act(async () => {});
|
|
515
|
+
|
|
516
|
+
expect(
|
|
517
|
+
container
|
|
518
|
+
.querySelector<HTMLAnchorElement>('[data-testid="login-link"]')
|
|
519
|
+
?.getAttribute("href"),
|
|
520
|
+
).toBe("/sign-in?redirectTo=%2Fblog%2Fpost-1%23comments");
|
|
521
|
+
});
|
|
499
522
|
});
|
|
500
523
|
|
|
501
524
|
describe("CommentForm inline field errors (StackError)", () => {
|
|
@@ -69,6 +69,11 @@ export interface CommentThreadProps {
|
|
|
69
69
|
className?: string;
|
|
70
70
|
/** Localization strings — defaults to English */
|
|
71
71
|
localization?: Partial<CommentsLocalization>;
|
|
72
|
+
/**
|
|
73
|
+
* Sign-in URL for unauthenticated users in this thread.
|
|
74
|
+
* Overrides the `loginPath` from the nearest `StackProvider`.
|
|
75
|
+
*/
|
|
76
|
+
loginHref?: string;
|
|
72
77
|
/**
|
|
73
78
|
* Number of top-level comments to load per page.
|
|
74
79
|
* Clicking "Load more" fetches the next page. Default: 10.
|
|
@@ -103,7 +108,6 @@ type ResolvedCommentThreadProps = CommentThreadProps & {
|
|
|
103
108
|
apiBaseURL: string;
|
|
104
109
|
apiBasePath: string;
|
|
105
110
|
currentUserId?: string;
|
|
106
|
-
loginHref?: string;
|
|
107
111
|
};
|
|
108
112
|
|
|
109
113
|
const DEFAULT_RENDERER: ComponentType<CommentRendererProps> = ({ body }) => (
|
|
@@ -842,7 +846,7 @@ export function CommentThread(props: CommentThreadProps) {
|
|
|
842
846
|
apiBaseURL: api?.baseURL ?? "",
|
|
843
847
|
apiBasePath: api?.basePath ?? "",
|
|
844
848
|
currentUserId,
|
|
845
|
-
loginHref: auth?.loginPath,
|
|
849
|
+
loginHref: props.loginHref ?? auth?.loginPath,
|
|
846
850
|
};
|
|
847
851
|
|
|
848
852
|
return (
|
|
@@ -10,6 +10,7 @@ import { useLoaderData, useRouteError } from "react-router";
|
|
|
10
10
|
import { normalizePath } from "../client/path-utils";
|
|
11
11
|
import {
|
|
12
12
|
type GetStackClient,
|
|
13
|
+
type ResolveStackClient,
|
|
13
14
|
stackDehydrateOptions,
|
|
14
15
|
} from "../shared/entry-factories";
|
|
15
16
|
|
|
@@ -19,8 +20,13 @@ import {
|
|
|
19
20
|
* are not available inside the library, so the factory types the subset it
|
|
20
21
|
* uses; the results remain assignable to the generated route exports.
|
|
21
22
|
*/
|
|
22
|
-
export interface ReactRouterPageLoaderArgs {
|
|
23
|
+
export interface ReactRouterPageLoaderArgs<TContext = unknown> {
|
|
24
|
+
/** The request that triggered the route loader. */
|
|
25
|
+
request: Request;
|
|
26
|
+
/** Parameters for the React Router catch-all route. */
|
|
23
27
|
params: { "*"?: string } & Record<string, string | undefined>;
|
|
28
|
+
/** Request-local values supplied by React Router middleware. */
|
|
29
|
+
context: TContext;
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
export interface CreateReactRouterPageOptions {
|
|
@@ -75,22 +81,37 @@ export function createReactRouterPage(options: CreateReactRouterPageOptions) {
|
|
|
75
81
|
dehydrateOptions = stackDehydrateOptions,
|
|
76
82
|
} = options;
|
|
77
83
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
/** Creates a loader with an optional async, request-aware stack client. */
|
|
85
|
+
function createLoader<TContext = unknown>(
|
|
86
|
+
getLoaderStackClient?: ResolveStackClient<
|
|
87
|
+
ReactRouterPageLoaderArgs<TContext>
|
|
88
|
+
>,
|
|
89
|
+
) {
|
|
90
|
+
return async function loader(
|
|
91
|
+
loaderArgs: ReactRouterPageLoaderArgs<TContext>,
|
|
92
|
+
) {
|
|
93
|
+
const { params } = loaderArgs;
|
|
94
|
+
const queryClient = getQueryClient();
|
|
95
|
+
const path = normalizePath(params["*"]);
|
|
96
|
+
const stackClient = getLoaderStackClient
|
|
97
|
+
? await getLoaderStackClient(queryClient, loaderArgs)
|
|
98
|
+
: getStackClient(queryClient);
|
|
99
|
+
const route = stackClient.router.getRoute(path);
|
|
82
100
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
101
|
+
if (route?.loader) {
|
|
102
|
+
await route.loader();
|
|
103
|
+
}
|
|
86
104
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
105
|
+
return {
|
|
106
|
+
path,
|
|
107
|
+
dehydratedState: dehydrate(queryClient, dehydrateOptions),
|
|
108
|
+
meta: await route?.meta?.(),
|
|
109
|
+
};
|
|
91
110
|
};
|
|
92
111
|
}
|
|
93
112
|
|
|
113
|
+
const loader = createLoader();
|
|
114
|
+
|
|
94
115
|
type LoaderData = Awaited<ReturnType<typeof loader>>;
|
|
95
116
|
|
|
96
117
|
// Recent React Router versions pass `loaderData`; older 7.x used `data`.
|
|
@@ -132,6 +153,7 @@ export function createReactRouterPage(options: CreateReactRouterPageOptions) {
|
|
|
132
153
|
|
|
133
154
|
return {
|
|
134
155
|
loader,
|
|
156
|
+
createLoader,
|
|
135
157
|
meta,
|
|
136
158
|
Component,
|
|
137
159
|
ErrorBoundary: CustomErrorBoundary ?? DefaultErrorBoundary,
|
|
@@ -35,6 +35,15 @@ export interface StackClientLike {
|
|
|
35
35
|
*/
|
|
36
36
|
export type GetStackClient = (queryClient: QueryClient) => StackClientLike;
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Consumer-provided resolver for lifecycle phases that can await request-aware
|
|
40
|
+
* stack client creation.
|
|
41
|
+
*/
|
|
42
|
+
export type ResolveStackClient<TContext> = (
|
|
43
|
+
queryClient: QueryClient,
|
|
44
|
+
context: TContext,
|
|
45
|
+
) => StackClientLike | PromiseLike<StackClientLike>;
|
|
46
|
+
|
|
38
47
|
/**
|
|
39
48
|
* A framework-agnostic BTST API handler, as returned by
|
|
40
49
|
* `createBackendHandler(...).handler`.
|