@btst/stack 2.4.0 → 2.5.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 (136) hide show
  1. package/dist/packages/stack/src/plugins/ai-chat/api/plugin.cjs +33 -47
  2. package/dist/packages/stack/src/plugins/ai-chat/api/plugin.mjs +33 -47
  3. package/dist/packages/stack/src/plugins/ai-chat/client/plugin.cjs +14 -21
  4. package/dist/packages/stack/src/plugins/ai-chat/client/plugin.mjs +15 -22
  5. package/dist/packages/stack/src/plugins/blog/api/plugin.cjs +28 -45
  6. package/dist/packages/stack/src/plugins/blog/api/plugin.mjs +22 -39
  7. package/dist/packages/stack/src/plugins/blog/client/plugin.cjs +23 -27
  8. package/dist/packages/stack/src/plugins/blog/client/plugin.mjs +24 -28
  9. package/dist/packages/stack/src/plugins/cms/api/plugin.cjs +14 -17
  10. package/dist/packages/stack/src/plugins/cms/api/plugin.mjs +14 -17
  11. package/dist/packages/stack/src/plugins/cms/client/plugin.cjs +11 -15
  12. package/dist/packages/stack/src/plugins/cms/client/plugin.mjs +12 -16
  13. package/dist/packages/stack/src/plugins/form-builder/api/plugin.cjs +58 -62
  14. package/dist/packages/stack/src/plugins/form-builder/api/plugin.mjs +58 -62
  15. package/dist/packages/stack/src/plugins/form-builder/client/plugin.cjs +12 -12
  16. package/dist/packages/stack/src/plugins/form-builder/client/plugin.mjs +13 -13
  17. package/dist/packages/stack/src/plugins/kanban/api/plugin.cjs +86 -117
  18. package/dist/packages/stack/src/plugins/kanban/api/plugin.mjs +83 -114
  19. package/dist/packages/stack/src/plugins/kanban/client/plugin.cjs +22 -29
  20. package/dist/packages/stack/src/plugins/kanban/client/plugin.mjs +23 -30
  21. package/dist/packages/stack/src/plugins/ui-builder/client/plugin.cjs +8 -8
  22. package/dist/packages/stack/src/plugins/ui-builder/client/plugin.mjs +9 -9
  23. package/dist/packages/stack/src/plugins/utils.cjs +42 -0
  24. package/dist/packages/stack/src/plugins/utils.mjs +41 -1
  25. package/dist/plugins/ai-chat/api/index.d.cts +1 -1
  26. package/dist/plugins/ai-chat/api/index.d.mts +1 -1
  27. package/dist/plugins/ai-chat/api/index.d.ts +1 -1
  28. package/dist/plugins/ai-chat/client/hooks/index.d.cts +1 -1
  29. package/dist/plugins/ai-chat/client/hooks/index.d.mts +1 -1
  30. package/dist/plugins/ai-chat/client/hooks/index.d.ts +1 -1
  31. package/dist/plugins/ai-chat/client/index.d.cts +8 -8
  32. package/dist/plugins/ai-chat/client/index.d.mts +8 -8
  33. package/dist/plugins/ai-chat/client/index.d.ts +8 -8
  34. package/dist/plugins/ai-chat/query-keys.d.cts +1 -1
  35. package/dist/plugins/ai-chat/query-keys.d.mts +1 -1
  36. package/dist/plugins/ai-chat/query-keys.d.ts +1 -1
  37. package/dist/plugins/blog/api/index.d.cts +1 -1
  38. package/dist/plugins/blog/api/index.d.mts +1 -1
  39. package/dist/plugins/blog/api/index.d.ts +1 -1
  40. package/dist/plugins/blog/client/index.d.cts +12 -12
  41. package/dist/plugins/blog/client/index.d.mts +12 -12
  42. package/dist/plugins/blog/client/index.d.ts +12 -12
  43. package/dist/plugins/blog/query-keys.d.cts +1 -1
  44. package/dist/plugins/blog/query-keys.d.mts +1 -1
  45. package/dist/plugins/blog/query-keys.d.ts +1 -1
  46. package/dist/plugins/client/index.cjs +1 -0
  47. package/dist/plugins/client/index.d.cts +8 -1
  48. package/dist/plugins/client/index.d.mts +8 -1
  49. package/dist/plugins/client/index.d.ts +8 -1
  50. package/dist/plugins/client/index.mjs +1 -1
  51. package/dist/plugins/cms/api/index.d.cts +2 -2
  52. package/dist/plugins/cms/api/index.d.mts +2 -2
  53. package/dist/plugins/cms/api/index.d.ts +2 -2
  54. package/dist/plugins/cms/client/hooks/index.d.cts +1 -1
  55. package/dist/plugins/cms/client/hooks/index.d.mts +1 -1
  56. package/dist/plugins/cms/client/hooks/index.d.ts +1 -1
  57. package/dist/plugins/cms/client/index.d.cts +6 -6
  58. package/dist/plugins/cms/client/index.d.mts +6 -6
  59. package/dist/plugins/cms/client/index.d.ts +6 -6
  60. package/dist/plugins/cms/query-keys.d.cts +2 -2
  61. package/dist/plugins/cms/query-keys.d.mts +2 -2
  62. package/dist/plugins/cms/query-keys.d.ts +2 -2
  63. package/dist/plugins/form-builder/api/index.d.cts +2 -2
  64. package/dist/plugins/form-builder/api/index.d.mts +2 -2
  65. package/dist/plugins/form-builder/api/index.d.ts +2 -2
  66. package/dist/plugins/form-builder/client/components/index.d.cts +1 -1
  67. package/dist/plugins/form-builder/client/components/index.d.mts +1 -1
  68. package/dist/plugins/form-builder/client/components/index.d.ts +1 -1
  69. package/dist/plugins/form-builder/client/hooks/index.d.cts +1 -1
  70. package/dist/plugins/form-builder/client/hooks/index.d.mts +1 -1
  71. package/dist/plugins/form-builder/client/hooks/index.d.ts +1 -1
  72. package/dist/plugins/form-builder/client/index.d.cts +6 -6
  73. package/dist/plugins/form-builder/client/index.d.mts +6 -6
  74. package/dist/plugins/form-builder/client/index.d.ts +6 -6
  75. package/dist/plugins/form-builder/query-keys.d.cts +2 -2
  76. package/dist/plugins/form-builder/query-keys.d.mts +2 -2
  77. package/dist/plugins/form-builder/query-keys.d.ts +2 -2
  78. package/dist/plugins/kanban/api/index.d.cts +1 -1
  79. package/dist/plugins/kanban/api/index.d.mts +1 -1
  80. package/dist/plugins/kanban/api/index.d.ts +1 -1
  81. package/dist/plugins/kanban/client/index.d.cts +12 -12
  82. package/dist/plugins/kanban/client/index.d.mts +12 -12
  83. package/dist/plugins/kanban/client/index.d.ts +12 -12
  84. package/dist/plugins/kanban/query-keys.d.cts +1 -1
  85. package/dist/plugins/kanban/query-keys.d.mts +1 -1
  86. package/dist/plugins/kanban/query-keys.d.ts +1 -1
  87. package/dist/plugins/ui-builder/client/hooks/index.d.cts +1 -1
  88. package/dist/plugins/ui-builder/client/hooks/index.d.mts +1 -1
  89. package/dist/plugins/ui-builder/client/hooks/index.d.ts +1 -1
  90. package/dist/plugins/ui-builder/client/index.d.cts +3 -3
  91. package/dist/plugins/ui-builder/client/index.d.mts +3 -3
  92. package/dist/plugins/ui-builder/client/index.d.ts +3 -3
  93. package/dist/plugins/ui-builder/index.d.cts +2 -2
  94. package/dist/plugins/ui-builder/index.d.mts +2 -2
  95. package/dist/plugins/ui-builder/index.d.ts +2 -2
  96. package/dist/shared/{stack.C-WUPMT6.d.cts → stack.B2xZTSiO.d.cts} +4 -4
  97. package/dist/shared/{stack.CczspVn2.d.mts → stack.B58oHdqm.d.mts} +1 -1
  98. package/dist/shared/{stack.CVDTkMoO.d.mts → stack.B8QD11QU.d.cts} +7 -7
  99. package/dist/shared/{stack.CVDTkMoO.d.cts → stack.B8QD11QU.d.mts} +7 -7
  100. package/dist/shared/{stack.CVDTkMoO.d.ts → stack.B8QD11QU.d.ts} +7 -7
  101. package/dist/shared/{stack.Kq2-QzOC.d.ts → stack.BDVEpue1.d.ts} +2 -2
  102. package/dist/shared/{stack.B7ONvlD_.d.mts → stack.BTvbxZvw.d.cts} +2 -2
  103. package/dist/shared/{stack.DdI5W6MB.d.mts → stack.BozPgbrZ.d.cts} +19 -19
  104. package/dist/shared/{stack.DdI5W6MB.d.cts → stack.BozPgbrZ.d.mts} +19 -19
  105. package/dist/shared/{stack.DdI5W6MB.d.ts → stack.BozPgbrZ.d.ts} +19 -19
  106. package/dist/shared/{stack.BUkC2EsZ.d.cts → stack.C9Mg2Q46.d.cts} +1 -1
  107. package/dist/shared/{stack.BEn34wW6.d.ts → stack.CTDVxbrA.d.ts} +12 -12
  108. package/dist/shared/{stack.C-Ptrz8s.d.ts → stack.Cj_zKww4.d.ts} +4 -4
  109. package/dist/shared/{stack.BepFXT3w.d.mts → stack.CxaFNQCV.d.mts} +25 -25
  110. package/dist/shared/{stack.DWoCZff7.d.cts → stack.D-b5zbPm.d.cts} +12 -12
  111. package/dist/shared/{stack.kcdnD4gA.d.cts → stack.DTtmJPQO.d.mts} +2 -2
  112. package/dist/shared/{stack.CL8ts1Mu.d.ts → stack.DXnclTG7.d.ts} +8 -8
  113. package/dist/shared/{stack.heOA9gzA.d.cts → stack.DaZM10cp.d.cts} +8 -8
  114. package/dist/shared/{stack.DTDxgFj8.d.mts → stack.FVWf2JhZ.d.mts} +12 -12
  115. package/dist/shared/{stack.Dk5r4W1F.d.mts → stack.cfCkioTe.d.mts} +8 -8
  116. package/dist/shared/{stack.6fUOjLs9.d.mts → stack.dH7u-TJH.d.mts} +4 -4
  117. package/dist/shared/{stack.CgWzG5jH.d.ts → stack.j75TpKh2.d.ts} +25 -25
  118. package/dist/shared/{stack.D3GB6wKv.d.cts → stack.n1_i1p2B.d.cts} +25 -25
  119. package/dist/shared/{stack.DASmUVjX.d.ts → stack.sO33ZDhK.d.ts} +1 -1
  120. package/package.json +1 -1
  121. package/src/plugins/ai-chat/api/plugin.ts +48 -63
  122. package/src/plugins/ai-chat/client/plugin.tsx +23 -31
  123. package/src/plugins/blog/api/plugin.ts +31 -47
  124. package/src/plugins/blog/client/plugin.tsx +36 -39
  125. package/src/plugins/client/index.ts +5 -1
  126. package/src/plugins/cms/api/plugin.ts +14 -17
  127. package/src/plugins/cms/client/plugin.tsx +18 -21
  128. package/src/plugins/cms/types.ts +7 -7
  129. package/src/plugins/form-builder/api/plugin.ts +64 -64
  130. package/src/plugins/form-builder/client/plugin.tsx +19 -18
  131. package/src/plugins/form-builder/types.ts +19 -24
  132. package/src/plugins/kanban/api/plugin.ts +111 -136
  133. package/src/plugins/kanban/client/plugin.tsx +35 -41
  134. package/src/plugins/ui-builder/client/plugin.tsx +11 -10
  135. package/src/plugins/ui-builder/types.ts +4 -4
  136. package/src/plugins/utils.ts +92 -1
