@btst/stack 2.11.8 → 2.12.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.
Files changed (80) hide show
  1. package/dist/packages/stack/src/plugins/cms/api/plugin.cjs +14 -11
  2. package/dist/packages/stack/src/plugins/cms/api/plugin.mjs +14 -11
  3. package/dist/packages/stack/src/plugins/cms/client/components/forms/relation-field.cjs +87 -15
  4. package/dist/packages/stack/src/plugins/cms/client/components/forms/relation-field.mjs +87 -15
  5. package/dist/packages/stack/src/plugins/cms/schemas.cjs +11 -6
  6. package/dist/packages/stack/src/plugins/cms/schemas.mjs +10 -6
  7. package/dist/packages/stack/src/plugins/comments/api/query-key-defs.cjs +2 -1
  8. package/dist/packages/stack/src/plugins/comments/api/query-key-defs.mjs +2 -1
  9. package/dist/packages/stack/src/plugins/comments/client/components/comment-thread.cjs +6 -2
  10. package/dist/packages/stack/src/plugins/comments/client/components/comment-thread.mjs +6 -2
  11. package/dist/packages/stack/src/plugins/comments/client/hooks/use-comments.cjs +5 -3
  12. package/dist/packages/stack/src/plugins/comments/client/hooks/use-comments.mjs +5 -3
  13. package/dist/packages/ui/src/components/auto-form/fields/array.cjs +181 -4
  14. package/dist/packages/ui/src/components/auto-form/fields/array.mjs +184 -7
  15. package/dist/packages/ui/src/components/auto-form/index.cjs +16 -1
  16. package/dist/packages/ui/src/components/auto-form/index.mjs +17 -2
  17. package/dist/plugins/cms/api/index.cjs +2 -0
  18. package/dist/plugins/cms/api/index.d.cts +3 -2
  19. package/dist/plugins/cms/api/index.d.mts +3 -2
  20. package/dist/plugins/cms/api/index.d.ts +3 -2
  21. package/dist/plugins/cms/api/index.mjs +1 -0
  22. package/dist/plugins/cms/client/hooks/index.d.cts +1 -1
  23. package/dist/plugins/cms/client/hooks/index.d.mts +1 -1
  24. package/dist/plugins/cms/client/hooks/index.d.ts +1 -1
  25. package/dist/plugins/cms/query-keys.d.cts +398 -7
  26. package/dist/plugins/cms/query-keys.d.mts +398 -7
  27. package/dist/plugins/cms/query-keys.d.ts +398 -7
  28. package/dist/plugins/comments/api/index.d.cts +4 -4
  29. package/dist/plugins/comments/api/index.d.mts +4 -4
  30. package/dist/plugins/comments/api/index.d.ts +4 -4
  31. package/dist/plugins/comments/client/components/index.d.cts +11 -0
  32. package/dist/plugins/comments/client/components/index.d.mts +11 -0
  33. package/dist/plugins/comments/client/components/index.d.ts +11 -0
  34. package/dist/plugins/comments/client/hooks/index.d.cts +17 -1
  35. package/dist/plugins/comments/client/hooks/index.d.mts +17 -1
  36. package/dist/plugins/comments/client/hooks/index.d.ts +17 -1
  37. package/dist/plugins/comments/client/index.d.cts +9 -0
  38. package/dist/plugins/comments/client/index.d.mts +9 -0
  39. package/dist/plugins/comments/client/index.d.ts +9 -0
  40. package/dist/plugins/comments/query-keys.cjs +1 -0
  41. package/dist/plugins/comments/query-keys.d.cts +3 -2
  42. package/dist/plugins/comments/query-keys.d.mts +3 -2
  43. package/dist/plugins/comments/query-keys.d.ts +3 -2
  44. package/dist/plugins/comments/query-keys.mjs +1 -0
  45. package/dist/plugins/kanban/api/index.d.cts +1 -1
  46. package/dist/plugins/kanban/api/index.d.mts +1 -1
  47. package/dist/plugins/kanban/api/index.d.ts +1 -1
  48. package/dist/plugins/kanban/query-keys.d.cts +1 -1
  49. package/dist/plugins/kanban/query-keys.d.mts +1 -1
  50. package/dist/plugins/kanban/query-keys.d.ts +1 -1
  51. package/dist/plugins/ui-builder/index.d.cts +1 -1
  52. package/dist/plugins/ui-builder/index.d.mts +1 -1
  53. package/dist/plugins/ui-builder/index.d.ts +1 -1
  54. package/dist/shared/{stack.CJE9sAjV.d.ts → stack.Bq3kiUaM.d.ts} +1 -1
  55. package/dist/shared/{stack.C-b3Sn8j.d.cts → stack.C7KNNiJm.d.cts} +3 -1
  56. package/dist/shared/{stack.C-b3Sn8j.d.mts → stack.C7KNNiJm.d.mts} +3 -1
  57. package/dist/shared/{stack.C-b3Sn8j.d.ts → stack.C7KNNiJm.d.ts} +3 -1
  58. package/dist/shared/{stack.Dcz6636A.d.mts → stack.CCncwK6g.d.cts} +1 -1
  59. package/dist/shared/{stack.CmHRdhl8.d.cts → stack.CO1g47IS.d.mts} +1 -1
  60. package/dist/shared/{stack.B8QD11QU.d.cts → stack.DFtpFS00.d.cts} +10 -0
  61. package/dist/shared/{stack.B8QD11QU.d.mts → stack.DFtpFS00.d.mts} +10 -0
  62. package/dist/shared/{stack.B8QD11QU.d.ts → stack.DFtpFS00.d.ts} +10 -0
  63. package/package.json +1 -1
  64. package/src/plugins/cms/__tests__/schema-roundtrip.test.ts +203 -0
  65. package/src/plugins/cms/api/index.ts +1 -0
  66. package/src/plugins/cms/api/plugin.ts +33 -16
  67. package/src/plugins/cms/client/components/forms/relation-field.tsx +121 -36
  68. package/src/plugins/cms/schemas.ts +26 -6
  69. package/src/plugins/cms/types.ts +10 -0
  70. package/src/plugins/comments/api/query-key-defs.ts +5 -1
  71. package/src/plugins/comments/client/components/comment-thread.tsx +15 -0
  72. package/src/plugins/comments/client/hooks/use-comments.tsx +26 -3
  73. package/src/plugins/comments/client/overrides.ts +10 -0
  74. package/src/plugins/comments/query-keys.ts +2 -0
  75. package/dist/shared/stack.DbnCLdNF.d.mts +0 -399
  76. package/dist/shared/stack.fXlShnhv.d.ts +0 -399
  77. package/dist/shared/stack.r6HohbyX.d.cts +0 -399
  78. package/dist/shared/{stack.B6S3cgwN.d.cts → stack.CuI_-z-_.d.cts} +16 -16
  79. package/dist/shared/{stack.Bzfx-_lq.d.mts → stack.D2RVkQD8.d.mts} +16 -16
  80. package/dist/shared/{stack.j5SFLC1d.d.ts → stack.DXrOtuek.d.ts} +16 -16
