@btst/stack 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/index.cjs +9 -1
- package/dist/api/index.d.cts +4 -4
- package/dist/api/index.d.mts +4 -4
- package/dist/api/index.d.ts +4 -4
- package/dist/api/index.mjs +9 -1
- package/dist/client/index.d.cts +2 -2
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/packages/stack/src/plugins/ai-chat/api/getters.cjs +42 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/getters.mjs +39 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.cjs +5 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.mjs +5 -0
- package/dist/packages/stack/src/plugins/blog/api/getters.cjs +131 -0
- package/dist/packages/stack/src/plugins/blog/api/getters.mjs +127 -0
- package/dist/packages/stack/src/plugins/blog/api/plugin.cjs +60 -107
- package/dist/packages/stack/src/plugins/blog/api/plugin.mjs +60 -107
- package/dist/packages/stack/src/plugins/blog/api/query-key-defs.cjs +18 -0
- package/dist/packages/stack/src/plugins/blog/api/query-key-defs.mjs +15 -0
- package/dist/packages/stack/src/plugins/blog/api/serializers.cjs +21 -0
- package/dist/packages/stack/src/plugins/blog/api/serializers.mjs +18 -0
- package/dist/packages/stack/src/plugins/blog/client/plugin.cjs +16 -1
- package/dist/packages/stack/src/plugins/blog/client/plugin.mjs +17 -2
- package/dist/packages/stack/src/plugins/cms/api/getters.cjs +156 -0
- package/dist/packages/stack/src/plugins/cms/api/getters.mjs +147 -0
- package/dist/packages/stack/src/plugins/cms/api/plugin.cjs +624 -617
- package/dist/packages/stack/src/plugins/cms/api/plugin.mjs +623 -616
- package/dist/packages/stack/src/plugins/cms/api/query-key-defs.cjs +29 -0
- package/dist/packages/stack/src/plugins/cms/api/query-key-defs.mjs +26 -0
- package/dist/packages/stack/src/plugins/cms/client/components/pages/content-editor-page.internal.cjs +1 -1
- package/dist/packages/stack/src/plugins/cms/client/components/pages/content-editor-page.internal.mjs +1 -1
- package/dist/packages/stack/src/plugins/cms/client/hooks/cms-hooks.cjs +6 -3
- package/dist/packages/stack/src/plugins/cms/client/hooks/cms-hooks.mjs +6 -3
- package/dist/packages/stack/src/plugins/cms/client/plugin.cjs +15 -0
- package/dist/packages/stack/src/plugins/cms/client/plugin.mjs +16 -1
- package/dist/packages/stack/src/plugins/form-builder/api/getters.cjs +120 -0
- package/dist/packages/stack/src/plugins/form-builder/api/getters.mjs +112 -0
- package/dist/packages/stack/src/plugins/form-builder/api/plugin.cjs +75 -86
- package/dist/packages/stack/src/plugins/form-builder/api/plugin.mjs +71 -82
- package/dist/packages/stack/src/plugins/form-builder/api/query-key-defs.cjs +37 -0
- package/dist/packages/stack/src/plugins/form-builder/api/query-key-defs.mjs +33 -0
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.cjs +1 -1
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.mjs +1 -1
- package/dist/packages/stack/src/plugins/form-builder/client/plugin.cjs +15 -0
- package/dist/packages/stack/src/plugins/form-builder/client/plugin.mjs +16 -1
- package/dist/packages/stack/src/plugins/kanban/api/getters.cjs +84 -0
- package/dist/packages/stack/src/plugins/kanban/api/getters.mjs +81 -0
- package/dist/packages/stack/src/plugins/kanban/api/plugin.cjs +37 -123
- package/dist/packages/stack/src/plugins/kanban/api/plugin.mjs +37 -123
- package/dist/packages/stack/src/plugins/kanban/api/query-key-defs.cjs +26 -0
- package/dist/packages/stack/src/plugins/kanban/api/query-key-defs.mjs +23 -0
- package/dist/packages/stack/src/plugins/kanban/api/serializers.cjs +30 -0
- package/dist/packages/stack/src/plugins/kanban/api/serializers.mjs +26 -0
- package/dist/packages/stack/src/plugins/kanban/client/plugin.cjs +11 -1
- package/dist/packages/stack/src/plugins/kanban/client/plugin.mjs +12 -2
- package/dist/packages/stack/src/plugins/utils.cjs +6 -0
- package/dist/packages/stack/src/plugins/utils.mjs +6 -1
- package/dist/plugins/ai-chat/api/index.cjs +3 -0
- package/dist/plugins/ai-chat/api/index.d.cts +27 -4
- package/dist/plugins/ai-chat/api/index.d.mts +27 -4
- package/dist/plugins/ai-chat/api/index.d.ts +27 -4
- package/dist/plugins/ai-chat/api/index.mjs +1 -0
- package/dist/plugins/ai-chat/client/hooks/index.d.cts +2 -2
- package/dist/plugins/ai-chat/client/hooks/index.d.mts +2 -2
- package/dist/plugins/ai-chat/client/hooks/index.d.ts +2 -2
- package/dist/plugins/ai-chat/query-keys.d.cts +9 -284
- package/dist/plugins/ai-chat/query-keys.d.mts +9 -284
- package/dist/plugins/ai-chat/query-keys.d.ts +9 -284
- package/dist/plugins/api/index.d.cts +4 -3
- package/dist/plugins/api/index.d.mts +4 -3
- package/dist/plugins/api/index.d.ts +4 -3
- package/dist/plugins/blog/api/index.cjs +9 -0
- package/dist/plugins/blog/api/index.d.cts +20 -4
- package/dist/plugins/blog/api/index.d.mts +20 -4
- package/dist/plugins/blog/api/index.d.ts +20 -4
- package/dist/plugins/blog/api/index.mjs +3 -0
- package/dist/plugins/blog/client/hooks/index.d.cts +5 -5
- package/dist/plugins/blog/client/hooks/index.d.mts +5 -5
- package/dist/plugins/blog/client/hooks/index.d.ts +5 -5
- package/dist/plugins/blog/client/index.d.cts +1 -1
- package/dist/plugins/blog/client/index.d.mts +1 -1
- package/dist/plugins/blog/client/index.d.ts +1 -1
- package/dist/plugins/blog/query-keys.cjs +13 -9
- package/dist/plugins/blog/query-keys.d.cts +8 -333
- package/dist/plugins/blog/query-keys.d.mts +8 -333
- package/dist/plugins/blog/query-keys.d.ts +8 -333
- package/dist/plugins/blog/query-keys.mjs +13 -9
- package/dist/plugins/client/index.cjs +1 -0
- package/dist/plugins/client/index.d.cts +10 -3
- package/dist/plugins/client/index.d.mts +10 -3
- package/dist/plugins/client/index.d.ts +10 -3
- package/dist/plugins/client/index.mjs +1 -1
- package/dist/plugins/cms/api/index.cjs +10 -0
- package/dist/plugins/cms/api/index.d.cts +7 -163
- package/dist/plugins/cms/api/index.d.mts +7 -163
- package/dist/plugins/cms/api/index.d.ts +7 -163
- package/dist/plugins/cms/api/index.mjs +2 -0
- 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/query-keys.cjs +2 -1
- package/dist/plugins/cms/query-keys.d.cts +6 -9
- package/dist/plugins/cms/query-keys.d.mts +6 -9
- package/dist/plugins/cms/query-keys.d.ts +6 -9
- package/dist/plugins/cms/query-keys.mjs +2 -1
- package/dist/plugins/form-builder/api/index.cjs +10 -0
- package/dist/plugins/form-builder/api/index.d.cts +7 -141
- package/dist/plugins/form-builder/api/index.d.mts +7 -141
- package/dist/plugins/form-builder/api/index.d.ts +7 -141
- package/dist/plugins/form-builder/api/index.mjs +2 -0
- 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/query-keys.cjs +3 -2
- package/dist/plugins/form-builder/query-keys.d.cts +7 -6
- package/dist/plugins/form-builder/query-keys.d.mts +7 -6
- package/dist/plugins/form-builder/query-keys.d.ts +7 -6
- package/dist/plugins/form-builder/query-keys.mjs +3 -2
- package/dist/plugins/kanban/api/index.cjs +9 -0
- package/dist/plugins/kanban/api/index.d.cts +17 -395
- package/dist/plugins/kanban/api/index.d.mts +17 -395
- package/dist/plugins/kanban/api/index.d.ts +17 -395
- package/dist/plugins/kanban/api/index.mjs +3 -0
- package/dist/plugins/kanban/client/components/index.d.cts +1 -1
- package/dist/plugins/kanban/client/components/index.d.mts +1 -1
- package/dist/plugins/kanban/client/components/index.d.ts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.cts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.mts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.ts +1 -1
- package/dist/plugins/kanban/client/index.d.cts +1 -1
- package/dist/plugins/kanban/client/index.d.mts +1 -1
- package/dist/plugins/kanban/client/index.d.ts +1 -1
- package/dist/plugins/kanban/query-keys.cjs +6 -12
- package/dist/plugins/kanban/query-keys.d.cts +5 -16
- package/dist/plugins/kanban/query-keys.d.mts +5 -16
- package/dist/plugins/kanban/query-keys.d.ts +5 -16
- package/dist/plugins/kanban/query-keys.mjs +6 -12
- package/dist/plugins/open-api/api/index.d.cts +2 -2
- package/dist/plugins/open-api/api/index.d.mts +2 -2
- package/dist/plugins/open-api/api/index.d.ts +2 -2
- package/dist/plugins/route-docs/client/index.d.cts +1 -1
- package/dist/plugins/route-docs/client/index.d.mts +1 -1
- package/dist/plugins/route-docs/client/index.d.ts +1 -1
- package/dist/plugins/ui-builder/index.d.cts +1 -1
- package/dist/plugins/ui-builder/index.d.mts +1 -1
- package/dist/plugins/ui-builder/index.d.ts +1 -1
- package/dist/shared/{stack.BoA0xkJv.d.cts → stack.7n9Y_u7N.d.cts} +33 -7
- package/dist/shared/{stack.BoA0xkJv.d.mts → stack.7n9Y_u7N.d.mts} +33 -7
- package/dist/shared/{stack.BoA0xkJv.d.ts → stack.7n9Y_u7N.d.ts} +33 -7
- package/dist/shared/stack.B1EeBt1b.d.ts +297 -0
- package/dist/shared/stack.BIXEI6v_.d.mts +419 -0
- package/dist/shared/stack.BKfolAyK.d.ts +419 -0
- package/dist/shared/stack.BeSm90va.d.ts +289 -0
- package/dist/shared/stack.BpolpQpf.d.cts +445 -0
- package/dist/shared/stack.C5dtIncc.d.mts +293 -0
- package/dist/shared/stack.CIP6QS9l.d.ts +293 -0
- package/dist/shared/stack.CMh_EdxW.d.cts +289 -0
- package/dist/shared/stack.CP68pFEH.d.mts +297 -0
- package/dist/shared/{stack.BsXokfNh.d.mts → stack.CVDTkMoO.d.cts} +8 -2
- package/dist/shared/{stack.BsXokfNh.d.ts → stack.CVDTkMoO.d.mts} +8 -2
- package/dist/shared/{stack.BsXokfNh.d.cts → stack.CVDTkMoO.d.ts} +8 -2
- package/dist/shared/{stack.DKDMI-QO.d.mts → stack.DJaKVY7v.d.cts} +7 -1
- package/dist/shared/{stack.DKDMI-QO.d.ts → stack.DJaKVY7v.d.mts} +7 -1
- package/dist/shared/{stack.DKDMI-QO.d.cts → stack.DJaKVY7v.d.ts} +7 -1
- package/dist/shared/{stack.DzH_wcvr.d.mts → stack.DdI5W6MB.d.cts} +9 -3
- package/dist/shared/{stack.DzH_wcvr.d.ts → stack.DdI5W6MB.d.mts} +9 -3
- package/dist/shared/{stack.DzH_wcvr.d.cts → stack.DdI5W6MB.d.ts} +9 -3
- package/dist/shared/stack.Dg09R0oB.d.mts +289 -0
- package/dist/shared/stack.Dw0Ly2TM.d.cts +293 -0
- package/dist/shared/stack.IdtKDRka.d.cts +297 -0
- package/dist/shared/stack.TIBF2AOx.d.ts +445 -0
- package/dist/shared/stack.rTy7-wQU.d.mts +445 -0
- package/dist/shared/stack.snB1EDP7.d.cts +419 -0
- package/package.json +3 -3
- package/src/__tests__/stack-api.test.ts +118 -0
- package/src/api/index.ts +15 -1
- package/src/plugins/ai-chat/__tests__/getters.test.ts +109 -0
- package/src/plugins/ai-chat/api/getters.ts +71 -0
- package/src/plugins/ai-chat/api/index.ts +1 -0
- package/src/plugins/ai-chat/api/plugin.ts +8 -0
- package/src/plugins/api/index.ts +3 -1
- package/src/plugins/blog/__tests__/getters.test.ts +540 -0
- package/src/plugins/blog/api/getters.ts +243 -0
- package/src/plugins/blog/api/index.ts +9 -0
- package/src/plugins/blog/api/plugin.ts +98 -141
- package/src/plugins/blog/api/query-key-defs.ts +46 -0
- package/src/plugins/blog/api/serializers.ts +27 -0
- package/src/plugins/blog/client/plugin.tsx +21 -1
- package/src/plugins/blog/query-keys.ts +21 -20
- package/src/plugins/client/index.ts +1 -1
- package/src/plugins/cms/__tests__/getters.test.ts +206 -0
- package/src/plugins/cms/api/getters.ts +268 -0
- package/src/plugins/cms/api/index.ts +15 -1
- package/src/plugins/cms/api/plugin.ts +151 -150
- package/src/plugins/cms/api/query-key-defs.ts +53 -0
- package/src/plugins/cms/api/serializers.ts +12 -0
- package/src/plugins/cms/client/components/pages/content-editor-page.internal.tsx +1 -1
- package/src/plugins/cms/client/hooks/cms-hooks.tsx +3 -0
- package/src/plugins/cms/client/plugin.tsx +19 -0
- package/src/plugins/cms/query-keys.ts +2 -1
- package/src/plugins/cms/types.ts +1 -1
- package/src/plugins/form-builder/__tests__/getters.test.ts +159 -0
- package/src/plugins/form-builder/api/getters.ts +226 -0
- package/src/plugins/form-builder/api/index.ts +15 -1
- package/src/plugins/form-builder/api/plugin.ts +107 -109
- package/src/plugins/form-builder/api/query-key-defs.ts +79 -0
- package/src/plugins/form-builder/api/serializers.ts +12 -0
- package/src/plugins/form-builder/client/components/pages/submissions-page.internal.tsx +1 -1
- package/src/plugins/form-builder/client/plugin.tsx +19 -0
- package/src/plugins/form-builder/query-keys.ts +6 -2
- package/src/plugins/form-builder/types.ts +2 -2
- package/src/plugins/kanban/__tests__/getters.test.ts +172 -0
- package/src/plugins/kanban/api/getters.ts +149 -0
- package/src/plugins/kanban/api/index.ts +4 -0
- package/src/plugins/kanban/api/plugin.ts +65 -146
- package/src/plugins/kanban/api/query-key-defs.ts +54 -0
- package/src/plugins/kanban/api/serializers.ts +49 -0
- package/src/plugins/kanban/client/plugin.tsx +15 -1
- package/src/plugins/kanban/query-keys.ts +10 -14
- package/src/plugins/utils.ts +19 -0
- package/src/types.ts +44 -5
- package/dist/shared/{stack.CbuN2zVV.d.cts → stack.CBON0dWL.d.cts} +7 -7
- package/dist/shared/{stack.CbuN2zVV.d.mts → stack.CBON0dWL.d.mts} +7 -7
- package/dist/shared/{stack.CbuN2zVV.d.ts → stack.CBON0dWL.d.ts} +7 -7
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
|
+
import { QueryClient } from '@tanstack/react-query';
|
|
3
|
+
import { createApiClient } from '@btst/stack/plugins/client';
|
|
4
|
+
import { P as Post, T as Tag, c as createPostSchema, u as updatePostSchema, S as SerializedPost, a as SerializedTag } from './stack.CBON0dWL.cjs';
|
|
5
|
+
import * as _btst_stack_plugins_api from '@btst/stack/plugins/api';
|
|
6
|
+
import * as better_call from 'better-call';
|
|
7
|
+
import { Adapter } from '@btst/db';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Parameters for filtering/paginating posts.
|
|
12
|
+
* Mirrors the shape of the list API query schema.
|
|
13
|
+
*/
|
|
14
|
+
interface PostListParams {
|
|
15
|
+
slug?: string;
|
|
16
|
+
tagSlug?: string;
|
|
17
|
+
offset?: number;
|
|
18
|
+
limit?: number;
|
|
19
|
+
query?: string;
|
|
20
|
+
published?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Paginated result returned by {@link getAllPosts}.
|
|
24
|
+
*/
|
|
25
|
+
interface PostListResult {
|
|
26
|
+
items: Array<Post & {
|
|
27
|
+
tags: Tag[];
|
|
28
|
+
}>;
|
|
29
|
+
total: number;
|
|
30
|
+
limit?: number;
|
|
31
|
+
offset?: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Retrieve all posts matching optional filter criteria.
|
|
35
|
+
* Pure DB function — no hooks, no HTTP context. Safe for SSG and server-side use.
|
|
36
|
+
*
|
|
37
|
+
* @remarks **Security:** Authorization hooks (e.g. `onBeforeListPosts`) are NOT
|
|
38
|
+
* called. The caller is responsible for any access-control checks before
|
|
39
|
+
* invoking this function.
|
|
40
|
+
*
|
|
41
|
+
* @param adapter - The database adapter
|
|
42
|
+
* @param params - Optional filter/pagination parameters (same shape as the list API query)
|
|
43
|
+
*/
|
|
44
|
+
declare function getAllPosts(adapter: Adapter, params?: PostListParams): Promise<PostListResult>;
|
|
45
|
+
/**
|
|
46
|
+
* Retrieve a single post by its slug, including associated tags.
|
|
47
|
+
* Returns null if no post is found.
|
|
48
|
+
* Pure DB function — no hooks, no HTTP context. Safe for SSG and server-side use.
|
|
49
|
+
*
|
|
50
|
+
* @remarks **Security:** Authorization hooks are NOT called. The caller is
|
|
51
|
+
* responsible for any access-control checks before invoking this function.
|
|
52
|
+
*
|
|
53
|
+
* @param adapter - The database adapter
|
|
54
|
+
* @param slug - The post slug
|
|
55
|
+
*/
|
|
56
|
+
declare function getPostBySlug(adapter: Adapter, slug: string): Promise<(Post & {
|
|
57
|
+
tags: Tag[];
|
|
58
|
+
}) | null>;
|
|
59
|
+
/**
|
|
60
|
+
* Retrieve all tags, sorted alphabetically by name.
|
|
61
|
+
* Pure DB function — no hooks, no HTTP context. Safe for SSG and server-side use.
|
|
62
|
+
*
|
|
63
|
+
* @remarks **Security:** Authorization hooks are NOT called. The caller is
|
|
64
|
+
* responsible for any access-control checks before invoking this function.
|
|
65
|
+
*
|
|
66
|
+
* @param adapter - The database adapter
|
|
67
|
+
*/
|
|
68
|
+
declare function getAllTags(adapter: Adapter): Promise<Tag[]>;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Route keys for the blog plugin — matches the keys returned by
|
|
72
|
+
* `stackClient.router.getRoute(path).routeKey`.
|
|
73
|
+
*/
|
|
74
|
+
type BlogRouteKey = "posts" | "drafts" | "post" | "tag" | "newPost" | "editPost";
|
|
75
|
+
/**
|
|
76
|
+
* Overloaded signature for `prefetchForRoute`.
|
|
77
|
+
* TypeScript enforces the correct params for each routeKey at call sites.
|
|
78
|
+
*/
|
|
79
|
+
interface BlogPrefetchForRoute {
|
|
80
|
+
(key: "posts" | "drafts" | "newPost", qc: QueryClient): Promise<void>;
|
|
81
|
+
(key: "post" | "editPost", qc: QueryClient, params: {
|
|
82
|
+
slug: string;
|
|
83
|
+
}): Promise<void>;
|
|
84
|
+
(key: "tag", qc: QueryClient, params: {
|
|
85
|
+
tagSlug: string;
|
|
86
|
+
}): Promise<void>;
|
|
87
|
+
}
|
|
88
|
+
declare const PostListQuerySchema: z.ZodObject<{
|
|
89
|
+
slug: z.ZodOptional<z.ZodString>;
|
|
90
|
+
tagSlug: z.ZodOptional<z.ZodString>;
|
|
91
|
+
offset: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
|
|
92
|
+
limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
|
|
93
|
+
query: z.ZodOptional<z.ZodString>;
|
|
94
|
+
published: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<boolean | undefined, string | undefined>>;
|
|
95
|
+
}, z.core.$strip>;
|
|
96
|
+
declare const NextPreviousPostsQuerySchema: z.ZodObject<{
|
|
97
|
+
date: z.ZodCoercedDate<unknown>;
|
|
98
|
+
}, z.core.$strip>;
|
|
99
|
+
/**
|
|
100
|
+
* Context passed to blog API hooks
|
|
101
|
+
*/
|
|
102
|
+
interface BlogApiContext<TBody = any, TParams = any, TQuery = any> {
|
|
103
|
+
body?: TBody;
|
|
104
|
+
params?: TParams;
|
|
105
|
+
query?: TQuery;
|
|
106
|
+
request?: Request;
|
|
107
|
+
headers?: Headers;
|
|
108
|
+
[key: string]: any;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Configuration hooks for blog backend plugin
|
|
112
|
+
* All hooks are optional and allow consumers to customize behavior
|
|
113
|
+
*/
|
|
114
|
+
interface BlogBackendHooks {
|
|
115
|
+
/**
|
|
116
|
+
* Called before listing posts. Return false to deny access.
|
|
117
|
+
* @param filter - Query parameters for filtering posts
|
|
118
|
+
* @param context - Request context with headers, etc.
|
|
119
|
+
*/
|
|
120
|
+
onBeforeListPosts?: (filter: z.infer<typeof PostListQuerySchema>, context: BlogApiContext) => Promise<boolean> | boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Called before creating a post. Return false to deny access.
|
|
123
|
+
* @param data - Post data being created
|
|
124
|
+
* @param context - Request context with headers, etc.
|
|
125
|
+
*/
|
|
126
|
+
onBeforeCreatePost?: (data: z.infer<typeof createPostSchema>, context: BlogApiContext) => Promise<boolean> | boolean;
|
|
127
|
+
/**
|
|
128
|
+
* Called before updating a post. Return false to deny access.
|
|
129
|
+
* @param postId - ID of the post being updated
|
|
130
|
+
* @param data - Updated post data
|
|
131
|
+
* @param context - Request context with headers, etc.
|
|
132
|
+
*/
|
|
133
|
+
onBeforeUpdatePost?: (postId: string, data: z.infer<typeof updatePostSchema>, context: BlogApiContext) => Promise<boolean> | boolean;
|
|
134
|
+
/**
|
|
135
|
+
* Called before deleting a post. Return false to deny access.
|
|
136
|
+
* @param postId - ID of the post being deleted
|
|
137
|
+
* @param context - Request context with headers, etc.
|
|
138
|
+
*/
|
|
139
|
+
onBeforeDeletePost?: (postId: string, context: BlogApiContext) => Promise<boolean> | boolean;
|
|
140
|
+
/**
|
|
141
|
+
* Called after posts are read successfully
|
|
142
|
+
* @param posts - The list of posts returned by the query
|
|
143
|
+
* @param filter - Query parameters used for filtering
|
|
144
|
+
* @param context - Request context
|
|
145
|
+
*/
|
|
146
|
+
onPostsRead?: (posts: Array<Post & {
|
|
147
|
+
tags: Tag[];
|
|
148
|
+
}>, filter: z.infer<typeof PostListQuerySchema>, context: BlogApiContext) => Promise<void> | void;
|
|
149
|
+
/**
|
|
150
|
+
* Called after a post is created successfully
|
|
151
|
+
* @param post - The created post
|
|
152
|
+
* @param context - Request context
|
|
153
|
+
*/
|
|
154
|
+
onPostCreated?: (post: Post, context: BlogApiContext) => Promise<void> | void;
|
|
155
|
+
/**
|
|
156
|
+
* Called after a post is updated successfully
|
|
157
|
+
* @param post - The updated post
|
|
158
|
+
* @param context - Request context
|
|
159
|
+
*/
|
|
160
|
+
onPostUpdated?: (post: Post, context: BlogApiContext) => Promise<void> | void;
|
|
161
|
+
/**
|
|
162
|
+
* Called after a post is deleted successfully
|
|
163
|
+
* @param postId - ID of the deleted post
|
|
164
|
+
* @param context - Request context
|
|
165
|
+
*/
|
|
166
|
+
onPostDeleted?: (postId: string, context: BlogApiContext) => Promise<void> | void;
|
|
167
|
+
/**
|
|
168
|
+
* Called when listing posts fails
|
|
169
|
+
* @param error - The error that occurred
|
|
170
|
+
* @param context - Request context
|
|
171
|
+
*/
|
|
172
|
+
onListPostsError?: (error: Error, context: BlogApiContext) => Promise<void> | void;
|
|
173
|
+
/**
|
|
174
|
+
* Called when creating a post fails
|
|
175
|
+
* @param error - The error that occurred
|
|
176
|
+
* @param context - Request context
|
|
177
|
+
*/
|
|
178
|
+
onCreatePostError?: (error: Error, context: BlogApiContext) => Promise<void> | void;
|
|
179
|
+
/**
|
|
180
|
+
* Called when updating a post fails
|
|
181
|
+
* @param error - The error that occurred
|
|
182
|
+
* @param context - Request context
|
|
183
|
+
*/
|
|
184
|
+
onUpdatePostError?: (error: Error, context: BlogApiContext) => Promise<void> | void;
|
|
185
|
+
/**
|
|
186
|
+
* Called when deleting a post fails
|
|
187
|
+
* @param error - The error that occurred
|
|
188
|
+
* @param context - Request context
|
|
189
|
+
*/
|
|
190
|
+
onDeletePostError?: (error: Error, context: BlogApiContext) => Promise<void> | void;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Blog backend plugin
|
|
194
|
+
* Provides API endpoints for managing blog posts
|
|
195
|
+
* Uses better-db adapter for database operations
|
|
196
|
+
*
|
|
197
|
+
* @param hooks - Optional configuration hooks for customizing plugin behavior
|
|
198
|
+
*/
|
|
199
|
+
declare const blogBackendPlugin: (hooks?: BlogBackendHooks) => _btst_stack_plugins_api.BackendPlugin<{
|
|
200
|
+
readonly listPosts: better_call.StrictEndpoint<"/posts", {
|
|
201
|
+
method: "GET";
|
|
202
|
+
query: z.ZodObject<{
|
|
203
|
+
slug: z.ZodOptional<z.ZodString>;
|
|
204
|
+
tagSlug: z.ZodOptional<z.ZodString>;
|
|
205
|
+
offset: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
|
|
206
|
+
limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
|
|
207
|
+
query: z.ZodOptional<z.ZodString>;
|
|
208
|
+
published: z.ZodPipe<z.ZodOptional<z.ZodString>, z.ZodTransform<boolean | undefined, string | undefined>>;
|
|
209
|
+
}, z.core.$strip>;
|
|
210
|
+
}, PostListResult>;
|
|
211
|
+
readonly createPost: better_call.StrictEndpoint<"/posts", {
|
|
212
|
+
method: "POST";
|
|
213
|
+
body: z.ZodObject<{
|
|
214
|
+
tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
215
|
+
name: z.ZodString;
|
|
216
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
217
|
+
id: z.ZodString;
|
|
218
|
+
name: z.ZodString;
|
|
219
|
+
slug: z.ZodString;
|
|
220
|
+
}, z.core.$strip>]>>>>;
|
|
221
|
+
slug: z.ZodOptional<z.ZodString>;
|
|
222
|
+
publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
223
|
+
createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
224
|
+
updatedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
225
|
+
title: z.ZodString;
|
|
226
|
+
content: z.ZodString;
|
|
227
|
+
excerpt: z.ZodString;
|
|
228
|
+
image: z.ZodOptional<z.ZodString>;
|
|
229
|
+
published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
230
|
+
}, z.core.$strip>;
|
|
231
|
+
}, Post>;
|
|
232
|
+
readonly updatePost: better_call.StrictEndpoint<"/posts/:id", {
|
|
233
|
+
method: "PUT";
|
|
234
|
+
body: z.ZodObject<{
|
|
235
|
+
publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
236
|
+
createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
237
|
+
updatedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
238
|
+
title: z.ZodString;
|
|
239
|
+
content: z.ZodString;
|
|
240
|
+
excerpt: z.ZodString;
|
|
241
|
+
image: z.ZodOptional<z.ZodString>;
|
|
242
|
+
published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
243
|
+
slug: z.ZodString;
|
|
244
|
+
tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
245
|
+
name: z.ZodString;
|
|
246
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
247
|
+
id: z.ZodString;
|
|
248
|
+
name: z.ZodString;
|
|
249
|
+
slug: z.ZodString;
|
|
250
|
+
}, z.core.$strip>]>>>>;
|
|
251
|
+
id: z.ZodString;
|
|
252
|
+
}, z.core.$strip>;
|
|
253
|
+
}, Post>;
|
|
254
|
+
readonly deletePost: better_call.StrictEndpoint<"/posts/:id", {
|
|
255
|
+
method: "DELETE";
|
|
256
|
+
}, {
|
|
257
|
+
success: boolean;
|
|
258
|
+
}>;
|
|
259
|
+
readonly getNextPreviousPosts: better_call.StrictEndpoint<"/posts/next-previous", {
|
|
260
|
+
method: "GET";
|
|
261
|
+
query: z.ZodObject<{
|
|
262
|
+
date: z.ZodCoercedDate<unknown>;
|
|
263
|
+
}, z.core.$strip>;
|
|
264
|
+
}, {
|
|
265
|
+
previous: {
|
|
266
|
+
tags: Tag[];
|
|
267
|
+
id: string;
|
|
268
|
+
authorId?: string;
|
|
269
|
+
defaultLocale?: string;
|
|
270
|
+
slug: string;
|
|
271
|
+
title: string;
|
|
272
|
+
content: string;
|
|
273
|
+
excerpt: string;
|
|
274
|
+
image?: string;
|
|
275
|
+
published: boolean;
|
|
276
|
+
status?: "DRAFT" | "PUBLISHED";
|
|
277
|
+
publishedAt?: Date;
|
|
278
|
+
createdAt: Date;
|
|
279
|
+
updatedAt: Date;
|
|
280
|
+
} | null;
|
|
281
|
+
next: {
|
|
282
|
+
tags: Tag[];
|
|
283
|
+
id: string;
|
|
284
|
+
authorId?: string;
|
|
285
|
+
defaultLocale?: string;
|
|
286
|
+
slug: string;
|
|
287
|
+
title: string;
|
|
288
|
+
content: string;
|
|
289
|
+
excerpt: string;
|
|
290
|
+
image?: string;
|
|
291
|
+
published: boolean;
|
|
292
|
+
status?: "DRAFT" | "PUBLISHED";
|
|
293
|
+
publishedAt?: Date;
|
|
294
|
+
createdAt: Date;
|
|
295
|
+
updatedAt: Date;
|
|
296
|
+
} | null;
|
|
297
|
+
}>;
|
|
298
|
+
readonly listTags: better_call.StrictEndpoint<"/tags", {
|
|
299
|
+
method: "GET";
|
|
300
|
+
}, Tag[]>;
|
|
301
|
+
}, {
|
|
302
|
+
getAllPosts: (params?: Parameters<typeof getAllPosts>[1]) => Promise<PostListResult>;
|
|
303
|
+
getPostBySlug: (slug: string) => Promise<(Post & {
|
|
304
|
+
tags: Tag[];
|
|
305
|
+
}) | null>;
|
|
306
|
+
getAllTags: () => Promise<Tag[]>;
|
|
307
|
+
prefetchForRoute: BlogPrefetchForRoute;
|
|
308
|
+
}>;
|
|
309
|
+
type BlogApiRouter = ReturnType<ReturnType<typeof blogBackendPlugin>["routes"]>;
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Internal query key constants for the blog plugin.
|
|
313
|
+
* Shared between query-keys.ts (HTTP path) and prefetchForRoute (DB path)
|
|
314
|
+
* to prevent key drift between SSR loaders and SSG prefetching.
|
|
315
|
+
*/
|
|
316
|
+
interface PostsListDiscriminator {
|
|
317
|
+
query: string | undefined;
|
|
318
|
+
limit: number;
|
|
319
|
+
published: boolean;
|
|
320
|
+
tagSlug: string | undefined;
|
|
321
|
+
}
|
|
322
|
+
/** Full query key builders — use these with queryClient.setQueryData() */
|
|
323
|
+
declare const BLOG_QUERY_KEYS: {
|
|
324
|
+
postsList: (params: {
|
|
325
|
+
published: boolean;
|
|
326
|
+
limit?: number;
|
|
327
|
+
tagSlug?: string;
|
|
328
|
+
}) => readonly ["posts", "list", PostsListDiscriminator];
|
|
329
|
+
postDetail: (slug: string) => readonly ["posts", "detail", string];
|
|
330
|
+
tagsList: () => readonly ["tags", "list", "tags"];
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
interface PostsListParams {
|
|
334
|
+
query?: string;
|
|
335
|
+
limit?: number;
|
|
336
|
+
published?: boolean;
|
|
337
|
+
tagSlug?: string;
|
|
338
|
+
}
|
|
339
|
+
declare function createBlogQueryKeys(client: ReturnType<typeof createApiClient<BlogApiRouter>>, headers?: HeadersInit): {
|
|
340
|
+
posts: {
|
|
341
|
+
_def: readonly ["posts"];
|
|
342
|
+
} & {
|
|
343
|
+
list: ((params?: PostsListParams | undefined) => Omit<Omit<{
|
|
344
|
+
queryKey: readonly ["posts", "list", PostsListDiscriminator];
|
|
345
|
+
queryFn: _tanstack_react_query.QueryFunction<unknown, readonly ["posts", "list", PostsListDiscriminator]>;
|
|
346
|
+
}, "queryFn"> & {
|
|
347
|
+
_def: readonly ["posts", "list"];
|
|
348
|
+
}, "_def">) & {
|
|
349
|
+
_def: readonly ["posts", "list"];
|
|
350
|
+
};
|
|
351
|
+
detail: ((slug: string) => Omit<{
|
|
352
|
+
queryKey: readonly ["posts", "detail", string];
|
|
353
|
+
queryFn: _tanstack_react_query.QueryFunction<SerializedPost | null, readonly ["posts", "detail", string]>;
|
|
354
|
+
} & {
|
|
355
|
+
_def: readonly ["posts", "detail"];
|
|
356
|
+
}, "_def">) & {
|
|
357
|
+
_def: readonly ["posts", "detail"];
|
|
358
|
+
};
|
|
359
|
+
nextPrevious: ((date: string | Date) => Omit<{
|
|
360
|
+
queryKey: readonly ["posts", "nextPrevious", string, string | Date];
|
|
361
|
+
queryFn: _tanstack_react_query.QueryFunction<{
|
|
362
|
+
previous: SerializedPost | null;
|
|
363
|
+
next: SerializedPost | null;
|
|
364
|
+
}, readonly ["posts", "nextPrevious", string, string | Date]>;
|
|
365
|
+
} & {
|
|
366
|
+
_def: readonly ["posts", "nextPrevious"];
|
|
367
|
+
}, "_def">) & {
|
|
368
|
+
_def: readonly ["posts", "nextPrevious"];
|
|
369
|
+
};
|
|
370
|
+
recent: ((params?: {
|
|
371
|
+
limit?: number;
|
|
372
|
+
excludeSlug?: string;
|
|
373
|
+
} | undefined) => Omit<{
|
|
374
|
+
queryKey: readonly ["posts", "recent", string, {
|
|
375
|
+
limit?: number;
|
|
376
|
+
excludeSlug?: string;
|
|
377
|
+
} | undefined];
|
|
378
|
+
queryFn: _tanstack_react_query.QueryFunction<SerializedPost[], readonly ["posts", "recent", string, {
|
|
379
|
+
limit?: number;
|
|
380
|
+
excludeSlug?: string;
|
|
381
|
+
} | undefined]>;
|
|
382
|
+
} & {
|
|
383
|
+
_def: readonly ["posts", "recent"];
|
|
384
|
+
}, "_def">) & {
|
|
385
|
+
_def: readonly ["posts", "recent"];
|
|
386
|
+
};
|
|
387
|
+
};
|
|
388
|
+
drafts: {
|
|
389
|
+
_def: readonly ["drafts"];
|
|
390
|
+
} & {
|
|
391
|
+
list: ((params?: PostsListParams | undefined) => Omit<Omit<{
|
|
392
|
+
queryKey: readonly ["drafts", "list", {
|
|
393
|
+
limit?: number | undefined;
|
|
394
|
+
}];
|
|
395
|
+
queryFn: _tanstack_react_query.QueryFunction<unknown, readonly ["drafts", "list", {
|
|
396
|
+
limit?: number | undefined;
|
|
397
|
+
}]>;
|
|
398
|
+
}, "queryFn"> & {
|
|
399
|
+
_def: readonly ["drafts", "list"];
|
|
400
|
+
}, "_def">) & {
|
|
401
|
+
_def: readonly ["drafts", "list"];
|
|
402
|
+
};
|
|
403
|
+
};
|
|
404
|
+
tags: {
|
|
405
|
+
_def: readonly ["tags"];
|
|
406
|
+
} & {
|
|
407
|
+
list: (() => Omit<{
|
|
408
|
+
queryKey: readonly ["tags", "list", string];
|
|
409
|
+
queryFn: _tanstack_react_query.QueryFunction<SerializedTag[], readonly ["tags", "list", string]>;
|
|
410
|
+
} & {
|
|
411
|
+
_def: readonly ["tags", "list"];
|
|
412
|
+
}, "_def">) & {
|
|
413
|
+
_def: readonly ["tags", "list"];
|
|
414
|
+
};
|
|
415
|
+
};
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
export { BLOG_QUERY_KEYS as B, NextPreviousPostsQuerySchema as N, getPostBySlug as a, getAllTags as b, createBlogQueryKeys as d, PostListQuerySchema as f, getAllPosts as g, blogBackendPlugin as j };
|
|
419
|
+
export type { PostListParams as P, PostListResult as c, BlogRouteKey as e, BlogApiContext as h, BlogBackendHooks as i, BlogApiRouter as k };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btst/stack",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "A composable, plugin-based library for building full-stack applications.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -458,7 +458,7 @@
|
|
|
458
458
|
},
|
|
459
459
|
"peerDependencies": {
|
|
460
460
|
"@ai-sdk/react": ">=2.0.0",
|
|
461
|
-
"@btst/yar": ">=1.
|
|
461
|
+
"@btst/yar": ">=1.2.0",
|
|
462
462
|
"@hookform/resolvers": ">=5.0.0",
|
|
463
463
|
"@radix-ui/react-dialog": ">=1.1.0",
|
|
464
464
|
"@radix-ui/react-label": ">=2.1.0",
|
|
@@ -494,7 +494,7 @@
|
|
|
494
494
|
"devDependencies": {
|
|
495
495
|
"@ai-sdk/react": "^2.0.94",
|
|
496
496
|
"@btst/adapter-memory": "2.0.3",
|
|
497
|
-
"@btst/yar": "1.
|
|
497
|
+
"@btst/yar": "1.2.0",
|
|
498
498
|
"@types/react": "^19.0.0",
|
|
499
499
|
"@types/slug": "^5.0.9",
|
|
500
500
|
"@workspace/ui": "workspace:*",
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { stack } from "../api";
|
|
3
|
+
import { defineBackendPlugin } from "../plugins/api";
|
|
4
|
+
import { createDbPlugin } from "@btst/db";
|
|
5
|
+
import { createMemoryAdapter } from "@btst/adapter-memory";
|
|
6
|
+
import type { Adapter, DatabaseDefinition } from "@btst/db";
|
|
7
|
+
import { blogBackendPlugin } from "../plugins/blog/api";
|
|
8
|
+
import { kanbanBackendPlugin } from "../plugins/kanban/api";
|
|
9
|
+
|
|
10
|
+
const testAdapter = (db: DatabaseDefinition): Adapter =>
|
|
11
|
+
createMemoryAdapter(db)({});
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A minimal plugin with no `api` factory, to verify backward compatibility.
|
|
15
|
+
*/
|
|
16
|
+
const noApiPlugin = defineBackendPlugin({
|
|
17
|
+
name: "no-api",
|
|
18
|
+
dbPlugin: createDbPlugin("no-api", {}),
|
|
19
|
+
routes: () => ({}),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("stack.api surface", () => {
|
|
23
|
+
it("exposes adapter on the returned backend", () => {
|
|
24
|
+
const backend = stack({
|
|
25
|
+
basePath: "/api",
|
|
26
|
+
plugins: { blog: blogBackendPlugin() },
|
|
27
|
+
adapter: testAdapter,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
expect(backend.adapter).toBeDefined();
|
|
31
|
+
expect(typeof backend.adapter.findMany).toBe("function");
|
|
32
|
+
expect(typeof backend.adapter.findOne).toBe("function");
|
|
33
|
+
expect(typeof backend.adapter.create).toBe("function");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("exposes typed api namespace for plugins with api factory", () => {
|
|
37
|
+
const backend = stack({
|
|
38
|
+
basePath: "/api",
|
|
39
|
+
plugins: { blog: blogBackendPlugin() },
|
|
40
|
+
adapter: testAdapter,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(backend.api).toBeDefined();
|
|
44
|
+
expect(backend.api.blog).toBeDefined();
|
|
45
|
+
expect(typeof backend.api.blog.getAllPosts).toBe("function");
|
|
46
|
+
expect(typeof backend.api.blog.getPostBySlug).toBe("function");
|
|
47
|
+
expect(typeof backend.api.blog.getAllTags).toBe("function");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("exposes kanban api namespace", () => {
|
|
51
|
+
const backend = stack({
|
|
52
|
+
basePath: "/api",
|
|
53
|
+
plugins: { kanban: kanbanBackendPlugin() },
|
|
54
|
+
adapter: testAdapter,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(backend.api.kanban).toBeDefined();
|
|
58
|
+
expect(typeof backend.api.kanban.getAllBoards).toBe("function");
|
|
59
|
+
expect(typeof backend.api.kanban.getBoardById).toBe("function");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("plugins without api factory are not present in api", () => {
|
|
63
|
+
const backend = stack({
|
|
64
|
+
basePath: "/api",
|
|
65
|
+
plugins: { noApi: noApiPlugin },
|
|
66
|
+
adapter: testAdapter,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
expect((backend.api as any).noApi).toBeUndefined();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("api functions are bound to the shared adapter and return real data", async () => {
|
|
73
|
+
const backend = stack({
|
|
74
|
+
basePath: "/api",
|
|
75
|
+
plugins: { blog: blogBackendPlugin() },
|
|
76
|
+
adapter: testAdapter,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Seed data via adapter directly
|
|
80
|
+
await backend.adapter.create({
|
|
81
|
+
model: "post",
|
|
82
|
+
data: {
|
|
83
|
+
title: "Hello World",
|
|
84
|
+
slug: "hello-world",
|
|
85
|
+
content: "Content",
|
|
86
|
+
excerpt: "",
|
|
87
|
+
published: true,
|
|
88
|
+
tags: [],
|
|
89
|
+
createdAt: new Date(),
|
|
90
|
+
updatedAt: new Date(),
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Retrieve via stack.api
|
|
95
|
+
const posts = await backend.api.blog.getAllPosts();
|
|
96
|
+
expect(posts.items).toHaveLength(1);
|
|
97
|
+
expect(posts.items[0]!.slug).toBe("hello-world");
|
|
98
|
+
|
|
99
|
+
// Verify same adapter - data is shared
|
|
100
|
+
const bySlug = await backend.api.blog.getPostBySlug("hello-world");
|
|
101
|
+
expect(bySlug).not.toBeNull();
|
|
102
|
+
expect(bySlug!.title).toBe("Hello World");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("combines multiple plugins in a single stack call", () => {
|
|
106
|
+
const backend = stack({
|
|
107
|
+
basePath: "/api",
|
|
108
|
+
plugins: {
|
|
109
|
+
blog: blogBackendPlugin(),
|
|
110
|
+
kanban: kanbanBackendPlugin(),
|
|
111
|
+
},
|
|
112
|
+
adapter: testAdapter,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
expect(typeof backend.api.blog.getAllPosts).toBe("function");
|
|
116
|
+
expect(typeof backend.api.kanban.getAllBoards).toBe("function");
|
|
117
|
+
});
|
|
118
|
+
});
|
package/src/api/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
BackendLibConfig,
|
|
4
4
|
BackendLib,
|
|
5
5
|
PrefixedPluginRoutes,
|
|
6
|
+
PluginApis,
|
|
6
7
|
StackContext,
|
|
7
8
|
} from "../types";
|
|
8
9
|
import { defineDb } from "@btst/db";
|
|
@@ -33,7 +34,9 @@ export function stack<
|
|
|
33
34
|
TPlugins extends Record<string, any>,
|
|
34
35
|
TRoutes extends
|
|
35
36
|
PrefixedPluginRoutes<TPlugins> = PrefixedPluginRoutes<TPlugins>,
|
|
36
|
-
>(
|
|
37
|
+
>(
|
|
38
|
+
config: BackendLibConfig<TPlugins>,
|
|
39
|
+
): BackendLib<TRoutes, PluginApis<TPlugins>> {
|
|
37
40
|
const { plugins, adapter, dbSchema, basePath } = config;
|
|
38
41
|
|
|
39
42
|
// Collect all routes from all plugins with type-safe prefixed keys
|
|
@@ -67,6 +70,14 @@ export function stack<
|
|
|
67
70
|
}
|
|
68
71
|
}
|
|
69
72
|
|
|
73
|
+
// Build the typed api surface by calling each plugin's api factory
|
|
74
|
+
const pluginApis = {} as PluginApis<TPlugins>;
|
|
75
|
+
for (const [pluginKey, plugin] of Object.entries(plugins)) {
|
|
76
|
+
if (plugin.api) {
|
|
77
|
+
(pluginApis as any)[pluginKey] = plugin.api(adapterInstance);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
70
81
|
// Create the composed router
|
|
71
82
|
const router = createRouter(allRoutes, {
|
|
72
83
|
basePath: basePath,
|
|
@@ -76,6 +87,8 @@ export function stack<
|
|
|
76
87
|
handler: router.handler,
|
|
77
88
|
router,
|
|
78
89
|
dbSchema: betterDbSchema,
|
|
90
|
+
adapter: adapterInstance,
|
|
91
|
+
api: pluginApis,
|
|
79
92
|
};
|
|
80
93
|
}
|
|
81
94
|
|
|
@@ -83,5 +96,6 @@ export type {
|
|
|
83
96
|
BackendPlugin,
|
|
84
97
|
BackendLibConfig,
|
|
85
98
|
BackendLib,
|
|
99
|
+
PluginApis,
|
|
86
100
|
StackContext,
|
|
87
101
|
} from "../types";
|