@btst/stack 2.2.0 → 2.3.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.
Files changed (159) hide show
  1. package/dist/packages/stack/src/plugins/blog/api/plugin.cjs +52 -1
  2. package/dist/packages/stack/src/plugins/blog/api/plugin.mjs +52 -1
  3. package/dist/packages/stack/src/plugins/blog/api/query-key-defs.cjs +18 -0
  4. package/dist/packages/stack/src/plugins/blog/api/query-key-defs.mjs +15 -0
  5. package/dist/packages/stack/src/plugins/blog/api/serializers.cjs +21 -0
  6. package/dist/packages/stack/src/plugins/blog/api/serializers.mjs +18 -0
  7. package/dist/packages/stack/src/plugins/blog/client/plugin.cjs +15 -0
  8. package/dist/packages/stack/src/plugins/blog/client/plugin.mjs +16 -1
  9. package/dist/packages/stack/src/plugins/cms/api/getters.cjs +10 -0
  10. package/dist/packages/stack/src/plugins/cms/api/getters.mjs +10 -1
  11. package/dist/packages/stack/src/plugins/cms/api/plugin.cjs +70 -1
  12. package/dist/packages/stack/src/plugins/cms/api/plugin.mjs +71 -2
  13. package/dist/packages/stack/src/plugins/cms/api/query-key-defs.cjs +29 -0
  14. package/dist/packages/stack/src/plugins/cms/api/query-key-defs.mjs +26 -0
  15. package/dist/packages/stack/src/plugins/cms/client/plugin.cjs +15 -0
  16. package/dist/packages/stack/src/plugins/cms/client/plugin.mjs +16 -1
  17. package/dist/packages/stack/src/plugins/form-builder/api/getters.cjs +9 -0
  18. package/dist/packages/stack/src/plugins/form-builder/api/getters.mjs +9 -1
  19. package/dist/packages/stack/src/plugins/form-builder/api/plugin.cjs +62 -1
  20. package/dist/packages/stack/src/plugins/form-builder/api/plugin.mjs +63 -2
  21. package/dist/packages/stack/src/plugins/form-builder/api/query-key-defs.cjs +37 -0
  22. package/dist/packages/stack/src/plugins/form-builder/api/query-key-defs.mjs +33 -0
  23. package/dist/packages/stack/src/plugins/form-builder/client/plugin.cjs +15 -0
  24. package/dist/packages/stack/src/plugins/form-builder/client/plugin.mjs +16 -1
  25. package/dist/packages/stack/src/plugins/kanban/api/plugin.cjs +29 -1
  26. package/dist/packages/stack/src/plugins/kanban/api/plugin.mjs +29 -1
  27. package/dist/packages/stack/src/plugins/kanban/api/query-key-defs.cjs +26 -0
  28. package/dist/packages/stack/src/plugins/kanban/api/query-key-defs.mjs +23 -0
  29. package/dist/packages/stack/src/plugins/kanban/api/serializers.cjs +30 -0
  30. package/dist/packages/stack/src/plugins/kanban/api/serializers.mjs +26 -0
  31. package/dist/packages/stack/src/plugins/kanban/client/plugin.cjs +10 -0
  32. package/dist/packages/stack/src/plugins/kanban/client/plugin.mjs +11 -1
  33. package/dist/packages/stack/src/plugins/utils.cjs +6 -0
  34. package/dist/packages/stack/src/plugins/utils.mjs +6 -1
  35. package/dist/plugins/blog/api/index.cjs +5 -0
  36. package/dist/plugins/blog/api/index.d.cts +19 -4
  37. package/dist/plugins/blog/api/index.d.mts +19 -4
  38. package/dist/plugins/blog/api/index.d.ts +19 -4
  39. package/dist/plugins/blog/api/index.mjs +2 -0
  40. package/dist/plugins/blog/client/hooks/index.d.cts +4 -4
  41. package/dist/plugins/blog/client/hooks/index.d.mts +4 -4
  42. package/dist/plugins/blog/client/hooks/index.d.ts +4 -4
  43. package/dist/plugins/blog/client/index.d.cts +1 -1
  44. package/dist/plugins/blog/client/index.d.mts +1 -1
  45. package/dist/plugins/blog/client/index.d.ts +1 -1
  46. package/dist/plugins/blog/query-keys.cjs +6 -5
  47. package/dist/plugins/blog/query-keys.d.cts +8 -387
  48. package/dist/plugins/blog/query-keys.d.mts +8 -387
  49. package/dist/plugins/blog/query-keys.d.ts +8 -387
  50. package/dist/plugins/blog/query-keys.mjs +6 -5
  51. package/dist/plugins/client/index.cjs +1 -0
  52. package/dist/plugins/client/index.d.cts +8 -1
  53. package/dist/plugins/client/index.d.mts +8 -1
  54. package/dist/plugins/client/index.d.ts +8 -1
  55. package/dist/plugins/client/index.mjs +1 -1
  56. package/dist/plugins/cms/api/index.cjs +6 -0
  57. package/dist/plugins/cms/api/index.d.cts +7 -219
  58. package/dist/plugins/cms/api/index.d.mts +7 -219
  59. package/dist/plugins/cms/api/index.d.ts +7 -219
  60. package/dist/plugins/cms/api/index.mjs +2 -1
  61. package/dist/plugins/cms/client/hooks/index.d.cts +1 -1
  62. package/dist/plugins/cms/client/hooks/index.d.mts +1 -1
  63. package/dist/plugins/cms/client/hooks/index.d.ts +1 -1
  64. package/dist/plugins/cms/query-keys.cjs +2 -1
  65. package/dist/plugins/cms/query-keys.d.cts +5 -9
  66. package/dist/plugins/cms/query-keys.d.mts +5 -9
  67. package/dist/plugins/cms/query-keys.d.ts +5 -9
  68. package/dist/plugins/cms/query-keys.mjs +2 -1
  69. package/dist/plugins/form-builder/api/index.cjs +6 -0
  70. package/dist/plugins/form-builder/api/index.d.cts +7 -211
  71. package/dist/plugins/form-builder/api/index.d.mts +7 -211
  72. package/dist/plugins/form-builder/api/index.d.ts +7 -211
  73. package/dist/plugins/form-builder/api/index.mjs +2 -1
  74. package/dist/plugins/form-builder/client/components/index.d.cts +1 -1
  75. package/dist/plugins/form-builder/client/components/index.d.mts +1 -1
  76. package/dist/plugins/form-builder/client/components/index.d.ts +1 -1
  77. package/dist/plugins/form-builder/client/hooks/index.d.cts +1 -1
  78. package/dist/plugins/form-builder/client/hooks/index.d.mts +1 -1
  79. package/dist/plugins/form-builder/client/hooks/index.d.ts +1 -1
  80. package/dist/plugins/form-builder/query-keys.cjs +3 -2
  81. package/dist/plugins/form-builder/query-keys.d.cts +6 -6
  82. package/dist/plugins/form-builder/query-keys.d.mts +6 -6
  83. package/dist/plugins/form-builder/query-keys.d.ts +6 -6
  84. package/dist/plugins/form-builder/query-keys.mjs +3 -2
  85. package/dist/plugins/kanban/api/index.cjs +6 -0
  86. package/dist/plugins/kanban/api/index.d.cts +17 -392
  87. package/dist/plugins/kanban/api/index.d.mts +17 -392
  88. package/dist/plugins/kanban/api/index.d.ts +17 -392
  89. package/dist/plugins/kanban/api/index.mjs +2 -0
  90. package/dist/plugins/kanban/client/components/index.d.cts +1 -1
  91. package/dist/plugins/kanban/client/components/index.d.mts +1 -1
  92. package/dist/plugins/kanban/client/components/index.d.ts +1 -1
  93. package/dist/plugins/kanban/client/hooks/index.d.cts +1 -1
  94. package/dist/plugins/kanban/client/hooks/index.d.mts +1 -1
  95. package/dist/plugins/kanban/client/hooks/index.d.ts +1 -1
  96. package/dist/plugins/kanban/client/index.d.cts +1 -1
  97. package/dist/plugins/kanban/client/index.d.mts +1 -1
  98. package/dist/plugins/kanban/client/index.d.ts +1 -1
  99. package/dist/plugins/kanban/query-keys.cjs +2 -9
  100. package/dist/plugins/kanban/query-keys.d.cts +4 -16
  101. package/dist/plugins/kanban/query-keys.d.mts +4 -16
  102. package/dist/plugins/kanban/query-keys.d.ts +4 -16
  103. package/dist/plugins/kanban/query-keys.mjs +2 -9
  104. package/dist/plugins/ui-builder/index.d.cts +1 -1
  105. package/dist/plugins/ui-builder/index.d.mts +1 -1
  106. package/dist/plugins/ui-builder/index.d.ts +1 -1
  107. package/dist/shared/stack.B1EeBt1b.d.ts +297 -0
  108. package/dist/shared/stack.BIXEI6v_.d.mts +419 -0
  109. package/dist/shared/stack.BKfolAyK.d.ts +419 -0
  110. package/dist/shared/stack.BpolpQpf.d.cts +445 -0
  111. package/dist/shared/stack.C5dtIncc.d.mts +293 -0
  112. package/dist/shared/stack.CIP6QS9l.d.ts +293 -0
  113. package/dist/shared/stack.CP68pFEH.d.mts +297 -0
  114. package/dist/shared/{stack.CXjzTMsb.d.mts → stack.CVDTkMoO.d.cts} +7 -1
  115. package/dist/shared/{stack.CXjzTMsb.d.ts → stack.CVDTkMoO.d.mts} +7 -1
  116. package/dist/shared/{stack.CXjzTMsb.d.cts → stack.CVDTkMoO.d.ts} +7 -1
  117. package/dist/shared/{stack.QD1y_7NY.d.mts → stack.DJaKVY7v.d.cts} +1 -1
  118. package/dist/shared/{stack.QD1y_7NY.d.ts → stack.DJaKVY7v.d.mts} +1 -1
  119. package/dist/shared/{stack.QD1y_7NY.d.cts → stack.DJaKVY7v.d.ts} +1 -1
  120. package/dist/shared/{stack.CIrIsc-A.d.mts → stack.DdI5W6MB.d.cts} +7 -1
  121. package/dist/shared/{stack.CIrIsc-A.d.ts → stack.DdI5W6MB.d.mts} +7 -1
  122. package/dist/shared/{stack.CIrIsc-A.d.cts → stack.DdI5W6MB.d.ts} +7 -1
  123. package/dist/shared/stack.Dw0Ly2TM.d.cts +293 -0
  124. package/dist/shared/stack.IdtKDRka.d.cts +297 -0
  125. package/dist/shared/stack.TIBF2AOx.d.ts +445 -0
  126. package/dist/shared/stack.rTy7-wQU.d.mts +445 -0
  127. package/dist/shared/stack.snB1EDP7.d.cts +419 -0
  128. package/package.json +3 -3
  129. package/src/plugins/blog/api/index.ts +2 -0
  130. package/src/plugins/blog/api/plugin.ts +85 -0
  131. package/src/plugins/blog/api/query-key-defs.ts +46 -0
  132. package/src/plugins/blog/api/serializers.ts +27 -0
  133. package/src/plugins/blog/client/plugin.tsx +19 -0
  134. package/src/plugins/blog/query-keys.ts +5 -7
  135. package/src/plugins/client/index.ts +1 -1
  136. package/src/plugins/cms/api/getters.ts +24 -0
  137. package/src/plugins/cms/api/index.ts +10 -1
  138. package/src/plugins/cms/api/plugin.ts +105 -0
  139. package/src/plugins/cms/api/query-key-defs.ts +53 -0
  140. package/src/plugins/cms/api/serializers.ts +12 -0
  141. package/src/plugins/cms/client/plugin.tsx +19 -0
  142. package/src/plugins/cms/query-keys.ts +2 -1
  143. package/src/plugins/form-builder/api/getters.ts +23 -0
  144. package/src/plugins/form-builder/api/index.ts +15 -2
  145. package/src/plugins/form-builder/api/plugin.ts +91 -0
  146. package/src/plugins/form-builder/api/query-key-defs.ts +79 -0
  147. package/src/plugins/form-builder/api/serializers.ts +12 -0
  148. package/src/plugins/form-builder/client/plugin.tsx +19 -0
  149. package/src/plugins/form-builder/query-keys.ts +6 -2
  150. package/src/plugins/kanban/api/index.ts +3 -0
  151. package/src/plugins/kanban/api/plugin.ts +49 -0
  152. package/src/plugins/kanban/api/query-key-defs.ts +54 -0
  153. package/src/plugins/kanban/api/serializers.ts +49 -0
  154. package/src/plugins/kanban/client/plugin.tsx +13 -0
  155. package/src/plugins/kanban/query-keys.ts +2 -9
  156. package/src/plugins/utils.ts +19 -0
  157. package/dist/shared/{stack.BkYlUT_8.d.cts → stack.CBON0dWL.d.cts} +6 -6
  158. package/dist/shared/{stack.BkYlUT_8.d.mts → stack.CBON0dWL.d.mts} +6 -6
  159. package/dist/shared/{stack.BkYlUT_8.d.ts → stack.CBON0dWL.d.ts} +6 -6