@@ -7,12 +7,12 @@ import { QueryClient } from '@tanstack/react-query';
7
7
 
8
8
  declare const createBoardSchema: z.ZodObject<{
9
9
  description: z.ZodOptional<z.ZodString>;
10
+ name: z.ZodString;
10
11
  slug: z.ZodOptional<z.ZodString>;
11
12
  ownerId: z.ZodOptional<z.ZodString>;
12
13
  organizationId: z.ZodOptional<z.ZodString>;
13
14
  createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
14
15
  updatedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
15
- name: z.ZodString;
16
16
  }, z.core.$strip>;
17
17
  declare const updateBoardSchema: z.ZodObject<{
18
18
  createdAt: z.ZodOptional<z.ZodOptional<z.ZodCoercedDate<unknown>>>;
@@ -189,25 +189,25 @@ interface KanbanApiContext<TBody = unknown, TParams = unknown, TQuery = unknown>
189
189
  */
190
190
  interface KanbanBackendHooks {
191
191
  /**
192
- * Called before listing boards. Return false to deny access.
192
+ * Called before listing boards. Throw an error to deny access.
193
193
  */
194
- onBeforeListBoards?: (filter: z.infer<typeof BoardListQuerySchema>, context: KanbanApiContext) => Promise<boolean> | boolean;
194
+ onBeforeListBoards?: (filter: z.infer<typeof BoardListQuerySchema>, context: KanbanApiContext) => Promise<void> | void;
195
195
  /**
196
- * Called before creating a board. Return false to deny access.
196
+ * Called before creating a board. Throw an error to deny access.
197
197
  */
198
- onBeforeCreateBoard?: (data: z.infer<typeof createBoardSchema>, context: KanbanApiContext) => Promise<boolean> | boolean;
198
+ onBeforeCreateBoard?: (data: z.infer<typeof createBoardSchema>, context: KanbanApiContext) => Promise<void> | void;
199
199
  /**
200
- * Called before reading a single board. Return false to deny access.
200
+ * Called before reading a single board. Throw an error to deny access.
201
201
  */
202
- onBeforeReadBoard?: (boardId: string, context: KanbanApiContext) => Promise<boolean> | boolean;
202
+ onBeforeReadBoard?: (boardId: string, context: KanbanApiContext) => Promise<void> | void;
203
203
  /**
204
- * Called before updating a board. Return false to deny access.
204
+ * Called before updating a board. Throw an error to deny access.
205
205
  */
206
- onBeforeUpdateBoard?: (boardId: string, data: z.infer<typeof updateBoardSchema>, context: KanbanApiContext) => Promise<boolean> | boolean;
206
+ onBeforeUpdateBoard?: (boardId: string, data: z.infer<typeof updateBoardSchema>, context: KanbanApiContext) => Promise<void> | void;
207
207
  /**
208
- * Called before deleting a board. Return false to deny access.
208
+ * Called before deleting a board. Throw an error to deny access.
209
209
  */
210
- onBeforeDeleteBoard?: (boardId: string, context: KanbanApiContext) => Promise<boolean> | boolean;
210
+ onBeforeDeleteBoard?: (boardId: string, context: KanbanApiContext) => Promise<void> | void;
211
211
  /**
212
212
  * Called after boards are listed successfully.
213
213
  * Receives the items array (same shape as `board[]`) for consistency
@@ -251,17 +251,17 @@ interface KanbanBackendHooks {
251
251
  */
252
252
  onDeleteBoardError?: (error: Error, context: KanbanApiContext) => Promise<void> | void;
253
253
  /**
254
- * Called before creating a column. Return false to deny access.
254
+ * Called before creating a column. Throw an error to deny access.
255
255
  */
256
- onBeforeCreateColumn?: (data: z.infer<typeof createColumnSchema>, context: KanbanApiContext) => Promise<boolean> | boolean;
256
+ onBeforeCreateColumn?: (data: z.infer<typeof createColumnSchema>, context: KanbanApiContext) => Promise<void> | void;
257
257
  /**
258
- * Called before updating a column. Return false to deny access.
258
+ * Called before updating a column. Throw an error to deny access.
259
259
  */
260
- onBeforeUpdateColumn?: (columnId: string, data: z.infer<typeof updateColumnSchema>, context: KanbanApiContext) => Promise<boolean> | boolean;
260
+ onBeforeUpdateColumn?: (columnId: string, data: z.infer<typeof updateColumnSchema>, context: KanbanApiContext) => Promise<void> | void;
261
261
  /**
262
- * Called before deleting a column. Return false to deny access.
262
+ * Called before deleting a column. Throw an error to deny access.
263
263
  */
264
- onBeforeDeleteColumn?: (columnId: string, context: KanbanApiContext) => Promise<boolean> | boolean;
264
+ onBeforeDeleteColumn?: (columnId: string, context: KanbanApiContext) => Promise<void> | void;
265
265
  /**
266
266
  * Called after a column is created successfully
267
267
  */
@@ -275,17 +275,17 @@ interface KanbanBackendHooks {
275
275
  */
276
276
  onColumnDeleted?: (columnId: string, context: KanbanApiContext) => Promise<void> | void;
277
277
  /**
278
- * Called before creating a task. Return false to deny access.
278
+ * Called before creating a task. Throw an error to deny access.
279
279
  */
280
- onBeforeCreateTask?: (data: z.infer<typeof createTaskSchema>, context: KanbanApiContext) => Promise<boolean> | boolean;
280
+ onBeforeCreateTask?: (data: z.infer<typeof createTaskSchema>, context: KanbanApiContext) => Promise<void> | void;
281
281
  /**
282
- * Called before updating a task. Return false to deny access.
282
+ * Called before updating a task. Throw an error to deny access.
283
283
  */
284
- onBeforeUpdateTask?: (taskId: string, data: z.infer<typeof updateTaskSchema>, context: KanbanApiContext) => Promise<boolean> | boolean;
284
+ onBeforeUpdateTask?: (taskId: string, data: z.infer<typeof updateTaskSchema>, context: KanbanApiContext) => Promise<void> | void;
285
285
  /**
286
- * Called before deleting a task. Return false to deny access.
286
+ * Called before deleting a task. Throw an error to deny access.
287
287
  */
288
- onBeforeDeleteTask?: (taskId: string, context: KanbanApiContext) => Promise<boolean> | boolean;
288
+ onBeforeDeleteTask?: (taskId: string, context: KanbanApiContext) => Promise<void> | void;
289
289
  /**
290
290
  * Called after a task is created successfully
291
291
  */
@@ -323,12 +323,12 @@ declare const kanbanBackendPlugin: (hooks?: KanbanBackendHooks) => _btst_stack_p
323
323
  method: "POST";
324
324
  body: z.ZodObject<{
325
325
  description: z.ZodOptional<z.ZodString>;
326
+ name: z.ZodString;
326
327
  slug: z.ZodOptional<z.ZodString>;
327
328
  ownerId: z.ZodOptional<z.ZodString>;
328
329
  organizationId: z.ZodOptional<z.ZodString>;
329
330
  createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
330
331
  updatedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
331
- name: z.ZodString;
332
332
  }, z.core.$strip>;
333
333
  }, {
334
334
  columns: ColumnWithTasks[];
@@ -345,12 +345,12 @@ declare const kanbanBackendPlugin: (hooks?: KanbanBackendHooks) => _btst_stack_p
345
345
  method: "PUT";
346
346
  body: z.ZodObject<{
347
347
  description: z.ZodOptional<z.ZodOptional<z.ZodString>>;
348
+ name: z.ZodOptional<z.ZodString>;
348
349
  slug: z.ZodOptional<z.ZodString>;
349
350
  ownerId: z.ZodOptional<z.ZodOptional<z.ZodString>>;
350
351
  organizationId: z.ZodOptional<z.ZodOptional<z.ZodString>>;
351
352
  createdAt: z.ZodOptional<z.ZodOptional<z.ZodCoercedDate<unknown>>>;
352
353
  updatedAt: z.ZodOptional<z.ZodOptional<z.ZodCoercedDate<unknown>>>;
353
- name: z.ZodOptional<z.ZodString>;
354
354
  }, z.core.$strip>;
355
355
  }, Board>;
356
356
  readonly deleteBoard: better_call.StrictEndpoint<"/boards/:id", {
@@ -1,5 +1,5 @@
1
1
  import * as _btst_stack_plugins_api from '@btst/stack/plugins/api';
2
- import { a as SerializedContentItemWithType, S as SerializedContentType, b as ContentType, c as ContentItem, d as SerializedContentItem, e as ContentItemWithType, f as CMSBackendConfig, I as InverseRelation } from './stack.CVDTkMoO.js';
2
+ import { a as SerializedContentItemWithType, S as SerializedContentType, b as ContentType, c as ContentItem, d as SerializedContentItem, e as ContentItemWithType, f as CMSBackendConfig, I as InverseRelation } from './stack.B8QD11QU.js';
3
3
  import * as better_call from 'better-call';
4
4
  import { z } from 'zod';
5
5
  import { Adapter } from '@btst/db';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btst/stack",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "A composable, plugin-based library for building full-stack applications.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,6 +21,7 @@ import {
21
21
  BUILT_IN_PAGE_TOOL_ROUTE_ALLOWLIST,
22
22
  BUILT_IN_PAGE_TOOL_SCHEMAS,
23
23
  } from "./page-tools";
24
+ import { runHookWithShim } from "../../utils";
24
25
 
25
26
  /**
26
27
  * Context passed to AI Chat API hooks
@@ -40,48 +41,46 @@ export interface ChatApiContext<TBody = any, TParams = any, TQuery = any> {
40
41
  */
41
42
  export interface AiChatBackendHooks {
42
43
  // ============== Authorization Hooks ==============
43
- // Return false to deny access
44
+ // Throw an error to deny access
44
45
 
45
46
  /**
46
- * Called before processing a chat message. Return false to deny access.
47
+ * Called before processing a chat message. Throw an error to deny access.
47
48
  * @param messages - Array of messages being sent
48
49
  * @param context - Request context with headers, etc.
49
50
  */
50
51
  onBeforeChat?: (
51
52
  messages: Array<{ role: string; content: string }>,
52
53
  context: ChatApiContext,
53
- ) => Promise<boolean> | boolean;
54
+ ) => Promise<void> | void;
54
55
 
55
56
  /**
56
- * Called before listing conversations. Return false to deny access.
57
+ * Called before listing conversations. Throw an error to deny access.
57
58
  * @param context - Request context with headers, etc.
58
59
  */
59
- onBeforeListConversations?: (
60
- context: ChatApiContext,
61
- ) => Promise<boolean> | boolean;
60
+ onBeforeListConversations?: (context: ChatApiContext) => Promise<void> | void;
62
61
 
63
62
  /**
64
- * Called before getting a single conversation. Return false to deny access.
63
+ * Called before getting a single conversation. Throw an error to deny access.
65
64
  * @param conversationId - ID of the conversation being accessed
66
65
  * @param context - Request context with headers, etc.
67
66
  */
68
67
  onBeforeGetConversation?: (
69
68
  conversationId: string,
70
69
  context: ChatApiContext,
71
- ) => Promise<boolean> | boolean;
70
+ ) => Promise<void> | void;
72
71
 
73
72
  /**
74
- * Called before creating a conversation. Return false to deny access.
73
+ * Called before creating a conversation. Throw an error to deny access.
75
74
  * @param data - Conversation data being created
76
75
  * @param context - Request context with headers, etc.
77
76
  */
78
77
  onBeforeCreateConversation?: (
79
78
  data: { id?: string; title?: string },
80
79
  context: ChatApiContext,
81
- ) => Promise<boolean> | boolean;
80
+ ) => Promise<void> | void;
82
81
 
83
82
  /**
84
- * Called before updating a conversation. Return false to deny access.
83
+ * Called before updating a conversation. Throw an error to deny access.
85
84
  * @param conversationId - ID of the conversation being updated
86
85
  * @param data - Updated conversation data
87
86
  * @param context - Request context with headers, etc.
@@ -90,17 +89,17 @@ export interface AiChatBackendHooks {
90
89
  conversationId: string,
91
90
  data: { title?: string },
92
91
  context: ChatApiContext,
93
- ) => Promise<boolean> | boolean;
92
+ ) => Promise<void> | void;
94
93
 
95
94
  /**
96
- * Called before deleting a conversation. Return false to deny access.
95
+ * Called before deleting a conversation. Throw an error to deny access.
97
96
  * @param conversationId - ID of the conversation being deleted
98
97
  * @param context - Request context with headers, etc.
99
98
  */
100
99
  onBeforeDeleteConversation?: (
101
100
  conversationId: string,
102
101
  context: ChatApiContext,
103
- ) => Promise<boolean> | boolean;
102
+ ) => Promise<void> | void;
104
103
 
