@hypercerts-org/sdk-core 0.9.0-beta.0 → 0.10.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  var oauthClientNode = require('@atproto/oauth-client-node');
4
+ var zod = require('zod');
4
5
  var lexicon$1 = require('@atproto/lexicon');
5
6
  var lexicon = require('@hypercerts-org/lexicon');
6
7
  var api = require('@atproto/api');
7
8
  var eventemitter3 = require('eventemitter3');
8
- var zod = require('zod');
9
9
 
10
10
  /**
11
11
  * Base error class for all SDK errors.
@@ -523,6 +523,990 @@ class InMemoryStateStore {
523
523
  }
524
524
  }
525
525
 
526
+ /**
527
+ * OAuth Scopes and Granular Permissions
528
+ *
529
+ * This module provides type-safe, Zod-validated OAuth scope and permission management
530
+ * for the ATProto SDK. It supports both legacy transitional scopes and the new
531
+ * granular permissions model.
532
+ *
533
+ * @see https://atproto.com/specs/oauth
534
+ * @see https://atproto.com/specs/permission
535
+ *
536
+ * @module auth/permissions
537
+ */
538
+ /**
539
+ * Base OAuth scope - required for all sessions
540
+ *
541
+ * @constant
542
+ */
543
+ const ATPROTO_SCOPE = "atproto";
544
+ /**
545
+ * Transitional OAuth scopes for legacy compatibility.
546
+ *
547
+ * These scopes provide broad access and are maintained for backwards compatibility.
548
+ * New applications should use granular permissions instead.
549
+ *
550
+ * @deprecated Use granular permissions (account:*, repo:*, etc.) for better control
551
+ * @constant
552
+ */
553
+ const TRANSITION_SCOPES = {
554
+ /** Broad PDS permissions including record creation, blob uploads, and preferences */
555
+ GENERIC: "transition:generic",
556
+ /** Direct messages access (requires transition:generic) */
557
+ CHAT: "transition:chat.bsky",
558
+ /** Email address and confirmation status */
559
+ EMAIL: "transition:email",
560
+ };
561
+ /**
562
+ * Zod schema for transitional scopes.
563
+ *
564
+ * Validates that a scope string is one of the known transitional scopes.
565
+ *
566
+ * @example
567
+ * ```typescript
568
+ * TransitionScopeSchema.parse('transition:email'); // Valid
569
+ * TransitionScopeSchema.parse('invalid'); // Throws ZodError
570
+ * ```
571
+ */
572
+ const TransitionScopeSchema = zod.z
573
+ .enum(["transition:generic", "transition:chat.bsky", "transition:email"])
574
+ .describe("Legacy transitional OAuth scopes");
575
+ /**
576
+ * Zod schema for account permission attributes.
577
+ *
578
+ * Account attributes specify what aspect of the account is being accessed.
579
+ */
580
+ const AccountAttrSchema = zod.z.enum(["email", "repo"]);
581
+ /**
582
+ * Zod schema for account actions.
583
+ *
584
+ * Account actions specify the level of access (read-only or management).
585
+ */
586
+ const AccountActionSchema = zod.z.enum(["read", "manage"]);
587
+ /**
588
+ * Zod schema for repository actions.
589
+ *
590
+ * Repository actions specify what operations can be performed on records.
591
+ */
592
+ const RepoActionSchema = zod.z.enum(["create", "update", "delete"]);
593
+ /**
594
+ * Zod schema for identity permission attributes.
595
+ *
596
+ * Identity attributes specify what identity information can be managed.
597
+ */
598
+ const IdentityAttrSchema = zod.z.enum(["handle", "*"]);
599
+ /**
600
+ * Zod schema for MIME type patterns.
601
+ *
602
+ * Validates MIME type strings like "image/*" or "video/mp4".
603
+ *
604
+ * @example
605
+ * ```typescript
606
+ * MimeTypeSchema.parse('image/*'); // Valid
607
+ * MimeTypeSchema.parse('video/mp4'); // Valid
608
+ * MimeTypeSchema.parse('invalid'); // Throws ZodError
609
+ * ```
610
+ */
611
+ const MimeTypeSchema = zod.z
612
+ .string()
613
+ .regex(/^[a-z]+\/[a-z0-9*+-]+$/i, 'Invalid MIME type pattern. Expected format: type/subtype (e.g., "image/*" or "video/mp4")');
614
+ /**
615
+ * Zod schema for NSID (Namespaced Identifier).
616
+ *
617
+ * NSIDs are reverse-DNS style identifiers used throughout ATProto
618
+ * (e.g., "app.bsky.feed.post" or "com.example.myrecord").
619
+ *
620
+ * @see https://atproto.com/specs/nsid
621
+ *
622
+ * @example
623
+ * ```typescript
624
+ * NsidSchema.parse('app.bsky.feed.post'); // Valid
625
+ * NsidSchema.parse('com.example.myrecord'); // Valid
626
+ * NsidSchema.parse('InvalidNSID'); // Throws ZodError
627
+ * ```
628
+ */
629
+ const NsidSchema = zod.z
630
+ .string()
631
+ .regex(/^[a-zA-Z][a-zA-Z0-9-]*(\.[a-zA-Z][a-zA-Z0-9-]*)+$/, 'Invalid NSID format. Expected reverse-DNS format (e.g., "app.bsky.feed.post")');
632
+ /**
633
+ * Zod schema for account permission.
634
+ *
635
+ * Account permissions control access to account-level information like email
636
+ * and repository management.
637
+ *
638
+ * @example Without action (read-only)
639
+ * ```typescript
640
+ * const input = { type: 'account', attr: 'email' };
641
+ * AccountPermissionSchema.parse(input); // Returns: "account:email"
642
+ * ```
643
+ *
644
+ * @example With action
645
+ * ```typescript
646
+ * const input = { type: 'account', attr: 'email', action: 'manage' };
647
+ * AccountPermissionSchema.parse(input); // Returns: "account:email?action=manage"
648
+ * ```
649
+ */
650
+ const AccountPermissionSchema = zod.z
651
+ .object({
652
+ type: zod.z.literal("account"),
653
+ attr: AccountAttrSchema,
654
+ action: AccountActionSchema.optional(),
655
+ })
656
+ .transform(({ attr, action }) => {
657
+ let perm = `account:${attr}`;
658
+ if (action) {
659
+ perm += `?action=${action}`;
660
+ }
661
+ return perm;
662
+ });
663
+ /**
664
+ * Zod schema for repository permission.
665
+ *
666
+ * Repository permissions control write access to records by collection type.
667
+ * The collection must be a valid NSID or wildcard (*).
668
+ *
669
+ * @example Without actions (all actions allowed)
670
+ * ```typescript
671
+ * const input = { type: 'repo', collection: 'app.bsky.feed.post' };
672
+ * RepoPermissionSchema.parse(input); // Returns: "repo:app.bsky.feed.post"
673
+ * ```
674
+ *
675
+ * @example With specific actions
676
+ * ```typescript
677
+ * const input = {
678
+ * type: 'repo',
679
+ * collection: 'app.bsky.feed.post',
680
+ * actions: ['create', 'update']
681
+ * };
682
+ * RepoPermissionSchema.parse(input); // Returns: "repo:app.bsky.feed.post?action=create&action=update"
683
+ * ```
684
+ *
685
+ * @example With wildcard collection
686
+ * ```typescript
687
+ * const input = { type: 'repo', collection: '*', actions: ['delete'] };
688
+ * RepoPermissionSchema.parse(input); // Returns: "repo:*?action=delete"
689
+ * ```
690
+ */
691
+ const RepoPermissionSchema = zod.z
692
+ .object({
693
+ type: zod.z.literal("repo"),
694
+ collection: NsidSchema.or(zod.z.literal("*")),
695
+ actions: zod.z.array(RepoActionSchema).optional(),
696
+ })
697
+ .transform(({ collection, actions }) => {
698
+ let perm = `repo:${collection}`;
699
+ if (actions && actions.length > 0) {
700
+ const params = actions.map((a) => `action=${a}`).join("&");
701
+ perm += `?${params}`;
702
+ }
703
+ return perm;
704
+ });
705
+ /**
706
+ * Zod schema for blob permission.
707
+ *
708
+ * Blob permissions control media file uploads constrained by MIME type patterns.
709
+ *
710
+ * @example Single MIME type
711
+ * ```typescript
712
+ * const input = { type: 'blob', mimeTypes: ['image/*'] };
713
+ * BlobPermissionSchema.parse(input); // Returns: "blob:image/*"
714
+ * ```
715
+ *
716
+ * @example Multiple MIME types
717
+ * ```typescript
718
+ * const input = { type: 'blob', mimeTypes: ['image/*', 'video/*'] };
719
+ * BlobPermissionSchema.parse(input); // Returns: "blob?accept=image/*&accept=video/*"
720
+ * ```
721
+ */
722
+ const BlobPermissionSchema = zod.z
723
+ .object({
724
+ type: zod.z.literal("blob"),
725
+ mimeTypes: zod.z.array(MimeTypeSchema).min(1, "At least one MIME type required"),
726
+ })
727
+ .transform(({ mimeTypes }) => {
728
+ if (mimeTypes.length === 1) {
729
+ return `blob:${mimeTypes[0]}`;
730
+ }
731
+ const accepts = mimeTypes.map((t) => `accept=${encodeURIComponent(t)}`).join("&");
732
+ return `blob?${accepts}`;
733
+ });
734
+ /**
735
+ * Zod schema for RPC permission.
736
+ *
737
+ * RPC permissions control authenticated API calls to remote services.
738
+ * At least one of lexicon or aud must be restricted (both cannot be wildcards).
739
+ *
740
+ * @example Specific lexicon with wildcard audience
741
+ * ```typescript
742
+ * const input = {
743
+ * type: 'rpc',
744
+ * lexicon: 'com.atproto.repo.createRecord',
745
+ * aud: '*'
746
+ * };
747
+ * RpcPermissionSchema.parse(input);
748
+ * // Returns: "rpc:com.atproto.repo.createRecord?aud=*"
749
+ * ```
750
+ *
751
+ * @example With specific audience
752
+ * ```typescript
753
+ * const input = {
754
+ * type: 'rpc',
755
+ * lexicon: 'com.atproto.repo.createRecord',
756
+ * aud: 'did:web:api.example.com',
757
+ * inheritAud: true
758
+ * };
759
+ * RpcPermissionSchema.parse(input);
760
+ * // Returns: "rpc:com.atproto.repo.createRecord?aud=did%3Aweb%3Aapi.example.com&inheritAud=true"
761
+ * ```
762
+ */
763
+ const RpcPermissionSchema = zod.z
764
+ .object({
765
+ type: zod.z.literal("rpc"),
766
+ lexicon: NsidSchema.or(zod.z.literal("*")),
767
+ aud: zod.z.string().min(1, "Audience is required"),
768
+ inheritAud: zod.z.boolean().optional(),
769
+ })
770
+ .refine(({ lexicon, aud }) => lexicon !== "*" || aud !== "*", "At least one of lexicon or aud must be restricted (wildcards cannot both be used)")
771
+ .transform(({ lexicon, aud, inheritAud }) => {
772
+ let perm = `rpc:${lexicon}?aud=${encodeURIComponent(aud)}`;
773
+ if (inheritAud) {
774
+ perm += "&inheritAud=true";
775
+ }
776
+ return perm;
777
+ });
778
+ /**
779
+ * Zod schema for identity permission.
780
+ *
781
+ * Identity permissions control access to DID documents and handles.
782
+ *
783
+ * @example Handle management
784
+ * ```typescript
785
+ * const input = { type: 'identity', attr: 'handle' };
786
+ * IdentityPermissionSchema.parse(input); // Returns: "identity:handle"
787
+ * ```
788
+ *
789
+ * @example All identity attributes
790
+ * ```typescript
791
+ * const input = { type: 'identity', attr: '*' };
792
+ * IdentityPermissionSchema.parse(input); // Returns: "identity:*"
793
+ * ```
794
+ */
795
+ const IdentityPermissionSchema = zod.z
796
+ .object({
797
+ type: zod.z.literal("identity"),
798
+ attr: IdentityAttrSchema,
799
+ })
800
+ .transform(({ attr }) => `identity:${attr}`);
801
+ /**
802
+ * Zod schema for permission set inclusion.
803
+ *
804
+ * Include permissions reference permission sets bundled under a single NSID.
805
+ *
806
+ * @example Without audience
807
+ * ```typescript
808
+ * const input = { type: 'include', nsid: 'com.example.authBasicFeatures' };
809
+ * IncludePermissionSchema.parse(input);
810
+ * // Returns: "include:com.example.authBasicFeatures"
811
+ * ```
812
+ *
813
+ * @example With audience
814
+ * ```typescript
815
+ * const input = {
816
+ * type: 'include',
817
+ * nsid: 'com.example.authBasicFeatures',
818
+ * aud: 'did:web:api.example.com'
819
+ * };
820
+ * IncludePermissionSchema.parse(input);
821
+ * // Returns: "include:com.example.authBasicFeatures?aud=did%3Aweb%3Aapi.example.com"
822
+ * ```
823
+ */
824
+ const IncludePermissionSchema = zod.z
825
+ .object({
826
+ type: zod.z.literal("include"),
827
+ nsid: NsidSchema,
828
+ aud: zod.z.string().optional(),
829
+ })
830
+ .transform(({ nsid, aud }) => {
831
+ let perm = `include:${nsid}`;
832
+ if (aud) {
833
+ perm += `?aud=${encodeURIComponent(aud)}`;
834
+ }
835
+ return perm;
836
+ });
837
+ /**
838
+ * Union schema for all permission types.
839
+ *
840
+ * This schema accepts any of the supported permission types and validates
841
+ * them according to their specific rules.
842
+ */
843
+ const PermissionSchema = zod.z.union([
844
+ AccountPermissionSchema,
845
+ RepoPermissionSchema,
846
+ BlobPermissionSchema,
847
+ RpcPermissionSchema,
848
+ IdentityPermissionSchema,
849
+ IncludePermissionSchema,
850
+ ]);
851
+ /**
852
+ * Fluent builder for constructing OAuth permission arrays.
853
+ *
854
+ * This class provides a convenient, type-safe way to build arrays of permissions
855
+ * using method chaining.
856
+ *
857
+ * @example Basic usage
858
+ * ```typescript
859
+ * const builder = new PermissionBuilder()
860
+ * .accountEmail('read')
861
+ * .repoWrite('app.bsky.feed.post')
862
+ * .blob(['image/*', 'video/*']);
863
+ *
864
+ * const permissions = builder.build();
865
+ * // Returns: ['account:email?action=read', 'repo:app.bsky.feed.post?action=create&action=update', 'blob:image/*,video/*']
866
+ * ```
867
+ *
868
+ * @example With transitional scopes
869
+ * ```typescript
870
+ * const builder = new PermissionBuilder()
871
+ * .transition('email')
872
+ * .transition('generic');
873
+ *
874
+ * const scopes = builder.build();
875
+ * // Returns: ['transition:email', 'transition:generic']
876
+ * ```
877
+ */
878
+ class PermissionBuilder {
879
+ constructor() {
880
+ this.permissions = [];
881
+ }
882
+ /**
883
+ * Add a transitional scope.
884
+ *
885
+ * @param scope - The transitional scope name ('email', 'generic', or 'chat.bsky')
886
+ * @returns This builder for chaining
887
+ *
888
+ * @example
889
+ * ```typescript
890
+ * builder.transition('email').transition('generic');
891
+ * ```
892
+ */
893
+ transition(scope) {
894
+ const fullScope = `transition:${scope}`;
895
+ const validated = TransitionScopeSchema.parse(fullScope);
896
+ this.permissions.push(validated);
897
+ return this;
898
+ }
899
+ /**
900
+ * Add an account permission.
901
+ *
902
+ * @param attr - The account attribute ('email' or 'repo')
903
+ * @param action - Optional action ('read' or 'manage')
904
+ * @returns This builder for chaining
905
+ *
906
+ * @example
907
+ * ```typescript
908
+ * builder.accountEmail('read').accountRepo('manage');
909
+ * ```
910
+ */
911
+ account(attr, action) {
912
+ const permission = AccountPermissionSchema.parse({
913
+ type: "account",
914
+ attr,
915
+ action,
916
+ });
917
+ this.permissions.push(permission);
918
+ return this;
919
+ }
920
+ /**
921
+ * Convenience method for account:email permission.
922
+ *
923
+ * @param action - Optional action ('read' or 'manage')
924
+ * @returns This builder for chaining
925
+ *
926
+ * @example
927
+ * ```typescript
928
+ * builder.accountEmail('read');
929
+ * ```
930
+ */
931
+ accountEmail(action) {
932
+ return this.account("email", action);
933
+ }
934
+ /**
935
+ * Convenience method for account:repo permission.
936
+ *
937
+ * @param action - Optional action ('read' or 'manage')
938
+ * @returns This builder for chaining
939
+ *
940
+ * @example
941
+ * ```typescript
942
+ * builder.accountRepo('manage');
943
+ * ```
944
+ */
945
+ accountRepo(action) {
946
+ return this.account("repo", action);
947
+ }
948
+ /**
949
+ * Add a repository permission.
950
+ *
951
+ * @param collection - The NSID of the collection or '*' for all
952
+ * @param actions - Optional array of actions ('create', 'update', 'delete')
953
+ * @returns This builder for chaining
954
+ *
955
+ * @example
956
+ * ```typescript
957
+ * builder.repo('app.bsky.feed.post', ['create', 'update']);
958
+ * ```
959
+ */
960
+ repo(collection, actions) {
961
+ const permission = RepoPermissionSchema.parse({
962
+ type: "repo",
963
+ collection,
964
+ actions,
965
+ });
966
+ this.permissions.push(permission);
967
+ return this;
968
+ }
969
+ /**
970
+ * Convenience method for repository write permissions (create + update).
971
+ *
972
+ * @param collection - The NSID of the collection or '*' for all
973
+ * @returns This builder for chaining
974
+ *
975
+ * @example
976
+ * ```typescript
977
+ * builder.repoWrite('app.bsky.feed.post');
978
+ * ```
979
+ */
980
+ repoWrite(collection) {
981
+ return this.repo(collection, ["create", "update"]);
982
+ }
983
+ /**
984
+ * Convenience method for repository read permission (no actions).
985
+ *
986
+ * @param collection - The NSID of the collection or '*' for all
987
+ * @returns This builder for chaining
988
+ *
989
+ * @example
990
+ * ```typescript
991
+ * builder.repoRead('app.bsky.feed.post');
992
+ * ```
993
+ */
994
+ repoRead(collection) {
995
+ return this.repo(collection, []);
996
+ }
997
+ /**
998
+ * Convenience method for full repository permissions (create + update + delete).
999
+ *
1000
+ * @param collection - The NSID of the collection or '*' for all
1001
+ * @returns This builder for chaining
1002
+ *
1003
+ * @example
1004
+ * ```typescript
1005
+ * builder.repoFull('app.bsky.feed.post');
1006
+ * ```
1007
+ */
1008
+ repoFull(collection) {
1009
+ return this.repo(collection, ["create", "update", "delete"]);
1010
+ }
1011
+ /**
1012
+ * Add a blob permission.
1013
+ *
1014
+ * @param mimeTypes - Array of MIME types or a single MIME type
1015
+ * @returns This builder for chaining
1016
+ *
1017
+ * @example
1018
+ * ```typescript
1019
+ * builder.blob(['image/*', 'video/*']);
1020
+ * builder.blob('image/*');
1021
+ * ```
1022
+ */
1023
+ blob(mimeTypes) {
1024
+ const types = Array.isArray(mimeTypes) ? mimeTypes : [mimeTypes];
1025
+ const permission = BlobPermissionSchema.parse({
1026
+ type: "blob",
1027
+ mimeTypes: types,
1028
+ });
1029
+ this.permissions.push(permission);
1030
+ return this;
1031
+ }
1032
+ /**
1033
+ * Add an RPC permission.
1034
+ *
1035
+ * @param lexicon - The NSID of the lexicon or '*' for all
1036
+ * @param aud - The audience (DID or URL)
1037
+ * @param inheritAud - Whether to inherit audience
1038
+ * @returns This builder for chaining
1039
+ *
1040
+ * @example
1041
+ * ```typescript
1042
+ * builder.rpc('com.atproto.repo.createRecord', 'did:web:api.example.com');
1043
+ * ```
1044
+ */
1045
+ rpc(lexicon, aud, inheritAud) {
1046
+ const permission = RpcPermissionSchema.parse({
1047
+ type: "rpc",
1048
+ lexicon,
1049
+ aud,
1050
+ inheritAud,
1051
+ });
1052
+ this.permissions.push(permission);
1053
+ return this;
1054
+ }
1055
+ /**
1056
+ * Add an identity permission.
1057
+ *
1058
+ * @param attr - The identity attribute ('handle' or '*')
1059
+ * @returns This builder for chaining
1060
+ *
1061
+ * @example
1062
+ * ```typescript
1063
+ * builder.identity('handle');
1064
+ * ```
1065
+ */
1066
+ identity(attr) {
1067
+ const permission = IdentityPermissionSchema.parse({
1068
+ type: "identity",
1069
+ attr,
1070
+ });
1071
+ this.permissions.push(permission);
1072
+ return this;
1073
+ }
1074
+ /**
1075
+ * Add an include permission.
1076
+ *
1077
+ * @param nsid - The NSID of the scope set to include
1078
+ * @param aud - Optional audience restriction
1079
+ * @returns This builder for chaining
1080
+ *
1081
+ * @example
1082
+ * ```typescript
1083
+ * builder.include('com.example.authBasicFeatures');
1084
+ * ```
1085
+ */
1086
+ include(nsid, aud) {
1087
+ const permission = IncludePermissionSchema.parse({
1088
+ type: "include",
1089
+ nsid,
1090
+ aud,
1091
+ });
1092
+ this.permissions.push(permission);
1093
+ return this;
1094
+ }
1095
+ /**
1096
+ * Add a custom permission string directly (bypasses validation).
1097
+ *
1098
+ * Use this for testing or special cases where you need to add
1099
+ * a permission that doesn't fit the standard types.
1100
+ *
1101
+ * @param permission - The permission string
1102
+ * @returns This builder for chaining
1103
+ *
1104
+ * @example
1105
+ * ```typescript
1106
+ * builder.custom('atproto');
1107
+ * ```
1108
+ */
1109
+ custom(permission) {
1110
+ this.permissions.push(permission);
1111
+ return this;
1112
+ }
1113
+ /**
1114
+ * Add the base atproto scope.
1115
+ *
1116
+ * @returns This builder for chaining
1117
+ *
1118
+ * @example
1119
+ * ```typescript
1120
+ * builder.atproto();
1121
+ * ```
1122
+ */
1123
+ atproto() {
1124
+ this.permissions.push(ATPROTO_SCOPE);
1125
+ return this;
1126
+ }
1127
+ /**
1128
+ * Build and return the array of permission strings.
1129
+ *
1130
+ * @returns Array of permission strings
1131
+ *
1132
+ * @example
1133
+ * ```typescript
1134
+ * const permissions = builder.build();
1135
+ * ```
1136
+ */
1137
+ build() {
1138
+ return [...this.permissions];
1139
+ }
1140
+ /**
1141
+ * Clear all permissions from the builder.
1142
+ *
1143
+ * @returns This builder for chaining
1144
+ *
1145
+ * @example
1146
+ * ```typescript
1147
+ * builder.clear().accountEmail('read');
1148
+ * ```
1149
+ */
1150
+ clear() {
1151
+ this.permissions = [];
1152
+ return this;
1153
+ }
1154
+ /**
1155
+ * Get the current number of permissions.
1156
+ *
1157
+ * @returns The number of permissions
1158
+ *
1159
+ * @example
1160
+ * ```typescript
1161
+ * const count = builder.count();
1162
+ * ```
1163
+ */
1164
+ count() {
1165
+ return this.permissions.length;
1166
+ }
1167
+ }
1168
+ /**
1169
+ * Build a scope string from an array of permissions.
1170
+ *
1171
+ * This is a convenience function that joins permission strings with spaces,
1172
+ * which is the standard format for OAuth scope parameters.
1173
+ *
1174
+ * @param permissions - Array of permission strings
1175
+ * @returns Space-separated scope string
1176
+ *
1177
+ * @example
1178
+ * ```typescript
1179
+ * const permissions = ['account:email?action=read', 'repo:app.bsky.feed.post'];
1180
+ * const scope = buildScope(permissions);
1181
+ * // Returns: "account:email?action=read repo:app.bsky.feed.post"
1182
+ * ```
1183
+ */
1184
+ function buildScope(permissions) {
1185
+ return permissions.join(" ");
1186
+ }
1187
+ /**
1188
+ * Pre-built scope presets for common use cases.
1189
+ *
1190
+ * These presets provide ready-to-use permission sets for typical application scenarios.
1191
+ */
1192
+ const ScopePresets = {
1193
+ /**
1194
+ * Email access scope - allows reading user's email address.
1195
+ *
1196
+ * Includes:
1197
+ * - account:email?action=read
1198
+ *
1199
+ * @example
1200
+ * ```typescript
1201
+ * const scope = ScopePresets.EMAIL_READ;
1202
+ * // Use in OAuth flow to request email access
1203
+ * ```
1204
+ */
1205
+ EMAIL_READ: buildScope(new PermissionBuilder().accountEmail("read").build()),
1206
+ /**
1207
+ * Profile read scope - allows reading user's profile.
1208
+ *
1209
+ * Includes:
1210
+ * - repo:app.bsky.actor.profile (read-only)
1211
+ *
1212
+ * @example
1213
+ * ```typescript
1214
+ * const scope = ScopePresets.PROFILE_READ;
1215
+ * ```
1216
+ */
1217
+ PROFILE_READ: buildScope(new PermissionBuilder().repoRead("app.bsky.actor.profile").build()),
1218
+ /**
1219
+ * Profile write scope - allows updating user's profile.
1220
+ *
1221
+ * Includes:
1222
+ * - repo:app.bsky.actor.profile (create + update)
1223
+ *
1224
+ * @example
1225
+ * ```typescript
1226
+ * const scope = ScopePresets.PROFILE_WRITE;
1227
+ * ```
1228
+ */
1229
+ PROFILE_WRITE: buildScope(new PermissionBuilder().repoWrite("app.bsky.actor.profile").build()),
1230
+ /**
1231
+ * Post creation scope - allows creating and updating posts.
1232
+ *
1233
+ * Includes:
1234
+ * - repo:app.bsky.feed.post (create + update)
1235
+ *
1236
+ * @example
1237
+ * ```typescript
1238
+ * const scope = ScopePresets.POST_WRITE;
1239
+ * ```
1240
+ */
1241
+ POST_WRITE: buildScope(new PermissionBuilder().repoWrite("app.bsky.feed.post").build()),
1242
+ /**
1243
+ * Social interactions scope - allows liking, reposting, and following.
1244
+ *
1245
+ * Includes:
1246
+ * - repo:app.bsky.feed.like (create + update)
1247
+ * - repo:app.bsky.feed.repost (create + update)
1248
+ * - repo:app.bsky.graph.follow (create + update)
1249
+ *
1250
+ * @example
1251
+ * ```typescript
1252
+ * const scope = ScopePresets.SOCIAL_WRITE;
1253
+ * ```
1254
+ */
1255
+ SOCIAL_WRITE: buildScope(new PermissionBuilder()
1256
+ .repoWrite("app.bsky.feed.like")
1257
+ .repoWrite("app.bsky.feed.repost")
1258
+ .repoWrite("app.bsky.graph.follow")
1259
+ .build()),
1260
+ /**
1261
+ * Media upload scope - allows uploading images and videos.
1262
+ *
1263
+ * Includes:
1264
+ * - blob permissions for image/* and video/*
1265
+ *
1266
+ * @example
1267
+ * ```typescript
1268
+ * const scope = ScopePresets.MEDIA_UPLOAD;
1269
+ * ```
1270
+ */
1271
+ MEDIA_UPLOAD: buildScope(new PermissionBuilder().blob(["image/*", "video/*"]).build()),
1272
+ /**
1273
+ * Image upload only scope - allows uploading images.
1274
+ *
1275
+ * Includes:
1276
+ * - blob:image/*
1277
+ *
1278
+ * @example
1279
+ * ```typescript
1280
+ * const scope = ScopePresets.IMAGE_UPLOAD;
1281
+ * ```
1282
+ */
1283
+ IMAGE_UPLOAD: buildScope(new PermissionBuilder().blob("image/*").build()),
1284
+ /**
1285
+ * Posting app scope - full posting capabilities including media.
1286
+ *
1287
+ * Includes:
1288
+ * - repo:app.bsky.feed.post (create + update)
1289
+ * - repo:app.bsky.feed.like (create + update)
1290
+ * - repo:app.bsky.feed.repost (create + update)
1291
+ * - blob permissions for image/* and video/*
1292
+ *
1293
+ * @example
1294
+ * ```typescript
1295
+ * const scope = ScopePresets.POSTING_APP;
1296
+ * ```
1297
+ */
1298
+ POSTING_APP: buildScope(new PermissionBuilder()
1299
+ .repoWrite("app.bsky.feed.post")
1300
+ .repoWrite("app.bsky.feed.like")
1301
+ .repoWrite("app.bsky.feed.repost")
1302
+ .blob(["image/*", "video/*"])
1303
+ .build()),
1304
+ /**
1305
+ * Read-only app scope - allows reading all repository data.
1306
+ *
1307
+ * Includes:
1308
+ * - repo:* (read-only, no actions)
1309
+ *
1310
+ * @example
1311
+ * ```typescript
1312
+ * const scope = ScopePresets.READ_ONLY;
1313
+ * ```
1314
+ */
1315
+ READ_ONLY: buildScope(new PermissionBuilder().repoRead("*").build()),
1316
+ /**
1317
+ * Full access scope - allows all repository operations.
1318
+ *
1319
+ * Includes:
1320
+ * - repo:* (create + update + delete)
1321
+ *
1322
+ * @example
1323
+ * ```typescript
1324
+ * const scope = ScopePresets.FULL_ACCESS;
1325
+ * ```
1326
+ */
1327
+ FULL_ACCESS: buildScope(new PermissionBuilder().repoFull("*").build()),
1328
+ /**
1329
+ * Email + Profile scope - common combination for user identification.
1330
+ *
1331
+ * Includes:
1332
+ * - account:email?action=read
1333
+ * - repo:app.bsky.actor.profile (read-only)
1334
+ *
1335
+ * @example
1336
+ * ```typescript
1337
+ * const scope = ScopePresets.EMAIL_AND_PROFILE;
1338
+ * ```
1339
+ */
1340
+ EMAIL_AND_PROFILE: buildScope(new PermissionBuilder().accountEmail("read").repoRead("app.bsky.actor.profile").build()),
1341
+ /**
1342
+ * Transitional email scope (legacy).
1343
+ *
1344
+ * Uses the transitional scope format for backward compatibility.
1345
+ *
1346
+ * @example
1347
+ * ```typescript
1348
+ * const scope = ScopePresets.TRANSITION_EMAIL;
1349
+ * ```
1350
+ */
1351
+ TRANSITION_EMAIL: buildScope(new PermissionBuilder().transition("email").build()),
1352
+ /**
1353
+ * Transitional generic scope (legacy).
1354
+ *
1355
+ * Uses the transitional scope format for backward compatibility.
1356
+ *
1357
+ * @example
1358
+ * ```typescript
1359
+ * const scope = ScopePresets.TRANSITION_GENERIC;
1360
+ * ```
1361
+ */
1362
+ TRANSITION_GENERIC: buildScope(new PermissionBuilder().transition("generic").build()),
1363
+ };
1364
+ /**
1365
+ * Parse a scope string into an array of individual permissions.
1366
+ *
1367
+ * This splits a space-separated scope string into individual permission strings.
1368
+ *
1369
+ * @param scope - Space-separated scope string
1370
+ * @returns Array of permission strings
1371
+ *
1372
+ * @example
1373
+ * ```typescript
1374
+ * const scope = "account:email?action=read repo:app.bsky.feed.post";
1375
+ * const permissions = parseScope(scope);
1376
+ * // Returns: ['account:email?action=read', 'repo:app.bsky.feed.post']
1377
+ * ```
1378
+ */
1379
+ function parseScope(scope) {
1380
+ return scope.trim().split(/\s+/).filter(Boolean);
1381
+ }
1382
+ /**
1383
+ * Check if a scope string contains a specific permission.
1384
+ *
1385
+ * This function performs exact string matching. For more advanced
1386
+ * permission checking (e.g., wildcard matching), you'll need to
1387
+ * implement custom logic.
1388
+ *
1389
+ * @param scope - Space-separated scope string
1390
+ * @param permission - The permission to check for
1391
+ * @returns True if the scope contains the permission
1392
+ *
1393
+ * @example
1394
+ * ```typescript
1395
+ * const scope = "account:email?action=read repo:app.bsky.feed.post";
1396
+ * hasPermission(scope, "account:email?action=read"); // true
1397
+ * hasPermission(scope, "account:repo"); // false
1398
+ * ```
1399
+ */
1400
+ function hasPermission(scope, permission) {
1401
+ const permissions = parseScope(scope);
1402
+ return permissions.includes(permission);
1403
+ }
1404
+ /**
1405
+ * Check if a scope string contains all of the specified permissions.
1406
+ *
1407
+ * @param scope - Space-separated scope string
1408
+ * @param requiredPermissions - Array of permissions to check for
1409
+ * @returns True if the scope contains all required permissions
1410
+ *
1411
+ * @example
1412
+ * ```typescript
1413
+ * const scope = "account:email?action=read repo:app.bsky.feed.post blob:image/*";
1414
+ * hasAllPermissions(scope, ["account:email?action=read", "blob:image/*"]); // true
1415
+ * hasAllPermissions(scope, ["account:email?action=read", "account:repo"]); // false
1416
+ * ```
1417
+ */
1418
+ function hasAllPermissions(scope, requiredPermissions) {
1419
+ const permissions = parseScope(scope);
1420
+ return requiredPermissions.every((required) => permissions.includes(required));
1421
+ }
1422
+ /**
1423
+ * Check if a scope string contains any of the specified permissions.
1424
+ *
1425
+ * @param scope - Space-separated scope string
1426
+ * @param checkPermissions - Array of permissions to check for
1427
+ * @returns True if the scope contains at least one of the permissions
1428
+ *
1429
+ * @example
1430
+ * ```typescript
1431
+ * const scope = "account:email?action=read repo:app.bsky.feed.post";
1432
+ * hasAnyPermission(scope, ["account:email?action=read", "account:repo"]); // true
1433
+ * hasAnyPermission(scope, ["account:repo", "identity:handle"]); // false
1434
+ * ```
1435
+ */
1436
+ function hasAnyPermission(scope, checkPermissions) {
1437
+ const permissions = parseScope(scope);
1438
+ return checkPermissions.some((check) => permissions.includes(check));
1439
+ }
1440
+ /**
1441
+ * Merge multiple scope strings into a single scope string with deduplicated permissions.
1442
+ *
1443
+ * @param scopes - Array of scope strings to merge
1444
+ * @returns Merged scope string with unique permissions
1445
+ *
1446
+ * @example
1447
+ * ```typescript
1448
+ * const scope1 = "account:email?action=read repo:app.bsky.feed.post";
1449
+ * const scope2 = "repo:app.bsky.feed.post blob:image/*";
1450
+ * const merged = mergeScopes([scope1, scope2]);
1451
+ * // Returns: "account:email?action=read repo:app.bsky.feed.post blob:image/*"
1452
+ * ```
1453
+ */
1454
+ function mergeScopes(scopes) {
1455
+ const allPermissions = scopes.flatMap(parseScope);
1456
+ const uniquePermissions = [...new Set(allPermissions)];
1457
+ return buildScope(uniquePermissions);
1458
+ }
1459
+ /**
1460
+ * Remove specific permissions from a scope string.
1461
+ *
1462
+ * @param scope - Space-separated scope string
1463
+ * @param permissionsToRemove - Array of permissions to remove
1464
+ * @returns New scope string without the specified permissions
1465
+ *
1466
+ * @example
1467
+ * ```typescript
1468
+ * const scope = "account:email?action=read repo:app.bsky.feed.post blob:image/*";
1469
+ * const filtered = removePermissions(scope, ["blob:image/*"]);
1470
+ * // Returns: "account:email?action=read repo:app.bsky.feed.post"
1471
+ * ```
1472
+ */
1473
+ function removePermissions(scope, permissionsToRemove) {
1474
+ const permissions = parseScope(scope);
1475
+ const filtered = permissions.filter((p) => !permissionsToRemove.includes(p));
1476
+ return buildScope(filtered);
1477
+ }
1478
+ /**
1479
+ * Validate that all permissions in a scope string are well-formed.
1480
+ *
1481
+ * This checks that each permission matches expected patterns for transitional
1482
+ * or granular permissions. It does NOT validate against the full Zod schemas.
1483
+ *
1484
+ * @param scope - Space-separated scope string
1485
+ * @returns Object with isValid flag and array of invalid permissions
1486
+ *
1487
+ * @example
1488
+ * ```typescript
1489
+ * const scope = "account:email?action=read invalid:permission";
1490
+ * const result = validateScope(scope);
1491
+ * // Returns: { isValid: false, invalidPermissions: ['invalid:permission'] }
1492
+ * ```
1493
+ */
1494
+ function validateScope(scope) {
1495
+ const permissions = parseScope(scope);
1496
+ const invalidPermissions = [];
1497
+ // Pattern for valid permission prefixes
1498
+ const validPrefixes = /^(atproto|transition:|account:|repo:|blob:?|rpc:|identity:|include:)/;
1499
+ for (const permission of permissions) {
1500
+ if (!validPrefixes.test(permission)) {
1501
+ invalidPermissions.push(permission);
1502
+ }
1503
+ }
1504
+ return {
1505
+ isValid: invalidPermissions.length === 0,
1506
+ invalidPermissions,
1507
+ };
1508
+ }
1509
+
526
1510
  /**
527
1511
  * OAuth 2.0 client for AT Protocol authentication with DPoP support.
528
1512
  *
@@ -657,7 +1641,7 @@ class OAuthClient {
657
1641
  */
