@btst/stack 1.0.1 → 1.1.1

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 (282) hide show
  1. package/README.md +156 -709
  2. package/dist/api/index.cjs +2 -1
  3. package/dist/api/index.d.cts +4 -3
  4. package/dist/api/index.d.mts +4 -3
  5. package/dist/api/index.d.ts +4 -3
  6. package/dist/api/index.mjs +1 -1
  7. package/dist/client/components/compose.cjs +68 -0
  8. package/dist/client/components/compose.mjs +65 -0
  9. package/dist/client/components/error-boundary.cjs +24 -0
  10. package/dist/client/components/error-boundary.mjs +22 -0
  11. package/dist/client/components/index.cjs +10 -0
  12. package/dist/client/components/index.d.cts +52 -0
  13. package/dist/client/components/index.d.mts +52 -0
  14. package/dist/client/components/index.d.ts +52 -0
  15. package/dist/client/components/index.mjs +2 -0
  16. package/dist/client/index.cjs +24 -5
  17. package/dist/client/index.d.cts +125 -8
  18. package/dist/client/index.d.mts +125 -8
  19. package/dist/client/index.d.ts +125 -8
  20. package/dist/client/index.mjs +21 -4
  21. package/dist/client/meta-utils.cjs +162 -0
  22. package/dist/client/meta-utils.mjs +160 -0
  23. package/dist/client/path-utils.cjs +15 -0
  24. package/dist/client/path-utils.mjs +13 -0
  25. package/dist/client/sitemap-utils.cjs +14 -0
  26. package/dist/client/sitemap-utils.mjs +12 -0
  27. package/dist/context/index.cjs +6 -63
  28. package/dist/context/index.d.cts +21 -24
  29. package/dist/context/index.d.mts +21 -24
  30. package/dist/context/index.d.ts +21 -24
  31. package/dist/context/index.mjs +1 -61
  32. package/dist/context/provider.cjs +51 -0
  33. package/dist/context/provider.mjs +46 -0
  34. package/dist/index.cjs +2 -3
  35. package/dist/index.d.cts +3 -2
  36. package/dist/index.d.mts +3 -2
  37. package/dist/index.d.ts +3 -2
  38. package/dist/index.mjs +1 -2
  39. package/dist/plugins/api/index.cjs +13 -0
  40. package/dist/plugins/api/index.d.cts +40 -0
  41. package/dist/plugins/api/index.d.mts +40 -0
  42. package/dist/plugins/api/index.d.ts +40 -0
  43. package/dist/plugins/api/index.mjs +8 -0
  44. package/dist/plugins/blog/api/index.cjs +11 -0
  45. package/dist/plugins/blog/api/index.d.cts +7 -0
  46. package/dist/plugins/blog/api/index.d.mts +7 -0
  47. package/dist/plugins/blog/api/index.d.ts +7 -0
  48. package/dist/plugins/blog/api/index.mjs +2 -0
  49. package/dist/plugins/blog/api/plugin.cjs +569 -0
  50. package/dist/plugins/blog/api/plugin.mjs +565 -0
  51. package/dist/plugins/blog/client/components/forms/image-field.cjs +133 -0
  52. package/dist/plugins/blog/client/components/forms/image-field.mjs +131 -0
  53. package/dist/plugins/blog/client/components/forms/markdown-editor-styles.css +30 -0
  54. package/dist/plugins/blog/client/components/forms/markdown-editor.cjs +106 -0
  55. package/dist/plugins/blog/client/components/forms/markdown-editor.mjs +104 -0
  56. package/dist/plugins/blog/client/components/forms/post-forms.cjs +401 -0
  57. package/dist/plugins/blog/client/components/forms/post-forms.mjs +398 -0
  58. package/dist/plugins/blog/client/components/forms/tags-multiselect.cjs +71 -0
  59. package/dist/plugins/blog/client/components/forms/tags-multiselect.mjs +65 -0
  60. package/dist/plugins/blog/client/components/index.cjs +17 -0
  61. package/dist/plugins/blog/client/components/index.d.cts +22 -0
  62. package/dist/plugins/blog/client/components/index.d.mts +22 -0
  63. package/dist/plugins/blog/client/components/index.d.ts +22 -0
  64. package/dist/plugins/blog/client/components/index.mjs +12 -0
  65. package/dist/plugins/blog/client/components/loading/form-page-skeleton.cjs +62 -0
  66. package/dist/plugins/blog/client/components/loading/form-page-skeleton.mjs +60 -0
  67. package/dist/plugins/blog/client/components/loading/index.cjs +20 -0
  68. package/dist/plugins/blog/client/components/loading/index.mjs +16 -0
  69. package/dist/plugins/blog/client/components/loading/list-page-skeleton.cjs +26 -0
  70. package/dist/plugins/blog/client/components/loading/list-page-skeleton.mjs +24 -0
  71. package/dist/plugins/blog/client/components/loading/page-header-skeleton.cjs +13 -0
  72. package/dist/plugins/blog/client/components/loading/page-header-skeleton.mjs +11 -0
  73. package/dist/plugins/blog/client/components/loading/post-card-skeleton.cjs +22 -0
  74. package/dist/plugins/blog/client/components/loading/post-card-skeleton.mjs +20 -0
  75. package/dist/plugins/blog/client/components/loading/post-page-skeleton.cjs +56 -0
  76. package/dist/plugins/blog/client/components/loading/post-page-skeleton.mjs +54 -0
  77. package/dist/plugins/blog/client/components/pages/404-page.cjs +19 -0
  78. package/dist/plugins/blog/client/components/pages/404-page.mjs +17 -0
  79. package/dist/plugins/blog/client/components/pages/edit-post-page.cjs +41 -0
  80. package/dist/plugins/blog/client/components/pages/edit-post-page.internal.cjs +57 -0
  81. package/dist/plugins/blog/client/components/pages/edit-post-page.internal.mjs +55 -0
  82. package/dist/plugins/blog/client/components/pages/edit-post-page.mjs +39 -0
  83. package/dist/plugins/blog/client/components/pages/home-page.cjs +41 -0
  84. package/dist/plugins/blog/client/components/pages/home-page.internal.cjs +61 -0
  85. package/dist/plugins/blog/client/components/pages/home-page.internal.mjs +59 -0
  86. package/dist/plugins/blog/client/components/pages/home-page.mjs +39 -0
  87. package/dist/plugins/blog/client/components/pages/new-post-page.cjs +37 -0
  88. package/dist/plugins/blog/client/components/pages/new-post-page.internal.cjs +53 -0
  89. package/dist/plugins/blog/client/components/pages/new-post-page.internal.mjs +51 -0
  90. package/dist/plugins/blog/client/components/pages/new-post-page.mjs +35 -0
  91. package/dist/plugins/blog/client/components/pages/post-page.cjs +39 -0
  92. package/dist/plugins/blog/client/components/pages/post-page.internal.cjs +101 -0
  93. package/dist/plugins/blog/client/components/pages/post-page.internal.mjs +99 -0
  94. package/dist/plugins/blog/client/components/pages/post-page.mjs +37 -0
  95. package/dist/plugins/blog/client/components/pages/tag-page.cjs +39 -0
  96. package/dist/plugins/blog/client/components/pages/tag-page.internal.cjs +61 -0
  97. package/dist/plugins/blog/client/components/pages/tag-page.internal.mjs +59 -0
  98. package/dist/plugins/blog/client/components/pages/tag-page.mjs +37 -0
  99. package/dist/plugins/blog/client/components/shared/better-blog-attribution.cjs +24 -0
  100. package/dist/plugins/blog/client/components/shared/better-blog-attribution.mjs +22 -0
  101. package/dist/plugins/blog/client/components/shared/default-error.cjs +18 -0
  102. package/dist/plugins/blog/client/components/shared/default-error.mjs +16 -0
  103. package/dist/plugins/blog/client/components/shared/defaults.cjs +13 -0
  104. package/dist/plugins/blog/client/components/shared/defaults.mjs +10 -0
  105. package/dist/plugins/blog/client/components/shared/empty-list.cjs +21 -0
  106. package/dist/plugins/blog/client/components/shared/empty-list.mjs +19 -0
  107. package/dist/plugins/blog/client/components/shared/error-placeholder.cjs +24 -0
  108. package/dist/plugins/blog/client/components/shared/error-placeholder.mjs +22 -0
  109. package/dist/plugins/blog/client/components/shared/highlight-text.cjs +53 -0
  110. package/dist/plugins/blog/client/components/shared/highlight-text.mjs +51 -0
  111. package/dist/plugins/blog/client/components/shared/markdown-content-styles.css +328 -0
  112. package/dist/plugins/blog/client/components/shared/markdown-content.cjs +324 -0
  113. package/dist/plugins/blog/client/components/shared/markdown-content.mjs +315 -0
  114. package/dist/plugins/blog/client/components/shared/on-this-page.cjs +161 -0
  115. package/dist/plugins/blog/client/components/shared/on-this-page.mjs +158 -0
  116. package/dist/plugins/blog/client/components/shared/page-header.cjs +40 -0
  117. package/dist/plugins/blog/client/components/shared/page-header.mjs +38 -0
  118. package/dist/plugins/blog/client/components/shared/page-layout.cjs +24 -0
  119. package/dist/plugins/blog/client/components/shared/page-layout.mjs +22 -0
  120. package/dist/plugins/blog/client/components/shared/page-wrapper.cjs +23 -0
  121. package/dist/plugins/blog/client/components/shared/page-wrapper.mjs +21 -0
  122. package/dist/plugins/blog/client/components/shared/post-card.cjs +279 -0
  123. package/dist/plugins/blog/client/components/shared/post-card.mjs +277 -0
  124. package/dist/plugins/blog/client/components/shared/post-navigation.cjs +74 -0
  125. package/dist/plugins/blog/client/components/shared/post-navigation.mjs +72 -0
  126. package/dist/plugins/blog/client/components/shared/posts-list.cjs +48 -0
  127. package/dist/plugins/blog/client/components/shared/posts-list.mjs +46 -0
  128. package/dist/plugins/blog/client/components/shared/recent-posts-carousel.cjs +59 -0
  129. package/dist/plugins/blog/client/components/shared/recent-posts-carousel.mjs +57 -0
  130. package/dist/plugins/blog/client/components/shared/search-input.cjs +136 -0
  131. package/dist/plugins/blog/client/components/shared/search-input.mjs +117 -0
  132. package/dist/plugins/blog/client/components/shared/search-modal.cjs +135 -0
  133. package/dist/plugins/blog/client/components/shared/search-modal.mjs +116 -0
  134. package/dist/plugins/blog/client/components/shared/tags-list.cjs +22 -0
  135. package/dist/plugins/blog/client/components/shared/tags-list.mjs +20 -0
  136. package/dist/plugins/blog/client/components/shared/use-route-lifecycle.cjs +50 -0
  137. package/dist/plugins/blog/client/components/shared/use-route-lifecycle.mjs +48 -0
  138. package/dist/plugins/blog/client/hooks/blog-hooks.cjs +380 -0
  139. package/dist/plugins/blog/client/hooks/blog-hooks.mjs +368 -0
  140. package/dist/plugins/blog/client/hooks/index.cjs +17 -0
  141. package/dist/plugins/blog/client/hooks/index.d.cts +150 -0
  142. package/dist/plugins/blog/client/hooks/index.d.mts +150 -0
  143. package/dist/plugins/blog/client/hooks/index.d.ts +150 -0
  144. package/dist/plugins/blog/client/hooks/index.mjs +1 -0
  145. package/dist/plugins/blog/client/hooks/use-debounce.cjs +16 -0
  146. package/dist/plugins/blog/client/hooks/use-debounce.mjs +14 -0
  147. package/dist/plugins/blog/client/index.cjs +7 -0
  148. package/dist/plugins/blog/client/index.d.cts +414 -0
  149. package/dist/plugins/blog/client/index.d.mts +414 -0
  150. package/dist/plugins/blog/client/index.d.ts +414 -0
  151. package/dist/plugins/blog/client/index.mjs +1 -0
  152. package/dist/plugins/blog/client/localization/blog-card.cjs +7 -0
  153. package/dist/plugins/blog/client/localization/blog-card.mjs +5 -0
  154. package/dist/plugins/blog/client/localization/blog-common.cjs +10 -0
  155. package/dist/plugins/blog/client/localization/blog-common.mjs +8 -0
  156. package/dist/plugins/blog/client/localization/blog-forms.cjs +40 -0
  157. package/dist/plugins/blog/client/localization/blog-forms.mjs +38 -0
  158. package/dist/plugins/blog/client/localization/blog-list.cjs +18 -0
  159. package/dist/plugins/blog/client/localization/blog-list.mjs +16 -0
  160. package/dist/plugins/blog/client/localization/blog-post.cjs +13 -0
  161. package/dist/plugins/blog/client/localization/blog-post.mjs +11 -0
  162. package/dist/plugins/blog/client/localization/index.cjs +17 -0
  163. package/dist/plugins/blog/client/localization/index.mjs +15 -0
  164. package/dist/plugins/blog/client/plugin.cjs +462 -0
  165. package/dist/plugins/blog/client/plugin.mjs +460 -0
  166. package/dist/plugins/blog/client.css +3 -0
  167. package/dist/plugins/blog/db.cjs +90 -0
  168. package/dist/plugins/blog/db.mjs +88 -0
  169. package/dist/plugins/blog/query-keys.cjs +181 -0
  170. package/dist/plugins/blog/query-keys.d.cts +530 -0
  171. package/dist/plugins/blog/query-keys.d.mts +530 -0
  172. package/dist/plugins/blog/query-keys.d.ts +530 -0
  173. package/dist/plugins/blog/query-keys.mjs +179 -0
  174. package/dist/plugins/blog/schemas.cjs +39 -0
  175. package/dist/plugins/blog/schemas.mjs +35 -0
  176. package/dist/plugins/blog/style.css +22 -0
  177. package/dist/plugins/blog/utils.cjs +97 -0
  178. package/dist/plugins/blog/utils.mjs +87 -0
  179. package/dist/plugins/client/index.cjs +15 -0
  180. package/dist/plugins/client/index.d.cts +57 -0
  181. package/dist/plugins/client/index.d.mts +57 -0
  182. package/dist/plugins/client/index.d.ts +57 -0
  183. package/dist/plugins/client/index.mjs +9 -0
  184. package/dist/{shared/stack.3OUyGp_E.mjs → plugins/utils.mjs} +1 -1
  185. package/dist/shared/{stack.DORw_1ps.d.cts → stack.ByOugz9d.d.cts} +17 -1
  186. package/dist/shared/{stack.DORw_1ps.d.mts → stack.ByOugz9d.d.mts} +17 -1
  187. package/dist/shared/{stack.DORw_1ps.d.ts → stack.ByOugz9d.d.ts} +17 -1
  188. package/dist/shared/stack.CoPoHVfV.d.cts +76 -0
  189. package/dist/shared/stack.CoPoHVfV.d.mts +76 -0
  190. package/dist/shared/stack.CoPoHVfV.d.ts +76 -0
  191. package/package.json +102 -14
  192. package/src/__tests__/plugins.test.tsx +539 -0
  193. package/src/__tests__/sitemap.test.ts +60 -0
  194. package/src/api/index.ts +75 -0
  195. package/src/client/components/compose.tsx +116 -0
  196. package/src/client/components/error-boundary.tsx +30 -0
  197. package/src/client/components/index.tsx +2 -0
  198. package/src/client/index.ts +109 -0
  199. package/src/client/meta-utils.ts +228 -0
  200. package/src/client/path-utils.ts +38 -0
  201. package/src/client/sitemap-utils.ts +46 -0
  202. package/src/context/index.ts +1 -0
  203. package/src/context/provider.tsx +157 -0
  204. package/src/index.ts +1 -0
  205. package/src/plugins/api/index.ts +50 -0
  206. package/src/plugins/blog/api/index.ts +2 -0
  207. package/src/plugins/blog/api/plugin.ts +759 -0
  208. package/src/plugins/blog/client/components/forms/image-field.tsx +165 -0
  209. package/src/plugins/blog/client/components/forms/markdown-editor-styles.css +30 -0
  210. package/src/plugins/blog/client/components/forms/markdown-editor.tsx +136 -0
  211. package/src/plugins/blog/client/components/forms/post-forms.tsx +531 -0
  212. package/src/plugins/blog/client/components/forms/tags-multiselect.tsx +79 -0
  213. package/src/plugins/blog/client/components/index.tsx +11 -0
  214. package/src/plugins/blog/client/components/loading/form-page-skeleton.tsx +75 -0
  215. package/src/plugins/blog/client/components/loading/index.tsx +27 -0
  216. package/src/plugins/blog/client/components/loading/list-page-skeleton.tsx +38 -0
  217. package/src/plugins/blog/client/components/loading/page-header-skeleton.tsx +10 -0
  218. package/src/plugins/blog/client/components/loading/post-card-skeleton.tsx +30 -0
  219. package/src/plugins/blog/client/components/loading/post-page-skeleton.tsx +75 -0
  220. package/src/plugins/blog/client/components/pages/404-page.tsx +23 -0
  221. package/src/plugins/blog/client/components/pages/edit-post-page.internal.tsx +60 -0
  222. package/src/plugins/blog/client/components/pages/edit-post-page.tsx +40 -0
  223. package/src/plugins/blog/client/components/pages/home-page.internal.tsx +71 -0
  224. package/src/plugins/blog/client/components/pages/home-page.tsx +42 -0
  225. package/src/plugins/blog/client/components/pages/new-post-page.internal.tsx +59 -0
  226. package/src/plugins/blog/client/components/pages/new-post-page.tsx +36 -0
  227. package/src/plugins/blog/client/components/pages/post-page.internal.tsx +142 -0
  228. package/src/plugins/blog/client/components/pages/post-page.tsx +38 -0
  229. package/src/plugins/blog/client/components/pages/tag-page.internal.tsx +74 -0
  230. package/src/plugins/blog/client/components/pages/tag-page.tsx +38 -0
  231. package/src/plugins/blog/client/components/shared/better-blog-attribution.tsx +19 -0
  232. package/src/plugins/blog/client/components/shared/default-error.tsx +20 -0
  233. package/src/plugins/blog/client/components/shared/defaults.tsx +9 -0
  234. package/src/plugins/blog/client/components/shared/empty-list.tsx +25 -0
  235. package/src/plugins/blog/client/components/shared/error-placeholder.tsx +20 -0
  236. package/src/plugins/blog/client/components/shared/highlight-text.tsx +80 -0
  237. package/src/plugins/blog/client/components/shared/markdown-content-styles.css +328 -0
  238. package/src/plugins/blog/client/components/shared/markdown-content.tsx +448 -0
  239. package/src/plugins/blog/client/components/shared/on-this-page.tsx +234 -0
  240. package/src/plugins/blog/client/components/shared/page-header.tsx +35 -0
  241. package/src/plugins/blog/client/components/shared/page-layout.tsx +23 -0
  242. package/src/plugins/blog/client/components/shared/page-wrapper.tsx +32 -0
  243. package/src/plugins/blog/client/components/shared/post-card.tsx +308 -0
  244. package/src/plugins/blog/client/components/shared/post-navigation.tsx +98 -0
  245. package/src/plugins/blog/client/components/shared/posts-list.tsx +67 -0
  246. package/src/plugins/blog/client/components/shared/recent-posts-carousel.tsx +79 -0
  247. package/src/plugins/blog/client/components/shared/search-input.tsx +146 -0
  248. package/src/plugins/blog/client/components/shared/search-modal.tsx +162 -0
  249. package/src/plugins/blog/client/components/shared/tags-list.tsx +34 -0
  250. package/src/plugins/blog/client/components/shared/use-route-lifecycle.tsx +68 -0
  251. package/src/plugins/blog/client/hooks/blog-hooks.tsx +623 -0
  252. package/src/plugins/blog/client/hooks/index.tsx +1 -0
  253. package/src/plugins/blog/client/hooks/use-debounce.ts +43 -0
  254. package/src/plugins/blog/client/index.ts +9 -0
  255. package/src/plugins/blog/client/localization/blog-card.ts +3 -0
  256. package/src/plugins/blog/client/localization/blog-common.ts +7 -0
  257. package/src/plugins/blog/client/localization/blog-forms.ts +45 -0
  258. package/src/plugins/blog/client/localization/blog-list.ts +14 -0
  259. package/src/plugins/blog/client/localization/blog-post.ts +9 -0
  260. package/src/plugins/blog/client/localization/index.ts +15 -0
  261. package/src/plugins/blog/client/overrides.ts +123 -0
  262. package/src/plugins/blog/client/plugin.tsx +672 -0
  263. package/src/plugins/blog/client.css +3 -0
  264. package/src/plugins/blog/db.ts +90 -0
  265. package/src/plugins/blog/query-keys.ts +267 -0
  266. package/src/plugins/blog/schemas.ts +39 -0
  267. package/src/plugins/blog/style.css +22 -0
  268. package/src/plugins/blog/types.ts +37 -0
  269. package/src/plugins/blog/utils.ts +144 -0
  270. package/src/plugins/client/index.ts +53 -0
  271. package/src/plugins/index.ts +0 -0
  272. package/src/plugins/utils.ts +35 -0
  273. package/src/types.ts +209 -0
  274. package/dist/plugins/index.cjs +0 -15
  275. package/dist/plugins/index.d.cts +0 -64
  276. package/dist/plugins/index.d.mts +0 -64
  277. package/dist/plugins/index.d.ts +0 -64
  278. package/dist/plugins/index.mjs +0 -11
  279. package/dist/shared/stack.DrUAVfIH.d.cts +0 -17
  280. package/dist/shared/stack.DrUAVfIH.d.mts +0 -17
  281. package/dist/shared/stack.DrUAVfIH.d.ts +0 -17
  282. /package/dist/{shared/stack.CktCg4PJ.cjs → plugins/utils.cjs} +0 -0
