@hypercerts-org/sdk-core 0.2.0-beta.0 → 0.5.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/README.md CHANGED
@@ -1,26 +1,412 @@
1
1
  # @hypercerts-org/sdk-core
2
2
 
3
- Framework-agnostic ATProto SDK for Hypercerts.
3
+ Framework-agnostic ATProto SDK for Hypercerts. Create, manage, and collaborate on hypercerts using the AT Protocol.
4
4
 
5
5
  ```bash
6
6
  pnpm add @hypercerts-org/sdk-core
7
7
  ```
8
8
 
9
- ## Entrypoints
9
+ ## Quick Start
10
10
 
11
+ ```typescript
12
+ import { createATProtoSDK } from "@hypercerts-org/sdk-core";
13
+
14
+ // 1. Create SDK with OAuth configuration
15
+ const sdk = createATProtoSDK({
16
+ oauth: {
17
+ clientId: "https://your-app.com/client-metadata.json",
18
+ redirectUri: "https://your-app.com/callback",
19
+ scope: "atproto",
20
+ jwksUri: "https://your-app.com/jwks.json",
21
+ jwkPrivate: process.env.ATPROTO_JWK_PRIVATE!,
22
+ },
23
+ });
24
+
25
+ // 2. Authenticate user
26
+ const authUrl = await sdk.authorize("user.bsky.social");
27
+ // Redirect user to authUrl...
28
+
29
+ // 3. Handle OAuth callback
30
+ const session = await sdk.callback(callbackParams);
31
+
32
+ // 4. Get repository and start creating hypercerts
33
+ const repo = sdk.getRepository(session);
34
+ const claim = await repo.hypercerts.create({
35
+ title: "Tree Planting Initiative 2025",
36
+ description: "Planted 1000 trees in the rainforest",
37
+ impact: {
38
+ scope: ["Environmental Conservation"],
39
+ work: { from: "2025-01-01", to: "2025-12-31" },
40
+ contributors: ["did:plc:contributor1"],
41
+ },
42
+ });
11
43
  ```