105
104
  /**
106
105
  * Called after the structural routeName/allowlist validation, with the list
@@ -452,15 +451,11 @@ export const aiChatBackendPlugin = <
452
451
  role: msg.role,
453
452
  content: getMessageTextContent(msg),
454
453
  }));
455
- const canChat = await config.hooks.onBeforeChat(
456
- messagesForHook,
457
- context,
454
+ await runHookWithShim(
455
+ () => config.hooks!.onBeforeChat!(messagesForHook, context),
456
+ ctx.error,
457
+ "Unauthorized: Cannot start chat",
458
458
  );
459
- if (!canChat) {
460
- throw ctx.error(403, {
461
- message: "Unauthorized: Cannot start chat",
462
- });
463
- }
464
459
  }
465
460
 
466
461
  const firstMessage = uiMessages[0];
@@ -848,15 +843,15 @@ export const aiChatBackendPlugin = <
848
843
 
849
844
  // Authorization hook
850
845
  if (config.hooks?.onBeforeCreateConversation) {
851
- const canCreate = await config.hooks.onBeforeCreateConversation(
852
- { id, title },
853
- context,
846
+ await runHookWithShim(
847
+ () =>
848
+ config.hooks!.onBeforeCreateConversation!(
849
+ { id, title },
850
+ context,
851
+ ),
852
+ ctx.error,
853
+ "Unauthorized: Cannot create conversation",
854
854
  );
855
- if (!canCreate) {
856
- throw ctx.error(403, {
857
- message: "Unauthorized: Cannot create conversation",
858
- });
859
- }
860
855
  }
861
856
 
862
857
  const newConv = await adapter.create<Conversation>({
@@ -914,13 +909,11 @@ export const aiChatBackendPlugin = <
914
909
 
915
910
  // Authorization hook
916
911
  if (config.hooks?.onBeforeListConversations) {
917
- const canList =
918
- await config.hooks.onBeforeListConversations(context);
919
- if (!canList) {
920
- throw ctx.error(403, {
921
- message: "Unauthorized: Cannot list conversations",
922
- });
923
- }
912
+ await runHookWithShim(
913
+ () => config.hooks!.onBeforeListConversations!(context),
914
+ ctx.error,
915
+ "Unauthorized: Cannot list conversations",
916
+ );
924
917
  }
925
918
 
926
919
  // Build where conditions - filter by userId if set
@@ -991,15 +984,11 @@ export const aiChatBackendPlugin = <
991
984
 
992
985
  // Authorization hook
993
986
  if (config.hooks?.onBeforeGetConversation) {
994
- const canGet = await config.hooks.onBeforeGetConversation(
995
- id,
996
- context,
987
+ await runHookWithShim(
988
+ () => config.hooks!.onBeforeGetConversation!(id, context),
989
+ ctx.error,
990
+ "Unauthorized: Cannot get conversation",
997
991
  );
998
- if (!canGet) {
999
- throw ctx.error(403, {
1000
- message: "Unauthorized: Cannot get conversation",
1001
- });
1002
- }
1003
992
  }
1004
993
 
1005
994
  // Fetch conversation with messages in a single query using join
@@ -1116,16 +1105,16 @@ export const aiChatBackendPlugin = <
1116
1105
 
1117
1106
  // Authorization hook
1118
1107
  if (config.hooks?.onBeforeUpdateConversation) {
1119
- const canUpdate = await config.hooks.onBeforeUpdateConversation(
1120
- id,
1121
- { title },
1122
- context,
1108
+ await runHookWithShim(
1109
+ () =>
1110
+ config.hooks!.onBeforeUpdateConversation!(
1111
+ id,
1112
+ { title },
1113
+ context,
1114
+ ),
1115
+ ctx.error,
1116
+ "Unauthorized: Cannot update conversation",
1123
1117
  );
1124
- if (!canUpdate) {
1125
- throw ctx.error(403, {
1126
- message: "Unauthorized: Cannot update conversation",
1127
- });
1128
- }
1129
1118
  }
1130
1119
 
1131
1120
  const updated = await adapter.update<Conversation>({
@@ -1211,15 +1200,11 @@ export const aiChatBackendPlugin = <
1211
1200
 
1212
1201
  // Authorization hook
1213
1202
  if (config.hooks?.onBeforeDeleteConversation) {
1214
- const canDelete = await config.hooks.onBeforeDeleteConversation(
1215
- id,
1216
- context,
1203
+ await runHookWithShim(
1204
+ () => config.hooks!.onBeforeDeleteConversation!(id, context),
1205
+ ctx.error,
1206
+ "Unauthorized: Cannot delete conversation",
1217
1207
  );
1218
- if (!canDelete) {
1219
- throw ctx.error(403, {
1220
- message: "Unauthorized: Cannot delete conversation",
1221
- });
1222
- }
1223
1208
  }
1224
1209
 
1225
1210
  // Messages are automatically deleted via cascade (onDelete: "cascade")
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  defineClientPlugin,
3
3
  createApiClient,
4
+ runClientHookWithShim,
4
5
  } from "@btst/stack/plugins/client";
5
6
  import { createRoute } from "@btst/yar";
6
7
  import type { QueryClient } from "@tanstack/react-query";
@@ -92,35 +93,33 @@ export interface AiChatClientConfig {
92
93
  */
