@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
@@ -4,6 +4,7 @@ import {
4
4
  defineClientPlugin,
5
5
  createApiClient,
6
6
  isConnectionError,
7
+ runClientHookWithShim,
7
8
  } from "@btst/stack/plugins/client";
8
9
  import { createRoute } from "@btst/yar";
9
10
  import type { QueryClient } from "@tanstack/react-query";
@@ -53,24 +54,24 @@ export interface LoaderContext {
53
54
  */
54
55
  export interface FormBuilderClientHooks {
55
56
  /**
56
- * Called before loading the form list page. Return false to cancel loading.
57
+ * Called before loading the form list page. Throw an error to cancel loading.
57
58
  * @param context - Loader context with path, params, etc.
58
59
  */
59
- beforeLoadFormList?: (context: LoaderContext) => Promise<boolean> | boolean;
60
+ beforeLoadFormList?: (context: LoaderContext) => Promise<void> | void;
60
61
  /**
61
62
  * Called after the form list is loaded.
62
63
  * @param context - Loader context
63
64
  */
64
65
  afterLoadFormList?: (context: LoaderContext) => Promise<void> | void;
65
66
  /**
66
- * Called before loading the form builder page. Return false to cancel loading.
67
+ * Called before loading the form builder page. Throw an error to cancel loading.
67
68
  * @param id - The form ID (undefined for new forms)
68
69
  * @param context - Loader context
69
70
  */
70
71
  beforeLoadFormBuilder?: (
71
72
  id: string | undefined,
72
73
  context: LoaderContext,
73
- ) => Promise<boolean> | boolean;
74
+ ) => Promise<void> | void;
74
75
  /**
75
76
  * Called after the form builder is loaded.
76
77
  * @param id - The form ID (undefined for new forms)
@@ -81,14 +82,14 @@ export interface FormBuilderClientHooks {
81
82
  context: LoaderContext,
82
83
  ) => Promise<void> | void;
83
84
  /**
84
- * Called before loading the submissions page. Return false to cancel loading.
85
+ * Called before loading the submissions page. Throw an error to cancel loading.
85
86
  * @param formId - The form ID
86
87
  * @param context - Loader context
87
88
  */
88
89
  beforeLoadSubmissions?: (
89
90
  formId: string,
90
91
  context: LoaderContext,
91
- ) => Promise<boolean> | boolean;
92
+ ) => Promise<void> | void;
92
93
  /**
93
94
  * Called after the submissions page is loaded.
94
95
  * @param formId - The form ID
@@ -146,10 +147,10 @@ function createFormListLoader(config: FormBuilderClientConfig) {
146
147
  try {
147
148
  // Before hook - authorization check
148
149
  if (hooks?.beforeLoadFormList) {
149
- const canLoad = await hooks.beforeLoadFormList(context);
150
- if (!canLoad) {
151
- throw new Error("Load prevented by beforeLoadFormList hook");
152
- }
150
+ await runClientHookWithShim(
151
+ () => hooks.beforeLoadFormList!(context),
152
+ "Load prevented by beforeLoadFormList hook",
153
+ );
153
154
  }
154
155
 
155
156
  const client = createApiClient<FormBuilderApiRouter>({
@@ -235,10 +236,10 @@ function createFormBuilderLoader(
235
236
  try {
236
237
  // Before hook - authorization check
237
238
  if (hooks?.beforeLoadFormBuilder) {
238
- const canLoad = await hooks.beforeLoadFormBuilder(id, context);
239
- if (!canLoad) {
240
- throw new Error("Load prevented by beforeLoadFormBuilder hook");
241
- }
239
+ await runClientHookWithShim(
240
+ () => hooks.beforeLoadFormBuilder!(id, context),
241
+ "Load prevented by beforeLoadFormBuilder hook",
242
+ );
242
243
  }
243
244
 
244
245
  const client = createApiClient<FormBuilderApiRouter>({
@@ -309,10 +310,10 @@ function createSubmissionsLoader(
309
310
  try {
310
311
  // Before hook - authorization check
311
312
  if (hooks?.beforeLoadSubmissions) {
312
- const canLoad = await hooks.beforeLoadSubmissions(formId, context);
313
- if (!canLoad) {
314
- throw new Error("Load prevented by beforeLoadSubmissions hook");
315
- }
313
+ await runClientHookWithShim(
314
+ () => hooks.beforeLoadSubmissions!(formId, context),
315
+ "Load prevented by beforeLoadSubmissions hook",
316
+ );
316
317
  }
317
318
 
318
319
  const client = createApiClient<FormBuilderApiRouter>({
@@ -167,23 +167,21 @@ export interface FormUpdate {
167
167
  * Backend hooks for Form Builder plugin
168
168
  *
169
169
  * All CRUD hooks receive ipAddress and headers for auth/rate limiting.
170
- * Return false from onBefore* hooks to reject the operation (throws 403).
170
+ * Throw an error from onBefore* hooks to reject the operation (throws 403).
171
171
  */
