Skip to content

Commit e4f585f

Browse files
committed
feat: update to match lexicon updates for attachments and remove evidence.
1 parent b8ad8b3 commit e4f585f

File tree

11 files changed

+628
-181
lines changed

11 files changed

+628
-181
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@hypercerts-org/sdk-core": minor
3+
---
4+
5+
Rename evidence records to attachments and update schema to match lexicon changes
6+
7+
**Breaking Changes:**
8+
9+
- `addEvidence()``addAttachment()`
10+
- `CreateHypercertEvidenceParams``CreateAttachmentParams`
11+
- `evidenceUris``attachmentUris` in create results
12+
- `evidenceAdded``attachmentAdded` event
13+
- Evidence type exports replaced with Attachment equivalents

packages/sdk-core/src/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ export type {
9191
OrganizationOperations,
9292
CreateHypercertParams,
9393
CreateHypercertResult,
94-
CreateHypercertEvidenceParams,
9594
CreateOrganizationParams,
9695
LocationParams,
9796
ContributionDetailsParams,
@@ -162,7 +161,7 @@ export type {
162161
BadgeResponse,
163162
FundingReceipt,
164163
// SDK-specific types
165-
HypercertEvidence,
164+
HypercertAttachment,
166165
HypercertImage,
167166
HypercertImageRecord,
168167
BlobRef,
@@ -171,6 +170,9 @@ export type {
171170
HypercertProjectWithMetadata,
172171
CreateProjectParams,
173172
UpdateProjectParams,
173+
CreateAttachmentParams,
174+
UpdateAttachmentParams,
175+
AttachmentParams,
174176
} from "./services/hypercerts/types.js";
175177

176178
// Re-export ATProto lexicon types

packages/sdk-core/src/lexicons.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -168,16 +168,10 @@ export const HYPERCERT_COLLECTIONS = {
168168
EVALUATION: EVALUATION_NSID,
169169

170170
/**
171-
* Attachment record collection (formerly evidence).
172-
* @remarks Renamed from EVIDENCE in beta.13
171+
* Attachment record collection.
173172
*/
174173
ATTACHMENT: ATTACHMENT_NSID,
175174

176-
/**
177-
* @deprecated Use ATTACHMENT instead. Renamed in beta.13.
178-
*/
179-
EVIDENCE: ATTACHMENT_NSID,
180-
181175
/**
182176
* Collection record collection (groups of hypercerts).
183177
* Projects are now collections with type='project'.

packages/sdk-core/src/lexicons/sidecar.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ export async function attachSidecar(repo: Repository, params: AttachSidecarParam
168168
* This orchestrates the creation of a main record followed by one or more
169169
* sidecar records that reference it. This is useful for workflows like:
170170
* - Creating a project with multiple hypercert claims
171-
* - Creating a hypercert with evidence and evaluation records
171+
* - Creating a hypercert with attachments and evaluation records
172172
*
173173
* @param repo - The repository instance
174174
* @param params - Parameters including the main record and sidecar definitions

packages/sdk-core/src/repository/HypercertOperationsImpl.ts

Lines changed: 142 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ import {
3636
type UpdateProjectParams,
3737
type CreateMeasurementParams,
3838
type UpdateMeasurementParams,
39+
type CreateAttachmentParams,
3940
} from "../services/hypercerts/types.js";
4041
import type {
41-
CreateHypercertEvidenceParams,
4242
LocationParams,
4343
CreateHypercertParams,
4444
CreateHypercertResult,
@@ -470,38 +470,38 @@ export class HypercertOperationsImpl extends EventEmitter<HypercertEvents> imple
470470
}
471471

472472
/**
473-
* Creates evidence records with progress tracking.
473+
* Creates attachment records and returns their URIs.
474474
*
475-
* @param hypercertUri - URI of the hypercert
476-
* @param evidenceItems - Array of evidence data (without subjectUri)
475+
* @param hypercertUri - URI of the parent hypercert
476+
* @param attachmentItems - Array of attachment items to create
477477
* @param onProgress - Optional progress callback
478-
* @returns Promise resolving to array of evidence URIs
478+
* @returns Promise resolving to array of attachment URIs
479479
* @internal
480480
*/
481-
private async createEvidenceWithProgress(
481+
private async createAttachmentsWithProgress(
482482
hypercertUri: string,
483-
evidenceItems: Array<Omit<CreateHypercertEvidenceParams, "subjectUri">>,
483+
attachmentItems: Array<Omit<CreateAttachmentParams, "subjects">>,
484484
onProgress?: (step: ProgressStep) => void,
485485
): Promise<string[]> {
486-
this.emitProgress(onProgress, { name: "addEvidence", status: "start" });
486+
this.emitProgress(onProgress, { name: "addAttachment", status: "start" });
487487
try {
488-
const evidenceUris = await Promise.all(
489-
evidenceItems.map((evidence) =>
490-
this.addEvidence({
491-
subjectUri: hypercertUri,
492-
...evidence,
493-
} as CreateHypercertEvidenceParams).then((result) => result.uri),
488+
const attachmentUris = await Promise.all(
489+
attachmentItems.map((attachment) =>
490+
this.addAttachment({
491+
...attachment,
492+
subjects: hypercertUri,
493+
} as CreateAttachmentParams).then((result) => result.uri),
494494
),
495495
);
496496
this.emitProgress(onProgress, {
497-
name: "addEvidence",
497+
name: "addAttachment",
498498
status: "success",
499-
data: { count: evidenceUris.length },
499+
data: { count: attachmentUris.length },
500500
});
501-
return evidenceUris;
501+
return attachmentUris;
502502
} catch (error) {
503-
this.emitProgress(onProgress, { name: "addEvidence", status: "error", error: error as Error });
504-
this.logger?.warn(`Failed to create evidence: ${error instanceof Error ? error.message : "Unknown"}`);
503+
this.emitProgress(onProgress, { name: "addAttachment", status: "error", error: error as Error });
504+
this.logger?.warn(`Failed to create attachments: ${error instanceof Error ? error.message : "Unknown"}`);
505505
throw error;
506506
}
507507
}
@@ -534,7 +534,7 @@ export class HypercertOperationsImpl extends EventEmitter<HypercertEvents> imple
534534
* - `createHypercert`: Main hypercert record creation
535535
* - `attachLocation`: Location record creation
536536
* - `createContributions`: Contribution records creation
537-
* - `addEvidence`: Evidence records creation
537+
* - `addAttachment`: Attachment records creation
538538
*
539539
* @example Minimal hypercert
540540
* ```typescript
@@ -569,7 +569,7 @@ export class HypercertOperationsImpl extends EventEmitter<HypercertEvents> imple
569569
* { contributors: ["did:plc:org1"], role: "coordinator" },
570570
* { contributors: ["did:plc:org2"], role: "implementer" },
571571
* ],
572-
* evidence: [{ uri: "https://...", description: "Satellite data" }],
572+
* attachments: [{ uri: "https://...", description: "Satellite data" }],
573573
* onProgress: console.log,
574574
* });
575575
* ```
@@ -621,10 +621,14 @@ export class HypercertOperationsImpl extends EventEmitter<HypercertEvents> imple
621621
result.hypercertUri = hypercertUri;
622622
result.hypercertCid = hypercertCid;
623623

624-
// Step 6: Add evidence records if provided
625-
if (params.evidence && params.evidence.length > 0) {
624+
// Step 6: Add attachment records if provided
625+
if (params.attachments && params.attachments.length > 0) {
626626
try {
627-
result.evidenceUris = await this.createEvidenceWithProgress(hypercertUri, params.evidence, params.onProgress);
627+
result.attachmentUris = await this.createAttachmentsWithProgress(
628+
hypercertUri,
629+
params.attachments,
630+
params.onProgress,
631+
);
628632
} catch {
629633
// Error already logged and progress emitted
630634
}
@@ -1020,7 +1024,7 @@ export class HypercertOperationsImpl extends EventEmitter<HypercertEvents> imple
10201024
}
10211025

10221026
/**
1023-
* Check if an AttachLocationParams is the object form (not a StrongRef or string).
1027+
* Check if an AttachLocationParams is the object form (not a StrongRef or string or Blob).
10241028
* @internal
10251029
*/
10261030
private isLocationObject(location: LocationParams): location is CreateLocationParams {
@@ -1029,7 +1033,8 @@ export class HypercertOperationsImpl extends EventEmitter<HypercertEvents> imple
10291033
!("uri" in location) &&
10301034
!("cid" in location) &&
10311035
location !== null &&
1032-
!Array.isArray(location)
1036+
!Array.isArray(location) &&
1037+
!(location instanceof Blob)
10331038
);
10341039
}
10351040

@@ -1101,62 +1106,136 @@ export class HypercertOperationsImpl extends EventEmitter<HypercertEvents> imple
11011106
}
11021107

11031108
/**
1104-
* TODO: Match Attachment Lexicon
1105-
* Adds evidence to any subject via the subject ref.
1109+
* Resolves attachment subjects to an array of StrongRefs.
1110+
* Accepts single or multiple subjects and normalizes to StrongRef array.
11061111
*
1107-
* @param evidence - HypercertEvidenceInput
1108-
* @returns Promise resolving to update result
1112+
* @param subjectsInput - Single subject or array of subjects (URI strings or StrongRefs)
1113+
* @returns Promise resolving to array of StrongRefs
1114+
* @throws {@link ValidationError} if subject format is invalid
1115+
* @throws {@link NetworkError} if fetching subject record fails
1116+
* @internal
1117+
*/
1118+
private async resolveAttachmentSubjects(
1119+
subjectsInput: string | StrongRef | Array<string | StrongRef>,
1120+
): Promise<StrongRef[]> {
1121+
const subjectsArray = Array.isArray(subjectsInput) ? subjectsInput : [subjectsInput];
1122+
1123+
return await Promise.all(subjectsArray.map((subject) => this.resolveToStrongRef(subject)));
1124+
}
1125+
1126+
/**
1127+
* Resolves attachment content items to an array of URI or Blob references.
1128+
* Accepts single or multiple content items and normalizes to array.
1129+
* Uploads Blob content and formats URI content.
1130+
*
1131+
* @param contentInput - Single content item or array (URI strings or Blobs)
1132+
* @returns Promise resolving to array of URI refs or Blob refs
1133+
* @throws {@link NetworkError} if blob upload fails
1134+
* @internal
1135+
*/
1136+
private async resolveAttachmentContent(contentInput: string | Blob | Array<string | Blob>) {
1137+
const contentArray = Array.isArray(contentInput) ? contentInput : [contentInput];
1138+
1139+
return await Promise.all(contentArray.map((item) => this.resolveUriOrBlob(item, "application/octet-stream")));
1140+
}
1141+
1142+
/**
1143+
* Builds an attachment record from resolved components.
1144+
*
1145+
* @param subjects - Resolved subject StrongRefs
1146+
* @param content - Resolved content items (URI or Blob refs)
1147+
* @param locationRef - Optional resolved location StrongRef
1148+
* @param rest - Remaining attachment parameters (title, description, etc.)
1149+
* @returns Fully constructed attachment record
1150+
* @internal
1151+
*/
1152+
private buildAttachmentRecord(
1153+
subjects: StrongRef[],
1154+
content: Awaited<ReturnType<typeof this.resolveAttachmentContent>>,
1155+
locationRef: StrongRef | undefined,
1156+
rest: Omit<CreateAttachmentParams, "subjects" | "content" | "location">,
1157+
): HypercertAttachment {
1158+
const createdAt = new Date().toISOString();
1159+
1160+
return {
1161+
...rest,
1162+
$type: HYPERCERT_COLLECTIONS.ATTACHMENT,
1163+
createdAt,
1164+
subjects,
1165+
content,
1166+
...(locationRef && { location: locationRef }),
1167+
} as HypercertAttachment;
1168+
}
1169+
1170+
/**
1171+
* Adds an attachment to any subject record.
1172+
*
1173+
* Attachments provide commentary, context, evidence, or documentary material
1174+
* related to hypercert records.
1175+
*
1176+
* @param attachment - Attachment parameters
1177+
* @returns Promise resolving to attachment record URI and CID
11091178
* @throws {@link ValidationError} if validation fails
11101179
* @throws {@link NetworkError} if the operation fails
11111180
*
1112-
* @example
1181+
* @example Single subject with URI content
1182+
* ```typescript
1183+
* await repo.hypercerts.addAttachment({
1184+
* subjects: "at://did:plc:u7h3dstby64di67bxaotzxcz/org.hypercerts.claim.activity/3mbvv5d7ixh2g",
1185+
* content: "https://example.com/report.pdf",
1186+
* title: "Impact Report",
1187+
* contentType: "report"
1188+
* });
1189+
* ```
1190+
*
1191+
* @example Multiple subjects with mixed content
11131192
* ```typescript
1114-
* await repo.hypercerts.addEvidence({
1115-
* subjectUri: "at://did:plc:u7h3dstby64di67bxaotzxcz/org.hypercerts.claim.activity/3mbvv5d7ixh2g"
1116-
* content: Blob,
1117-
* title: "Meeting Notes",
1118-
* shortDescription: "Meetings notes from the 3rd of December 2025",
1119-
* description: "The meeting with the board of directors and audience on 2025 in regards to the ecological landscape",
1120-
* relationType: "supports",
1121-
* })
1193+
* await repo.hypercerts.addAttachment({
1194+
* subjects: [
1195+
* "at://did:plc:abc/org.hypercerts.claim.activity/xyz",
1196+
* { uri: "at://...", cid: "..." }
1197+
* ],
1198+
* content: [
1199+
* "https://example.com/report.pdf",
1200+
* new Blob(["data"], { type: "application/pdf" })
1201+
* ],
1202+
* title: "Multi-source Evidence",
1203+
* location: { uri: "at://...", cid: "..." }
1204+
* });
11221205
* ```
11231206
*/
1124-
async addEvidence(evidence: CreateHypercertEvidenceParams): Promise<UpdateResult> {
1207+
async addAttachment(attachment: CreateAttachmentParams): Promise<UpdateResult> {
11251208
try {
1126-
const { subjectUri, content, ...rest } = evidence;
1127-
const subject = await this.get(subjectUri);
1128-
const createdAt = new Date().toISOString();
1209+
const { subjects: subjectsInput, content: contentInput, location: locationInput, ...rest } = attachment;
11291210

1130-
const evidenceContent = await this.resolveUriOrBlob(content, "application/octet-stream");
1131-
// Note: In beta.13, evidence was renamed to attachment with schema changes
1132-
// - subject -> subjects (array)
1133-
// - content is now an array
1134-
// This is a temporary fix to maintain backward compatibility
1135-
// and since evidence is no longer available in the lexicons
1136-
const evidenceRecord: HypercertAttachment = {
1137-
...rest,
1138-
$type: HYPERCERT_COLLECTIONS.ATTACHMENT,
1139-
createdAt,
1140-
content: [evidenceContent], // content is now an array
1141-
subjects: [{ uri: subject.uri, cid: subject.cid }], // subject -> subjects array
1142-
};
1143-
const validation = validate(evidenceRecord, HYPERCERT_COLLECTIONS.ATTACHMENT, "main", false);
1211+
if (!contentInput) {
1212+
throw new ValidationError("content is required for attachments");
1213+
}
1214+
const [subjects, content, locationRef] = await Promise.all([
1215+
this.resolveAttachmentSubjects(subjectsInput),
1216+
this.resolveAttachmentContent(contentInput),
1217+
locationInput ? this.resolveLocation(locationInput) : Promise.resolve(undefined),
1218+
]);
1219+
const attachmentRecord = this.buildAttachmentRecord(subjects, content, locationRef, rest);
1220+
const validation = validate(attachmentRecord, HYPERCERT_COLLECTIONS.ATTACHMENT, "main", false);
11441221
if (!validation.success) {
1145-
throw new ValidationError(`Invalid evidence record: ${validation.error?.message}`);
1222+
throw new ValidationError(`Invalid attachment record: ${validation.error?.message}`);
11461223
}
11471224
const result = await this.agent.com.atproto.repo.createRecord({
11481225
repo: this.repoDid,
1149-
collection: HYPERCERT_COLLECTIONS.EVIDENCE,
1150-
record: evidenceRecord,
1226+
collection: HYPERCERT_COLLECTIONS.ATTACHMENT,
1227+
record: attachmentRecord,
11511228
});
1229+
11521230
if (!result.success) {
1153-
throw new NetworkError(`Failed to add evidence`);
1231+
throw new NetworkError(`Failed to add attachment`);
11541232
}
1155-
this.emit("evidenceAdded", { uri: result.data.uri, cid: result.data.cid });
1233+
this.emit("attachmentAdded", { uri: result.data.uri, cid: result.data.cid });
1234+
11561235
return { uri: result.data.uri, cid: result.data.cid };
11571236
} catch (error) {
11581237
if (error instanceof ValidationError || error instanceof NetworkError) throw error;
1159-
throw new NetworkError(`Failed to add evidence: ${error instanceof Error ? error.message : "Unknown"}`, error);
1238+
throw new NetworkError(`Failed to add attachment: ${error instanceof Error ? error.message : "Unknown"}`, error);
11601239
}
11611240
}
11621241

packages/sdk-core/src/repository/Repository.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ export class Repository {
401401
* High-level hypercert operations.
402402
*
403403
* Provides a convenient API for creating and managing hypercerts,
404-
* including related records like locations, contributions, and evidence.
404+
* including related records like locations, contributions, and attachments.
405405
*
406406
* @returns {@link HypercertOperations} interface with EventEmitter capabilities
407407
*

0 commit comments

Comments
 (0)