@btst/stack 1.2.2 → 1.3.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 (32) hide show
  1. package/dist/packages/better-stack/src/plugins/blog/api/plugin.cjs +101 -120
  2. package/dist/packages/better-stack/src/plugins/blog/api/plugin.mjs +101 -120
  3. package/dist/packages/better-stack/src/plugins/blog/client/components/pages/post-page.internal.cjs +1 -1
  4. package/dist/packages/better-stack/src/plugins/blog/client/components/pages/post-page.internal.mjs +1 -1
  5. package/dist/packages/better-stack/src/plugins/blog/client/components/shared/on-this-page.cjs +1 -1
  6. package/dist/packages/better-stack/src/plugins/blog/client/components/shared/on-this-page.mjs +1 -1
  7. package/dist/packages/better-stack/src/plugins/blog/client/components/shared/recent-posts-carousel.cjs +2 -2
  8. package/dist/packages/better-stack/src/plugins/blog/client/components/shared/recent-posts-carousel.mjs +2 -2
  9. package/dist/packages/better-stack/src/plugins/blog/db.cjs +12 -2
  10. package/dist/packages/better-stack/src/plugins/blog/db.mjs +12 -2
  11. package/dist/plugins/blog/api/index.d.cts +1 -1
  12. package/dist/plugins/blog/api/index.d.mts +1 -1
  13. package/dist/plugins/blog/api/index.d.ts +1 -1
  14. package/dist/plugins/blog/client/hooks/index.d.cts +3 -3
  15. package/dist/plugins/blog/client/hooks/index.d.mts +3 -3
  16. package/dist/plugins/blog/client/hooks/index.d.ts +3 -3
  17. package/dist/plugins/blog/client/index.d.cts +1 -1
  18. package/dist/plugins/blog/client/index.d.mts +1 -1
  19. package/dist/plugins/blog/client/index.d.ts +1 -1
  20. package/dist/plugins/blog/query-keys.d.cts +6 -6
  21. package/dist/plugins/blog/query-keys.d.mts +6 -6
  22. package/dist/plugins/blog/query-keys.d.ts +6 -6
  23. package/package.json +3 -3
  24. package/src/plugins/blog/api/plugin.ts +122 -147
  25. package/src/plugins/blog/client/components/pages/post-page.internal.tsx +1 -1
  26. package/src/plugins/blog/client/components/shared/on-this-page.tsx +1 -1
  27. package/src/plugins/blog/client/components/shared/recent-posts-carousel.tsx +2 -2
  28. package/src/plugins/blog/db.ts +10 -0
  29. package/src/plugins/blog/types.ts +7 -0
  30. package/dist/shared/{stack.Cr2JoQdo.d.cts → stack.CbuN2zVV.d.cts} +3 -3
  31. package/dist/shared/{stack.Cr2JoQdo.d.mts → stack.CbuN2zVV.d.mts} +3 -3
  32. package/dist/shared/{stack.Cr2JoQdo.d.ts → stack.CbuN2zVV.d.ts} +3 -3
@@ -3,7 +3,7 @@ import { defineBackendPlugin } from "@btst/stack/plugins/api";
3
3
  import { createEndpoint } from "@btst/stack/plugins/api";
4
4
  import { z } from "zod";
5
5
  import { blogSchema as dbSchema } from "../db";
6
- import type { Post, Tag } from "../types";
6
+ import type { Post, PostWithPostTag, Tag } from "../types";
7
7
  import { slugify } from "../utils";
8
8
  import { createPostSchema, updatePostSchema } from "../schemas";
9
9
 
@@ -169,68 +169,10 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
169
169
  dbPlugin: dbSchema,
170
170
 
