@btst/stack 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/dist/api/index.cjs +9 -1
  2. package/dist/api/index.d.cts +4 -4
  3. package/dist/api/index.d.mts +4 -4
  4. package/dist/api/index.d.ts +4 -4
  5. package/dist/api/index.mjs +9 -1
  6. package/dist/client/index.d.cts +2 -2
  7. package/dist/client/index.d.mts +2 -2
  8. package/dist/client/index.d.ts +2 -2
  9. package/dist/index.d.cts +1 -1
  10. package/dist/index.d.mts +1 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/packages/stack/src/plugins/ai-chat/api/getters.cjs +42 -0
  13. package/dist/packages/stack/src/plugins/ai-chat/api/getters.mjs +39 -0
  14. package/dist/packages/stack/src/plugins/ai-chat/api/plugin.cjs +5 -0
  15. package/dist/packages/stack/src/plugins/ai-chat/api/plugin.mjs +5 -0
  16. package/dist/packages/stack/src/plugins/blog/api/getters.cjs +131 -0
  17. package/dist/packages/stack/src/plugins/blog/api/getters.mjs +127 -0
  18. package/dist/packages/stack/src/plugins/blog/api/plugin.cjs +9 -107
  19. package/dist/packages/stack/src/plugins/blog/api/plugin.mjs +9 -107
  20. package/dist/packages/stack/src/plugins/blog/client/plugin.cjs +1 -1
  21. package/dist/packages/stack/src/plugins/blog/client/plugin.mjs +1 -1
  22. package/dist/packages/stack/src/plugins/cms/api/getters.cjs +146 -0
  23. package/dist/packages/stack/src/plugins/cms/api/getters.mjs +138 -0
  24. package/dist/packages/stack/src/plugins/cms/api/plugin.cjs +560 -622
  25. package/dist/packages/stack/src/plugins/cms/api/plugin.mjs +559 -621
  26. package/dist/packages/stack/src/plugins/cms/client/components/pages/content-editor-page.internal.cjs +1 -1
  27. package/dist/packages/stack/src/plugins/cms/client/components/pages/content-editor-page.internal.mjs +1 -1
  28. package/dist/packages/stack/src/plugins/cms/client/hooks/cms-hooks.cjs +6 -3
  29. package/dist/packages/stack/src/plugins/cms/client/hooks/cms-hooks.mjs +6 -3
  30. package/dist/packages/stack/src/plugins/form-builder/api/getters.cjs +111 -0
  31. package/dist/packages/stack/src/plugins/form-builder/api/getters.mjs +104 -0
  32. package/dist/packages/stack/src/plugins/form-builder/api/plugin.cjs +16 -88
  33. package/dist/packages/stack/src/plugins/form-builder/api/plugin.mjs +12 -84
  34. package/dist/packages/stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.cjs +1 -1
  35. package/dist/packages/stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.mjs +1 -1
  36. package/dist/packages/stack/src/plugins/kanban/api/getters.cjs +84 -0
  37. package/dist/packages/stack/src/plugins/kanban/api/getters.mjs +81 -0
  38. package/dist/packages/stack/src/plugins/kanban/api/plugin.cjs +9 -123
  39. package/dist/packages/stack/src/plugins/kanban/api/plugin.mjs +9 -123
  40. package/dist/packages/stack/src/plugins/kanban/client/plugin.cjs +1 -1
  41. package/dist/packages/stack/src/plugins/kanban/client/plugin.mjs +1 -1
  42. package/dist/plugins/ai-chat/api/index.cjs +3 -0
  43. package/dist/plugins/ai-chat/api/index.d.cts +27 -4
  44. package/dist/plugins/ai-chat/api/index.d.mts +27 -4
  45. package/dist/plugins/ai-chat/api/index.d.ts +27 -4
  46. package/dist/plugins/ai-chat/api/index.mjs +1 -0
  47. package/dist/plugins/ai-chat/client/hooks/index.d.cts +2 -2
  48. package/dist/plugins/ai-chat/client/hooks/index.d.mts +2 -2
  49. package/dist/plugins/ai-chat/client/hooks/index.d.ts +2 -2
  50. package/dist/plugins/ai-chat/query-keys.d.cts +9 -284
  51. package/dist/plugins/ai-chat/query-keys.d.mts +9 -284
  52. package/dist/plugins/ai-chat/query-keys.d.ts +9 -284
  53. package/dist/plugins/api/index.d.cts +4 -3
  54. package/dist/plugins/api/index.d.mts +4 -3
  55. package/dist/plugins/api/index.d.ts +4 -3
  56. package/dist/plugins/blog/api/index.cjs +4 -0
  57. package/dist/plugins/blog/api/index.d.cts +3 -2
  58. package/dist/plugins/blog/api/index.d.mts +3 -2
  59. package/dist/plugins/blog/api/index.d.ts +3 -2
  60. package/dist/plugins/blog/api/index.mjs +1 -0
  61. package/dist/plugins/blog/client/hooks/index.d.cts +4 -4
  62. package/dist/plugins/blog/client/hooks/index.d.mts +4 -4
  63. package/dist/plugins/blog/client/hooks/index.d.ts +4 -4
  64. package/dist/plugins/blog/client/index.d.cts +1 -1
  65. package/dist/plugins/blog/client/index.d.mts +1 -1
  66. package/dist/plugins/blog/client/index.d.ts +1 -1
  67. package/dist/plugins/blog/query-keys.cjs +7 -4
  68. package/dist/plugins/blog/query-keys.d.cts +81 -27
  69. package/dist/plugins/blog/query-keys.d.mts +81 -27
  70. package/dist/plugins/blog/query-keys.d.ts +81 -27
  71. package/dist/plugins/blog/query-keys.mjs +7 -4
  72. package/dist/plugins/client/index.d.cts +2 -2
  73. package/dist/plugins/client/index.d.mts +2 -2
  74. package/dist/plugins/client/index.d.ts +2 -2
  75. package/dist/plugins/cms/api/index.cjs +4 -0
  76. package/dist/plugins/cms/api/index.d.cts +61 -5
  77. package/dist/plugins/cms/api/index.d.mts +61 -5
  78. package/dist/plugins/cms/api/index.d.ts +61 -5
  79. package/dist/plugins/cms/api/index.mjs +1 -0
  80. package/dist/plugins/cms/client/hooks/index.d.cts +1 -1
  81. package/dist/plugins/cms/client/hooks/index.d.mts +1 -1
  82. package/dist/plugins/cms/client/hooks/index.d.ts +1 -1
  83. package/dist/plugins/cms/query-keys.d.cts +2 -1
  84. package/dist/plugins/cms/query-keys.d.mts +2 -1
  85. package/dist/plugins/cms/query-keys.d.ts +2 -1
  86. package/dist/plugins/form-builder/api/index.cjs +4 -0
  87. package/dist/plugins/form-builder/api/index.d.cts +77 -7
  88. package/dist/plugins/form-builder/api/index.d.mts +77 -7
  89. package/dist/plugins/form-builder/api/index.d.ts +77 -7
  90. package/dist/plugins/form-builder/api/index.mjs +1 -0
  91. package/dist/plugins/form-builder/client/components/index.d.cts +1 -1
  92. package/dist/plugins/form-builder/client/components/index.d.mts +1 -1
  93. package/dist/plugins/form-builder/client/components/index.d.ts +1 -1
  94. package/dist/plugins/form-builder/client/hooks/index.d.cts +1 -1
  95. package/dist/plugins/form-builder/client/hooks/index.d.mts +1 -1
  96. package/dist/plugins/form-builder/client/hooks/index.d.ts +1 -1
  97. package/dist/plugins/form-builder/query-keys.d.cts +2 -1
  98. package/dist/plugins/form-builder/query-keys.d.mts +2 -1
  99. package/dist/plugins/form-builder/query-keys.d.ts +2 -1
  100. package/dist/plugins/kanban/api/index.cjs +3 -0
  101. package/dist/plugins/kanban/api/index.d.cts +40 -43
  102. package/dist/plugins/kanban/api/index.d.mts +40 -43
  103. package/dist/plugins/kanban/api/index.d.ts +40 -43
  104. package/dist/plugins/kanban/api/index.mjs +1 -0
  105. package/dist/plugins/kanban/client/components/index.d.cts +1 -1
  106. package/dist/plugins/kanban/client/components/index.d.mts +1 -1
  107. package/dist/plugins/kanban/client/components/index.d.ts +1 -1
  108. package/dist/plugins/kanban/client/hooks/index.d.cts +1 -1
  109. package/dist/plugins/kanban/client/hooks/index.d.mts +1 -1
  110. package/dist/plugins/kanban/client/hooks/index.d.ts +1 -1
  111. package/dist/plugins/kanban/client/index.d.cts +1 -1
  112. package/dist/plugins/kanban/client/index.d.mts +1 -1
  113. package/dist/plugins/kanban/client/index.d.ts +1 -1
  114. package/dist/plugins/kanban/query-keys.cjs +4 -3
  115. package/dist/plugins/kanban/query-keys.d.cts +2 -1
  116. package/dist/plugins/kanban/query-keys.d.mts +2 -1
  117. package/dist/plugins/kanban/query-keys.d.ts +2 -1
  118. package/dist/plugins/kanban/query-keys.mjs +4 -3
  119. package/dist/plugins/open-api/api/index.d.cts +2 -2
  120. package/dist/plugins/open-api/api/index.d.mts +2 -2
  121. package/dist/plugins/open-api/api/index.d.ts +2 -2
  122. package/dist/plugins/route-docs/client/index.d.cts +1 -1
  123. package/dist/plugins/route-docs/client/index.d.mts +1 -1
  124. package/dist/plugins/route-docs/client/index.d.ts +1 -1
  125. package/dist/plugins/ui-builder/index.d.cts +1 -1
  126. package/dist/plugins/ui-builder/index.d.mts +1 -1
  127. package/dist/plugins/ui-builder/index.d.ts +1 -1
  128. package/dist/shared/{stack.BoA0xkJv.d.cts → stack.7n9Y_u7N.d.cts} +33 -7
  129. package/dist/shared/{stack.BoA0xkJv.d.mts → stack.7n9Y_u7N.d.mts} +33 -7
  130. package/dist/shared/{stack.BoA0xkJv.d.ts → stack.7n9Y_u7N.d.ts} +33 -7
  131. package/dist/shared/stack.BeSm90va.d.ts +289 -0
  132. package/dist/shared/{stack.DzH_wcvr.d.mts → stack.CIrIsc-A.d.cts} +2 -2
  133. package/dist/shared/{stack.DzH_wcvr.d.ts → stack.CIrIsc-A.d.mts} +2 -2
  134. package/dist/shared/{stack.DzH_wcvr.d.cts → stack.CIrIsc-A.d.ts} +2 -2
  135. package/dist/shared/stack.CMh_EdxW.d.cts +289 -0
  136. package/dist/shared/{stack.BsXokfNh.d.mts → stack.CXjzTMsb.d.cts} +1 -1
  137. package/dist/shared/{stack.BsXokfNh.d.ts → stack.CXjzTMsb.d.mts} +1 -1
  138. package/dist/shared/{stack.BsXokfNh.d.cts → stack.CXjzTMsb.d.ts} +1 -1
  139. package/dist/shared/stack.Dg09R0oB.d.mts +289 -0
  140. package/dist/shared/{stack.DKDMI-QO.d.mts → stack.QD1y_7NY.d.cts} +7 -1
  141. package/dist/shared/{stack.DKDMI-QO.d.ts → stack.QD1y_7NY.d.mts} +7 -1
  142. package/dist/shared/{stack.DKDMI-QO.d.cts → stack.QD1y_7NY.d.ts} +7 -1
  143. package/package.json +1 -1
  144. package/src/__tests__/stack-api.test.ts +118 -0
  145. package/src/api/index.ts +15 -1
  146. package/src/plugins/ai-chat/__tests__/getters.test.ts +109 -0
  147. package/src/plugins/ai-chat/api/getters.ts +71 -0
  148. package/src/plugins/ai-chat/api/index.ts +1 -0
  149. package/src/plugins/ai-chat/api/plugin.ts +8 -0
  150. package/src/plugins/api/index.ts +3 -1
  151. package/src/plugins/blog/__tests__/getters.test.ts +540 -0
  152. package/src/plugins/blog/api/getters.ts +243 -0
  153. package/src/plugins/blog/api/index.ts +7 -0
  154. package/src/plugins/blog/api/plugin.ts +13 -141
  155. package/src/plugins/blog/client/plugin.tsx +2 -1
  156. package/src/plugins/blog/query-keys.ts +16 -13
  157. package/src/plugins/cms/__tests__/getters.test.ts +206 -0
  158. package/src/plugins/cms/api/getters.ts +244 -0
  159. package/src/plugins/cms/api/index.ts +5 -0
  160. package/src/plugins/cms/api/plugin.ts +50 -154
  161. package/src/plugins/cms/client/components/pages/content-editor-page.internal.tsx +1 -1
  162. package/src/plugins/cms/client/hooks/cms-hooks.tsx +3 -0
  163. package/src/plugins/cms/types.ts +1 -1
  164. package/src/plugins/form-builder/__tests__/getters.test.ts +159 -0
  165. package/src/plugins/form-builder/api/getters.ts +203 -0
  166. package/src/plugins/form-builder/api/index.ts +1 -0
  167. package/src/plugins/form-builder/api/plugin.ts +22 -115
  168. package/src/plugins/form-builder/client/components/pages/submissions-page.internal.tsx +1 -1
  169. package/src/plugins/form-builder/types.ts +2 -2
  170. package/src/plugins/kanban/__tests__/getters.test.ts +172 -0
  171. package/src/plugins/kanban/api/getters.ts +149 -0
  172. package/src/plugins/kanban/api/index.ts +1 -0
  173. package/src/plugins/kanban/api/plugin.ts +16 -146
  174. package/src/plugins/kanban/client/plugin.tsx +2 -1
  175. package/src/plugins/kanban/query-keys.ts +8 -5
  176. package/src/types.ts +44 -5
  177. package/dist/shared/{stack.CbuN2zVV.d.cts → stack.BkYlUT_8.d.cts} +6 -6
  178. package/dist/shared/{stack.CbuN2zVV.d.mts → stack.BkYlUT_8.d.mts} +6 -6
  179. package/dist/shared/{stack.CbuN2zVV.d.ts → stack.BkYlUT_8.d.ts} +6 -6