12
- @hypercerts-org/sdk-core
13
- ├── / → Full SDK (createATProtoSDK, Repository, types, errors)
14
- ├── /types → TypeScript types (re-exported from @hypercerts-org/lexicon)
15
- ├── /errors → Error classes
16
- ├── /lexicons → LexiconRegistry, HYPERCERT_LEXICONS, HYPERCERT_COLLECTIONS
17
- ├── /storage → InMemorySessionStore, InMemoryStateStore
18
- └── /testing → createMockSession, MockSessionStore
44
+
45
+ ## Core Concepts
46
+
47
+ ### 1. Authentication
48
+
49
+ The SDK uses OAuth 2.0 for authentication with support for both PDS (Personal Data Server) and SDS (Shared Data Server).
50
+
51
+ ```typescript
52
+ // First-time user authentication
53
+ const authUrl = await sdk.authorize("user.bsky.social");
54
+ // Redirect user to authUrl to complete OAuth flow
55
+
56
+ // Handle the OAuth callback
57
+ const session = await sdk.callback({
58
+ code: "...",
59
+ state: "...",
60
+ iss: "...",
61
+ });
62
+
63
+ // Restore existing session for returning users
64
+ const session = await sdk.restoreSession("did:plc:user123");
65
+
66
+ // Get repository for authenticated user
67
+ const repo = sdk.getRepository(session);
68
+ ```
69
+
70
+ ### 2. Working with Hypercerts
71
+
72
+ #### Creating a Hypercert
73
+
74
+ ```typescript
75
+ const hypercert = await repo.hypercerts.create({
76
+ title: "Climate Research Project",
77
+ description: "Research on carbon capture technologies",
78
+ image: imageBlob, // optional: File or Blob
79
+ externalUrl: "https://example.com/project",
80
+
81
+ impact: {
82
+ scope: ["Climate Change", "Carbon Capture"],
83
+ work: {
84
+ from: "2024-01-01",
85
+ to: "2025-12-31",
86
+ },
87
+ contributors: ["did:plc:researcher1", "did:plc:researcher2"],
88
+ },
89
+
90
+ rights: {
91
+ license: "CC-BY-4.0",
92
+ allowsDerivatives: true,
93
+ transferrable: false,
94
+ },
95
+ });
96
+
97
+ console.log("Created hypercert:", hypercert.uri);
98
+ ```
99
+
100
+ #### Retrieving Hypercerts
101
+
102
+ ```typescript
103
+ // Get a specific hypercert by URI
104
+ const hypercert = await repo.hypercerts.get(
105
+ "at://did:plc:user123/org.hypercerts.claim/abc123"
106
+ );
107
+
108
+ // List all hypercerts in the repository
109
+ const { records } = await repo.hypercerts.list();
110
+ for (const claim of records) {
111
+ console.log(claim.value.title);
112
+ }
113
+
114
+ // List with pagination
115
+ const { records, cursor } = await repo.hypercerts.list({ limit: 10 });
116
+ if (cursor) {
117
+ const nextPage = await repo.hypercerts.list({ limit: 10, cursor });
118
+ }
119
+ ```
120
+
121
+ #### Updating a Hypercert
122
+
123
+ ```typescript
124
+ // Update an existing hypercert
125
+ await repo.hypercerts.update(
126
+ "at://did:plc:user123/org.hypercerts.claim/abc123",
127
+ {
128
+ title: "Updated Climate Research Project",
129
+ description: "Expanded scope to include renewable energy",
130
+ impact: {
131
+ scope: ["Climate Change", "Carbon Capture", "Renewable Energy"],
132
+ work: { from: "2024-01-01", to: "2026-12-31" },
133
+ contributors: ["did:plc:researcher1", "did:plc:researcher2"],
134
+ },
135
+ }
136
+ );
137
+ ```
138
+
139
+ #### Deleting a Hypercert
140
+
141
+ ```typescript
142
+ await repo.hypercerts.delete(
143
+ "at://did:plc:user123/org.hypercerts.claim/abc123"
144
+ );
145
+ ```
146
+
147
+ ### 3. Contributions and Measurements
148
+
149
+ #### Adding Contributions
150
+
151
+ ```typescript
152
+ // Add a contribution to a hypercert
153
+ const contribution = await repo.hypercerts.addContribution({
154
+ claim: "at://did:plc:user123/org.hypercerts.claim/abc123",
155
+ contributor: "did:plc:contributor456",
156
+ description: "Led the research team and conducted field studies",
157
+ contributionType: "Work",
158
+ percentage: 40.0,
159
+ });
160
+ ```
161
+
162
+ #### Adding Measurements
163
+
164
+ ```typescript
165
+ // Add a measurement/evaluation
166
+ const measurement = await repo.hypercerts.addMeasurement({
167
+ claim: "at://did:plc:user123/org.hypercerts.claim/abc123",
168
+ type: "Impact",
169
+ value: 1000,
170
+ unit: "trees planted",
171
+ verifiedBy: "did:plc:auditor789",
172
+ verificationMethod: "On-site inspection with GPS verification",
173
+ measuredAt: new Date().toISOString(),
174
+ });
175
+ ```
176
+
177
+ ### 4. Blob Operations (Images & Files)
178
+
179
+ ```typescript
180
+ // Upload an image or file
181
+ const blobResult = await repo.blobs.upload(imageFile);
182
+ console.log("Blob uploaded:", blobResult.ref.$link);
183
+
184
+ // Download a blob
185
+ const blobData = await repo.blobs.get(
186
+ "did:plc:user123",
187
+ "bafyreiabc123..."
188
+ );
189
+ ```
190
+
191
+ ### 5. Organizations (SDS only)
192
+
193
+ Organizations allow multiple users to collaborate on shared repositories.
194
+
195
+ ```typescript
196
+ // Create an organization
197
+ const org = await repo.organizations.create({
198
+ name: "Climate Research Institute",
199
+ description: "Leading research on climate solutions",
200
+ handle: "climate-research", // optional: unique handle
201
+ });
202
+
203
+ console.log("Organization DID:", org.did);
204
+
205
+ // List all organizations you belong to
206
+ const { organizations } = await repo.organizations.list();
207
+ for (const org of organizations) {
208
+ console.log(`${org.name} (${org.role})`);
209
+ }
210
+
211
+ // List with pagination
212
+ const { organizations, cursor } = await repo.organizations.list({ limit: 10 });
213
+
214
+ // Get a specific organization
215
+ const org = await repo.organizations.get("did:plc:org123");
216
+ console.log(`${org.name} - ${org.description}`);
217
+ ```
218
+
219
+ ### 6. Collaborator Management (SDS only)
220
+
221
+ Manage who has access to your repository and what they can do.
222
+
223
+ #### Granting Access
224
+
225
+ ```typescript
226
+ // Grant different levels of access
227
+ await repo.collaborators.grant({
228
+ userDid: "did:plc:user123",
229
+ role: "editor", // viewer | editor | admin | owner
230
+ });
231
+
232
+ // Roles explained:
233
+ // - viewer: Read-only access
234
+ // - editor: Can create and edit records
235
+ // - admin: Can manage collaborators and settings
236
+ // - owner: Full control (same as repository owner)
237
+ ```
238
+
239
+ #### Managing Collaborators
240
+
241
+ ```typescript
242
+ // List all collaborators with pagination
243
+ const { collaborators, cursor } = await repo.collaborators.list();
244
+ for (const collab of collaborators) {
245
+ console.log(`${collab.userDid} - ${collab.role}`);
246
+ }
247
+
248
+ // List next page
249
+ if (cursor) {
250
+ const nextPage = await repo.collaborators.list({ cursor, limit: 20 });
251
+ }
252
+
253
+ // Check if a user has access
254
+ const hasAccess = await repo.collaborators.hasAccess("did:plc:user123");
255
+
256
+ // Get a specific user's role
257
+ const role = await repo.collaborators.getRole("did:plc:user123");
258
+ console.log(`User role: ${role}`); // "editor", "admin", etc.
259
+
260
+ // Get current user's permissions
261
+ const permissions = await repo.collaborators.getPermissions();
262
+ if (permissions.admin) {
263
+ console.log("You can manage collaborators");
264
+ }
265
+ if (permissions.create) {
266
+ console.log("You can create records");
267
+ }
268
+ ```
269
+
270
+ #### Revoking Access
271
+
272
+ ```typescript
273
+ // Remove a collaborator
274
+ await repo.collaborators.revoke({
275
+ userDid: "did:plc:user123",
276
+ });
277
+ ```
278
+
279
+ #### Transferring Ownership
280
+
281
+ ```typescript
282
+ // Transfer repository ownership (irreversible!)
283
+ await repo.collaborators.transferOwnership({
284
+ newOwnerDid: "did:plc:newowner456",
285
+ });
19
286
  ```
