@atproto/bsky 0.0.156 → 0.0.158

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 (130) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/api/app/bsky/notification/getPreferences.d.ts +4 -0
  3. package/dist/api/app/bsky/notification/getPreferences.d.ts.map +1 -0
  4. package/dist/api/app/bsky/notification/getPreferences.js +39 -0
  5. package/dist/api/app/bsky/notification/getPreferences.js.map +1 -0
  6. package/dist/api/app/bsky/notification/putPreferencesV2.d.ts +4 -0
  7. package/dist/api/app/bsky/notification/putPreferencesV2.d.ts.map +1 -0
  8. package/dist/api/app/bsky/notification/putPreferencesV2.js +47 -0
  9. package/dist/api/app/bsky/notification/putPreferencesV2.js.map +1 -0
  10. package/dist/api/app/bsky/notification/util.d.ts +9 -0
  11. package/dist/api/app/bsky/notification/util.d.ts.map +1 -0
  12. package/dist/api/app/bsky/notification/util.js +84 -0
  13. package/dist/api/app/bsky/notification/util.js.map +1 -0
  14. package/dist/api/index.d.ts.map +1 -1
  15. package/dist/api/index.js +4 -0
  16. package/dist/api/index.js.map +1 -1
  17. package/dist/context.d.ts +3 -0
  18. package/dist/context.d.ts.map +1 -1
  19. package/dist/context.js +3 -0
  20. package/dist/context.js.map +1 -1
  21. package/dist/data-plane/bsync/index.d.ts.map +1 -1
  22. package/dist/data-plane/bsync/index.js +84 -0
  23. package/dist/data-plane/bsync/index.js.map +1 -1
  24. package/dist/data-plane/server/db/database-schema.d.ts +2 -1
  25. package/dist/data-plane/server/db/database-schema.d.ts.map +1 -1
  26. package/dist/data-plane/server/db/db.d.ts +1 -0
  27. package/dist/data-plane/server/db/db.d.ts.map +1 -1
  28. package/dist/data-plane/server/db/db.js.map +1 -1
  29. package/dist/data-plane/server/db/index.d.ts +1 -0
  30. package/dist/data-plane/server/db/index.d.ts.map +1 -1
  31. package/dist/data-plane/server/db/migrations/20250602T190357447Z-add-private-data.d.ts +4 -0
  32. package/dist/data-plane/server/db/migrations/20250602T190357447Z-add-private-data.d.ts.map +1 -0
  33. package/dist/data-plane/server/db/migrations/20250602T190357447Z-add-private-data.js +24 -0
  34. package/dist/data-plane/server/db/migrations/20250602T190357447Z-add-private-data.js.map +1 -0
  35. package/dist/data-plane/server/db/migrations/index.d.ts +1 -0
  36. package/dist/data-plane/server/db/migrations/index.d.ts.map +1 -1
  37. package/dist/data-plane/server/db/migrations/index.js +2 -1
  38. package/dist/data-plane/server/db/migrations/index.js.map +1 -1
  39. package/dist/data-plane/server/db/tables/private-data.d.ts +13 -0
  40. package/dist/data-plane/server/db/tables/private-data.d.ts.map +1 -0
  41. package/dist/data-plane/server/db/tables/private-data.js +5 -0
  42. package/dist/data-plane/server/db/tables/private-data.js.map +1 -0
  43. package/dist/data-plane/server/db/tables/record.d.ts +2 -1
  44. package/dist/data-plane/server/db/tables/record.d.ts.map +1 -1
  45. package/dist/data-plane/server/db/tables/record.js.map +1 -1
  46. package/dist/data-plane/server/index.d.ts +2 -1
  47. package/dist/data-plane/server/index.d.ts.map +1 -1
  48. package/dist/data-plane/server/index.js.map +1 -1
  49. package/dist/data-plane/server/routes/index.d.ts.map +1 -1
  50. package/dist/data-plane/server/routes/index.js +2 -0
  51. package/dist/data-plane/server/routes/index.js.map +1 -1
  52. package/dist/data-plane/server/routes/private-data.d.ts +9 -0
  53. package/dist/data-plane/server/routes/private-data.d.ts.map +1 -0
  54. package/dist/data-plane/server/routes/private-data.js +64 -0
  55. package/dist/data-plane/server/routes/private-data.js.map +1 -0
  56. package/dist/data-plane/server/util.d.ts +6 -6
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +3 -0
  59. package/dist/index.js.map +1 -1
  60. package/dist/lexicon/index.d.ts +4 -0
  61. package/dist/lexicon/index.d.ts.map +1 -1
  62. package/dist/lexicon/index.js +8 -0
  63. package/dist/lexicon/index.js.map +1 -1
  64. package/dist/lexicon/lexicons.d.ts +418 -0
  65. package/dist/lexicon/lexicons.d.ts.map +1 -1
  66. package/dist/lexicon/lexicons.js +225 -0
  67. package/dist/lexicon/lexicons.js.map +1 -1
  68. package/dist/lexicon/types/app/bsky/notification/defs.d.ts +40 -0
  69. package/dist/lexicon/types/app/bsky/notification/defs.d.ts.map +1 -1
  70. package/dist/lexicon/types/app/bsky/notification/defs.js +36 -0
  71. package/dist/lexicon/types/app/bsky/notification/defs.js.map +1 -1
  72. package/dist/lexicon/types/app/bsky/notification/getPreferences.d.ts +35 -0
  73. package/dist/lexicon/types/app/bsky/notification/getPreferences.d.ts.map +1 -0
  74. package/dist/lexicon/types/app/bsky/notification/getPreferences.js +7 -0
  75. package/dist/lexicon/types/app/bsky/notification/getPreferences.js.map +1 -0
  76. package/dist/lexicon/types/app/bsky/notification/putPreferencesV2.d.ts +52 -0
  77. package/dist/lexicon/types/app/bsky/notification/putPreferencesV2.d.ts.map +1 -0
  78. package/dist/lexicon/types/app/bsky/notification/putPreferencesV2.js +7 -0
  79. package/dist/lexicon/types/app/bsky/notification/putPreferencesV2.js.map +1 -0
  80. package/dist/proto/bsky_connect.d.ts +10 -1
  81. package/dist/proto/bsky_connect.d.ts.map +1 -1
  82. package/dist/proto/bsky_connect.js +9 -0
  83. package/dist/proto/bsky_connect.js.map +1 -1
  84. package/dist/proto/bsky_pb.d.ts +238 -0
  85. package/dist/proto/bsky_pb.d.ts.map +1 -1
  86. package/dist/proto/bsky_pb.js +703 -5
  87. package/dist/proto/bsky_pb.js.map +1 -1
  88. package/dist/proto/bsync_pb.d.ts +10 -10
  89. package/dist/proto/bsync_pb.d.ts.map +1 -1
  90. package/dist/proto/bsync_pb.js +15 -15
  91. package/dist/proto/bsync_pb.js.map +1 -1
  92. package/dist/stash.d.ts +26 -0
  93. package/dist/stash.d.ts.map +1 -0
  94. package/dist/stash.js +56 -0
  95. package/dist/stash.js.map +1 -0
  96. package/package.json +8 -8
  97. package/proto/bsky.proto +62 -0
  98. package/src/api/app/bsky/notification/getPreferences.ts +50 -0
  99. package/src/api/app/bsky/notification/putPreferencesV2.ts +62 -0
  100. package/src/api/app/bsky/notification/util.ts +123 -0
  101. package/src/api/index.ts +4 -0
  102. package/src/context.ts +6 -0
  103. package/src/data-plane/bsync/index.ts +109 -1
  104. package/src/data-plane/server/db/database-schema.ts +3 -1
  105. package/src/data-plane/server/db/db.ts +2 -0
  106. package/src/data-plane/server/db/index.ts +1 -0
  107. package/src/data-plane/server/db/migrations/20250602T190357447Z-add-private-data.ts +22 -0
  108. package/src/data-plane/server/db/migrations/index.ts +1 -0
  109. package/src/data-plane/server/db/tables/private-data.ts +13 -0
  110. package/src/data-plane/server/db/tables/record.ts +3 -1
  111. package/src/data-plane/server/index.ts +3 -1
  112. package/src/data-plane/server/routes/index.ts +2 -0
  113. package/src/data-plane/server/routes/private-data.ts +94 -0
  114. package/src/index.ts +4 -0
  115. package/src/lexicon/index.ts +24 -0
  116. package/src/lexicon/lexicons.ts +227 -0
  117. package/src/lexicon/types/app/bsky/notification/defs.ts +76 -0
  118. package/src/lexicon/types/app/bsky/notification/getPreferences.ts +52 -0
  119. package/src/lexicon/types/app/bsky/notification/putPreferencesV2.ts +69 -0
  120. package/src/proto/bsky_connect.ts +11 -0
  121. package/src/proto/bsky_pb.ts +675 -0
  122. package/src/proto/bsync_pb.ts +15 -15
  123. package/src/stash.ts +75 -0
  124. package/tests/stash.test.ts +156 -0
  125. package/tests/views/notifications.test.ts +221 -0
  126. package/tests/views/thread-v2.test.ts +31 -33
  127. package/tsconfig.build.tsbuildinfo +1 -1
  128. package/tsconfig.tests.tsbuildinfo +1 -1
  129. package/tests/seed/thread-v2.ts +0 -874
  130. package/tests/seed/util.ts +0 -52
