@atproto/bsky 0.0.200 → 0.0.202

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 (87) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/api/app/bsky/ageassurance/begin.d.ts.map +1 -1
  3. package/dist/api/app/bsky/ageassurance/begin.js +15 -9
  4. package/dist/api/app/bsky/ageassurance/begin.js.map +1 -1
  5. package/dist/api/app/bsky/contact/sendNotification.d.ts +4 -0
  6. package/dist/api/app/bsky/contact/sendNotification.d.ts.map +1 -0
  7. package/dist/api/app/bsky/contact/sendNotification.js +30 -0
  8. package/dist/api/app/bsky/contact/sendNotification.js.map +1 -0
  9. package/dist/api/index.d.ts +1 -0
  10. package/dist/api/index.d.ts.map +1 -1
  11. package/dist/api/index.js +2 -1
  12. package/dist/api/index.js.map +1 -1
  13. package/dist/api/sitemap.d.ts +4 -0
  14. package/dist/api/sitemap.d.ts.map +1 -0
  15. package/dist/api/sitemap.js +67 -0
  16. package/dist/api/sitemap.js.map +1 -0
  17. package/dist/data-plane/server/routes/index.d.ts.map +1 -1
  18. package/dist/data-plane/server/routes/index.js +2 -0
  19. package/dist/data-plane/server/routes/index.js.map +1 -1
  20. package/dist/data-plane/server/routes/profile.d.ts.map +1 -1
  21. package/dist/data-plane/server/routes/profile.js +10 -8
  22. package/dist/data-plane/server/routes/profile.js.map +1 -1
  23. package/dist/data-plane/server/routes/sitemap.d.ts +5 -0
  24. package/dist/data-plane/server/routes/sitemap.d.ts.map +1 -0
  25. package/dist/data-plane/server/routes/sitemap.js +38 -0
  26. package/dist/data-plane/server/routes/sitemap.js.map +1 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +3 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/lexicon/index.d.ts +2 -0
  31. package/dist/lexicon/index.d.ts.map +1 -1
  32. package/dist/lexicon/index.js +4 -0
  33. package/dist/lexicon/index.js.map +1 -1
  34. package/dist/lexicon/lexicons.d.ts +146 -4
  35. package/dist/lexicon/lexicons.d.ts.map +1 -1
  36. package/dist/lexicon/lexicons.js +74 -0
  37. package/dist/lexicon/lexicons.js.map +1 -1
  38. package/dist/lexicon/types/app/bsky/actor/defs.d.ts +13 -1
  39. package/dist/lexicon/types/app/bsky/actor/defs.d.ts.map +1 -1
  40. package/dist/lexicon/types/app/bsky/actor/defs.js +9 -0
  41. package/dist/lexicon/types/app/bsky/actor/defs.js.map +1 -1
  42. package/dist/lexicon/types/app/bsky/contact/defs.d.ts +10 -0
  43. package/dist/lexicon/types/app/bsky/contact/defs.d.ts.map +1 -1
  44. package/dist/lexicon/types/app/bsky/contact/defs.js +9 -0
  45. package/dist/lexicon/types/app/bsky/contact/defs.js.map +1 -1
  46. package/dist/lexicon/types/app/bsky/contact/sendNotification.d.ts +26 -0
  47. package/dist/lexicon/types/app/bsky/contact/sendNotification.d.ts.map +1 -0
  48. package/dist/lexicon/types/app/bsky/contact/sendNotification.js +7 -0
  49. package/dist/lexicon/types/app/bsky/contact/sendNotification.js.map +1 -0
  50. package/dist/lexicon/types/app/bsky/notification/listNotifications.d.ts +1 -1
  51. package/dist/lexicon/types/app/bsky/notification/listNotifications.d.ts.map +1 -1
  52. package/dist/lexicon/types/app/bsky/notification/listNotifications.js.map +1 -1
  53. package/dist/proto/bsky_connect.d.ts +21 -1
  54. package/dist/proto/bsky_connect.d.ts.map +1 -1
  55. package/dist/proto/bsky_connect.js +20 -0
  56. package/dist/proto/bsky_connect.js.map +1 -1
  57. package/dist/proto/bsky_pb.d.ts +97 -0
  58. package/dist/proto/bsky_pb.d.ts.map +1 -1
  59. package/dist/proto/bsky_pb.js +256 -5
  60. package/dist/proto/bsky_pb.js.map +1 -1
  61. package/dist/stash.d.ts +2 -1
  62. package/dist/stash.d.ts.map +1 -1
  63. package/dist/stash.js +2 -1
  64. package/dist/stash.js.map +1 -1
  65. package/package.json +9 -9
  66. package/proto/bsky.proto +31 -0
  67. package/src/api/app/bsky/ageassurance/begin.ts +21 -14
  68. package/src/api/app/bsky/contact/sendNotification.ts +32 -0
  69. package/src/api/index.ts +2 -0
  70. package/src/api/sitemap.ts +76 -0
  71. package/src/data-plane/server/routes/index.ts +2 -0
  72. package/src/data-plane/server/routes/profile.ts +8 -6
  73. package/src/data-plane/server/routes/sitemap.ts +43 -0
  74. package/src/index.ts +6 -1
  75. package/src/lexicon/index.ts +13 -0
  76. package/src/lexicon/lexicons.ts +80 -0
  77. package/src/lexicon/types/app/bsky/actor/defs.ts +22 -0
  78. package/src/lexicon/types/app/bsky/contact/defs.ts +19 -0
  79. package/src/lexicon/types/app/bsky/contact/sendNotification.ts +44 -0
  80. package/src/lexicon/types/app/bsky/notification/listNotifications.ts +1 -0
  81. package/src/proto/bsky_connect.ts +21 -1
  82. package/src/proto/bsky_pb.ts +188 -0
  83. package/src/stash.ts +5 -2
  84. package/tests/sitemap.test.ts +75 -0
  85. package/tests/views/age-assurance-v2.test.ts +59 -1
  86. package/tsconfig.build.tsbuildinfo +1 -1
  87. package/tsconfig.tests.tsbuildinfo +1 -1
package/dist/stash.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { LexValue } from '@atproto/lexicon';
2
2
  import { BsyncClient } from './bsync';