93
94
  export interface AiChatClientHooks {
94
95
  /**
95
- * Called before loading conversations list. Return false to cancel loading.
96
+ * Called before loading conversations list. Throw an error to cancel loading.
96
97
  * @param context - Loader context with path, params, etc.
97
98
  */
98
- beforeLoadConversations?: (
99
- context: LoaderContext,
100
- ) => Promise<boolean> | boolean;
99
+ beforeLoadConversations?: (context: LoaderContext) => Promise<void> | void;
101
100
 
102
101
  /**
103
- * Called after conversations are loaded. Return false to cancel further processing.
102
+ * Called after conversations are loaded. Throw an error to cancel further processing.
104
103
  * @param conversations - Array of loaded conversations or null
105
104
  * @param context - Loader context
106
105
  */
107
106
  afterLoadConversations?: (
108
107
  conversations: SerializedConversation[] | null,
109
108
  context: LoaderContext,
110
- ) => Promise<boolean> | boolean;
109
+ ) => Promise<void> | void;
111
110
 
112
111
  /**
113
- * Called before loading a single conversation. Return false to cancel loading.
112
+ * Called before loading a single conversation. Throw an error to cancel loading.
114
113
  * @param id - Conversation ID being loaded
115
114
  * @param context - Loader context
116
115
  */
