@btst/stack 2.9.2 → 2.9.4
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/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/media/db.cjs +10 -2
- package/dist/packages/stack/src/plugins/media/db.mjs +10 -2
- 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/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/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/media/__tests__/plugin.test.ts +4 -4
- package/src/plugins/media/db.ts +8 -0
- package/src/plugins/ui-builder/client/plugin.tsx +57 -27
- package/src/plugins/utils.ts +18 -0
|
@@ -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);
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import { createRoute } from "@btst/yar";
|
|
9
9
|
import type { ComponentType } from "react";
|
|
10
10
|
import type { QueryClient } from "@tanstack/react-query";
|
|
11
|
+
import { createSanitizedSSRLoaderError } from "../../utils";
|
|
11
12
|
import type { CMSApiRouter } from "../api";
|
|
12
13
|
import { createCMSQueryKeys } from "../query-keys";
|
|
13
14
|
|
|
@@ -163,6 +164,12 @@ function createDashboardLoader(config: CMSClientConfig) {
|
|
|
163
164
|
apiBasePath,
|
|
164
165
|
headers,
|
|
165
166
|
};
|
|
167
|
+
const client = createApiClient<CMSApiRouter>({
|
|
168
|
+
baseURL: apiBaseURL,
|
|
169
|
+
basePath: apiBasePath,
|
|
170
|
+
});
|
|
171
|
+
const queries = createCMSQueryKeys(client, headers);
|
|
172
|
+
const typesQuery = queries.cmsTypes.list();
|
|
166
173
|
|
|
167
174
|
try {
|
|
168
175
|
// Before hook - authorization check
|
|
@@ -173,13 +180,7 @@ function createDashboardLoader(config: CMSClientConfig) {
|
|
|
173
180
|
);
|
|
174
181
|
}
|
|
175
182
|
|
|
176
|
-
|
|
177
|
-
baseURL: apiBaseURL,
|
|
178
|
-
basePath: apiBasePath,
|
|
179
|
-
});
|
|
180
|
-
const queries = createCMSQueryKeys(client, headers);
|
|
181
|
-
|
|
182
|
-
await queryClient.prefetchQuery(queries.cmsTypes.list());
|
|
183
|
+
await queryClient.prefetchQuery(typesQuery);
|
|
183
184
|
|
|
184
185
|
// After hook
|
|
185
186
|
if (hooks?.afterLoadDashboard) {
|
|
@@ -187,9 +188,7 @@ function createDashboardLoader(config: CMSClientConfig) {
|
|
|
187
188
|
}
|
|
188
189
|
|
|
189
190
|
// Check if there was an error
|
|
190
|
-
const queryState = queryClient.getQueryState(
|
|
191
|
-
queries.cmsTypes.list().queryKey,
|
|
192
|
-
);
|
|
191
|
+
const queryState = queryClient.getQueryState(typesQuery.queryKey);
|
|
193
192
|
if (queryState?.error && hooks?.onLoadError) {
|
|
194
193
|
const error =
|
|
195
194
|
queryState.error instanceof Error
|
|
@@ -205,6 +204,15 @@ function createDashboardLoader(config: CMSClientConfig) {
|
|
|
205
204
|
"[btst/cms] route.loader() failed — no server running at build time. " +
|
|
206
205
|
"Use myStack.api.cms.prefetchForRoute() for SSG data prefetching.",
|
|
207
206
|
);
|
|
207
|
+
} else {
|
|
208
|
+
const errToStore = createSanitizedSSRLoaderError();
|
|
209
|
+
await queryClient.prefetchQuery({
|
|
210
|
+
queryKey: typesQuery.queryKey,
|
|
211
|
+
queryFn: () => {
|
|
212
|
+
throw errToStore;
|
|
213
|
+
},
|
|
214
|
+
retry: false,
|
|
215
|
+
});
|
|
208
216
|
}
|
|
209
217
|
if (hooks?.onLoadError) {
|
|
210
218
|
await hooks.onLoadError(error as Error, context);
|
|
@@ -231,6 +239,18 @@ function createContentListLoader(typeSlug: string, config: CMSClientConfig) {
|
|
|
231
239
|
apiBasePath,
|
|
232
240
|
headers,
|
|
233
241
|
};
|
|
242
|
+
const client = createApiClient<CMSApiRouter>({
|
|
243
|
+
baseURL: apiBaseURL,
|
|
244
|
+
basePath: apiBasePath,
|
|
245
|
+
});
|
|
246
|
+
const queries = createCMSQueryKeys(client, headers);
|
|
247
|
+
const limit = 20;
|
|
248
|
+
const typesQuery = queries.cmsTypes.list();
|
|
249
|
+
const listQuery = queries.cmsContent.list({
|
|
250
|
+
typeSlug,
|
|
251
|
+
limit,
|
|
252
|
+
offset: 0,
|
|
253
|
+
});
|
|
234
254
|
|
|
235
255
|
try {
|
|
236
256
|
// Before hook - authorization check
|
|
@@ -241,22 +261,10 @@ function createContentListLoader(typeSlug: string, config: CMSClientConfig) {
|
|
|
241
261
|
);
|
|
242
262
|
}
|
|
243
263
|
|
|
244
|
-
const client = createApiClient<CMSApiRouter>({
|
|
245
|
-
baseURL: apiBaseURL,
|
|
246
|
-
basePath: apiBasePath,
|
|
247
|
-
});
|
|
248
|
-
const queries = createCMSQueryKeys(client, headers);
|
|
249
|
-
const limit = 20;
|
|
250
|
-
|
|
251
264
|
// Prefetch content types
|
|
252
|
-
await queryClient.prefetchQuery(
|
|
265
|
+
await queryClient.prefetchQuery(typesQuery);
|
|
253
266
|
|
|
254
267
|
// Prefetch content list using infinite query (matches useSuspenseInfiniteQuery in hooks)
|
|
255
|
-
const listQuery = queries.cmsContent.list({
|
|
256
|
-
typeSlug,
|
|
257
|
-
limit,
|
|
258
|
-
offset: 0,
|
|
259
|
-
});
|
|
260
268
|
await queryClient.prefetchInfiniteQuery({
|
|
261
269
|
queryKey: listQuery.queryKey,
|
|
262
270
|
queryFn: async ({ pageParam = 0 }) => {
|
|
@@ -285,9 +293,7 @@ function createContentListLoader(typeSlug: string, config: CMSClientConfig) {
|
|
|
285
293
|
}
|
|
286
294
|
|
|
287
295
|
// Check if there was an error in either query
|
|
288
|
-
const typesState = queryClient.getQueryState(
|
|
289
|
-
queries.cmsTypes.list().queryKey,
|
|
290
|
-
);
|
|
296
|
+
const typesState = queryClient.getQueryState(typesQuery.queryKey);
|
|
291
297
|
const listState = queryClient.getQueryState(listQuery.queryKey);
|
|
292
298
|
const queryError = typesState?.error || listState?.error;
|
|
293
299
|
if (queryError && hooks?.onLoadError) {
|
|
@@ -305,6 +311,16 @@ function createContentListLoader(typeSlug: string, config: CMSClientConfig) {
|
|
|
305
311
|
"[btst/cms] route.loader() failed — no server running at build time. " +
|
|
306
312
|
"Use myStack.api.cms.prefetchForRoute() for SSG data prefetching.",
|
|
307
313
|
);
|
|
314
|
+
} else {
|
|
315
|
+
const errToStore = createSanitizedSSRLoaderError();
|
|
316
|
+
await queryClient.prefetchInfiniteQuery({
|
|
317
|
+
queryKey: listQuery.queryKey,
|
|
318
|
+
queryFn: () => {
|
|
319
|
+
throw errToStore;
|
|
320
|
+
},
|
|
321
|
+
initialPageParam: 0,
|
|
322
|
+
retry: false,
|
|
323
|
+
});
|
|
308
324
|
}
|
|
309
325
|
if (hooks?.onLoadError) {
|
|
310
326
|
await hooks.onLoadError(error as Error, context);
|
|
@@ -335,6 +351,15 @@ function createContentEditorLoader(
|
|
|
335
351
|
apiBasePath,
|
|
336
352
|
headers,
|
|
337
353
|
};
|
|
354
|
+
const client = createApiClient<CMSApiRouter>({
|
|
355
|
+
baseURL: apiBaseURL,
|
|
356
|
+
basePath: apiBasePath,
|
|
357
|
+
});
|
|
358
|
+
const queries = createCMSQueryKeys(client, headers);
|
|
359
|
+
const typesQuery = queries.cmsTypes.list();
|
|
360
|
+
const detailQuery = id
|
|
361
|
+
? queries.cmsContent.detail(typeSlug, id)
|
|
362
|
+
: undefined;
|
|
338
363
|
|
|
339
364
|
try {
|
|
340
365
|
// Before hook - authorization check
|
|
@@ -345,17 +370,9 @@ function createContentEditorLoader(
|
|
|
345
370
|
);
|
|
346
371
|
}
|
|
347
372
|
|
|
348
|
-
const
|
|
349
|
-
baseURL: apiBaseURL,
|
|
350
|
-
basePath: apiBasePath,
|
|
351
|
-
});
|
|
352
|
-
const queries = createCMSQueryKeys(client, headers);
|
|
353
|
-
|
|
354
|
-
const promises = [queryClient.prefetchQuery(queries.cmsTypes.list())];
|
|
373
|
+
const promises = [queryClient.prefetchQuery(typesQuery)];
|
|
355
374
|
if (id) {
|
|
356
|
-
promises.push(
|
|
357
|
-
queryClient.prefetchQuery(queries.cmsContent.detail(typeSlug, id)),
|
|
358
|
-
);
|
|
375
|
+
promises.push(queryClient.prefetchQuery(detailQuery!));
|
|
359
376
|
}
|
|
360
377
|
await Promise.all(promises);
|
|
361
378
|
|
|
@@ -365,13 +382,9 @@ function createContentEditorLoader(
|
|
|
365
382
|
}
|
|
366
383
|
|
|
367
384
|
// Check if there was an error
|
|
368
|
-
const typesState = queryClient.getQueryState(
|
|
369
|
-
queries.cmsTypes.list().queryKey,
|
|
370
|
-
);
|
|
385
|
+
const typesState = queryClient.getQueryState(typesQuery.queryKey);
|
|
371
386
|
const itemState = id
|
|
372
|
-
? queryClient.getQueryState(
|
|
373
|
-
queries.cmsContent.detail(typeSlug, id).queryKey,
|
|
374
|
-
)
|
|
387
|
+
? queryClient.getQueryState(detailQuery!.queryKey)
|
|
375
388
|
: null;
|
|
376
389
|
const queryError = typesState?.error || itemState?.error;
|
|
377
390
|
if (queryError && hooks?.onLoadError) {
|
|
@@ -389,6 +402,24 @@ function createContentEditorLoader(
|
|
|
389
402
|
"[btst/cms] route.loader() failed — no server running at build time. " +
|
|
390
403
|
"Use myStack.api.cms.prefetchForRoute() for SSG data prefetching.",
|
|
391
404
|
);
|
|
405
|
+
} else {
|
|
406
|
+
const errToStore = createSanitizedSSRLoaderError();
|
|
407
|
+
await queryClient.prefetchQuery({
|
|
408
|
+
queryKey: typesQuery.queryKey,
|
|
409
|
+
queryFn: () => {
|
|
410
|
+
throw errToStore;
|
|
411
|
+
},
|
|
412
|
+
retry: false,
|
|
413
|
+
});
|
|
414
|
+
if (detailQuery) {
|
|
415
|
+
await queryClient.prefetchQuery({
|
|
416
|
+
queryKey: detailQuery.queryKey,
|
|
417
|
+
queryFn: () => {
|
|
418
|
+
throw errToStore;
|
|
419
|
+
},
|
|
420
|
+
retry: false,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
392
423
|
}
|
|
393
424
|
if (hooks?.onLoadError) {
|
|
394
425
|
await hooks.onLoadError(error as Error, context);
|