172
172
  export interface FormBuilderBackendHooks {
173
173
  // ============================================================================
174
174
  // FORM CRUD HOOKS (Admin operations)
175
175
  // ============================================================================
176
176
 
177
- /** Called before listing forms. Return false to deny access (403). */
178
- onBeforeListForms?: (
179
- ctx: FormBuilderHookContext,
180
- ) => Promise<boolean> | boolean;
177
+ /** Called before listing forms. Throw an error to deny access (403). */
178
+ onBeforeListForms?: (ctx: FormBuilderHookContext) => Promise<void> | void;
181
179
 
182
- /** Called before creating a form. Return false to deny, or modified data. */
180
+ /** Called before creating a form. Throw an error to deny, or return modified data. */
183
181
  onBeforeFormCreated?: (
184
182
  data: FormInput,
185
183
  ctx: FormBuilderHookContext,
186
- ) => Promise<FormInput | false> | FormInput | false;
184
+ ) => Promise<FormInput | void> | FormInput | void;
187
185
 
188
186
  /** Called after a form is created */
189
187
  onAfterFormCreated?: (
@@ -191,18 +189,18 @@ export interface FormBuilderBackendHooks {
191
189
  ctx: FormBuilderHookContext,
192
190
  ) => Promise<void> | void;
193
191
 
194
- /** Called before getting a form by ID or slug. Return false to deny access. */
192
+ /** Called before getting a form by ID or slug. Throw an error to deny access. */
195
193
  onBeforeGetForm?: (
196
194
  idOrSlug: string,
197
195
  ctx: FormBuilderHookContext,
198
- ) => Promise<boolean> | boolean;
196
+ ) => Promise<void> | void;
199
197
 
200
- /** Called before updating a form. Return false to deny, or modified data. */
198
+ /** Called before updating a form. Throw an error to deny, or return modified data. */
201
199
  onBeforeFormUpdated?: (
202
200
  id: string,
203
201
  data: FormUpdate,
204
202
  ctx: FormBuilderHookContext,
205
- ) => Promise<FormUpdate | false> | FormUpdate | false;
203
+ ) => Promise<FormUpdate | void> | FormUpdate | void;
206
204
 
207
205
  /** Called after a form is updated */
208
206
  onAfterFormUpdated?: (
@@ -210,11 +208,11 @@ export interface FormBuilderBackendHooks {
210
208
  ctx: FormBuilderHookContext,
211
209
  ) => Promise<void> | void;
212
210
 
213
- /** Called before deleting a form. Return false to deny. */
211
+ /** Called before deleting a form. Throw an error to deny. */
214
212
  onBeforeFormDeleted?: (
215
213
  id: string,
216
214
  ctx: FormBuilderHookContext,
217
- ) => Promise<boolean> | boolean;
215
+ ) => Promise<void> | void;
218
216
 
219
217
  /** Called after a form is deleted */