117
116
  beforeLoadConversation?: (
118
117
  id: string,
119
118
  context: LoaderContext,
120
- ) => Promise<boolean> | boolean;
119
+ ) => Promise<void> | void;
121
120
 
122
121
  /**
123
- * Called after a conversation is loaded. Return false to cancel further processing.
122
+ * Called after a conversation is loaded. Throw an error to cancel further processing.
124
123
  * @param conversation - Loaded conversation or null if not found
125
124
  * @param id - Conversation ID that was requested
126
125
  * @param context - Loader context
@@ -131,7 +130,7 @@ export interface AiChatClientHooks {
131
130
  | null,
132
131
  id: string,
133
132
  context: LoaderContext,
134
- ) => Promise<boolean> | boolean;
133
+ ) => Promise<void> | void;
135
134
 
136
135
  /**
137
136
  * Called when a loading error occurs
@@ -163,10 +162,10 @@ function createConversationsLoader(config: AiChatClientConfig) {
163
162
  try {
164
163
  // Before hook
165
164
  if (hooks?.beforeLoadConversations) {
166
- const canLoad = await hooks.beforeLoadConversations(context);
167
- if (!canLoad) {
168
- throw new Error("Load prevented by beforeLoadConversations hook");
169
- }
165
+ await runClientHookWithShim(
166
+ () => hooks.beforeLoadConversations!(context),
167
+ "Load prevented by beforeLoadConversations hook",
168
+ );
170
169
  }
171
170
 
172
171
  const client = createApiClient<AiChatApiRouter>({
@@ -185,13 +184,10 @@ function createConversationsLoader(config: AiChatClientConfig) {
185
184
  queryClient.getQueryData<SerializedConversation[]>(
186
185
  listQuery.queryKey,
187
186
  ) || null;
188
- const canContinue = await hooks.afterLoadConversations(
189
- conversations,
190
- context,
187
+ await runClientHookWithShim(
188
+ () => hooks.afterLoadConversations!(conversations, context),
189
+ "Load prevented by afterLoadConversations hook",
191
190
  );
192
- if (canContinue === false) {
193
- throw new Error("Load prevented by afterLoadConversations hook");
194
- }
195
191
  }
196
192
 
197
193
  // Check for errors
@@ -230,10 +226,10 @@ function createConversationLoader(id: string, config: AiChatClientConfig) {
230
226
  try {
231
227
  // Before hook
232
228
  if (hooks?.beforeLoadConversation) {
233
- const canLoad = await hooks.beforeLoadConversation(id, context);
234
- if (!canLoad) {
235
- throw new Error("Load prevented by beforeLoadConversation hook");
236
- }
229
+ await runClientHookWithShim(
230
+ () => hooks.beforeLoadConversation!(id, context),
231
+ "Load prevented by beforeLoadConversation hook",
232
+ );
237
233
  }
238
234
 
239
235
  const client = createApiClient<AiChatApiRouter>({
@@ -258,14 +254,10 @@ function createConversationLoader(id: string, config: AiChatClientConfig) {
258
254
  queryClient.getQueryData<
259
255
  SerializedConversation & { messages: SerializedMessage[] }
260
256
  >(conversationQuery.queryKey) || null;
261
- const canContinue = await hooks.afterLoadConversation(
262
- conversation,
263
- id,
264
- context,
257
+ await runClientHookWithShim(
258
+ () => hooks.afterLoadConversation!(conversation, id, context),
259
+ "Load prevented by afterLoadConversation hook",
265
260
  );
266
- if (canContinue === false) {
267
- throw new Error("Load prevented by afterLoadConversation hook");
268
- }
269
261
  }
270
262
 
271
263
  // Check for errors
@@ -10,6 +10,7 @@ import { getAllPosts, getPostBySlug, getAllTags } from "./getters";
10
10
  import { BLOG_QUERY_KEYS } from "./query-key-defs";
11
11
  import { serializePost, serializeTag } from "./serializers";
12
12
  import type { QueryClient } from "@tanstack/react-query";
13
+ import { runHookWithShim } from "../../utils";
13
14
 
14
15
  /**
15
16
  * Route keys for the blog plugin — matches the keys returned by
@@ -131,25 +132,25 @@ export interface BlogApiContext<TBody = any, TParams = any, TQuery = any> {
131
132
  */