@@ -90,6 +90,7 @@ function createCommentsThreadQueries(client, headers) {
90
90
  // The server resolves the caller's identity server-side via the
91
91
  // resolveCurrentUserId hook. It is still included in the queryKey
92
92
  // above for client-side cache segregation.
93
+ sort: params?.sort,
93
94
  limit: params?.limit ?? 20,
94
95
  offset: pageParam ?? 0
95
96
  },
@@ -1,7 +1,7 @@
1
1
  import * as _tanstack_react_query from '@tanstack/react-query';
2
- import { b as CommentsListDiscriminator, C as CommentListResult, c as CommentCountDiscriminator, a as CommentsThreadDiscriminator } from '../../shared/stack.C-b3Sn8j.cjs';
2
+ import { b as CommentsListDiscriminator, C as CommentListResult, c as CommentCountDiscriminator, a as CommentsThreadDiscriminator } from '../../shared/stack.C7KNNiJm.cjs';
3
3
  import { createApiClient } from '@btst/stack/plugins/client';
4
- import { C as CommentsApiRouter } from '../../shared/stack.CmHRdhl8.cjs';
4
+ import { C as CommentsApiRouter } from '../../shared/stack.CCncwK6g.cjs';
5
5
  import '@btst/stack/plugins/api';
6
6
  import 'better-call';
7
7
  import 'zod';