171
171
  routes: (adapter: Adapter) => {
172
- const createTagCache = () => {
173
- let cache: Tag[] | null = null;
174
- return {
175
- getAllTags: async (): Promise<Tag[]> => {
176
- if (!cache) {
177
- cache = await adapter.findMany<Tag>({
178
- model: "tag",
179
- });
180
- }
181
- return cache;
182
- },
183
- invalidate: () => {
184
- cache = null;
185
- },
186
- addTag: (tag: Tag) => {
187
- if (cache) {
188
- cache.push(tag);
189
- }
190
- },
191
- };
192
- };
193
-
194
- const createPostTagCache = () => {
195
- let cache: Array<{ postId: string; tagId: string }> | null = null;
196
- const getAllPostTags = async (): Promise<
197
- Array<{ postId: string; tagId: string }>
198
- > => {
199
- if (!cache) {
200
- cache = await adapter.findMany<{
201
- postId: string;
202
- tagId: string;
203
- }>({
204
- model: "postTag",
205
- });
206
- }
207
- return cache;
208
- };
209
- return {
210
- getAllPostTags,
211
- invalidate: () => {
212
- cache = null;
213
- },
214
- getByTagId: async (
215
- tagId: string,
216
- ): Promise<Array<{ postId: string; tagId: string }>> => {
217
- const allPostTags = await getAllPostTags();
218
- return allPostTags.filter((pt) => pt.tagId === tagId);
219
- },
220
- getByPostId: async (
221
- postId: string,
222
- ): Promise<Array<{ postId: string; tagId: string }>> => {
223
- const allPostTags = await getAllPostTags();
224
- return allPostTags.filter((pt) => pt.postId === postId);
225
- },
226
- };
227
- };
228
-
229
172
  const findOrCreateTags = async (
230
173
  tagInputs: Array<
231
174
  { name: string } | { id: string; name: string; slug: string }
232
175
  >,
233
- tagCache: ReturnType<typeof createTagCache>,
234
176
  ): Promise<Tag[]> => {
235
177
  if (tagInputs.length === 0) return [];
236
178
 
@@ -259,7 +201,9 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
259
201
  return tagsWithIds;
260
202
  }
261
203
 
262
- const allTags = await tagCache.getAllTags();
204
+ const allTags = await adapter.findMany<Tag>({
205
+ model: "tag",
206
+ });
263
207
  const tagMapBySlug = new Map<string, Tag>();
264
208
  for (const tag of allTags) {
265
209
  tagMapBySlug.set(tag.slug, tag);
@@ -296,45 +240,11 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
296
240
  },
297
241
  });
298
242
  createdTags.push(newTag);
299
- tagCache.addTag(newTag);
300
243
  }
301
244
 
302
245
  return [...tagsWithIds, ...foundTags, ...createdTags];
303
246
  };
304
247
 