132
133
  export interface BlogBackendHooks {
133
134
  /**
134
- * Called before listing posts. Return false to deny access.
135
+ * Called before listing posts. Throw an error to deny access.
135
136
  * @param filter - Query parameters for filtering posts
136
137
  * @param context - Request context with headers, etc.
137
138
  */
138
139
  onBeforeListPosts?: (
139
140
  filter: z.infer<typeof PostListQuerySchema>,
140
141
  context: BlogApiContext,
141
- ) => Promise<boolean> | boolean;
142
+ ) => Promise<void> | void;
142
143
  /**
143
- * Called before creating a post. Return false to deny access.
144
+ * Called before creating a post. Throw an error to deny access.
144
145
  * @param data - Post data being created
145
146
  * @param context - Request context with headers, etc.
146
147
  */
147
148
  onBeforeCreatePost?: (
148
149
  data: z.infer<typeof createPostSchema>,
149
150
  context: BlogApiContext,
150
- ) => Promise<boolean> | boolean;
151
+ ) => Promise<void> | void;
151
152
  /**
152
- * Called before updating a post. Return false to deny access.
153
+ * Called before updating a post. Throw an error to deny access.
153
154
  * @param postId - ID of the post being updated
154
155
  * @param data - Updated post data
155
156
  * @param context - Request context with headers, etc.
@@ -158,16 +159,16 @@ export interface BlogBackendHooks {
158
159
  postId: string,
159
160
  data: z.infer<typeof updatePostSchema>,
160
161
  context: BlogApiContext,
161
- ) => Promise<boolean> | boolean;
162
+ ) => Promise<void> | void;
162
163
  /**
163
- * Called before deleting a post. Return false to deny access.
164
+ * Called before deleting a post. Throw an error to deny access.
164
165
  * @param postId - ID of the post being deleted
165
166
  * @param context - Request context with headers, etc.
166
167
  */