@@ -56,6 +56,7 @@ declare function createCommentsQueryKeys(client: ReturnType<typeof createApiClie
56
56
  parentId?: string | null;
57
57
  status?: "pending" | "approved" | "spam";
58
58
  currentUserId?: string;
59
+ sort?: "asc" | "desc";
59
60
  limit?: number;
60
61
  } | undefined) => Omit<Omit<{
61
62
  queryKey: readonly ["commentsThread", "list", CommentsThreadDiscriminator];
@@ -1,7 +1,7 @@
1
1
  import * as _tanstack_react_query from '@tanstack/react-query';
2
- import { b as CommentsListDiscriminator, C as CommentListResult, c as CommentCountDiscriminator, a as CommentsThreadDiscriminator } from '../../shared/stack.C-b3Sn8j.mjs';
2
+ import { b as CommentsListDiscriminator, C as CommentListResult, c as CommentCountDiscriminator, a as CommentsThreadDiscriminator } from '../../shared/stack.C7KNNiJm.mjs';
3
3
  import { createApiClient } from '@btst/stack/plugins/client';
4
- import { C as CommentsApiRouter } from '../../shared/stack.Dcz6636A.mjs';
4
+ import { C as CommentsApiRouter } from '../../shared/stack.CO1g47IS.mjs';
5
5
  import '@btst/stack/plugins/api';
6
6
  import 'better-call';
7
7
  import 'zod';
@@ -56,6 +56,7 @@ declare function createCommentsQueryKeys(client: ReturnType<typeof createApiClie
56
56
  parentId?: string | null;
57
57
  status?: "pending" | "approved" | "spam";
58
58
  currentUserId?: string;
59
+ sort?: "asc" | "desc";
59
60
  limit?: number;
60
61
  } | undefined) => Omit<Omit<{
61
62
  queryKey: readonly ["commentsThread", "list", CommentsThreadDiscriminator];
@@ -1,7 +1,7 @@
1
1
  import * as _tanstack_react_query from '@tanstack/react-query';
2
- import { b as CommentsListDiscriminator, C as CommentListResult, c as CommentCountDiscriminator, a as CommentsThreadDiscriminator } from '../../shared/stack.C-b3Sn8j.js';
2
+ import { b as CommentsListDiscriminator, C as CommentListResult, c as CommentCountDiscriminator, a as CommentsThreadDiscriminator } from '../../shared/stack.C7KNNiJm.js';
3
3
  import { createApiClient } from '@btst/stack/plugins/client';
4
- import { C as CommentsApiRouter } from '../../shared/stack.CJE9sAjV.js';
4
+ import { C as CommentsApiRouter } from '../../shared/stack.Bq3kiUaM.js';
5
5
  import '@btst/stack/plugins/api';
6
6
  import 'better-call';
7
7
  import 'zod';
@@ -56,6 +56,7 @@ declare function createCommentsQueryKeys(client: ReturnType<typeof createApiClie
56
56
  parentId?: string | null;
57
57
  status?: "pending" | "approved" | "spam";
58
58
  currentUserId?: string;
59
+ sort?: "asc" | "desc";
59
60
  limit?: number;
60
61
  } | undefined) => Omit<Omit<{
61
62
  queryKey: readonly ["commentsThread", "list", CommentsThreadDiscriminator];
@@ -88,6 +88,7 @@ function createCommentsThreadQueries(client, headers) {
88
88
  // The server resolves the caller's identity server-side via the
89
89
  // resolveCurrentUserId hook. It is still included in the queryKey
90
90
  // above for client-side cache segregation.
91
+ sort: params?.sort,
91
92
  limit: params?.limit ?? 20,
92
93
  offset: pageParam ?? 0
93
94
  },
@@ -1,4 +1,4 @@
1
- export { B as BoardListResult, C as CreateKanbanTaskInput, i as KANBAN_QUERY_KEYS, b as KanbanApiContext, K as KanbanApiRouter, c as KanbanBackendHooks, a as KanbanRouteKey, e as createKanbanTask, f as findOrCreateKanbanBoard, g as getAllBoards, d as getBoardById, h as getKanbanColumnsByBoardId, k as kanbanBackendPlugin } from '../../../shared/stack.B6S3cgwN.cjs';
1
+ export { B as BoardListResult, C as CreateKanbanTaskInput, i as KANBAN_QUERY_KEYS, b as KanbanApiContext, K as KanbanApiRouter, c as KanbanBackendHooks, a as KanbanRouteKey, e as createKanbanTask, f as findOrCreateKanbanBoard, g as getAllBoards, d as getBoardById, h as getKanbanColumnsByBoardId, k as kanbanBackendPlugin } from '../../../shared/stack.CuI_-z-_.cjs';
2
2
  import { B as BoardWithColumns, S as SerializedBoardWithColumns, C as ColumnWithTasks, a as SerializedColumn, T as Task, b as SerializedTask } from '../../../shared/stack.DJaKVY7v.cjs';
3
3
  import '@btst/stack/plugins/api';
4
4
  import '@btst/db';
@@ -1,4 +1,4 @@
1
- export { B as BoardListResult, C as CreateKanbanTaskInput, i as KANBAN_QUERY_KEYS, b as KanbanApiContext, K as KanbanApiRouter, c as KanbanBackendHooks, a as KanbanRouteKey, e as createKanbanTask, f as findOrCreateKanbanBoard, g as getAllBoards, d as getBoardById, h as getKanbanColumnsByBoardId, k as kanbanBackendPlugin } from '../../../shared/stack.Bzfx-_lq.mjs';
1
+ export { B as BoardListResult, C as CreateKanbanTaskInput, i as KANBAN_QUERY_KEYS, b as KanbanApiContext, K as KanbanApiRouter, c as KanbanBackendHooks, a as KanbanRouteKey, e as createKanbanTask, f as findOrCreateKanbanBoard, g as getAllBoards, d as getBoardById, h as getKanbanColumnsByBoardId, k as kanbanBackendPlugin } from '../../../shared/stack.D2RVkQD8.mjs';
2
2
  import { B as BoardWithColumns, S as SerializedBoardWithColumns, C as ColumnWithTasks, a as SerializedColumn, T as Task, b as SerializedTask } from '../../../shared/stack.DJaKVY7v.mjs';
3
3
  import '@btst/stack/plugins/api';
4
4
  import '@btst/db';
@@ -1,4 +1,4 @@
1
- export { B as BoardListResult, C as CreateKanbanTaskInput, i as KANBAN_QUERY_KEYS, b as KanbanApiContext, K as KanbanApiRouter, c as KanbanBackendHooks, a as KanbanRouteKey, e as createKanbanTask, f as findOrCreateKanbanBoard, g as getAllBoards, d as getBoardById, h as getKanbanColumnsByBoardId, k as kanbanBackendPlugin } from '../../../shared/stack.j5SFLC1d.js';
1
+ export { B as BoardListResult, C as CreateKanbanTaskInput, i as KANBAN_QUERY_KEYS, b as KanbanApiContext, K as KanbanApiRouter, c as KanbanBackendHooks, a as KanbanRouteKey, e as createKanbanTask, f as findOrCreateKanbanBoard, g as getAllBoards, d as getBoardById, h as getKanbanColumnsByBoardId, k as kanbanBackendPlugin } from '../../../shared/stack.DXrOtuek.js';
2
2
  import { B as BoardWithColumns, S as SerializedBoardWithColumns, C as ColumnWithTasks, a as SerializedColumn, T as Task, b as SerializedTask } from '../../../shared/stack.DJaKVY7v.js';
3
3
  import '@btst/stack/plugins/api';
4
4
  import '@btst/db';
@@ -1,5 +1,5 @@
1
1
  import * as _tanstack_react_query from '@tanstack/react-query';
2
- import { K as KanbanApiRouter, j as BoardsListDiscriminator } from '../../shared/stack.B6S3cgwN.cjs';
2
+ import { K as KanbanApiRouter, j as BoardsListDiscriminator } from '../../shared/stack.CuI_-z-_.cjs';
3
3
  import { createApiClient } from '@btst/stack/plugins/client';
4
4
  import { S as SerializedBoardWithColumns } from '../../shared/stack.DJaKVY7v.cjs';
5
5
  import '@btst/stack/plugins/api';
@@ -1,5 +1,5 @@
1
1
  import * as _tanstack_react_query from '@tanstack/react-query';
2
- import { K as KanbanApiRouter, j as BoardsListDiscriminator } from '../../shared/stack.Bzfx-_lq.mjs';
2
+ import { K as KanbanApiRouter, j as BoardsListDiscriminator } from '../../shared/stack.D2RVkQD8.mjs';
3
3
  import { createApiClient } from '@btst/stack/plugins/client';
4
4
  import { S as SerializedBoardWithColumns } from '../../shared/stack.DJaKVY7v.mjs';
5
5
  import '@btst/stack/plugins/api';
@@ -1,5 +1,5 @@
1
1
  import * as _tanstack_react_query from '@tanstack/react-query';
2
- import { K as KanbanApiRouter, j as BoardsListDiscriminator } from '../../shared/stack.j5SFLC1d.js';
2
+ import { K as KanbanApiRouter, j as BoardsListDiscriminator } from '../../shared/stack.DXrOtuek.js';
3
3
  import { createApiClient } from '@btst/stack/plugins/client';
4
4
  import { S as SerializedBoardWithColumns } from '../../shared/stack.DJaKVY7v.js';
5
5
  import '@btst/stack/plugins/api';
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { C as ContentTypeConfig } from '../../shared/stack.B8QD11QU.cjs';
2
+ import { C as ContentTypeConfig } from '../../shared/stack.DFtpFS00.cjs';
3
3
  export { L as LoaderContext, b as PaginatedUIBuilderPages, P as ParsedUIBuilderPage, S as SerializedUIBuilderPage, c as UIBuilderClientHooks, a as UIBuilderPage, U as UIBuilderPageData } from '../../shared/stack.C1nXGBr6.cjs';
4
4
  export { C as ComponentLayer, a as ComponentRegistry, F as FunctionRegistry, R as RegistryEntry, V as Variable } from '../../shared/stack.BOCvd9HK.cjs';
5
5
  import 'react';
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { C as ContentTypeConfig } from '../../shared/stack.B8QD11QU.mjs';
2
+ import { C as ContentTypeConfig } from '../../shared/stack.DFtpFS00.mjs';
3
3
  export { L as LoaderContext, b as PaginatedUIBuilderPages, P as ParsedUIBuilderPage, S as SerializedUIBuilderPage, c as UIBuilderClientHooks, a as UIBuilderPage, U as UIBuilderPageData } from '../../shared/stack.DLPa6Gzm.mjs';
4
4
  export { C as ComponentLayer, a as ComponentRegistry, F as FunctionRegistry, R as RegistryEntry, V as Variable } from '../../shared/stack.BFoBvGML.mjs';
5
5
  import 'react';
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { C as ContentTypeConfig } from '../../shared/stack.B8QD11QU.js';
2
+ import { C as ContentTypeConfig } from '../../shared/stack.DFtpFS00.js';
3
3
  export { L as LoaderContext, b as PaginatedUIBuilderPages, P as ParsedUIBuilderPage, S as SerializedUIBuilderPage, c as UIBuilderClientHooks, a as UIBuilderPage, U as UIBuilderPageData } from '../../shared/stack.DxJ-tHLt.js';
4
4
  export { C as ComponentLayer, a as ComponentRegistry, F as FunctionRegistry, R as RegistryEntry, V as Variable } from '../../shared/stack.BmMB0LNC.js';
5
5
  import 'react';
@@ -1,5 +1,5 @@
1
1
  import * as _btst_stack_plugins_api from '@btst/stack/plugins/api';
2
- import { d as Comment, S as SerializedComment, C as CommentListResult } from './stack.C-b3Sn8j.js';
2
+ import { d as Comment, S as SerializedComment, C as CommentListResult } from './stack.C7KNNiJm.js';
3
3
  import * as better_call from 'better-call';
4
4
  import { z } from 'zod';
5
5
 
@@ -90,6 +90,7 @@ interface CommentsThreadDiscriminator {
90
90
  parentId: string | null | undefined;
91
91
  status: string | undefined;
92
92
  currentUserId: string | undefined;
93
+ sort: string | undefined;
93
94
  limit: number;
94
95
  }
95
96
  /** Full query key builders — use with queryClient.setQueryData() */
@@ -125,7 +126,7 @@ declare const COMMENTS_QUERY_KEYS: {
125
126
  }) => readonly ["comments", "count", CommentCountDiscriminator];
