@atproto/bsky 0.0.16 → 0.0.17

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 (109) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/cache/read-through.d.ts +30 -0
  3. package/dist/config.d.ts +18 -0
  4. package/dist/context.d.ts +6 -6
  5. package/dist/daemon/config.d.ts +15 -0
  6. package/dist/daemon/context.d.ts +15 -0
  7. package/dist/daemon/index.d.ts +23 -0
  8. package/dist/daemon/logger.d.ts +3 -0
  9. package/dist/daemon/notifications.d.ts +18 -0
  10. package/dist/daemon/services.d.ts +11 -0
  11. package/dist/db/database-schema.d.ts +1 -2
  12. package/dist/db/index.js +16 -1
  13. package/dist/db/index.js.map +3 -3
  14. package/dist/db/migrations/20231205T000257238Z-remove-did-cache.d.ts +3 -0
  15. package/dist/db/migrations/index.d.ts +1 -0
  16. package/dist/did-cache.d.ts +10 -7
  17. package/dist/index.d.ts +4 -0
  18. package/dist/index.js +1917 -934
  19. package/dist/index.js.map +3 -3
  20. package/dist/indexer/context.d.ts +2 -0
  21. package/dist/indexer/index.d.ts +1 -0
  22. package/dist/lexicon/index.d.ts +12 -0
  23. package/dist/lexicon/lexicons.d.ts +134 -0
  24. package/dist/lexicon/types/com/atproto/admin/deleteAccount.d.ts +25 -0
  25. package/dist/lexicon/types/com/atproto/temp/importRepo.d.ts +32 -0
  26. package/dist/lexicon/types/com/atproto/temp/pushBlob.d.ts +25 -0
  27. package/dist/lexicon/types/com/atproto/temp/transferAccount.d.ts +42 -0
  28. package/dist/logger.d.ts +1 -0
  29. package/dist/redis.d.ts +10 -1
  30. package/dist/services/actor/index.d.ts +18 -4
  31. package/dist/services/actor/views.d.ts +4 -3
  32. package/dist/services/feed/index.d.ts +6 -4
  33. package/dist/services/feed/views.d.ts +5 -4
  34. package/dist/services/index.d.ts +3 -7
  35. package/dist/services/label/index.d.ts +10 -4
  36. package/dist/services/moderation/index.d.ts +0 -1
  37. package/dist/services/types.d.ts +3 -0
  38. package/dist/services/util/notification.d.ts +5 -0
  39. package/dist/services/util/post.d.ts +6 -6
  40. package/dist/util/retry.d.ts +1 -6
  41. package/package.json +5 -5
  42. package/src/cache/read-through.ts +151 -0
  43. package/src/config.ts +90 -1
  44. package/src/context.ts +7 -7
  45. package/src/daemon/config.ts +60 -0
  46. package/src/daemon/context.ts +27 -0
  47. package/src/daemon/index.ts +78 -0
  48. package/src/daemon/logger.ts +6 -0
  49. package/src/daemon/notifications.ts +54 -0
  50. package/src/daemon/services.ts +22 -0
  51. package/src/db/database-schema.ts +0 -2
  52. package/src/db/migrations/20231205T000257238Z-remove-did-cache.ts +14 -0
  53. package/src/db/migrations/index.ts +1 -0
  54. package/src/did-cache.ts +33 -56
  55. package/src/feed-gen/index.ts +0 -4
  56. package/src/index.ts +55 -16
  57. package/src/indexer/context.ts +5 -0
  58. package/src/indexer/index.ts +10 -7
  59. package/src/lexicon/index.ts +50 -0
  60. package/src/lexicon/lexicons.ts +156 -0
  61. package/src/lexicon/types/com/atproto/admin/deleteAccount.ts +38 -0
  62. package/src/lexicon/types/com/atproto/temp/importRepo.ts +45 -0
  63. package/src/lexicon/types/com/atproto/temp/pushBlob.ts +39 -0
  64. package/src/lexicon/types/com/atproto/temp/transferAccount.ts +62 -0
  65. package/src/logger.ts +2 -0
  66. package/src/redis.ts +43 -3
  67. package/src/services/actor/index.ts +55 -7
  68. package/src/services/actor/views.ts +13 -7
  69. package/src/services/feed/index.ts +27 -13
  70. package/src/services/feed/views.ts +20 -10
  71. package/src/services/index.ts +14 -14
  72. package/src/services/indexing/index.ts +7 -10
  73. package/src/services/indexing/plugins/post.ts +13 -0
  74. package/src/services/label/index.ts +66 -22
  75. package/src/services/moderation/index.ts +1 -1
  76. package/src/services/moderation/status.ts +1 -4
  77. package/src/services/types.ts +4 -0
  78. package/src/services/util/notification.ts +70 -0
  79. package/src/util/retry.ts +1 -44
  80. package/tests/admin/get-repo.test.ts +5 -3
  81. package/tests/admin/moderation.test.ts +2 -2
  82. package/tests/admin/repo-search.test.ts +1 -0
  83. package/tests/algos/hot-classic.test.ts +1 -2
  84. package/tests/auth.test.ts +1 -1
  85. package/tests/auto-moderator/labeler.test.ts +19 -20
  86. package/tests/auto-moderator/takedowns.test.ts +16 -10
  87. package/tests/blob-resolver.test.ts +4 -2
  88. package/tests/daemon.test.ts +191 -0
  89. package/tests/did-cache.test.ts +20 -5
  90. package/tests/handle-invalidation.test.ts +1 -5
  91. package/tests/indexing.test.ts +20 -13
  92. package/tests/redis-cache.test.ts +231 -0
  93. package/tests/seeds/basic.ts +3 -0
  94. package/tests/subscription/repo.test.ts +4 -7
  95. package/tests/views/profile.test.ts +0 -1
  96. package/tests/views/thread.test.ts +73 -78
  97. package/tests/views/threadgating.test.ts +38 -0
  98. package/dist/db/tables/did-cache.d.ts +0 -10
  99. package/dist/feed-gen/best-of-follows.d.ts +0 -29
  100. package/dist/feed-gen/whats-hot.d.ts +0 -29
  101. package/dist/feed-gen/with-friends.d.ts +0 -3
  102. package/dist/label-cache.d.ts +0 -19
  103. package/src/db/tables/did-cache.ts +0 -13
  104. package/src/feed-gen/best-of-follows.ts +0 -77
  105. package/src/feed-gen/whats-hot.ts +0 -101
  106. package/src/feed-gen/with-friends.ts +0 -43
  107. package/src/label-cache.ts +0 -90
  108. package/tests/algos/whats-hot.test.ts +0 -118
  109. package/tests/algos/with-friends.test.ts +0 -145