@@ -5,7 +5,7 @@ import { z } from "zod";
5
5
  import { kanbanSchema as dbSchema } from "../db";
6
6
  import type {
7
7
  Board,
8
- BoardWithKanbanColumn,
8
+ BoardWithColumns,
9
9
  Column,
10
10
  ColumnWithTasks,
11
11
  Task,
@@ -23,6 +23,7 @@ import {
23
23
  updateColumnSchema,
24
24
  updateTaskSchema,
25
25
  } from "../schemas";
26
+ import { getAllBoards, getBoardById } from "./getters";
26
27
 
27
28
  /**
28
29
  * Context passed to kanban API hooks
@@ -84,10 +85,12 @@ export interface KanbanBackendHooks {
84
85
  ) => Promise<boolean> | boolean;
85
86
 
86
87
  /**
87
- * Called after boards are listed successfully
88
+ * Called after boards are listed successfully.
89
+ * Receives the items array (same shape as `board[]`) for consistency
90
+ * with analogous hooks in other plugins (e.g. `onPostsRead`).
88
91
  */
89
92
  onBoardsRead?: (
90
- boards: Board[],
93
+ boards: BoardWithColumns[],
91
94
  filter: z.infer<typeof BoardListQuerySchema>,
92
95
  context: KanbanApiContext,
93
96
  ) => Promise<void> | void;
@@ -261,6 +264,12 @@ export const kanbanBackendPlugin = (hooks?: KanbanBackendHooks) =>
261
264
 
262
265
  dbPlugin: dbSchema,
263
266
 
267
+ api: (adapter) => ({
268
+ getAllBoards: (params?: Parameters<typeof getAllBoards>[1]) =>
269
+ getAllBoards(adapter, params),
270
+ getBoardById: (id: string) => getBoardById(adapter, id),
271
+ }),
272
+
264
273
  routes: (adapter: Adapter) => {
265
274
  // ============ Board Endpoints ============
266
275
 
@@ -284,100 +293,10 @@ export const kanbanBackendPlugin = (hooks?: KanbanBackendHooks) =>
284
293
  }
285
294
  }