126
127
  /**
127
128
  * Key for the infinite thread query (top-level comments, load-more).
128
- * Full key: ["commentsThread", "list", { resourceId, resourceType, parentId, status, currentUserId, limit }]
129
+ * Full key: ["commentsThread", "list", { resourceId, resourceType, parentId, status, currentUserId, sort, limit }]
129
130
  * Offset is excluded — it is driven by `pageParam`, not baked into the key.
130
131
  */
131
132
  commentsThread: (params?: {
@@ -134,6 +135,7 @@ declare const COMMENTS_QUERY_KEYS: {
134
135
  parentId?: string | null;
135
136
  status?: string;
136
137
  currentUserId?: string;
138
+ sort?: string;
137
139
  limit?: number;
138
140
  }) => readonly ["commentsThread", "list", CommentsThreadDiscriminator];
139
141
  };
@@ -90,6 +90,7 @@ interface CommentsThreadDiscriminator {
90
90
  parentId: string | null | undefined;
91
91
  status: string | undefined;
92
92
  currentUserId: string | undefined;
93
+ sort: string | undefined;
93
94
  limit: number;
94
95
  }
95
96
  /** Full query key builders — use with queryClient.setQueryData() */
@@ -125,7 +126,7 @@ declare const COMMENTS_QUERY_KEYS: {
125
126
  }) => readonly ["comments", "count", CommentCountDiscriminator];
