Skip to content

Commit a2d2bca

Browse files
committed
feat(sdk-core): align collection/project types with lexicon + add inline location
This is a BREAKING change. Type alignment with lexicon: - Changed createCollection to use lexicon-aligned 'items' instead of 'claims' - Updated avatar/banner to use proper lexicon union types (smallImage/largeImage) - Simplified project methods to delegate to collection methods - Added CreateCollectionParams, UpdateCollectionParams derived from lexicon Inline location support: - AttachLocationParams now supports StrongRef, AT-URI string, or location object - Add optional location field to CreateCollectionParams/UpdateCollectionParams - Add locationUri to CreateCollectionResult - Implement location handling in createCollection() supporting all forms Example: const project = await repo.hypercerts.createProject({ title: 'Climate Initiative', items: [...], location: { uri: 'at://...', cid: '...' }, // StrongRef });
1 parent 2626812 commit a2d2bca

9 files changed

Lines changed: 1204 additions & 514 deletions

File tree

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
---
2+
"@hypercerts-org/sdk-core": minor
3+
---
4+
5+
feat: align collection/project types with lexicon + add inline location support
6+
7+
This is a BREAKING change.
8+
9+
**Type Alignment with Lexicon:**
10+
11+
- Changed `createCollection` to use lexicon-aligned `items` array instead of `claims`
12+
- Updated avatar/banner handling to use proper lexicon union types
13+
- Simplified project methods to delegate to collection methods
14+
- Added `CreateCollectionParams`, `UpdateCollectionParams`, and result types derived from lexicon
15+
- Improved type safety by deriving SDK input types from lexicon definitions
16+
17+
**Inline Location Support:**
18+
19+
`AttachLocationParams` now supports three ways to specify location:
20+
21+
1. **StrongRef** - Direct reference with uri and cid (no record creation)
22+
2. **AT-URI string** - Reference to existing location record
23+
3. **Location object** - Full location data to create a new location record
24+
25+
**Changes:**
26+
27+
- Add `AttachLocationParams` union type supporting StrongRef, string URI, or location object
28+
- Add optional `location` field to `CreateCollectionParams`
29+
- Add optional `location` field to `UpdateCollectionParams` (supports `null` to remove)
30+
- Update `CreateCollectionResult` to include optional `locationUri` field
31+
- Implement location handling in `createCollection()` - supports all three forms
32+
- Add tests for collection creation with StrongRef, string URI, and location object
33+
34+
**Example Usage:**
35+
36+
```typescript
37+
// Create a project with location (StrongRef - direct reference)
38+
const project = await repo.hypercerts.createProject({
39+
title: "Climate Initiative",
40+
items: [...],
41+
location: { uri: "at://did:plc:alice/app.certified.location/abc", cid: "bafy..." },
42+
});
43+
44+
// Create with location (AT-URI string - fetches CID)
45+
const project2 = await repo.hypercerts.createProject({
46+
title: "Forest Project",
47+
items: [...],
48+
location: "at://did:plc:bob/app.certified.location/xyz",
49+
});
50+
51+
// Create with location (location object - creates new record)
52+
const project3 = await repo.hypercerts.createProject({
53+
title: "Ocean Project",
54+
items: [...],
55+
location: {
56+
lpVersion: "1.0",
57+
srs: "EPSG:4326",
58+
locationType: "coordinate-decimal",
59+
location: "37.7749, -122.4194",
60+
},
61+
});
62+
63+
// Update project to change location
64+
await repo.hypercerts.updateProject(project.uri, {
65+
location: "at://did:plc:alice/app.certified.location/new",
66+
});
67+
68+
// Remove location
69+
await repo.hypercerts.updateProject(project.uri, {
70+
location: null,
71+
});
72+
```

packages/sdk-core/README.md

Lines changed: 178 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,130 @@ const measurement = await repo.hypercerts.addMeasurement({
411411
});
412412
```
413413

414-
### 5. Blob Operations (Images & Files)
414+
### 5. Collections and Projects
415+
416+
Collections organize multiple hypercerts into logical groupings. Projects are a special type of collection with
417+
`type="project"`.
418+
419+
#### Creating a Collection
420+
421+
```typescript
422+
// Create a collection with weighted items
423+
const collection = await repo.hypercerts.createCollection({
424+
title: "Climate Projects 2024",
425+
shortDescription: "Our climate impact portfolio",
426+
description: "A curated collection of climate-related hypercerts",
427+
items: [
428+
{
429+
itemIdentifier: { uri: hypercert1Uri, cid: hypercert1Cid },
430+
itemWeight: "0.5",
431+
},
432+
{
433+
itemIdentifier: { uri: hypercert2Uri, cid: hypercert2Cid },
434+
itemWeight: "0.3",
435+
},
436+
{
437+
itemIdentifier: { uri: hypercert3Uri, cid: hypercert3Cid },
438+
itemWeight: "0.2",
439+
},
440+
],
441+
avatar: avatarBlob, // optional
442+
banner: bannerBlob, // optional
443+
});
444+
445+
console.log("Created collection:", collection.uri);
446+
```
447+
448+
#### Creating a Project
449+
450+
Projects are collections with automatic `type="project"`:
451+
452+
```typescript
453+
const project = await repo.hypercerts.createProject({
454+
title: "Rainforest Restoration",
455+
shortDescription: "Multi-year restoration initiative",
456+
description: "Comprehensive rainforest restoration project",
457+
items: [
458+
{
459+
itemIdentifier: { uri: activity1Uri, cid: activity1Cid },
460+
itemWeight: "0.6",
461+
},
462+
{
463+
itemIdentifier: { uri: activity2Uri, cid: activity2Cid },
464+
itemWeight: "0.4",
465+
},
466+
],
467+
});
468+
```
469+
470+
#### Attaching Locations
471+
472+
Both collections and projects can have location data attached as a sidecar record:
473+
474+
```typescript
475+
// Attach location to a project
476+
const locationResult = await repo.hypercerts.attachLocationToProject(projectUri, {
477+
lpVersion: "1.0",
478+
srs: "EPSG:4326",
479+
locationType: "coordinate-decimal",
480+
location: "https://example.com/location.geojson", // or use a Blob
481+
name: "Project Site",
482+
description: "Main restoration site coordinates",
483+
});
484+
485+
// Also works for collections
486+
await repo.hypercerts.attachLocationToCollection(collectionUri, locationParams);
487+
```
488+
489+
#### Listing and Retrieving
490+
491+
```typescript
492+
// Get a specific collection
493+
const collection = await repo.hypercerts.getCollection(collectionUri);
494+
495+
// List all collections
496+
const { records } = await repo.hypercerts.listCollections();
497+
498+
// Get a specific project
499+
const project = await repo.hypercerts.getProject(projectUri);
500+
501+
// List all projects
502+
const { records } = await repo.hypercerts.listProjects();
503+
```
504+
505+
#### Updating Collections and Projects
506+
507+
```typescript
508+
// Update collection
509+
await repo.hypercerts.updateCollection(collectionUri, {
510+
title: "Updated Title",
511+
items: [
512+
/* updated items */
513+
],
514+
avatar: newAvatarBlob, // or null to remove
515+
});
516+
517+
// Update project (same API)
518+
await repo.hypercerts.updateProject(projectUri, {
519+
shortDescription: "Updated description",
520+
banner: null, // removes banner
521+
});
522+
```
523+
524+
#### Deleting
525+
526+
```typescript
527+
// Delete collection
528+
await repo.hypercerts.deleteCollection(collectionUri);
529+
530+
// Delete project
531+
await repo.hypercerts.deleteProject(projectUri);
532+
533+
// Remove location from project
534+
await repo.hypercerts.removeLocationFromProject(projectUri);
535+
```
536+
537+
### 6. Blob Operations (Images & Files)
415538

416539
```typescript
417540
// Upload an image or file
@@ -422,7 +545,7 @@ console.log("Blob uploaded:", blobResult.ref.$link);
422545
const blobData = await repo.blobs.get("did:plc:user123", "bafyreiabc123...");
423546
```
424547

425-
### 6. Organizations (SDS only)
548+
### 7. Organizations (SDS only)
426549

427550
Organizations allow multiple users to collaborate on shared repositories.
428551

@@ -450,7 +573,7 @@ const org = await repo.organizations.get("did:plc:org123");
450573
console.log(`${org.name} - ${org.description}`);
451574
```
452575

453-
### 7. Collaborator Management (SDS only)
576+
### 8. Collaborator Management (SDS only)
454577

455578
Manage who has access to your repository and what they can do.
456579

@@ -519,7 +642,7 @@ await repo.collaborators.transferOwnership({
519642
});
520643
```
521644

522-
### 8. Generic Record Operations
645+
### 9. Generic Record Operations
523646

524647
For working with any ATProto record type:
525648

@@ -564,7 +687,7 @@ const { records, cursor } = await repo.records.list({
564687
});
565688
```
566689

567-
### 9. Profile Management (PDS only)
690+
### 10. Profile Management (PDS only)
568691

569692
```typescript
570693
// Get user profile
@@ -584,40 +707,56 @@ await repo.profile.update({
584707

585708
### Repository Operations
586709

587-
| Operation | Method | PDS | SDS | Returns |
588-
| ------------------ | ---------------------------------------- | --- | --- | ---------------------------- |
589-
| **Records** | | | | |
590-
| Create record | `repo.records.create()` ||| `{ uri, cid }` |
591-
| Get record | `repo.records.get()` ||| Record data |
592-
| Update record | `repo.records.update()` ||| `{ uri, cid }` |
593-
| Delete record | `repo.records.delete()` ||| void |
594-
| List records | `repo.records.list()` ||| `{ records, cursor? }` |
595-
| **Hypercerts** | | | | |
596-
| Create hypercert | `repo.hypercerts.create()` ||| `{ uri, cid, value }` |
597-
| Get hypercert | `repo.hypercerts.get()` ||| Full hypercert |
598-
| Update hypercert | `repo.hypercerts.update()` ||| `{ uri, cid }` |
599-
| Delete hypercert | `repo.hypercerts.delete()` ||| void |
600-
| List hypercerts | `repo.hypercerts.list()` ||| `{ records, cursor? }` |
601-
| Add contribution | `repo.hypercerts.addContribution()` ||| Contribution |
602-
| Add measurement | `repo.hypercerts.addMeasurement()` ||| Measurement |
603-
| **Blobs** | | | | |
604-
| Upload blob | `repo.blobs.upload()` ||| `{ ref, mimeType, size }` |
605-
| Get blob | `repo.blobs.get()` ||| Blob data |
606-
| **Profile** | | | | |
607-
| Get profile | `repo.profile.get()` ||| Profile data |
608-
| Update profile | `repo.profile.update()` ||| void |
609-
| **Organizations** | | | | |
610-
| Create org | `repo.organizations.create()` ||| `{ did, name, ... }` |
611-
| Get org | `repo.organizations.get()` ||| Organization |
612-
| List orgs | `repo.organizations.list()` ||| `{ organizations, cursor? }` |
613-
| **Collaborators** | | | | |
614-
| Grant access | `repo.collaborators.grant()` ||| void |
615-
| Revoke access | `repo.collaborators.revoke()` ||| void |
616-
| List collaborators | `repo.collaborators.list()` ||| `{ collaborators, cursor? }` |
617-
| Check access | `repo.collaborators.hasAccess()` ||| boolean |
618-
| Get role | `repo.collaborators.getRole()` ||| Role string |
619-
| Get permissions | `repo.collaborators.getPermissions()` ||| Permissions |
620-
| Transfer ownership | `repo.collaborators.transferOwnership()` ||| void |
710+
| Operation | Method | PDS | SDS | Returns |
711+
| ------------------ | ------------------------------------------------ | --- | --- | ---------------------------- |
712+
| **Records** | | | | |
713+
| Create record | `repo.records.create()` ||| `{ uri, cid }` |
714+
| Get record | `repo.records.get()` ||| Record data |
715+
| Update record | `repo.records.update()` ||| `{ uri, cid }` |
716+
| Delete record | `repo.records.delete()` ||| void |
717+
| List records | `repo.records.list()` ||| `{ records, cursor? }` |
718+
| **Hypercerts** | | | | |
719+
| Create hypercert | `repo.hypercerts.create()` ||| `{ uri, cid, value }` |
720+
| Get hypercert | `repo.hypercerts.get()` ||| Full hypercert |
721+
| Update hypercert | `repo.hypercerts.update()` ||| `{ uri, cid }` |
722+
| Delete hypercert | `repo.hypercerts.delete()` ||| void |
723+
| List hypercerts | `repo.hypercerts.list()` ||| `{ records, cursor? }` |
724+
| Add contribution | `repo.hypercerts.addContribution()` ||| Contribution |
725+
| Add measurement | `repo.hypercerts.addMeasurement()` ||| Measurement |
726+
| **Collections** | | | | |
727+
| Create collection | `repo.hypercerts.createCollection()` ||| `{ uri, cid, locationUri? }` |
728+
| Get collection | `repo.hypercerts.getCollection()` ||| Collection data |
729+
| List collections | `repo.hypercerts.listCollections()` ||| `{ records, cursor? }` |
730+
| Update collection | `repo.hypercerts.updateCollection()` ||| `{ uri, cid }` |
731+
| Delete collection | `repo.hypercerts.deleteCollection()` ||| void |
732+
| Attach location | `repo.hypercerts.attachLocationToCollection()` ||| `{ uri, cid }` |
733+
| Remove location | `repo.hypercerts.removeLocationFromCollection()` ||| void |
734+
| **Projects** | | | | |
735+
| Create project | `repo.hypercerts.createProject()` ||| `{ uri, cid, locationUri? }` |
736+
| Get project | `repo.hypercerts.getProject()` ||| Project data |
737+
| List projects | `repo.hypercerts.listProjects()` ||| `{ records, cursor? }` |
738+
| Update project | `repo.hypercerts.updateProject()` ||| `{ uri, cid }` |
739+
| Delete project | `repo.hypercerts.deleteProject()` ||| void |
740+
| Attach location | `repo.hypercerts.attachLocationToProject()` ||| `{ uri, cid }` |
741+
| Remove location | `repo.hypercerts.removeLocationFromProject()` ||| void |
742+
| **Blobs** | | | | |
743+
| Upload blob | `repo.blobs.upload()` ||| `{ ref, mimeType, size }` |
744+
| Get blob | `repo.blobs.get()` ||| Blob data |
745+
| **Profile** | | | | |
746+
| Get profile | `repo.profile.get()` ||| Profile data |
747+
| Update profile | `repo.profile.update()` ||| void |
748+
| **Organizations** | | | | |
749+
| Create org | `repo.organizations.create()` ||| `{ did, name, ... }` |
750+
| Get org | `repo.organizations.get()` ||| Organization |
751+
| List orgs | `repo.organizations.list()` ||| `{ organizations, cursor? }` |
752+
| **Collaborators** | | | | |
753+
| Grant access | `repo.collaborators.grant()` ||| void |
754+
| Revoke access | `repo.collaborators.revoke()` ||| void |
755+
| List collaborators | `repo.collaborators.list()` ||| `{ collaborators, cursor? }` |
756+
| Check access | `repo.collaborators.hasAccess()` ||| boolean |
757+
| Get role | `repo.collaborators.getRole()` ||| Role string |
758+
| Get permissions | `repo.collaborators.getPermissions()` ||| Permissions |
759+
| Transfer ownership | `repo.collaborators.transferOwnership()` ||| void |
621760

622761
## Type System
623762

packages/sdk-core/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,9 @@
8888
"@atproto/lexicon": "^0.5.1",
8989
"@atproto/oauth-client": "^0.5.10",
9090
"@atproto/oauth-client-node": "^0.3.12",
91-
"@hypercerts-org/lexicon": "0.10.0-beta.10",
91+
"@hypercerts-org/lexicon": "0.10.0-beta.11",
9292
"eventemitter3": "^5.0.1",
93+
"type-fest": "^5.4.1",
9394
"zod": "^3.24.4"
9495
}
9596
}

0 commit comments

Comments
 (0)