@@ -736,14 +736,14 @@ export class Operation extends Message<Operation> {
736
736
  actorDid = ''
737
737
 
738
738
  /**
739
- * @generated from field: string collection = 3;
739
+ * @generated from field: string namespace = 3;
740
740
  */
741
- collection = ''
741
+ namespace = ''
742
742
 
743
743
  /**
744
- * @generated from field: string rkey = 4;
744
+ * @generated from field: string key = 4;
745
745
  */
746
- rkey = ''
746
+ key = ''
747
747
 
748
748
  /**
749
749
  * @generated from field: bsync.Method method = 5;
@@ -765,8 +765,8 @@ export class Operation extends Message<Operation> {
765
765
  static readonly fields: FieldList = proto3.util.newFieldList(() => [
766
766
  { no: 1, name: 'id', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
767
767
  { no: 2, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
768
- { no: 3, name: 'collection', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
769
- { no: 4, name: 'rkey', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
768
+ { no: 3, name: 'namespace', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
769
+ { no: 4, name: 'key', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
770
770
  { no: 5, name: 'method', kind: 'enum', T: proto3.getEnumType(Method) },
771
771
  { no: 6, name: 'payload', kind: 'scalar', T: 12 /* ScalarType.BYTES */ },
772
772
  ])
@@ -805,19 +805,19 @@ export class Operation extends Message<Operation> {
805
805
  */
806
806
  export class PutOperationRequest extends Message<PutOperationRequest> {
807
807
  /**
808
- * @generated from field: string collection = 1;
808
+ * @generated from field: string actor_did = 1;
809
809
  */
810
- collection = ''
810
+ actorDid = ''
811
811
 
812
812
  /**
813
- * @generated from field: string actor_did = 2;
813
+ * @generated from field: string namespace = 2;
814
814
  */
815
- actorDid = ''
815
+ namespace = ''
816
816
 
817
817
  /**
818
- * @generated from field: string rkey = 3;
818
+ * @generated from field: string key = 3;
819
819
  */
820
- rkey = ''
820
+ key = ''
821
821
 
822
822
  /**
823
823
  * @generated from field: bsync.Method method = 4;
@@ -837,9 +837,9 @@ export class PutOperationRequest extends Message<PutOperationRequest> {
837
837
  static readonly runtime: typeof proto3 = proto3
838
838
  static readonly typeName = 'bsync.PutOperationRequest'
839
839
  static readonly fields: FieldList = proto3.util.newFieldList(() => [
840
- { no: 1, name: 'collection', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
841
- { no: 2, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
842
- { no: 3, name: 'rkey', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
840
+ { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
841
+ { no: 2, name: 'namespace', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
842
+ { no: 3, name: 'key', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
843
843
  { no: 4, name: 'method', kind: 'enum', T: proto3.getEnumType(Method) },
844
844
  { no: 5, name: 'payload', kind: 'scalar', T: 12 /* ScalarType.BYTES */ },
845
845
  ])
