@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.
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.cjs +33 -47
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.mjs +33 -47
- package/dist/packages/stack/src/plugins/ai-chat/client/plugin.cjs +14 -21
- package/dist/packages/stack/src/plugins/ai-chat/client/plugin.mjs +15 -22
- package/dist/packages/stack/src/plugins/blog/api/plugin.cjs +28 -45
- package/dist/packages/stack/src/plugins/blog/api/plugin.mjs +22 -39
- package/dist/packages/stack/src/plugins/blog/client/plugin.cjs +23 -27
- package/dist/packages/stack/src/plugins/blog/client/plugin.mjs +24 -28
- package/dist/packages/stack/src/plugins/cms/api/plugin.cjs +14 -17
- package/dist/packages/stack/src/plugins/cms/api/plugin.mjs +14 -17
- package/dist/packages/stack/src/plugins/cms/client/plugin.cjs +11 -15
- package/dist/packages/stack/src/plugins/cms/client/plugin.mjs +12 -16
- package/dist/packages/stack/src/plugins/form-builder/api/plugin.cjs +58 -62
- package/dist/packages/stack/src/plugins/form-builder/api/plugin.mjs +58 -62
- package/dist/packages/stack/src/plugins/form-builder/client/plugin.cjs +12 -12
- package/dist/packages/stack/src/plugins/form-builder/client/plugin.mjs +13 -13
- package/dist/packages/stack/src/plugins/kanban/api/plugin.cjs +86 -117
- package/dist/packages/stack/src/plugins/kanban/api/plugin.mjs +83 -114
- package/dist/packages/stack/src/plugins/kanban/client/plugin.cjs +22 -29
- package/dist/packages/stack/src/plugins/kanban/client/plugin.mjs +23 -30
- package/dist/packages/stack/src/plugins/ui-builder/client/plugin.cjs +8 -8
- package/dist/packages/stack/src/plugins/ui-builder/client/plugin.mjs +9 -9
- package/dist/packages/stack/src/plugins/utils.cjs +42 -0
- package/dist/packages/stack/src/plugins/utils.mjs +41 -1
- package/dist/plugins/ai-chat/api/index.d.cts +1 -1
- package/dist/plugins/ai-chat/api/index.d.mts +1 -1
- package/dist/plugins/ai-chat/api/index.d.ts +1 -1
- package/dist/plugins/ai-chat/client/hooks/index.d.cts +1 -1
- package/dist/plugins/ai-chat/client/hooks/index.d.mts +1 -1
- package/dist/plugins/ai-chat/client/hooks/index.d.ts +1 -1
- package/dist/plugins/ai-chat/client/index.d.cts +8 -8
- package/dist/plugins/ai-chat/client/index.d.mts +8 -8
- package/dist/plugins/ai-chat/client/index.d.ts +8 -8
- package/dist/plugins/ai-chat/query-keys.d.cts +1 -1
- package/dist/plugins/ai-chat/query-keys.d.mts +1 -1
- package/dist/plugins/ai-chat/query-keys.d.ts +1 -1
- package/dist/plugins/blog/api/index.d.cts +1 -1
- package/dist/plugins/blog/api/index.d.mts +1 -1
- package/dist/plugins/blog/api/index.d.ts +1 -1
- package/dist/plugins/blog/client/index.d.cts +12 -12
- package/dist/plugins/blog/client/index.d.mts +12 -12
- package/dist/plugins/blog/client/index.d.ts +12 -12
- package/dist/plugins/blog/query-keys.d.cts +1 -1
- package/dist/plugins/blog/query-keys.d.mts +1 -1
- package/dist/plugins/blog/query-keys.d.ts +1 -1
- package/dist/plugins/client/index.cjs +1 -0
- package/dist/plugins/client/index.d.cts +8 -1
- package/dist/plugins/client/index.d.mts +8 -1
- package/dist/plugins/client/index.d.ts +8 -1
- package/dist/plugins/client/index.mjs +1 -1
- package/dist/plugins/cms/api/index.d.cts +2 -2
- package/dist/plugins/cms/api/index.d.mts +2 -2
- package/dist/plugins/cms/api/index.d.ts +2 -2
- package/dist/plugins/cms/client/hooks/index.d.cts +1 -1
- package/dist/plugins/cms/client/hooks/index.d.mts +1 -1
- package/dist/plugins/cms/client/hooks/index.d.ts +1 -1
- package/dist/plugins/cms/client/index.d.cts +6 -6
- package/dist/plugins/cms/client/index.d.mts +6 -6
- package/dist/plugins/cms/client/index.d.ts +6 -6
- package/dist/plugins/cms/query-keys.d.cts +2 -2
- package/dist/plugins/cms/query-keys.d.mts +2 -2
- package/dist/plugins/cms/query-keys.d.ts +2 -2
- package/dist/plugins/form-builder/api/index.d.cts +2 -2
- package/dist/plugins/form-builder/api/index.d.mts +2 -2
- package/dist/plugins/form-builder/api/index.d.ts +2 -2
- package/dist/plugins/form-builder/client/components/index.d.cts +1 -1
- package/dist/plugins/form-builder/client/components/index.d.mts +1 -1
- package/dist/plugins/form-builder/client/components/index.d.ts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.cts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.mts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.ts +1 -1
- package/dist/plugins/form-builder/client/index.d.cts +6 -6
- package/dist/plugins/form-builder/client/index.d.mts +6 -6
- package/dist/plugins/form-builder/client/index.d.ts +6 -6
- package/dist/plugins/form-builder/query-keys.d.cts +2 -2
- package/dist/plugins/form-builder/query-keys.d.mts +2 -2
- package/dist/plugins/form-builder/query-keys.d.ts +2 -2
- package/dist/plugins/kanban/api/index.d.cts +1 -1
- package/dist/plugins/kanban/api/index.d.mts +1 -1
- package/dist/plugins/kanban/api/index.d.ts +1 -1
- package/dist/plugins/kanban/client/index.d.cts +12 -12
- package/dist/plugins/kanban/client/index.d.mts +12 -12
- package/dist/plugins/kanban/client/index.d.ts +12 -12
- package/dist/plugins/kanban/query-keys.d.cts +1 -1
- package/dist/plugins/kanban/query-keys.d.mts +1 -1
- package/dist/plugins/kanban/query-keys.d.ts +1 -1
- package/dist/plugins/ui-builder/client/hooks/index.d.cts +1 -1
- package/dist/plugins/ui-builder/client/hooks/index.d.mts +1 -1
- package/dist/plugins/ui-builder/client/hooks/index.d.ts +1 -1
- package/dist/plugins/ui-builder/client/index.d.cts +3 -3
- package/dist/plugins/ui-builder/client/index.d.mts +3 -3
- package/dist/plugins/ui-builder/client/index.d.ts +3 -3
- package/dist/plugins/ui-builder/index.d.cts +2 -2
- package/dist/plugins/ui-builder/index.d.mts +2 -2
- package/dist/plugins/ui-builder/index.d.ts +2 -2
- package/dist/shared/{stack.C-WUPMT6.d.cts → stack.B2xZTSiO.d.cts} +4 -4
- package/dist/shared/{stack.CczspVn2.d.mts → stack.B58oHdqm.d.mts} +1 -1
- package/dist/shared/{stack.CVDTkMoO.d.mts → stack.B8QD11QU.d.cts} +7 -7
- package/dist/shared/{stack.CVDTkMoO.d.cts → stack.B8QD11QU.d.mts} +7 -7
- package/dist/shared/{stack.CVDTkMoO.d.ts → stack.B8QD11QU.d.ts} +7 -7
- package/dist/shared/{stack.Kq2-QzOC.d.ts → stack.BDVEpue1.d.ts} +2 -2
- package/dist/shared/{stack.B7ONvlD_.d.mts → stack.BTvbxZvw.d.cts} +2 -2
- package/dist/shared/{stack.DdI5W6MB.d.mts → stack.BozPgbrZ.d.cts} +19 -19
- package/dist/shared/{stack.DdI5W6MB.d.cts → stack.BozPgbrZ.d.mts} +19 -19
- package/dist/shared/{stack.DdI5W6MB.d.ts → stack.BozPgbrZ.d.ts} +19 -19
- package/dist/shared/{stack.BUkC2EsZ.d.cts → stack.C9Mg2Q46.d.cts} +1 -1
- package/dist/shared/{stack.BEn34wW6.d.ts → stack.CTDVxbrA.d.ts} +12 -12
- package/dist/shared/{stack.C-Ptrz8s.d.ts → stack.Cj_zKww4.d.ts} +4 -4
- package/dist/shared/{stack.BepFXT3w.d.mts → stack.CxaFNQCV.d.mts} +25 -25
- package/dist/shared/{stack.DWoCZff7.d.cts → stack.D-b5zbPm.d.cts} +12 -12
- package/dist/shared/{stack.kcdnD4gA.d.cts → stack.DTtmJPQO.d.mts} +2 -2
- package/dist/shared/{stack.CL8ts1Mu.d.ts → stack.DXnclTG7.d.ts} +8 -8
- package/dist/shared/{stack.heOA9gzA.d.cts → stack.DaZM10cp.d.cts} +8 -8
- package/dist/shared/{stack.DTDxgFj8.d.mts → stack.FVWf2JhZ.d.mts} +12 -12
- package/dist/shared/{stack.Dk5r4W1F.d.mts → stack.cfCkioTe.d.mts} +8 -8
- package/dist/shared/{stack.6fUOjLs9.d.mts → stack.dH7u-TJH.d.mts} +4 -4
- package/dist/shared/{stack.CgWzG5jH.d.ts → stack.j75TpKh2.d.ts} +25 -25
- package/dist/shared/{stack.D3GB6wKv.d.cts → stack.n1_i1p2B.d.cts} +25 -25
- package/dist/shared/{stack.DASmUVjX.d.ts → stack.sO33ZDhK.d.ts} +1 -1
- package/package.json +1 -1
- package/src/plugins/ai-chat/api/plugin.ts +48 -63
- package/src/plugins/ai-chat/client/plugin.tsx +23 -31
- package/src/plugins/blog/api/plugin.ts +31 -47
- package/src/plugins/blog/client/plugin.tsx +36 -39
- package/src/plugins/client/index.ts +5 -1
- package/src/plugins/cms/api/plugin.ts +14 -17
- package/src/plugins/cms/client/plugin.tsx +18 -21
- package/src/plugins/cms/types.ts +7 -7
- package/src/plugins/form-builder/api/plugin.ts +64 -64
- package/src/plugins/form-builder/client/plugin.tsx +19 -18
- package/src/plugins/form-builder/types.ts +19 -24
- package/src/plugins/kanban/api/plugin.ts +111 -136
- package/src/plugins/kanban/client/plugin.tsx +35 -41
- package/src/plugins/ui-builder/client/plugin.tsx +11 -10
- package/src/plugins/ui-builder/types.ts +4 -4
- package/src/plugins/utils.ts +92 -1
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
defineClientPlugin,
|
|
3
3
|
createApiClient,
|
|
4
4
|
isConnectionError,
|
|
5
|
+
runClientHookWithShim,
|
|
5
6
|
} from "@btst/stack/plugins/client";
|
|
6
7
|
import { createRoute } from "@btst/yar";
|
|
7
8
|
import type { QueryClient } from "@tanstack/react-query";
|
|
@@ -86,39 +87,39 @@ export interface KanbanClientConfig {
|
|
|
86
87
|
*/
|
|
87
88
|
export interface KanbanClientHooks {
|
|
88
89
|
/**
|
|
89
|
-
* Called before loading boards list.
|
|
90
|
+
* Called before loading boards list. Throw an error to cancel loading.
|
|
90
91
|
*/
|
|
91
|
-
beforeLoadBoards?: (context: LoaderContext) => Promise<
|
|
92
|
+
beforeLoadBoards?: (context: LoaderContext) => Promise<void> | void;
|
|
92
93
|
/**
|
|
93
|
-
* Called after boards are loaded.
|
|
94
|
+
* Called after boards are loaded. Throw an error to cancel further processing.
|
|
94
95
|
*/
|
|
95
96
|
afterLoadBoards?: (
|
|
96
97
|
boards: SerializedBoardWithColumns[] | null,
|
|
97
98
|
context: LoaderContext,
|
|
98
|
-
) => Promise<
|
|
99
|
+
) => Promise<void> | void;
|
|
99
100
|
/**
|
|
100
|
-
* Called before loading a single board.
|
|
101
|
+
* Called before loading a single board. Throw an error to cancel loading.
|
|
101
102
|
*/
|
|
102
103
|
beforeLoadBoard?: (
|
|
103
104
|
boardId: string,
|
|
104
105
|
context: LoaderContext,
|
|
105
|
-
) => Promise<
|
|
106
|
+
) => Promise<void> | void;
|
|
106
107
|
/**
|
|
107
|
-
* Called after a board is loaded.
|
|
108
|
+
* Called after a board is loaded. Throw an error to cancel further processing.
|
|
108
109
|
*/
|
|
109
110
|
afterLoadBoard?: (
|
|
110
111
|
board: SerializedBoardWithColumns | null,
|
|
111
112
|
boardId: string,
|
|
112
113
|
context: LoaderContext,
|
|
113
|
-
) => Promise<
|
|
114
|
+
) => Promise<void> | void;
|
|
114
115
|
/**
|
|
115
|
-
* Called before loading the new board page.
|
|
116
|
+
* Called before loading the new board page. Throw an error to cancel.
|
|
116
117
|
*/
|
|
117
|
-
beforeLoadNewBoard?: (context: LoaderContext) => Promise<
|
|
118
|
+
beforeLoadNewBoard?: (context: LoaderContext) => Promise<void> | void;
|
|
118
119
|
/**
|
|
119
|
-
* Called after the new board page is loaded.
|
|
120
|
+
* Called after the new board page is loaded. Throw an error to cancel.
|
|
120
121
|
*/
|
|
121
|
-
afterLoadNewBoard?: (context: LoaderContext) => Promise<
|
|
122
|
+
afterLoadNewBoard?: (context: LoaderContext) => Promise<void> | void;
|
|
122
123
|
/**
|
|
123
124
|
* Called when a loading error occurs
|
|
124
125
|
*/
|
|
@@ -141,10 +142,10 @@ function createBoardsLoader(config: KanbanClientConfig) {
|
|
|
141
142
|
|
|
142
143
|
try {
|
|
143
144
|
if (hooks?.beforeLoadBoards) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
await runClientHookWithShim(
|
|
146
|
+
() => hooks.beforeLoadBoards!(context),
|
|
147
|
+
"Load prevented by beforeLoadBoards hook",
|
|
148
|
+
);
|
|
148
149
|
}
|
|
149
150
|
|
|
150
151
|
const client = createApiClient<KanbanApiRouter>({
|
|
@@ -161,13 +162,10 @@ function createBoardsLoader(config: KanbanClientConfig) {
|
|
|
161
162
|
const boards = queryClient.getQueryData<SerializedBoardWithColumns[]>(
|
|
162
163
|
listQuery.queryKey,
|
|
163
164
|
);
|
|
164
|
-
|
|
165
|
-
boards || null,
|
|
166
|
-
|
|
165
|
+
await runClientHookWithShim(
|
|
166
|
+
() => hooks.afterLoadBoards!(boards || null, context),
|
|
167
|
+
"Load prevented by afterLoadBoards hook",
|
|
167
168
|
);
|
|
168
|
-
if (canContinue === false) {
|
|
169
|
-
throw new Error("Load prevented by afterLoadBoards hook");
|
|
170
|
-
}
|
|
171
169
|
}
|
|
172
170
|
|
|
173
171
|
const queryState = queryClient.getQueryState(listQuery.queryKey);
|
|
@@ -210,10 +208,10 @@ function createBoardLoader(boardId: string, config: KanbanClientConfig) {
|
|
|
210
208
|
|
|
211
209
|
try {
|
|
212
210
|
if (hooks?.beforeLoadBoard) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
211
|
+
await runClientHookWithShim(
|
|
212
|
+
() => hooks.beforeLoadBoard!(boardId, context),
|
|
213
|
+
"Load prevented by beforeLoadBoard hook",
|
|
214
|
+
);
|
|
217
215
|
}
|
|
218
216
|
|
|
219
217
|
const client = createApiClient<KanbanApiRouter>({
|
|
@@ -229,14 +227,10 @@ function createBoardLoader(boardId: string, config: KanbanClientConfig) {
|
|
|
229
227
|
const board = queryClient.getQueryData<SerializedBoardWithColumns>(
|
|
230
228
|
boardQuery.queryKey,
|
|
231
229
|
);
|
|
232
|
-
|
|
233
|
-
board || null,
|
|
234
|
-
|
|
235
|
-
context,
|
|
230
|
+
await runClientHookWithShim(
|
|
231
|
+
() => hooks.afterLoadBoard!(board || null, boardId, context),
|
|
232
|
+
"Load prevented by afterLoadBoard hook",
|
|
236
233
|
);
|
|
237
|
-
if (canContinue === false) {
|
|
238
|
-
throw new Error("Load prevented by afterLoadBoard hook");
|
|
239
|
-
}
|
|
240
234
|
}
|
|
241
235
|
|
|
242
236
|
const queryState = queryClient.getQueryState(boardQuery.queryKey);
|
|
@@ -278,17 +272,17 @@ function createNewBoardLoader(config: KanbanClientConfig) {
|
|
|
278
272
|
|
|
279
273
|
try {
|
|
280
274
|
if (hooks?.beforeLoadNewBoard) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
275
|
+
await runClientHookWithShim(
|
|
276
|
+
() => hooks.beforeLoadNewBoard!(context),
|
|
277
|
+
"Load prevented by beforeLoadNewBoard hook",
|
|
278
|
+
);
|
|
285
279
|
}
|
|
286
280
|
|
|
287
281
|
if (hooks?.afterLoadNewBoard) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
282
|
+
await runClientHookWithShim(
|
|
283
|
+
() => hooks.afterLoadNewBoard!(context),
|
|
284
|
+
"Load prevented by afterLoadNewBoard hook",
|
|
285
|
+
);
|
|
292
286
|
}
|
|
293
287
|
} catch (error) {
|
|
294
288
|
if (hooks?.onLoadError) {
|
|
@@ -3,6 +3,7 @@ import { lazy } from "react";
|
|
|
3
3
|
import {
|
|
4
4
|
defineClientPlugin,
|
|
5
5
|
createApiClient,
|
|
6
|
+
runClientHookWithShim,
|
|
6
7
|
} from "@btst/stack/plugins/client";
|
|
7
8
|
import { createRoute } from "@btst/yar";
|
|
8
9
|
import type { QueryClient } from "@tanstack/react-query";
|
|
@@ -69,10 +70,10 @@ function createPageListLoader(config: UIBuilderClientConfig) {
|
|
|
69
70
|
try {
|
|
70
71
|
// Before hook - authorization check
|
|
71
72
|
if (hooks?.beforeLoadPageList) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
await runClientHookWithShim(
|
|
74
|
+
() => hooks.beforeLoadPageList!(context),
|
|
75
|
+
"Load prevented by beforeLoadPageList hook",
|
|
76
|
+
);
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
const client = createApiClient<CMSApiRouter>({
|
|
@@ -161,10 +162,10 @@ function createPageBuilderLoader(
|
|
|
161
162
|
try {
|
|
162
163
|
// Before hook - authorization check
|
|
163
164
|
if (hooks?.beforeLoadPageBuilder) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
await runClientHookWithShim(
|
|
166
|
+
() => hooks.beforeLoadPageBuilder!(id, context),
|
|
167
|
+
"Load prevented by beforeLoadPageBuilder hook",
|
|
168
|
+
);
|
|
168
169
|
}
|
|
169
170
|
|
|
170
171
|
const client = createApiClient<CMSApiRouter>({
|
|
@@ -273,11 +274,11 @@ function createPageBuilderMeta(
|
|
|
273
274
|
* hooks: {
|
|
274
275
|
* beforeLoadPageList: async (ctx) => {
|
|
275
276
|
* const session = await getSession(ctx.headers)
|
|
276
|
-
*
|
|
277
|
+
* if (!session?.user?.isAdmin) throw new Error("Admin access required")
|
|
277
278
|
* },
|
|
278
279
|
* beforeLoadPageBuilder: async (pageId, ctx) => {
|
|
279
280
|
* const session = await getSession(ctx.headers)
|
|
280
|
-
*
|
|
281
|
+
* if (!session?.user?.isAdmin) throw new Error("Admin access required")
|
|
281
282
|
* },
|
|
282
283
|
* onLoadError: () => redirect("/auth/sign-in"),
|
|
283
284
|
* },
|
|
@@ -92,24 +92,24 @@ export interface LoaderContext {
|
|
|
92
92
|
*/
|
|
93
93
|
export interface UIBuilderClientHooks {
|
|
94
94
|
/**
|
|
95
|
-
* Called before loading the page list.
|
|
95
|
+
* Called before loading the page list. Throw an error to cancel loading.
|
|
96
96
|
* @param context - Loader context with path, params, etc.
|
|
97
97
|
*/
|
|
98
|
-
beforeLoadPageList?: (context: LoaderContext) => Promise<
|
|
98
|
+
beforeLoadPageList?: (context: LoaderContext) => Promise<void> | void;
|
|
99
99
|
/**
|
|
100
100
|
* Called after the page list is loaded.
|
|
101
101
|
* @param context - Loader context
|
|
102
102
|
*/
|
|
103
103
|
afterLoadPageList?: (context: LoaderContext) => Promise<void> | void;
|
|
104
104
|
/**
|
|
105
|
-
* Called before loading the page builder.
|
|
105
|
+
* Called before loading the page builder. Throw an error to cancel loading.
|
|
106
106
|
* @param pageId - The page ID (undefined for new pages)
|
|
107
107
|
* @param context - Loader context
|
|
108
108
|
*/
|
|
109
109
|
beforeLoadPageBuilder?: (
|
|
110
110
|
pageId: string | undefined,
|
|
111
111
|
context: LoaderContext,
|
|
112
|
-
) => Promise<
|
|
112
|
+
) => Promise<void> | void;
|
|
113
113
|
/**
|
|
114
114
|
* Called after the page builder is loaded.
|
|
115
115
|
* @param pageId - The page ID (undefined for new pages)
|
package/src/plugins/utils.ts
CHANGED
|
@@ -1,5 +1,96 @@
|
|
|
1
1
|
import { createClient } from "better-call/client";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Runs a hook with backward-compatible denial handling.
|
|
5
|
+
* Hooks may deny by returning a falsy value (old) or throwing (new).
|
|
6
|
+
* Both are normalized to an HTTP error via `createError` (`ctx.error`).
|
|
7
|
+
* Returns the hook's result so transform hooks can apply mutations.
|
|
8
|
+
*
|
|
9
|
+
* ## Migration note (v2.4 → v2.5)
|
|
10
|
+
*
|
|
11
|
+
* Old-style hooks signalled denial by returning `false` and allowance by returning `true`.
|
|
12
|
+
* Pre-shim call sites used `if (!result)` which treated `undefined` (fall-through) as deny.
|
|
13
|
+
* New-style hooks throw an Error to deny and return void/undefined to allow.
|
|
14
|
+
*
|
|
15
|
+
* The shim detects old-style boolean returns at runtime and emits a deprecation warning so
|
|
16
|
+
* that any hook with a code path returning a boolean is surfaced immediately. Hooks that fall
|
|
17
|
+
* through to `undefined` on **every** code path (no boolean return anywhere) cannot be
|
|
18
|
+
* distinguished from new-style void hooks — those hooks must be audited manually and updated
|
|
19
|
+
* to throw explicitly when they intend to deny access.
|
|
20
|
+
*/
|
|
21
|
+
export async function runHookWithShim<T>(
|
|
22
|
+
hookFn: () => Promise<T> | T,
|
|
23
|
+
createError: (
|
|
24
|
+
status: keyof typeof statusCodes | Status,
|
|
25
|
+
body: { message: string },
|
|
26
|
+
) => any,
|
|
27
|
+
defaultMessage: string,
|
|
28
|
+
errorStatus = 403 as keyof typeof statusCodes | Status,
|
|
29
|
+
): Promise<Exclude<Awaited<T>, false>> {
|
|
30
|
+
let result: Awaited<T>;
|
|
31
|
+
try {
|
|
32
|
+
result = await hookFn();
|
|
33
|
+
} catch (e) {
|
|
34
|
+
throw createError(errorStatus, {
|
|
35
|
+
message: e instanceof Error ? e.message : defaultMessage,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
// Detect old-style boolean returns (pre-v2.5 pattern).
|
|
39
|
+
// Emitting a warning here is the only reliable way to surface hooks that still rely on
|
|
40
|
+
// boolean returns — including hooks where one branch returns `false` and another falls
|
|
41
|
+
// through to `undefined`, which was previously denied by `if (!result)` at the call site
|
|
42
|
+
// but would now silently allow if the warning is not acted on.
|
|
43
|
+
if (typeof result === "boolean") {
|
|
44
|
+
if (process.env.NODE_ENV !== "production") {
|
|
45
|
+
console.warn(
|
|
46
|
+
`[btst] A lifecycle hook returned a boolean (${result}). ` +
|
|
47
|
+
`Boolean returns are deprecated — throw an Error to deny access instead. ` +
|
|
48
|
+
`IMPORTANT: any code path in this hook that falls through to undefined ` +
|
|
49
|
+
`now ALLOWS access (previously denied). ` +
|
|
50
|
+
`Update the hook to throw new Error("Unauthorized") to deny.`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
if (!result) {
|
|
54
|
+
throw createError(errorStatus, { message: defaultMessage });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return result as Exclude<Awaited<T>, false>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Client-side equivalent of runHookWithShim — throws a plain Error instead of an HTTP error.
|
|
62
|
+
* Hooks may deny by returning false (old) or throwing (new); both normalize to an Error.
|
|
63
|
+
*
|
|
64
|
+
* See `runHookWithShim` for the full migration note on boolean-vs-void semantics.
|
|
65
|
+
*/
|
|
66
|
+
export async function runClientHookWithShim<T>(
|
|
67
|
+
hookFn: () => Promise<T> | T,
|
|
68
|
+
defaultMessage: string,
|
|
69
|
+
): Promise<Exclude<Awaited<T>, false>> {
|
|
70
|
+
let result: Awaited<T>;
|
|
71
|
+
try {
|
|
72
|
+
result = await hookFn();
|
|
73
|
+
} catch (e) {
|
|
74
|
+
throw e instanceof Error ? e : new Error(defaultMessage);
|
|
75
|
+
}
|
|
76
|
+
// Detect old-style boolean returns and warn; see runHookWithShim for rationale.
|
|
77
|
+
if (typeof result === "boolean") {
|
|
78
|
+
if (process.env.NODE_ENV !== "production") {
|
|
79
|
+
console.warn(
|
|
80
|
+
`[btst] A lifecycle hook returned a boolean (${result}). ` +
|
|
81
|
+
`Boolean returns are deprecated — throw an Error to deny access instead. ` +
|
|
82
|
+
`IMPORTANT: any code path in this hook that falls through to undefined ` +
|
|
83
|
+
`now ALLOWS access (previously denied). ` +
|
|
84
|
+
`Update the hook to throw new Error("Unauthorized") to deny.`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
if (!result) {
|
|
88
|
+
throw new Error(defaultMessage);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return result as Exclude<Awaited<T>, false>;
|
|
92
|
+
}
|
|
93
|
+
|
|
3
94
|
/**
|
|
4
95
|
* Returns true when a fetch error is a connection-refused / no-server error.
|
|
5
96
|
* Used in SSR loaders to emit an actionable build-time warning when
|
|
@@ -18,7 +109,7 @@ export function isConnectionError(err: unknown): boolean {
|
|
|
18
109
|
code === "ERR_CONNECTION_REFUSED"
|
|
19
110
|
);
|
|
20
111
|
}
|
|
21
|
-
import type { Router, Endpoint } from "better-call";
|
|
112
|
+
import type { Router, Endpoint, Status, statusCodes } from "better-call";
|
|
22
113
|
|
|
23
114
|
interface CreateApiClientOptions {
|
|
24
115
|
baseURL?: string;
|