@btst/stack 1.2.1 → 1.2.2

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.
@@ -482,12 +482,15 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
482
482
  }
483
483
  }
484
484
  const date = query.date;
485
- const targetTime = new Date(date).getTime();
486
- const WINDOW_SIZE = 100;
487
- const allPosts = await adapter.findMany({
485
+ const previousPost = await adapter.findMany({
488
486
  model: "post",
489
- limit: WINDOW_SIZE,
487
+ limit: 1,
490
488
  where: [
489
+ {
490
+ field: "createdAt",
491
+ value: date,
492
+ operator: "lt"
493
+ },
491
494
  {
492
495
  field: "published",
493
496
  value: true,
@@ -499,27 +502,29 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
499
502
  direction: "desc"
500
503
  }
501
504
  });
502
- const sortedPosts = allPosts.sort((a, b) => {
503
- const timeA = new Date(a.createdAt).getTime();
504
- const timeB = new Date(b.createdAt).getTime();
505
- return timeB - timeA;
506
- });
507
- let previousPost = null;
508
- let nextPost = null;
509
- for (let i = 0; i < sortedPosts.length; i++) {
510
- const post = sortedPosts[i];
511
- if (!post) continue;
512
- const postTime = new Date(post.createdAt).getTime();
513
- if (postTime > targetTime) {
514
- nextPost = post;
515
- } else if (postTime < targetTime) {
516
- previousPost = post;
517
- break;
505
+ const nextPost = await adapter.findMany({
506
+ model: "post",
507
+ limit: 1,
508
+ where: [
509
+ {
510
+ field: "createdAt",
511
+ value: date,
512
+ operator: "gt"
513
+ },
514
+ {
515
+ field: "published",
516
+ value: true,
517
+ operator: "eq"
518
+ }
519
+ ],
520
+ sortBy: {
521
+ field: "createdAt",
522
+ direction: "asc"
518
523
  }
519
- }
524
+ });
520
525
  const postIds = [
521
- ...previousPost ? [previousPost.id] : [],
522
- ...nextPost ? [nextPost.id] : []
526
+ ...previousPost?.[0] ? [previousPost[0].id] : [],
527
+ ...nextPost?.[0] ? [nextPost[0].id] : []
523
528
  ];
524
529
  const postTagsMap = await loadTagsForPosts(
525
530
  postIds,
@@ -527,13 +532,13 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
527
532
  postTagCache
528
533
  );
529
534
  return {
530
- previous: previousPost ? {
531
- ...previousPost,
532
- tags: postTagsMap.get(previousPost.id) || []
535
+ previous: previousPost?.[0] ? {
536
+ ...previousPost[0],
537
+ tags: postTagsMap.get(previousPost[0].id) || []
533
538
  } : null,
534
- next: nextPost ? {
535
- ...nextPost,
536
- tags: postTagsMap.get(nextPost.id) || []
539
+ next: nextPost?.[0] ? {
540
+ ...nextPost[0],
541
+ tags: postTagsMap.get(nextPost[0].id) || []
537
542
  } : null
538
543
  };
539
544
  } catch (error) {
@@ -480,12 +480,15 @@ const blogBackendPlugin = (hooks) => defineBackendPlugin({
480
480
  }
481
481
  }
482
482
  const date = query.date;
483
- const targetTime = new Date(date).getTime();
484
- const WINDOW_SIZE = 100;
485
- const allPosts = await adapter.findMany({
483
+ const previousPost = await adapter.findMany({
486
484
  model: "post",
487
- limit: WINDOW_SIZE,
485
+ limit: 1,
488
486
  where: [
487
+ {
488
+ field: "createdAt",
489
+ value: date,
490
+ operator: "lt"
491
+ },
489
492
  {
490
493
  field: "published",
491
494
  value: true,
@@ -497,27 +500,29 @@ const blogBackendPlugin = (hooks) => defineBackendPlugin({
497
500
  direction: "desc"
498
501
  }
499
502
  });
500
- const sortedPosts = allPosts.sort((a, b) => {
501
- const timeA = new Date(a.createdAt).getTime();
502
- const timeB = new Date(b.createdAt).getTime();
503
- return timeB - timeA;
504
- });
505
- let previousPost = null;
506
- let nextPost = null;
507
- for (let i = 0; i < sortedPosts.length; i++) {
508
- const post = sortedPosts[i];
509
- if (!post) continue;
510
- const postTime = new Date(post.createdAt).getTime();
511
- if (postTime > targetTime) {
512
- nextPost = post;
513
- } else if (postTime < targetTime) {
514
- previousPost = post;
515
- break;
503
+ const nextPost = await adapter.findMany({
504
+ model: "post",
505
+ limit: 1,
506
+ where: [
507
+ {
508
+ field: "createdAt",
509
+ value: date,
510
+ operator: "gt"
511
+ },
512
+ {
513
+ field: "published",
514
+ value: true,
515
+ operator: "eq"
516
+ }
517
+ ],
518
+ sortBy: {
519
+ field: "createdAt",
520
+ direction: "asc"
516
521
  }
517
- }
522
+ });
518
523
  const postIds = [
519
- ...previousPost ? [previousPost.id] : [],
520
- ...nextPost ? [nextPost.id] : []
524
+ ...previousPost?.[0] ? [previousPost[0].id] : [],
525
+ ...nextPost?.[0] ? [nextPost[0].id] : []
521
526
  ];
522
527
  const postTagsMap = await loadTagsForPosts(
523
528
  postIds,
@@ -525,13 +530,13 @@ const blogBackendPlugin = (hooks) => defineBackendPlugin({
525
530
  postTagCache
526
531
  );
527
532
  return {
528
- previous: previousPost ? {
529
- ...previousPost,
530
- tags: postTagsMap.get(previousPost.id) || []
533
+ previous: previousPost?.[0] ? {
534
+ ...previousPost[0],
535
+ tags: postTagsMap.get(previousPost[0].id) || []
531
536
  } : null,
532
- next: nextPost ? {
533
- ...nextPost,
534
- tags: postTagsMap.get(nextPost.id) || []
537
+ next: nextPost?.[0] ? {
538
+ ...nextPost[0],
539
+ tags: postTagsMap.get(nextPost[0].id) || []
535
540
  } : null
536
541
  };
537
542
  } catch (error) {
@@ -477,6 +477,10 @@ const blogClientPlugin = (config) => client.defineClientPlugin({
477
477
  if (page.length < limit) break;
478
478
  offset += limit;
479
479
  }
480
+ const tagsRes = await client$1("/tags", {
481
+ method: "GET"
482
+ });
483
+ const tags = tagsRes.data ?? [];
480
484
  const getLastModified = (p) => {
481
485
  const dates = [p.updatedAt, p.publishedAt, p.createdAt].filter(
482
486
  Boolean
@@ -499,6 +503,12 @@ const blogClientPlugin = (config) => client.defineClientPlugin({
499
503
  lastModified: getLastModified(p),
500
504
  changeFrequency: "monthly",
501
505
  priority: 0.6
506
+ })),
507
+ ...tags.map((t) => ({
508
+ url: `${origin}/blog/tag/${t.slug}`,
509
+ lastModified: t.updatedAt ? new Date(t.updatedAt) : void 0,
510
+ changeFrequency: "weekly",
511
+ priority: 0.5
502
512
  }))
503
513
  ];
504
514
  return entries;
@@ -475,6 +475,10 @@ const blogClientPlugin = (config) => defineClientPlugin({
475
475
  if (page.length < limit) break;
476
476
  offset += limit;
477
477
  }
478
+ const tagsRes = await client("/tags", {
479
+ method: "GET"
480
+ });
481
+ const tags = tagsRes.data ?? [];
478
482
  const getLastModified = (p) => {
479
483
  const dates = [p.updatedAt, p.publishedAt, p.createdAt].filter(
480
484
  Boolean
@@ -497,6 +501,12 @@ const blogClientPlugin = (config) => defineClientPlugin({
497
501
  lastModified: getLastModified(p),
498
502
  changeFrequency: "monthly",
499
503
  priority: 0.6
504
+ })),
505
+ ...tags.map((t) => ({
506
+ url: `${origin}/blog/tag/${t.slug}`,
507
+ lastModified: t.updatedAt ? new Date(t.updatedAt) : void 0,
508
+ changeFrequency: "weekly",
509
+ priority: 0.5
500
510
  }))
501
511
  ];
502
512
  return entries;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btst/stack",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "A composable, plugin-based library for building full-stack applications.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -720,27 +720,17 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
720
720
  }
721
721
 
722
722
  const date = query.date;
723
- const targetTime = new Date(date).getTime();
724
-
725
- // Window-based approach for finding next/previous posts
726
- // This avoids relying on comparison operators (lt/gt) which may not be
727
- // consistently implemented across all database adapters (e.g., Drizzle).
728
- //
729
- // Strategy:
730
- // 1. Fetch a window of recent published posts (sorted by date DESC)
731
- // 2. Filter in memory to find posts immediately before/after target date
732
- //
733
- // Trade-offs:
734
- // - Works reliably across all adapters (only uses eq operator and sorting)
735
- // - Efficient for typical blog sizes (100 most recent posts)
736
- // - Limitation: If target post is outside the window, we may not find neighbors
737
- // (acceptable for typical blog navigation where users browse recent content)
738
- const WINDOW_SIZE = 100;
739
-
740
- const allPosts = await adapter.findMany<Post>({
723
+
724
+ // Get previous post (createdAt < date, newest first)
725
+ const previousPost = await adapter.findMany<Post>({
741
726
  model: "post",
742
- limit: WINDOW_SIZE,
727
+ limit: 1,
743
728
  where: [
729
+ {
730
+ field: "createdAt",
731
+ value: date,
732
+ operator: "lt" as const,
733
+ },
744
734
  {
745
735
  field: "published",
746
736
  value: true,
@@ -753,40 +743,30 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
753
743
  },
754
744
  });
755
745
 
756
- // Sort posts by createdAt descending (newest first)
757
- const sortedPosts = allPosts.sort((a, b) => {
758
- const timeA = new Date(a.createdAt).getTime();
759
- const timeB = new Date(b.createdAt).getTime();
760
- return timeB - timeA;
746
+ const nextPost = await adapter.findMany<Post>({
747
+ model: "post",
748
+ limit: 1,
749
+ where: [
750
+ {
751
+ field: "createdAt",
752
+ value: date,
753
+ operator: "gt" as const,
754
+ },
755
+ {
756
+ field: "published",
757
+ value: true,
758
+ operator: "eq" as const,
759
+ },
760
+ ],
761
+ sortBy: {
762
+ field: "createdAt",
763
+ direction: "asc",
764
+ },
761
765
  });
762
766
 
763
- // Find posts immediately before and after the target date
764
- // In DESC sorted array: newer posts come before, older posts come after
765
- let previousPost: Post | null = null;
766
- let nextPost: Post | null = null;
767
-
768
- for (let i = 0; i < sortedPosts.length; i++) {
769
- const post = sortedPosts[i];
770
- if (!post) continue;
771
-
772
- const postTime = new Date(post.createdAt).getTime();
773
-
774
- if (postTime > targetTime) {
775
- // This post is newer than target - it's a next post candidate
776
- // Keep the last one we find (closest to target date)
777
- nextPost = post;
778
- } else if (postTime < targetTime) {
779
- // This is the first post older than target - this is the previous post
780
- previousPost = post;
781
- // We've found both neighbors, no need to continue
782
- break;
783
- }
784
- // Skip posts with exactly the same timestamp (the current post itself)
785
- }
786
-
787
767
  const postIds = [
788
- ...(previousPost ? [previousPost.id] : []),
789
- ...(nextPost ? [nextPost.id] : []),
768
+ ...(previousPost?.[0] ? [previousPost[0].id] : []),
769
+ ...(nextPost?.[0] ? [nextPost[0].id] : []),
790
770
  ];
791
771
  const postTagsMap = await loadTagsForPosts(
792
772
  postIds,
@@ -795,16 +775,16 @@ export const blogBackendPlugin = (hooks?: BlogBackendHooks) =>
795
775
  );
796
776
 
797
777
  return {
798
- previous: previousPost
778
+ previous: previousPost?.[0]
799
779
  ? {
800
- ...previousPost,
801
- tags: postTagsMap.get(previousPost.id) || [],
780
+ ...previousPost[0],
781
+ tags: postTagsMap.get(previousPost[0].id) || [],
802
782
  }
803
783
  : null,
804
- next: nextPost
784
+ next: nextPost?.[0]
805
785
  ? {
806
- ...nextPost,
807
- tags: postTagsMap.get(nextPost.id) || [],
786
+ ...nextPost[0],
787
+ tags: postTagsMap.get(nextPost[0].id) || [],
808
788
  }
809
789
  : null,
810
790
  };
@@ -755,6 +755,12 @@ export const blogClientPlugin = (config: BlogClientConfig) =>
755
755
  offset += limit;
756
756
  }
757
757
 
758
+ // Fetch all tags
759
+ const tagsRes = await client("/tags", {
760
+ method: "GET",
761
+ });
762
+ const tags = (tagsRes.data ?? []) as unknown as SerializedTag[];
763
+
758
764
  const getLastModified = (p: SerializedPost): Date | undefined => {
759
765
  const dates = [p.updatedAt, p.publishedAt, p.createdAt].filter(
760
766
  Boolean,
@@ -784,6 +790,12 @@ export const blogClientPlugin = (config: BlogClientConfig) =>
784
790
  changeFrequency: "monthly" as const,
785
791
  priority: 0.6,
786
792
  })),
793
+ ...tags.map((t) => ({
794
+ url: `${origin}/blog/tag/${t.slug}`,
795
+ lastModified: t.updatedAt ? new Date(t.updatedAt) : undefined,
796
+ changeFrequency: "weekly" as const,
797
+ priority: 0.5,
798
+ })),
787
799
  ];
788
800
 
789
801
  return entries;