20
287
 
288
+ ### 7. Generic Record Operations
289
+
290
+ For working with any ATProto record type:
291
+
292
+ ```typescript
293
+ // Create a generic record
294
+ const record = await repo.records.create({
295
+ collection: "org.hypercerts.claim",
296
+ record: {
297
+ $type: "org.hypercerts.claim",
298
+ title: "My Claim",
299
+ // ... record data
300
+ },
301
+ });
302
+
303
+ // Get a record
304
+ const record = await repo.records.get({
305
+ collection: "org.hypercerts.claim",
306
+ rkey: "abc123",
307
+ });
308
+
309
+ // Update a record
310
+ await repo.records.update({
311
+ collection: "org.hypercerts.claim",
312
+ rkey: "abc123",
313
+ record: {
314
+ $type: "org.hypercerts.claim",
315
+ title: "Updated Title",
316
+ // ... updated data
317
+ },
318
+ });
319
+
320
+ // Delete a record
321
+ await repo.records.delete({
322
+ collection: "org.hypercerts.claim",
323
+ rkey: "abc123",
324
+ });
325
+
326
+ // List records with pagination
327
+ const { records, cursor } = await repo.records.list({
328
+ collection: "org.hypercerts.claim",
329
+ limit: 50,
330
+ });
331
+ ```
332
+
333
+ ### 8. Profile Management (PDS only)
334
+
335
+ ```typescript
336
+ // Get user profile
337
+ const profile = await repo.profile.get();
338
+ console.log(`${profile.displayName} (@${profile.handle})`);
339
+
340
+ // Update profile
341
+ await repo.profile.update({
342
+ displayName: "Jane Researcher",
343
+ description: "Climate scientist and hypercert enthusiast",
344
+ avatar: avatarBlob, // optional
345
+ banner: bannerBlob, // optional
346
+ });
347
+ ```
348
+
349
+ ## API Reference
350
+
351
+ ### Repository Operations
352
+
353
+ | Operation | Method | PDS | SDS | Returns |
354
+ |-----------|--------|-----|-----|---------|
355
+ | **Records** | | | | |
356
+ | Create record | `repo.records.create()` | ✅ | ✅ | `{ uri, cid }` |
357
+ | Get record | `repo.records.get()` | ✅ | ✅ | Record data |
358
+ | Update record | `repo.records.update()` | ✅ | ✅ | `{ uri, cid }` |
359
+ | Delete record | `repo.records.delete()` | ✅ | ✅ | void |
360
+ | List records | `repo.records.list()` | ✅ | ✅ | `{ records, cursor? }` |
361
+ | **Hypercerts** | | | | |
362
+ | Create hypercert | `repo.hypercerts.create()` | ✅ | ✅ | `{ uri, cid, value }` |
363
+ | Get hypercert | `repo.hypercerts.get()` | ✅ | ✅ | Full hypercert |
364
+ | Update hypercert | `repo.hypercerts.update()` | ✅ | ✅ | `{ uri, cid }` |
365
+ | Delete hypercert | `repo.hypercerts.delete()` | ✅ | ✅ | void |
366
+ | List hypercerts | `repo.hypercerts.list()` | ✅ | ✅ | `{ records, cursor? }` |
367
+ | Add contribution | `repo.hypercerts.addContribution()` | ✅ | ✅ | Contribution |
368
+ | Add measurement | `repo.hypercerts.addMeasurement()` | ✅ | ✅ | Measurement |
369
+ | **Blobs** | | | | |
370
+ | Upload blob | `repo.blobs.upload()` | ✅ | ✅ | `{ ref, mimeType, size }` |
371
+ | Get blob | `repo.blobs.get()` | ✅ | ✅ | Blob data |
372
+ | **Profile** | | | | |
373
+ | Get profile | `repo.profile.get()` | ✅ | ❌ | Profile data |
374
+ | Update profile | `repo.profile.update()` | ✅ | ❌ | void |
375
+ | **Organizations** | | | | |
376
+ | Create org | `repo.organizations.create()` | ❌ | ✅ | `{ did, name, ... }` |
377
+ | Get org | `repo.organizations.get()` | ❌ | ✅ | Organization |
378
+ | List orgs | `repo.organizations.list()` | ❌ | ✅ | `{ organizations, cursor? }` |
379
+ | **Collaborators** | | | | |
380
+ | Grant access | `repo.collaborators.grant()` | ❌ | ✅ | void |
381
+ | Revoke access | `repo.collaborators.revoke()` | ❌ | ✅ | void |
382
+ | List collaborators | `repo.collaborators.list()` | ❌ | ✅ | `{ collaborators, cursor? }` |
383
+ | Check access | `repo.collaborators.hasAccess()` | ❌ | ✅ | boolean |
384
+ | Get role | `repo.collaborators.getRole()` | ❌ | ✅ | Role string |
385
+ | Get permissions | `repo.collaborators.getPermissions()` | ❌ | ✅ | Permissions |
386
+ | Transfer ownership | `repo.collaborators.transferOwnership()` | ❌ | ✅ | void |
387
+
21
388
  ## Type System