167
168
  onBeforeDeletePost?: (
168
169
  postId: string,
169
170
  context: BlogApiContext,
170
- ) => Promise<boolean> | boolean;
171
+ ) => Promise<void> | void;
171
172
 
172
173
  /**
173
174
  * Called after posts are read successfully
@@ -350,12 +351,11 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
350
351
 
351
352
  try {
352
353
  if (hooks?.onBeforeListPosts) {
353
- const canList = await hooks.onBeforeListPosts(query, context);
354
- if (!canList) {
355
- throw ctx.error(403, {
356
- message: "Unauthorized: Cannot list posts",
357
- });
358
- }
354
+ await runHookWithShim(
355
+ () => hooks.onBeforeListPosts!(query, context),
356
+ ctx.error,
357
+ "Unauthorized: Cannot list posts",
358
+ );
359
359
  }
360
360
 
361
361
  const result = await getAllPosts(adapter, query);
@@ -387,15 +387,11 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
387
387
 
388
388
  try {
389
389
  if (hooks?.onBeforeCreatePost) {
390
- const canCreate = await hooks.onBeforeCreatePost(
391
- ctx.body,
392
- context,
390
+ await runHookWithShim(
391
+ () => hooks.onBeforeCreatePost!(ctx.body, context),
392
+ ctx.error,
393
+ "Unauthorized: Cannot create post",
393
394
  );
394
- if (!canCreate) {
395
- throw ctx.error(403, {
396
- message: "Unauthorized: Cannot create post",
397
- });
398
- }
399
395
  }
400
396
 
401
397
  const { tags, ...postData } = ctx.body;
@@ -471,16 +467,12 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
471
467
 
472
468
  try {
473
469
  if (hooks?.onBeforeUpdatePost) {
474
- const canUpdate = await hooks.onBeforeUpdatePost(
475
- ctx.params.id,
476
- ctx.body,
477
- context,
470
+ await runHookWithShim(
471
+ () =>
472
+ hooks.onBeforeUpdatePost!(ctx.params.id, ctx.body, context),
473
+ ctx.error,
474
+ "Unauthorized: Cannot update post",
478
475
  );
479
- if (!canUpdate) {
480
- throw ctx.error(403, {
481
- message: "Unauthorized: Cannot update post",
482
- });
483
- }
484
476
  }
485
477
 
486
478
  const { tags, slug: rawSlug, ...restPostData } = ctx.body;
@@ -598,15 +590,11 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
598
590
  try {
599
591
  // Authorization hook
600
592
  if (hooks?.onBeforeDeletePost) {
601
- const canDelete = await hooks.onBeforeDeletePost(
602
- ctx.params.id,
603
- context,
593
+ await runHookWithShim(
594
+ () => hooks.onBeforeDeletePost!(ctx.params.id, context),
595
+ ctx.error,
596
+ "Unauthorized: Cannot delete post",
604
597
  );
605
- if (!canDelete) {
606
- throw ctx.error(403, {
607
- message: "Unauthorized: Cannot delete post",
608
- });
609
- }
610
598
  }
611
599
 
612
600
  await adapter.delete<Post>({
@@ -642,15 +630,11 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
642
630
 
643
631
  try {
644
632
  if (hooks?.onBeforeListPosts) {
645
- const canList = await hooks.onBeforeListPosts(
646
- { published: true },
647
- context,
633
+ await runHookWithShim(
634
+ () => hooks.onBeforeListPosts!({ published: true }, context),
635
+ ctx.error,
636
+ "Unauthorized: Cannot list posts",
648
637
  );
649
- if (!canList) {
650
- throw ctx.error(403, {
651
- message: "Unauthorized: Cannot list posts",
652
- });
653
- }
654
638
  }
655
639
 
656
640
  const date = query.date;