@@ -2,28 +2,28 @@ import DatabaseSchema from '../../db/database-schema';
2
2
  export declare const getDescendentsQb: (db: DatabaseSchema, opts: {
3
3
  uri: string;
4
4
  depth: number;
5
- }) => import("kysely/dist/cjs/parser/with-parser").QueryCreatorWithCommonTableExpression<import("../../db/database-schema").DatabaseSchemaType, "descendent(uri, depth)", (cte: import("kysely").QueryCreator<import("../../db/tables/duplicate-record").PartialDB & import("../../db/tables/profile").PartialDB & import("../../db/tables/profile-agg").PartialDB & import("../../db/tables/post").PartialDB & import("../../db/tables/post-embed").PartialDB & import("../../db/tables/post-agg").PartialDB & import("../../db/tables/repost").PartialDB & import("../../db/tables/thread-gate").PartialDB & import("../../db/tables/feed-item").PartialDB & import("../../db/tables/follow").PartialDB & import("../../db/tables/like").PartialDB & import("../../db/tables/list").PartialDB & import("../../db/tables/list-item").PartialDB & import("../../db/tables/list-mute").PartialDB & import("../../db/tables/list-block").PartialDB & import("../../db/tables/mute").PartialDB & import("../../db/tables/actor-block").PartialDB & import("../../db/tables/feed-generator").PartialDB & import("../../db/tables/subscription").PartialDB & import("../../db/tables/actor").PartialDB & import("../../db/tables/actor-state").PartialDB & import("../../db/tables/actor-sync").PartialDB & import("../../db/tables/record").PartialDB & import("../../db/tables/notification").PartialDB & import("../../db/tables/notification-push-token").PartialDB & import("../../db/tables/did-cache").PartialDB & import("../../db/tables/moderation").PartialDB & import("../../db/tables/label").PartialDB & import("../../db/tables/algo").PartialDB & import("../../db/tables/view-param").PartialDB & import("../../db/tables/suggested-follow").PartialDB & import("../../db/tables/suggested-feed").PartialDB & Record<"descendent", {
5
+ }) => import("kysely/dist/cjs/parser/with-parser").QueryCreatorWithCommonTableExpression<import("../../db/database-schema").DatabaseSchemaType, "descendent(uri, depth)", (cte: import("kysely").QueryCreator<import("../../db/tables/duplicate-record").PartialDB & import("../../db/tables/profile").PartialDB & import("../../db/tables/profile-agg").PartialDB & import("../../db/tables/post").PartialDB & import("../../db/tables/post-embed").PartialDB & import("../../db/tables/post-agg").PartialDB & import("../../db/tables/repost").PartialDB & import("../../db/tables/thread-gate").PartialDB & import("../../db/tables/feed-item").PartialDB & import("../../db/tables/follow").PartialDB & import("../../db/tables/like").PartialDB & import("../../db/tables/list").PartialDB & import("../../db/tables/list-item").PartialDB & import("../../db/tables/list-mute").PartialDB & import("../../db/tables/list-block").PartialDB & import("../../db/tables/mute").PartialDB & import("../../db/tables/actor-block").PartialDB & import("../../db/tables/feed-generator").PartialDB & import("../../db/tables/subscription").PartialDB & import("../../db/tables/actor").PartialDB & import("../../db/tables/actor-state").PartialDB & import("../../db/tables/actor-sync").PartialDB & import("../../db/tables/record").PartialDB & import("../../db/tables/notification").PartialDB & import("../../db/tables/notification-push-token").PartialDB & import("../../db/tables/moderation").PartialDB & import("../../db/tables/label").PartialDB & import("../../db/tables/algo").PartialDB & import("../../db/tables/view-param").PartialDB & import("../../db/tables/suggested-follow").PartialDB & import("../../db/tables/suggested-feed").PartialDB & Record<"descendent", {
6
6
  uri: any;
7
7
  depth: any;
8
- }>>) => import("kysely").SelectQueryBuilder<import("kysely/dist/cjs/parser/table-parser").From<import("../../db/tables/duplicate-record").PartialDB & import("../../db/tables/profile").PartialDB & import("../../db/tables/profile-agg").PartialDB & import("../../db/tables/post").PartialDB & import("../../db/tables/post-embed").PartialDB & import("../../db/tables/post-agg").PartialDB & import("../../db/tables/repost").PartialDB & import("../../db/tables/thread-gate").PartialDB & import("../../db/tables/feed-item").PartialDB & import("../../db/tables/follow").PartialDB & import("../../db/tables/like").PartialDB & import("../../db/tables/list").PartialDB & import("../../db/tables/list-item").PartialDB & import("../../db/tables/list-mute").PartialDB & import("../../db/tables/list-block").PartialDB & import("../../db/tables/mute").PartialDB & import("../../db/tables/actor-block").PartialDB & import("../../db/tables/feed-generator").PartialDB & import("../../db/tables/subscription").PartialDB & import("../../db/tables/actor").PartialDB & import("../../db/tables/actor-state").PartialDB & import("../../db/tables/actor-sync").PartialDB & import("../../db/tables/record").PartialDB & import("../../db/tables/notification").PartialDB & import("../../db/tables/notification-push-token").PartialDB & import("../../db/tables/did-cache").PartialDB & import("../../db/tables/moderation").PartialDB & import("../../db/tables/label").PartialDB & import("../../db/tables/algo").PartialDB & import("../../db/tables/view-param").PartialDB & import("../../db/tables/suggested-follow").PartialDB & import("../../db/tables/suggested-feed").PartialDB & Record<"descendent", {
8
+ }>>) => import("kysely").SelectQueryBuilder<import("kysely/dist/cjs/parser/table-parser").From<import("../../db/tables/duplicate-record").PartialDB & import("../../db/tables/profile").PartialDB & import("../../db/tables/profile-agg").PartialDB & import("../../db/tables/post").PartialDB & import("../../db/tables/post-embed").PartialDB & import("../../db/tables/post-agg").PartialDB & import("../../db/tables/repost").PartialDB & import("../../db/tables/thread-gate").PartialDB & import("../../db/tables/feed-item").PartialDB & import("../../db/tables/follow").PartialDB & import("../../db/tables/like").PartialDB & import("../../db/tables/list").PartialDB & import("../../db/tables/list-item").PartialDB & import("../../db/tables/list-mute").PartialDB & import("../../db/tables/list-block").PartialDB & import("../../db/tables/mute").PartialDB & import("../../db/tables/actor-block").PartialDB & import("../../db/tables/feed-generator").PartialDB & import("../../db/tables/subscription").PartialDB & import("../../db/tables/actor").PartialDB & import("../../db/tables/actor-state").PartialDB & import("../../db/tables/actor-sync").PartialDB & import("../../db/tables/record").PartialDB & import("../../db/tables/notification").PartialDB & import("../../db/tables/notification-push-token").PartialDB & import("../../db/tables/moderation").PartialDB & import("../../db/tables/label").PartialDB & import("../../db/tables/algo").PartialDB & import("../../db/tables/view-param").PartialDB & import("../../db/tables/suggested-follow").PartialDB & import("../../db/tables/suggested-feed").PartialDB & Record<"descendent", {
9
9
  uri: any;
10
10
  depth: any;
11
- }>, "post">, "post", import("kysely").Selection<import("kysely/dist/cjs/parser/table-parser").From<import("../../db/tables/duplicate-record").PartialDB & import("../../db/tables/profile").PartialDB & import("../../db/tables/profile-agg").PartialDB & import("../../db/tables/post").PartialDB & import("../../db/tables/post-embed").PartialDB & import("../../db/tables/post-agg").PartialDB & import("../../db/tables/repost").PartialDB & import("../../db/tables/thread-gate").PartialDB & import("../../db/tables/feed-item").PartialDB & import("../../db/tables/follow").PartialDB & import("../../db/tables/like").PartialDB & import("../../db/tables/list").PartialDB & import("../../db/tables/list-item").PartialDB & import("../../db/tables/list-mute").PartialDB & import("../../db/tables/list-block").PartialDB & import("../../db/tables/mute").PartialDB & import("../../db/tables/actor-block").PartialDB & import("../../db/tables/feed-generator").PartialDB & import("../../db/tables/subscription").PartialDB & import("../../db/tables/actor").PartialDB & import("../../db/tables/actor-state").PartialDB & import("../../db/tables/actor-sync").PartialDB & import("../../db/tables/record").PartialDB & import("../../db/tables/notification").PartialDB & import("../../db/tables/notification-push-token").PartialDB & import("../../db/tables/did-cache").PartialDB & import("../../db/tables/moderation").PartialDB & import("../../db/tables/label").PartialDB & import("../../db/tables/algo").PartialDB & import("../../db/tables/view-param").PartialDB & import("../../db/tables/suggested-follow").PartialDB & import("../../db/tables/suggested-feed").PartialDB & Record<"descendent", {
11
+ }>, "post">, "post", import("kysely").Selection<import("kysely/dist/cjs/parser/table-parser").From<import("../../db/tables/duplicate-record").PartialDB & import("../../db/tables/profile").PartialDB & import("../../db/tables/profile-agg").PartialDB & import("../../db/tables/post").PartialDB & import("../../db/tables/post-embed").PartialDB & import("../../db/tables/post-agg").PartialDB & import("../../db/tables/repost").PartialDB & import("../../db/tables/thread-gate").PartialDB & import("../../db/tables/feed-item").PartialDB & import("../../db/tables/follow").PartialDB & import("../../db/tables/like").PartialDB & import("../../db/tables/list").PartialDB & import("../../db/tables/list-item").PartialDB & import("../../db/tables/list-mute").PartialDB & import("../../db/tables/list-block").PartialDB & import("../../db/tables/mute").PartialDB & import("../../db/tables/actor-block").PartialDB & import("../../db/tables/feed-generator").PartialDB & import("../../db/tables/subscription").PartialDB & import("../../db/tables/actor").PartialDB & import("../../db/tables/actor-state").PartialDB & import("../../db/tables/actor-sync").PartialDB & import("../../db/tables/record").PartialDB & import("../../db/tables/notification").PartialDB & import("../../db/tables/notification-push-token").PartialDB & import("../../db/tables/moderation").PartialDB & import("../../db/tables/label").PartialDB & import("../../db/tables/algo").PartialDB & import("../../db/tables/view-param").PartialDB & import("../../db/tables/suggested-follow").PartialDB & import("../../db/tables/suggested-feed").PartialDB & Record<"descendent", {
12
12
  uri: any;
13
13
  depth: any;
14
14
  }>, "post">, "post", "post.uri as uri" | import("kysely").AliasedRawBuilder<number, "depth">>>>;
