@btst/stack 1.2.1 → 1.3.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 (26) hide show
  1. package/dist/packages/better-stack/src/plugins/blog/api/plugin.cjs +117 -137
  2. package/dist/packages/better-stack/src/plugins/blog/api/plugin.mjs +117 -137
  3. package/dist/packages/better-stack/src/plugins/blog/client/plugin.cjs +10 -0
  4. package/dist/packages/better-stack/src/plugins/blog/client/plugin.mjs +10 -0
  5. package/dist/packages/better-stack/src/plugins/blog/db.cjs +12 -2
  6. package/dist/packages/better-stack/src/plugins/blog/db.mjs +12 -2
  7. package/dist/plugins/blog/api/index.d.cts +1 -1
  8. package/dist/plugins/blog/api/index.d.mts +1 -1
  9. package/dist/plugins/blog/api/index.d.ts +1 -1
  10. package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
  11. package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
  12. package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
  13. package/dist/plugins/blog/client/index.d.cts +1 -1
  14. package/dist/plugins/blog/client/index.d.mts +1 -1
  15. package/dist/plugins/blog/client/index.d.ts +1 -1
  16. package/dist/plugins/blog/query-keys.d.cts +5 -5
  17. package/dist/plugins/blog/query-keys.d.mts +5 -5
  18. package/dist/plugins/blog/query-keys.d.ts +5 -5
  19. package/package.json +3 -3
  20. package/src/plugins/blog/api/plugin.ts +139 -190
  21. package/src/plugins/blog/client/plugin.tsx +12 -0
  22. package/src/plugins/blog/db.ts +10 -0
  23. package/src/plugins/blog/types.ts +7 -0
  24. package/dist/shared/{stack.Cr2JoQdo.d.cts → stack.DLhzx1-D.d.cts} +2 -2
  25. package/dist/shared/{stack.Cr2JoQdo.d.mts → stack.DLhzx1-D.d.mts} +2 -2
  26. package/dist/shared/{stack.Cr2JoQdo.d.ts → stack.DLhzx1-D.d.ts} +2 -2
@@ -26,53 +26,7 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
26
26
  name: "blog",
27
27
  dbPlugin: db.blogSchema,
28
28
  routes: (adapter) => {
29
- const createTagCache = () => {
30
- let cache = null;
31
- return {
32
- getAllTags: async () => {
33
- if (!cache) {
34
- cache = await adapter.findMany({
35
- model: "tag"
36
- });
37
- }
38
- return cache;
39
- },
40
- invalidate: () => {
41
- cache = null;
42
- },
43
- addTag: (tag) => {
44
- if (cache) {
45
- cache.push(tag);
46
- }
47
- }
48
- };
49
- };
50
- const createPostTagCache = () => {
51
- let cache = null;
52
- const getAllPostTags = async () => {
53
- if (!cache) {
54
- cache = await adapter.findMany({
55
- model: "postTag"
56
- });
57
- }
58
- return cache;
59
- };
60
- return {
61
- getAllPostTags,
62
- invalidate: () => {
63
- cache = null;
64
- },
65
- getByTagId: async (tagId) => {
66
- const allPostTags = await getAllPostTags();
67
- return allPostTags.filter((pt) => pt.tagId === tagId);
68
- },
69
- getByPostId: async (postId) => {
70
- const allPostTags = await getAllPostTags();
71
- return allPostTags.filter((pt) => pt.postId === postId);
72
- }
73
- };
74
- };
75
- const findOrCreateTags = async (tagInputs, tagCache) => {
29
+ const findOrCreateTags = async (tagInputs) => {
76
30
  if (tagInputs.length === 0) return [];
77
31
  const normalizeTagName = (name) => {
78
32
  return name.trim();
@@ -95,7 +49,9 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
95
49
  if (tagsToFindOrCreate.length === 0) {
96
50
  return tagsWithIds;
97
51
  }
98
- const allTags = await tagCache.getAllTags();
52
+ const allTags = await adapter.findMany({
53
+ model: "tag"
54
+ });
99
55
  const tagMapBySlug = /* @__PURE__ */ new Map();
100
56
  for (const tag of allTags) {
101
57
  tagMapBySlug.set(tag.slug, tag);
@@ -128,33 +84,9 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
128
84
  }
129
85
  });
130
86
  createdTags.push(newTag);
131
- tagCache.addTag(newTag);
132
87
  }
133
88
  return [...tagsWithIds, ...foundTags, ...createdTags];
134
89
  };