22
389
 
23
- Types are generated from ATProto lexicon definitions in `@hypercerts-org/lexicon` and re-exported with friendly aliases:
390
+ Types are generated from ATProto lexicon definitions and exported with friendly aliases:
391
+
392
+ ```typescript
393
+ import type {
394
+ HypercertClaim,
395
+ HypercertRights,
396
+ HypercertContribution,
397
+ HypercertMeasurement,
398
+ HypercertEvaluation,
399
+ HypercertCollection,
400
+ HypercertLocation,
401
+ } from "@hypercerts-org/sdk-core";
402
+
403
+ // For validation, use namespaced imports
404
+ import { OrgHypercertsClaim } from "@hypercerts-org/sdk-core";
405
+
406
+ if (OrgHypercertsClaim.isRecord(data)) {
407
+ // data is typed as HypercertClaim
408
+ }
409
+ ```
24
410
 
25
411
  | Lexicon Type | SDK Alias |
26
412
  |--------------|-----------|
@@ -32,69 +418,106 @@ Types are generated from ATProto lexicon definitions in `@hypercerts-org/lexicon
32
418
  | `OrgHypercertsCollection.Main` | `HypercertCollection` |
33
419
  | `AppCertifiedLocation.Main` | `HypercertLocation` |
34
420
 
35
- ```typescript
36
- import type { HypercertClaim, HypercertRights } from "@hypercerts-org/sdk-core";
421
+ ## Error Handling
37
422
 
38
- // For validation functions, import the namespaced types
39
- import { OrgHypercertsClaim } from "@hypercerts-org/sdk-core";
423
+ ```typescript
424
+ import {
425
+ ValidationError,
426
+ NetworkError,
427
+ AuthenticationError,
428
+ SDSRequiredError,
429
+ } from "@hypercerts-org/sdk-core/errors";
40
430
 
41
- if (OrgHypercertsClaim.isRecord(data)) {
42
- // data is typed as HypercertClaim
431
+ try {
432
+ await repo.hypercerts.create({ ... });
433
+ } catch (error) {
434
+ if (error instanceof ValidationError) {
435
+ console.error("Invalid hypercert data:", error.message);
436
+ } else if (error instanceof NetworkError) {
437
+ console.error("Network issue:", error.message);
438
+ } else if (error instanceof AuthenticationError) {
439
+ console.error("Authentication failed:", error.message);
440
+ } else if (error instanceof SDSRequiredError) {
441
+ console.error("This operation requires SDS:", error.message);
442
+ }
43
443
  }
44
444
  ```
