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

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/README.md CHANGED
@@ -53,12 +53,14 @@ const claim = await repo.hypercerts.create({
53
53
  The SDK supports two types of AT Protocol servers:
54
54
 
55
55
  #### Personal Data Server (PDS)
56
+
56
57
  - **Purpose**: User's own data storage (e.g., Bluesky)
57
58
  - **Use case**: Individual hypercerts, personal records
58
59
  - **Features**: Profile management, basic CRUD operations
59
60
  - **Example**: `bsky.social`, any Bluesky PDS
60
61
 
61
62
  #### Shared Data Server (SDS)
63
+
62
64
  - **Purpose**: Collaborative data storage with access control
63
65
  - **Use case**: Organization hypercerts, team collaboration
64
66
  - **Features**: Organizations, multi-user access, role-based permissions
@@ -84,25 +86,27 @@ await orgRepo.hypercerts.list(); // Queries organization's hypercerts on SDS
84
86
  The SDK uses a `ConfigurableAgent` to route requests to different servers while maintaining your OAuth authentication:
85
87
 
86
88
  1. **Initial Repository Creation**
89
+
87
90
  ```typescript
88
91
  // User authenticates (OAuth session knows user's PDS)
89
92
  const session = await sdk.callback(params);
90
-
93
+
91
94
  // Create PDS repository - routes to user's PDS
92
95
  const pdsRepo = sdk.repository(session);
93
-
96
+
94
97
  // Create SDS repository - routes to SDS server
95
98
  const sdsRepo = sdk.repository(session, { server: "sds" });
96
99
  ```
97
100
 
98
101
  2. **Switching Repositories with `.repo()`**
102
+
99
103
  ```typescript
100
104
  // Start with user's SDS repository
101
105
  const userSdsRepo = sdk.repository(session, { server: "sds" });
102
-
106
+
103
107
  // Switch to organization's repository
104
108
  const orgRepo = userSdsRepo.repo("did:plc:org-did");
105
-
109
+
106
110
  // All operations on orgRepo still route to SDS, not user's PDS
107
111
  await orgRepo.hypercerts.list(); // ✅ Queries SDS
108
112
  await orgRepo.collaborators.list(); // ✅ Queries SDS
@@ -141,9 +145,11 @@ const orgRepo = sdsRepo.repo(organizationDid);
141
145
  // Teammate can now access orgRepo and create hypercerts
142
146
  ```
143
147
 
144
- ### 2. Authentication
148
+ ### 2. Authentication & OAuth Permissions
149
+
150
+ The SDK uses OAuth 2.0 for authentication with granular permission control.
145
151
 
146
- The SDK uses OAuth 2.0 for authentication with support for both PDS (Personal Data Server) and SDS (Shared Data Server).
152
+ #### Basic Authentication
147
153
 
148
154
  ```typescript
149
155
  // First-time user authentication
@@ -164,6 +170,49 @@ const session = await sdk.restoreSession("did:plc:user123");
164
170
  const repo = sdk.getRepository(session);
165
171
  ```
166
172
 
173
+ #### OAuth Scopes & Permissions
174
+
175
+ Control exactly what your app can access using type-safe permission builders:
176
+
177
+ ```typescript
178
+ import { PermissionBuilder, ScopePresets, buildScope } from "@hypercerts-org/sdk-core";
179
+
180
+ // Use ready-made presets
181
+ const scope = ScopePresets.EMAIL_AND_PROFILE; // Request email + profile access
182
+ const scope = ScopePresets.POSTING_APP; // Full posting capabilities
183
+
184
+ // Or build custom permissions
185
+ const scope = buildScope(
186
+ new PermissionBuilder()
187
+ .accountEmail("read") // Read user's email
188
+ .repoWrite("app.bsky.feed.post") // Create/update posts
189
+ .blob(["image/*", "video/*"]) // Upload media
190
+ .build(),
191
+ );
192
+
193
+ // Use in OAuth configuration
194
+ const sdk = createATProtoSDK({
195
+ oauth: {
196
+ clientId: "your-client-id",
197
+ redirectUri: "https://your-app.com/callback",
198
+ scope: scope, // Your custom scope
199
+ // ... other config
200
+ },
201
+ });
202
+ ```
203
+
204
+ **Available Presets:**
205
+
206
+ - `EMAIL_READ` - User's email address
207
+ - `PROFILE_READ` / `PROFILE_WRITE` - Profile access
208
+ - `POST_WRITE` - Create posts
209
+ - `SOCIAL_WRITE` - Likes, reposts, follows
210
+ - `MEDIA_UPLOAD` - Image and video uploads
211
+ - `POSTING_APP` - Full posting with media
212
+ - `EMAIL_AND_PROFILE` - Common combination
213
+
214
+ See [OAuth Permissions Documentation](./docs/implementations/atproto_oauth_scopes.md) for detailed usage.
215
+
167
216
  ### 3. Working with Hypercerts
168
217
 
169
218
  #### Creating a Hypercert
@@ -174,7 +223,7 @@ const hypercert = await repo.hypercerts.create({
174
223
  description: "Research on carbon capture technologies",
175
224
  image: imageBlob, // optional: File or Blob
176
225
  externalUrl: "https://example.com/project",
177
-
226
+
178
227
  impact: {
179
228
  scope: ["Climate Change", "Carbon Capture"],
180
229
  work: {
@@ -183,7 +232,7 @@ const hypercert = await repo.hypercerts.create({
183
232
  },
184
233
  contributors: ["did:plc:researcher1", "did:plc:researcher2"],
185
234
  },
186
-
235
+
187
236
  rights: {
188
237
  license: "CC-BY-4.0",
189
238
  allowsDerivatives: true,
@@ -198,9 +247,7 @@ console.log("Created hypercert:", hypercert.uri);
198
247
 
199
248
  ```typescript
200
249
  // Get a specific hypercert by URI
201
- const hypercert = await repo.hypercerts.get(
202
- "at://did:plc:user123/org.hypercerts.claim/abc123"
203
- );
250
+ const hypercert = await repo.hypercerts.get("at://did:plc:user123/org.hypercerts.claim/abc123");
204
251
 
205
252
  // List all hypercerts in the repository
206
253
  const { records } = await repo.hypercerts.list();
@@ -219,26 +266,21 @@ if (cursor) {
219
266
 
220
267
  ```typescript
221
268
  // Update an existing hypercert
222
- await repo.hypercerts.update(
223
- "at://did:plc:user123/org.hypercerts.claim/abc123",
224
- {
225
- title: "Updated Climate Research Project",
226
- description: "Expanded scope to include renewable energy",
227
- impact: {
228
- scope: ["Climate Change", "Carbon Capture", "Renewable Energy"],
229
- work: { from: "2024-01-01", to: "2026-12-31" },
230
- contributors: ["did:plc:researcher1", "did:plc:researcher2"],
231
- },
232
- }
233
- );
269
+ await repo.hypercerts.update("at://did:plc:user123/org.hypercerts.claim/abc123", {
270
+ title: "Updated Climate Research Project",
271
+ description: "Expanded scope to include renewable energy",
272
+ impact: {
273
+ scope: ["Climate Change", "Carbon Capture", "Renewable Energy"],
274
+ work: { from: "2024-01-01", to: "2026-12-31" },
275
+ contributors: ["did:plc:researcher1", "did:plc:researcher2"],
276
+ },
277
+ });
234
278
  ```
235
279
 
236
280
  #### Deleting a Hypercert
237
281
 
238
282
  ```typescript
239
- await repo.hypercerts.delete(
240
- "at://did:plc:user123/org.hypercerts.claim/abc123"
241
- );
283
+ await repo.hypercerts.delete("at://did:plc:user123/org.hypercerts.claim/abc123");
242
284
  ```
243
285
 
244
286
  ### 4. Contributions and Measurements
@@ -279,10 +321,7 @@ const blobResult = await repo.blobs.upload(imageFile);
279
321
  console.log("Blob uploaded:", blobResult.ref.$link);
280
322
 
281
323
  // Download a blob
282
- const blobData = await repo.blobs.get(
283
- "did:plc:user123",
284
- "bafyreiabc123..."
285
- );
324
+ const blobData = await repo.blobs.get("did:plc:user123", "bafyreiabc123...");
286
325
  ```
287
326
 
288
327
  ### 6. Organizations (SDS only)
@@ -447,40 +486,40 @@ await repo.profile.update({
447
486
 
448
487
  ### Repository Operations
449
488
 
450
- | Operation | Method | PDS | SDS | Returns |
451
- |-----------|--------|-----|-----|---------|
452
- | **Records** | | | | |
453
- | Create record | `repo.records.create()` | ✅ | ✅ | `{ uri, cid }` |
454
- | Get record | `repo.records.get()` | ✅ | ✅ | Record data |
455
- | Update record | `repo.records.update()` | ✅ | ✅ | `{ uri, cid }` |
456
- | Delete record | `repo.records.delete()` | ✅ | ✅ | void |
457
- | List records | `repo.records.list()` | ✅ | ✅ | `{ records, cursor? }` |
458
- | **Hypercerts** | | | | |
459
- | Create hypercert | `repo.hypercerts.create()` | ✅ | ✅ | `{ uri, cid, value }` |
460
- | Get hypercert | `repo.hypercerts.get()` | ✅ | ✅ | Full hypercert |
461
- | Update hypercert | `repo.hypercerts.update()` | ✅ | ✅ | `{ uri, cid }` |
462
- | Delete hypercert | `repo.hypercerts.delete()` | ✅ | ✅ | void |
463
- | List hypercerts | `repo.hypercerts.list()` | ✅ | ✅ | `{ records, cursor? }` |
464
- | Add contribution | `repo.hypercerts.addContribution()` | ✅ | ✅ | Contribution |
465
- | Add measurement | `repo.hypercerts.addMeasurement()` | ✅ | ✅ | Measurement |
466
- | **Blobs** | | | | |
467
- | Upload blob | `repo.blobs.upload()` | ✅ | ✅ | `{ ref, mimeType, size }` |
468
- | Get blob | `repo.blobs.get()` | ✅ | ✅ | Blob data |
469
- | **Profile** | | | | |
470
- | Get profile | `repo.profile.get()` | ✅ | ❌ | Profile data |
471
- | Update profile | `repo.profile.update()` | ✅ | ❌ | void |
472
- | **Organizations** | | | | |
473
- | Create org | `repo.organizations.create()` | ❌ | ✅ | `{ did, name, ... }` |
474
- | Get org | `repo.organizations.get()` | ❌ | ✅ | Organization |
475
- | List orgs | `repo.organizations.list()` | ❌ | ✅ | `{ organizations, cursor? }` |
476
- | **Collaborators** | | | | |
477
- | Grant access | `repo.collaborators.grant()` | ❌ | ✅ | void |
478
- | Revoke access | `repo.collaborators.revoke()` | ❌ | ✅ | void |
479
- | List collaborators | `repo.collaborators.list()` | ❌ | ✅ | `{ collaborators, cursor? }` |
480
- | Check access | `repo.collaborators.hasAccess()` | ❌ | ✅ | boolean |
481
- | Get role | `repo.collaborators.getRole()` | ❌ | ✅ | Role string |
482
- | Get permissions | `repo.collaborators.getPermissions()` | ❌ | ✅ | Permissions |
483
- | Transfer ownership | `repo.collaborators.transferOwnership()` | ❌ | ✅ | void |
489
+ | Operation | Method | PDS | SDS | Returns |
490
+ | ------------------ | ---------------------------------------- | --- | --- | ---------------------------- |
491
+ | **Records** | | | | |
492
+ | Create record | `repo.records.create()` | ✅ | ✅ | `{ uri, cid }` |
493
+ | Get record | `repo.records.get()` | ✅ | ✅ | Record data |
494
+ | Update record | `repo.records.update()` | ✅ | ✅ | `{ uri, cid }` |
495
+ | Delete record | `repo.records.delete()` | ✅ | ✅ | void |
496
+ | List records | `repo.records.list()` | ✅ | ✅ | `{ records, cursor? }` |
497
+ | **Hypercerts** | | | | |
498
+ | Create hypercert | `repo.hypercerts.create()` | ✅ | ✅ | `{ uri, cid, value }` |
499
+ | Get hypercert | `repo.hypercerts.get()` | ✅ | ✅ | Full hypercert |
500
+ | Update hypercert | `repo.hypercerts.update()` | ✅ | ✅ | `{ uri, cid }` |
501
+ | Delete hypercert | `repo.hypercerts.delete()` | ✅ | ✅ | void |
502
+ | List hypercerts | `repo.hypercerts.list()` | ✅ | ✅ | `{ records, cursor? }` |
503
+ | Add contribution | `repo.hypercerts.addContribution()` | ✅ | ✅ | Contribution |
504
+ | Add measurement | `repo.hypercerts.addMeasurement()` | ✅ | ✅ | Measurement |
505
+ | **Blobs** | | | | |
506
+ | Upload blob | `repo.blobs.upload()` | ✅ | ✅ | `{ ref, mimeType, size }` |
507
+ | Get blob | `repo.blobs.get()` | ✅ | ✅ | Blob data |
508
+ | **Profile** | | | | |
509
+ | Get profile | `repo.profile.get()` | ✅ | ❌ | Profile data |
510
+ | Update profile | `repo.profile.update()` | ✅ | ❌ | void |
511
+ | **Organizations** | | | | |
512
+ | Create org | `repo.organizations.create()` | ❌ | ✅ | `{ did, name, ... }` |
513
+ | Get org | `repo.organizations.get()` | ❌ | ✅ | Organization |
514
+ | List orgs | `repo.organizations.list()` | ❌ | ✅ | `{ organizations, cursor? }` |
515
+ | **Collaborators** | | | | |
516
+ | Grant access | `repo.collaborators.grant()` | ❌ | ✅ | void |
517
+ | Revoke access | `repo.collaborators.revoke()` | ❌ | ✅ | void |
518
+ | List collaborators | `repo.collaborators.list()` | ❌ | ✅ | `{ collaborators, cursor? }` |
519
+ | Check access | `repo.collaborators.hasAccess()` | ❌ | ✅ | boolean |
520
+ | Get role | `repo.collaborators.getRole()` | ❌ | ✅ | Role string |
521
+ | Get permissions | `repo.collaborators.getPermissions()` | ❌ | ✅ | Permissions |
522
+ | Transfer ownership | `repo.collaborators.transferOwnership()` | ❌ | ✅ | void |
484
523
 
485
524
  ## Type System
486
525
 
@@ -505,15 +544,15 @@ if (OrgHypercertsClaim.isRecord(data)) {
505
544
  }
506
545
  ```
507
546
 
508
- | Lexicon Type | SDK Alias |
509
- |--------------|-----------|
510
- | `OrgHypercertsClaim.Main` | `HypercertClaim` |
511
- | `OrgHypercertsClaimRights.Main` | `HypercertRights` |
547
+ | Lexicon Type | SDK Alias |
548
+ | ------------------------------------- | ----------------------- |
549
+ | `OrgHypercertsClaim.Main` | `HypercertClaim` |
550
+ | `OrgHypercertsClaimRights.Main` | `HypercertRights` |
512
551
  | `OrgHypercertsClaimContribution.Main` | `HypercertContribution` |
513
- | `OrgHypercertsClaimMeasurement.Main` | `HypercertMeasurement` |
514
- | `OrgHypercertsClaimEvaluation.Main` | `HypercertEvaluation` |
515
- | `OrgHypercertsCollection.Main` | `HypercertCollection` |
516
- | `AppCertifiedLocation.Main` | `HypercertLocation` |
552
+ | `OrgHypercertsClaimMeasurement.Main` | `HypercertMeasurement` |
553
+ | `OrgHypercertsClaimEvaluation.Main` | `HypercertEvaluation` |
554
+ | `OrgHypercertsCollection.Main` | `HypercertCollection` |
555
+ | `AppCertifiedLocation.Main` | `HypercertLocation` |
517
556
 
518
557
  ## Error Handling
519
558
 
@@ -578,6 +617,7 @@ await sdsAgent.com.atproto.repo.listRecords({...});
578
617
  ```
579
618
 
580
619
  This is useful for:
620
+
581
621
  - Connecting to multiple SDS instances simultaneously
582
622
  - Testing against different server environments
583
623
  - Building tools that work across multiple organizations
@@ -611,7 +651,8 @@ await mockStore.set(mockSession);
611
651
 
612
652
  ### Working with Lexicons
613
653
 
614
- The SDK exports lexicon types and validation utilities from the `@hypercerts-org/lexicon` package for direct record manipulation and validation.
654
+ The SDK exports lexicon types and validation utilities from the `@hypercerts-org/lexicon` package for direct record
655
+ manipulation and validation.
615
656
 
616
657
  #### Lexicon Types
617
658
 
@@ -636,8 +677,8 @@ const claim: HypercertClaim = {
636
677
  shortDescription: "Urban garden serving 50 families", // REQUIRED
637
678
  description: "Detailed description...",
638
679
  workScope: "Food Security",
639
- workTimeFrameFrom: "2024-01-01T00:00:00Z", // Note: Capital 'F'
640
- workTimeFrameTo: "2024-12-31T00:00:00Z", // Note: Capital 'F'
680
+ workTimeFrameFrom: "2024-01-01T00:00:00Z", // Note: Capital 'F'
681
+ workTimeFrameTo: "2024-12-31T00:00:00Z", // Note: Capital 'F'
641
682
  rights: { uri: "at://...", cid: "..." },
642
683
  createdAt: new Date().toISOString(),
643
684
  };
@@ -648,16 +689,12 @@ const claim: HypercertClaim = {
648
689
  Validate records before creating them:
649
690
 
650
691
  ```typescript
651
- import {
652
- validate,
653
- OrgHypercertsClaim,
654
- HYPERCERT_COLLECTIONS,
655
- } from "@hypercerts-org/sdk-core";
692
+ import { validate, OrgHypercertsClaim, HYPERCERT_COLLECTIONS } from "@hypercerts-org/sdk-core";
656
693
 
657
694
  // Validate using the lexicon package
658
695
  const validation = validate(
659
- HYPERCERT_COLLECTIONS.CLAIM, // "org.hypercerts.claim"
660
- claim
696
+ HYPERCERT_COLLECTIONS.CLAIM, // "org.hypercerts.claim"
697
+ claim,
661
698
  );
662
699
 
663
700
  if (!validation.valid) {
@@ -674,20 +711,13 @@ const validationResult = OrgHypercertsClaim.validateMain(claim);
674
711
  For repository-level validation:
675
712
 
676
713
  ```typescript
677
- import {
678
- LexiconRegistry,
679
- HYPERCERT_LEXICONS,
680
- HYPERCERT_COLLECTIONS,
681
- } from "@hypercerts-org/sdk-core";
714
+ import { LexiconRegistry, HYPERCERT_LEXICONS, HYPERCERT_COLLECTIONS } from "@hypercerts-org/sdk-core";
682
715
 
683
716
  const registry = new LexiconRegistry();
684
717
  registry.registerLexicons(HYPERCERT_LEXICONS);
685
718
 
686
719
  // Validate a record
687
- const result = registry.validate(
688
- HYPERCERT_COLLECTIONS.CLAIM,
689
- claimData
690
- );
720
+ const result = registry.validate(HYPERCERT_COLLECTIONS.CLAIM, claimData);
691
721
 
692
722
  if (!result.valid) {
693
723
  console.error("Invalid record:", result.error);
@@ -697,10 +727,7 @@ if (!result.valid) {
697
727
  #### Creating Records with Proper Types
698
728
 
699
729
  ```typescript
700
- import type {
701
- HypercertContribution,
702
- StrongRef,
703
- } from "@hypercerts-org/sdk-core";
730
+ import type { HypercertContribution, StrongRef } from "@hypercerts-org/sdk-core";
704
731
  import { HYPERCERT_COLLECTIONS } from "@hypercerts-org/sdk-core";
705
732
 
706
733
  // Create a contribution record
@@ -713,8 +740,8 @@ const contribution: HypercertContribution = {
713
740
  contributors: ["did:plc:contributor1", "did:plc:contributor2"],
714
741
  role: "implementer",
715
742
  description: "On-ground implementation team",
716
- workTimeframeFrom: "2024-01-01T00:00:00Z", // Note: lowercase 'f' for contributions
717
- workTimeframeTo: "2024-06-30T00:00:00Z", // Note: lowercase 'f' for contributions
743
+ workTimeframeFrom: "2024-01-01T00:00:00Z", // Note: lowercase 'f' for contributions
744
+ workTimeframeTo: "2024-06-30T00:00:00Z", // Note: lowercase 'f' for contributions
718
745
  createdAt: new Date().toISOString(),
719
746
  };
720
747
 
@@ -731,14 +758,14 @@ await repo.records.create({
731
758
  import { HYPERCERT_COLLECTIONS } from "@hypercerts-org/sdk-core";
732
759
 
733
760
  // Collection NSIDs
734
- HYPERCERT_COLLECTIONS.CLAIM // "org.hypercerts.claim"
735
- HYPERCERT_COLLECTIONS.RIGHTS // "org.hypercerts.claim.rights"
736
- HYPERCERT_COLLECTIONS.CONTRIBUTION // "org.hypercerts.claim.contribution"
737
- HYPERCERT_COLLECTIONS.MEASUREMENT // "org.hypercerts.claim.measurement"
738
- HYPERCERT_COLLECTIONS.EVALUATION // "org.hypercerts.claim.evaluation"
739
- HYPERCERT_COLLECTIONS.EVIDENCE // "org.hypercerts.claim.evidence"
740
- HYPERCERT_COLLECTIONS.COLLECTION // "org.hypercerts.collection"
741
- HYPERCERT_COLLECTIONS.LOCATION // "app.certified.location"
761
+ HYPERCERT_COLLECTIONS.CLAIM; // "org.hypercerts.claim"
762
+ HYPERCERT_COLLECTIONS.RIGHTS; // "org.hypercerts.claim.rights"
763
+ HYPERCERT_COLLECTIONS.CONTRIBUTION; // "org.hypercerts.claim.contribution"
764
+ HYPERCERT_COLLECTIONS.MEASUREMENT; // "org.hypercerts.claim.measurement"
765
+ HYPERCERT_COLLECTIONS.EVALUATION; // "org.hypercerts.claim.evaluation"
766
+ HYPERCERT_COLLECTIONS.EVIDENCE; // "org.hypercerts.claim.evidence"
767
+ HYPERCERT_COLLECTIONS.COLLECTION; // "org.hypercerts.collection"
768
+ HYPERCERT_COLLECTIONS.LOCATION; // "app.certified.location"
742
769
  ```
743
770
 
744
771
  ## Development