3
3
  export declare const Namespaces: {
4
+ AppBskyAgeassuranceDefsEvent: string;
4
5
  AppBskyBookmarkDefsBookmark: string;
6
+ AppBskyContactDefsNotification: string;
5
7
  AppBskyNotificationDefsPreferences: string;
6
8
  AppBskyNotificationDefsSubjectActivitySubscription: string;
7
9
  AppBskyUnspeccedDefsAgeAssuranceEvent: string;
8
- AppBskyAgeassuranceDefsEvent: string;
9
10
  };
10
11
  export type Namespace = (typeof Namespaces)[keyof typeof Namespaces];
11
12
  export declare const createStashClient: (bsyncClient: BsyncClient) => StashClient;
@@ -1 +1 @@
1
- {"version":3,"file":"stash.d.ts","sourceRoot":"","sources":["../src/stash.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAgB,MAAM,kBAAkB,CAAA;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAarC,eAAO,MAAM,UAAU;;;;;;CAWtB,CAAA;AAED,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,OAAO,UAAU,CAAC,CAAA;AAEpE,eAAO,MAAM,iBAAiB,GAAI,aAAa,WAAW,KAAG,WAE5D,CAAA;AAID,qBAAa,WAAW;IACV,OAAO,CAAC,QAAQ,CAAC,WAAW;gBAAX,WAAW,EAAE,WAAW;IAErD,MAAM,CAAC,KAAK,EAAE,WAAW;IAKzB,MAAM,CAAC,KAAK,EAAE,WAAW;IAKzB,MAAM,CAAC,KAAK,EAAE,WAAW;IAIzB,OAAO,CAAC,eAAe;YAOT,YAAY;CAiB3B;AASD,KAAK,WAAW,GAAG;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,SAAS,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,QAAQ,CAAA;CAClB,CAAA;AAED,KAAK,WAAW,GAAG,WAAW,CAAA;AAE9B,KAAK,WAAW,GAAG;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,SAAS,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA"}
1
+ {"version":3,"file":"stash.d.ts","sourceRoot":"","sources":["../src/stash.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAgB,MAAM,kBAAkB,CAAA;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAcrC,eAAO,MAAM,UAAU;;;;;;;CAatB,CAAA;AAED,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,OAAO,UAAU,CAAC,CAAA;AAEpE,eAAO,MAAM,iBAAiB,GAAI,aAAa,WAAW,KAAG,WAE5D,CAAA;AAID,qBAAa,WAAW;IACV,OAAO,CAAC,QAAQ,CAAC,WAAW;gBAAX,WAAW,EAAE,WAAW;IAErD,MAAM,CAAC,KAAK,EAAE,WAAW;IAKzB,MAAM,CAAC,KAAK,EAAE,WAAW;IAKzB,MAAM,CAAC,KAAK,EAAE,WAAW;IAIzB,OAAO,CAAC,eAAe;YAOT,YAAY;CAiB3B;AASD,KAAK,WAAW,GAAG;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,SAAS,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,QAAQ,CAAA;CAClB,CAAA;AAED,KAAK,WAAW,GAAG,WAAW,CAAA;AAE9B,KAAK,WAAW,GAAG;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,SAAS,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA"}
package/dist/stash.js CHANGED
@@ -6,11 +6,12 @@ const lexicon_1 = require("@atproto/lexicon");
6
6
  const lexicons_1 = require("./lexicon/lexicons");
7
7
  const bsync_pb_1 = require("./proto/bsync_pb");
8
8
  exports.Namespaces = {
9
+ AppBskyAgeassuranceDefsEvent: 'app.bsky.ageassurance.defs#event',
9
10
  AppBskyBookmarkDefsBookmark: 'app.bsky.bookmark.defs#bookmark',
11
+ AppBskyContactDefsNotification: 'app.bsky.contact.defs#notification',
10
12
  AppBskyNotificationDefsPreferences: 'app.bsky.notification.defs#preferences',
11
13
  AppBskyNotificationDefsSubjectActivitySubscription: 'app.bsky.notification.defs#subjectActivitySubscription',
12
14
  AppBskyUnspeccedDefsAgeAssuranceEvent: 'app.bsky.unspecced.defs#ageAssuranceEvent',
13
- AppBskyAgeassuranceDefsEvent: 'app.bsky.ageassurance.defs#event',
14
15
  };
15
16
  const createStashClient = (bsyncClient) => {
16
17
  return new StashClient(bsyncClient);
package/dist/stash.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"stash.js","sourceRoot":"","sources":["../src/stash.ts"],"names":[],"mappings":";AAAA,yCAAyC;;;AAEzC,8CAAyD;AAEzD,iDAA6C;AAQ7C,+CAAyC;AAI5B,QAAA,UAAU,GAAG;IACxB,2BAA2B,EACzB,iCAA8D;IAChE,kCAAkC,EAChC,wCAAwE;IAC1E,kDAAkD,EAChD,wDAAwG;IAC1G,qCAAqC,EACnC,2CAAiF;IACnF,4BAA4B,EAC1B,kCAA0E;CAC7E,CAAA;AAIM,MAAM,iBAAiB,GAAG,CAAC,WAAwB,EAAe,EAAE;IACzE,OAAO,IAAI,WAAW,CAAC,WAAW,CAAC,CAAA;AACrC,CAAC,CAAA;AAFY,QAAA,iBAAiB,qBAE7B;AAED,8EAA8E;AAC9E,2FAA2F;AAC3F,MAAa,WAAW;IACtB,YAA6B,WAAwB;QAAzC;;;;mBAAiB,WAAW;WAAa;IAAG,CAAC;IAEzD,MAAM,CAAC,KAAkB;QACvB,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;QACpD,OAAO,IAAI,CAAC,YAAY,CAAC,iBAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IAChD,CAAC;IAED,MAAM,CAAC,KAAkB;QACvB,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;QACpD,OAAO,IAAI,CAAC,YAAY,CAAC,iBAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IAChD,CAAC;IAED,MAAM,CAAC,KAAkB;QACvB,OAAO,IAAI,CAAC,YAAY,CAAC,iBAAM,CAAC,MAAM,EAAE,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAA;IAC3E,CAAC;IAEO,eAAe,CAAC,SAAoB,EAAE,OAAiB;QAC7D,MAAM,MAAM,GAAG,mBAAQ,CAAC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;QACpD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,MAAM,CAAC,KAAK,CAAA;QACpB,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,YAAY,CAAC,MAAc,EAAE,KAAwB;QACjE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,KAAK,CAAA;QACnD,MAAM,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC;YAClC,QAAQ;YACR,SAAS;YACT,GAAG;YACH,MAAM;YACN,OAAO,EAAE,OAAO;gBACd,CAAC,CAAC,MAAM,CAAC,IAAI,CACT,IAAA,sBAAY,EAAC;oBACX,KAAK,EAAE,SAAS;oBAChB,GAAG,OAAO;iBACX,CAAC,CACH;gBACH,CAAC,CAAC,SAAS;SACd,CAAC,CAAA;IACJ,CAAC;CACF;AAzCD,kCAyCC","sourcesContent":["/* eslint-disable import/no-deprecated */\n\nimport { LexValue, stringifyLex } from '@atproto/lexicon'\nimport { BsyncClient } from './bsync'\nimport { lexicons } from './lexicon/lexicons'\nimport { Event as AgeAssuranceEventV2 } from './lexicon/types/app/bsky/ageassurance/defs'\nimport { Bookmark } from './lexicon/types/app/bsky/bookmark/defs'\nimport {\n Preferences,\n SubjectActivitySubscription,\n} from './lexicon/types/app/bsky/notification/defs'\nimport { AgeAssuranceEvent } from './lexicon/types/app/bsky/unspecced/defs'\nimport { Method } from './proto/bsync_pb'\n\ntype PickNSID<T extends { $type?: string }> = Exclude<T['$type'], undefined>\n\nexport const Namespaces = {\n AppBskyBookmarkDefsBookmark:\n 'app.bsky.bookmark.defs#bookmark' satisfies PickNSID<Bookmark>,\n AppBskyNotificationDefsPreferences:\n 'app.bsky.notification.defs#preferences' satisfies PickNSID<Preferences>,\n AppBskyNotificationDefsSubjectActivitySubscription:\n 'app.bsky.notification.defs#subjectActivitySubscription' satisfies PickNSID<SubjectActivitySubscription>,\n AppBskyUnspeccedDefsAgeAssuranceEvent:\n 'app.bsky.unspecced.defs#ageAssuranceEvent' satisfies PickNSID<AgeAssuranceEvent>,\n AppBskyAgeassuranceDefsEvent:\n 'app.bsky.ageassurance.defs#event' satisfies PickNSID<AgeAssuranceEventV2>,\n}\n\nexport type Namespace = (typeof Namespaces)[keyof typeof Namespaces]\n\nexport const createStashClient = (bsyncClient: BsyncClient): StashClient => {\n return new StashClient(bsyncClient)\n}\n\n// An abstraction over the BsyncClient, that uses the bsync `PutOperation` RPC\n// to store private data, which can be indexed by the dataplane and queried by the appview.\nexport class StashClient {\n constructor(private readonly bsyncClient: BsyncClient) {}\n\n create(input: CreateInput) {\n this.validateLexicon(input.namespace, input.payload)\n return this.putOperation(Method.CREATE, input)\n }\n\n update(input: UpdateInput) {\n this.validateLexicon(input.namespace, input.payload)\n return this.putOperation(Method.UPDATE, input)\n }\n\n delete(input: DeleteInput) {\n return this.putOperation(Method.DELETE, { ...input, payload: undefined })\n }\n\n private validateLexicon(namespace: Namespace, payload: LexValue) {\n const result = lexicons.validate(namespace, payload)\n if (!result.success) {\n throw result.error\n }\n }\n\n private async putOperation(method: Method, input: PutOperationInput) {\n const { actorDid, namespace, key, payload } = input\n await this.bsyncClient.putOperation({\n actorDid,\n namespace,\n key,\n method,\n payload: payload\n ? Buffer.from(\n stringifyLex({\n $type: namespace,\n ...payload,\n }),\n )\n : undefined,\n })\n }\n}\n\ntype PutOperationInput = {\n actorDid: string\n namespace: Namespace\n key: string\n payload: LexValue | undefined\n}\n\ntype CreateInput = {\n actorDid: string\n namespace: Namespace\n key: string\n payload: LexValue\n}\n\ntype UpdateInput = CreateInput\n\ntype DeleteInput = {\n actorDid: string\n namespace: Namespace\n key: string\n}\n"]}
1
+ {"version":3,"file":"stash.js","sourceRoot":"","sources":["../src/stash.ts"],"names":[],"mappings":";AAAA,yCAAyC;;;AAEzC,8CAAyD;AAEzD,iDAA6C;AAS7C,+CAAyC;AAI5B,QAAA,UAAU,GAAG;IACxB,4BAA4B,EAC1B,kCAA0E;IAC5E,2BAA2B,EACzB,iCAA8D;IAChE,8BAA8B,EAC5B,oCAAqE;IACvE,kCAAkC,EAChC,wCAAwE;IAC1E,kDAAkD,EAChD,wDAAwG;IAC1G,qCAAqC,EACnC,2CAAiF;CACpF,CAAA;AAIM,MAAM,iBAAiB,GAAG,CAAC,WAAwB,EAAe,EAAE;IACzE,OAAO,IAAI,WAAW,CAAC,WAAW,CAAC,CAAA;AACrC,CAAC,CAAA;AAFY,QAAA,iBAAiB,qBAE7B;AAED,8EAA8E;AAC9E,2FAA2F;AAC3F,MAAa,WAAW;IACtB,YAA6B,WAAwB;QAAzC;;;;mBAAiB,WAAW;WAAa;IAAG,CAAC;IAEzD,MAAM,CAAC,KAAkB;QACvB,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;QACpD,OAAO,IAAI,CAAC,YAAY,CAAC,iBAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IAChD,CAAC;IAED,MAAM,CAAC,KAAkB;QACvB,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;QACpD,OAAO,IAAI,CAAC,YAAY,CAAC,iBAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IAChD,CAAC;IAED,MAAM,CAAC,KAAkB;QACvB,OAAO,IAAI,CAAC,YAAY,CAAC,iBAAM,CAAC,MAAM,EAAE,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAA;IAC3E,CAAC;IAEO,eAAe,CAAC,SAAoB,EAAE,OAAiB;QAC7D,MAAM,MAAM,GAAG,mBAAQ,CAAC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;QACpD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,MAAM,CAAC,KAAK,CAAA;QACpB,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,YAAY,CAAC,MAAc,EAAE,KAAwB;QACjE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,KAAK,CAAA;QACnD,MAAM,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC;YAClC,QAAQ;YACR,SAAS;YACT,GAAG;YACH,MAAM;YACN,OAAO,EAAE,OAAO;gBACd,CAAC,CAAC,MAAM,CAAC,IAAI,CACT,IAAA,sBAAY,EAAC;oBACX,KAAK,EAAE,SAAS;oBAChB,GAAG,OAAO;iBACX,CAAC,CACH;gBACH,CAAC,CAAC,SAAS;SACd,CAAC,CAAA;IACJ,CAAC;CACF;AAzCD,kCAyCC","sourcesContent":["/* eslint-disable import/no-deprecated */\n\nimport { LexValue, stringifyLex } from '@atproto/lexicon'\nimport { BsyncClient } from './bsync'\nimport { lexicons } from './lexicon/lexicons'\nimport { Event as AgeAssuranceEventV2 } from './lexicon/types/app/bsky/ageassurance/defs'\nimport { Bookmark } from './lexicon/types/app/bsky/bookmark/defs'\nimport { Notification } from './lexicon/types/app/bsky/contact/defs'\nimport {\n Preferences,\n SubjectActivitySubscription,\n} from './lexicon/types/app/bsky/notification/defs'\nimport { AgeAssuranceEvent } from './lexicon/types/app/bsky/unspecced/defs'\nimport { Method } from './proto/bsync_pb'\n\ntype PickNSID<T extends { $type?: string }> = Exclude<T['$type'], undefined>\n\nexport const Namespaces = {\n AppBskyAgeassuranceDefsEvent:\n 'app.bsky.ageassurance.defs#event' satisfies PickNSID<AgeAssuranceEventV2>,\n AppBskyBookmarkDefsBookmark:\n 'app.bsky.bookmark.defs#bookmark' satisfies PickNSID<Bookmark>,\n AppBskyContactDefsNotification:\n 'app.bsky.contact.defs#notification' satisfies PickNSID<Notification>,\n AppBskyNotificationDefsPreferences:\n 'app.bsky.notification.defs#preferences' satisfies PickNSID<Preferences>,\n AppBskyNotificationDefsSubjectActivitySubscription:\n 'app.bsky.notification.defs#subjectActivitySubscription' satisfies PickNSID<SubjectActivitySubscription>,\n AppBskyUnspeccedDefsAgeAssuranceEvent:\n 'app.bsky.unspecced.defs#ageAssuranceEvent' satisfies PickNSID<AgeAssuranceEvent>,\n}\n\nexport type Namespace = (typeof Namespaces)[keyof typeof Namespaces]\n\nexport const createStashClient = (bsyncClient: BsyncClient): StashClient => {\n return new StashClient(bsyncClient)\n}\n\n// An abstraction over the BsyncClient, that uses the bsync `PutOperation` RPC\n// to store private data, which can be indexed by the dataplane and queried by the appview.\nexport class StashClient {\n constructor(private readonly bsyncClient: BsyncClient) {}\n\n create(input: CreateInput) {\n this.validateLexicon(input.namespace, input.payload)\n return this.putOperation(Method.CREATE, input)\n }\n\n update(input: UpdateInput) {\n this.validateLexicon(input.namespace, input.payload)\n return this.putOperation(Method.UPDATE, input)\n }\n\n delete(input: DeleteInput) {\n return this.putOperation(Method.DELETE, { ...input, payload: undefined })\n }\n\n private validateLexicon(namespace: Namespace, payload: LexValue) {\n const result = lexicons.validate(namespace, payload)\n if (!result.success) {\n throw result.error\n }\n }\n\n private async putOperation(method: Method, input: PutOperationInput) {\n const { actorDid, namespace, key, payload } = input\n await this.bsyncClient.putOperation({\n actorDid,\n namespace,\n key,\n method,\n payload: payload\n ? Buffer.from(\n stringifyLex({\n $type: namespace,\n ...payload,\n }),\n )\n : undefined,\n })\n }\n}\n\ntype PutOperationInput = {\n actorDid: string\n namespace: Namespace\n key: string\n payload: LexValue | undefined\n}\n\ntype CreateInput = {\n actorDid: string\n namespace: Namespace\n key: string\n payload: LexValue\n}\n\ntype UpdateInput = CreateInput\n\ntype DeleteInput = {\n actorDid: string\n namespace: Namespace\n key: string\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/bsky",
3
- "version": "0.0.200",
3
+ "version": "0.0.202",
4
4
  "license": "MIT",
5
5
  "description": "Reference implementation of app.bsky App View (Bluesky API)",
6
6
  "keywords": [
@@ -51,9 +51,9 @@
51
51
  "uint8arrays": "3.0.0",
52
52
  "undici": "^6.19.8",
53
53
  "zod": "3.23.8",
54
- "@atproto-labs/xrpc-utils": "0.0.24",
55
54
  "@atproto-labs/fetch-node": "0.2.0",
56
- "@atproto/api": "^0.18.5",
55
+ "@atproto-labs/xrpc-utils": "0.0.24",
56
+ "@atproto/api": "^0.18.7",
57
57
  "@atproto/common": "^0.5.3",
58
58
  "@atproto/crypto": "^0.4.5",
59
59
  "@atproto/did": "^0.2.3",
@@ -61,8 +61,8 @@
61
61
  "@atproto/lexicon": "^0.6.0",
62
62
  "@atproto/repo": "^0.8.12",
63
63
  "@atproto/sync": "^0.1.39",
64
- "@atproto/xrpc-server": "^0.10.3",
65
- "@atproto/syntax": "^0.4.2"
64
+ "@atproto/syntax": "^0.4.2",
65
+ "@atproto/xrpc-server": "^0.10.3"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@bufbuild/buf": "^1.28.1",
@@ -78,10 +78,10 @@
78
78
  "jest": "^28.1.2",
79
79
  "ts-node": "^10.8.2",
80
80
  "typescript": "^5.6.3",
81
- "@atproto/api": "^0.18.5",
82
- "@atproto/lex-cli": "^0.9.8",
83
- "@atproto/pds": "^0.4.198",
84
- "@atproto/xrpc": "^0.7.7"
81
+ "@atproto/api": "^0.18.7",
82
+ "@atproto/xrpc": "^0.7.7",
83
+ "@atproto/pds": "^0.4.199",
84
+ "@atproto/lex-cli": "^0.9.8"
85
85
  },
86
86
  "scripts": {
87
87
  "codegen": "lex gen-server --yes ./src/lexicon ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/* ../../lexicons/chat/bsky/*/*",
package/proto/bsky.proto CHANGED
@@ -1310,6 +1310,33 @@ message GetFollowsFollowingResponse {
1310
1310
  repeated FollowsFollowing results = 1;
1311
1311
  }
1312
1312
 
1313
+ message GetSitemapIndexRequest {
1314
+ SitemapPageType type = 1;
1315
+ }
1316
+
1317
+ message GetSitemapIndexResponse {
1318
+ // GZIP compressed XML sitemap
1319
+ bytes sitemap = 1;
1320
+ }
1321
+
1322
+ // Sitemap HTTP paths are typically of the form `/type/yyyy-mm-dd/N.xml.gz`, i.e. `/users/2025-01-01/1.xml.gz`
1323
+ message GetSitemapPageRequest {
1324
+ SitemapPageType type = 1;
1325
+ google.protobuf.Timestamp date = 2;
1326
+ // One-indexed
1327
+ int32 bucket = 3;
1328
+ }
1329
+
1330
+ enum SitemapPageType {
1331
+ SITEMAP_PAGE_TYPE_UNSPECIFIED = 0;
1332
+ SITEMAP_PAGE_TYPE_USER = 1;
1333
+ }
1334
+
1335
+ message GetSitemapPageResponse {
1336
+ // GZIP compressed XML sitemap
1337
+ bytes sitemap = 1;
1338
+ }
1339
+
1313
1340
  // Ping
1314
1341
  message PingRequest {}
1315
1342
  message PingResponse {}
@@ -1470,6 +1497,10 @@ service Service {
1470
1497
  // Graph
1471
1498
  rpc GetFollowsFollowing(GetFollowsFollowingRequest) returns (GetFollowsFollowingResponse);
1472
1499
 
1500
+ // Sitemaps
1501
+ rpc GetSitemapIndex(GetSitemapIndexRequest) returns (GetSitemapIndexResponse);
1502
+ rpc GetSitemapPage(GetSitemapPageRequest) returns (GetSitemapPageResponse);
1503
+
1473
1504
  // Ping
1474
1505
  rpc Ping(PingRequest) returns (PingResponse);
1475
1506
 
@@ -37,17 +37,14 @@ export default function (server: Server, ctx: AppContext) {
37
37
 
38
38
  const actorDid = auth.credentials.iss
39
39
  const actorInfo = await getAgeVerificationState(ctx, actorDid)
40
+ const existingStatus = actorInfo?.ageAssuranceStatus?.status
41
+ const existingAccess = actorInfo?.ageAssuranceStatus?.access
40
42
 
41
- if (actorInfo?.ageAssuranceStatus) {
42
- if (
43
- actorInfo.ageAssuranceStatus.status !== 'unknown' &&
44
- actorInfo.ageAssuranceStatus.status !== 'pending'
45
- ) {
46
- throw new InvalidRequestError(
47
- `Cannot initiate age assurance flow from current state: ${actorInfo.ageAssuranceStatus.status}`,
48
- 'InvalidInitiation',
49
- )
50
- }
43
+ if (existingStatus === 'blocked') {
44
+ throw new InvalidRequestError(
45
+ `Cannot initiate age assurance flow from current state: ${existingStatus}`,
46
+ 'InvalidInitiation',
47
+ )
51
48
  }
52
49
 
53
50
  const attemptId = crypto.randomUUID()
@@ -110,14 +107,24 @@ export default function (server: Server, ctx: AppContext) {
110
107
  })
111
108
  }
112
109
 
110
+ // If we have existing status/access for this region, retain it.
111
+ const nextStatus =
112
+ existingStatus && existingStatus !== 'unknown'
113
+ ? existingStatus
114
+ : 'pending'
115
+ const nextAccess =
116
+ existingAccess && existingAccess !== 'unknown'
117
+ ? existingAccess
118
+ : 'unknown'
119
+
113
120
  const event = await createEvent(ctx, actorDid, {
114
121
  attemptId,
115
122
  email,
116
123
  // Assumes `app.set('trust proxy', ...)` configured with `true` or specific values.
117
124
  initIp: req.ip,
118
125
  initUa: getClientUa(req),
119
- status: 'pending',
120
- access: 'unknown',
126
+ status: nextStatus,
127
+ access: nextAccess,
121
128
  countryCode,
122
129
  regionCode,
123
130
  })
@@ -126,8 +133,8 @@ export default function (server: Server, ctx: AppContext) {
126
133
  encoding: 'application/json',
127
134
  body: {
128
135
  lastInitiatedAt: event.createdAt,
129
- status: 'pending',
130
- access: 'unknown',
136
+ status: nextStatus,
137
+ access: nextAccess,
131
138
  },
132
139
  }
133
140
  },
@@ -0,0 +1,32 @@
1
+ import { TID } from '@atproto/common'
2
+ import { AppContext } from '../../../../context'
3
+ import { Server } from '../../../../lexicon'
4
+ import { Namespaces } from '../../../../stash'
5
+ import { assertRolodexOrThrowUnimplemented } from './util'
6
+
7
+ export default function (server: Server, ctx: AppContext) {
8
+ server.app.bsky.contact.sendNotification({
9
+ auth: ctx.authVerifier.role,
10
+ handler: async ({ input }) => {
11
+ // Assert rolodex even though we don't call it, it is a proxy to whether the app is configured with contact import support.
12
+ assertRolodexOrThrowUnimplemented(ctx)
13
+
14
+ const { from, to } = input.body
15
+
16
+ await ctx.stashClient.create({
17
+ actorDid: from,
18
+ namespace: Namespaces.AppBskyContactDefsNotification,
19
+ payload: {
20
+ from,
21
+ to,
22
+ },
23
+ key: TID.nextStr(),
24
+ })
25
+
26
+ return {
27
+ encoding: 'application/json',
28
+ body: {},
29
+ }
30
+ },
31
+ })
32
+ }
package/src/api/index.ts CHANGED
@@ -95,6 +95,8 @@ export * as blobResolver from './blob-resolver'
95
95
 
96
96
  export * as external from './external'
97
97
 
98
+ export * as sitemap from './sitemap'
99
+
98
100
  export default function (server: Server, ctx: AppContext) {
99
101
  // app.bsky
100
102
  getTimeline(server, ctx)
@@ -0,0 +1,76 @@
1
+ import { Readable } from 'node:stream'
2
+ import { Timestamp } from '@bufbuild/protobuf'
3
+ import { Code, ConnectError } from '@connectrpc/connect'
4
+ import express, { RequestHandler, Router } from 'express'
5
+ import { AppContext } from '../context'
6
+ import { httpLogger as log } from '../logger'
7
+ import { SitemapPageType } from '../proto/bsky_pb'
8
+
9
+ export const createRouter = (ctx: AppContext): Router => {
10
+ const router = Router()
11
+ router.get('/external/sitemap/users.xml.gz', userIndexHandler(ctx))
12
+ router.get(
13
+ '/external/sitemap/users/:date/:bucket.xml.gz',
14
+ userPageHandler(ctx),
15
+ )
16
+ return router
17
+ }
18
+
19
+ const userIndexHandler =
20
+ (ctx: AppContext): RequestHandler =>
21
+ async (_req: express.Request, res: express.Response) => {
22
+ try {
23
+ const result = await ctx.dataplane.getSitemapIndex({
24
+ type: SitemapPageType.USER,
25
+ })
26
+ res.set('Content-Type', 'application/gzip')
27
+ res.set('Content-Encoding', 'gzip')
28
+ Readable.from(Buffer.from(result.sitemap)).pipe(res)
29
+ } catch (err) {
30
+ log.error({ err }, 'failed to get sitemap index')
31
+ return res.status(500).send('Internal Server Error')
32
+ }
33
+ }
34
+
35
+ const userPageHandler =
36
+ (ctx: AppContext): RequestHandler =>
37
+ async (req: express.Request, res: express.Response) => {
38
+ const { date, bucket } = req.params
39
+
40
+ // Parse date (YYYY-MM-DD format)
41
+ const dateParts = date.split('-')
42
+ if (dateParts.length !== 3) {
43
+ return res.status(400).send('Invalid date format. Expected YYYY-MM-DD')
44
+ }
45
+
46
+ const year = parseInt(dateParts[0], 10)
47
+ const month = parseInt(dateParts[1], 10)
48
+ const day = parseInt(dateParts[2], 10)
49
+
50
+ if (isNaN(year) || isNaN(month) || isNaN(day)) {
51
+ return res.status(400).send('Invalid date format. Expected YYYY-MM-DD')
52
+ }
53
+
54
+ // Parse bucket (1-indexed)
55
+ const bucketNum = parseInt(bucket, 10)
56
+ if (isNaN(bucketNum) || bucketNum < 1) {
57
+ return res.status(400).send('Invalid bucket number')
58
+ }
59
+
60
+ try {
61
+ const result = await ctx.dataplane.getSitemapPage({
62
+ type: SitemapPageType.USER,
63
+ date: Timestamp.fromDate(new Date(year, month - 1, day)),
64
+ bucket: bucketNum,
65
+ })
66
+ res.set('Content-Type', 'application/gzip')
67
+ res.set('Content-Encoding', 'gzip')
68
+ Readable.from(Buffer.from(result.sitemap)).pipe(res)
69
+ } catch (err) {
70
+ if (err instanceof ConnectError && err.code === Code.NotFound) {
71
+ return res.status(404).send('Sitemap page not found')
72
+ }
73
+ log.error({ err }, 'failed to get sitemap page')
74
+ return res.status(500).send('Internal Server Error')
75
+ }
76
+ }
@@ -22,6 +22,7 @@ import records from './records'
22
22
  import relationships from './relationships'
23
23
  import reposts from './reposts'
24
24
  import search from './search'
25
+ import sitemap from './sitemap'
25
26
  import starterPacks from './starter-packs'
26
27
  import suggestions from './suggestions'
27
28
  import sync from './sync'
@@ -50,6 +51,7 @@ export default (db: Database, idResolver: IdResolver) =>
50
51
  ...relationships(db),
51
52
  ...reposts(db),
52
53
  ...search(db),
54
+ ...sitemap(),
53
55
  ...suggestions(db),
54
56
  ...sync(db),
55
57
  ...threads(db),
@@ -136,12 +136,14 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
136
136
 
137
137
  const status = row?.ageAssuranceStatus ?? 'unknown'
138
138
  let access = row?.ageAssuranceAccess
139
- if (status === 'assured') {
140
- access = 'full'
141
- } else if (status === 'blocked') {
142
- access = 'none'
143
- } else {
144
- access = 'unknown'
139
+ if (!access || access === 'unknown') {
140
+ if (status === 'assured') {
141
+ access = 'full'
142
+ } else if (status === 'blocked') {
143
+ access = 'none'
144
+ } else {
145
+ access = 'unknown'
146
+ }
145
147
  }
146
148
 
147
149
  return {
@@ -0,0 +1,43 @@
1
+ import { gzipSync } from 'node:zlib'
2
+ import { Code, ConnectError, ServiceImpl } from '@connectrpc/connect'
3
+ import { Service } from '../../../proto/bsky_connect'
4
+ import { GetSitemapPageRequest } from '../../../proto/bsky_pb'
5
+
6
+ const MOCK_SITEMAP_INDEX = `<?xml version="1.0" encoding="UTF-8"?>
7
+ <sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
8
+ <sitemap>
9
+ <loc>https://bsky.app/sitemap/users/2025-01-01/1.xml.gz</loc>
10
+ </sitemap>
11
+ </sitemapindex>`
12
+
13
+ const MOCK_SITEMAP_PAGE = `<?xml version="1.0" encoding="UTF-8"?>
14
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
15
+ <url>
16
+ <loc>https://bsky.app/profile/test.bsky.social</loc>
17
+ </url>
18
+ </urlset>`
19
+
20
+ export default (): Partial<ServiceImpl<typeof Service>> => ({
21
+ async getSitemapIndex() {
22
+ return {
23
+ sitemap: gzipSync(Buffer.from(MOCK_SITEMAP_INDEX)),
24
+ }
25
+ },
26
+ async getSitemapPage(req: GetSitemapPageRequest) {
27
+ const date = req.date?.toDate()
28
+ const isExpectedDate =
29
+ date &&
30
+ date.getFullYear() === 2025 &&
31
+ date.getMonth() === 0 &&
32
+ date.getDate() === 1
33
+ const isExpectedBucket = req.bucket === 1
34
+
35
+ if (!isExpectedDate || !isExpectedBucket) {
36
+ throw new ConnectError('Sitemap page not found', Code.NotFound)
37
+ }
38
+
39
+ return {
40
+ sitemap: gzipSync(Buffer.from(MOCK_SITEMAP_PAGE)),
41
+ }
42
+ },
43
+ })
package/src/index.ts CHANGED
@@ -10,7 +10,7 @@ import { AtpAgent } from '@atproto/api'
10
10
  import { DAY, SECOND } from '@atproto/common'
11
11
  import { Keypair } from '@atproto/crypto'
12
12
  import { IdResolver } from '@atproto/identity'
13
- import API, { blobResolver, external, health, wellKnown } from './api'
13
+ import API, { blobResolver, external, health, sitemap, wellKnown } from './api'
14
14
  import { createBlobDispatcher } from './api/blob-dispatcher'
15
15
  import { AuthVerifier, createPublicKeyObject } from './auth-verifier'
16
16
  import { authWithApiKey as bsyncAuth, createBsyncClient } from './bsync'
@@ -225,6 +225,11 @@ export class BskyAppView {
225
225
  app.use(wellKnown.createRouter(ctx))
226
226
  app.use(blobResolver.createMiddleware(ctx))
227
227
  app.use(imageServer.createMiddleware(ctx, { prefix: '/img/' }))
228
+
229
+ if (config.dataplaneUrls.length > 0 || config.dataplaneUrlsEtcdKeyPrefix) {
230
+ app.use(sitemap.createRouter(ctx))
231
+ }
232
+
228
233
  app.use(server.xrpc.router)
229
234
  app.use(error.handler)
230
235
  app.use('/external', external.createRouter(ctx))
@@ -28,6 +28,7 @@ import * as AppBskyContactGetMatches from './types/app/bsky/contact/getMatches.j
28
28
  import * as AppBskyContactGetSyncStatus from './types/app/bsky/contact/getSyncStatus.js'
29
29
  import * as AppBskyContactImportContacts from './types/app/bsky/contact/importContacts.js'
30
30
  import * as AppBskyContactRemoveData from './types/app/bsky/contact/removeData.js'
31
+ import * as AppBskyContactSendNotification from './types/app/bsky/contact/sendNotification.js'
31
32
  import * as AppBskyContactStartPhoneVerification from './types/app/bsky/contact/startPhoneVerification.js'
32
33
  import * as AppBskyContactVerifyPhone from './types/app/bsky/contact/verifyPhone.js'
33
34
  import * as AppBskyFeedDescribeFeedGenerator from './types/app/bsky/feed/describeFeedGenerator.js'
@@ -557,6 +558,18 @@ export class AppBskyContactNS {
557
558
  return this._server.xrpc.method(nsid, cfg)
558
559
  }
559
560
 
561
+ sendNotification<A extends Auth = void>(
562
+ cfg: MethodConfigOrHandler<
563
+ A,
564
+ AppBskyContactSendNotification.QueryParams,
565
+ AppBskyContactSendNotification.HandlerInput,
566
+ AppBskyContactSendNotification.HandlerOutput
567
+ >,
568
+ ) {
569
+ const nsid = 'app.bsky.contact.sendNotification' // @ts-ignore
570
+ return this._server.xrpc.method(nsid, cfg)
571
+ }
572
+
560
573
  startPhoneVerification<A extends Auth = void>(
561
574
  cfg: MethodConfigOrHandler<
562
575
  A,
@@ -401,6 +401,7 @@ export const schemaDict = {
401
401
  'lex:app.bsky.actor.defs#savedFeedsPref',
402
402
  'lex:app.bsky.actor.defs#savedFeedsPrefV2',
403
403
  'lex:app.bsky.actor.defs#personalDetailsPref',
404
+ 'lex:app.bsky.actor.defs#declaredAgePref',
404
405
  'lex:app.bsky.actor.defs#feedViewPref',
405
406
  'lex:app.bsky.actor.defs#threadViewPref',
406
407
  'lex:app.bsky.actor.defs#interestsPref',
@@ -507,6 +508,28 @@ export const schemaDict = {
507
508
  },
508
509
  },
509
510
  },
511
+ declaredAgePref: {
512
+ type: 'object',
513
+ description:
514
+ "Read-only preference containing value(s) inferred from the user's declared birthdate. Absence of this preference object in the response indicates that the user has not made a declaration.",
515
+ properties: {
516
+ isOverAge13: {
517
+ type: 'boolean',
518
+ description:
519
+ 'Indicates if the user has declared that they are over 13 years of age.',
520
+ },
521
+ isOverAge16: {
522
+ type: 'boolean',
523
+ description:
524
+ 'Indicates if the user has declared that they are over 16 years of age.',
525
+ },
526
+ isOverAge18: {
527
+ type: 'boolean',
528
+ description:
529
+ 'Indicates if the user has declared that they are over 18 years of age.',
530
+ },
531
+ },
532
+ },
510
533
  feedViewPref: {
511
534
  type: 'object',
512
535
  required: ['feed'],
@@ -1776,6 +1799,24 @@ export const schemaDict = {
1776
1799
  },
1777
1800
  },
1778
1801
  },
1802
+ notification: {
1803
+ description:
1804
+ 'A stash object to be sent via bsync representing a notification to be created.',
1805
+ type: 'object',
1806
+ required: ['from', 'to'],
1807
+ properties: {
1808
+ from: {
1809
+ description: 'The DID of who this notification comes from.',
1810
+ type: 'string',
1811
+ format: 'did',
1812
+ },
1813
+ to: {
1814
+ description: 'The DID of who this notification should go to.',
1815
+ type: 'string',
1816
+ format: 'did',
1817
+ },
1818
+ },
1819
+ },
1779
1820
  },
1780
1821
  },
1781
1822
  AppBskyContactDismissMatch: {
@@ -1991,6 +2032,43 @@ export const schemaDict = {
1991
2032
  },
1992
2033
  },
1993
2034
  },
2035
+ AppBskyContactSendNotification: {
2036
+ lexicon: 1,
2037
+ id: 'app.bsky.contact.sendNotification',
2038
+ defs: {
2039
+ main: {
2040
+ type: 'procedure',
2041
+ description:
2042
+ "WARNING: This is unstable and under active development, don't use it while this warning is here. System endpoint to send notifications related to contact imports. Requires role authentication.",
2043
+ input: {
2044
+ encoding: 'application/json',
2045
+ schema: {
2046
+ type: 'object',
2047
+ required: ['from', 'to'],
2048
+ properties: {
2049
+ from: {
2050
+ description: 'The DID of who this notification comes from.',
2051
+ type: 'string',
2052
+ format: 'did',
2053
+ },
2054
+ to: {
2055
+ description: 'The DID of who this notification should go to.',
2056
+ type: 'string',
2057
+ format: 'did',
2058
+ },
2059
+ },
2060
+ },
2061
+ },
2062
+ output: {
2063
+ encoding: 'application/json',
2064
+ schema: {
2065
+ type: 'object',
2066
+ properties: {},
2067
+ },
2068
+ },
2069
+ },
2070
+ },
2071
+ },
1994
2072
  AppBskyContactStartPhoneVerification: {
1995
2073
  lexicon: 1,
1996
2074
  id: 'app.bsky.contact.startPhoneVerification',
@@ -6725,6 +6803,7 @@ export const schemaDict = {
6725
6803
  'like-via-repost',
6726
6804
  'repost-via-repost',
6727
6805
  'subscribed-post',
6806
+ 'contact-match',
6728
6807
  ],
6729
6808
  },
6730
6809
  reasonSubject: {
@@ -14793,6 +14872,7 @@ export const ids = {
14793
14872
  AppBskyContactGetSyncStatus: 'app.bsky.contact.getSyncStatus',
14794
14873
  AppBskyContactImportContacts: 'app.bsky.contact.importContacts',
14795
14874
  AppBskyContactRemoveData: 'app.bsky.contact.removeData',
14875
+ AppBskyContactSendNotification: 'app.bsky.contact.sendNotification',
14796
14876
  AppBskyContactStartPhoneVerification:
14797
14877
  'app.bsky.contact.startPhoneVerification',
14798
14878
  AppBskyContactVerifyPhone: 'app.bsky.contact.verifyPhone',
@@ -259,6 +259,7 @@ export type Preferences = (
259
259
  | $Typed<SavedFeedsPref>
260
260
  | $Typed<SavedFeedsPrefV2>
261
261
  | $Typed<PersonalDetailsPref>
262
+ | $Typed<DeclaredAgePref>
262
263
  | $Typed<FeedViewPref>
263
264
  | $Typed<ThreadViewPref>
264
265
  | $Typed<InterestsPref>
@@ -370,6 +371,27 @@ export function validatePersonalDetailsPref<V>(v: V) {
370
371
  return validate<PersonalDetailsPref & V>(v, id, hashPersonalDetailsPref)
371
372
  }
372
373
 
374
+ /** Read-only preference containing value(s) inferred from the user's declared birthdate. Absence of this preference object in the response indicates that the user has not made a declaration. */
375
+ export interface DeclaredAgePref {
376
+ $type?: 'app.bsky.actor.defs#declaredAgePref'
377
+ /** Indicates if the user has declared that they are over 13 years of age. */
378
+ isOverAge13?: boolean
379
+ /** Indicates if the user has declared that they are over 16 years of age. */
380
+ isOverAge16?: boolean
381
+ /** Indicates if the user has declared that they are over 18 years of age. */
382
+ isOverAge18?: boolean
383
+ }
384
+
385
+ const hashDeclaredAgePref = 'declaredAgePref'
386
+
387
+ export function isDeclaredAgePref<V>(v: V) {
388
+ return is$typed(v, id, hashDeclaredAgePref)
389
+ }
390
+
391
+ export function validateDeclaredAgePref<V>(v: V) {
392
+ return validate<DeclaredAgePref & V>(v, id, hashDeclaredAgePref)
393
+ }
394
+
373
395
  export interface FeedViewPref {
374
396
  $type?: 'app.bsky.actor.defs#feedViewPref'
375
397
  /** The URI of the feed, or an identifier which describes the feed. */
@@ -50,3 +50,22 @@ export function isSyncStatus<V>(v: V) {
50
50
  export function validateSyncStatus<V>(v: V) {
51
51
  return validate<SyncStatus & V>(v, id, hashSyncStatus)
52
52
  }
53
+
54
+ /** A stash object to be sent via bsync representing a notification to be created. */
55
+ export interface Notification {
56
+ $type?: 'app.bsky.contact.defs#notification'
57
+ /** The DID of who this notification comes from. */
58
+ from: string
59
+ /** The DID of who this notification should go to. */
60
+ to: string
61
+ }
62
+
63
+ const hashNotification = 'notification'
64
+
65
+ export function isNotification<V>(v: V) {
66
+ return is$typed(v, id, hashNotification)
67
+ }
68
+
69
+ export function validateNotification<V>(v: V) {
70
+ return validate<Notification & V>(v, id, hashNotification)
71
+ }