package/src/stash.ts ADDED
@@ -0,0 +1,75 @@
1
+ import { LexValue, stringifyLex } from '@atproto/lexicon'
2
+ import { BsyncClient } from './bsync'
3
+ import { lexicons } from './lexicon/lexicons'
4
+ import { Method } from './proto/bsync_pb'
5
+
6
+ export const createStashClient = (bsyncClient: BsyncClient): StashClient => {
7
+ return new StashClient(bsyncClient)
8
+ }
9
+
10
+ // An abstraction over the BsyncClient, that uses the bsync `PutOperation` RPC
11
+ // to store private data, which can be indexed by the dataplane and queried by the appview.
12
+ export class StashClient {
13
+ constructor(private readonly bsyncClient: BsyncClient) {}
14
+
15
+ create(input: CreateInput) {
16
+ this.validateLexicon(input.namespace, input.payload)
17
+ return this.putOperation(Method.CREATE, input)
18
+ }
19
+
20
+ update(input: UpdateInput) {
21
+ this.validateLexicon(input.namespace, input.payload)
22
+ return this.putOperation(Method.UPDATE, input)
23
+ }
24
+
25
+ delete(input: DeleteInput) {
26
+ return this.putOperation(Method.DELETE, { ...input, payload: undefined })
27
+ }
28
+
29
+ private validateLexicon(namespace: string, payload: LexValue) {
30
+ const result = lexicons.validate(namespace, payload)
31
+ if (!result.success) {
32
+ throw result.error
33
+ }
34
+ }
35
+
36
+ private async putOperation(method: Method, input: PutOperationInput) {
37
+ const { actorDid, namespace, key, payload } = input
38
+ await this.bsyncClient.putOperation({
39
+ actorDid,
40
+ namespace,
41
+ key,
42
+ method,
43
+ payload: payload
44
+ ? Buffer.from(
45
+ stringifyLex({
46
+ $type: namespace,
47
+ ...payload,
48
+ }),
49
+ )
50
+ : undefined,
51
+ })
52
+ }
53
+ }
54
+
55
+ type PutOperationInput = {
56
+ actorDid: string
57
+ namespace: string
58
+ key: string
59
+ payload: LexValue | undefined
60
+ }
61
+
62
+ type CreateInput = {
63
+ actorDid: string
64
+ namespace: string
65
+ key: string
66
+ payload: LexValue
67
+ }
68
+
69
+ type UpdateInput = CreateInput
70
+
71
+ type DeleteInput = {
72
+ actorDid: string
73
+ namespace: string
74
+ key: string
75
+ }
@@ -0,0 +1,156 @@
1
+ import { TestNetwork } from '@atproto/dev-env'
2
+ import { ProfileAssociatedChat } from '../dist/lexicon/types/app/bsky/actor/defs'
3
+ import { StashClient } from '../dist/stash'
4
+
5
+ type Database = TestNetwork['bsky']['db']
6
+
7
+ describe('private data', () => {
8
+ let network: TestNetwork
9
+ let stashClient: StashClient
10
+ let db: Database
11
+
12
+ const actorDid = 'did:plc:example'
13
+ // This lexicon has nothing special other than being simple, convenient to use in a test.
14
+ const namespace = 'app.bsky.actor.defs#profileAssociatedChat'
15
+ const key = 'self'
16
+
17
+ const validPayload0: ProfileAssociatedChat = { allowIncoming: 'all' }
18
+ const validPayload1: ProfileAssociatedChat = { allowIncoming: 'following' }
19
+ const invalidPayload: ProfileAssociatedChat = {
20
+ invalid: 'all',
21
+ } as unknown as ProfileAssociatedChat
22
+
23
+ beforeAll(async () => {
24
+ network = await TestNetwork.create({
25
+ dbPostgresSchema: 'bsky_private_data',
26
+ })
27
+ db = network.bsky.db
28
+ stashClient = network.bsky.ctx.stashClient
29
+ })
30
+
31
+ afterEach(async () => {
32
+ await clearPrivateData(db)
33
+ })
34
+
35
+ afterAll(async () => {
36
+ await network.close()
37
+ })
38
+
39
+ describe('create', () => {
40
+ it('creates entry', async () => {
41
+ await stashClient.create({
42
+ actorDid,
43
+ namespace,
44
+ key,
45
+ payload: validPayload0,
46
+ })
47
+ await network.processAll()
48
+
49
+ const dbResult = await db.db
50
+ .selectFrom('private_data')
51
+ .selectAll()
52
+ .where('actorDid', '=', actorDid)
53
+ .where('namespace', '=', namespace)
54
+ .where('key', '=', key)
55
+ .executeTakeFirstOrThrow()
56
+ expect(dbResult).toStrictEqual({
57
+ actorDid,
58
+ namespace,
59
+ key,
60
+ payload: JSON.stringify({ $type: namespace, ...validPayload0 }),
61
+ indexedAt: expect.any(String),
62
+ updatedAt: expect.any(String),
63
+ })
64
+ })
65
+
66
+ it('validates lexicon', async () => {
67
+ expect(() =>
68
+ stashClient.create({
69
+ actorDid,
70
+ namespace,
71
+ key,
72
+ payload: invalidPayload,
73
+ }),
74
+ ).toThrow('Object must have the property "allowIncoming"')
75
+ })
76
+ })
77
+
78
+ describe('update', () => {
79
+ it('updates entry', async () => {
80
+ await stashClient.create({
81
+ actorDid,
82
+ namespace,
83
+ key,
84
+ payload: validPayload0,
85
+ })
86
+ await network.processAll()
87
+
88
+ await stashClient.update({
89
+ actorDid,
90
+ namespace,
91
+ key,
92
+ payload: validPayload1,
93
+ })
94
+ await network.processAll()
95
+
96
+ const dbResult = await db.db
97
+ .selectFrom('private_data')
98
+ .selectAll()
99
+ .where('actorDid', '=', actorDid)
100
+ .where('namespace', '=', namespace)
101
+ .where('key', '=', key)
102
+ .executeTakeFirstOrThrow()
103
+ expect(dbResult).toStrictEqual({
104
+ actorDid,
105
+ namespace,
106
+ key,
107
+ payload: JSON.stringify({ $type: namespace, ...validPayload1 }),
108
+ indexedAt: expect.any(String),
109
+ updatedAt: expect.any(String),
110
+ })
111
+ })
112
+
113
+ it('validates lexicon', async () => {
114
+ expect(() =>
115
+ stashClient.update({
116
+ actorDid,
117
+ namespace,
118
+ key,
119
+ payload: invalidPayload,
120
+ }),
121
+ ).toThrow('Object must have the property "allowIncoming"')
122
+ })
123
+ })
124
+
125
+ describe('delete', () => {
126
+ it('deletes entry', async () => {
127
+ await stashClient.create({
128
+ actorDid,
129
+ namespace,
130
+ key,
131
+ payload: validPayload0,
132
+ })
133
+ await network.processAll()
134
+
135
+ await stashClient.delete({
136
+ actorDid,
137
+ namespace,
138
+ key,
139
+ })
140
+ await network.processAll()
141
+
142
+ const dbResult = await db.db
143
+ .selectFrom('private_data')
144
+ .selectAll()
145
+ .where('actorDid', '=', actorDid)
146
+ .where('namespace', '=', namespace)
147
+ .where('key', '=', key)
148
+ .executeTakeFirst()
149
+ expect(dbResult).toBe(undefined)
150
+ })
151
+ })
152
+ })
153
+
154
+ const clearPrivateData = async (db: Database) => {
155
+ await db.db.deleteFrom('private_data').execute()
156
+ }
@@ -2,11 +2,22 @@ import { AtpAgent } from '@atproto/api'
2
2
  import { SeedClient, TestNetwork, basicSeed } from '@atproto/dev-env'