220
218
  onAfterFormDeleted?: (
@@ -230,16 +228,13 @@ export interface FormBuilderBackendHooks {
230
228
  * Called before processing a form submission.
231
229
  * Use for: spam protection, rate limiting, data validation/enrichment.
232
230
  *
233
- * @returns false to reject submission (400), or modified data to continue
231
+ * Throw an error to reject submission (400), or return modified data to continue.
234
232
  */
235
233
  onBeforeSubmission?: (
236
234
  formSlug: string,
237
235
  data: Record<string, unknown>,
238
236
  ctx: SubmissionHookContext,
239
- ) =>
240
- | Promise<Record<string, unknown> | false>
241
- | Record<string, unknown>
242
- | false;
237
+ ) => Promise<Record<string, unknown> | void> | Record<string, unknown> | void;
243
238
 
244
239
  /**
245
240
  * Called after a submission is saved.
@@ -263,23 +258,23 @@ export interface FormBuilderBackendHooks {
263
258
  // SUBMISSIONS MANAGEMENT HOOKS (Admin viewing submissions)
264
259
  // ============================================================================
265
260
 
266
- /** Called before listing submissions. Return false to deny access (403). */
261
+ /** Called before listing submissions. Throw an error to deny access (403). */
267
262
  onBeforeListSubmissions?: (
268
263
  formId: string,
269
264
  ctx: FormBuilderHookContext,
270
- ) => Promise<boolean> | boolean;
265
+ ) => Promise<void> | void;
271
266
 
272
- /** Called before getting a submission. Return false to deny access. */
267
+ /** Called before getting a submission. Throw an error to deny access. */
273
268
  onBeforeGetSubmission?: (
274
269
  submissionId: string,
275
270
  ctx: FormBuilderHookContext,
276
- ) => Promise<boolean> | boolean;
271
+ ) => Promise<void> | void;
277
272
 
278
- /** Called before deleting a submission. Return false to deny. */
273
+ /** Called before deleting a submission. Throw an error to deny. */
279
274
  onBeforeSubmissionDeleted?: (
280
275
  submissionId: string,
281
276
  ctx: FormBuilderHookContext,
282
- ) => Promise<boolean> | boolean;
277
+ ) => Promise<void> | void;
283
278
 
284
279
  /** Called after a submission is deleted */
285
280
  onAfterSubmissionDeleted?: (
@@ -30,6 +30,7 @@ import {
30
30
  getKanbanColumnsByBoardId,
31
31
  } from "./mutations";
32
32
  import { KANBAN_QUERY_KEYS } from "./query-key-defs";
33
+ import { runHookWithShim } from "../../utils";
33
34
  import { serializeBoard } from "./serializers";
34
35
  import type { QueryClient } from "@tanstack/react-query";
35
36
 
@@ -101,41 +102,41 @@ export interface KanbanApiContext<
101
102
  export interface KanbanBackendHooks {
102
103
  // ============ Board Hooks ============
103
104
  /**
104
- * Called before listing boards. Return false to deny access.
105
+ * Called before listing boards. Throw an error to deny access.
105
106
  */
106
107
  onBeforeListBoards?: (
107
108
  filter: z.infer<typeof BoardListQuerySchema>,
108
109
  context: KanbanApiContext,
109
- ) => Promise<boolean> | boolean;
110
+ ) => Promise<void> | void;
110
111
  /**
111
- * Called before creating a board. Return false to deny access.
112
+ * Called before creating a board. Throw an error to deny access.
112
113
  */
113
114
  onBeforeCreateBoard?: (
114
115
  data: z.infer<typeof createBoardSchema>,
115
116
  context: KanbanApiContext,
116
- ) => Promise<boolean> | boolean;
117
+ ) => Promise<void> | void;
117
118
  /**
118
- * Called before reading a single board. Return false to deny access.
119
+ * Called before reading a single board. Throw an error to deny access.
119
120
  */
120
121
  onBeforeReadBoard?: (
121
122
  boardId: string,
122
123
  context: KanbanApiContext,
123
- ) => Promise<boolean> | boolean;
124
+ ) => Promise<void> | void;
124
125
  /**
125
- * Called before updating a board. Return false to deny access.
126
+ * Called before updating a board. Throw an error to deny access.
126
127
  */