126
127
  /**
127
128
  * Key for the infinite thread query (top-level comments, load-more).
128
- * Full key: ["commentsThread", "list", { resourceId, resourceType, parentId, status, currentUserId, limit }]
129
+ * Full key: ["commentsThread", "list", { resourceId, resourceType, parentId, status, currentUserId, sort, limit }]
129
130
  * Offset is excluded — it is driven by `pageParam`, not baked into the key.
130
131
  */
131
132
  commentsThread: (params?: {
@@ -134,6 +135,7 @@ declare const COMMENTS_QUERY_KEYS: {
134
135
  parentId?: string | null;
135
136
  status?: string;
136
137
  currentUserId?: string;
138
+ sort?: string;
137
139
  limit?: number;
138
140
  }) => readonly ["commentsThread", "list", CommentsThreadDiscriminator];
139
141
  };
@@ -90,6 +90,7 @@ interface CommentsThreadDiscriminator {
90
90
  parentId: string | null | undefined;
91
91
  status: string | undefined;
92
92
  currentUserId: string | undefined;
93
+ sort: string | undefined;
93
94
  limit: number;
94
95
  }
95
96
  /** Full query key builders — use with queryClient.setQueryData() */
@@ -125,7 +126,7 @@ declare const COMMENTS_QUERY_KEYS: {
125
126
  }) => readonly ["comments", "count", CommentCountDiscriminator];
126
127
  /**
127
128
  * Key for the infinite thread query (top-level comments, load-more).
128
- * Full key: ["commentsThread", "list", { resourceId, resourceType, parentId, status, currentUserId, limit }]
129
+ * Full key: ["commentsThread", "list", { resourceId, resourceType, parentId, status, currentUserId, sort, limit }]
129
130
  * Offset is excluded — it is driven by `pageParam`, not baked into the key.
130
131
  */
131
132
  commentsThread: (params?: {
@@ -134,6 +135,7 @@ declare const COMMENTS_QUERY_KEYS: {
134
135
  parentId?: string | null;
135
136
  status?: string;
136
137
  currentUserId?: string;
138
+ sort?: string;
137
139
  limit?: number;
138
140
  }) => readonly ["commentsThread", "list", CommentsThreadDiscriminator];
139
141
  };
@@ -1,5 +1,5 @@
1
1
  import * as _btst_stack_plugins_api from '@btst/stack/plugins/api';
2
- import { d as Comment, S as SerializedComment, C as CommentListResult } from './stack.C-b3Sn8j.mjs';
2
+ import { d as Comment, S as SerializedComment, C as CommentListResult } from './stack.C7KNNiJm.cjs';
3
3
  import * as better_call from 'better-call';
4
4
  import { z } from 'zod';
5
5
 
@@ -1,5 +1,5 @@
1
1
  import * as _btst_stack_plugins_api from '@btst/stack/plugins/api';
2
- import { d as Comment, S as SerializedComment, C as CommentListResult } from './stack.C-b3Sn8j.cjs';
2
+ import { d as Comment, S as SerializedComment, C as CommentListResult } from './stack.C7KNNiJm.mjs';
3
3
  import * as better_call from 'better-call';
4
4
  import { z } from 'zod';
5
5
 