45
445
 
46
- ## Usage
446
+ ## Package Entrypoints
447
+
448
+ ```
449
+ @hypercerts-org/sdk-core
450
+ ├── / → Full SDK (createATProtoSDK, Repository, types, errors)
451
+ ├── /types → TypeScript types (re-exported from @hypercerts-org/lexicon)
452
+ ├── /errors → Error classes
453
+ ├── /lexicons → LexiconRegistry, HYPERCERT_LEXICONS, HYPERCERT_COLLECTIONS
454
+ ├── /storage → InMemorySessionStore, InMemoryStateStore
455
+ └── /testing → createMockSession, MockSessionStore
456
+ ```
457
+
458
+ ## Advanced Usage
459
+
460
+ ### Custom Session Storage
47
461
 
48
462
  ```typescript
49
463
  import { createATProtoSDK } from "@hypercerts-org/sdk-core";
464
+ import { InMemorySessionStore } from "@hypercerts-org/sdk-core/storage";
50
465
 
51
- // 1. Create SDK instance
52
466
  const sdk = createATProtoSDK({
53
- oauth: {
54
- clientId: "https://your-app.com/client-metadata.json",
55
- redirectUri: "https://your-app.com/callback",
56
- scope: "atproto",
57
- jwksUri: "https://your-app.com/jwks.json",
58
- jwkPrivate: process.env.ATPROTO_JWK_PRIVATE!,
59
- },
467
+ oauth: { ... },
468
+ sessionStore: new InMemorySessionStore(),
60
469
  });
470
+ ```
61
471
 
62
- // 2. Start OAuth flow → redirect user to authUrl
63
- const authUrl = await sdk.authorize("user.bsky.social");
472
+ ### Testing with Mocks
64
473
 
65
- // 3. Handle callback at redirectUri → exchange code for session
66
- const session = await sdk.callback(params); // params from callback URL
474
+ ```typescript
475
+ import { createMockSession, MockSessionStore } from "@hypercerts-org/sdk-core/testing";
67
476
 
68
- // 4. Use session to interact with repositories
69
- const repo = sdk.getRepository(session);
70
- await repo.hypercerts.create({ title: "My Hypercert", ... });
477
+ const mockSession = createMockSession({
478
+ did: "did:plc:test123",
479
+ handle: "test.user",
480
+ });
71
481
 
72
- // For returning users, restore session by DID
73
- const existingSession = await sdk.restoreSession("did:plc:...");
482
+ const mockStore = new MockSessionStore();
483
+ await mockStore.set(mockSession);
74
484
  ```
75
485
 
76
- ## Repository API
486
+ ### Working with Lexicons
77
487
 
78
- ```
79
- repo
80
- ├── .records → create, get, update, delete, list
81
- ├── .blobs → upload, get
82
- ├── .profile → get, update
83
- ├── .hypercerts → create, get, update, delete, list, addContribution, addMeasurement
84
- ├── .collaborators → grant, revoke, list, hasAccess (SDS only)
85
- └── .organizations → create, get, list (SDS only)
86
- ```
488
+ ```typescript
489
+ import {
490
+ LexiconRegistry,
491
+ HYPERCERT_LEXICONS,
492
+ HYPERCERT_COLLECTIONS,
493
+ } from "@hypercerts-org/sdk-core/lexicons";
87
494
 
88
- ## Errors
495
+ const registry = new LexiconRegistry();
496
+ registry.registerLexicons(HYPERCERT_LEXICONS);
89
497
 
90
- ```typescript
91
- import { ValidationError, NetworkError, AuthenticationError } from "@hypercerts-org/sdk-core/errors";
498
+ // Validate a record
499
+ const isValid = registry.validate(
500
+ "org.hypercerts.claim",
501
+ claimData
502
+ );
92
503
  ```
93
504
 
94
505
  ## Development
95
506
 
96
507
  ```bash
97
- pnpm build # Build
98
- pnpm test # Test
99
- pnpm test:coverage # Coverage
508
+ pnpm install # Install dependencies
509
+ pnpm build # Build the package
510
+ pnpm test # Run tests
511
+ pnpm test:coverage # Run tests with coverage
512
+ pnpm test:watch # Run tests in watch mode
100
513
  ```
514
+
515
+ ## License
516
+
517
+ MIT
518
+
519
+ ## Resources
520
+
521
+ - [ATProto Documentation](https://atproto.com/docs)
522
+ - [Hypercerts Documentation](https://hypercerts.org)
523
+ - [GitHub Repository](https://github.com/hypercerts-org/hypercerts-sdk)