286
295
 
287
- const whereConditions = [];
288
-
289
- if (query.slug) {
290
- whereConditions.push({
291
- field: "slug",
292
- value: query.slug,
293
- operator: "eq" as const,
294
- });
295
- }
296
-
297
- if (query.ownerId) {
298
- whereConditions.push({
299
- field: "ownerId",
300
- value: query.ownerId,
301
- operator: "eq" as const,
302
- });
303
- }
304
-
305
- if (query.organizationId) {
306
- whereConditions.push({
307
- field: "organizationId",
308
- value: query.organizationId,
309
- operator: "eq" as const,
310
- });
311
- }
312
-
313
- const boards = await adapter.findMany<BoardWithKanbanColumn>({
314
- model: "kanbanBoard",
315
- limit: query.limit ?? 50,
316
- offset: query.offset ?? 0,
317
- where: whereConditions,
318
- sortBy: {
319
- field: "createdAt",
320
- direction: "desc",
321
- },
322
- join: {
323
- kanbanColumn: true,
324
- },
325
- });
326
-
327
- // Get all column IDs to fetch tasks
328
- // Note: adapter returns joined data under schema key name ("column"), not model name
329
- const columnIds: string[] = [];
330
- for (const board of boards) {
331
- if (board.column) {
332
- for (const col of board.column) {
333
- columnIds.push(col.id);
334
- }
335
- }
336
- }
337
-
338
- // Fetch tasks for each column in parallel (avoids loading all tasks from DB)
339
- const tasksByColumn = new Map<string, Task[]>();
340
- if (columnIds.length > 0) {
341
- const taskQueries = columnIds.map((columnId) =>
342
- adapter.findMany<Task>({
343
- model: "kanbanTask",
344
- where: [
345
- {
346
- field: "columnId",
347
- value: columnId,
348
- operator: "eq" as const,
349
- },
350
- ],
351
- sortBy: { field: "order", direction: "asc" },
352
- }),
353
- );
354
- const taskResults = await Promise.all(taskQueries);
355
- for (let i = 0; i < columnIds.length; i++) {
356
- const columnId = columnIds[i];
357
- const tasks = taskResults[i];
358
- if (columnId && tasks) {
359
- tasksByColumn.set(columnId, tasks);
360
- }
361
- }
362
- }
363
-
364
- // Map boards with columns and tasks
365
- const result = boards.map((board) => {
366
- const columns = (board.column || [])
367
- .sort((a, b) => a.order - b.order)
368
- .map((col) => ({
369
- ...col,
370
- tasks: tasksByColumn.get(col.id) || [],
371
- }));
372
- const { column: _, ...boardWithoutJoin } = board;
373
- return {
374
- ...boardWithoutJoin,
375
- columns,
376
- };
377
- });
296
+ const result = await getAllBoards(adapter, query);
378
297
 