@@ -158,6 +158,16 @@ interface CMSBackendConfig {
158
158
  contentTypes: ContentTypeConfig[];
159
159
  /** Optional hooks for customizing behavior */
160
160
  hooks?: CMSBackendHooks;
161
+ /**
162
+ * Maximum number of items that can be requested in a single page.
163
+ * Applied to all list endpoints (`/content/:typeSlug`, by-relation, and inverse-relation).
164
+ *
165
+ * Raise this when your content types have many items and you need to fetch
166
+ * large pages (e.g. for SSG, CSV export, or programmatic access).
167
+ *
168
+ * @default 1000
169
+ */
170
+ maxPageSize?: number;
161
171
  }
162
172
 
163
173
  export type { ContentTypeConfig as C, InverseRelation as I, PaginatedContentItems as P, SerializedContentType as S, SerializedContentItemWithType as a, ContentType as b, ContentItem as c, SerializedContentItem as d, ContentItemWithType as e, CMSBackendConfig as f };
@@ -158,6 +158,16 @@ interface CMSBackendConfig {
158
158
  contentTypes: ContentTypeConfig[];
159
159
  /** Optional hooks for customizing behavior */
160
160
  hooks?: CMSBackendHooks;
161
+ /**
162
+ * Maximum number of items that can be requested in a single page.
163
+ * Applied to all list endpoints (`/content/:typeSlug`, by-relation, and inverse-relation).
164
+ *
165
+ * Raise this when your content types have many items and you need to fetch
166
+ * large pages (e.g. for SSG, CSV export, or programmatic access).
167
+ *
168
+ * @default 1000
169
+ */
170
+ maxPageSize?: number;
161
171
  }
162
172
 
163
173
  export type { ContentTypeConfig as C, InverseRelation as I, PaginatedContentItems as P, SerializedContentType as S, SerializedContentItemWithType as a, ContentType as b, ContentItem as c, SerializedContentItem as d, ContentItemWithType as e, CMSBackendConfig as f };
@@ -158,6 +158,16 @@ interface CMSBackendConfig {
158
158
  contentTypes: ContentTypeConfig[];
159
159
  /** Optional hooks for customizing behavior */
160
160
  hooks?: CMSBackendHooks;
161
+ /**
162
+ * Maximum number of items that can be requested in a single page.
163
+ * Applied to all list endpoints (`/content/:typeSlug`, by-relation, and inverse-relation).
164
+ *
165
+ * Raise this when your content types have many items and you need to fetch
166
+ * large pages (e.g. for SSG, CSV export, or programmatic access).
167
+ *
168
+ * @default 1000
169
+ */
170
+ maxPageSize?: number;
161
171
  }
162
172
 
163
173
  export type { ContentTypeConfig as C, InverseRelation as I, PaginatedContentItems as P, SerializedContentType as S, SerializedContentItemWithType as a, ContentType as b, ContentItem as c, SerializedContentItem as d, ContentItemWithType as e, CMSBackendConfig as f };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btst/stack",
3
- "version": "2.11.8",
3
+ "version": "2.12.1",
4
4
  "description": "A composable, plugin-based library for building full-stack applications.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -804,3 +804,206 @@ describe("Zod to JSON Schema roundtrip", () => {
804
804
  });
805
805
  });
806
806
  });