15
15
  export declare const getAncestorsAndSelfQb: (db: DatabaseSchema, opts: {
16
16
  uri: string;
17
17
  parentHeight: number;
18
- }) => import("kysely/dist/cjs/parser/with-parser").QueryCreatorWithCommonTableExpression<import("../../db/database-schema").DatabaseSchemaType, "ancestor(uri, ancestorUri, height)", (cte: import("kysely").QueryCreator<import("../../db/tables/duplicate-record").PartialDB & import("../../db/tables/profile").PartialDB & import("../../db/tables/profile-agg").PartialDB & import("../../db/tables/post").PartialDB & import("../../db/tables/post-embed").PartialDB & import("../../db/tables/post-agg").PartialDB & import("../../db/tables/repost").PartialDB & import("../../db/tables/thread-gate").PartialDB & import("../../db/tables/feed-item").PartialDB & import("../../db/tables/follow").PartialDB & import("../../db/tables/like").PartialDB & import("../../db/tables/list").PartialDB & import("../../db/tables/list-item").PartialDB & import("../../db/tables/list-mute").PartialDB & import("../../db/tables/list-block").PartialDB & import("../../db/tables/mute").PartialDB & import("../../db/tables/actor-block").PartialDB & import("../../db/tables/feed-generator").PartialDB & import("../../db/tables/subscription").PartialDB & import("../../db/tables/actor").PartialDB & import("../../db/tables/actor-state").PartialDB & import("../../db/tables/actor-sync").PartialDB & import("../../db/tables/record").PartialDB & import("../../db/tables/notification").PartialDB & import("../../db/tables/notification-push-token").PartialDB & import("../../db/tables/did-cache").PartialDB & import("../../db/tables/moderation").PartialDB & import("../../db/tables/label").PartialDB & import("../../db/tables/algo").PartialDB & import("../../db/tables/view-param").PartialDB & import("../../db/tables/suggested-follow").PartialDB & import("../../db/tables/suggested-feed").PartialDB & Record<"ancestor", {
18
+ }) => import("kysely/dist/cjs/parser/with-parser").QueryCreatorWithCommonTableExpression<import("../../db/database-schema").DatabaseSchemaType, "ancestor(uri, ancestorUri, height)", (cte: import("kysely").QueryCreator<import("../../db/tables/duplicate-record").PartialDB & import("../../db/tables/profile").PartialDB & import("../../db/tables/profile-agg").PartialDB & import("../../db/tables/post").PartialDB & import("../../db/tables/post-embed").PartialDB & import("../../db/tables/post-agg").PartialDB & import("../../db/tables/repost").PartialDB & import("../../db/tables/thread-gate").PartialDB & import("../../db/tables/feed-item").PartialDB & import("../../db/tables/follow").PartialDB & import("../../db/tables/like").PartialDB & import("../../db/tables/list").PartialDB & import("../../db/tables/list-item").PartialDB & import("../../db/tables/list-mute").PartialDB & import("../../db/tables/list-block").PartialDB & import("../../db/tables/mute").PartialDB & import("../../db/tables/actor-block").PartialDB & import("../../db/tables/feed-generator").PartialDB & import("../../db/tables/subscription").PartialDB & import("../../db/tables/actor").PartialDB & import("../../db/tables/actor-state").PartialDB & import("../../db/tables/actor-sync").PartialDB & import("../../db/tables/record").PartialDB & import("../../db/tables/notification").PartialDB & import("../../db/tables/notification-push-token").PartialDB & import("../../db/tables/moderation").PartialDB & import("../../db/tables/label").PartialDB & import("../../db/tables/algo").PartialDB & import("../../db/tables/view-param").PartialDB & import("../../db/tables/suggested-follow").PartialDB & import("../../db/tables/suggested-feed").PartialDB & Record<"ancestor", {
19
19
  uri: any;
20
20
  height: any;
21
21
  ancestorUri: any;
22
- }>>) => import("kysely").SelectQueryBuilder<import("kysely/dist/cjs/parser/table-parser").From<import("../../db/tables/duplicate-record").PartialDB & import("../../db/tables/profile").PartialDB & import("../../db/tables/profile-agg").PartialDB & import("../../db/tables/post").PartialDB & import("../../db/tables/post-embed").PartialDB & import("../../db/tables/post-agg").PartialDB & import("../../db/tables/repost").PartialDB & import("../../db/tables/thread-gate").PartialDB & import("../../db/tables/feed-item").PartialDB & import("../../db/tables/follow").PartialDB & import("../../db/tables/like").PartialDB & import("../../db/tables/list").PartialDB & import("../../db/tables/list-item").PartialDB & import("../../db/tables/list-mute").PartialDB & import("../../db/tables/list-block").PartialDB & import("../../db/tables/mute").PartialDB & import("../../db/tables/actor-block").PartialDB & import("../../db/tables/feed-generator").PartialDB & import("../../db/tables/subscription").PartialDB & import("../../db/tables/actor").PartialDB & import("../../db/tables/actor-state").PartialDB & import("../../db/tables/actor-sync").PartialDB & import("../../db/tables/record").PartialDB & import("../../db/tables/notification").PartialDB & import("../../db/tables/notification-push-token").PartialDB & import("../../db/tables/did-cache").PartialDB & import("../../db/tables/moderation").PartialDB & import("../../db/tables/label").PartialDB & import("../../db/tables/algo").PartialDB & import("../../db/tables/view-param").PartialDB & import("../../db/tables/suggested-follow").PartialDB & import("../../db/tables/suggested-feed").PartialDB & Record<"ancestor", {
22
+ }>>) => import("kysely").SelectQueryBuilder<import("kysely/dist/cjs/parser/table-parser").From<import("../../db/tables/duplicate-record").PartialDB & import("../../db/tables/profile").PartialDB & import("../../db/tables/profile-agg").PartialDB & import("../../db/tables/post").PartialDB & import("../../db/tables/post-embed").PartialDB & import("../../db/tables/post-agg").PartialDB & import("../../db/tables/repost").PartialDB & import("../../db/tables/thread-gate").PartialDB & import("../../db/tables/feed-item").PartialDB & import("../../db/tables/follow").PartialDB & import("../../db/tables/like").PartialDB & import("../../db/tables/list").PartialDB & import("../../db/tables/list-item").PartialDB & import("../../db/tables/list-mute").PartialDB & import("../../db/tables/list-block").PartialDB & import("../../db/tables/mute").PartialDB & import("../../db/tables/actor-block").PartialDB & import("../../db/tables/feed-generator").PartialDB & import("../../db/tables/subscription").PartialDB & import("../../db/tables/actor").PartialDB & import("../../db/tables/actor-state").PartialDB & import("../../db/tables/actor-sync").PartialDB & import("../../db/tables/record").PartialDB & import("../../db/tables/notification").PartialDB & import("../../db/tables/notification-push-token").PartialDB & import("../../db/tables/moderation").PartialDB & import("../../db/tables/label").PartialDB & import("../../db/tables/algo").PartialDB & import("../../db/tables/view-param").PartialDB & import("../../db/tables/suggested-follow").PartialDB & import("../../db/tables/suggested-feed").PartialDB & Record<"ancestor", {
23
23
  uri: any;
24
24
  height: any;
25
25
  ancestorUri: any;
26
- }>, "post">, "post", import("kysely").Selection<import("kysely/dist/cjs/parser/table-parser").From<import("../../db/tables/duplicate-record").PartialDB & import("../../db/tables/profile").PartialDB & import("../../db/tables/profile-agg").PartialDB & import("../../db/tables/post").PartialDB & import("../../db/tables/post-embed").PartialDB & import("../../db/tables/post-agg").PartialDB & import("../../db/tables/repost").PartialDB & import("../../db/tables/thread-gate").PartialDB & import("../../db/tables/feed-item").PartialDB & import("../../db/tables/follow").PartialDB & import("../../db/tables/like").PartialDB & import("../../db/tables/list").PartialDB & import("../../db/tables/list-item").PartialDB & import("../../db/tables/list-mute").PartialDB & import("../../db/tables/list-block").PartialDB & import("../../db/tables/mute").PartialDB & import("../../db/tables/actor-block").PartialDB & import("../../db/tables/feed-generator").PartialDB & import("../../db/tables/subscription").PartialDB & import("../../db/tables/actor").PartialDB & import("../../db/tables/actor-state").PartialDB & import("../../db/tables/actor-sync").PartialDB & import("../../db/tables/record").PartialDB & import("../../db/tables/notification").PartialDB & import("../../db/tables/notification-push-token").PartialDB & import("../../db/tables/did-cache").PartialDB & import("../../db/tables/moderation").PartialDB & import("../../db/tables/label").PartialDB & import("../../db/tables/algo").PartialDB & import("../../db/tables/view-param").PartialDB & import("../../db/tables/suggested-follow").PartialDB & import("../../db/tables/suggested-feed").PartialDB & Record<"ancestor", {
26
+ }>, "post">, "post", import("kysely").Selection<import("kysely/dist/cjs/parser/table-parser").From<import("../../db/tables/duplicate-record").PartialDB & import("../../db/tables/profile").PartialDB & import("../../db/tables/profile-agg").PartialDB & import("../../db/tables/post").PartialDB & import("../../db/tables/post-embed").PartialDB & import("../../db/tables/post-agg").PartialDB & import("../../db/tables/repost").PartialDB & import("../../db/tables/thread-gate").PartialDB & import("../../db/tables/feed-item").PartialDB & import("../../db/tables/follow").PartialDB & import("../../db/tables/like").PartialDB & import("../../db/tables/list").PartialDB & import("../../db/tables/list-item").PartialDB & import("../../db/tables/list-mute").PartialDB & import("../../db/tables/list-block").PartialDB & import("../../db/tables/mute").PartialDB & import("../../db/tables/actor-block").PartialDB & import("../../db/tables/feed-generator").PartialDB & import("../../db/tables/subscription").PartialDB & import("../../db/tables/actor").PartialDB & import("../../db/tables/actor-state").PartialDB & import("../../db/tables/actor-sync").PartialDB & import("../../db/tables/record").PartialDB & import("../../db/tables/notification").PartialDB & import("../../db/tables/notification-push-token").PartialDB & import("../../db/tables/moderation").PartialDB & import("../../db/tables/label").PartialDB & import("../../db/tables/algo").PartialDB & import("../../db/tables/view-param").PartialDB & import("../../db/tables/suggested-follow").PartialDB & import("../../db/tables/suggested-feed").PartialDB & Record<"ancestor", {
27
27
  uri: any;
28
28
  height: any;
29
29
  ancestorUri: any;
@@ -1,8 +1,3 @@
1
- export declare function retry<T>(fn: () => Promise<T>, opts?: RetryOptions): Promise<T>;
1
+ import { RetryOptions } from '@atproto/common';
2
2
  export declare function retryHttp<T>(fn: () => Promise<T>, opts?: RetryOptions): Promise<T>;
3
3
  export declare function retryableHttp(err: unknown): boolean;
4
- declare type RetryOptions = {
5
- max?: number;
6
- retryable?: (err: unknown) => boolean;
7
- };
8
- export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/bsky",
3
- "version": "0.0.16",
3
+ "version": "0.0.17",
4
4
  "license": "MIT",
5
5
  "description": "Reference implementation of app.bsky App View (Bluesky API)",
6
6
  "keywords": [
@@ -35,7 +35,7 @@
35
35
  "sharp": "^0.32.6",
36
36
  "typed-emitter": "^2.1.0",
37
37
  "uint8arrays": "3.0.0",
38
- "@atproto/api": "^0.6.24",
38
+ "@atproto/api": "^0.7.0",
39
39
  "@atproto/common": "^0.3.3",
40
40
  "@atproto/crypto": "^0.3.0",
41
41
  "@atproto/syntax": "^0.1.5",
@@ -52,10 +52,10 @@
52
52
  "@types/pg": "^8.6.6",
53
53
  "@types/qs": "^6.9.7",
54
54
  "axios": "^0.27.2",
55
- "@atproto/api": "^0.6.24",
56
- "@atproto/dev-env": "^0.2.16",
55
+ "@atproto/api": "^0.7.0",
56
+ "@atproto/dev-env": "^0.2.17",
57
57
  "@atproto/lex-cli": "^0.2.5",
58
- "@atproto/pds": "^0.3.4",
58
+ "@atproto/pds": "^0.3.5",
59
59
  "@atproto/xrpc": "^0.4.1"
60
60
  },
61
61
  "scripts": {
@@ -0,0 +1,151 @@
1
+ import { cacheLogger as log } from '../logger'
2
+ import { Redis } from '../redis'
3
+
4
+ export type CacheItem<T> = {
5
+ val: T | null // null here is for negative caching
6
+ updatedAt: number
7
+ }
8
+
9
+ export type CacheOptions<T> = {
10
+ staleTTL: number
11
+ maxTTL: number
12
+ fetchMethod: (key: string) => Promise<T | null>
13
+ fetchManyMethod?: (keys: string[]) => Promise<Record<string, T | null>>
14
+ }
15
+
16
+ export class ReadThroughCache<T> {
17
+ constructor(public redis: Redis, public opts: CacheOptions<T>) {}
18
+
19
+ private async _fetchMany(keys: string[]): Promise<Record<string, T | null>> {
20
+ let result: Record<string, T | null> = {}
21
+ if (this.opts.fetchManyMethod) {
22
+ result = await this.opts.fetchManyMethod(keys)
23
+ } else {
24
+ const got = await Promise.all(keys.map((k) => this.opts.fetchMethod(k)))
25
+ for (let i = 0; i < keys.length; i++) {
26
+ result[keys[i]] = got[i] ?? null
27
+ }
28
+ }
29
+ // ensure caching negatives
30
+ for (const key of keys) {
31
+ result[key] ??= null
32
+ }
33
+ return result
34
+ }
35
+
36
+ private async fetchAndCache(key: string): Promise<T | null> {
37
+ const fetched = await this.opts.fetchMethod(key)
38
+ this.set(key, fetched).catch((err) =>
39
+ log.error({ err, key }, 'failed to set cache value'),
40
+ )
41
+ return fetched
42
+ }
43
+
44
+ private async fetchAndCacheMany(keys: string[]): Promise<Record<string, T>> {
45
+ const fetched = await this._fetchMany(keys)
46
+ this.setMany(fetched).catch((err) =>
47
+ log.error({ err, keys }, 'failed to set cache values'),
48
+ )
49
+ return removeNulls(fetched)
50
+ }
51
+
52
+ async get(key: string, opts?: { revalidate?: boolean }): Promise<T | null> {
53
+ if (opts?.revalidate) {
54
+ return this.fetchAndCache(key)
55
+ }
56
+ let cached: CacheItem<T> | null
57
+ try {
58
+ const got = await this.redis.get(key)
59
+ cached = got ? JSON.parse(got) : null
60
+ } catch (err) {
61
+ cached = null
62
+ log.warn({ key, err }, 'failed to fetch value from cache')
63
+ }
64
+ if (!cached || this.isExpired(cached)) {
65
+ return this.fetchAndCache(key)
66
+ }
67
+ if (this.isStale(cached)) {
68
+ this.fetchAndCache(key).catch((err) =>
69
+ log.warn({ key, err }, 'failed to refresh stale cache value'),
70
+ )
71
+ }
72
+ return cached.val
73
+ }
74
+
75
+ async getMany(
76
+ keys: string[],
77
+ opts?: { revalidate?: boolean },
78
+ ): Promise<Record<string, T>> {
79
+ if (opts?.revalidate) {
80
+ return this.fetchAndCacheMany(keys)
81
+ }
82
+ let cached: Record<string, string>
83
+ try {
84
+ cached = await this.redis.getMulti(keys)
85
+ } catch (err) {
86
+ cached = {}
87
+ log.warn({ keys, err }, 'failed to fetch values from cache')
88
+ }
89
+
90
+ const stale: string[] = []
91
+ const toFetch: string[] = []
92
+ const results: Record<string, T> = {}
93
+ for (const key of keys) {
94
+ const val = cached[key] ? (JSON.parse(cached[key]) as CacheItem<T>) : null
95
+ if (!val || this.isExpired(val)) {
96
+ toFetch.push(key)
97
+ continue
98
+ }
99
+ if (this.isStale(val)) {
100
+ stale.push(key)
101
+ }
102
+ if (val.val) {
103
+ results[key] = val.val
104
+ }
105
+ }
106
+ const fetched = await this.fetchAndCacheMany(toFetch)
107
+ this.fetchAndCacheMany(stale).catch((err) =>
108
+ log.warn({ keys, err }, 'failed to refresh stale cache values'),
109
+ )
110
+ return {
111
+ ...results,
112
+ ...fetched,
113
+ }
114
+ }
115
+
116
+ async set(key: string, val: T | null) {
117
+ await this.setMany({ [key]: val })
118
+ }
119
+
120
+ async setMany(vals: Record<string, T | null>) {
121
+ const items: Record<string, string> = {}
122
+ for (const key of Object.keys(vals)) {
123
+ items[key] = JSON.stringify({
124
+ val: vals[key],
125
+ updatedAt: Date.now(),
126
+ })
127
+ }
128
+ await this.redis.setMulti(items, this.opts.maxTTL)
129
+ }
130
+
131
+ async clearEntry(key: string) {
132
+ await this.redis.del(key)
133
+ }
134
+
135
+ isExpired(result: CacheItem<T>) {
136
+ return Date.now() > result.updatedAt + this.opts.maxTTL
137
+ }
138
+
139
+ isStale(result: CacheItem<T>) {
140
+ return Date.now() > result.updatedAt + this.opts.staleTTL
141
+ }
142
+ }
143
+
144
+ const removeNulls = <T>(obj: Record<string, T | null>): Record<string, T> => {
145
+ return Object.entries(obj).reduce((acc, [key, val]) => {
146
+ if (val !== null) {
147
+ acc[key] = val
148
+ }
149
+ return acc
150
+ }, {} as Record<string, T>)
151
+ }
package/src/config.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  import assert from 'assert'
2
- import { DAY, HOUR, parseIntWithFallback } from '@atproto/common'
2
+ import {
3
+ DAY,
4
+ HOUR,
5
+ MINUTE,
6
+ SECOND,
7
+ parseIntWithFallback,
8
+ } from '@atproto/common'
3
9
 
4
10
  export interface ServerConfigValues {
5
11
  version: string
@@ -12,9 +18,15 @@ export interface ServerConfigValues {
12
18
  dbReplicaPostgresUrls?: string[]
13
19
  dbReplicaTags?: Record<string, number[]> // E.g. { timeline: [0], thread: [1] }
14
20
  dbPostgresSchema?: string
21
+ redisHost?: string // either set redis host, or both sentinel name and hosts
22
+ redisSentinelName?: string
23
+ redisSentinelHosts?: string[]
24
+ redisPassword?: string
15
25
  didPlcUrl: string
16
26
  didCacheStaleTTL: number
17
27
  didCacheMaxTTL: number
28
+ labelCacheStaleTTL: number
29
+ labelCacheMaxTTL: number
18
30
  handleResolveNameservers?: string[]
19
31
  imgUriEndpoint?: string
20
32
  blobCacheLocation?: string
@@ -24,6 +36,9 @@ export interface ServerConfigValues {
24
36
  moderatorPassword?: string
25
37
  triagePassword?: string
26
38
  moderationPushUrl?: string
39
+ rateLimitsEnabled: boolean
40
+ rateLimitBypassKey?: string
41
+ rateLimitBypassIps?: string[]
27
42
  }
28
43
 
29
44
  export class ServerConfig {
@@ -38,6 +53,19 @@ export class ServerConfig {
38
53
  const feedGenDid = process.env.FEED_GEN_DID
39
54
  const envPort = parseInt(process.env.PORT || '', 10)
40
55
  const port = isNaN(envPort) ? 2584 : envPort
56
+ const redisHost =
57
+ overrides?.redisHost || process.env.REDIS_HOST || undefined
58
+ const redisSentinelName =
59
+ overrides?.redisSentinelName ||
60
+ process.env.REDIS_SENTINEL_NAME ||
61
+ undefined
62
+ const redisSentinelHosts =
63
+ overrides?.redisSentinelHosts ||
64
+ (process.env.REDIS_SENTINEL_HOSTS
65
+ ? process.env.REDIS_SENTINEL_HOSTS.split(',')
66
+ : [])
67
+ const redisPassword =
68
+ overrides?.redisPassword || process.env.REDIS_PASSWORD || undefined
41
69
  const didPlcUrl = process.env.DID_PLC_URL || 'http://localhost:2582'
42
70
  const didCacheStaleTTL = parseIntWithFallback(
43
71
  process.env.DID_CACHE_STALE_TTL,
@@ -47,6 +75,14 @@ export class ServerConfig {
47
75
  process.env.DID_CACHE_MAX_TTL,
48
76
  DAY,
49
77
  )
78
+ const labelCacheStaleTTL = parseIntWithFallback(
79
+ process.env.LABEL_CACHE_STALE_TTL,
80
+ 30 * SECOND,
81
+ )
82
+ const labelCacheMaxTTL = parseIntWithFallback(
83
+ process.env.LABEL_CACHE_MAX_TTL,
84
+ MINUTE,
85
+ )
50
86
  const handleResolveNameservers = process.env.HANDLE_RESOLVE_NAMESERVERS
51
87
  ? process.env.HANDLE_RESOLVE_NAMESERVERS.split(',')
52
88
  : []
@@ -82,6 +118,14 @@ export class ServerConfig {
82
118
  overrides?.moderationPushUrl ||
83
119
  process.env.MODERATION_PUSH_URL ||
84
120
  undefined
121
+ const rateLimitsEnabled = process.env.RATE_LIMITS_ENABLED === 'true'
122
+ const rateLimitBypassKey = process.env.RATE_LIMIT_BYPASS_KEY
123
+ const rateLimitBypassIps = process.env.RATE_LIMIT_BYPASS_IPS
124
+ ? process.env.RATE_LIMIT_BYPASS_IPS.split(',').map((ipOrCidr) =>
125
+ ipOrCidr.split('/')[0]?.trim(),
126
+ )
127
+ : undefined
128
+
85
129
  return new ServerConfig({
86
130
  version,
87
131
  debugMode,
@@ -93,9 +137,15 @@ export class ServerConfig {
93
137
  dbReplicaPostgresUrls,
94
138
  dbReplicaTags,
95
139
  dbPostgresSchema,
140
+ redisHost,
141
+ redisSentinelName,
142
+ redisSentinelHosts,
143
+ redisPassword,
96
144
  didPlcUrl,
97
145
  didCacheStaleTTL,
98
146
  didCacheMaxTTL,
147
+ labelCacheStaleTTL,
148
+ labelCacheMaxTTL,
99
149
  handleResolveNameservers,
100
150
  imgUriEndpoint,
101
151
  blobCacheLocation,
@@ -105,6 +155,9 @@ export class ServerConfig {
105
155
  moderatorPassword,
106
156
  triagePassword,
107
157
  moderationPushUrl,
158
+ rateLimitsEnabled,
159
+ rateLimitBypassKey,
160
+ rateLimitBypassIps,
108
161
  ...stripUndefineds(overrides ?? {}),
109
162
  })
110
163
  }
@@ -162,6 +215,22 @@ export class ServerConfig {
162
215
  return this.cfg.dbPostgresSchema
163
216
  }
164
217
 
218
+ get redisHost() {
219
+ return this.cfg.redisHost
220
+ }
221
+
222
+ get redisSentinelName() {
223
+ return this.cfg.redisSentinelName
224
+ }
225
+
226
+ get redisSentinelHosts() {
227
+ return this.cfg.redisSentinelHosts
228
+ }
229
+
230
+ get redisPassword() {
231
+ return this.cfg.redisPassword
232
+ }
233
+
165
234
  get didCacheStaleTTL() {
166
235
  return this.cfg.didCacheStaleTTL
167
236
  }
@@ -170,6 +239,14 @@ export class ServerConfig {
170
239
  return this.cfg.didCacheMaxTTL
171
240
  }
172
241
 
242
+ get labelCacheStaleTTL() {
243
+ return this.cfg.labelCacheStaleTTL
244
+ }
245
+
246
+ get labelCacheMaxTTL() {
247
+ return this.cfg.labelCacheMaxTTL
248
+ }
249
+
173
250
  get handleResolveNameservers() {
174
251
  return this.cfg.handleResolveNameservers
175
252
  }
@@ -209,6 +286,18 @@ export class ServerConfig {
209
286
  get moderationPushUrl() {
210
287
  return this.cfg.moderationPushUrl
211
288
  }
289
+
290
+ get rateLimitsEnabled() {
291
+ return this.cfg.rateLimitsEnabled
292
+ }
293
+
294
+ get rateLimitBypassKey() {
295
+ return this.cfg.rateLimitBypassKey
296
+ }
297
+
298
+ get rateLimitBypassIps() {
299
+ return this.cfg.rateLimitBypassIps
300
+ }
212
301
  }
213
302
 
214
303
  function getTagIdxs(str?: string): number[] {
package/src/context.ts CHANGED
@@ -8,11 +8,11 @@ import { ServerConfig } from './config'
8
8
  import { ImageUriBuilder } from './image/uri'
9
9
  import { Services } from './services'
10
10
  import * as auth from './auth'
11
- import DidSqlCache from './did-cache'
11
+ import DidRedisCache from './did-cache'
12
12
  import { BackgroundQueue } from './background'
13
13
  import { MountedAlgos } from './feed-gen/types'
14
- import { LabelCache } from './label-cache'
15
14
  import { NotificationServer } from './notifications'
15
+ import { Redis } from './redis'
16
16
 
17
17
  export class AppContext {
18
18
  public moderationPushAgent: AtpAgent | undefined
@@ -24,8 +24,8 @@ export class AppContext {
24
24
  services: Services
25
25
  signingKey: Keypair
26
26
  idResolver: IdResolver
27
- didCache: DidSqlCache
28
- labelCache: LabelCache
27
+ didCache: DidRedisCache
28
+ redis: Redis
29
29
  backgroundQueue: BackgroundQueue
30
30
  searchAgent?: AtpAgent
31
31
  algos: MountedAlgos
@@ -70,12 +70,12 @@ export class AppContext {
70
70
  return this.opts.idResolver
71
71
  }
72
72
 
73
- get didCache(): DidSqlCache {
73
+ get didCache(): DidRedisCache {
74
74
  return this.opts.didCache
75
75
  }
76
76
 
77
- get labelCache(): LabelCache {
78
- return this.opts.labelCache
77
+ get redis(): Redis {
78
+ return this.opts.redis
79
79
  }
80
80
 
81
81
  get notifServer(): NotificationServer {
@@ -0,0 +1,60 @@
1
+ import assert from 'assert'
2
+
3
+ export interface DaemonConfigValues {
4
+ version: string
5
+ dbPostgresUrl: string
6
+ dbPostgresSchema?: string
7
+ notificationsDaemonFromDid?: string
8
+ }
9
+
10
+ export class DaemonConfig {
11
+ constructor(private cfg: DaemonConfigValues) {}
12
+
13
+ static readEnv(overrides?: Partial<DaemonConfigValues>) {
14
+ const version = process.env.BSKY_VERSION || '0.0.0'
15
+ const dbPostgresUrl =
16
+ overrides?.dbPostgresUrl || process.env.DB_PRIMARY_POSTGRES_URL
17
+ const dbPostgresSchema =
18
+ overrides?.dbPostgresSchema || process.env.DB_POSTGRES_SCHEMA
19
+ const notificationsDaemonFromDid =
20
+ overrides?.notificationsDaemonFromDid ||
21
+ process.env.BSKY_NOTIFS_DAEMON_FROM_DID ||
22
+ undefined
23
+ assert(dbPostgresUrl)
24
+ return new DaemonConfig({
25
+ version,
26
+ dbPostgresUrl,
27
+ dbPostgresSchema,
28
+ notificationsDaemonFromDid,
29
+ ...stripUndefineds(overrides ?? {}),
30
+ })
31
+ }
32
+
33
+ get version() {
34
+ return this.cfg.version
35
+ }
36
+
37
+ get dbPostgresUrl() {
38
+ return this.cfg.dbPostgresUrl
39
+ }
40
+
41
+ get dbPostgresSchema() {
42
+ return this.cfg.dbPostgresSchema
43
+ }
44
+
45
+ get notificationsDaemonFromDid() {
46
+ return this.cfg.notificationsDaemonFromDid
47
+ }
48
+ }
49
+
50
+ function stripUndefineds(
51
+ obj: Record<string, unknown>,
52
+ ): Record<string, unknown> {
53
+ const result = {}
54
+ Object.entries(obj).forEach(([key, val]) => {
55
+ if (val !== undefined) {
56
+ result[key] = val
57
+ }
58
+ })
59
+ return result
60
+ }
@@ -0,0 +1,27 @@
1
+ import { PrimaryDatabase } from '../db'
2
+ import { DaemonConfig } from './config'
3
+ import { Services } from './services'
4
+
5
+ export class DaemonContext {
6
+ constructor(
7
+ private opts: {
8
+ db: PrimaryDatabase
9
+ cfg: DaemonConfig
10
+ services: Services
11
+ },
12
+ ) {}
13
+
14
+ get db(): PrimaryDatabase {
15
+ return this.opts.db
16
+ }
17
+
18
+ get cfg(): DaemonConfig {
19
+ return this.opts.cfg
20
+ }
21
+
22
+ get services(): Services {
23
+ return this.opts.services
24
+ }
25
+ }
26
+
27
+ export default DaemonContext
@@ -0,0 +1,78 @@
1
+ import { PrimaryDatabase } from '../db'
2
+ import { dbLogger } from '../logger'
3
+ import { DaemonConfig } from './config'
4
+ import { DaemonContext } from './context'
5
+ import { createServices } from './services'
6
+ import { ImageUriBuilder } from '../image/uri'
7
+ import { NotificationsDaemon } from './notifications'
8
+ import logger from './logger'
9
+
10
+ export { DaemonConfig } from './config'
11
+ export type { DaemonConfigValues } from './config'
12
+
13
+ export class BskyDaemon {
14
+ public ctx: DaemonContext
15
+ public notifications: NotificationsDaemon
16
+ private dbStatsInterval: NodeJS.Timer
17
+ private notifStatsInterval: NodeJS.Timer
18
+
19
+ constructor(opts: {
20
+ ctx: DaemonContext
21
+ notifications: NotificationsDaemon
22
+ }) {
23
+ this.ctx = opts.ctx
24
+ this.notifications = opts.notifications
25
+ }
26
+
27
+ static create(opts: { db: PrimaryDatabase; cfg: DaemonConfig }): BskyDaemon {
28
+ const { db, cfg } = opts
29
+ const imgUriBuilder = new ImageUriBuilder('https://daemon.invalid') // will not be used by daemon
30
+ const services = createServices({
31
+ imgUriBuilder,
32
+ })
33
+ const ctx = new DaemonContext({
34
+ db,
35
+ cfg,
36
+ services,
37
+ })
38
+ const notifications = new NotificationsDaemon(ctx)
39
+ return new BskyDaemon({ ctx, notifications })
40
+ }
41
+
42
+ async start() {
43
+ const { db, cfg } = this.ctx
44
+ const pool = db.pool
45
+ this.notifications.run({
46
+ startFromDid: cfg.notificationsDaemonFromDid,
47
+ })
48
+ this.dbStatsInterval = setInterval(() => {
49
+ dbLogger.info(
50
+ {
51
+ idleCount: pool.idleCount,
52
+ totalCount: pool.totalCount,
53
+ waitingCount: pool.waitingCount,
54
+ },
55
+ 'db pool stats',
56
+ )
57
+ }, 10000)
58
+ this.notifStatsInterval = setInterval(() => {
59
+ logger.info(
60
+ {
61
+ count: this.notifications.count,
62
+ lastDid: this.notifications.lastDid,
63
+ },
64
+ 'notifications daemon stats',
65
+ )
66
+ }, 10000)
67
+ return this
68
+ }
69
+
70
+ async destroy(): Promise<void> {
71
+ await this.notifications.destroy()
72
+ await this.ctx.db.close()
73
+ clearInterval(this.dbStatsInterval)
74
+ clearInterval(this.notifStatsInterval)
75
+ }
76
+ }
77
+
78
+ export default BskyDaemon
@@ -0,0 +1,6 @@
1
+ import { subsystemLogger } from '@atproto/common'
2
+
3
+ const logger: ReturnType<typeof subsystemLogger> =
4
+ subsystemLogger('bsky:daemon')
5
+
6
+ export default logger