@@ -6,7 +6,57 @@ const db = require('../db.cjs');
6
6
  const utils = require('../utils.cjs');
7
7
  const schemas = require('../schemas.cjs');
8
8
  const getters = require('./getters.cjs');
9
+ const queryKeyDefs = require('./query-key-defs.cjs');
10
+ const serializers = require('./serializers.cjs');
9
11
 
12
+ function createBlogPrefetchForRoute(adapter) {
13
+ return async function prefetchForRoute(key, qc, params) {
14
+ switch (key) {
15
+ case "posts":
16
+ case "drafts": {
17
+ const published = key === "posts";
18
+ const [result, tags] = await Promise.all([
19
+ getters.getAllPosts(adapter, { published, limit: 10 }),
20
+ getters.getAllTags(adapter)
21
+ ]);
22
+ qc.setQueryData(queryKeyDefs.BLOG_QUERY_KEYS.postsList({ published, limit: 10 }), {
23
+ pages: [result.items.map(serializers.serializePost)],
24
+ pageParams: [0]
25
+ });
26
+ qc.setQueryData(queryKeyDefs.BLOG_QUERY_KEYS.tagsList(), tags.map(serializers.serializeTag));
27
+ break;
28
+ }
29
+ case "post":
30
+ case "editPost": {
31
+ const slug = params?.slug ?? "";
32
+ if (slug) {
33
+ const post = await getters.getPostBySlug(adapter, slug);
34
+ qc.setQueryData(
35
+ queryKeyDefs.BLOG_QUERY_KEYS.postDetail(slug),
36
+ post ? serializers.serializePost(post) : null
37
+ );
38
+ }
39
+ break;
40
+ }
41
+ case "tag": {
42
+ const tagSlug = params?.tagSlug ?? "";
43
+ const [result, tags] = await Promise.all([
44
+ getters.getAllPosts(adapter, { published: true, limit: 10, tagSlug }),
45
+ getters.getAllTags(adapter)
46
+ ]);
47
+ qc.setQueryData(
48
+ queryKeyDefs.BLOG_QUERY_KEYS.postsList({ published: true, limit: 10, tagSlug }),
49
+ {
50
+ pages: [result.items.map(serializers.serializePost)],
51
+ pageParams: [0]
52
+ }
53
+ );
54
+ qc.setQueryData(queryKeyDefs.BLOG_QUERY_KEYS.tagsList(), tags.map(serializers.serializeTag));
55
+ break;
56
+ }
57
+ }
58
+ };
59
+ }
10
60
  const PostListQuerySchema = z.z.object({
11
61
  slug: z.z.string().optional(),
12
62
  tagSlug: z.z.string().optional(),
@@ -29,7 +79,8 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
29
79
  api: (adapter) => ({
30
80
  getAllPosts: (params) => getters.getAllPosts(adapter, params),
31
81
  getPostBySlug: (slug) => getters.getPostBySlug(adapter, slug),
32
- getAllTags: () => getters.getAllTags(adapter)
82
+ getAllTags: () => getters.getAllTags(adapter),
83
+ prefetchForRoute: createBlogPrefetchForRoute(adapter)
33
84
  }),
34
85
  routes: (adapter) => {
35
86
  const findOrCreateTags = async (tagInputs) => {
@@ -4,7 +4,57 @@ import { blogSchema } from '../db.mjs';
4
4
  import { slugify } from '../utils.mjs';
5
5
  import { createPostSchema, updatePostSchema } from '../schemas.mjs';
6
6
  import { getAllTags, getPostBySlug, getAllPosts } from './getters.mjs';
7
+ import { BLOG_QUERY_KEYS } from './query-key-defs.mjs';
8
+ import { serializePost, serializeTag } from './serializers.mjs';
7
9
 
10
+ function createBlogPrefetchForRoute(adapter) {
11
+ return async function prefetchForRoute(key, qc, params) {
12
+ switch (key) {
13
+ case "posts":
14
+ case "drafts": {
15
+ const published = key === "posts";
16
+ const [result, tags] = await Promise.all([
17
+ getAllPosts(adapter, { published, limit: 10 }),
18
+ getAllTags(adapter)
19
+ ]);
20
+ qc.setQueryData(BLOG_QUERY_KEYS.postsList({ published, limit: 10 }), {
21
+ pages: [result.items.map(serializePost)],
22
+ pageParams: [0]
23
+ });
24
+ qc.setQueryData(BLOG_QUERY_KEYS.tagsList(), tags.map(serializeTag));
25
+ break;
26
+ }
27
+ case "post":
28
+ case "editPost": {
29
+ const slug = params?.slug ?? "";
30
+ if (slug) {
31
+ const post = await getPostBySlug(adapter, slug);
32
+ qc.setQueryData(
33
+ BLOG_QUERY_KEYS.postDetail(slug),
34
+ post ? serializePost(post) : null
35
+ );
36
+ }
37
+ break;
38
+ }
39
+ case "tag": {
40
+ const tagSlug = params?.tagSlug ?? "";
41
+ const [result, tags] = await Promise.all([
42
+ getAllPosts(adapter, { published: true, limit: 10, tagSlug }),
43
+ getAllTags(adapter)
44
+ ]);
45
+ qc.setQueryData(
46
+ BLOG_QUERY_KEYS.postsList({ published: true, limit: 10, tagSlug }),
47
+ {
48
+ pages: [result.items.map(serializePost)],
49
+ pageParams: [0]
50
+ }
51
+ );
52
+ qc.setQueryData(BLOG_QUERY_KEYS.tagsList(), tags.map(serializeTag));
53
+ break;
54
+ }
55
+ }
56
+ };
57
+ }
8
58
  const PostListQuerySchema = z.object({
9
59
  slug: z.string().optional(),
10
60
  tagSlug: z.string().optional(),
@@ -27,7 +77,8 @@ const blogBackendPlugin = (hooks) => defineBackendPlugin({
27
77
  api: (adapter) => ({
28
78
  getAllPosts: (params) => getAllPosts(adapter, params),
29
79
  getPostBySlug: (slug) => getPostBySlug(adapter, slug),
30
- getAllTags: () => getAllTags(adapter)
80
+ getAllTags: () => getAllTags(adapter),
81
+ prefetchForRoute: createBlogPrefetchForRoute(adapter)
31
82
  }),
32
83
  routes: (adapter) => {
33
84
  const findOrCreateTags = async (tagInputs) => {
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ function postsListDiscriminator(params) {
4
+ return {
5
+ query: params.query !== void 0 && params.query.trim() === "" ? void 0 : params.query,
6
+ limit: params.limit ?? 10,
7
+ published: params.published,
8
+ tagSlug: params.tagSlug
9
+ };
10
+ }
11
+ const BLOG_QUERY_KEYS = {
12
+ postsList: (params) => ["posts", "list", postsListDiscriminator(params)],
13
+ postDetail: (slug) => ["posts", "detail", slug],
14
+ tagsList: () => ["tags", "list", "tags"]
15
+ };
16
+
17
+ exports.BLOG_QUERY_KEYS = BLOG_QUERY_KEYS;
18
+ exports.postsListDiscriminator = postsListDiscriminator;
@@ -0,0 +1,15 @@
1
+ function postsListDiscriminator(params) {
2
+ return {
3
+ query: params.query !== void 0 && params.query.trim() === "" ? void 0 : params.query,
4
+ limit: params.limit ?? 10,
5
+ published: params.published,
6
+ tagSlug: params.tagSlug
7
+ };
8
+ }
9
+ const BLOG_QUERY_KEYS = {
10
+ postsList: (params) => ["posts", "list", postsListDiscriminator(params)],
11
+ postDetail: (slug) => ["posts", "detail", slug],
12
+ tagsList: () => ["tags", "list", "tags"]
13
+ };
14
+
15
+ export { BLOG_QUERY_KEYS, postsListDiscriminator };
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ function serializeTag(tag) {
4
+ return {
5
+ ...tag,
6
+ createdAt: tag.createdAt.toISOString(),
7
+ updatedAt: tag.updatedAt.toISOString()
8
+ };
9
+ }
10
+ function serializePost(post) {
11
+ return {
12
+ ...post,
13
+ createdAt: post.createdAt.toISOString(),
14
+ updatedAt: post.updatedAt.toISOString(),
15
+ publishedAt: post.publishedAt?.toISOString(),
16
+ tags: post.tags.map(serializeTag)
17
+ };
18
+ }
19
+
20
+ exports.serializePost = serializePost;
21
+ exports.serializeTag = serializeTag;
@@ -0,0 +1,18 @@
1
+ function serializeTag(tag) {
2
+ return {
3
+ ...tag,
4
+ createdAt: tag.createdAt.toISOString(),
5
+ updatedAt: tag.updatedAt.toISOString()
6
+ };
7
+ }
8
+ function serializePost(post) {
9
+ return {
10
+ ...post,
11
+ createdAt: post.createdAt.toISOString(),
12
+ updatedAt: post.updatedAt.toISOString(),
13
+ publishedAt: post.publishedAt?.toISOString(),
14
+ tags: post.tags.map(serializeTag)
15
+ };
16
+ }
17
+
18
+ export { serializePost, serializeTag };
@@ -64,6 +64,11 @@ function createPostsLoader(published, config) {
64
64
  }
65
65
  }
66
66
  } catch (error) {
67
+ if (client.isConnectionError(error)) {
68
+ console.warn(
69
+ "[btst/blog] route.loader() failed \u2014 no server running at build time. Use myStack.api.blog.prefetchForRoute() for SSG data prefetching."
70
+ );
71
+ }
67
72
  if (hooks?.onLoadError) {
68
73
  await hooks.onLoadError(error, context);
69
74
  }
@@ -112,6 +117,11 @@ function createPostLoader(slug, config, path) {
112
117
  }
113
118
  }
114
119
  } catch (error) {
120
+ if (client.isConnectionError(error)) {
121
+ console.warn(
122
+ "[btst/blog] route.loader() failed \u2014 no server running at build time. Use myStack.api.blog.prefetchForRoute() for SSG data prefetching."
123
+ );
124
+ }
115
125
  if (hooks?.onLoadError) {
116
126
  await hooks.onLoadError(error, context);
117
127
  }
@@ -190,6 +200,11 @@ function createTagLoader(tagSlug, config) {
190
200
  await hooks.onLoadError(error, context);
191
201
  }
192
202
  } catch (error) {
203
+ if (client.isConnectionError(error)) {
204
+ console.warn(
205
+ "[btst/blog] route.loader() failed \u2014 no server running at build time. Use myStack.api.blog.prefetchForRoute() for SSG data prefetching."
206
+ );
207
+ }
193
208
  if (hooks?.onLoadError) {
194
209
  await hooks.onLoadError(error, context);
195
210
  }
@@ -1,5 +1,5 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
- import { defineClientPlugin, createApiClient } from '@btst/stack/plugins/client';
2
+ import { defineClientPlugin, createApiClient, isConnectionError } from '@btst/stack/plugins/client';
3
3
  import { createRoute } from '@btst/yar';
4
4
  import { createBlogQueryKeys } from '../../../../../../plugins/blog/query-keys.mjs';
5
5
  import { HomePageComponent } from './components/pages/home-page.mjs';
@@ -62,6 +62,11 @@ function createPostsLoader(published, config) {
62
62
  }
63
63
  }
64
64
  } catch (error) {
65
+ if (isConnectionError(error)) {
66
+ console.warn(
67
+ "[btst/blog] route.loader() failed \u2014 no server running at build time. Use myStack.api.blog.prefetchForRoute() for SSG data prefetching."
68
+ );
69
+ }
65
70
  if (hooks?.onLoadError) {
66
71
  await hooks.onLoadError(error, context);
67
72
  }
@@ -110,6 +115,11 @@ function createPostLoader(slug, config, path) {
110
115
  }
111
116
  }
112
117
  } catch (error) {
118
+ if (isConnectionError(error)) {
119
+ console.warn(
120
+ "[btst/blog] route.loader() failed \u2014 no server running at build time. Use myStack.api.blog.prefetchForRoute() for SSG data prefetching."
121
+ );
122
+ }
113
123
  if (hooks?.onLoadError) {
114
124
  await hooks.onLoadError(error, context);
115
125
  }
@@ -188,6 +198,11 @@ function createTagLoader(tagSlug, config) {
188
198
  await hooks.onLoadError(error, context);
189
199
  }
190
200
  } catch (error) {
201
+ if (isConnectionError(error)) {
202
+ console.warn(
203
+ "[btst/blog] route.loader() failed \u2014 no server running at build time. Use myStack.api.blog.prefetchForRoute() for SSG data prefetching."
204
+ );
205
+ }
191
206
  if (hooks?.onLoadError) {
192
207
  await hooks.onLoadError(error, context);
193
208
  }
@@ -105,6 +105,15 @@ async function getAllContentItems(adapter, contentTypeSlug, params) {
105
105
  offset: params?.offset
106
106
  };
107
107
  }
108
+ async function getContentItemById(adapter, id) {
109
+ const item = await adapter.findOne({
110
+ model: "contentItem",
111
+ where: [{ field: "id", value: id, operator: "eq" }],
112
+ join: { contentType: true }
113
+ });
114
+ if (!item) return null;
115
+ return serializeContentItemWithType(item);
116
+ }
108
117
  async function getContentItemBySlug(adapter, contentTypeSlug, slug) {
109
118
  const contentType = await adapter.findOne({
110
119
  model: "contentType",
@@ -139,6 +148,7 @@ async function getContentItemBySlug(adapter, contentTypeSlug, slug) {
139
148
 
140
149
  exports.getAllContentItems = getAllContentItems;
141
150
  exports.getAllContentTypes = getAllContentTypes;
151
+ exports.getContentItemById = getContentItemById;
142
152
  exports.getContentItemBySlug = getContentItemBySlug;
143
153
  exports.migrateToUnifiedSchema = migrateToUnifiedSchema;
144
154
  exports.serializeContentItem = serializeContentItem;
@@ -103,6 +103,15 @@ async function getAllContentItems(adapter, contentTypeSlug, params) {
103
103
  offset: params?.offset
104
104
  };
105
105
  }
106
+ async function getContentItemById(adapter, id) {
107
+ const item = await adapter.findOne({
108
+ model: "contentItem",
109
+ where: [{ field: "id", value: id, operator: "eq" }],
110
+ join: { contentType: true }
111
+ });
112
+ if (!item) return null;
113
+ return serializeContentItemWithType(item);
114
+ }
106
115
  async function getContentItemBySlug(adapter, contentTypeSlug, slug) {
107
116
  const contentType = await adapter.findOne({
108
117
  model: "contentType",
@@ -135,4 +144,4 @@ async function getContentItemBySlug(adapter, contentTypeSlug, slug) {
135
144
  return serializeContentItemWithType(item);
136
145
  }
137
146
 
138
- export { getAllContentItems, getAllContentTypes, getContentItemBySlug, migrateToUnifiedSchema, serializeContentItem, serializeContentItemWithType, serializeContentType };
147
+ export { getAllContentItems, getAllContentTypes, getContentItemById, getContentItemBySlug, migrateToUnifiedSchema, serializeContentItem, serializeContentItemWithType, serializeContentType };
@@ -7,6 +7,7 @@ const db = require('../db.cjs');
7
7
  const schemas = require('../schemas.cjs');
8
8
  const utils = require('../utils.cjs');
9
9
  const getters = require('./getters.cjs');
10
+ const queryKeyDefs = require('./query-key-defs.cjs');
10
11
 
11
12
  async function syncContentTypes(adapter, config) {
12
13
  for (const ct of config.contentTypes) {
@@ -243,6 +244,69 @@ const cmsBackendPlugin = (config) => {
243
244
  }
244
245
  return syncPromise;
245
246
  };
247
+ const getContentTypesWithCounts = async (adapter) => {
248
+ const contentTypes = await getters.getAllContentTypes(adapter);
249
+ return Promise.all(
250
+ contentTypes.map(async (ct) => {
251
+ const count = await adapter.count({
252
+ model: "contentItem",
253
+ where: [
254
+ { field: "contentTypeId", value: ct.id, operator: "eq" }
255
+ ]
256
+ });
257
+ return { ...ct, itemCount: count };
258
+ })
259
+ );
260
+ };
261
+ const createCMSPrefetchForRoute = (adapter) => {
262
+ return async function prefetchForRoute(key, qc, params) {
263
+ await ensureSynced(adapter);
264
+ switch (key) {
265
+ case "dashboard":
266
+ case "newContent": {
267
+ const typesWithCounts = await getContentTypesWithCounts(adapter);
268
+ qc.setQueryData(queryKeyDefs.CMS_QUERY_KEYS.typesList(), typesWithCounts);
269
+ break;
270
+ }
271
+ case "contentList": {
272
+ const typeSlug = params?.typeSlug ?? "";
273
+ const [contentTypes, contentItems] = await Promise.all([
274
+ getContentTypesWithCounts(adapter),
275
+ getters.getAllContentItems(adapter, typeSlug, { limit: 20, offset: 0 })
276
+ ]);
277
+ qc.setQueryData(queryKeyDefs.CMS_QUERY_KEYS.typesList(), contentTypes);
278
+ qc.setQueryData(
279
+ queryKeyDefs.CMS_QUERY_KEYS.contentList({ typeSlug, limit: 20, offset: 0 }),
280
+ {
281
+ pages: [
282
+ {
283
+ items: contentItems.items,
284
+ total: contentItems.total,
285
+ limit: contentItems.limit ?? 20,
286
+ offset: contentItems.offset ?? 0
287
+ }
288
+ ],
289
+ pageParams: [0]
290
+ }
291
+ );
292
+ break;
293
+ }
294
+ case "editContent": {
295
+ const typeSlug = params?.typeSlug ?? "";
296
+ const id = params?.id ?? "";
297
+ const [contentTypes, item] = await Promise.all([
298
+ getContentTypesWithCounts(adapter),
299
+ id ? getters.getContentItemById(adapter, id) : Promise.resolve(null)
300
+ ]);
301
+ qc.setQueryData(queryKeyDefs.CMS_QUERY_KEYS.typesList(), contentTypes);
302
+ if (id) {
303
+ qc.setQueryData(queryKeyDefs.CMS_QUERY_KEYS.contentDetail(typeSlug, id), item);
304
+ }
305
+ break;
306
+ }
307
+ }
308
+ };
309
+ };
246
310
  return api.defineBackendPlugin({
247
311
  name: "cms",
248
312
  dbPlugin: db.cmsSchema,
@@ -258,7 +322,12 @@ const cmsBackendPlugin = (config) => {
258
322
  getContentItemBySlug: async (contentTypeSlug, slug) => {
259
323
  await ensureSynced(adapter);
260
324
  return getters.getContentItemBySlug(adapter, contentTypeSlug, slug);
261
- }
325
+ },
326
+ getContentItemById: async (id) => {
327
+ await ensureSynced(adapter);
328
+ return getters.getContentItemById(adapter, id);
329
+ },
330
+ prefetchForRoute: createCMSPrefetchForRoute(adapter)
262
331
  }),
263
332
  routes: (adapter) => {
264
333
  const getContentType = async (slug) => {
@@ -4,7 +4,8 @@ import { formSchemaToZod, zodToFormSchema } from '../../../../../ui/src/lib/sche
4
4
  import { cmsSchema } from '../db.mjs';
5
5
  import { listContentQuerySchema } from '../schemas.mjs';
6
6
  import { slugify } from '../utils.mjs';
7
- import { serializeContentType, getAllContentItems, serializeContentItemWithType, serializeContentItem, getContentItemBySlug, getAllContentTypes } from './getters.mjs';
7
+ import { serializeContentType, getAllContentItems, serializeContentItemWithType, serializeContentItem, getContentItemById, getContentItemBySlug, getAllContentTypes } from './getters.mjs';
8
+ import { CMS_QUERY_KEYS } from './query-key-defs.mjs';
8
9
 
9
10
  async function syncContentTypes(adapter, config) {
10
11
  for (const ct of config.contentTypes) {
@@ -241,6 +242,69 @@ const cmsBackendPlugin = (config) => {
241
242
  }
242
243
  return syncPromise;
243
244
  };
245
+ const getContentTypesWithCounts = async (adapter) => {
246
+ const contentTypes = await getAllContentTypes(adapter);
247
+ return Promise.all(
248
+ contentTypes.map(async (ct) => {
249
+ const count = await adapter.count({
250
+ model: "contentItem",
251
+ where: [
252
+ { field: "contentTypeId", value: ct.id, operator: "eq" }
253
+ ]
254
+ });
255
+ return { ...ct, itemCount: count };
256
+ })
257
+ );
258
+ };
259
+ const createCMSPrefetchForRoute = (adapter) => {
260
+ return async function prefetchForRoute(key, qc, params) {
261
+ await ensureSynced(adapter);
262
+ switch (key) {
263
+ case "dashboard":
264
+ case "newContent": {
265
+ const typesWithCounts = await getContentTypesWithCounts(adapter);
266
+ qc.setQueryData(CMS_QUERY_KEYS.typesList(), typesWithCounts);
267
+ break;
268
+ }
269
+ case "contentList": {
270
+ const typeSlug = params?.typeSlug ?? "";
271
+ const [contentTypes, contentItems] = await Promise.all([
272
+ getContentTypesWithCounts(adapter),
273
+ getAllContentItems(adapter, typeSlug, { limit: 20, offset: 0 })
274
+ ]);
275
+ qc.setQueryData(CMS_QUERY_KEYS.typesList(), contentTypes);
276
+ qc.setQueryData(
277
+ CMS_QUERY_KEYS.contentList({ typeSlug, limit: 20, offset: 0 }),
278
+ {
279
+ pages: [
280
+ {
281
+ items: contentItems.items,
282
+ total: contentItems.total,
283
+ limit: contentItems.limit ?? 20,
284
+ offset: contentItems.offset ?? 0
285
+ }
286
+ ],
287
+ pageParams: [0]
288
+ }
289
+ );
290
+ break;
291
+ }
292
+ case "editContent": {
293
+ const typeSlug = params?.typeSlug ?? "";
294
+ const id = params?.id ?? "";
295
+ const [contentTypes, item] = await Promise.all([
296
+ getContentTypesWithCounts(adapter),
297
+ id ? getContentItemById(adapter, id) : Promise.resolve(null)
298
+ ]);
299
+ qc.setQueryData(CMS_QUERY_KEYS.typesList(), contentTypes);
300
+ if (id) {
301
+ qc.setQueryData(CMS_QUERY_KEYS.contentDetail(typeSlug, id), item);
302
+ }
303
+ break;
304
+ }
305
+ }
306
+ };
307
+ };
244
308
  return defineBackendPlugin({
245
309
  name: "cms",
246
310
  dbPlugin: cmsSchema,
@@ -256,7 +320,12 @@ const cmsBackendPlugin = (config) => {
256
320
  getContentItemBySlug: async (contentTypeSlug, slug) => {
257
321
  await ensureSynced(adapter);
258
322
  return getContentItemBySlug(adapter, contentTypeSlug, slug);
259
- }
323
+ },
324
+ getContentItemById: async (id) => {
325
+ await ensureSynced(adapter);
326
+ return getContentItemById(adapter, id);
327
+ },
328
+ prefetchForRoute: createCMSPrefetchForRoute(adapter)
260
329
  }),
261
330
  routes: (adapter) => {
262
331
  const getContentType = async (slug) => {
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ function contentListDiscriminator(params) {
4
+ return {
5
+ typeSlug: params.typeSlug,
6
+ limit: params.limit ?? 20,
7
+ offset: params.offset ?? 0
8
+ };
9
+ }
10
+ const CMS_QUERY_KEYS = {
11
+ /**
12
+ * Key for the cmsTypes.list() query.
13
+ * Full key: ["cmsTypes", "list", "list"]
14
+ */
15
+ typesList: () => ["cmsTypes", "list", "list"],
16
+ /**
17
+ * Key for the cmsContent.list({ typeSlug, limit, offset }) query.
18
+ * Full key: ["cmsContent", "list", { typeSlug, limit, offset }]
19
+ */
20
+ contentList: (params) => ["cmsContent", "list", contentListDiscriminator(params)],
21
+ /**
22
+ * Key for the cmsContent.detail(typeSlug, id) query.
23
+ * Full key: ["cmsContent", "detail", typeSlug, id]
24
+ */
25
+ contentDetail: (typeSlug, id) => ["cmsContent", "detail", typeSlug, id]
26
+ };
27
+
28
+ exports.CMS_QUERY_KEYS = CMS_QUERY_KEYS;
29
+ exports.contentListDiscriminator = contentListDiscriminator;
@@ -0,0 +1,26 @@
1
+ function contentListDiscriminator(params) {
2
+ return {
3
+ typeSlug: params.typeSlug,
4
+ limit: params.limit ?? 20,
5
+ offset: params.offset ?? 0
6
+ };
7
+ }
8
+ const CMS_QUERY_KEYS = {
9
+ /**
10
+ * Key for the cmsTypes.list() query.
11
+ * Full key: ["cmsTypes", "list", "list"]
12
+ */
13
+ typesList: () => ["cmsTypes", "list", "list"],
14
+ /**
15
+ * Key for the cmsContent.list({ typeSlug, limit, offset }) query.
16
+ * Full key: ["cmsContent", "list", { typeSlug, limit, offset }]
17
+ */
18
+ contentList: (params) => ["cmsContent", "list", contentListDiscriminator(params)],
19
+ /**
20
+ * Key for the cmsContent.detail(typeSlug, id) query.
21
+ * Full key: ["cmsContent", "detail", typeSlug, id]
22
+ */
23
+ contentDetail: (typeSlug, id) => ["cmsContent", "detail", typeSlug, id]
24
+ };
25
+
26
+ export { CMS_QUERY_KEYS, contentListDiscriminator };
@@ -56,6 +56,11 @@ function createDashboardLoader(config) {
56
56
  await hooks.onLoadError(error, context);
57
57
  }
58
58
  } catch (error) {
59
+ if (client.isConnectionError(error)) {
60
+ console.warn(
61
+ "[btst/cms] route.loader() failed \u2014 no server running at build time. Use myStack.api.cms.prefetchForRoute() for SSG data prefetching."
62
+ );
63
+ }
59
64
  if (hooks?.onLoadError) {
60
65
  await hooks.onLoadError(error, context);
61
66
  }
@@ -123,6 +128,11 @@ function createContentListLoader(typeSlug, config) {
123
128
  await hooks.onLoadError(error, context);
124
129
  }
125
130
  } catch (error) {
131
+ if (client.isConnectionError(error)) {
132
+ console.warn(
133
+ "[btst/cms] route.loader() failed \u2014 no server running at build time. Use myStack.api.cms.prefetchForRoute() for SSG data prefetching."
134
+ );
135
+ }
126
136
  if (hooks?.onLoadError) {
127
137
  await hooks.onLoadError(error, context);
128
138
  }
@@ -180,6 +190,11 @@ function createContentEditorLoader(typeSlug, id, config) {
180
190
  await hooks.onLoadError(error, context);
181
191
  }
182
192
  } catch (error) {
193
+ if (client.isConnectionError(error)) {
194
+ console.warn(
195
+ "[btst/cms] route.loader() failed \u2014 no server running at build time. Use myStack.api.cms.prefetchForRoute() for SSG data prefetching."
196
+ );
197
+ }
183
198
  if (hooks?.onLoadError) {
184
199
  await hooks.onLoadError(error, context);
185
200
  }
@@ -1,6 +1,6 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { lazy } from 'react';
3
- import { defineClientPlugin, createApiClient } from '@btst/stack/plugins/client';
3
+ import { defineClientPlugin, createApiClient, isConnectionError } from '@btst/stack/plugins/client';
4
4
  import { createRoute } from '@btst/yar';
5
5
  import { createCMSQueryKeys } from '../../../../../../plugins/cms/query-keys.mjs';
6
6
 
@@ -54,6 +54,11 @@ function createDashboardLoader(config) {
54
54
  await hooks.onLoadError(error, context);
55
55
  }
56
56
  } catch (error) {
57
+ if (isConnectionError(error)) {
58
+ console.warn(
59
+ "[btst/cms] route.loader() failed \u2014 no server running at build time. Use myStack.api.cms.prefetchForRoute() for SSG data prefetching."
60
+ );
61
+ }
57
62
  if (hooks?.onLoadError) {
58
63
  await hooks.onLoadError(error, context);
59
64
  }
@@ -121,6 +126,11 @@ function createContentListLoader(typeSlug, config) {
121
126
  await hooks.onLoadError(error, context);
122
127
  }
123
128
  } catch (error) {
129
+ if (isConnectionError(error)) {
130
+ console.warn(
131
+ "[btst/cms] route.loader() failed \u2014 no server running at build time. Use myStack.api.cms.prefetchForRoute() for SSG data prefetching."
132
+ );
133
+ }
124
134
  if (hooks?.onLoadError) {
125
135
  await hooks.onLoadError(error, context);
126
136
  }
@@ -178,6 +188,11 @@ function createContentEditorLoader(typeSlug, id, config) {
178
188
  await hooks.onLoadError(error, context);
179
189
  }
180
190
  } catch (error) {
191
+ if (isConnectionError(error)) {
192
+ console.warn(
193
+ "[btst/cms] route.loader() failed \u2014 no server running at build time. Use myStack.api.cms.prefetchForRoute() for SSG data prefetching."
194
+ );
195
+ }
181
196
  if (hooks?.onLoadError) {
182
197
  await hooks.onLoadError(error, context);
183
198
  }