379
298
  if (hooks?.onBoardsRead) {
380
- await hooks.onBoardsRead(result, query, context);
299
+ await hooks.onBoardsRead(result.items, query, context);
381
300
  }
382
301
 
383
302
  return result;
@@ -409,61 +328,12 @@ export const kanbanBackendPlugin = (hooks?: KanbanBackendHooks) =>
409
328
  }
410
329
  }
411
330
 
412
- const board = await adapter.findOne<BoardWithKanbanColumn>({
413
- model: "kanbanBoard",
414
- where: [
415
- { field: "id", value: params.id, operator: "eq" as const },
416
- ],
417
- join: {
418
- kanbanColumn: true,
419
- },
420
- });
331
+ const result = await getBoardById(adapter, params.id);
421
332
 
422
- if (!board) {
333
+ if (!result) {
423
334
  throw ctx.error(404, { message: "Board not found" });
424
335
  }
425
336
 
426
- // Fetch tasks for each column in parallel (avoids loading all tasks from DB)
427
- // Note: adapter returns joined data under schema key name ("column"), not model name
428
- const columnIds = (board.column || []).map((c) => c.id);
429
- const tasksByColumn = new Map<string, Task[]>();
430
- if (columnIds.length > 0) {
431
- const taskQueries = columnIds.map((columnId) =>
432
- adapter.findMany<Task>({
433
- model: "kanbanTask",
434
- where: [
435
- {
436
- field: "columnId",
437
- value: columnId,
438
- operator: "eq" as const,
439
- },
440
- ],
441
- sortBy: { field: "order", direction: "asc" },
442
- }),
443
- );
444
- const taskResults = await Promise.all(taskQueries);
445
- for (let i = 0; i < columnIds.length; i++) {
446
- const columnId = columnIds[i];
447
- const tasks = taskResults[i];
448
- if (columnId && tasks) {
449
- tasksByColumn.set(columnId, tasks);
450
- }
451
- }
452
- }
453
-
454
- const columns = (board.column || [])
455
- .sort((a, b) => a.order - b.order)
456
- .map((col) => ({
457
- ...col,
458
- tasks: tasksByColumn.get(col.id) || [],
459
- }));
460
-
461
- const { column: _, ...boardWithoutJoin } = board;
462
- const result = {
463
- ...boardWithoutJoin,
464
- columns,
465
- };
466
-
467
337
  if (hooks?.onBoardRead) {
468
338
  await hooks.onBoardRead(result, context);
469
339
  }
@@ -437,7 +437,8 @@ export const kanbanClientPlugin = (config: KanbanClientConfig) =>
437
437
  method: "GET",
438
438
  query: { limit: 100 },
439
439
  });