135
- const loadTagsForPosts = async (postIds, tagCache, postTagCache) => {
136
- if (postIds.length === 0) return /* @__PURE__ */ new Map();
137
- const allPostTags = await postTagCache.getAllPostTags();
138
- const relevantPostTags = allPostTags.filter(
139
- (pt) => postIds.includes(pt.postId)
140
- );
141
- const tagIds = [...new Set(relevantPostTags.map((pt) => pt.tagId))];
142
- if (tagIds.length === 0) return /* @__PURE__ */ new Map();
143
- const allTags = await tagCache.getAllTags();
144
- const tagMap = /* @__PURE__ */ new Map();
145
- for (const tag of allTags) {
146
- tagMap.set(tag.id, tag);
147
- }
148
- const postTagsMap = /* @__PURE__ */ new Map();
149
- for (const postTag of relevantPostTags) {
150
- const tag = tagMap.get(postTag.tagId);
151
- if (tag) {
152
- const existing = postTagsMap.get(postTag.postId) || [];
153
- postTagsMap.set(postTag.postId, [...existing, { ...tag }]);
154
- }
155
- }
156
- return postTagsMap;
157
- };
158
90
  const listPosts = api.createEndpoint(
159
91
  "/posts",
160
92
  {
@@ -164,8 +96,6 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
164
96
  async (ctx) => {
165
97
  const { query, headers } = ctx;
166
98
  const context = { query, headers };
167
- const tagCache = createTagCache();
168
- const postTagCache = createPostTagCache();
169
99
  try {
170
100
  if (hooks?.onBeforeListPosts) {
171
101
  const canList = await hooks.onBeforeListPosts(query, context);
@@ -177,12 +107,29 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
177
107
  }
178
108
  let tagFilterPostIds = null;
179
109
  if (query.tagSlug) {
180
- const allTags = await tagCache.getAllTags();
181
- const tag = allTags.find((t) => t.slug === query.tagSlug);
110
+ const tag = await adapter.findOne({
111
+ model: "tag",
112
+ where: [
113
+ {
114
+ field: "slug",
115
+ value: query.tagSlug,
116
+ operator: "eq"
117
+ }
118
+ ]
119
+ });
182
120
  if (!tag) {
183
121
  return [];
184
122
  }
185
- const postTags = await postTagCache.getByTagId(tag.id);
123
+ const postTags = await adapter.findMany({
124
+ model: "postTag",
125
+ where: [
126
+ {
127
+ field: "tagId",
128
+ value: tag.id,
129
+ operator: "eq"
130
+ }
131
+ ]
132
+ });
186
133
  tagFilterPostIds = new Set(postTags.map((pt) => pt.postId));
187
134
  if (tagFilterPostIds.size === 0) {
188
135
  return [];
@@ -211,17 +158,36 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
211
158
  sortBy: {
212
159
  field: "createdAt",
213
160
  direction: "desc"
161
+ },
162
+ join: {
163
+ postTag: true
164
+ }
165
+ });
166
+ const tagIds = /* @__PURE__ */ new Set();
167
+ for (const post of posts) {
168
+ if (post.postTag) {
169
+ for (const pt of post.postTag) {
170
+ tagIds.add(pt.tagId);
171
+ }
214
172
  }
173
+ }
174
+ const tags = tagIds.size > 0 ? await adapter.findMany({
175
+ model: "tag"
176
+ }) : [];
177
+ const tagMap = /* @__PURE__ */ new Map();
178
+ for (const tag of tags) {
179
+ if (tagIds.has(tag.id)) {
180
+ tagMap.set(tag.id, tag);
181
+ }
182
+ }
183
+ let result = posts.map((post) => {
184
+ const postTags = (post.postTag || []).map((pt) => tagMap.get(pt.tagId)).filter((tag) => tag !== void 0);
185
+ const { postTag: _, ...postWithoutJoin } = post;
186
+ return {
187
+ ...postWithoutJoin,
188
+ tags: postTags
189
+ };
215
190
  });
216
- const postTagsMap = await loadTagsForPosts(
217
- posts.map((post) => post.id),
218
- tagCache,
219
- postTagCache
220
- );
221
- let result = posts.map((post) => ({
222
- ...post,
223
- tags: postTagsMap.get(post.id) || []
224
- }));
225
191
  if (tagFilterPostIds) {
226
192
  result = result.filter((post) => tagFilterPostIds.has(post.id));
227
193
  }
@@ -262,7 +228,6 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
262
228
  body: ctx.body,
263
229
  headers: ctx.headers
264
230
  };
265
- const tagCache = createTagCache();
266
231
  try {
267
232
  if (hooks?.onBeforeCreatePost) {
268
233
  const canCreate = await hooks.onBeforeCreatePost(
@@ -288,7 +253,7 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
288
253
  }
289
254
  });
290
255
  if (tagNames.length > 0) {
291
- const createdTags = await findOrCreateTags(tagNames, tagCache);
256
+ const createdTags = await findOrCreateTags(tagNames);
292
257
  await adapter.transaction(async (tx) => {
293
258
  for (const tag of createdTags) {
294
259
  await tx.create({
@@ -328,7 +293,6 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
328
293
  params: ctx.params,
329
294
  headers: ctx.headers
330
295
  };
331
- const tagCache = createTagCache();
332
296
  try {
333
297
  if (hooks?.onBeforeUpdatePost) {
334
298
  const canUpdate = await hooks.onBeforeUpdatePost(
@@ -386,7 +350,7 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
386
350
  });
387
351
  }
388
352
  if (tagNames.length > 0) {
389
- const createdTags = await findOrCreateTags(tagNames, tagCache);
353
+ const createdTags = await findOrCreateTags(tagNames);
390
354
  for (const tag of createdTags) {
391
355
  await tx.create({
392
356
  model: "postTag",
@@ -436,15 +400,9 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
436
400
  });
437
401
  }
438
402
  }
439
- await adapter.transaction(async (tx) => {
440
- await tx.delete({
441
- model: "postTag",
442
- where: [{ field: "postId", value: ctx.params.id }]
443
- });
444
- await tx.delete({
445
- model: "post",
446
- where: [{ field: "id", value: ctx.params.id }]
447
- });
403
+ await adapter.delete({
404
+ model: "post",
405
+ where: [{ field: "id", value: ctx.params.id }]
448
406
  });
449
407
  if (hooks?.onPostDeleted) {
450
408
  await hooks.onPostDeleted(ctx.params.id, context);
@@ -467,8 +425,6 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
467
425
  async (ctx) => {
468
426
  const { query, headers } = ctx;
469
427
  const context = { query, headers };
470
- const tagCache = createTagCache();
471
- const postTagCache = createPostTagCache();
472
428
  try {
473
429
  if (hooks?.onBeforeListPosts) {
474
430
  const canList = await hooks.onBeforeListPosts(
@@ -482,12 +438,15 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
482
438
  }
483
439
  }
484
440
  const date = query.date;
485
- const targetTime = new Date(date).getTime();
486
- const WINDOW_SIZE = 100;
487
- const allPosts = await adapter.findMany({
441
+ const previousPosts = await adapter.findMany({
488
442
  model: "post",
489
- limit: WINDOW_SIZE,
443
+ limit: 1,
490
444
  where: [
445
+ {
446
+ field: "createdAt",
447
+ value: date,
448
+ operator: "lt"
449
+ },
491
450
  {
492
451
  field: "published",
493
452
  value: true,
@@ -497,44 +456,65 @@ const blogBackendPlugin = (hooks) => api.defineBackendPlugin({
497
456
  sortBy: {
498
457
  field: "createdAt",
499
458
  direction: "desc"
459
+ },
460
+ join: {
461
+ postTag: true
500
462
  }
501
463
  });
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;
464
+ const nextPosts = await adapter.findMany({
465
+ model: "post",
466
+ limit: 1,
467
+ where: [
468
+ {
469
+ field: "createdAt",
470
+ value: date,
471
+ operator: "gt"
472
+ },
473
+ {
474
+ field: "published",
475
+ value: true,
476
+ operator: "eq"
477
+ }
478
+ ],
479
+ sortBy: {
480
+ field: "createdAt",
481
+ direction: "asc"
482
+ },
483
+ join: {
484
+ postTag: true
485
+ }
506
486
  });
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;
487
+ const tagIds = /* @__PURE__ */ new Set();
488
+ const allPosts = [...previousPosts, ...nextPosts];
489
+ for (const post of allPosts) {
490
+ if (post.postTag) {
491
+ for (const pt of post.postTag) {
492
+ tagIds.add(pt.tagId);
493
+ }
494
+ }
495
+ }
496
+ const tagMap = /* @__PURE__ */ new Map();
497
+ if (tagIds.size > 0) {
498
+ const tags = await adapter.findMany({
499
+ model: "tag"
500
+ });
501
+ for (const tag of tags) {
502
+ if (tagIds.has(tag.id)) {
503
+ tagMap.set(tag.id, tag);
504
+ }
518
505
  }
519
506
  }
520
- const postIds = [
521
- ...previousPost ? [previousPost.id] : [],
522
- ...nextPost ? [nextPost.id] : []
523
- ];
524
- const postTagsMap = await loadTagsForPosts(
525
- postIds,
526
- tagCache,
527
- postTagCache
528
- );
507
+ const mapPostWithTags = (post) => {
508
+ const tags = (post.postTag || []).map((pt) => tagMap.get(pt.tagId)).filter((tag) => tag !== void 0);
509
+ const { postTag: _, ...postWithoutJoin } = post;
510
+ return {
511
+ ...postWithoutJoin,
512
+ tags
513
+ };
514
+ };
529
515
  return {
530
- previous: previousPost ? {
531
- ...previousPost,
532
- tags: postTagsMap.get(previousPost.id) || []
533
- } : null,
534
- next: nextPost ? {
535
- ...nextPost,
536
- tags: postTagsMap.get(nextPost.id) || []
537
- } : null
516
+ previous: previousPosts[0] ? mapPostWithTags(previousPosts[0]) : null,
517
+ next: nextPosts[0] ? mapPostWithTags(nextPosts[0]) : null
538
518
  };
539
519
  } catch (error) {
540
520
  if (hooks?.onListPostsError) {