@@ -0,0 +1,40 @@
1
+ import { B as BackendPlugin } from '../../shared/stack.ByOugz9d.js';
2
+ export { C as ClientPlugin } from '../../shared/stack.ByOugz9d.js';
3
+ import { Endpoint } from 'better-call';
4
+ export { Endpoint, Router, createEndpoint, createRouter } from 'better-call';
5
+ export { Adapter, DatabaseDefinition, DbPlugin, createDbPlugin } from '@btst/db';
6
+ import '@btst/yar';
7
+
8
+ /**
9
+ * Plugin utilities and types for building standalone plugins
10
+ *
11
+ * This module exports everything needed to create custom plugins
12
+ * for Better Stack outside of this package.
13
+ *
14
+ * Note: Backend and Client plugins are separate to prevent SSR issues
15
+ * and enable better code splitting. Import them separately:
16
+ * - Backend: import type { BackendPlugin } from "@btst/stack/plugins/api"
17
+ * - Client: import type { ClientPlugin } from "@btst/stack/plugins/client"
18
+ */
19
+
20
+ /**
21
+ * Helper to define a backend plugin with full type inference
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const messagesPlugin = defineBackendPlugin({
26
+ * name: "messages",
27
+ * dbPlugin: createDbPlugin("messages", messagesSchema),
28
+ * routes: (adapter) => ({
29
+ * list: endpoint("/messages", { method: "GET" }, async () => { ... }),
30
+ * create: endpoint("/messages", { method: "POST" }, async () => { ... })
31
+ * })
32
+ * });
33
+ * // Route keys "list" and "create" are preserved in types
34
+ * ```
35
+ *
36
+ * @template TRoutes - The exact shape of routes (auto-inferred from routes function)
37
+ */
38
+ declare function defineBackendPlugin<TRoutes extends Record<string, Endpoint> = Record<string, Endpoint>>(plugin: BackendPlugin<TRoutes>): BackendPlugin<TRoutes>;
39
+
40
+ export { BackendPlugin, defineBackendPlugin };
@@ -0,0 +1,8 @@
1
+ export { createEndpoint, createRouter } from 'better-call';
2
+ export { createDbPlugin } from '@btst/db';
3
+
4
+ function defineBackendPlugin(plugin) {
5
+ return plugin;
6
+ }
7
+
8
+ export { defineBackendPlugin };
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ const plugin = require('./plugin.cjs');
4
+ const plugins_blog_queryKeys = require('../query-keys.cjs');
5
+
6
+
7
+
8
+ exports.NextPreviousPostsQuerySchema = plugin.NextPreviousPostsQuerySchema;
9
+ exports.PostListQuerySchema = plugin.PostListQuerySchema;
10
+ exports.blogBackendPlugin = plugin.blogBackendPlugin;
11
+ exports.createBlogQueryKeys = plugins_blog_queryKeys.createBlogQueryKeys;
@@ -0,0 +1,7 @@
1
+ export { B as BlogApiContext, c as BlogApiRouter, a as BlogBackendHooks, N as NextPreviousPostsQuerySchema, P as PostListQuerySchema, b as blogBackendPlugin, createBlogQueryKeys } from '../query-keys.cjs';
2
+ import '@btst/stack/plugins/api';
3
+ import 'better-call';
4
+ import 'zod';
5
+ import '../../../shared/stack.CoPoHVfV.cjs';
6
+ import '@tanstack/react-query';
7
+ import '@btst/stack/plugins/client';
@@ -0,0 +1,7 @@
1
+ export { B as BlogApiContext, c as BlogApiRouter, a as BlogBackendHooks, N as NextPreviousPostsQuerySchema, P as PostListQuerySchema, b as blogBackendPlugin, createBlogQueryKeys } from '../query-keys.mjs';
2
+ import '@btst/stack/plugins/api';
3
+ import 'better-call';
4
+ import 'zod';
5
+ import '../../../shared/stack.CoPoHVfV.mjs';
6
+ import '@tanstack/react-query';
7
+ import '@btst/stack/plugins/client';
@@ -0,0 +1,7 @@
1
+ export { B as BlogApiContext, c as BlogApiRouter, a as BlogBackendHooks, N as NextPreviousPostsQuerySchema, P as PostListQuerySchema, b as blogBackendPlugin, createBlogQueryKeys } from '../query-keys.js';
2
+ import '@btst/stack/plugins/api';
3
+ import 'better-call';
4
+ import 'zod';
5
+ import '../../../shared/stack.CoPoHVfV.js';
6
+ import '@tanstack/react-query';
7
+ import '@btst/stack/plugins/client';
@@ -0,0 +1,2 @@
1
+ export { NextPreviousPostsQuerySchema, PostListQuerySchema, blogBackendPlugin } from './plugin.mjs';
2
+ export { createBlogQueryKeys } from '../query-keys.mjs';
@@ -0,0 +1,569 @@
1
+ 'use strict';
2
+
3
+ const api = require('@btst/stack/plugins/api');
4
+ const zod = require('zod');
5
+ const db = require('../db.cjs');
6
+ const utils = require('../utils.cjs');
7
+ const schemas = require('../schemas.cjs');
8
+
9
+ const PostListQuerySchema = zod.z.object({
10
+ slug: zod.z.string().optional(),
11
+ tagSlug: zod.z.string().optional(),
12
+ offset: zod.z.coerce.number().int().min(0).optional(),
13
+ limit: zod.z.coerce.number().int().min(1).max(100).optional(),
14
+ query: zod.z.string().optional(),
15
+ published: zod.z.string().optional().transform((val) => {
16
+ if (val === void 0) return void 0;
17
+ if (val === "true") return true;
18
+ if (val === "false") return false;
19
+ return void 0;
20
+ })
21
+ });
22
+ const NextPreviousPostsQuerySchema = zod.z.object({
23
+ date: zod.z.coerce.date()
24
+ });
25
+ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
26
+ name: "blog",
27
+ dbPlugin: db.blogSchema,
28
+ routes: (adapter) => {
29
+ const createTagCache = () => {
30
+ let cache = null;
31
+ return {
32
+ getAllTags: async () => {
33
+ if (!cache) {
34
+ cache = await adapter.findMany({
35
+ model: "tag"
36
+ });
37
+ }
38
+ return cache;
39
+ },
40
+ invalidate: () => {
41
+ cache = null;
42
+ },
43
+ addTag: (tag) => {
44
+ if (cache) {
45
+ cache.push(tag);
46
+ }
47
+ }
48
+ };
49
+ };
50
+ const createPostTagCache = () => {
51
+ let cache = null;
52
+ const getAllPostTags = async () => {
53
+ if (!cache) {
54
+ cache = await adapter.findMany({
55
+ model: "postTag"
56
+ });
57
+ }
58
+ return cache;
59
+ };
60
+ return {
61
+ getAllPostTags,
62
+ invalidate: () => {
63
+ cache = null;
64
+ },
65
+ getByTagId: async (tagId) => {
66
+ const allPostTags = await getAllPostTags();
67
+ return allPostTags.filter((pt) => pt.tagId === tagId);
68
+ },
69
+ getByPostId: async (postId) => {
70
+ const allPostTags = await getAllPostTags();
71
+ return allPostTags.filter((pt) => pt.postId === postId);
72
+ }
73
+ };
74
+ };
75
+ const findOrCreateTags = async (tagInputs, tagCache) => {
76
+ if (tagInputs.length === 0) return [];
77
+ const normalizeTagName = (name) => {
78
+ return name.trim();
79
+ };
80
+ const tagsWithIds = [];
81
+ const tagsToFindOrCreate = [];
82
+ for (const tagInput of tagInputs) {
83
+ if ("id" in tagInput && tagInput.id) {
84
+ tagsWithIds.push({
85
+ id: tagInput.id,
86
+ name: normalizeTagName(tagInput.name),
87
+ slug: tagInput.slug,
88
+ createdAt: /* @__PURE__ */ new Date(),
89
+ updatedAt: /* @__PURE__ */ new Date()
90
+ });
91
+ } else {
92
+ tagsToFindOrCreate.push({ name: normalizeTagName(tagInput.name) });
93
+ }
94
+ }
95
+ if (tagsToFindOrCreate.length === 0) {
96
+ return tagsWithIds;
97
+ }
98
+ const allTags = await tagCache.getAllTags();
99
+ const tagMapBySlug = /* @__PURE__ */ new Map();
100
+ for (const tag of allTags) {
101
+ tagMapBySlug.set(tag.slug, tag);
102
+ }
103
+ const tagSlugs = tagsToFindOrCreate.map((tag) => utils.slugify(tag.name));
104
+ const foundTags = [];
105
+ for (const slug of tagSlugs) {
106
+ const tag = tagMapBySlug.get(slug);
107
+ if (tag) {
108
+ foundTags.push(tag);
109
+ }
110
+ }
111
+ const existingSlugs = /* @__PURE__ */ new Set([
112
+ ...tagsWithIds.map((tag) => tag.slug),
113
+ ...foundTags.map((tag) => tag.slug)
114
+ ]);
115
+ const tagsToCreate = tagsToFindOrCreate.filter(
116
+ (tag) => !existingSlugs.has(utils.slugify(tag.name))
117
+ );
118
+ const createdTags = [];
119
+ for (const tag of tagsToCreate) {
120
+ const normalizedName = normalizeTagName(tag.name);
121
+ const newTag = await adapter.create({
122
+ model: "tag",
123
+ data: {
124
+ name: normalizedName,
125
+ slug: utils.slugify(normalizedName),
126
+ createdAt: /* @__PURE__ */ new Date(),
127
+ updatedAt: /* @__PURE__ */ new Date()
128
+ }
129
+ });
130
+ createdTags.push(newTag);
131
+ tagCache.addTag(newTag);
132
+ }
133
+ return [...tagsWithIds, ...foundTags, ...createdTags];
134
+ };
135
+ const loadTagsForPosts = async (postIds, tagCache, postTagCache) => {
136
+ if (postIds.length === 0) return /* @__PURE__ */ new Map();
137
+ const allPostTags = await postTagCache.getAllPostTags();
138
+ const relevantPostTags = allPostTags.filter(
139
+ (pt) => postIds.includes(pt.postId)
140
+ );
141
+ const tagIds = [...new Set(relevantPostTags.map((pt) => pt.tagId))];
142
+ if (tagIds.length === 0) return /* @__PURE__ */ new Map();
143
+ const allTags = await tagCache.getAllTags();
144
+ const tagMap = /* @__PURE__ */ new Map();
145
+ for (const tag of allTags) {
146
+ tagMap.set(tag.id, tag);
147
+ }
148
+ const postTagsMap = /* @__PURE__ */ new Map();
149
+ for (const postTag of relevantPostTags) {
150
+ const tag = tagMap.get(postTag.tagId);
151
+ if (tag) {
152
+ const existing = postTagsMap.get(postTag.postId) || [];
153
+ postTagsMap.set(postTag.postId, [...existing, { ...tag }]);
154
+ }
155
+ }
156
+ return postTagsMap;
157
+ };
158
+ const listPosts = api.createEndpoint(
159
+ "/posts",
160
+ {
161
+ method: "GET",
162
+ query: PostListQuerySchema
163
+ },
164
+ async (ctx) => {
165
+ const { query } = ctx;
166
+ const context = { query };
167
+ const tagCache = createTagCache();
168
+ const postTagCache = createPostTagCache();
169
+ try {
170
+ if (hooks?.onBeforeListPosts) {
171
+ const canList = await hooks.onBeforeListPosts(query, context);
172
+ if (!canList) {
173
+ throw ctx.error(403, {
174
+ message: "Unauthorized: Cannot list posts"
175
+ });
176
+ }
177
+ }
178
+ let tagFilterPostIds = null;
179
+ if (query.tagSlug) {
180
+ const allTags = await tagCache.getAllTags();
181
+ const tag = allTags.find((t) => t.slug === query.tagSlug);
182
+ if (!tag) {
183
+ return [];
184
+ }
185
+ const postTags = await postTagCache.getByTagId(tag.id);
186
+ tagFilterPostIds = new Set(postTags.map((pt) => pt.postId));
187
+ if (tagFilterPostIds.size === 0) {
188
+ return [];
189
+ }
190
+ }
191
+ const whereConditions = [];
192
+ if (query.published !== void 0) {
193
+ whereConditions.push({
194
+ field: "published",
195
+ value: query.published,
196
+ operator: "eq"
197
+ });
198
+ }
199
+ if (query.slug) {
200
+ whereConditions.push({
201
+ field: "slug",
202
+ value: query.slug,
203
+ operator: "eq"
204
+ });
205
+ }
206
+ const posts = await adapter.findMany({
207
+ model: "post",
208
+ limit: query.query || query.tagSlug ? void 0 : query.limit ?? 10,
209
+ offset: query.query || query.tagSlug ? void 0 : query.offset ?? 0,
210
+ where: whereConditions,
211
+ sortBy: {
212
+ field: "createdAt",
213
+ direction: "desc"
214
+ }
215
+ });
216
+ const postTagsMap = await loadTagsForPosts(
217
+ posts.map((post) => post.id),
218
+ tagCache,
219
+ postTagCache
220
+ );
221
+ let result = posts.map((post) => ({
222
+ ...post,
223
+ tags: postTagsMap.get(post.id) || []
224
+ }));
225
+ if (tagFilterPostIds) {
226
+ result = result.filter((post) => tagFilterPostIds.has(post.id));
227
+ }
228
+ if (query.query) {
229
+ const searchLower = query.query.toLowerCase();
230
+ result = result.filter((post) => {
231
+ const titleMatch = post.title?.toLowerCase().includes(searchLower);
232
+ const contentMatch = post.content?.toLowerCase().includes(searchLower);
233
+ const excerptMatch = post.excerpt?.toLowerCase().includes(searchLower);
234
+ return titleMatch || contentMatch || excerptMatch;
235
+ });
236
+ }
237
+ if (query.tagSlug || query.query) {
238
+ const offset = query.offset ?? 0;
239
+ const limit = query.limit ?? 10;
240
+ result = result.slice(offset, offset + limit);
241
+ }
242
+ if (hooks?.onPostsRead) {
243
+ await hooks.onPostsRead(result, query, context);
244
+ }
245
+ return result;
246
+ } catch (error) {
247
+ if (hooks?.onListPostsError) {
248
+ await hooks.onListPostsError(error, context);
249
+ }
250
+ throw error;
251
+ }
252
+ }
253
+ );
254
+ const createPost = api.createEndpoint(
255
+ "/posts",
256
+ {
257
+ method: "POST",
258
+ body: schemas.createPostSchema
259
+ },
260
+ async (ctx) => {
261
+ const context = { body: ctx.body };
262
+ const tagCache = createTagCache();
263
+ try {
264
+ if (hooks?.onBeforeCreatePost) {
265
+ const canCreate = await hooks.onBeforeCreatePost(
266
+ ctx.body,
267
+ context
268
+ );
269
+ if (!canCreate) {
270
+ throw ctx.error(403, {
271
+ message: "Unauthorized: Cannot create post"
272
+ });
273
+ }
274
+ }
275
+ const { tags, ...postData } = ctx.body;
276
+ const tagNames = tags || [];
277
+ const newPost = await adapter.create({
278
+ model: "post",
279
+ data: {
280
+ ...postData,
281
+ slug: postData.slug ? postData.slug : utils.slugify(postData.title),
282
+ tags: [],
283
+ createdAt: /* @__PURE__ */ new Date(),
284
+ updatedAt: /* @__PURE__ */ new Date()
285
+ }
286
+ });
287
+ if (tagNames.length > 0) {
288
+ const createdTags = await findOrCreateTags(tagNames, tagCache);
289
+ await adapter.transaction(async (tx) => {
290
+ for (const tag of createdTags) {
291
+ await tx.create({
292
+ model: "postTag",
293
+ data: {
294
+ postId: newPost.id,
295
+ tagId: tag.id
296
+ }
297
+ });
298
+ }
299
+ });
300
+ newPost.tags = createdTags.map((tag) => ({ ...tag }));
301
+ } else {
302
+ newPost.tags = [];
303
+ }
304
+ if (hooks?.onPostCreated) {
305
+ await hooks.onPostCreated(newPost, context);
306
+ }
307
+ return newPost;
308
+ } catch (error) {
309
+ if (hooks?.onCreatePostError) {
310
+ await hooks.onCreatePostError(error, context);
311
+ }
312
+ throw error;
313
+ }
314
+ }
315
+ );
316
+ const updatePost = api.createEndpoint(
317
+ "/posts/:id",
318
+ {
319
+ method: "PUT",
320
+ body: schemas.updatePostSchema
321
+ },
322
+ async (ctx) => {
323
+ const context = {
324
+ body: ctx.body,
325
+ params: ctx.params
326
+ };
327
+ const tagCache = createTagCache();
328
+ try {
329
+ if (hooks?.onBeforeUpdatePost) {
330
+ const canUpdate = await hooks.onBeforeUpdatePost(
331
+ ctx.params.id,
332
+ ctx.body,
333
+ context
334
+ );
335
+ if (!canUpdate) {
336
+ throw ctx.error(403, {
337
+ message: "Unauthorized: Cannot update post"
338
+ });
339
+ }
340
+ }
341
+ const { tags, ...postData } = ctx.body;
342
+ const tagNames = tags || [];
343
+ const updated = await adapter.transaction(async (tx) => {
344
+ const existingPostTags = await tx.findMany({
345
+ model: "postTag",
346
+ where: [
347
+ {
348
+ field: "postId",
349
+ value: ctx.params.id,
350
+ operator: "eq"
351
+ }
352
+ ]
353
+ });
354
+ const updatedPost = await tx.update({
355
+ model: "post",
356
+ where: [{ field: "id", value: ctx.params.id }],
357
+ update: {
358
+ ...postData,
359
+ updatedAt: /* @__PURE__ */ new Date()
360
+ }
361
+ });
362
+ if (!updatedPost) {
363
+ throw ctx.error(404, {
364
+ message: "Post not found"
365
+ });
366
+ }
367
+ for (const postTag of existingPostTags) {
368
+ await tx.delete({
369
+ model: "postTag",
370
+ where: [
371
+ {
372
+ field: "postId",
373
+ value: postTag.postId,
374
+ operator: "eq"
375
+ },
376
+ {
377
+ field: "tagId",
378
+ value: postTag.tagId,
379
+ operator: "eq"
380
+ }
381
+ ]
382
+ });
383
+ }
384
+ if (tagNames.length > 0) {
385
+ const createdTags = await findOrCreateTags(tagNames, tagCache);
386
+ for (const tag of createdTags) {
387
+ await tx.create({
388
+ model: "postTag",
389
+ data: {
390
+ postId: ctx.params.id,
391
+ tagId: tag.id
392
+ }
393
+ });
394
+ }
395
+ updatedPost.tags = createdTags.map((tag) => ({ ...tag }));
396
+ } else {
397
+ updatedPost.tags = [];
398
+ }
399
+ return updatedPost;
400
+ });
401
+ if (hooks?.onPostUpdated) {
402
+ await hooks.onPostUpdated(updated, context);
403
+ }
404
+ return updated;
405
+ } catch (error) {
406
+ if (hooks?.onUpdatePostError) {
407
+ await hooks.onUpdatePostError(error, context);
408
+ }
409
+ throw error;
410
+ }
411
+ }
412
+ );
413
+ const deletePost = api.createEndpoint(
414
+ "/posts/:id",
415
+ {
416
+ method: "DELETE"
417
+ },
418
+ async (ctx) => {
419
+ const context = { params: ctx.params };
420
+ try {
421
+ if (hooks?.onBeforeDeletePost) {
422
+ const canDelete = await hooks.onBeforeDeletePost(
423
+ ctx.params.id,
424
+ context
425
+ );
426
+ if (!canDelete) {
427
+ throw ctx.error(403, {
428
+ message: "Unauthorized: Cannot delete post"
429
+ });
430
+ }
431
+ }
432
+ await adapter.transaction(async (tx) => {
433
+ await tx.delete({
434
+ model: "postTag",
435
+ where: [{ field: "postId", value: ctx.params.id }]
436
+ });
437
+ await tx.delete({
438
+ model: "post",
439
+ where: [{ field: "id", value: ctx.params.id }]
440
+ });
441
+ });
442
+ if (hooks?.onPostDeleted) {
443
+ await hooks.onPostDeleted(ctx.params.id, context);
444
+ }
445
+ return { success: true };
446
+ } catch (error) {
447
+ if (hooks?.onDeletePostError) {
448
+ await hooks.onDeletePostError(error, context);
449
+ }
450
+ throw error;
451
+ }
452
+ }
453
+ );
454
+ const getNextPreviousPosts = api.createEndpoint(
455
+ "/posts/next-previous",
456
+ {
457
+ method: "GET",
458
+ query: NextPreviousPostsQuerySchema
459
+ },
460
+ async (ctx) => {
461
+ const { query } = ctx;
462
+ const context = { query };
463
+ const tagCache = createTagCache();
464
+ const postTagCache = createPostTagCache();
465
+ try {
466
+ if (hooks?.onBeforeListPosts) {
467
+ const canList = await hooks.onBeforeListPosts(
468
+ { published: true },
469
+ context
470
+ );
471
+ if (!canList) {
472
+ throw ctx.error(403, {
473
+ message: "Unauthorized: Cannot list posts"
474
+ });
475
+ }
476
+ }
477
+ const date = query.date;
478
+ const previousPost = await adapter.findMany({
479
+ model: "post",
480
+ limit: 1,
481
+ where: [
482
+ {
483
+ field: "createdAt",
484
+ value: date,
485
+ operator: "lt"
486
+ },
487
+ {
488
+ field: "published",
489
+ value: true,
490
+ operator: "eq"
491
+ }
492
+ ],
493
+ sortBy: {
494
+ field: "createdAt",
495
+ direction: "desc"
496
+ }
497
+ });
498
+ const nextPost = await adapter.findMany({
499
+ model: "post",
500
+ limit: 1,
501
+ where: [
502
+ {
503
+ field: "createdAt",
504
+ value: date,
505
+ operator: "gt"
506
+ },
507
+ {
508
+ field: "published",
509
+ value: true,
510
+ operator: "eq"
511
+ }
512
+ ],
513
+ sortBy: {
514
+ field: "createdAt",
515
+ direction: "asc"
516
+ }
517
+ });
518
+ const postIds = [
519
+ ...previousPost?.[0] ? [previousPost[0].id] : [],
520
+ ...nextPost?.[0] ? [nextPost[0].id] : []
521
+ ];
522
+ const postTagsMap = await loadTagsForPosts(
523
+ postIds,
524
+ tagCache,
525
+ postTagCache
526
+ );
527
+ return {
528
+ previous: previousPost?.[0] ? {
529
+ ...previousPost[0],
530
+ tags: postTagsMap.get(previousPost[0].id) || []
531
+ } : null,
532
+ next: nextPost?.[0] ? {
533
+ ...nextPost[0],
534
+ tags: postTagsMap.get(nextPost[0].id) || []
535
+ } : null
536
+ };
537
+ } catch (error) {
538
+ if (hooks?.onListPostsError) {
539
+ await hooks.onListPostsError(error, context);
540
+ }
541
+ throw error;
542
+ }
543
+ }
544
+ );
545
+ const listTags = api.createEndpoint(
546
+ "/tags",
547
+ {
548
+ method: "GET"
549
+ },
550
+ async () => {
551
+ return await adapter.findMany({
552
+ model: "tag"
553
+ });
554
+ }
555
+ );
556
+ return {
557
+ listPosts,
558
+ createPost,
559
+ updatePost,
560
+ deletePost,
561
+ getNextPreviousPosts,
562
+ listTags
563
+ };
564
+ }
565
+ });
566
+
567
+ exports.NextPreviousPostsQuerySchema = NextPreviousPostsQuerySchema;
568
+ exports.PostListQuerySchema = PostListQuerySchema;
569
+ exports.blogBackendPlugin = blogBackendPlugin;