440
- boards = ((res as { data?: unknown }).data ??
440
+ // /boards returns BoardListResult { items, total, limit, offset }
441
+ boards = ((res.data as any)?.items ??
441
442
  []) as SerializedBoardWithColumns[];
442
443
  } catch {
443
444
  // Ignore errors for sitemap
@@ -91,8 +91,10 @@ function createBoardsQueries(
91
91
  throw toError(errorResponse.error);
92
92
  }
93
93
 
94
- return ((response as { data?: unknown }).data ??
95
- []) as unknown as SerializedBoardWithColumns[];
94
+ const envelope = (response as { data?: unknown }).data as
95
+ | { items?: SerializedBoardWithColumns[] }
96
+ | undefined;
97
+ return envelope?.items ?? ([] as SerializedBoardWithColumns[]);
96
98
  } catch (error) {
97
99
  throw error;
98
100
  }
@@ -142,9 +144,10 @@ function createBoardsQueries(
142
144
  throw toError(errorResponse.error);
143
145
  }
144
146
 
145
- const boards = ((response as { data?: unknown }).data ??
146
- []) as unknown as SerializedBoardWithColumns[];
147
- return boards[0] ?? null;
147
+ const envelope = (response as { data?: unknown }).data as
148
+ | { items?: SerializedBoardWithColumns[] }
149
+ | undefined;
150
+ return envelope?.items?.[0] ?? null;
148
151
  } catch (error) {
149
152
  throw error;
150
153
  }