305
- const loadTagsForPosts = async (
306
- postIds: string[],
307
- tagCache: ReturnType<typeof createTagCache>,
308
- postTagCache: ReturnType<typeof createPostTagCache>,
309
- ): Promise<Map<string, Tag[]>> => {
310
- if (postIds.length === 0) return new Map();
311
-
312
- const allPostTags = await postTagCache.getAllPostTags();
313
- const relevantPostTags = allPostTags.filter((pt) =>
314
- postIds.includes(pt.postId),
315
- );
316
-
317
- const tagIds = [...new Set(relevantPostTags.map((pt) => pt.tagId))];
318
- if (tagIds.length === 0) return new Map();
319
-
320
- const allTags = await tagCache.getAllTags();
321
- const tagMap = new Map<string, Tag>();
322
- for (const tag of allTags) {
323
- tagMap.set(tag.id, tag);
324
- }
325
-
326
- const postTagsMap = new Map<string, Tag[]>();
327
- for (const postTag of relevantPostTags) {
328
- const tag = tagMap.get(postTag.tagId);
329
- if (tag) {
330
- const existing = postTagsMap.get(postTag.postId) || [];
331
- postTagsMap.set(postTag.postId, [...existing, { ...tag }]);
332
- }
333
- }
334
-
335
- return postTagsMap;
336
- };
337
-
338
248
  const listPosts = createEndpoint(
339
249
  "/posts",
340
250
  {
@@ -344,8 +254,6 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
344
254
  async (ctx) => {
345
255
  const { query, headers } = ctx;
346
256
  const context: BlogApiContext = { query, headers };
347
- const tagCache = createTagCache();
348
- const postTagCache = createPostTagCache();
349
257
 
350
258
  try {
351
259
  if (hooks?.onBeforeListPosts) {
@@ -360,14 +268,34 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
360
268
  let tagFilterPostIds: Set<string> | null = null;
361
269
 
362
270
  if (query.tagSlug) {
363
- const allTags = await tagCache.getAllTags();
364
- const tag = allTags.find((t) => t.slug === query.tagSlug);
271
+ const tag = await adapter.findOne<Tag>({
272
+ model: "tag",
273
+ where: [
274
+ {
275
+ field: "slug",
276
+ value: query.tagSlug,
277
+ operator: "eq" as const,
278
+ },
279
+ ],
280
+ });
365
281
 
366
282
  if (!tag) {
367
283
  return [];
368
284
  }
369
285
 
370
- const postTags = await postTagCache.getByTagId(tag.id);
286
+ const postTags = await adapter.findMany<{
287
+ postId: string;
288
+ tagId: string;
289
+ }>({
290
+ model: "postTag",
291
+ where: [
292
+ {
293
+ field: "tagId",
294
+ value: tag.id,
295
+ operator: "eq" as const,
296
+ },
297
+ ],
298
+ });
371
299
  tagFilterPostIds = new Set(postTags.map((pt) => pt.postId));
372
300
  if (tagFilterPostIds.size === 0) {
373
301
  return [];
@@ -392,7 +320,7 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
392
320
  });
393
321
  }
394
322
 
395
- const posts = await adapter.findMany<Post>({
323
+ const posts = await adapter.findMany<PostWithPostTag>({
396
324
  model: "post",
397
325
  limit:
398
326
  query.query || query.tagSlug ? undefined : (query.limit ?? 10),
@@ -403,18 +331,49 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
403
331
  field: "createdAt",
404
332
  direction: "desc",
405
333
  },
334
+ join: {
335
+ postTag: true,
336
+ },
406
337
  });
407
338
 
408
- const postTagsMap = await loadTagsForPosts(
409
- posts.map((post) => post.id),
410
- tagCache,
411
- postTagCache,
412
- );
339
+ // Collect unique tag IDs from joined postTag data
340
+ const tagIds = new Set<string>();
341
+ for (const post of posts) {
342
+ if (post.postTag) {
343
+ for (const pt of post.postTag) {
344
+ tagIds.add(pt.tagId);
345
+ }
346
+ }
347
+ }
348
+
349
+ // Fetch all tags at once
350
+ const tags =
351
+ tagIds.size > 0
352
+ ? await adapter.findMany<Tag>({
353
+ model: "tag",
354
+ })
355
+ : [];
356
+ const tagMap = new Map<string, Tag>();
357
+ for (const tag of tags) {
358
+ if (tagIds.has(tag.id)) {
359
+ tagMap.set(tag.id, tag);
360
+ }
361
+ }
413
362
 
414
- let result = posts.map((post) => ({
415
- ...post,
416
- tags: postTagsMap.get(post.id) || [],
417
- }));
363
+ // Map tags to posts (spread to avoid circular references)
364
+ let result = posts.map((post) => {
365
+ const postTags = (post.postTag || [])
366
+ .map((pt) => {
367
+ const tag = tagMap.get(pt.tagId);
368
+ return tag ? { ...tag } : undefined;
369
+ })
370
+ .filter((tag): tag is Tag => tag !== undefined);
371
+ const { postTag: _, ...postWithoutJoin } = post;
372
+ return {
373
+ ...postWithoutJoin,
374
+ tags: postTags,
375
+ };
376
+ });
418
377
 
419
378
  if (tagFilterPostIds) {
420
379
  result = result.filter((post) => tagFilterPostIds!.has(post.id));
@@ -466,7 +425,6 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
466
425
  body: ctx.body,
467
426
  headers: ctx.headers,
468
427
  };
469
- const tagCache = createTagCache();
470
428
 
471
429
  try {
472
430
  if (hooks?.onBeforeCreatePost) {
@@ -496,7 +454,7 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
496
454
  });
497
455
 
498
456
  if (tagNames.length > 0) {
499
- const createdTags = await findOrCreateTags(tagNames, tagCache);
457
+ const createdTags = await findOrCreateTags(tagNames);
500
458
 
501
459
  await adapter.transaction(async (tx) => {
502
460
  for (const tag of createdTags) {
@@ -540,7 +498,6 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
540
498
  params: ctx.params,
541
499
  headers: ctx.headers,
542
500
  };
543
- const tagCache = createTagCache();
544
501
 
545
502
  try {
546
503
  if (hooks?.onBeforeUpdatePost) {
@@ -608,7 +565,7 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
608
565
  }
609
566
 
610
567
  if (tagNames.length > 0) {
611
- const createdTags = await findOrCreateTags(tagNames, tagCache);
568
+ const createdTags = await findOrCreateTags(tagNames);
612
569
 
613
570
  for (const tag of createdTags) {
614
571
  await tx.create<{ postId: string; tagId: string }>({
@@ -666,16 +623,9 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
666
623
  }
667
624
  }
668
625
 
669
- await adapter.transaction(async (tx) => {
670
- await tx.delete({
671
- model: "postTag",
672
- where: [{ field: "postId", value: ctx.params.id }],
673
- });
674
-
675
- await tx.delete<Post>({
676
- model: "post",
677
- where: [{ field: "id", value: ctx.params.id }],
678
- });
626
+ await adapter.delete<Post>({
627
+ model: "post",
628
+ where: [{ field: "id", value: ctx.params.id }],
679
629
  });
680
630
 
681
631
  // Lifecycle hook
@@ -703,8 +653,6 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
703
653
  async (ctx) => {
704
654
  const { query, headers } = ctx;
705
655
  const context: BlogApiContext = { query, headers };
706
- const tagCache = createTagCache();
707
- const postTagCache = createPostTagCache();
708
656
 
709
657
  try {
710
658
  if (hooks?.onBeforeListPosts) {
@@ -722,7 +670,7 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
722
670
  const date = query.date;
723
671
 
724
672
  // Get previous post (createdAt < date, newest first)
725
- const previousPost = await adapter.findMany<Post>({
673
+ const previousPosts = await adapter.findMany<PostWithPostTag>({
726
674
  model: "post",
727
675
  limit: 1,
728
676
  where: [
@@ -741,9 +689,12 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
741
689
  field: "createdAt",
742
690
  direction: "desc",
743
691
  },
692
+ join: {
693
+ postTag: true,
694
+ },
744
695
  });
745
696
 
746
- const nextPost = await adapter.findMany<Post>({
697
+ const nextPosts = await adapter.findMany<PostWithPostTag>({
747
698
  model: "post",
748
699
  limit: 1,
749
700
  where: [
@@ -762,31 +713,55 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
762
713
  field: "createdAt",
763
714
  direction: "asc",
764
715
  },
716
+ join: {
717
+ postTag: true,
718
+ },
765
719
  });
766
720
 
767
- const postIds = [
768
- ...(previousPost?.[0] ? [previousPost[0].id] : []),
769
- ...(nextPost?.[0] ? [nextPost[0].id] : []),
770
- ];
771
- const postTagsMap = await loadTagsForPosts(
772
- postIds,
773
- tagCache,
774
- postTagCache,
775
- );
721
+ // Collect unique tag IDs from joined data
722
+ const tagIds = new Set<string>();
723
+ const allPosts = [...previousPosts, ...nextPosts];
724
+ for (const post of allPosts) {
725
+ if (post.postTag) {
726
+ for (const pt of post.postTag) {
727
+ tagIds.add(pt.tagId);
728
+ }
729
+ }
730
+ }
731
+
732
+ // Fetch tags if needed
733
+ const tagMap = new Map<string, Tag>();
734
+ if (tagIds.size > 0) {
735
+ const tags = await adapter.findMany<Tag>({
736
+ model: "tag",
737
+ });
738
+ for (const tag of tags) {
739
+ if (tagIds.has(tag.id)) {
740
+ tagMap.set(tag.id, tag);
741
+ }
742
+ }
743
+ }
744
+
745
+ // Helper to map post with tags (spread to avoid circular references)
746
+ const mapPostWithTags = (post: PostWithPostTag) => {
747
+ const tags = (post.postTag || [])
748
+ .map((pt) => {
749
+ const tag = tagMap.get(pt.tagId);
750
+ return tag ? { ...tag } : undefined;
751
+ })
752
+ .filter((tag): tag is Tag => tag !== undefined);
753
+ const { postTag: _, ...postWithoutJoin } = post;
754
+ return {
755
+ ...postWithoutJoin,
756
+ tags,
757
+ };
758
+ };
776
759
 
777
760
  return {
778
- previous: previousPost?.[0]
779
- ? {
780
- ...previousPost[0],
781
- tags: postTagsMap.get(previousPost[0].id) || [],
782
- }
783
- : null,
784
- next: nextPost?.[0]
785
- ? {
786
- ...nextPost[0],
787
- tags: postTagsMap.get(nextPost[0].id) || [],
788
- }
761
+ previous: previousPosts[0]
762
+ ? mapPostWithTags(previousPosts[0])
789
763
  : null,
764
+ next: nextPosts[0] ? mapPostWithTags(nextPosts[0]) : null,
790
765
  };
791
766
  } catch (error) {
792
767
  // Error hook
@@ -71,7 +71,7 @@ export function PostPage({ slug }: { slug: string }) {
71
71
  }
72
72
 
73
73
  return (
74
- <PageWrapper className="gap-0 px-4 lg:px-4 py-0 pb-18" testId="post-page">
74
+ <PageWrapper className="gap-0 px-4 lg:px-2 py-0 pb-18" testId="post-page">
75
75
  <div className="flex items-start w-full">
76
76
  <div className="w-44 shrink-0 hidden xl:flex mr-auto" />
77
77
  <div className="flex flex-col items-center flex-1 mx-auto w-full max-w-4xl min-w-0">
@@ -63,7 +63,7 @@ export function OnThisPage({ markdown, className }: OnThisPageProps) {
63
63
  aria-label="Table of contents"
64
64
  >
65
65
  <div className="overflow-y-auto px-2">
66
- <div className="flex flex-col gap-1 p-4 pt-0 text-sm">
66
+ <div className="flex flex-col gap-1 p-2 pt-0 text-sm">
67
67
  <p className="flex items-center gap-2 font-semibold text-muted-foreground sticky top-0 h-6 text-xs">
68
68
  <TextAlignStart className="w-3 h-3" />{" "}
69
69
  {localization.BLOG_POST_ON_THIS_PAGE}
@@ -68,8 +68,8 @@ export function RecentPostsCarousel({ posts, ref }: RecentPostsCarouselProps) {
68
68
  </CarouselItem>
69
69
  ))}
70
70
  </CarouselContent>
71
- <CarouselPrevious className="-left-4 lg:-left-12 hover:cursor-pointer" />
72
- <CarouselNext className="-right-4 lg:-right-12 hover:cursor-pointer" />
71
+ <CarouselPrevious className="-left-4 z-50 hover:cursor-pointer" />
72
+ <CarouselNext className="-right-4 z-50 hover:cursor-pointer" />
73
73
  </Carousel>
74
74
  </div>
75
75
  </>
@@ -80,10 +80,20 @@ export const blogSchema = createDbPlugin("blog", {
80
80
  postId: {
81
81
  type: "string",
82
82
  required: true,
83
+ references: {
84
+ model: "post",
85
+ field: "id",
86
+ onDelete: "cascade",
87
+ },
83
88
  },
84
89
  tagId: {
85
90
  type: "string",
86
91
  required: true,
92
+ references: {
93
+ model: "tag",
94
+ field: "id",
95
+ onDelete: "cascade",
96
+ },
87
97
  },
88
98
  },
89
99
  },
@@ -23,6 +23,13 @@ export type Tag = {
23
23
  updatedAt: Date;
24
24
  };
25
25
 
26
+ /**
27
+ * Post with joined postTag relationships from the database
28
+ */
29
+ export type PostWithPostTag = Post & {
30
+ postTag?: Array<{ postId: string; tagId: string }>;
31
+ };
32
+
26
33
  export interface SerializedPost
27
34
  extends Omit<Post, "createdAt" | "updatedAt" | "publishedAt" | "tags"> {
28
35
  tags: SerializedTag[];
@@ -35,15 +35,15 @@ interface SerializedTag extends Omit<Tag, "createdAt" | "updatedAt"> {
35
35
  }
36
36
 
37
37
  declare const createPostSchema: z.ZodObject<{
38
- title: z.ZodString;
39
38
  slug: z.ZodOptional<z.ZodString>;
40
- published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
41
- createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
42
39
  publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
40
+ createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
43
41
  updatedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
42
+ title: z.ZodString;
44
43
  content: z.ZodString;
45
44
  excerpt: z.ZodString;
46
45
  image: z.ZodOptional<z.ZodString>;
46
+ published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
47
47
  tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
48
48
  name: z.ZodString;
49
49
  }, z.core.$strip>, z.ZodObject<{
@@ -35,15 +35,15 @@ interface SerializedTag extends Omit<Tag, "createdAt" | "updatedAt"> {
35
35
  }
36
36
 
37
37
  declare const createPostSchema: z.ZodObject<{
38
- title: z.ZodString;
39
38
  slug: z.ZodOptional<z.ZodString>;
40
- published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
41
- createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
42
39
  publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
40
+ createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
43
41
  updatedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
42
+ title: z.ZodString;
44
43
  content: z.ZodString;
45
44
  excerpt: z.ZodString;
46
45
  image: z.ZodOptional<z.ZodString>;
46
+ published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
47
47
  tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
48
48
  name: z.ZodString;
49
49
  }, z.core.$strip>, z.ZodObject<{
@@ -35,15 +35,15 @@ interface SerializedTag extends Omit<Tag, "createdAt" | "updatedAt"> {
35
35
  }
36
36
 
37
37
  declare const createPostSchema: z.ZodObject<{
38
- title: z.ZodString;
39
38
  slug: z.ZodOptional<z.ZodString>;
40
- published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
41
- createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
42
39
  publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
40
+ createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
43
41
  updatedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
42
+ title: z.ZodString;
44
43
  content: z.ZodString;
45
44
  excerpt: z.ZodString;
46
45
  image: z.ZodOptional<z.ZodString>;
46
+ published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
47
47
  tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
48
48
  name: z.ZodString;
49
49
  }, z.core.$strip>, z.ZodObject<{