807
+
808
+ /**
809
+ * These tests confirm and protect against a bug where `useFieldArray` in
810
+ * AutoFormArray corrupts primitive string values in arrays like
811
+ * `structuredContraindications: z.array(z.string()).default([])`.
812
+ *
813
+ * When react-hook-form's `useFieldArray` processes a primitive array
814
+ * (e.g. `["pregnancy", "active malignancy"]`), it wraps each element as a
815
+ * tracking object `{ id: "rhf_generated_id" }`, discarding the original
816
+ * string value. When `form.watch()` fires, it returns these objects. The
817
+ * `handleValuesChange` callback then calls `setFormData(values)` which stores
818
+ * the corrupted objects. On form submit, `zodResolver` validates the corrupted
819
+ * values against `z.array(z.string())` and FAILS — causing the Save button
820
+ * to do nothing (no error shown, no API request made).
821
+ *
822
+ * The fix: AutoFormArray must NOT use `useFieldArray` for primitive (non-object)
823
+ * arrays. Instead it uses `form.watch` + `form.setValue` directly so primitive
824
+ * values are always preserved in the form state.
825
+ */
826
+ describe("Primitive string array — useFieldArray corruption bug", () => {
827
+ /**
828
+ * Simulates what zodToFormSchema + formSchemaToZod does:
829
+ * Zod schema → JSON Schema (stored in DB) → reconstructed Zod schema.
830
+ */
831
+ function roundtripSchema(schema: z.ZodType): z.ZodType {
832
+ const jsonSchema = z.toJSONSchema(schema, { unrepresentable: "any" });
833
+ return z.fromJSONSchema(jsonSchema as z.core.JSONSchema.JSONSchema);
834
+ }
835
+
836
+ it("z.array(z.string()) survives JSON Schema roundtrip and accepts string values", () => {
837
+ const schema = z.object({
838
+ structuredContraindications: z.array(z.string()).default([]),
839
+ });
840
+
841
+ const reconstructed = roundtripSchema(schema);
842
+
843
+ // Actual compound data — should PASS
844
+ expect(
845
+ reconstructed.safeParse({
846
+ structuredContraindications: [
847
+ "pregnancy",
848
+ "active malignancy",
849
+ "active cancer",
850
+ "trying to conceive",
851
+ ],
852
+ }).success,
853
+ ).toBe(true);
854
+
855
+ // Empty array — should PASS (default)
856
+ expect(
857
+ reconstructed.safeParse({ structuredContraindications: [] }).success,
858
+ ).toBe(true);
859
+ });
860
+
861
+ it("useFieldArray-corrupted values (objects) fail z.array(z.string()) validation — this is the root cause of the silent save failure", () => {
862
+ const schema = z.object({
863
+ structuredContraindications: z.array(z.string()).default([]),
864
+ });
865
+
866
+ const reconstructed = roundtripSchema(schema);
867
+
868
+ // Simulates what react-hook-form's useFieldArray returns when used with
869
+ // primitive string arrays: each string is replaced by a tracking object
870
+ // { id: "rhf_generated_id" } and the original value is lost.
871
+ const corruptedByUseFieldArray = {
872
+ structuredContraindications: [
873
+ { id: "rhf_internal_id_1" },
874
+ { id: "rhf_internal_id_2" },
875
+ { id: "rhf_internal_id_3" },
876
+ { id: "rhf_internal_id_4" },
877
+ ],
878
+ };
879
+
880
+ // This is the actual validation error that occurs when Save is clicked:
881
+ // zodResolver validates the corrupted objects against z.array(z.string())
882
+ // and FAILS, so onSubmit is never called → no API request → "nothing happens"
883
+ const result = reconstructed.safeParse(corruptedByUseFieldArray);
884
+ expect(result.success).toBe(false);
885
+ if (!result.success) {
886
+ // Confirm the error is specifically about the string array elements
887
+ const paths = result.error.issues.map((i) => i.path.join("."));
888
+ expect(
889
+ paths.some((p) => p.startsWith("structuredContraindications")),
890
+ ).toBe(true);
891
+ }
892
+ });
893
+
894
+ it("primitive string array values are preserved correctly (NOT corrupted) when form state is managed without useFieldArray", () => {
895
+ // After the fix, AutoFormArray uses form.watch + form.setValue for primitive
896
+ // arrays. The values remain as strings throughout the lifecycle:
897
+ // initialData → formData → form state → zodResolver → submit
898
+
899
+ const schema = z.object({
900
+ structuredContraindications: z.array(z.string()).default([]),
901
+ });
902
+
903
+ const reconstructed = roundtripSchema(schema);
904
+
905
+ // The correctly-preserved values (no useFieldArray wrapping)
906
+ const preservedValues = {
907
+ structuredContraindications: [
908
+ "pregnancy",
909
+ "active malignancy",
910
+ "active cancer",
911
+ "trying to conceive",
912
+ ],
913
+ };
914
+
915
+ // With the fix applied, these values pass validation → Save works
916
+ expect(reconstructed.safeParse(preservedValues).success).toBe(true);
917
+ });
918
+
919
+ it("compound schema with structuredContraindications passes full validation with string array values", () => {
920
+ // Representative subset of CompoundSchema fields that appear on the
921
+ // Epitalon compound page — verifies the full schema round-trip for the
922
+ // fields that are relevant to the reported bug.
923
+ const compoundSchema = z.object({
924
+ name: z.string().min(1),
925
+ compoundType: z.enum([
926
+ "healing-peptide",
927
+ "gh-axis",
928
+ "metabolic-peptide",
929
+ "sarm",
930
+ "steroid",
931
+ "nootropic",
932
+ "supplement",
933
+ "ancillary-pct",
934
+ "longevity",
935
+ "hair",
936
+ "skin",
937
+ "sexual",
938
+ "other",
939
+ ]),
940
+ researchStatus: z.enum([
941
+ "research-only",
942
+ "approved",
943
+ "banned",
944
+ "grey-market",
945
+ "supplement",
946
+ ]),
947
+ legalStatus: z.enum([
948
+ "OTC",
949
+ "Research",
950
+ "Grey-Market",
951
+ "Rx-Only",
952
+ "Schedule-III",
953
+ "Banned",
954
+ ]),
955
+ doseUnit: z.enum(["mcg", "mg", "IU", "ml", "g"]),
956
+ doseFrequency: z.enum([
957
+ "once-daily",
958
+ "twice-daily",
959
+ "three-times-daily",
960
+ "every-other-day",
961
+ "weekly",
962
+ "twice-weekly",
963
+ "three-times-weekly",
964
+ "as-needed",
965
+ "custom",
966
+ ]),
967
+ structuredContraindications: z.array(z.string()).default([]),
968
+ affiliates: z
969
+ .array(
970
+ z.object({
971
+ partnerId: z.object({ id: z.string() }).optional(),
972
+ title: z.string().optional(),
973
+ url: z.string().min(1),
974
+ }),
975
+ )
976
+ .default([]),
977
+ });
978
+
979
+ const reconstructed = roundtripSchema(compoundSchema);
980
+
981
+ // Epitalon-like data — all values as they come from parsedData
982
+ const epitalon = {
983
+ name: "Epitalon",
984
+ compoundType: "longevity",
985
+ researchStatus: "research-only",
986
+ legalStatus: "Research",
987
+ doseUnit: "mg",
988
+ doseFrequency: "once-daily",
989
+ // The 4 string items that were causing the silent save failure
990
+ structuredContraindications: [
991
+ "pregnancy",
992
+ "active malignancy",
993
+ "active cancer",
994
+ "trying to conceive",
995
+ ],
996
+ affiliates: [
997
+ {
998
+ partnerId: { id: "v6yAqOSO_example_id" },
999
+ title: "Buy Epitalon 10mg",
1000
+ url: "https://swisschems.is/product/epitalon-10mg-price-is-per-vial/",
1001
+ },
1002
+ ],
1003
+ };
1004
+
1005
+ const result = reconstructed.safeParse(epitalon);
1006
+ // After the fix, this should PASS (the save button works)
1007
+ expect(result.success).toBe(true);
1008
+ });
1009
+ });
@@ -18,3 +18,4 @@ export {
18
18
  type CreateCMSContentItemOptions,
19
19
  } from "./mutations";