3
3
  import { delayCursor } from '../../src/api/app/bsky/notification/listNotifications'
4
4
  import { ids } from '../../src/lexicon/lexicons'
5
+ import {
6
+ ChatPreference,
7
+ FilterablePreference,
8
+ Preference,
9
+ Preferences,
10
+ } from '../../src/lexicon/types/app/bsky/notification/defs'
5
11
  import { Notification } from '../../src/lexicon/types/app/bsky/notification/listNotifications'
12
+ import { InputSchema } from '../../src/lexicon/types/app/bsky/notification/putPreferencesV2'
6
13
  import { forSnapshot, paginateAll } from '../_util'
7
14
 
15
+ type Database = TestNetwork['bsky']['db']
16
+
8
17
  describe('notification views', () => {
9
18
  let network: TestNetwork
19
+ let db: Database
20
+
10
21
  let agent: AtpAgent
11
22
  let sc: SeedClient
12
23
 
@@ -19,6 +30,7 @@ describe('notification views', () => {
19
30
  network = await TestNetwork.create({
20
31
  dbPostgresSchema: 'bsky_views_notifications',
21
32
  })
33
+ db = network.bsky.db
22
34
  agent = network.bsky.getClient()
23
35
  sc = network.getSeedClient()
24
36
  await basicSeed(sc)
@@ -905,4 +917,213 @@ describe('notification views', () => {
905
917
  })
906
918
  })
907
919
  })
