@btst/stack 1.0.1 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -709
- package/dist/api/index.cjs +2 -1
- package/dist/api/index.d.cts +4 -3
- package/dist/api/index.d.mts +4 -3
- package/dist/api/index.d.ts +4 -3
- package/dist/api/index.mjs +1 -1
- package/dist/client/components/compose.cjs +68 -0
- package/dist/client/components/compose.mjs +65 -0
- package/dist/client/components/error-boundary.cjs +24 -0
- package/dist/client/components/error-boundary.mjs +22 -0
- package/dist/client/components/index.cjs +10 -0
- package/dist/client/components/index.d.cts +52 -0
- package/dist/client/components/index.d.mts +52 -0
- package/dist/client/components/index.d.ts +52 -0
- package/dist/client/components/index.mjs +2 -0
- package/dist/client/index.cjs +24 -5
- package/dist/client/index.d.cts +125 -8
- package/dist/client/index.d.mts +125 -8
- package/dist/client/index.d.ts +125 -8
- package/dist/client/index.mjs +21 -4
- package/dist/client/meta-utils.cjs +162 -0
- package/dist/client/meta-utils.mjs +160 -0
- package/dist/client/path-utils.cjs +15 -0
- package/dist/client/path-utils.mjs +13 -0
- package/dist/client/sitemap-utils.cjs +14 -0
- package/dist/client/sitemap-utils.mjs +12 -0
- package/dist/context/index.cjs +6 -63
- package/dist/context/index.d.cts +21 -24
- package/dist/context/index.d.mts +21 -24
- package/dist/context/index.d.ts +21 -24
- package/dist/context/index.mjs +1 -61
- package/dist/context/provider.cjs +51 -0
- package/dist/context/provider.mjs +46 -0
- package/dist/index.cjs +2 -3
- package/dist/index.d.cts +3 -2
- package/dist/index.d.mts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.mjs +1 -2
- package/dist/plugins/api/index.cjs +13 -0
- package/dist/plugins/api/index.d.cts +40 -0
- package/dist/plugins/api/index.d.mts +40 -0
- package/dist/plugins/api/index.d.ts +40 -0
- package/dist/plugins/api/index.mjs +8 -0
- package/dist/plugins/blog/api/index.cjs +11 -0
- package/dist/plugins/blog/api/index.d.cts +7 -0
- package/dist/plugins/blog/api/index.d.mts +7 -0
- package/dist/plugins/blog/api/index.d.ts +7 -0
- package/dist/plugins/blog/api/index.mjs +2 -0
- package/dist/plugins/blog/api/plugin.cjs +569 -0
- package/dist/plugins/blog/api/plugin.mjs +565 -0
- package/dist/plugins/blog/client/components/forms/image-field.cjs +133 -0
- package/dist/plugins/blog/client/components/forms/image-field.mjs +131 -0
- package/dist/plugins/blog/client/components/forms/markdown-editor-styles.css +30 -0
- package/dist/plugins/blog/client/components/forms/markdown-editor.cjs +106 -0
- package/dist/plugins/blog/client/components/forms/markdown-editor.mjs +104 -0
- package/dist/plugins/blog/client/components/forms/post-forms.cjs +401 -0
- package/dist/plugins/blog/client/components/forms/post-forms.mjs +398 -0
- package/dist/plugins/blog/client/components/forms/tags-multiselect.cjs +71 -0
- package/dist/plugins/blog/client/components/forms/tags-multiselect.mjs +65 -0
- package/dist/plugins/blog/client/components/index.cjs +17 -0
- package/dist/plugins/blog/client/components/index.d.cts +22 -0
- package/dist/plugins/blog/client/components/index.d.mts +22 -0
- package/dist/plugins/blog/client/components/index.d.ts +22 -0
- package/dist/plugins/blog/client/components/index.mjs +12 -0
- package/dist/plugins/blog/client/components/loading/form-page-skeleton.cjs +62 -0
- package/dist/plugins/blog/client/components/loading/form-page-skeleton.mjs +60 -0
- package/dist/plugins/blog/client/components/loading/index.cjs +20 -0
- package/dist/plugins/blog/client/components/loading/index.mjs +16 -0
- package/dist/plugins/blog/client/components/loading/list-page-skeleton.cjs +26 -0
- package/dist/plugins/blog/client/components/loading/list-page-skeleton.mjs +24 -0
- package/dist/plugins/blog/client/components/loading/page-header-skeleton.cjs +13 -0
- package/dist/plugins/blog/client/components/loading/page-header-skeleton.mjs +11 -0
- package/dist/plugins/blog/client/components/loading/post-card-skeleton.cjs +22 -0
- package/dist/plugins/blog/client/components/loading/post-card-skeleton.mjs +20 -0
- package/dist/plugins/blog/client/components/loading/post-page-skeleton.cjs +56 -0
- package/dist/plugins/blog/client/components/loading/post-page-skeleton.mjs +54 -0
- package/dist/plugins/blog/client/components/pages/404-page.cjs +19 -0
- package/dist/plugins/blog/client/components/pages/404-page.mjs +17 -0
- package/dist/plugins/blog/client/components/pages/edit-post-page.cjs +41 -0
- package/dist/plugins/blog/client/components/pages/edit-post-page.internal.cjs +57 -0
- package/dist/plugins/blog/client/components/pages/edit-post-page.internal.mjs +55 -0
- package/dist/plugins/blog/client/components/pages/edit-post-page.mjs +39 -0
- package/dist/plugins/blog/client/components/pages/home-page.cjs +41 -0
- package/dist/plugins/blog/client/components/pages/home-page.internal.cjs +61 -0
- package/dist/plugins/blog/client/components/pages/home-page.internal.mjs +59 -0
- package/dist/plugins/blog/client/components/pages/home-page.mjs +39 -0
- package/dist/plugins/blog/client/components/pages/new-post-page.cjs +37 -0
- package/dist/plugins/blog/client/components/pages/new-post-page.internal.cjs +53 -0
- package/dist/plugins/blog/client/components/pages/new-post-page.internal.mjs +51 -0
- package/dist/plugins/blog/client/components/pages/new-post-page.mjs +35 -0
- package/dist/plugins/blog/client/components/pages/post-page.cjs +39 -0
- package/dist/plugins/blog/client/components/pages/post-page.internal.cjs +101 -0
- package/dist/plugins/blog/client/components/pages/post-page.internal.mjs +99 -0
- package/dist/plugins/blog/client/components/pages/post-page.mjs +37 -0
- package/dist/plugins/blog/client/components/pages/tag-page.cjs +39 -0
- package/dist/plugins/blog/client/components/pages/tag-page.internal.cjs +61 -0
- package/dist/plugins/blog/client/components/pages/tag-page.internal.mjs +59 -0
- package/dist/plugins/blog/client/components/pages/tag-page.mjs +37 -0
- package/dist/plugins/blog/client/components/shared/better-blog-attribution.cjs +24 -0
- package/dist/plugins/blog/client/components/shared/better-blog-attribution.mjs +22 -0
- package/dist/plugins/blog/client/components/shared/default-error.cjs +18 -0
- package/dist/plugins/blog/client/components/shared/default-error.mjs +16 -0
- package/dist/plugins/blog/client/components/shared/defaults.cjs +13 -0
- package/dist/plugins/blog/client/components/shared/defaults.mjs +10 -0
- package/dist/plugins/blog/client/components/shared/empty-list.cjs +21 -0
- package/dist/plugins/blog/client/components/shared/empty-list.mjs +19 -0
- package/dist/plugins/blog/client/components/shared/error-placeholder.cjs +24 -0
- package/dist/plugins/blog/client/components/shared/error-placeholder.mjs +22 -0
- package/dist/plugins/blog/client/components/shared/highlight-text.cjs +53 -0
- package/dist/plugins/blog/client/components/shared/highlight-text.mjs +51 -0
- package/dist/plugins/blog/client/components/shared/markdown-content-styles.css +328 -0
- package/dist/plugins/blog/client/components/shared/markdown-content.cjs +324 -0
- package/dist/plugins/blog/client/components/shared/markdown-content.mjs +315 -0
- package/dist/plugins/blog/client/components/shared/on-this-page.cjs +161 -0
- package/dist/plugins/blog/client/components/shared/on-this-page.mjs +158 -0
- package/dist/plugins/blog/client/components/shared/page-header.cjs +40 -0
- package/dist/plugins/blog/client/components/shared/page-header.mjs +38 -0
- package/dist/plugins/blog/client/components/shared/page-layout.cjs +24 -0
- package/dist/plugins/blog/client/components/shared/page-layout.mjs +22 -0
- package/dist/plugins/blog/client/components/shared/page-wrapper.cjs +23 -0
- package/dist/plugins/blog/client/components/shared/page-wrapper.mjs +21 -0
- package/dist/plugins/blog/client/components/shared/post-card.cjs +279 -0
- package/dist/plugins/blog/client/components/shared/post-card.mjs +277 -0
- package/dist/plugins/blog/client/components/shared/post-navigation.cjs +74 -0
- package/dist/plugins/blog/client/components/shared/post-navigation.mjs +72 -0
- package/dist/plugins/blog/client/components/shared/posts-list.cjs +48 -0
- package/dist/plugins/blog/client/components/shared/posts-list.mjs +46 -0
- package/dist/plugins/blog/client/components/shared/recent-posts-carousel.cjs +59 -0
- package/dist/plugins/blog/client/components/shared/recent-posts-carousel.mjs +57 -0
- package/dist/plugins/blog/client/components/shared/search-input.cjs +136 -0
- package/dist/plugins/blog/client/components/shared/search-input.mjs +117 -0
- package/dist/plugins/blog/client/components/shared/search-modal.cjs +135 -0
- package/dist/plugins/blog/client/components/shared/search-modal.mjs +116 -0
- package/dist/plugins/blog/client/components/shared/tags-list.cjs +22 -0
- package/dist/plugins/blog/client/components/shared/tags-list.mjs +20 -0
- package/dist/plugins/blog/client/components/shared/use-route-lifecycle.cjs +50 -0
- package/dist/plugins/blog/client/components/shared/use-route-lifecycle.mjs +48 -0
- package/dist/plugins/blog/client/hooks/blog-hooks.cjs +380 -0
- package/dist/plugins/blog/client/hooks/blog-hooks.mjs +368 -0
- package/dist/plugins/blog/client/hooks/index.cjs +17 -0
- package/dist/plugins/blog/client/hooks/index.d.cts +150 -0
- package/dist/plugins/blog/client/hooks/index.d.mts +150 -0
- package/dist/plugins/blog/client/hooks/index.d.ts +150 -0
- package/dist/plugins/blog/client/hooks/index.mjs +1 -0
- package/dist/plugins/blog/client/hooks/use-debounce.cjs +16 -0
- package/dist/plugins/blog/client/hooks/use-debounce.mjs +14 -0
- package/dist/plugins/blog/client/index.cjs +7 -0
- package/dist/plugins/blog/client/index.d.cts +414 -0
- package/dist/plugins/blog/client/index.d.mts +414 -0
- package/dist/plugins/blog/client/index.d.ts +414 -0
- package/dist/plugins/blog/client/index.mjs +1 -0
- package/dist/plugins/blog/client/localization/blog-card.cjs +7 -0
- package/dist/plugins/blog/client/localization/blog-card.mjs +5 -0
- package/dist/plugins/blog/client/localization/blog-common.cjs +10 -0
- package/dist/plugins/blog/client/localization/blog-common.mjs +8 -0
- package/dist/plugins/blog/client/localization/blog-forms.cjs +40 -0
- package/dist/plugins/blog/client/localization/blog-forms.mjs +38 -0
- package/dist/plugins/blog/client/localization/blog-list.cjs +18 -0
- package/dist/plugins/blog/client/localization/blog-list.mjs +16 -0
- package/dist/plugins/blog/client/localization/blog-post.cjs +13 -0
- package/dist/plugins/blog/client/localization/blog-post.mjs +11 -0
- package/dist/plugins/blog/client/localization/index.cjs +17 -0
- package/dist/plugins/blog/client/localization/index.mjs +15 -0
- package/dist/plugins/blog/client/plugin.cjs +462 -0
- package/dist/plugins/blog/client/plugin.mjs +460 -0
- package/dist/plugins/blog/client.css +3 -0
- package/dist/plugins/blog/db.cjs +90 -0
- package/dist/plugins/blog/db.mjs +88 -0
- package/dist/plugins/blog/query-keys.cjs +181 -0
- package/dist/plugins/blog/query-keys.d.cts +530 -0
- package/dist/plugins/blog/query-keys.d.mts +530 -0
- package/dist/plugins/blog/query-keys.d.ts +530 -0
- package/dist/plugins/blog/query-keys.mjs +179 -0
- package/dist/plugins/blog/schemas.cjs +39 -0
- package/dist/plugins/blog/schemas.mjs +35 -0
- package/dist/plugins/blog/style.css +22 -0
- package/dist/plugins/blog/utils.cjs +97 -0
- package/dist/plugins/blog/utils.mjs +87 -0
- package/dist/plugins/client/index.cjs +15 -0
- package/dist/plugins/client/index.d.cts +57 -0
- package/dist/plugins/client/index.d.mts +57 -0
- package/dist/plugins/client/index.d.ts +57 -0
- package/dist/plugins/client/index.mjs +9 -0
- package/dist/{shared/stack.3OUyGp_E.mjs → plugins/utils.mjs} +1 -1
- package/dist/shared/{stack.DORw_1ps.d.cts → stack.ByOugz9d.d.cts} +17 -1
- package/dist/shared/{stack.DORw_1ps.d.mts → stack.ByOugz9d.d.mts} +17 -1
- package/dist/shared/{stack.DORw_1ps.d.ts → stack.ByOugz9d.d.ts} +17 -1
- package/dist/shared/stack.CoPoHVfV.d.cts +76 -0
- package/dist/shared/stack.CoPoHVfV.d.mts +76 -0
- package/dist/shared/stack.CoPoHVfV.d.ts +76 -0
- package/package.json +102 -14
- package/src/__tests__/plugins.test.tsx +539 -0
- package/src/__tests__/sitemap.test.ts +60 -0
- package/src/api/index.ts +75 -0
- package/src/client/components/compose.tsx +116 -0
- package/src/client/components/error-boundary.tsx +30 -0
- package/src/client/components/index.tsx +2 -0
- package/src/client/index.ts +109 -0
- package/src/client/meta-utils.ts +228 -0
- package/src/client/path-utils.ts +38 -0
- package/src/client/sitemap-utils.ts +46 -0
- package/src/context/index.ts +1 -0
- package/src/context/provider.tsx +157 -0
- package/src/index.ts +1 -0
- package/src/plugins/api/index.ts +50 -0
- package/src/plugins/blog/api/index.ts +2 -0
- package/src/plugins/blog/api/plugin.ts +759 -0
- package/src/plugins/blog/client/components/forms/image-field.tsx +165 -0
- package/src/plugins/blog/client/components/forms/markdown-editor-styles.css +30 -0
- package/src/plugins/blog/client/components/forms/markdown-editor.tsx +136 -0
- package/src/plugins/blog/client/components/forms/post-forms.tsx +531 -0
- package/src/plugins/blog/client/components/forms/tags-multiselect.tsx +79 -0
- package/src/plugins/blog/client/components/index.tsx +11 -0
- package/src/plugins/blog/client/components/loading/form-page-skeleton.tsx +75 -0
- package/src/plugins/blog/client/components/loading/index.tsx +27 -0
- package/src/plugins/blog/client/components/loading/list-page-skeleton.tsx +38 -0
- package/src/plugins/blog/client/components/loading/page-header-skeleton.tsx +10 -0
- package/src/plugins/blog/client/components/loading/post-card-skeleton.tsx +30 -0
- package/src/plugins/blog/client/components/loading/post-page-skeleton.tsx +75 -0
- package/src/plugins/blog/client/components/pages/404-page.tsx +23 -0
- package/src/plugins/blog/client/components/pages/edit-post-page.internal.tsx +60 -0
- package/src/plugins/blog/client/components/pages/edit-post-page.tsx +40 -0
- package/src/plugins/blog/client/components/pages/home-page.internal.tsx +71 -0
- package/src/plugins/blog/client/components/pages/home-page.tsx +42 -0
- package/src/plugins/blog/client/components/pages/new-post-page.internal.tsx +59 -0
- package/src/plugins/blog/client/components/pages/new-post-page.tsx +36 -0
- package/src/plugins/blog/client/components/pages/post-page.internal.tsx +142 -0
- package/src/plugins/blog/client/components/pages/post-page.tsx +38 -0
- package/src/plugins/blog/client/components/pages/tag-page.internal.tsx +74 -0
- package/src/plugins/blog/client/components/pages/tag-page.tsx +38 -0
- package/src/plugins/blog/client/components/shared/better-blog-attribution.tsx +19 -0
- package/src/plugins/blog/client/components/shared/default-error.tsx +20 -0
- package/src/plugins/blog/client/components/shared/defaults.tsx +9 -0
- package/src/plugins/blog/client/components/shared/empty-list.tsx +25 -0
- package/src/plugins/blog/client/components/shared/error-placeholder.tsx +20 -0
- package/src/plugins/blog/client/components/shared/highlight-text.tsx +80 -0
- package/src/plugins/blog/client/components/shared/markdown-content-styles.css +328 -0
- package/src/plugins/blog/client/components/shared/markdown-content.tsx +448 -0
- package/src/plugins/blog/client/components/shared/on-this-page.tsx +234 -0
- package/src/plugins/blog/client/components/shared/page-header.tsx +35 -0
- package/src/plugins/blog/client/components/shared/page-layout.tsx +23 -0
- package/src/plugins/blog/client/components/shared/page-wrapper.tsx +32 -0
- package/src/plugins/blog/client/components/shared/post-card.tsx +308 -0
- package/src/plugins/blog/client/components/shared/post-navigation.tsx +98 -0
- package/src/plugins/blog/client/components/shared/posts-list.tsx +67 -0
- package/src/plugins/blog/client/components/shared/recent-posts-carousel.tsx +79 -0
- package/src/plugins/blog/client/components/shared/search-input.tsx +146 -0
- package/src/plugins/blog/client/components/shared/search-modal.tsx +162 -0
- package/src/plugins/blog/client/components/shared/tags-list.tsx +34 -0
- package/src/plugins/blog/client/components/shared/use-route-lifecycle.tsx +68 -0
- package/src/plugins/blog/client/hooks/blog-hooks.tsx +623 -0
- package/src/plugins/blog/client/hooks/index.tsx +1 -0
- package/src/plugins/blog/client/hooks/use-debounce.ts +43 -0
- package/src/plugins/blog/client/index.ts +9 -0
- package/src/plugins/blog/client/localization/blog-card.ts +3 -0
- package/src/plugins/blog/client/localization/blog-common.ts +7 -0
- package/src/plugins/blog/client/localization/blog-forms.ts +45 -0
- package/src/plugins/blog/client/localization/blog-list.ts +14 -0
- package/src/plugins/blog/client/localization/blog-post.ts +9 -0
- package/src/plugins/blog/client/localization/index.ts +15 -0
- package/src/plugins/blog/client/overrides.ts +123 -0
- package/src/plugins/blog/client/plugin.tsx +672 -0
- package/src/plugins/blog/client.css +3 -0
- package/src/plugins/blog/db.ts +90 -0
- package/src/plugins/blog/query-keys.ts +267 -0
- package/src/plugins/blog/schemas.ts +39 -0
- package/src/plugins/blog/style.css +22 -0
- package/src/plugins/blog/types.ts +37 -0
- package/src/plugins/blog/utils.ts +144 -0
- package/src/plugins/client/index.ts +53 -0
- package/src/plugins/index.ts +0 -0
- package/src/plugins/utils.ts +35 -0
- package/src/types.ts +209 -0
- package/dist/plugins/index.cjs +0 -15
- package/dist/plugins/index.d.cts +0 -64
- package/dist/plugins/index.d.mts +0 -64
- package/dist/plugins/index.d.ts +0 -64
- package/dist/plugins/index.mjs +0 -11
- package/dist/shared/stack.DrUAVfIH.d.cts +0 -17
- package/dist/shared/stack.DrUAVfIH.d.mts +0 -17
- package/dist/shared/stack.DrUAVfIH.d.ts +0 -17
- /package/dist/{shared/stack.CktCg4PJ.cjs → plugins/utils.cjs} +0 -0
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { betterStack } from "../api";
|
|
3
|
+
import { createStackClient } from "../client";
|
|
4
|
+
import { defineBackendPlugin } from "../plugins/api";
|
|
5
|
+
import { defineClientPlugin } from "../plugins/client";
|
|
6
|
+
import type { BackendPlugin, ClientPlugin } from "../types";
|
|
7
|
+
import type { BetterAuthDBSchema, DatabaseDefinition, Adapter } from "@btst/db";
|
|
8
|
+
import { createDbPlugin } from "@btst/db";
|
|
9
|
+
import { createMemoryAdapter } from "@btst/adapter-memory";
|
|
10
|
+
import { createEndpoint as endpoint } from "better-call";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { createRoute } from "@btst/yar";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Adapter wrapper for testing
|
|
16
|
+
* Wraps createMemoryAdapter to match the expected signature
|
|
17
|
+
*/
|
|
18
|
+
const testAdapter = (db: DatabaseDefinition): Adapter => {
|
|
19
|
+
return createMemoryAdapter(db)({});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Test schema for messages plugin
|
|
24
|
+
* Using BetterAuthDBSchema format
|
|
25
|
+
*/
|
|
26
|
+
const messagesSchema: BetterAuthDBSchema = {
|
|
27
|
+
messages: {
|
|
28
|
+
modelName: "Message",
|
|
29
|
+
fields: {
|
|
30
|
+
id: {
|
|
31
|
+
type: "number",
|
|
32
|
+
unique: true,
|
|
33
|
+
required: true,
|
|
34
|
+
},
|
|
35
|
+
content: {
|
|
36
|
+
type: "string",
|
|
37
|
+
required: true,
|
|
38
|
+
},
|
|
39
|
+
userId: {
|
|
40
|
+
type: "string",
|
|
41
|
+
required: true,
|
|
42
|
+
},
|
|
43
|
+
createdAt: {
|
|
44
|
+
type: "number",
|
|
45
|
+
required: true,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Test backend plugin - Messages
|
|
53
|
+
* Demonstrates how to build a 3rd party plugin using Better Stack
|
|
54
|
+
* Now using defineBackendPlugin for full type inference - no casts needed!
|
|
55
|
+
*/
|
|
56
|
+
const messagesBackendPlugin = defineBackendPlugin({
|
|
57
|
+
name: "messages",
|
|
58
|
+
dbPlugin: createDbPlugin("messages", messagesSchema),
|
|
59
|
+
routes: (adapter) => ({
|
|
60
|
+
list: endpoint(
|
|
61
|
+
"/messages",
|
|
62
|
+
{
|
|
63
|
+
method: "GET",
|
|
64
|
+
query: z.object({
|
|
65
|
+
userId: z.string().optional(),
|
|
66
|
+
}),
|
|
67
|
+
},
|
|
68
|
+
async ({ query }) => {
|
|
69
|
+
const messages = await adapter.findMany({
|
|
70
|
+
model: "messages",
|
|
71
|
+
where: query.userId
|
|
72
|
+
? [{ field: "userId", value: query.userId, operator: "eq" }]
|
|
73
|
+
: undefined,
|
|
74
|
+
});
|
|
75
|
+
return {
|
|
76
|
+
status: 200,
|
|
77
|
+
body: messages,
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
),
|
|
81
|
+
create: endpoint(
|
|
82
|
+
"/messages",
|
|
83
|
+
{
|
|
84
|
+
method: "POST",
|
|
85
|
+
body: z.object({
|
|
86
|
+
content: z.string().min(1),
|
|
87
|
+
userId: z.string().min(1),
|
|
88
|
+
}),
|
|
89
|
+
},
|
|
90
|
+
async ({ body }) => {
|
|
91
|
+
const message = await adapter.create({
|
|
92
|
+
model: "messages",
|
|
93
|
+
data: { ...body, id: Date.now(), createdAt: Date.now() },
|
|
94
|
+
});
|
|
95
|
+
return {
|
|
96
|
+
status: 201,
|
|
97
|
+
body: message,
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
),
|
|
101
|
+
delete: endpoint(
|
|
102
|
+
"/messages/:id",
|
|
103
|
+
{
|
|
104
|
+
method: "DELETE",
|
|
105
|
+
params: z.object({
|
|
106
|
+
id: z.coerce.number(),
|
|
107
|
+
}),
|
|
108
|
+
},
|
|
109
|
+
async ({ params }) => {
|
|
110
|
+
await adapter.delete({
|
|
111
|
+
model: "messages",
|
|
112
|
+
where: [{ field: "id", value: params.id, operator: "eq" }],
|
|
113
|
+
});
|
|
114
|
+
return {
|
|
115
|
+
status: 204,
|
|
116
|
+
body: null,
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
),
|
|
120
|
+
}),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Test components for client plugin
|
|
125
|
+
*/
|
|
126
|
+
const MessagesListComponent = () => {
|
|
127
|
+
return <div>Messages List</div>;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const MessageDetailComponent = () => {
|
|
131
|
+
return <div>Message Detail</div>;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Test loaders for client plugin
|
|
136
|
+
*/
|
|
137
|
+
const messagesListLoader = async () => {
|
|
138
|
+
return { messages: [{ id: 1, content: "Test message" }] };
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const messageDetailLoader = async ({ params }: { params: { id: string } }) => {
|
|
142
|
+
return { message: { id: params.id, content: "Detail message" } };
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Test client plugin - Messages
|
|
147
|
+
* Now using defineClientPlugin for full type inference - no casts needed!
|
|
148
|
+
* Using Yar's createRoute() to create proper route handlers.
|
|
149
|
+
*/
|
|
150
|
+
const messagesClientPlugin = defineClientPlugin({
|
|
151
|
+
name: "messages",
|
|
152
|
+
routes: () => ({
|
|
153
|
+
messagesList: createRoute("/messages", () => ({
|
|
154
|
+
PageComponent: MessagesListComponent,
|
|
155
|
+
loader: messagesListLoader,
|
|
156
|
+
})),
|
|
157
|
+
messageDetail: createRoute("/messages/:id", ({ params }) => ({
|
|
158
|
+
PageComponent: MessageDetailComponent,
|
|
159
|
+
loader: () => messageDetailLoader({ params }),
|
|
160
|
+
})),
|
|
161
|
+
}),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe("3rd Party Plugin Support", () => {
|
|
165
|
+
describe("Backend Plugin", () => {
|
|
166
|
+
it("should create backend with custom plugin", () => {
|
|
167
|
+
const backend = betterStack({
|
|
168
|
+
basePath: "/api",
|
|
169
|
+
plugins: {
|
|
170
|
+
messages: messagesBackendPlugin,
|
|
171
|
+
},
|
|
172
|
+
adapter: testAdapter,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
expect(backend).toBeDefined();
|
|
176
|
+
expect(backend.handler).toBeDefined();
|
|
177
|
+
expect(backend.router).toBeDefined();
|
|
178
|
+
expect(backend.dbSchema).toBeDefined();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("should create backend with multiple custom plugins", () => {
|
|
182
|
+
// Create a second plugin
|
|
183
|
+
const notificationsSchema: BetterAuthDBSchema = {
|
|
184
|
+
notifications: {
|
|
185
|
+
modelName: "Notification",
|
|
186
|
+
fields: {
|
|
187
|
+
id: {
|
|
188
|
+
type: "number",
|
|
189
|
+
unique: true,
|
|
190
|
+
required: true,
|
|
191
|
+
},
|
|
192
|
+
message: {
|
|
193
|
+
type: "string",
|
|
194
|
+
required: true,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const notificationsPlugin = defineBackendPlugin({
|
|
201
|
+
name: "notifications",
|
|
202
|
+
dbPlugin: createDbPlugin("notifications", notificationsSchema),
|
|
203
|
+
routes: (adapter) => ({
|
|
204
|
+
list: endpoint(
|
|
205
|
+
"/notifications",
|
|
206
|
+
{
|
|
207
|
+
method: "GET",
|
|
208
|
+
},
|
|
209
|
+
async () => {
|
|
210
|
+
const notifications = await adapter.findMany({
|
|
211
|
+
model: "notifications",
|
|
212
|
+
});
|
|
213
|
+
return {
|
|
214
|
+
status: 200,
|
|
215
|
+
body: notifications,
|
|
216
|
+
};
|
|
217
|
+
},
|
|
218
|
+
),
|
|
219
|
+
}),
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const backend = betterStack({
|
|
223
|
+
basePath: "/api",
|
|
224
|
+
plugins: {
|
|
225
|
+
messages: messagesBackendPlugin,
|
|
226
|
+
notifications: notificationsPlugin,
|
|
227
|
+
},
|
|
228
|
+
adapter: testAdapter,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
expect(backend).toBeDefined();
|
|
232
|
+
expect(backend.router).toBeDefined();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("should generate routes for custom plugin", () => {
|
|
236
|
+
const backend = betterStack({
|
|
237
|
+
basePath: "/api",
|
|
238
|
+
plugins: {
|
|
239
|
+
messages: messagesBackendPlugin,
|
|
240
|
+
},
|
|
241
|
+
adapter: testAdapter,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Check that routes are properly registered
|
|
245
|
+
// The router should have routes prefixed with plugin name
|
|
246
|
+
expect(backend.router).toBeDefined();
|
|
247
|
+
expect(typeof backend.router.handler).toBe("function");
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("should include plugin schema in database", () => {
|
|
251
|
+
const backend = betterStack({
|
|
252
|
+
basePath: "/api",
|
|
253
|
+
plugins: {
|
|
254
|
+
messages: messagesBackendPlugin,
|
|
255
|
+
},
|
|
256
|
+
adapter: testAdapter,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Check that the schema includes the messages table
|
|
260
|
+
expect(backend.dbSchema).toBeDefined();
|
|
261
|
+
expect(backend.dbSchema.schema).toBeDefined();
|
|
262
|
+
expect(backend.dbSchema.schema.messages).toBeDefined();
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe("Client Plugin", () => {
|
|
267
|
+
it("should create client with custom plugin", () => {
|
|
268
|
+
const client = createStackClient({
|
|
269
|
+
plugins: {
|
|
270
|
+
messages: messagesClientPlugin,
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
expect(client).toBeDefined();
|
|
275
|
+
expect(client.router).toBeDefined();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("should have routes from custom plugin", () => {
|
|
279
|
+
const client = createStackClient({
|
|
280
|
+
plugins: {
|
|
281
|
+
messages: messagesClientPlugin,
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Verify the router is created and has the expected structure
|
|
286
|
+
expect(client.router).toBeDefined();
|
|
287
|
+
expect(typeof client.router).toBe("object");
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("should return correct route shape with Component and loader", async () => {
|
|
291
|
+
// Test the route object directly from the plugin - types are now preserved!
|
|
292
|
+
const routes = messagesClientPlugin.routes();
|
|
293
|
+
const listRoute = routes.messagesList;
|
|
294
|
+
|
|
295
|
+
expect(listRoute).toBeDefined();
|
|
296
|
+
expect(typeof listRoute).toBe("function"); // Yar routes ARE functions
|
|
297
|
+
expect(listRoute.path).toBe("/messages");
|
|
298
|
+
|
|
299
|
+
// Call the route handler to get the route data
|
|
300
|
+
const routeData = listRoute();
|
|
301
|
+
expect(routeData).toBeDefined();
|
|
302
|
+
// Handler returns PageComponent
|
|
303
|
+
expect(routeData.PageComponent).toBeDefined();
|
|
304
|
+
expect(typeof routeData.PageComponent).toBe("function");
|
|
305
|
+
expect(routeData.loader).toBeDefined();
|
|
306
|
+
expect(typeof routeData.loader).toBe("function");
|
|
307
|
+
|
|
308
|
+
// Verify PageComponent returns expected shape
|
|
309
|
+
expect(routeData.PageComponent).toBeDefined();
|
|
310
|
+
|
|
311
|
+
// Verify loader returns expected shape
|
|
312
|
+
const data = await routeData.loader?.();
|
|
313
|
+
expect(data).toBeDefined();
|
|
314
|
+
expect(data?.messages).toBeDefined();
|
|
315
|
+
expect(Array.isArray(data?.messages)).toBe(true);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it("should return correct route shape with params", async () => {
|
|
319
|
+
// Test the route object directly from the plugin - types are now preserved!
|
|
320
|
+
const routes = messagesClientPlugin.routes();
|
|
321
|
+
const detailRoute = routes.messageDetail;
|
|
322
|
+
|
|
323
|
+
expect(detailRoute).toBeDefined();
|
|
324
|
+
expect(typeof detailRoute).toBe("function"); // Yar routes ARE functions
|
|
325
|
+
expect(detailRoute.path).toBe("/messages/:id");
|
|
326
|
+
|
|
327
|
+
// Call the route handler with params to get the route data
|
|
328
|
+
const routeData = detailRoute({ params: { id: "123" } });
|
|
329
|
+
expect(routeData).toBeDefined();
|
|
330
|
+
// Handler returns PageComponent
|
|
331
|
+
expect(routeData.PageComponent).toBeDefined();
|
|
332
|
+
expect(typeof routeData.PageComponent).toBe("function");
|
|
333
|
+
expect(routeData.loader).toBeDefined();
|
|
334
|
+
expect(typeof routeData.loader).toBeDefined();
|
|
335
|
+
|
|
336
|
+
// Verify loader can be called
|
|
337
|
+
const data = await routeData.loader?.();
|
|
338
|
+
expect(data).toBeDefined();
|
|
339
|
+
expect(data?.message).toBeDefined();
|
|
340
|
+
expect(data?.message.id).toBe("123");
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it("should have all expected routes defined", () => {
|
|
344
|
+
const routes = messagesClientPlugin.routes();
|
|
345
|
+
|
|
346
|
+
// Verify all expected routes exist - types are fully inferred!
|
|
347
|
+
expect(routes.messagesList).toBeDefined();
|
|
348
|
+
expect(routes.messageDetail).toBeDefined();
|
|
349
|
+
|
|
350
|
+
// Verify route paths (Yar routes have .path property)
|
|
351
|
+
expect(routes.messagesList.path).toBe("/messages");
|
|
352
|
+
expect(routes.messageDetail.path).toBe("/messages/:id");
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it("should maintain type safety across route definitions", async () => {
|
|
356
|
+
// This test verifies that TypeScript types are preserved
|
|
357
|
+
const routes = messagesClientPlugin.routes();
|
|
358
|
+
const route = routes.messagesList;
|
|
359
|
+
|
|
360
|
+
// Route should be a function (Yar's handler)
|
|
361
|
+
expect(typeof route).toBe("function");
|
|
362
|
+
|
|
363
|
+
// Calling route handler returns route data
|
|
364
|
+
const routeData = route();
|
|
365
|
+
// Handler returns PageComponent
|
|
366
|
+
expect(routeData.PageComponent).toBeDefined();
|
|
367
|
+
|
|
368
|
+
// PageComponent should be callable
|
|
369
|
+
expect(routeData.PageComponent).toBeDefined();
|
|
370
|
+
|
|
371
|
+
// Loader should be callable and return a promise
|
|
372
|
+
const loaderResult = routeData.loader?.();
|
|
373
|
+
expect(loaderResult).toBeInstanceOf(Promise);
|
|
374
|
+
await loaderResult; // Ensure it resolves
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it("should support multiple client plugins", () => {
|
|
378
|
+
const NotificationsComponent: React.FC = () => {
|
|
379
|
+
return <div>Notifications</div>;
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
const notificationsLoader = async () => {
|
|
383
|
+
return { notifications: [{ id: 1, message: "Test notification" }] };
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
const notificationsClientPlugin = defineClientPlugin({
|
|
387
|
+
name: "notifications",
|
|
388
|
+
routes: () => ({
|
|
389
|
+
notificationsList: createRoute("/notifications", () => ({
|
|
390
|
+
PageComponent: NotificationsComponent,
|
|
391
|
+
loader: notificationsLoader,
|
|
392
|
+
})),
|
|
393
|
+
}),
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
const client = createStackClient({
|
|
397
|
+
plugins: {
|
|
398
|
+
messages: messagesClientPlugin,
|
|
399
|
+
notifications: notificationsClientPlugin,
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// Verify the router is created with both plugins
|
|
404
|
+
expect(client.router).toBeDefined();
|
|
405
|
+
|
|
406
|
+
// Verify both plugin routes are accessible from their definitions - full type safety!
|
|
407
|
+
const messagesRoutes = messagesClientPlugin.routes();
|
|
408
|
+
const notificationsRoutes = notificationsClientPlugin.routes();
|
|
409
|
+
|
|
410
|
+
expect(messagesRoutes.messagesList).toBeDefined();
|
|
411
|
+
expect(typeof messagesRoutes.messagesList).toBe("function");
|
|
412
|
+
expect(messagesRoutes.messagesList.path).toBe("/messages");
|
|
413
|
+
|
|
414
|
+
expect(notificationsRoutes.notificationsList).toBeDefined();
|
|
415
|
+
expect(typeof notificationsRoutes.notificationsList).toBe("function");
|
|
416
|
+
expect(notificationsRoutes.notificationsList.path).toBe("/notifications");
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
describe("Separate Backend and Client Plugins", () => {
|
|
421
|
+
it("should use backend and client plugins independently", () => {
|
|
422
|
+
// Backend and client plugins are completely separate
|
|
423
|
+
// This prevents SSR issues and enables better code splitting
|
|
424
|
+
const backend = betterStack({
|
|
425
|
+
basePath: "/api",
|
|
426
|
+
plugins: {
|
|
427
|
+
messages: messagesBackendPlugin,
|
|
428
|
+
},
|
|
429
|
+
adapter: testAdapter,
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
const client = createStackClient({
|
|
433
|
+
plugins: {
|
|
434
|
+
messages: messagesClientPlugin,
|
|
435
|
+
},
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
expect(backend.router).toBeDefined();
|
|
439
|
+
expect(client.router).toBeDefined();
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it("should demonstrate proper route usage pattern", async () => {
|
|
443
|
+
// This test mimics the actual usage pattern in a Next.js app
|
|
444
|
+
// Uses router.getRoute() to match paths and get route handlers
|
|
445
|
+
|
|
446
|
+
const client = createStackClient({
|
|
447
|
+
plugins: {
|
|
448
|
+
messages: messagesClientPlugin,
|
|
449
|
+
},
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// Test route resolution by path
|
|
453
|
+
const matchedRoute = client.router.getRoute("/messages");
|
|
454
|
+
|
|
455
|
+
expect(matchedRoute).toBeDefined();
|
|
456
|
+
expect(matchedRoute).not.toBeNull();
|
|
457
|
+
|
|
458
|
+
if (matchedRoute) {
|
|
459
|
+
// Route should have the expected properties from Yar
|
|
460
|
+
expect(matchedRoute.params).toBeDefined();
|
|
461
|
+
|
|
462
|
+
// Yar's getRoute() returns the route handler's result
|
|
463
|
+
// which includes PageComponent and loader
|
|
464
|
+
expect(matchedRoute.PageComponent).toBeDefined();
|
|
465
|
+
expect(typeof matchedRoute.PageComponent).toBe("function");
|
|
466
|
+
|
|
467
|
+
// Can render the component
|
|
468
|
+
if (matchedRoute.PageComponent) {
|
|
469
|
+
const rendered = <matchedRoute.PageComponent />;
|
|
470
|
+
expect(rendered).toBeDefined();
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Verify loader is present and callable
|
|
474
|
+
expect(matchedRoute.loader).toBeDefined();
|
|
475
|
+
expect(typeof matchedRoute.loader).toBe("function");
|
|
476
|
+
|
|
477
|
+
// Can call the loader and get data - type is inferred from loader
|
|
478
|
+
const data = await matchedRoute.loader?.();
|
|
479
|
+
expect(data).toBeDefined();
|
|
480
|
+
|
|
481
|
+
// TypeScript knows this is a union of all route loaders
|
|
482
|
+
// Narrow by checking which properties exist
|
|
483
|
+
if (data && "messages" in data) {
|
|
484
|
+
// This is the messagesList route
|
|
485
|
+
expect(data.messages).toBeDefined();
|
|
486
|
+
expect(Array.isArray(data.messages)).toBe(true);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it("should handle parameterized routes correctly", async () => {
|
|
492
|
+
// Test that router.getRoute() correctly extracts path parameters
|
|
493
|
+
const client = createStackClient({
|
|
494
|
+
plugins: {
|
|
495
|
+
messages: messagesClientPlugin,
|
|
496
|
+
},
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
// Match a route with params
|
|
500
|
+
const matchedRoute = client.router.getRoute("/messages/42");
|
|
501
|
+
|
|
502
|
+
expect(matchedRoute).toBeDefined();
|
|
503
|
+
expect(matchedRoute).not.toBeNull();
|
|
504
|
+
|
|
505
|
+
if (matchedRoute) {
|
|
506
|
+
// Yar should extract params from the path
|
|
507
|
+
expect(matchedRoute.params).toBeDefined();
|
|
508
|
+
expect(matchedRoute.params.id).toBe("42");
|
|
509
|
+
|
|
510
|
+
// getRoute() returns the handler result with PageComponent and loader
|
|
511
|
+
expect(matchedRoute.PageComponent).toBeDefined();
|
|
512
|
+
expect(matchedRoute.loader).toBeDefined();
|
|
513
|
+
|
|
514
|
+
// Loader should be callable - type is properly inferred from the route
|
|
515
|
+
const data = await matchedRoute.loader?.();
|
|
516
|
+
expect(data).toBeDefined();
|
|
517
|
+
|
|
518
|
+
// TypeScript knows this is a union type from both routes
|
|
519
|
+
// We can narrow by checking which properties exist
|
|
520
|
+
if (data && "message" in data) {
|
|
521
|
+
// This is the messageDetail route
|
|
522
|
+
expect(data.message).toBeDefined();
|
|
523
|
+
expect(data.message.id).toBe("42");
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
describe("Plugin Type Exports", () => {
|
|
530
|
+
it("should export all necessary types for building plugins", () => {
|
|
531
|
+
// This test verifies the types are available at compile time
|
|
532
|
+
const backendPlugin: BackendPlugin = messagesBackendPlugin;
|
|
533
|
+
const clientPlugin: ClientPlugin = messagesClientPlugin;
|
|
534
|
+
|
|
535
|
+
expect(backendPlugin.name).toBe("messages");
|
|
536
|
+
expect(clientPlugin.name).toBe("messages");
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { createStackClient } from "../client";
|
|
3
|
+
import { defineClientPlugin } from "../plugins/client";
|
|
4
|
+
import { createRoute } from "@btst/yar";
|
|
5
|
+
|
|
6
|
+
describe("Client sitemap generation", () => {
|
|
7
|
+
it("aggregates sitemaps from multiple plugins and de-duplicates by URL", async () => {
|
|
8
|
+
const pluginA = defineClientPlugin({
|
|
9
|
+
name: "a",
|
|
10
|
+
routes: () => ({
|
|
11
|
+
a: createRoute("/a", () => ({ PageComponent: () => null })),
|
|
12
|
+
}),
|
|
13
|
+
sitemap: () => [
|
|
14
|
+
{ url: "https://example.com/a", priority: 0.8 },
|
|
15
|
+
{ url: "https://example.com/b", changeFrequency: "weekly" },
|
|
16
|
+
],
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const pluginB = defineClientPlugin({
|
|
20
|
+
name: "b",
|
|
21
|
+
routes: () => ({
|
|
22
|
+
b: createRoute("/b", () => ({ PageComponent: () => null })),
|
|
23
|
+
}),
|
|
24
|
+
sitemap: async () => [
|
|
25
|
+
{ url: "https://example.com/b", priority: 0.5 }, // duplicate
|
|
26
|
+
{ url: "https://example.com/c", changeFrequency: "monthly" },
|
|
27
|
+
],
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const client = createStackClient({
|
|
31
|
+
plugins: { a: pluginA, b: pluginB },
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const entries = await client.generateSitemap();
|
|
35
|
+
const urls = entries.map((e) => e.url).sort();
|
|
36
|
+
expect(urls).toEqual([
|
|
37
|
+
"https://example.com/a",
|
|
38
|
+
"https://example.com/b",
|
|
39
|
+
"https://example.com/c",
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
// Ensure de-duplication preserves first occurrence properties
|
|
43
|
+
const bEntry = entries.find((e) => e.url === "https://example.com/b");
|
|
44
|
+
expect(bEntry?.changeFrequency).toBe("weekly");
|
|
45
|
+
expect(bEntry?.priority).toBeUndefined();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("returns empty array when no plugins provide a sitemap", async () => {
|
|
49
|
+
const noop = defineClientPlugin({
|
|
50
|
+
name: "noop",
|
|
51
|
+
routes: () => ({
|
|
52
|
+
home: createRoute("/", () => ({ PageComponent: () => null })),
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const client = createStackClient({ plugins: { noop } });
|
|
57
|
+
const entries = await client.generateSitemap();
|
|
58
|
+
expect(entries).toEqual([]);
|
|
59
|
+
});
|
|
60
|
+
});
|
package/src/api/index.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createRouter } from "better-call";
|
|
2
|
+
import type {
|
|
3
|
+
BackendLibConfig,
|
|
4
|
+
BackendLib,
|
|
5
|
+
PrefixedPluginRoutes,
|
|
6
|
+
} from "../types";
|
|
7
|
+
import { defineDb } from "@btst/db";
|
|
8
|
+
|
|
9
|
+
export { toNodeHandler } from "better-call/node";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates the backend library with plugin support
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const api = betterStack({
|
|
17
|
+
* plugins: {
|
|
18
|
+
* messages: messagesPlugin.backend
|
|
19
|
+
* },
|
|
20
|
+
* adapter: memoryAdapter
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* // Use in API route:
|
|
24
|
+
* export const GET = api.handler;
|
|
25
|
+
* export const POST = api.handler;
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @template TPlugins - The exact plugins map (inferred from config)
|
|
29
|
+
* @template TRoutes - All routes with prefixed keys like "pluginName_routeName" (computed automatically)
|
|
30
|
+
*/
|
|
31
|
+
export function betterStack<
|
|
32
|
+
TPlugins extends Record<string, any>,
|
|
33
|
+
TRoutes extends
|
|
34
|
+
PrefixedPluginRoutes<TPlugins> = PrefixedPluginRoutes<TPlugins>,
|
|
35
|
+
>(config: BackendLibConfig<TPlugins>): BackendLib<TRoutes> {
|
|
36
|
+
const { plugins, adapter, dbSchema, basePath } = config;
|
|
37
|
+
|
|
38
|
+
// Collect all routes from all plugins with type-safe prefixed keys
|
|
39
|
+
const allRoutes = {} as TRoutes;
|
|
40
|
+
|
|
41
|
+
let betterDbSchema = dbSchema ?? defineDb({});
|
|
42
|
+
|
|
43
|
+
// use all the db plugins on the betterDbSchema
|
|
44
|
+
for (const [pluginKey, plugin] of Object.entries(plugins)) {
|
|
45
|
+
betterDbSchema = betterDbSchema.use(plugin.dbPlugin);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const [pluginKey, plugin] of Object.entries(plugins)) {
|
|
49
|
+
// Pass the adapter directly to plugin routes
|
|
50
|
+
const pluginRoutes = plugin.routes(adapter(betterDbSchema));
|
|
51
|
+
|
|
52
|
+
// Prefix route keys with plugin name to avoid collisions
|
|
53
|
+
for (const [routeKey, endpoint] of Object.entries(pluginRoutes)) {
|
|
54
|
+
const compositeKey = `${pluginKey}_${routeKey}` as keyof TRoutes;
|
|
55
|
+
(allRoutes as any)[compositeKey] = endpoint;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Create the composed router
|
|
60
|
+
const router = createRouter(allRoutes, {
|
|
61
|
+
basePath: basePath,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
handler: router.handler,
|
|
66
|
+
router,
|
|
67
|
+
dbSchema: betterDbSchema,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type {
|
|
72
|
+
BackendPlugin,
|
|
73
|
+
BackendLibConfig,
|
|
74
|
+
BackendLib,
|
|
75
|
+
} from "../types";
|