127
128
  onBeforeUpdateBoard?: (
128
129
  boardId: string,
129
130
  data: z.infer<typeof updateBoardSchema>,
130
131
  context: KanbanApiContext,
131
- ) => Promise<boolean> | boolean;
132
+ ) => Promise<void> | void;
132
133
  /**
133
- * Called before deleting a board. Return false to deny access.
134
+ * Called before deleting a board. Throw an error to deny access.
134
135
  */
135
136
  onBeforeDeleteBoard?: (
136
137
  boardId: string,
137
138
  context: KanbanApiContext,
138
- ) => Promise<boolean> | boolean;
139
+ ) => Promise<void> | void;
139
140
 
140
141
  /**
141
142
  * Called after boards are listed successfully.
@@ -214,27 +215,27 @@ export interface KanbanBackendHooks {
214
215
 
215
216
  // ============ Column Hooks ============
216
217
  /**
217
- * Called before creating a column. Return false to deny access.
218
+ * Called before creating a column. Throw an error to deny access.
218
219
  */
219
220
  onBeforeCreateColumn?: (
220
221
  data: z.infer<typeof createColumnSchema>,
221
222
  context: KanbanApiContext,
222
- ) => Promise<boolean> | boolean;
223
+ ) => Promise<void> | void;
223
224
  /**
224
- * Called before updating a column. Return false to deny access.
225
+ * Called before updating a column. Throw an error to deny access.
225
226
  */
226
227
  onBeforeUpdateColumn?: (
227
228
  columnId: string,
228
229
  data: z.infer<typeof updateColumnSchema>,
229
230
  context: KanbanApiContext,
230
- ) => Promise<boolean> | boolean;
231
+ ) => Promise<void> | void;
231
232
  /**
232
- * Called before deleting a column. Return false to deny access.
233
+ * Called before deleting a column. Throw an error to deny access.
233
234
  */
234
235
  onBeforeDeleteColumn?: (
235
236
  columnId: string,
236
237
  context: KanbanApiContext,
237
- ) => Promise<boolean> | boolean;
238
+ ) => Promise<void> | void;
238
239
 
239
240
  /**
240
241
  * Called after a column is created successfully
@@ -260,27 +261,27 @@ export interface KanbanBackendHooks {
260
261
 
261
262
  // ============ Task Hooks ============
262
263
  /**
263
- * Called before creating a task. Return false to deny access.
264
+ * Called before creating a task. Throw an error to deny access.
264
265
  */
265
266
  onBeforeCreateTask?: (
266
267
  data: z.infer<typeof createTaskSchema>,
267
268
  context: KanbanApiContext,
268
- ) => Promise<boolean> | boolean;
269
+ ) => Promise<void> | void;
269
270
  /**
270
- * Called before updating a task. Return false to deny access.
271
+ * Called before updating a task. Throw an error to deny access.
271
272
  */
272
273
  onBeforeUpdateTask?: (
273
274
  taskId: string,
274
275
  data: z.infer<typeof updateTaskSchema>,
275
276
  context: KanbanApiContext,
276
- ) => Promise<boolean> | boolean;
277
+ ) => Promise<void> | void;
277
278
  /**
278
- * Called before deleting a task. Return false to deny access.
279
+ * Called before deleting a task. Throw an error to deny access.
279
280
  */
280
281
  onBeforeDeleteTask?: (
281
282
  taskId: string,
282
283
  context: KanbanApiContext,
283
- ) => Promise<boolean> | boolean;
284
+ ) => Promise<void> | void;
284
285
 
