@btst/stack 2.9.3 → 2.10.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/README.md +12 -0
- package/dist/packages/stack/src/plugins/blog/client/plugin.cjs +22 -11
- package/dist/packages/stack/src/plugins/blog/client/plugin.mjs +22 -11
- package/dist/packages/stack/src/plugins/cms/client/plugin.cjs +71 -39
- package/dist/packages/stack/src/plugins/cms/client/plugin.mjs +71 -39
- package/dist/packages/stack/src/plugins/comments/client/plugin.cjs +62 -2
- package/dist/packages/stack/src/plugins/comments/client/plugin.mjs +63 -3
- package/dist/packages/stack/src/plugins/comments/client/utils.cjs +2 -11
- package/dist/packages/stack/src/plugins/comments/client/utils.mjs +2 -11
- package/dist/packages/stack/src/plugins/comments/error-utils.cjs +15 -0
- package/dist/packages/stack/src/plugins/comments/error-utils.mjs +13 -0
- package/dist/packages/stack/src/plugins/form-builder/client/plugin.cjs +59 -31
- package/dist/packages/stack/src/plugins/form-builder/client/plugin.mjs +59 -31
- package/dist/packages/stack/src/plugins/ui-builder/client/plugin.cjs +52 -25
- package/dist/packages/stack/src/plugins/ui-builder/client/plugin.mjs +53 -26
- package/dist/packages/stack/src/plugins/utils.cjs +6 -0
- package/dist/packages/stack/src/plugins/utils.mjs +5 -1
- 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/client/index.cjs +2 -0
- package/dist/plugins/client/index.d.cts +15 -1
- package/dist/plugins/client/index.d.mts +15 -1
- package/dist/plugins/client/index.d.ts +15 -1
- package/dist/plugins/client/index.mjs +1 -1
- package/dist/plugins/comments/client/index.d.cts +5 -0
- package/dist/plugins/comments/client/index.d.mts +5 -0
- package/dist/plugins/comments/client/index.d.ts +5 -0
- package/dist/plugins/comments/query-keys.cjs +4 -4
- package/dist/plugins/comments/query-keys.mjs +1 -1
- 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/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/shared/{stack.IUeyQKrm.d.mts → stack.BSqJrCTM.d.cts} +5 -5
- package/dist/shared/{stack.D7HSzZdG.d.ts → stack.BXxrFL9R.d.ts} +5 -5
- package/dist/shared/{stack.6mEHS2WH.d.mts → stack.DOZ1EXjM.d.mts} +3 -3
- package/dist/shared/{stack.AJTXI7kw.d.cts → stack.DX-tQ93o.d.cts} +3 -3
- package/dist/shared/{stack.DjgpFWq3.d.cts → stack.DzOhpIYM.d.mts} +5 -5
- package/dist/shared/{stack.QYn-Px94.d.ts → stack.VF6FhyZw.d.ts} +3 -3
- package/package.json +1 -1
- package/src/__tests__/client-plugin-ssr-loaders.test.ts +329 -0
- package/src/plugins/blog/client/plugin.tsx +23 -14
- package/src/plugins/client/index.ts +2 -0
- package/src/plugins/cms/client/plugin.tsx +73 -42
- package/src/plugins/comments/client/plugin.tsx +82 -2
- package/src/plugins/comments/client/utils.ts +2 -14
- package/src/plugins/comments/error-utils.ts +17 -0
- package/src/plugins/comments/query-keys.ts +1 -1
- package/src/plugins/form-builder/client/plugin.tsx +59 -35
- package/src/plugins/ui-builder/client/plugin.tsx +57 -27
- package/src/plugins/utils.ts +18 -0
- package/dist/shared/{stack.eq5eg1yt.d.cts → stack.BOokfhZD.d.cts} +13 -13
- package/dist/shared/{stack.BQmuNl5p.d.ts → stack.BWp0hcm9.d.cts} +3 -3
- package/dist/shared/{stack.BQmuNl5p.d.cts → stack.BWp0hcm9.d.mts} +3 -3
- package/dist/shared/{stack.BQmuNl5p.d.mts → stack.BWp0hcm9.d.ts} +3 -3
- package/dist/shared/{stack.CMbX8Q5C.d.ts → stack.BvCR4-9H.d.ts} +13 -13
- package/dist/shared/{stack.Dj04W2c3.d.mts → stack.CWxAl9K3.d.mts} +13 -13
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
|
-
import { S as SerializedPost, c as createPostSchema, u as updatePostSchema, a as SerializedTag } from './stack.
|
|
2
|
+
import { S as SerializedPost, c as createPostSchema, u as updatePostSchema, a as SerializedTag } from './stack.BWp0hcm9.mjs';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -135,14 +135,14 @@ declare function useCreatePost(): _tanstack_react_query.UseMutationResult<Serial
|
|
|
135
135
|
name: string;
|
|
136
136
|
slug: string;
|
|
137
137
|
})[];
|
|
138
|
+
published: boolean;
|
|
138
139
|
title: string;
|
|
139
140
|
content: string;
|
|
140
141
|
excerpt: string;
|
|
141
|
-
published: boolean;
|
|
142
142
|
slug?: string | undefined;
|
|
143
|
+
publishedAt?: Date | undefined;
|
|
143
144
|
createdAt?: Date | undefined;
|
|
144
145
|
updatedAt?: Date | undefined;
|
|
145
|
-
publishedAt?: Date | undefined;
|
|
146
146
|
image?: string | undefined;
|
|
147
147
|
}, unknown>;
|
|
148
148
|
/** Update an existing post by id */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
|
-
import { S as SerializedPost, c as createPostSchema, u as updatePostSchema, a as SerializedTag } from './stack.
|
|
2
|
+
import { S as SerializedPost, c as createPostSchema, u as updatePostSchema, a as SerializedTag } from './stack.BWp0hcm9.cjs';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -135,14 +135,14 @@ declare function useCreatePost(): _tanstack_react_query.UseMutationResult<Serial
|
|
|
135
135
|
name: string;
|
|
136
136
|
slug: string;
|
|
137
137
|
})[];
|
|
138
|
+
published: boolean;
|
|
138
139
|
title: string;
|
|
139
140
|
content: string;
|
|
140
141
|
excerpt: string;
|
|
141
|
-
published: boolean;
|
|
142
142
|
slug?: string | undefined;
|
|
143
|
+
publishedAt?: Date | undefined;
|
|
143
144
|
createdAt?: Date | undefined;
|
|
144
145
|
updatedAt?: Date | undefined;
|
|
145
|
-
publishedAt?: Date | undefined;
|
|
146
146
|
image?: string | undefined;
|
|
147
147
|
}, unknown>;
|
|
148
148
|
/** Update an existing post by id */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
2
|
import { QueryClient } from '@tanstack/react-query';
|
|
3
3
|
import { createApiClient } from '@btst/stack/plugins/client';
|
|
4
|
-
import { P as Post, T as Tag, c as createPostSchema, u as updatePostSchema, S as SerializedPost, a as SerializedTag } from './stack.
|
|
4
|
+
import { P as Post, T as Tag, c as createPostSchema, u as updatePostSchema, S as SerializedPost, a as SerializedTag } from './stack.BWp0hcm9.mjs';
|
|
5
5
|
import * as _btst_stack_plugins_api from '@btst/stack/plugins/api';
|
|
6
6
|
import { DBAdapter } from '@btst/db';
|
|
7
7
|
import * as better_call from 'better-call';
|
|
@@ -244,11 +244,11 @@ declare const blogBackendPlugin: (hooks?: BlogBackendHooks) => _btst_stack_plugi
|
|
|
244
244
|
slug: string;
|
|
245
245
|
})[] | undefined;
|
|
246
246
|
slug?: string | undefined;
|
|
247
|
+
published?: boolean | undefined;
|
|
248
|
+
publishedAt?: unknown;
|
|
247
249
|
createdAt?: unknown;
|
|
248
250
|
updatedAt?: unknown;
|
|
249
|
-
publishedAt?: unknown;
|
|
250
251
|
image?: string | undefined;
|
|
251
|
-
published?: boolean | undefined;
|
|
252
252
|
}, {
|
|
253
253
|
title: string;
|
|
254
254
|
content: string;
|
|
@@ -261,11 +261,11 @@ declare const blogBackendPlugin: (hooks?: BlogBackendHooks) => _btst_stack_plugi
|
|
|
261
261
|
slug: string;
|
|
262
262
|
})[] | undefined;
|
|
263
263
|
slug?: string | undefined;
|
|
264
|
+
published?: boolean | undefined;
|
|
265
|
+
publishedAt?: unknown;
|
|
264
266
|
createdAt?: unknown;
|
|
265
267
|
updatedAt?: unknown;
|
|
266
|
-
publishedAt?: unknown;
|
|
267
268
|
image?: string | undefined;
|
|
268
|
-
published?: boolean | undefined;
|
|
269
269
|
}>;
|
|
270
270
|
}, Post>;
|
|
271
271
|
readonly updatePost: better_call.StrictEndpoint<"/posts/:id", {} & {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
|
-
import { S as SerializedPost, c as createPostSchema, u as updatePostSchema, a as SerializedTag } from './stack.
|
|
2
|
+
import { S as SerializedPost, c as createPostSchema, u as updatePostSchema, a as SerializedTag } from './stack.BWp0hcm9.js';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -135,14 +135,14 @@ declare function useCreatePost(): _tanstack_react_query.UseMutationResult<Serial
|
|
|
135
135
|
name: string;
|
|
136
136
|
slug: string;
|
|
137
137
|
})[];
|
|
138
|
+
published: boolean;
|
|
138
139
|
title: string;
|
|
139
140
|
content: string;
|
|
140
141
|
excerpt: string;
|
|
141
|
-
published: boolean;
|
|
142
142
|
slug?: string | undefined;
|
|
143
|
+
publishedAt?: Date | undefined;
|
|
143
144
|
createdAt?: Date | undefined;
|
|
144
145
|
updatedAt?: Date | undefined;
|
|
145
|
-
publishedAt?: Date | undefined;
|
|
146
146
|
image?: string | undefined;
|
|
147
147
|
}, unknown>;
|
|
148
148
|
/** Update an existing post by id */
|
package/package.json
CHANGED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
3
|
+
import { createApiClient, SSR_LOADER_ERROR_MESSAGE } from "../plugins/client";
|
|
4
|
+
import { blogClientPlugin } from "../plugins/blog/client";
|
|
5
|
+
import type { BlogApiRouter } from "../plugins/blog/api";
|
|
6
|
+
import { createBlogQueryKeys } from "../plugins/blog/query-keys";
|
|
7
|
+
import { cmsClientPlugin } from "../plugins/cms/client";
|
|
8
|
+
import type { CMSApiRouter } from "../plugins/cms/api";
|
|
9
|
+
import { createCMSQueryKeys } from "../plugins/cms/query-keys";
|
|
10
|
+
import { formBuilderClientPlugin } from "../plugins/form-builder/client";
|
|
11
|
+
import type { FormBuilderApiRouter } from "../plugins/form-builder/api";
|
|
12
|
+
import { createFormBuilderQueryKeys } from "../plugins/form-builder/query-keys";
|
|
13
|
+
import { uiBuilderClientPlugin } from "../plugins/ui-builder/client";
|
|
14
|
+
import { UI_BUILDER_TYPE_SLUG } from "../plugins/ui-builder";
|
|
15
|
+
import { commentsClientPlugin } from "../plugins/comments/client";
|
|
16
|
+
import type { CommentsApiRouter } from "../plugins/comments/api";
|
|
17
|
+
import { createCommentsQueryKeys } from "../plugins/comments/query-keys";
|
|
18
|
+
|
|
19
|
+
const API_BASE_URL = "http://localhost:3000";
|
|
20
|
+
const API_BASE_PATH = "/api/data";
|
|
21
|
+
const SITE_BASE_URL = "http://localhost:3000";
|
|
22
|
+
const SITE_BASE_PATH = "/pages";
|
|
23
|
+
const TEST_HEADERS = new Headers();
|
|
24
|
+
|
|
25
|
+
function getErrorMessage(
|
|
26
|
+
queryClient: QueryClient,
|
|
27
|
+
queryKey: readonly unknown[],
|
|
28
|
+
) {
|
|
29
|
+
const error = queryClient.getQueryState(queryKey)?.error;
|
|
30
|
+
return error instanceof Error ? error.message : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function mockFetchApiError(errorMessage: string) {
|
|
34
|
+
return vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
|
35
|
+
new Response(JSON.stringify({ message: errorMessage }), {
|
|
36
|
+
status: 500,
|
|
37
|
+
headers: { "content-type": "application/json" },
|
|
38
|
+
}),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe("client plugin SSR loaders", () => {
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
vi.restoreAllMocks();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("blog drafts loader seeds query error when beforeLoadPosts throws", async () => {
|
|
48
|
+
const queryClient = new QueryClient();
|
|
49
|
+
const expectedError = new Error("blog drafts blocked");
|
|
50
|
+
|
|
51
|
+
const plugin = blogClientPlugin({
|
|
52
|
+
apiBaseURL: API_BASE_URL,
|
|
53
|
+
apiBasePath: API_BASE_PATH,
|
|
54
|
+
siteBaseURL: SITE_BASE_URL,
|
|
55
|
+
siteBasePath: SITE_BASE_PATH,
|
|
56
|
+
queryClient,
|
|
57
|
+
headers: TEST_HEADERS,
|
|
58
|
+
hooks: {
|
|
59
|
+
beforeLoadPosts: () => {
|
|
60
|
+
throw expectedError;
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const route = plugin.routes().drafts();
|
|
66
|
+
await route.loader?.();
|
|
67
|
+
|
|
68
|
+
const client = createApiClient<BlogApiRouter>({
|
|
69
|
+
baseURL: API_BASE_URL,
|
|
70
|
+
basePath: API_BASE_PATH,
|
|
71
|
+
});
|
|
72
|
+
const queries = createBlogQueryKeys(client, TEST_HEADERS);
|
|
73
|
+
const listQuery = queries.posts.list({
|
|
74
|
+
query: undefined,
|
|
75
|
+
limit: 10,
|
|
76
|
+
published: false,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(getErrorMessage(queryClient, listQuery.queryKey)).toBe(
|
|
80
|
+
SSR_LOADER_ERROR_MESSAGE,
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("cms content list loader seeds query error when beforeLoadContentList throws", async () => {
|
|
85
|
+
const queryClient = new QueryClient();
|
|
86
|
+
const expectedError = new Error("cms list blocked");
|
|
87
|
+
const typeSlug = "article";
|
|
88
|
+
|
|
89
|
+
const plugin = cmsClientPlugin({
|
|
90
|
+
apiBaseURL: API_BASE_URL,
|
|
91
|
+
apiBasePath: API_BASE_PATH,
|
|
92
|
+
siteBaseURL: SITE_BASE_URL,
|
|
93
|
+
siteBasePath: SITE_BASE_PATH,
|
|
94
|
+
queryClient,
|
|
95
|
+
headers: TEST_HEADERS,
|
|
96
|
+
hooks: {
|
|
97
|
+
beforeLoadContentList: () => {
|
|
98
|
+
throw expectedError;
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const route = plugin.routes().contentList({ params: { typeSlug } });
|
|
104
|
+
await route.loader?.();
|
|
105
|
+
|
|
106
|
+
const client = createApiClient<CMSApiRouter>({
|
|
107
|
+
baseURL: API_BASE_URL,
|
|
108
|
+
basePath: API_BASE_PATH,
|
|
109
|
+
});
|
|
110
|
+
const queries = createCMSQueryKeys(client, TEST_HEADERS);
|
|
111
|
+
const listQuery = queries.cmsContent.list({
|
|
112
|
+
typeSlug,
|
|
113
|
+
limit: 20,
|
|
114
|
+
offset: 0,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(getErrorMessage(queryClient, listQuery.queryKey)).toBe(
|
|
118
|
+
SSR_LOADER_ERROR_MESSAGE,
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("form list loader seeds query error when beforeLoadFormList throws", async () => {
|
|
123
|
+
const queryClient = new QueryClient();
|
|
124
|
+
const expectedError = new Error("form list blocked");
|
|
125
|
+
|
|
126
|
+
const plugin = formBuilderClientPlugin({
|
|
127
|
+
apiBaseURL: API_BASE_URL,
|
|
128
|
+
apiBasePath: API_BASE_PATH,
|
|
129
|
+
siteBaseURL: SITE_BASE_URL,
|
|
130
|
+
siteBasePath: SITE_BASE_PATH,
|
|
131
|
+
queryClient,
|
|
132
|
+
headers: TEST_HEADERS,
|
|
133
|
+
hooks: {
|
|
134
|
+
beforeLoadFormList: () => {
|
|
135
|
+
throw expectedError;
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const route = plugin.routes().formList();
|
|
141
|
+
await route.loader?.();
|
|
142
|
+
|
|
143
|
+
const client = createApiClient<FormBuilderApiRouter>({
|
|
144
|
+
baseURL: API_BASE_URL,
|
|
145
|
+
basePath: API_BASE_PATH,
|
|
146
|
+
});
|
|
147
|
+
const queries = createFormBuilderQueryKeys(client, TEST_HEADERS);
|
|
148
|
+
const listQuery = queries.forms.list({ limit: 20, offset: 0 });
|
|
149
|
+
|
|
150
|
+
expect(getErrorMessage(queryClient, listQuery.queryKey)).toBe(
|
|
151
|
+
SSR_LOADER_ERROR_MESSAGE,
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("ui-builder list loader seeds query error when beforeLoadPageList throws", async () => {
|
|
156
|
+
const queryClient = new QueryClient();
|
|
157
|
+
const expectedError = new Error("ui-builder list blocked");
|
|
158
|
+
|
|
159
|
+
const plugin = uiBuilderClientPlugin({
|
|
160
|
+
apiBaseURL: API_BASE_URL,
|
|
161
|
+
apiBasePath: API_BASE_PATH,
|
|
162
|
+
siteBaseURL: SITE_BASE_URL,
|
|
163
|
+
siteBasePath: SITE_BASE_PATH,
|
|
164
|
+
queryClient,
|
|
165
|
+
headers: TEST_HEADERS,
|
|
166
|
+
componentRegistry: {},
|
|
167
|
+
hooks: {
|
|
168
|
+
beforeLoadPageList: () => {
|
|
169
|
+
throw expectedError;
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const route = plugin.routes().pageList();
|
|
175
|
+
await route.loader?.();
|
|
176
|
+
|
|
177
|
+
const client = createApiClient<CMSApiRouter>({
|
|
178
|
+
baseURL: API_BASE_URL,
|
|
179
|
+
basePath: API_BASE_PATH,
|
|
180
|
+
});
|
|
181
|
+
const queries = createCMSQueryKeys(client, TEST_HEADERS);
|
|
182
|
+
const listQuery = queries.cmsContent.list({
|
|
183
|
+
typeSlug: UI_BUILDER_TYPE_SLUG,
|
|
184
|
+
limit: 20,
|
|
185
|
+
offset: 0,
|
|
186
|
+
});
|
|
187
|
+
const uiBuilderQueryKey = [...listQuery.queryKey, "ui-builder"] as const;
|
|
188
|
+
|
|
189
|
+
expect(getErrorMessage(queryClient, uiBuilderQueryKey)).toBe(
|
|
190
|
+
SSR_LOADER_ERROR_MESSAGE,
|
|
191
|
+
);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("comments moderation loader seeds query error when beforeLoadModeration throws", async () => {
|
|
195
|
+
const queryClient = new QueryClient();
|
|
196
|
+
const expectedError = new Error("comments moderation blocked");
|
|
197
|
+
|
|
198
|
+
const plugin = commentsClientPlugin({
|
|
199
|
+
apiBaseURL: API_BASE_URL,
|
|
200
|
+
apiBasePath: API_BASE_PATH,
|
|
201
|
+
siteBaseURL: SITE_BASE_URL,
|
|
202
|
+
siteBasePath: SITE_BASE_PATH,
|
|
203
|
+
queryClient,
|
|
204
|
+
headers: TEST_HEADERS,
|
|
205
|
+
hooks: {
|
|
206
|
+
beforeLoadModeration: () => {
|
|
207
|
+
throw expectedError;
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const route = plugin.routes().moderation();
|
|
213
|
+
await route.loader?.();
|
|
214
|
+
|
|
215
|
+
const client = createApiClient<CommentsApiRouter>({
|
|
216
|
+
baseURL: API_BASE_URL,
|
|
217
|
+
basePath: API_BASE_PATH,
|
|
218
|
+
});
|
|
219
|
+
const queries = createCommentsQueryKeys(client, TEST_HEADERS);
|
|
220
|
+
const listQuery = queries.comments.list({
|
|
221
|
+
status: "pending",
|
|
222
|
+
limit: 20,
|
|
223
|
+
offset: 0,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
expect(getErrorMessage(queryClient, listQuery.queryKey)).toBe(
|
|
227
|
+
SSR_LOADER_ERROR_MESSAGE,
|
|
228
|
+
);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("comments user loader seeds query error with user-scoped key when beforeLoadUserComments throws", async () => {
|
|
232
|
+
const queryClient = new QueryClient();
|
|
233
|
+
const expectedError = new Error("comments user view blocked");
|
|
234
|
+
const currentUserId = "user-123";
|
|
235
|
+
|
|
236
|
+
const plugin = commentsClientPlugin({
|
|
237
|
+
apiBaseURL: API_BASE_URL,
|
|
238
|
+
apiBasePath: API_BASE_PATH,
|
|
239
|
+
siteBaseURL: SITE_BASE_URL,
|
|
240
|
+
siteBasePath: SITE_BASE_PATH,
|
|
241
|
+
queryClient,
|
|
242
|
+
headers: TEST_HEADERS,
|
|
243
|
+
hooks: {
|
|
244
|
+
beforeLoadUserComments: (context) => {
|
|
245
|
+
context.currentUserId = currentUserId;
|
|
246
|
+
throw expectedError;
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const route = plugin.routes().userComments();
|
|
252
|
+
await route.loader?.();
|
|
253
|
+
|
|
254
|
+
const client = createApiClient<CommentsApiRouter>({
|
|
255
|
+
baseURL: API_BASE_URL,
|
|
256
|
+
basePath: API_BASE_PATH,
|
|
257
|
+
});
|
|
258
|
+
const queries = createCommentsQueryKeys(client, TEST_HEADERS);
|
|
259
|
+
const listQuery = queries.comments.list({
|
|
260
|
+
authorId: currentUserId,
|
|
261
|
+
sort: "desc",
|
|
262
|
+
limit: 20,
|
|
263
|
+
offset: 0,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
expect(getErrorMessage(queryClient, listQuery.queryKey)).toBe(
|
|
267
|
+
SSR_LOADER_ERROR_MESSAGE,
|
|
268
|
+
);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("comments moderation loader calls onLoadError when prefetch stores API error", async () => {
|
|
272
|
+
const queryClient = new QueryClient();
|
|
273
|
+
const apiErrorMessage = "comments moderation api failed";
|
|
274
|
+
const onLoadError = vi.fn();
|
|
275
|
+
mockFetchApiError(apiErrorMessage);
|
|
276
|
+
|
|
277
|
+
const plugin = commentsClientPlugin({
|
|
278
|
+
apiBaseURL: API_BASE_URL,
|
|
279
|
+
apiBasePath: API_BASE_PATH,
|
|
280
|
+
siteBaseURL: SITE_BASE_URL,
|
|
281
|
+
siteBasePath: SITE_BASE_PATH,
|
|
282
|
+
queryClient,
|
|
283
|
+
headers: TEST_HEADERS,
|
|
284
|
+
hooks: {
|
|
285
|
+
onLoadError,
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const route = plugin.routes().moderation();
|
|
290
|
+
await route.loader?.();
|
|
291
|
+
|
|
292
|
+
expect(onLoadError).toHaveBeenCalledTimes(1);
|
|
293
|
+
const [errorArg] = onLoadError.mock.calls[0] ?? [];
|
|
294
|
+
expect(errorArg).toBeInstanceOf(Error);
|
|
295
|
+
expect((errorArg as Error).message).toBe(apiErrorMessage);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("comments user loader calls onLoadError when user-scoped prefetch stores API error", async () => {
|
|
299
|
+
const queryClient = new QueryClient();
|
|
300
|
+
const apiErrorMessage = "comments user api failed";
|
|
301
|
+
const onLoadError = vi.fn();
|
|
302
|
+
const currentUserId = "user-123";
|
|
303
|
+
mockFetchApiError(apiErrorMessage);
|
|
304
|
+
|
|
305
|
+
const plugin = commentsClientPlugin({
|
|
306
|
+
apiBaseURL: API_BASE_URL,
|
|
307
|
+
apiBasePath: API_BASE_PATH,
|
|
308
|
+
siteBaseURL: SITE_BASE_URL,
|
|
309
|
+
siteBasePath: SITE_BASE_PATH,
|
|
310
|
+
queryClient,
|
|
311
|
+
headers: TEST_HEADERS,
|
|
312
|
+
hooks: {
|
|
313
|
+
beforeLoadUserComments: (context) => {
|
|
314
|
+
context.currentUserId = currentUserId;
|
|
315
|
+
},
|
|
316
|
+
onLoadError,
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
const route = plugin.routes().userComments();
|
|
321
|
+
await route.loader?.();
|
|
322
|
+
|
|
323
|
+
expect(onLoadError).toHaveBeenCalledTimes(1);
|
|
324
|
+
const [errorArg, contextArg] = onLoadError.mock.calls[0] ?? [];
|
|
325
|
+
expect(errorArg).toBeInstanceOf(Error);
|
|
326
|
+
expect((errorArg as Error).message).toBe(apiErrorMessage);
|
|
327
|
+
expect(contextArg).toMatchObject({ currentUserId });
|
|
328
|
+
});
|
|
329
|
+
});
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { createRoute } from "@btst/yar";
|
|
8
8
|
import type { ComponentType } from "react";
|
|
9
9
|
import type { QueryClient } from "@tanstack/react-query";
|
|
10
|
+
import { createSanitizedSSRLoaderError } from "../../utils";
|
|
10
11
|
import type { BlogApiRouter } from "../api";
|
|
11
12
|
import { createBlogQueryKeys } from "../query-keys";
|
|
12
13
|
import type { Post, SerializedPost, SerializedTag } from "../types";
|
|
@@ -183,6 +184,18 @@ function createPostsLoader(published: boolean, config: BlogClientConfig) {
|
|
|
183
184
|
apiBasePath,
|
|
184
185
|
headers,
|
|
185
186
|
};
|
|
187
|
+
const limit = 10;
|
|
188
|
+
const client = createApiClient<BlogApiRouter>({
|
|
189
|
+
baseURL: apiBaseURL,
|
|
190
|
+
basePath: apiBasePath,
|
|
191
|
+
});
|
|
192
|
+
// note: for a module not to be bundled with client, and to be shared by client and server we need to add it to build.config.ts as an entry
|
|
193
|
+
const queries = createBlogQueryKeys(client, headers);
|
|
194
|
+
const listQuery = queries.posts.list({
|
|
195
|
+
query: undefined,
|
|
196
|
+
limit,
|
|
197
|
+
published: published,
|
|
198
|
+
});
|
|
186
199
|
|
|
187
200
|
try {
|
|
188
201
|
// Before hook
|
|
@@ -193,20 +206,6 @@ function createPostsLoader(published: boolean, config: BlogClientConfig) {
|
|
|
193
206
|
);
|
|
194
207
|
}
|
|
195
208
|
|
|
196
|
-
const limit = 10;
|
|
197
|
-
const client = createApiClient<BlogApiRouter>({
|
|
198
|
-
baseURL: apiBaseURL,
|
|
199
|
-
basePath: apiBasePath,
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// note: for a module not to be bundled with client, and to be shared by client and server we need to add it to build.config.ts as an entry
|
|
203
|
-
const queries = createBlogQueryKeys(client, headers);
|
|
204
|
-
const listQuery = queries.posts.list({
|
|
205
|
-
query: undefined,
|
|
206
|
-
limit,
|
|
207
|
-
published: published,
|
|
208
|
-
});
|
|
209
|
-
|
|
210
209
|
await queryClient.prefetchInfiniteQuery({
|
|
211
210
|
...listQuery,
|
|
212
211
|
initialPageParam: 0,
|
|
@@ -250,6 +249,16 @@ function createPostsLoader(published: boolean, config: BlogClientConfig) {
|
|
|
250
249
|
"[btst/blog] route.loader() failed — no server running at build time. " +
|
|
251
250
|
"Use myStack.api.blog.prefetchForRoute() for SSG data prefetching.",
|
|
252
251
|
);
|
|
252
|
+
} else {
|
|
253
|
+
const errToStore = createSanitizedSSRLoaderError();
|
|
254
|
+
await queryClient.prefetchInfiniteQuery({
|
|
255
|
+
queryKey: listQuery.queryKey,
|
|
256
|
+
queryFn: () => {
|
|
257
|
+
throw errToStore;
|
|
258
|
+
},
|
|
259
|
+
initialPageParam: 0,
|
|
260
|
+
retry: false,
|
|
261
|
+
});
|
|
253
262
|
}
|
|
254
263
|
if (hooks?.onLoadError) {
|
|
255
264
|
await hooks.onLoadError(error as Error, context);
|