920
+
921
+ describe('preferences v2', () => {
922
+ beforeEach(async () => {
923
+ await clearPrivateData(db)
924
+ })
925
+
926
+ // Defaults
927
+ const fp: FilterablePreference = {
928
+ filter: 'all',
929
+ list: true,
930
+ push: true,
931
+ }
932
+ const p: Preference = {
933
+ list: true,
934
+ push: true,
935
+ }
936
+ const cp: ChatPreference = {
937
+ filter: 'all',
938
+ push: true,
939
+ }
940
+
941
+ it('gets preferences filling up with the defaults', async () => {
942
+ const actorDid = sc.dids.carol
943
+
944
+ const getAndAssert = async (
945
+ expectedApi: Preferences,
946
+ expectedDb: Preferences | undefined,
947
+ ) => {
948
+ const { data } = await agent.app.bsky.notification.getPreferences(
949
+ {},
950
+ {
951
+ headers: await network.serviceHeaders(
952
+ actorDid,
953
+ ids.AppBskyNotificationGetPreferences,
954
+ ),
955
+ },
956
+ )
957
+ expect(data.preferences).toStrictEqual(expectedApi)
958
+
959
+ const dbResult = await db.db
960
+ .selectFrom('private_data')
961
+ .selectAll()
962
+ .where('actorDid', '=', actorDid)
963
+ .where('namespace', '=', 'app.bsky.notification.defs#preferences')
964
+ .where('key', '=', 'self')
965
+ .executeTakeFirst()
966
+ if (dbResult === undefined) {
967
+ expect(dbResult).toBe(expectedDb)
968
+ } else {
969
+ expect(dbResult).toStrictEqual({
970
+ actorDid: actorDid,
971
+ namespace: 'app.bsky.notification.defs#preferences',
972
+ key: 'self',
973
+ indexedAt: expect.any(String),
974
+ payload: expect.anything(), // Better to compare payload parsed.
975
+ updatedAt: expect.any(String),
976
+ })
977
+ expect(JSON.parse(dbResult.payload)).toStrictEqual({
978
+ $type: 'app.bsky.notification.defs#preferences',
979
+ ...expectedDb,
980
+ })
981
+ }
982
+ }
983
+
984
+ const expectedApi0: Preferences = {
985
+ chat: cp,
986
+ follow: fp,
987
+ like: fp,
988
+ likeViaRepost: fp,
989
+ mention: fp,
990
+ quote: fp,
991
+ reply: fp,
992
+ repost: fp,
993
+ repostViaRepost: fp,
994
+ starterpackJoined: p,
995
+ subscribedPost: p,
996
+ unverified: p,
997
+ verified: p,
998
+ }
999
+ // The user has no preferences set yet, so nothing stored.
1000
+ const expectedDb0 = undefined
1001
+ await getAndAssert(expectedApi0, expectedDb0)
1002
+
1003
+ await agent.app.bsky.notification.putPreferencesV2(
1004
+ { verified: { list: false, push: false } },
1005
+ {
1006
+ encoding: 'application/json',
1007
+ headers: await network.serviceHeaders(
1008
+ actorDid,
1009
+ ids.AppBskyNotificationPutPreferencesV2,
1010
+ ),
1011
+ },
1012
+ )
1013
+ await network.processAll()
1014
+
1015
+ const expectedApi1: Preferences = {
1016
+ chat: cp,
1017
+ follow: fp,
1018
+ like: fp,
1019
+ likeViaRepost: fp,
1020
+ mention: fp,
1021
+ quote: fp,
1022
+ reply: fp,
1023
+ repost: fp,
1024
+ repostViaRepost: fp,
1025
+ starterpackJoined: p,
1026
+ subscribedPost: p,
1027
+ unverified: p,
1028
+ verified: { list: false, push: false },
1029
+ }
1030
+ // Stored all the defaults.
1031
+ const expectedDb1 = expectedApi1
1032
+ await getAndAssert(expectedApi1, expectedDb1)
1033
+ })
1034
+
1035
+ it('stores the preferences setting the defaults', async () => {
1036
+ const actorDid = sc.dids.carol
1037
+
1038
+ const putAndAssert = async (
1039
+ input: InputSchema,
1040
+ expected: Preferences,
1041
+ ) => {
1042
+ const { data } = await agent.app.bsky.notification.putPreferencesV2(
1043
+ input,
1044
+ {
1045
+ encoding: 'application/json',
1046
+ headers: await network.serviceHeaders(
1047
+ actorDid,
1048
+ ids.AppBskyNotificationPutPreferencesV2,
1049
+ ),
1050
+ },
1051
+ )
1052
+ await network.processAll()
1053
+ expect(data.preferences).toStrictEqual(expected)
1054
+
1055
+ const dbResult = await db.db
1056
+ .selectFrom('private_data')
1057
+ .selectAll()
1058
+ .where('actorDid', '=', actorDid)
1059
+ .where('namespace', '=', 'app.bsky.notification.defs#preferences')
1060
+ .where('key', '=', 'self')
1061
+ .executeTakeFirstOrThrow()
1062
+ expect(dbResult).toStrictEqual({
1063
+ actorDid: actorDid,
1064
+ namespace: 'app.bsky.notification.defs#preferences',
1065
+ key: 'self',
1066
+ indexedAt: expect.any(String),
1067
+ payload: expect.anything(), // Better to compare payload parsed.
1068
+ updatedAt: expect.any(String),
1069
+ })
1070
+ expect(JSON.parse(dbResult.payload)).toStrictEqual({
1071
+ $type: 'app.bsky.notification.defs#preferences',
1072
+ ...expected,
1073
+ })
1074
+ }
1075
+
1076
+ const input0 = {
1077
+ chat: {
1078
+ push: false,
1079
+ filter: 'accepted',
1080
+ },
1081
+ }
1082
+ const expected0: Preferences = {
1083
+ chat: input0.chat,
1084
+ follow: fp,
1085
+ like: fp,
1086
+ likeViaRepost: fp,
1087
+ mention: fp,
1088
+ quote: fp,
1089
+ reply: fp,
1090
+ repost: fp,
1091
+ repostViaRepost: fp,
1092
+ starterpackJoined: p,
1093
+ subscribedPost: p,
1094
+ unverified: p,
1095
+ verified: p,
1096
+ }
1097
+ await putAndAssert(input0, expected0)
1098
+
1099
+ const input1 = {
1100
+ mention: {
1101
+ list: false,
1102
+ push: false,
1103
+ filter: 'follows',
1104
+ },
1105
+ }
1106
+ const expected1: Preferences = {
1107
+ // Kept from the previous call.
1108
+ chat: input0.chat,
1109
+ follow: fp,
1110
+ like: fp,
1111
+ likeViaRepost: fp,
1112
+ mention: input1.mention,
1113
+ quote: fp,
1114
+ reply: fp,
1115
+ repost: fp,
1116
+ repostViaRepost: fp,
1117
+ starterpackJoined: p,
1118
+ subscribedPost: p,
1119
+ unverified: p,
1120
+ verified: p,
1121
+ }
1122
+ await putAndAssert(input1, expected1)
1123
+ })
1124
+ })
908
1125
  })
1126
+
1127
+ const clearPrivateData = async (db: Database) => {
1128
+ await db.db.deleteFrom('private_data').execute()
1129
+ }