285
286
  /**
286
287
  * Called after a task is created successfully
@@ -346,12 +347,11 @@ export const kanbanBackendPlugin = (hooks?: KanbanBackendHooks) =>
346
347
 
347
348
  try {
348
349
  if (hooks?.onBeforeListBoards) {
349
- const canList = await hooks.onBeforeListBoards(query, context);
350
- if (!canList) {
351
- throw ctx.error(403, {
352
- message: "Unauthorized: Cannot list boards",
353
- });
354
- }
350
+ await runHookWithShim(
351
+ () => hooks.onBeforeListBoards!(query, context),
352
+ ctx.error,
353
+ "Unauthorized: Cannot list boards",
354
+ );
355
355
  }
356
356
 
357
357
  const result = await getAllBoards(adapter, query);
@@ -381,12 +381,11 @@ export const kanbanBackendPlugin = (hooks?: KanbanBackendHooks) =>
381
381
 
382
382
  try {
383
383
  if (hooks?.onBeforeReadBoard) {
384
- const canRead = await hooks.onBeforeReadBoard(params.id, context);
385
- if (!canRead) {
386
- throw ctx.error(403, {
387
- message: "Unauthorized: Cannot read board",
388
- });
389
- }
384
+ await runHookWithShim(
385
+ () => hooks.onBeforeReadBoard!(params.id, context),
386
+ ctx.error,
387
+ "Unauthorized: Cannot read board",
388
+ );
390
389
  }
391
390
 
392
391
  const result = await getBoardById(adapter, params.id);
@@ -423,15 +422,11 @@ export const kanbanBackendPlugin = (hooks?: KanbanBackendHooks) =>
423
422
 
424
423
  try {
425
424
  if (hooks?.onBeforeCreateBoard) {
426
- const canCreate = await hooks.onBeforeCreateBoard(
427
- ctx.body,
428
- context,
425
+ await runHookWithShim(
426
+ () => hooks.onBeforeCreateBoard!(ctx.body, context),
427
+ ctx.error,
428
+ "Unauthorized: Cannot create board",
429
429
  );
430
- if (!canCreate) {
431
- throw ctx.error(403, {
432
- message: "Unauthorized: Cannot create board",
433
- });
434
- }
435
430
  }
436
431
 
437
432
  const { ...boardData } = ctx.body;
@@ -516,16 +511,16 @@ export const kanbanBackendPlugin = (hooks?: KanbanBackendHooks) =>
516
511
 
517
512
  try {
518
513
  if (hooks?.onBeforeUpdateBoard) {
519
- const canUpdate = await hooks.onBeforeUpdateBoard(
520
- ctx.params.id,
521
- { ...ctx.body, id: ctx.params.id },
522
- context,
514
+ await runHookWithShim(
515
+ () =>
516
+ hooks.onBeforeUpdateBoard!(
517
+ ctx.params.id,
518
+ { ...ctx.body, id: ctx.params.id },
519
+ context,
520
+ ),
521
+ ctx.error,
522
+ "Unauthorized: Cannot update board",
523
523
  );
524
- if (!canUpdate) {
525
- throw ctx.error(403, {
526
- message: "Unauthorized: Cannot update board",
527
- });
528
- }
529
524
  }
530
525
 
531
526
  const { slug: rawSlug, ...restBoardData } = ctx.body;
@@ -595,15 +590,11 @@ export const kanbanBackendPlugin = (hooks?: KanbanBackendHooks) =>
595
590
  }
596
591
 
597
592
  if (hooks?.onBeforeDeleteBoard) {
598
- const canDelete = await hooks.onBeforeDeleteBoard(
599
- ctx.params.id,
600
- context,
593
+ await runHookWithShim(
594
+ () => hooks.onBeforeDeleteBoard!(ctx.params.id, context),
595
+ ctx.error,
596
+ "Unauthorized: Cannot delete board",
601
597
  );
602
- if (!canDelete) {
603
- throw ctx.error(403, {
604
- message: "Unauthorized: Cannot delete board",
605
- });
606
- }
607
598
  }
608
599
 
609
600
  await adapter.delete<Board>({
@@ -641,15 +632,11 @@ export const kanbanBackendPlugin = (hooks?: KanbanBackendHooks) =>
641
632
 
642
633
  try {
643
634
  if (hooks?.onBeforeCreateColumn) {
644
- const canCreate = await hooks.onBeforeCreateColumn(
645
- ctx.body,
646
- context,
635
+ await runHookWithShim(
636
+ () => hooks.onBeforeCreateColumn!(ctx.body, context),
637
+ ctx.error,
638
+ "Unauthorized: Cannot create column",
647
639
  );
648
- if (!canCreate) {
649
- throw ctx.error(403, {
650
- message: "Unauthorized: Cannot create column",
651
- });
652
- }
653
640
  }
654
641
 
655
642
  // Get existing columns to determine order
@@ -704,16 +691,16 @@ export const kanbanBackendPlugin = (hooks?: KanbanBackendHooks) =>
704
691
 
705
692
  try {
706
693
  if (hooks?.onBeforeUpdateColumn) {
707
- const canUpdate = await hooks.onBeforeUpdateColumn(
708
- ctx.params.id,
709
- { ...ctx.body, id: ctx.params.id },
710
- context,
694
+ await runHookWithShim(
695
+ () =>
696
+ hooks.onBeforeUpdateColumn!(
697
+ ctx.params.id,
698
+ { ...ctx.body, id: ctx.params.id },
699
+ context,
700
+ ),
701
+ ctx.error,
702
+ "Unauthorized: Cannot update column",
711
703
  );
712
- if (!canUpdate) {
713
- throw ctx.error(403, {
714
- message: "Unauthorized: Cannot update column",
715
- });
716
- }
717
704
  }
718
705
 
719
706
  const updated = await adapter.update<Column>({
@@ -765,15 +752,11 @@ export const kanbanBackendPlugin = (hooks?: KanbanBackendHooks) =>
765
752
  }
766
753
 
767
754
  if (hooks?.onBeforeDeleteColumn) {
768
- const canDelete = await hooks.onBeforeDeleteColumn(
769
- ctx.params.id,
770
- context,
755
+ await runHookWithShim(
756
+ () => hooks.onBeforeDeleteColumn!(ctx.params.id, context),
757
+ ctx.error,
758
+ "Unauthorized: Cannot delete column",
771
759
  );
772
- if (!canDelete) {
773
- throw ctx.error(403, {
774
- message: "Unauthorized: Cannot delete column",
775
- });
776
- }
777
760
  }
778
761
 
779
762
  await adapter.delete<Column>({
@@ -810,16 +793,16 @@ export const kanbanBackendPlugin = (hooks?: KanbanBackendHooks) =>
810
793
  for (let i = 0; i < columnIds.length; i++) {
811
794
  const columnId = columnIds[i];
812
795
  if (!columnId) continue;
813
- const canUpdate = await hooks.onBeforeUpdateColumn(
814
- columnId,
815
- { id: columnId, order: i },
816
- context,
796
+ await runHookWithShim(
797
+ () =>
798
+ hooks.onBeforeUpdateColumn!(
799
+ columnId,
800
+ { id: columnId, order: i },
801
+ context,
802
+ ),
803
+ ctx.error,
804
+ "Unauthorized: Cannot reorder columns",
817
805
  );
818
- if (!canUpdate) {
819
- throw ctx.error(403, {
820
- message: "Unauthorized: Cannot reorder columns",
821
- });
822
- }
823
806
  }
824
807
  }
825
808
 
@@ -869,15 +852,11 @@ export const kanbanBackendPlugin = (hooks?: KanbanBackendHooks) =>
869
852
 
870
853
  try {
871
854
  if (hooks?.onBeforeCreateTask) {
872
- const canCreate = await hooks.onBeforeCreateTask(
873
- ctx.body,
874
- context,
855
+ await runHookWithShim(
856
+ () => hooks.onBeforeCreateTask!(ctx.body, context),
857
+ ctx.error,
858
+ "Unauthorized: Cannot create task",
875
859
  );
876
- if (!canCreate) {
877
- throw ctx.error(403, {
878
- message: "Unauthorized: Cannot create task",
879
- });
880
- }
881
860
  }
882
861
 
883
862
  // Get existing tasks in column to determine order
@@ -939,16 +918,16 @@ export const kanbanBackendPlugin = (hooks?: KanbanBackendHooks) =>
939
918
 
940
919
  try {
941
920
  if (hooks?.onBeforeUpdateTask) {
942
- const canUpdate = await hooks.onBeforeUpdateTask(
943
- ctx.params.id,
944
- { ...ctx.body, id: ctx.params.id },
945
- context,
921
+ await runHookWithShim(
922
+ () =>
923
+ hooks.onBeforeUpdateTask!(
924
+ ctx.params.id,
925
+ { ...ctx.body, id: ctx.params.id },
926
+ context,
927
+ ),
928
+ ctx.error,
929
+ "Unauthorized: Cannot update task",
946
930
  );
947
- if (!canUpdate) {
948
- throw ctx.error(403, {
949
- message: "Unauthorized: Cannot update task",
950
- });
951
- }
952
931
  }
953
932
 
954
933
  const updated = await adapter.update<Task>({
@@ -1000,15 +979,11 @@ export const kanbanBackendPlugin = (hooks?: KanbanBackendHooks) =>
1000
979
  }
1001
980
 
1002
981
  if (hooks?.onBeforeDeleteTask) {
1003
- const canDelete = await hooks.onBeforeDeleteTask(
1004
- ctx.params.id,
1005
- context,
982
+ await runHookWithShim(
983
+ () => hooks.onBeforeDeleteTask!(ctx.params.id, context),
984
+ ctx.error,
985
+ "Unauthorized: Cannot delete task",
1006
986
  );
1007
- if (!canDelete) {
1008
- throw ctx.error(403, {
1009
- message: "Unauthorized: Cannot delete task",
1010
- });
1011
- }
1012
987
  }
1013
988
 
1014
989
  await adapter.delete<Task>({
@@ -1052,16 +1027,16 @@ export const kanbanBackendPlugin = (hooks?: KanbanBackendHooks) =>
1052
1027
 
1053
1028
  // Check authorization before moving task
1054
1029
  if (hooks?.onBeforeUpdateTask) {
1055
- const canUpdate = await hooks.onBeforeUpdateTask(
1056
- taskId,
1057
- { id: taskId, columnId: targetColumnId, order: targetOrder },
1058
- context,
1030
+ await runHookWithShim(
1031
+ () =>
1032
+ hooks.onBeforeUpdateTask!(
1033
+ taskId,
1034
+ { id: taskId, columnId: targetColumnId, order: targetOrder },
1035
+ context,
1036
+ ),
1037
+ ctx.error,
1038
+ "Unauthorized: Cannot move task",
1059
1039
  );
1060
- if (!canUpdate) {
1061
- throw ctx.error(403, {
1062
- message: "Unauthorized: Cannot move task",
1063
- });
1064
- }
1065
1040
  }
1066
1041
 
1067
1042
  // Update task with new column and order
@@ -1105,16 +1080,16 @@ export const kanbanBackendPlugin = (hooks?: KanbanBackendHooks) =>
1105
1080
  for (let i = 0; i < taskIds.length; i++) {
1106
1081
  const taskId = taskIds[i];
1107
1082
  if (!taskId) continue;
1108
- const canUpdate = await hooks.onBeforeUpdateTask(
1109
- taskId,
1110
- { id: taskId, order: i },
1111
- context,
1083
+ await runHookWithShim(
1084
+ () =>
1085
+ hooks.onBeforeUpdateTask!(
1086
+ taskId,
1087
+ { id: taskId, order: i },
1088
+ context,
1089
+ ),
1090
+ ctx.error,
1091
+ "Unauthorized: Cannot reorder tasks",
1112
1092
  );
1113
- if (!canUpdate) {
1114
- throw ctx.error(403, {
1115
- message: "Unauthorized: Cannot reorder tasks",
1116
- });
1117
- }
1118
1093
  }
1119
1094
  }
1120
1095