package/src/types.ts CHANGED
@@ -8,7 +8,7 @@ import type { Endpoint, Router } from "better-call";
8
8
  */
9
9
  export interface StackContext {
10
10
  /** All registered backend plugins */
11
- plugins: Record<string, BackendPlugin<any>>;
11
+ plugins: Record<string, BackendPlugin<any, any>>;
12
12
  /** The API base path (e.g., "/api/data") */
13
13
  basePath: string;
14
14
  /** The database adapter */
@@ -40,9 +40,13 @@ export interface ClientStackContext<
40
40
  * You can optionally provide a base schema via the dbSchema config option.
41
41
  *
42
42
  * @template TRoutes - The exact shape of routes this plugin provides (preserves keys and endpoint types)
43
+ * @template TApi - The shape of the server-side API surface exposed via `stack().api`.
44
+ * Defaults to `never` so that plugins without an `api` factory are excluded from the
45
+ * `stack().api` namespace entirely, preventing accidental access of `undefined` at runtime.
43
46
  */
44
47
  export interface BackendPlugin<
45
48
  TRoutes extends Record<string, Endpoint> = Record<string, Endpoint>,
49
+ TApi extends Record<string, (...args: any[]) => any> = never,
46
50
  > {
47
51
  name: string;
48
52
 
@@ -56,6 +60,15 @@ export interface BackendPlugin<
56
60
  */
57
61
  routes: (adapter: Adapter, context?: StackContext) => TRoutes;
58
62
  dbPlugin: DbPlugin;
63
+
64
+ /**
65
+ * Optional factory that returns server-side getter functions bound to the adapter.
66
+ * The returned object is merged into `stack().api.<pluginName>.*` for direct
67
+ * server-side or SSG data access without going through HTTP.
68
+ *
69
+ * @param adapter - The adapter instance shared with `routes`
70
+ */
71
+ api?: (adapter: Adapter) => TApi;
59
72
  }
60
73
 
61
74
  /**
@@ -87,13 +100,30 @@ export interface ClientPlugin<
87
100
  sitemap?: () => Promise<Sitemap> | Sitemap;
88
101
  }
89
102
 
103
+ /**
104
+ * Utility type that maps each plugin key to the return type of its `api` factory.
105
+ * Plugin keys whose `TApi` resolves to `never` (i.e. plugins with no `api` factory)
106
+ * are excluded from the resulting type via key remapping, preventing TypeScript from
107
+ * suggesting callable functions on what is actually `undefined` at runtime.
108
+ */
109
+ export type PluginApis<
110
+ TPlugins extends Record<string, BackendPlugin<any, any>>,
111
+ > = {
112
+ [K in keyof TPlugins as _ApiOf<TPlugins[K]> extends never
113
+ ? never
114
+ : K]: _ApiOf<TPlugins[K]>;
115
+ };
116
+
117
+ /** @internal Extract the TApi parameter from a BackendPlugin type. */
118
+ type _ApiOf<T> = T extends BackendPlugin<any, infer TApi> ? TApi : never;
119
+
90
120
  /**
91
121
  * Configuration for creating the backend library
92
122
  */
93
123
  export interface BackendLibConfig<
94
- TPlugins extends Record<string, BackendPlugin<any>> = Record<
124
+ TPlugins extends Record<string, BackendPlugin<any, any>> = Record<
95
125
  string,
96
- BackendPlugin<any>
126
+ BackendPlugin<any, any>
97
127
  >,
98
128
  > {
99
129
  basePath: string;
@@ -150,11 +180,12 @@ export type PluginRoutes<
150
180
  * Example: { messages: { list: Endpoint } } => { messages_list: Endpoint }
151
181
  */
152
182
  export type PrefixedPluginRoutes<
153
- TPlugins extends Record<string, BackendPlugin<any>>,
183
+ TPlugins extends Record<string, BackendPlugin<any, any>>,
154
184
  > = UnionToIntersection<
155
185
  {
156
186
  [PluginKey in keyof TPlugins]: TPlugins[PluginKey] extends BackendPlugin<
157
- infer TRoutes
187
+ infer TRoutes,
188
+ any
158
189
  >
159
190
  ? {
160
191
  [RouteKey in keyof TRoutes as `${PluginKey & string}_${RouteKey & string}`]: TRoutes[RouteKey];
@@ -172,10 +203,18 @@ export type PrefixedPluginRoutes<
172
203
  */
173
204
  export interface BackendLib<
174
205
  TRoutes extends Record<string, Endpoint> = Record<string, Endpoint>,
206
+ TApis extends Record<
207
+ string,
208
+ Record<string, (...args: any[]) => any>
209
+ > = Record<string, Record<string, (...args: any[]) => any>>,
175
210
  > {
176
211
  handler: (request: Request) => Promise<Response>; // API route handler
177
212
  router: Router; // Better-call router
178
213
  dbSchema: DatabaseDefinition; // Better-db schema
214
+ /** The database adapter shared across all plugins */
215
+ adapter: Adapter;
216
+ /** Fully-typed server-side getter functions, namespaced per plugin */
217
+ api: TApis;
179
218
  }
180
219
 
181
220
  /**
@@ -35,15 +35,12 @@ interface SerializedTag extends Omit<Tag, "createdAt" | "updatedAt"> {
35
35
  }
36
36
 
37
37
  declare const createPostSchema: z.ZodObject<{
38
+ title: z.ZodString;
38
39
  slug: z.ZodOptional<z.ZodString>;
39
- publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
40
+ published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
40
41
  createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
41
42
  updatedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
42
- title: z.ZodString;
43
- content: z.ZodString;
44
- excerpt: z.ZodString;
45
- image: z.ZodOptional<z.ZodString>;
46
- published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
43
+ publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
47
44
  tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
48
45
  name: z.ZodString;
49
46
  }, z.core.$strip>, z.ZodObject<{
@@ -51,6 +48,9 @@ declare const createPostSchema: z.ZodObject<{
51
48
  name: z.ZodString;
52
49
  slug: z.ZodString;
53
50
  }, z.core.$strip>]>>>>;
51
+ content: z.ZodString;
52
+ excerpt: z.ZodString;
53
+ image: z.ZodOptional<z.ZodString>;
54
54
  }, z.core.$strip>;
55
55
  declare const updatePostSchema: z.ZodObject<{
56
56
  publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
@@ -35,15 +35,12 @@ interface SerializedTag extends Omit<Tag, "createdAt" | "updatedAt"> {
35
35
  }
36
36
 
37
37
  declare const createPostSchema: z.ZodObject<{
38
+ title: z.ZodString;
38
39
  slug: z.ZodOptional<z.ZodString>;
39
- publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
40
+ published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
40
41
  createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
41
42
  updatedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
42
- title: z.ZodString;
43
- content: z.ZodString;
44
- excerpt: z.ZodString;
45
- image: z.ZodOptional<z.ZodString>;
46
- published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
43
+ publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
47
44
  tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
48
45
  name: z.ZodString;
49
46
  }, z.core.$strip>, z.ZodObject<{
@@ -51,6 +48,9 @@ declare const createPostSchema: z.ZodObject<{
51
48
  name: z.ZodString;
52
49
  slug: z.ZodString;
53
50
  }, z.core.$strip>]>>>>;
51
+ content: z.ZodString;
52
+ excerpt: z.ZodString;
53
+ image: z.ZodOptional<z.ZodString>;
54
54
  }, z.core.$strip>;
55
55
  declare const updatePostSchema: z.ZodObject<{
56
56
  publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
@@ -35,15 +35,12 @@ interface SerializedTag extends Omit<Tag, "createdAt" | "updatedAt"> {
35
35
  }
36
36
 
37
37
  declare const createPostSchema: z.ZodObject<{
38
+ title: z.ZodString;
38
39
  slug: z.ZodOptional<z.ZodString>;
39
- publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
40
+ published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
40
41
  createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
41
42
  updatedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
42
- title: z.ZodString;
43
- content: z.ZodString;
44
- excerpt: z.ZodString;
45
- image: z.ZodOptional<z.ZodString>;
46
- published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
43
+ publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
47
44
  tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
48
45
  name: z.ZodString;
49
46
  }, z.core.$strip>, z.ZodObject<{
@@ -51,6 +48,9 @@ declare const createPostSchema: z.ZodObject<{
51
48
  name: z.ZodString;
52
49
  slug: z.ZodString;
53
50
  }, z.core.$strip>]>>>>;
51
+ content: z.ZodString;
52
+ excerpt: z.ZodString;
53
+ image: z.ZodOptional<z.ZodString>;
54
54
  }, z.core.$strip>;
55
55
  declare const updatePostSchema: z.ZodObject<{
56
56
  publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;