658
1642
  buildClientMetadata() {
659
1643
  const clientIdUrl = new URL(this.config.oauth.clientId);
660
- return {
1644
+ const metadata = {
661
1645
  client_id: this.config.oauth.clientId,
662
1646
  client_name: "ATProto SDK Client",
663
1647
  client_uri: clientIdUrl.origin,
@@ -671,6 +1655,77 @@ class OAuthClient {
671
1655
  dpop_bound_access_tokens: true,
672
1656
  jwks_uri: this.config.oauth.jwksUri,
673
1657
  };
1658
+ // Validate scope before returning metadata
1659
+ this.validateClientMetadataScope(metadata.scope);
1660
+ return metadata;
1661
+ }
1662
+ /**
1663
+ * Validates the OAuth scope in client metadata and logs warnings/suggestions.
1664
+ *
1665
+ * This method:
1666
+ * 1. Checks if the scope is well-formed using permission utilities
1667
+ * 2. Detects mixing of transitional and granular permissions
1668
+ * 3. Logs warnings for missing `atproto` scope
1669
+ * 4. Suggests migration to granular permissions for transitional scopes
1670
+ *
1671
+ * @param scope - The OAuth scope string to validate
1672
+ * @internal
1673
+ */
1674
+ validateClientMetadataScope(scope) {
1675
+ // Parse the scope into individual permissions
1676
+ const permissions = parseScope(scope);
1677
+ // Validate well-formedness
1678
+ const validation = validateScope(scope);
1679
+ if (!validation.isValid) {
1680
+ this.logger?.error("Invalid OAuth scope detected", {
1681
+ invalidPermissions: validation.invalidPermissions,
1682
+ scope,
1683
+ });
1684
+ }
1685
+ // Check for atproto scope
1686
+ const hasAtproto = permissions.includes(ATPROTO_SCOPE);
1687
+ if (!hasAtproto) {
1688
+ this.logger?.warn("OAuth scope missing 'atproto' - basic API access may be limited", {
1689
+ scope,
1690
+ suggestion: "Add 'atproto' to your scope for basic API access",
1691
+ });
1692
+ }
1693
+ // Detect transitional scopes
1694
+ const transitionalScopes = permissions.filter((p) => p.startsWith("transition:"));
1695
+ const granularScopes = permissions.filter((p) => p.startsWith("account:") ||
1696
+ p.startsWith("repo:") ||
1697
+ p.startsWith("blob") ||
1698
+ p.startsWith("rpc:") ||
1699
+ p.startsWith("identity:") ||
1700
+ p.startsWith("include:"));
1701
+ // Log info about transitional scopes
1702
+ if (transitionalScopes.length > 0) {
1703
+ this.logger?.info("Using transitional OAuth scopes (legacy)", {
1704
+ transitionalScopes,
1705
+ note: "Transitional scopes are supported but granular permissions are recommended",
1706
+ });
1707
+ // Suggest migration to granular permissions
1708
+ if (transitionalScopes.includes("transition:email")) {
1709
+ this.logger?.info("Consider migrating 'transition:email' to granular permissions", {
1710
+ suggestion: "Use: account:email?action=read",
1711
+ example: "import { ScopePresets } from '@hypercerts-org/sdk-core'; scope: ScopePresets.EMAIL_READ",
1712
+ });
1713
+ }
1714
+ if (transitionalScopes.includes("transition:generic")) {
1715
+ this.logger?.info("Consider migrating 'transition:generic' to granular permissions", {
1716
+ suggestion: "Use specific permissions like: repo:* account:repo?action=read",
1717
+ example: "import { ScopePresets } from '@hypercerts-org/sdk-core'; scope: ScopePresets.FULL_ACCESS",
1718
+ });
1719
+ }
1720
+ }
1721
+ // Warn if mixing transitional and granular
1722
+ if (transitionalScopes.length > 0 && granularScopes.length > 0) {
1723
+ this.logger?.warn("Mixing transitional and granular OAuth scopes", {
1724
+ transitionalScopes,
1725
+ granularScopes,
1726
+ note: "While supported, it's recommended to use either transitional or granular permissions consistently",
1727
+ });
1728
+ }
674
1729
  }
675
1730
  /**
676
1731
  * Creates a fetch handler with timeout support.
@@ -4183,9 +5238,34 @@ const OAuthConfigSchema = zod.z.object({
4183
5238
  redirectUri: zod.z.string().url(),
4184
5239
  /**
4185
5240
  * OAuth scopes to request, space-separated.
4186
- * Common scopes: "atproto", "transition:generic"
5241
+ *
5242
+ * Can be a string of space-separated permissions or use the permission system:
5243
+ *
5244
+ * @example Using presets
5245
+ * ```typescript
5246
+ * import { ScopePresets } from '@hypercerts-org/sdk-core';
5247
+ * scope: ScopePresets.EMAIL_AND_PROFILE
5248
+ * ```
5249
+ *
5250
+ * @example Building custom scopes
5251
+ * ```typescript
5252
+ * import { PermissionBuilder, buildScope } from '@hypercerts-org/sdk-core';
5253
+ * scope: buildScope(
5254
+ * new PermissionBuilder()
5255
+ * .accountEmail('read')
5256
+ * .repoWrite('app.bsky.feed.post')
5257
+ * .build()
5258
+ * )
5259
+ * ```
5260
+ *
5261
+ * @example Legacy scopes
5262
+ * ```typescript
5263
+ * scope: "atproto transition:generic"
5264
+ * ```
5265
+ *
5266
+ * @see https://atproto.com/specs/permission for permission details
4187
5267
  */
4188
- scope: zod.z.string(),
5268
+ scope: zod.z.string().min(1, "OAuth scope is required"),
4189
5269
  /**
4190
5270
  * URL to your public JWKS (JSON Web Key Set) endpoint.
4191
5271
  * Used by the authorization server to verify your client's signatures.
@@ -4477,6 +5557,91 @@ class ATProtoSDK {
4477
5557
  }
4478
5558
  return this.oauthClient.revoke(did.trim());
4479
5559
  }
5560
+ /**
5561
+ * Gets the account email address from the authenticated session.
5562
+ *
5563
+ * This method retrieves the email address associated with the user's account
5564
+ * by calling the `com.atproto.server.getSession` endpoint. The email will only
5565
+ * be returned if the appropriate OAuth scope was granted during authorization.
5566
+ *
5567
+ * Required OAuth scopes:
5568
+ * - **Granular permissions**: `account:email?action=read` or `account:email`
5569
+ * - **Transitional permissions**: `transition:email`
5570
+ *
5571
+ * @param session - An authenticated OAuth session
5572
+ * @returns A Promise resolving to email info, or `null` if permission not granted
5573
+ * @throws {@link ValidationError} if the session is invalid
5574
+ * @throws {@link NetworkError} if the API request fails
5575
+ *
5576
+ * @example Using granular permissions
5577
+ * ```typescript
5578
+ * import { ScopePresets } from '@hypercerts-org/sdk-core';
5579
+ *
5580
+ * // Authorize with email scope
5581
+ * const authUrl = await sdk.authorize("user.bsky.social", {
5582
+ * scope: ScopePresets.EMAIL_READ
5583
+ * });
5584
+ *
5585
+ * // After callback...
5586
+ * const emailInfo = await sdk.getAccountEmail(session);
5587
+ * if (emailInfo) {
5588
+ * console.log(`Email: ${emailInfo.email}`);
5589
+ * console.log(`Confirmed: ${emailInfo.emailConfirmed}`);
5590
+ * } else {
5591
+ * console.log("Email permission not granted");
5592
+ * }
5593
+ * ```
5594
+ *
5595
+ * @example Using transitional permissions (legacy)
5596
+ * ```typescript
5597
+ * // Authorize with transition:email scope
5598
+ * const authUrl = await sdk.authorize("user.bsky.social", {
5599
+ * scope: "atproto transition:email"
5600
+ * });
5601
+ *
5602
+ * // After callback...
5603
+ * const emailInfo = await sdk.getAccountEmail(session);
5604
+ * ```
5605
+ */
5606
+ async getAccountEmail(session) {
5607
+ if (!session) {
5608
+ throw new ValidationError("Session is required");
5609
+ }
5610
+ try {
5611
+ // Determine PDS URL from session or config
5612
+ const pdsUrl = this.config.servers?.pds;
5613
+ if (!pdsUrl) {
5614
+ throw new ValidationError("PDS server URL not configured");
5615
+ }
5616
+ // Call com.atproto.server.getSession endpoint using session's fetchHandler
5617
+ // which automatically includes proper authorization with DPoP
5618
+ const response = await session.fetchHandler("/xrpc/com.atproto.server.getSession", {
5619
+ method: "GET",
5620
+ headers: {
5621
+ "Content-Type": "application/json",
5622
+ },
5623
+ });
5624
+ if (!response.ok) {
5625
+ throw new NetworkError(`Failed to get session info: ${response.status} ${response.statusText}`);
5626
+ }
5627
+ const data = (await response.json());
5628
+ // Return null if email not present (permission not granted)
5629
+ if (!data.email) {
5630
+ return null;
5631
+ }
5632
+ return {
5633
+ email: data.email,
5634
+ emailConfirmed: data.emailConfirmed ?? false,
5635
+ };
5636
+ }
5637
+ catch (error) {
5638
+ this.logger?.error("Failed to get account email", { error });
5639
+ if (error instanceof ValidationError || error instanceof NetworkError) {
5640
+ throw error;
5641
+ }
5642
+ throw new NetworkError(`Failed to get account email: ${error instanceof Error ? error.message : String(error)}`, error);
5643
+ }
5644
+ }
4480
5645
  /**
4481
5646
  * Creates a repository instance for data operations.
4482
5647
  *
@@ -4791,24 +5956,50 @@ Object.defineProperty(exports, "validate", {
4791
5956
  enumerable: true,
4792
5957
  get: function () { return lexicon.validate; }
4793
5958
  });
5959
+ exports.ATPROTO_SCOPE = ATPROTO_SCOPE;
4794
5960
  exports.ATProtoSDK = ATProtoSDK;
4795
5961
  exports.ATProtoSDKConfigSchema = ATProtoSDKConfigSchema;
4796
5962
  exports.ATProtoSDKError = ATProtoSDKError;
5963
+ exports.AccountActionSchema = AccountActionSchema;
5964
+ exports.AccountAttrSchema = AccountAttrSchema;
5965
+ exports.AccountPermissionSchema = AccountPermissionSchema;
4797
5966
  exports.AuthenticationError = AuthenticationError;
5967
+ exports.BlobPermissionSchema = BlobPermissionSchema;
4798
5968
  exports.CollaboratorPermissionsSchema = CollaboratorPermissionsSchema;
4799
5969
  exports.CollaboratorSchema = CollaboratorSchema;
4800
5970
  exports.ConfigurableAgent = ConfigurableAgent;
5971
+ exports.IdentityAttrSchema = IdentityAttrSchema;
5972
+ exports.IdentityPermissionSchema = IdentityPermissionSchema;
4801
5973
  exports.InMemorySessionStore = InMemorySessionStore;
4802
5974
  exports.InMemoryStateStore = InMemoryStateStore;
5975
+ exports.IncludePermissionSchema = IncludePermissionSchema;
4803
5976
  exports.LexiconRegistry = LexiconRegistry;
5977
+ exports.MimeTypeSchema = MimeTypeSchema;
4804
5978
  exports.NetworkError = NetworkError;
5979
+ exports.NsidSchema = NsidSchema;
4805
5980
  exports.OAuthConfigSchema = OAuthConfigSchema;
4806
5981
  exports.OrganizationSchema = OrganizationSchema;
5982
+ exports.PermissionBuilder = PermissionBuilder;
5983
+ exports.PermissionSchema = PermissionSchema;
5984
+ exports.RepoActionSchema = RepoActionSchema;
5985
+ exports.RepoPermissionSchema = RepoPermissionSchema;
4807
5986
  exports.Repository = Repository;
5987
+ exports.RpcPermissionSchema = RpcPermissionSchema;
4808
5988
  exports.SDSRequiredError = SDSRequiredError;
5989
+ exports.ScopePresets = ScopePresets;
4809
5990
  exports.ServerConfigSchema = ServerConfigSchema;
4810
5991
  exports.SessionExpiredError = SessionExpiredError;
5992
+ exports.TRANSITION_SCOPES = TRANSITION_SCOPES;
4811
5993
  exports.TimeoutConfigSchema = TimeoutConfigSchema;
5994
+ exports.TransitionScopeSchema = TransitionScopeSchema;
4812
5995
  exports.ValidationError = ValidationError;
5996
+ exports.buildScope = buildScope;
4813
5997
  exports.createATProtoSDK = createATProtoSDK;
5998
+ exports.hasAllPermissions = hasAllPermissions;
5999
+ exports.hasAnyPermission = hasAnyPermission;
6000
+ exports.hasPermission = hasPermission;
6001
+ exports.mergeScopes = mergeScopes;
6002
+ exports.parseScope = parseScope;
6003
+ exports.removePermissions = removePermissions;
6004
+ exports.validateScope = validateScope;
4814
6005
  //# sourceMappingURL=index.cjs.map