20
20
  export { CMS_QUERY_KEYS } from "./query-key-defs";
21
+ export { createCMSQueryKeys } from "../query-keys";
@@ -18,7 +18,10 @@ import type {
18
18
  RelationValue,
19
19
  InverseRelation,
20
20
  } from "../types";
21
- import { listContentQuerySchema } from "../schemas";
21
+ import {
22
+ createListContentQuerySchema,
23
+ DEFAULT_MAX_PAGE_SIZE,
24
+ } from "../schemas";
22
25
  import { slugify } from "../utils";
23
26
  import {
24
27
  getAllContentTypes,
@@ -492,6 +495,20 @@ export const cmsBackendPlugin = (config: CMSBackendConfig) => {
492
495
  }),
493
496
 
494
497
  routes: (adapter: Adapter) => {
498
+ // Build pagination schemas once — honours config.maxPageSize
499
+ const listContentQuerySchema = createListContentQuerySchema(
500
+ config.maxPageSize,
501
+ );
502
+ const paginationQuerySchema = z.object({
503
+ limit: z.coerce
504
+ .number()
505
+ .min(1)
506
+ .max(config.maxPageSize ?? DEFAULT_MAX_PAGE_SIZE)
507
+ .optional()
508
+ .default(20),
509
+ offset: z.coerce.number().min(0).optional().default(0),
510
+ });
511
+
495
512
  // Helper to get content type by slug
496
513
  const getContentType = async (
497
514
  slug: string,
@@ -525,10 +542,10 @@ export const cmsBackendPlugin = (config: CMSBackendConfig) => {
525
542
  sortBy: { field: "name", direction: "asc" },
526
543
  });
527
544
 
528
- // Get item counts for each content type
545
+ // Get item counts for each content type via adapter.count() (avoids N+1 scan)
529
546
  const typesWithCounts = await Promise.all(
530
547
  contentTypes.map(async (ct) => {
531
- const items = await adapter.findMany<ContentItem>({
548
+ const itemCount: number = await adapter.count({
532
549
  model: "contentItem",
533
550
  where: [
534
551
  {
@@ -540,7 +557,7 @@ export const cmsBackendPlugin = (config: CMSBackendConfig) => {
540
557
  });
541
558
  return {
542
559
  ...serializeContentType(ct),
543
- itemCount: items.length,
560
+ itemCount,
544
561
  };
545
562
  }),
546
563
  );
@@ -964,12 +981,12 @@ export const cmsBackendPlugin = (config: CMSBackendConfig) => {
964
981
  {
965
982
  method: "GET",
966
983
  params: z.object({ typeSlug: z.string() }),
967
- query: z.object({
968
- field: z.string(),
969
- targetId: z.string(),
970
- limit: z.coerce.number().min(1).max(100).optional().default(20),
971
- offset: z.coerce.number().min(0).optional().default(0),
972
- }),
984
+ query: z
985
+ .object({
986
+ field: z.string(),
987
+ targetId: z.string(),
988
+ })
989
+ .merge(paginationQuerySchema),
973
990
  },
974
991
  async (ctx) => {
975
992
  const { typeSlug } = ctx.params;
@@ -1144,12 +1161,12 @@ export const cmsBackendPlugin = (config: CMSBackendConfig) => {
1144
1161
  slug: z.string(),
1145
1162
  sourceType: z.string(),
1146
1163
  }),
1147
- query: z.object({
1148
- itemId: z.string(),
1149
- fieldName: z.string(),
1150
- limit: z.coerce.number().min(1).max(100).optional().default(20),
1151
- offset: z.coerce.number().min(0).optional().default(0),
1152
- }),
1164
+ query: z
1165
+ .object({
1166
+ itemId: z.string(),
1167
+ fieldName: z.string(),
1168
+ })
1169
+ .merge(paginationQuerySchema),
1153
1170
  },
1154
1171
  async (ctx) => {
1155
1172
  const { slug, sourceType } = ctx.params;