Skip to content

feat: add profile update and create operations#125

Open
Kzoeps wants to merge 3 commits intodevelopfrom
profile
Open

feat: add profile update and create operations#125
Kzoeps wants to merge 3 commits intodevelopfrom
profile

Conversation

@Kzoeps
Copy link
Contributor

@Kzoeps Kzoeps commented Jan 29, 2026

Summary

  • Migrates profile operations to use the Hypercerts profile lexicon (app.certified.profile) instead of Bluesky actor profile
  • Adds support for profile creation and updates with proper hypercerts image format handling (smallImage/largeImage)
  • Implements two-step profile fetching: handle from identity system + record from profile collection
  • Adds pronouns field support (max 20 graphemes)
  • Wraps avatar/banner uploads in appropriate hypercerts image formats (smallImage for avatar, largeImage for banner)
  • Updates SDK types and interfaces to use HypercertImageRecord for avatar/banner
  • Adds comprehensive test coverage for profile operations including image format handling

Changes

Core SDK (sdk-core)

  • ProfileOperationsImpl: Refactored to use app.certified.profile collection with proper image wrapping
  • Types: Added HypercertProfile, CreateHypercertProfileParams, UpdateHypercertProfileParams types
  • Interfaces: Updated ProfileOperations and ProfileParams to include pronouns and use HypercertImageRecord
  • Tests: Enhanced test suite with 70% coverage including image format validation

React SDK (sdk-react)

  • Types: Updated Profile interface to use HypercertImageRecord for avatar/banner
  • Mocks & Fixtures: Updated test utilities to use hypercerts image format

Breaking Changes

  • Profile avatar/banner fields now return HypercertImageRecord objects instead of strings
  • Profile operations now target app.certified.profile collection instead of app.bsky.actor.profile

Summary by CodeRabbit

  • New Features

    • Dual-profile API: separate Bluesky and Certified profile methods (get/create/update).
    • Blob URL utilities to construct and extract blob CIDs.
  • Improvements

    • Avatar/banner now use structured image records with CDN/blob URL support.
    • Evaluations include structured evaluator objects; blob uploads return standardized refs.
    • Profile handling supports pronouns and pds/server URL integration.
  • Tests

    • Expanded unit tests covering profiles, blob URLs, uploads, and image handling.
  • Breaking Changes

    • profile.avatar and profile.banner changed from string URLs to structured image objects.

@changeset-bot
Copy link

changeset-bot bot commented Jan 29, 2026

🦋 Changeset detected

Latest commit: 1e5e282

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@hypercerts-org/sdk-core Minor
@hypercerts-org/sdk-react Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link

coderabbitai bot commented Jan 29, 2026

Important

Review skipped

This PR was authored by the user configured for CodeRabbit reviews. By default, CodeRabbit skips reviewing PRs authored by this user. It's recommended to use a dedicated user account to post CodeRabbit review feedback.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Profile storage moved from app.bsky.actor.profile to app.certified.profile; profiles now support pronouns and use Hypercert image records for avatar/banner. Blob URL utilities and CID extraction added. Blob upload API now returns BlobRef. ProfileOperations and related types/methods were split into Bluesky vs Certified flows and tests updated.

Changes

Cohort / File(s) Summary
Package manifest
packages/sdk-core/package.json
Added devDependencies and pinned/updated several dependencies (@atproto/api, @atproto/lexicon, @hypercerts-org/lexicon, multiformats, types).
Profile operations & public types
packages/sdk-core/src/repository/ProfileOperationsImpl.ts, packages/sdk-core/src/repository/interfaces.ts, packages/sdk-core/src/services/hypercerts/types.ts, packages/sdk-core/src/types.ts
Reworked profile API into separate Bluesky vs Certified methods (get/create/update for each); constructor now accepts pdsUrl/serverUrl; public types updated (BskyProfile, CertifiedProfile, Create/Update params); avatar/banner handled as Hypercert image records; pronouns added.
Repository integration
packages/sdk-core/src/repository/Repository.ts
Repository now instantiates ProfileOperationsImpl with serverUrl (constructor signature changed).
Hypercert operations
packages/sdk-core/src/repository/HypercertOperationsImpl.ts
Deterministic idempotent hashing introduced (sha256Hash); blob handling adjusted to use BlobRef; evaluator type changed from string[] to Array<{ did: string }>; public type imports adjusted.
Blob operations
packages/sdk-core/src/repository/BlobOperationsImpl.ts, packages/sdk-core/src/repository/types.ts
BlobOperations.upload now returns BlobRef (not structured ref/mime/size); SDS path simplified; deprecation JSDoc added for old shapes.
Blob URL utilities & lexicons
packages/sdk-core/src/lib/blob-url.ts, packages/sdk-core/src/index.ts, packages/sdk-core/src/lexicons.ts
Added getBlobUrl and extractCidFromImage utilities and exported them; registered certified profile lexicon/NSID and collection constants.
Core tests
packages/sdk-core/tests/lib/blob-url.test.ts, packages/sdk-core/tests/repository/ProfileOperationsImpl.test.ts, packages/sdk-core/tests/repository/BlobOperationsImpl.test.ts, packages/sdk-core/tests/repository/Repository.test.ts
New and updated tests for blob-url utilities, profile get/create/update flows (Bluesky + Certified), and updated BlobOperations expectations for BlobRef shape.
React SDK changes & tests
packages/sdk-react/src/testing/mocks.ts, packages/sdk-react/tests/utils/fixtures.ts, packages/sdk-react/tests/testing/mocks.test.ts, packages/sdk-react/src/types.ts, packages/sdk-react/src/hooks/useProfile.ts, packages/sdk-react/src/utils/ssr.ts
Mocks and fixtures adapted: workScope now typed object with $type; Profile types/docs updated to treat avatar/banner as URLs; hooks/SSR switched to use getCertifiedProfile/updateCertifiedProfile.
Exports & re-exports
packages/sdk-core/src/index.ts, packages/sdk-core/src/types.ts
Added re-exports for blob URL utilities and new Hypercert profile-related types.
Changeset / changelog
.changeset/profile-certified-lexicon.md
Documented profile storage switch, avatar/banner image format changes, pronouns addition, and breaking changes.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Client as rgba(66,133,244,0.5)
participant Agent as rgba(52,168,83,0.5)
participant Repo as rgba(255,193,7,0.5)
participant PDS as rgba(234,67,53,0.5)

Client->>Agent: request profile
Agent->>Repo: fetch Bluesky profile (if applicable)
Agent->>Repo: getRecord(collection=app.certified.profile, rkey="self")
Repo-->>Agent: certified profile record (may include smallImage/largeImage)
Agent->>PDS: getBlobUrl(did, cid) / fetch image URL
PDS-->>Agent: blob URL
Agent-->>Client: merged profile with avatar/banner as resolved URLs

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • aspiers
  • bitbeckers

Poem

"I hopped through code with curious paws,
Swapped profiles, stitched new image laws,
Pronouns snug in records bright,
CIDs and blobs dance into light 🐇,
Hooray — ready for the next deploy!"

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: add profile update and create operations' directly reflects the main changes: implementation of profile create and update functionality, which are core features added throughout the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@packages/sdk-core/package.json`:
- Line 91: Replace the local filesystem dependency for "@hypercerts-org/lexicon"
in package.json with a published npm version; specifically change the value that
currently points to "file:/home/kzoeps/Projects/gainforest/hypercerts-lexicon"
to a semver string (e.g., "^0.10.0-beta.13") so CI and other developers can
install from the registry—if the required changes aren’t yet published, publish
the package first and then update the "@hypercerts-org/lexicon" entry to the
released version.

In `@packages/sdk-core/src/repository/ProfileOperationsImpl.ts`:
- Around line 245-247: The catch in ProfileOperationsImpl (around the getRecord
call) is too broad and swallows all errors; change it to only handle the "record
not found" case (e.g., check error.status === 404 or error.name/type/message
that your PDS client uses for not-found) and in that case fall through to return
just the handle, but for any other error rethrow (or log and rethrow) so
network/auth/server errors aren't hidden; update the error check around
getRecord in ProfileOperationsImpl to discriminate and only suppress the
specific not-found error.
🧹 Nitpick comments (1)
packages/sdk-core/tests/repository/ProfileOperationsImpl.test.ts (1)

8-8: Consider importing PROFILE_COLLECTION from the implementation.

The constant PROFILE_COLLECTION is duplicated here. If the value changes in the implementation but not in tests, tests could pass with incorrect assertions. Consider exporting the constant from ProfileOperationsImpl.ts or a shared constants file.

@Kzoeps Kzoeps force-pushed the profile branch 5 times, most recently from a244549 to ae706d9 Compare January 29, 2026 16:15
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/sdk-core/src/repository/ProfileOperationsImpl.ts (1)

294-303: Replace ProfileParams with CreateHypercertProfileParams and UpdateHypercertProfileParams.

The ProfileOperations interface uses ProfileParams for both create() and update() methods, but this violates SDK-core conventions. The correct types already exist:

  • create(params: ProfileParams) should be create(params: CreateHypercertProfileParams)
  • update(params: ProfileParams) should be update(params: UpdateHypercertProfileParams)

UpdateHypercertProfileParams correctly implements the convention pattern (Omit<Partial<CreateHypercertProfileParams>, ...> with nullable image fields), so update the interface signatures and remove the unused ProfileParams definition.

🤖 Fix all issues with AI agents
In `@packages/sdk-core/src/services/hypercerts/types.ts`:
- Around line 163-223: Add a missing HypercertProfileParams union type for the
profile variants (e.g., type HypercertProfileParams =
CreateHypercertProfileParams | UpdateHypercertProfileParams) and refactor
UpdateHypercertProfileParams to use OverrideProperties for consistency: replace
the current Omit<Partial<CreateHypercertProfileParams>, "avatar" | "banner"> & {
avatar?: HypercertImage | null; banner?: HypercertImage | null; } with
OverrideProperties<Partial<CreateHypercertProfileParams>, { avatar?:
HypercertImage | null; banner?: HypercertImage | null; }>, keeping the existing
CreateHypercertProfileParams and HypercertImage symbols intact.
🧹 Nitpick comments (1)
.changeset/profile-certified-lexicon.md (1)

1-25: Add a pre‑1.0 semver caveat for the minor bump.

The changeset already lists breaking changes, but a short note that these packages are still 0.x will better set expectations for consumers.

✏️ Suggested wording
 **Breaking Changes:**

+_Note: These packages are still pre‑1.0; minor bumps may include breaking changes._
 
 - `profile.avatar` and `profile.banner` are now `HypercertImageRecord | undefined` instead of `string | undefined`
 - Profile data is stored in a different collection (`app.certified.profile` instead of `app.bsky.actor.profile`)

Based on learnings: For pre-1.0 libraries (0.x), minor version bumps can introduce breaking changes. In changeset files, note that breaking changes may occur with minor bumps and clearly document any API changes, removal or behavioral changes, and compatibility caveats.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/sdk-core/src/repository/HypercertOperationsImpl.ts (1)

1798-1807: Critical: evaluator shape mismatches type/schema (build failure).

HypercertEvaluation.evaluators is currently typed as string[] (TS2322 in CI), but the record now writes { did }[]. This will also fail lexicon validation if the schema still expects strings. Please align the SDK type and lexicon schema, or revert to the string array until the lexicon is updated.

If the lexicon still expects strings, a quick fix is:

🔧 Minimal fix if schema expects string[]
-        evaluators: params.evaluators.map((evaluator) => ({ did: evaluator })),
+        evaluators: params.evaluators,
#!/bin/bash
# Verify the lexicon schema and generated types for evaluators shape.
rg -n "evaluators" node_modules/@hypercerts-org/lexicon -g '*.json' -g '*.d.ts' -g '*.ts'

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/sdk-core/src/repository/HypercertOperationsImpl.ts`:
- Around line 1803-1809: Add a new CreateEvaluationParams type (and the required
Main/alias/Update/union types for evaluation) to
packages/sdk-core/src/services/hypercerts/types.ts, then update the
HypercertOperationsImpl.addEvaluation method signature to async
addEvaluation(params: CreateEvaluationParams): Promise<CreateResult>; inside
addEvaluation build the HypercertEvaluation using params and preserve
caller-provided $type and createdAt via nullish coalescing (e.g. $type:
params.$type ?? HYPERCERT_COLLECTIONS.EVALUATION and createdAt: params.createdAt
?? new Date().toISOString()), and keep mapping evaluators from params.evaluators
to { did } objects before returning the CreateResult.
🧹 Nitpick comments (1)
packages/sdk-core/src/repository/ProfileOperationsImpl.ts (1)

231-240: Consider runtime validation for record fields.

The type assertions (as string | undefined, as HypercertImageRecord | undefined) are unsafe casts from Record<string, unknown>. If the PDS returns unexpected data shapes, these could lead to runtime issues downstream.

Per coding guidelines, consider using Zod schemas for runtime validation of the profile record structure.

Comment on lines 1803 to 1809
const evaluationRecord: HypercertEvaluation = {
$type: HYPERCERT_COLLECTIONS.EVALUATION,
subject: { uri: subject.uri, cid: subject.cid },
evaluators: params.evaluators,
evaluators: params.evaluators.map((evaluator) => ({ did: evaluator })),
summary: params.summary,
createdAt,
};
Copy link

@coderabbitai coderabbitai bot Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's see the actual code at lines 1803-1809
echo "=== Code at lines 1803-1809 ==="
sed -n '1800,1815p' packages/sdk-core/src/repository/HypercertOperationsImpl.ts

# Find the HypercertEvaluation type definition
echo -e "\n=== HypercertEvaluation type definition ==="
rg -n "type HypercertEvaluation|interface HypercertEvaluation" packages/sdk-core/src/

# Check evaluators field definition
echo -e "\n=== Evaluators field in types ==="
rg -n "evaluators" packages/sdk-core/src/services/hypercerts/types.ts -A 2 -B 2

Repository: hypercerts-org/hypercerts-sdk

Length of output: 1047


🏁 Script executed:

#!/bin/bash
# Check the lexicon definition for the Evaluation record
echo "=== Searching lexicon for Evaluation ==="
find . -name "*.ts" -o -name "*.json" | xargs grep -l "Evaluation" | grep -i lexicon | head -5

# Look for OrgHypercertsEvaluation in lexicon
echo -e "\n=== OrgHypercerts Evaluation type ==="
rg -n "OrgHypercertsEvaluation" packages/sdk-core/src/ -A 5 -B 1

Repository: hypercerts-org/hypercerts-sdk

Length of output: 431


🏁 Script executed:

#!/bin/bash
# Check how evaluators is used elsewhere in the file
echo "=== All evaluators references in HypercertOperationsImpl ==="
rg -n "evaluators" packages/sdk-core/src/repository/HypercertOperationsImpl.ts -B 2 -A 2

Repository: hypercerts-org/hypercerts-sdk

Length of output: 1190


🏁 Script executed:

#!/bin/bash
# Find OrgHypercertsClaimEvaluation definition
echo "=== Looking for OrgHypercertsClaimEvaluation definition ==="
rg -n "OrgHypercertsClaimEvaluation" packages/sdk-core/src/ -A 10 | head -50

# Check the main lexicon definitions
echo -e "\n=== Checking for lexicon files ==="
find packages/sdk-core/src -name "*lexicon*" -type f

Repository: hypercerts-org/hypercerts-sdk

Length of output: 3924


🏁 Script executed:

#!/bin/bash
# Look for where OrgHypercertsClaimEvaluation comes from
echo "=== Imports of OrgHypercertsClaimEvaluation ==="
rg "import.*OrgHypercertsClaimEvaluation" packages/sdk-core/src/

# Check the schema files
echo -e "\n=== Schema and definition files ==="
find packages/sdk-core/src -name "*.ts" | xargs grep -l "evaluators.*did" | head -10

Repository: hypercerts-org/hypercerts-sdk

Length of output: 217


🏁 Script executed:

#!/bin/bash
# Look at the types file more carefully
echo "=== Full types.ts file (first 200 lines) ==="
head -200 packages/sdk-core/src/services/hypercerts/types.ts

Repository: hypercerts-org/hypercerts-sdk

Length of output: 7180


🏁 Script executed:

#!/bin/bash
# Check what `@hypercerts-org/lexicon` exports for OrgHypercertsClaimEvaluation
echo "=== Checking lexicon package ==="
# Since it's from `@hypercerts-org/lexicon`, let's see if there's a local copy or we need web search
find packages -name "lexicon*" -type d

# Check the lexicons.ts file
echo -e "\n=== Content of lexicons.ts ==="
cat packages/sdk-core/src/lexicons.ts

Repository: hypercerts-org/hypercerts-sdk

Length of output: 5715


🌐 Web query:

@hypercerts-org/lexicon OrgHypercertsClaimEvaluation evaluators field type definition

💡 Result:

I couldn’t confirm the evaluators field’s type for OrgHypercertsClaimEvaluation from publicly accessible schema text.

What I was able to verify is that @hypercerts-org/lexicon is generated from the hypercerts-lexicon repository’s ATProto lexicon JSON schemas, but the repo pages that would show the actual JSON for the org.hypercerts.claim.evaluation record (where evaluators would be defined) weren’t retrievable in a way that exposed the file contents. [1]

The quickest way to get the exact type definition is to open the lexicon JSON for the claim evaluation record (e.g., org.hypercerts.claim.evaluation) and look at record.properties.evaluators (and any referenced defs). If the schema is published to ATProto, Lexicon Garden also suggests resolving it directly by NSID via https://lexicon.garden/nsid/<nsid>. [2]

Sources: [1], [2]


🏁 Script executed:

#!/bin/bash
# Find where EVALUATION_LEXICON_JSON comes from - it's from `@hypercerts-org/lexicon`
# Let's check if there's a local definition or if we need to check node_modules structure

echo "=== Searching for evaluation lexicon JSON files ==="
find packages -name "*evaluation*" -type f | grep -E "\.(json|ts)$"

# Look for lexicon JSON in a common location
echo -e "\n=== Checking node_modules structure ==="
find node_modules/@hypercerts-org/lexicon -name "*evaluation*" 2>/dev/null | head -20

Repository: hypercerts-org/hypercerts-sdk

Length of output: 165


🏁 Script executed:

#!/bin/bash
# Let's check the generated types from the lexicon package
# Look for the type definition in `@hypercerts-org/lexicon`
echo "=== Looking for OrgHypercertsClaimEvaluation in node_modules ==="
find node_modules/@hypercerts-org/lexicon -name "*.d.ts" 2>/dev/null | xargs grep -l "OrgHypercertsClaimEvaluation" 2>/dev/null | head -5

# And check for the actual evaluation schema
echo -e "\n=== Looking for evaluation schema ==="
find node_modules/@hypercerts-org/lexicon -name "*.json" 2>/dev/null | xargs grep -l "evaluation" 2>/dev/null | head -5

Repository: hypercerts-org/hypercerts-sdk

Length of output: 176


🏁 Script executed:

#!/bin/bash
# Check if nullish coalescing is used for $type and createdAt in the current code
echo "=== Checking for nullish coalescing pattern at lines 1803-1809 ==="
sed -n '1800,1815p' packages/sdk-core/src/repository/HypercertOperationsImpl.ts

# Look for similar patterns in other record creation methods
echo -e "\n=== Looking for other record creation methods with nullish coalescing ==="
rg -n "buildAttachmentRecord|createLocationRecord" packages/sdk-core/src/repository/ -A 10 | head -50

Repository: hypercerts-org/hypercerts-sdk

Length of output: 5608


🏁 Script executed:

#!/bin/bash
# Get the full buildAttachmentRecord method to see nullish coalescing pattern
echo "=== Full buildAttachmentRecord method ==="
sed -n '1131,1155p' packages/sdk-core/src/repository/HypercertOperationsImpl.ts

# Now let's check the buildLocationRecord or createLocationRecord
echo -e "\n=== Looking for createLocationRecord ==="
rg -n "createLocationRecord|buildLocationRecord" packages/sdk-core/src/repository/HypercertOperationsImpl.ts -A 15 | head -60

Repository: hypercerts-org/hypercerts-sdk

Length of output: 3233


🏁 Script executed:

#!/bin/bash
# Search for all nullish coalescing usage for $type and createdAt in the file
echo "=== Nullish coalescing for \$type and createdAt patterns ==="
rg -n "\$\?|createdAt.*\?\?" packages/sdk-core/src/repository/HypercertOperationsImpl.ts | head -20

Repository: hypercerts-org/hypercerts-sdk

Length of output: 244


🏁 Script executed:

#!/bin/bash
# Let's try running TypeScript compiler to see if there's actually a TS error
echo "=== Checking if there's a TS compiler available ==="
which tsc

# Try to run tsc on just the specific file
echo -e "\n=== Running TypeScript check ==="
cd packages/sdk-core && npx tsc --noEmit src/repository/HypercertOperationsImpl.ts 2>&1 | grep -A 5 -B 5 "1806\|evaluators" | head -30

Repository: hypercerts-org/hypercerts-sdk

Length of output: 219


🏁 Script executed:

#!/bin/bash
# Check if CreateEvaluationParams or similar type is defined
echo "=== Looking for CreateEvaluationParams type ==="
rg -n "CreateEvaluationParams|CreateHypercertEvaluationParams" packages/sdk-core/src/

# Check what types are defined in types.ts for other entities
echo -e "\n=== Checking what Create*Params types exist ==="
rg -n "^export type Create" packages/sdk-core/src/services/hypercerts/types.ts | head -20

Repository: hypercerts-org/hypercerts-sdk

Length of output: 646


🏁 Script executed:

#!/bin/bash
# Check the full method signature and see if there's a params type
echo "=== Full addEvaluation method signature and start ==="
sed -n '1798,1815p' packages/sdk-core/src/repository/HypercertOperationsImpl.ts

# Look for other similar add* methods to see the pattern
echo -e "\n=== Other add* methods ==="
rg -n "async add\w+\(" packages/sdk-core/src/repository/HypercertOperationsImpl.ts | head -10

Repository: hypercerts-org/hypercerts-sdk

Length of output: 1315


🏁 Script executed:

#!/bin/bash
# Check how measurement was created (similar pattern)
echo "=== Looking for addMeasurement method ==="
rg -n "async addMeasurement" packages/sdk-core/src/repository/HypercertOperationsImpl.ts -A 20 | head -40

Repository: hypercerts-org/hypercerts-sdk

Length of output: 1031


🏁 Script executed:

#!/bin/bash
# Check the complete types.ts to see all Create*Params patterns
echo "=== All Create*Params and Update*Params types ==="
rg -n "export type (Create|Update)" packages/sdk-core/src/services/hypercerts/types.ts

# Look for EvaluationParams specifically
echo -e "\n=== Any Evaluation*Params types ==="
rg -n "Evaluation" packages/sdk-core/src/services/hypercerts/types.ts

Repository: hypercerts-org/hypercerts-sdk

Length of output: 1194


🏁 Script executed:

#!/bin/bash
# Check CreateMeasurementParams to see the pattern
echo "=== CreateMeasurementParams definition ==="
rg -n "CreateMeasurementParams" packages/sdk-core/src/services/hypercerts/types.ts -A 10

# Check CreateAttachmentParams pattern
echo -e "\n=== CreateAttachmentParams definition ==="
rg -n "CreateAttachmentParams" packages/sdk-core/src/services/hypercerts/types.ts -A 10

Repository: hypercerts-org/hypercerts-sdk

Length of output: 4769


🏁 Script executed:

#!/bin/bash
# Look at the addEvaluation method more carefully - check if $type uses nullish coalescing
echo "=== Complete addEvaluation method ==="
sed -n '1798,1825p' packages/sdk-core/src/repository/HypercertOperationsImpl.ts

# Compare with addMeasurement to see the pattern
echo -e "\n=== addMeasurement for comparison ==="
sed -n '1657,1690p' packages/sdk-core/src/repository/HypercertOperationsImpl.ts

Repository: hypercerts-org/hypercerts-sdk

Length of output: 2579


🏁 Script executed:

#!/bin/bash
# Let's verify if there's supposed to be a CreateEvaluationParams type
# by checking the learnings guideline more carefully
echo "=== Looking for any UpdateEvaluationParams pattern ==="
rg -n "UpdateMeasurementParams|UpdateAttachmentParams" packages/sdk-core/src/services/hypercerts/types.ts

# Check if evaluation should have both Create and Update params
echo -e "\n=== Pattern: every entity should have Create/Update params ==="
rg -n "^export type (Create|Update)" packages/sdk-core/src/services/hypercerts/types.ts | grep -E "(Measurement|Attachment|Evaluation)"

# Look at the complete addMeasurement to see if it uses nullish coalescing
echo -e "\n=== Full measurementRecord creation with nullish coalescing check ==="
sed -n '1664,1677p' packages/sdk-core/src/repository/HypercertOperationsImpl.ts

Repository: hypercerts-org/hypercerts-sdk

Length of output: 1359


🏁 Script executed:

#!/bin/bash
# Check if there's a pattern for params.$type and params.createdAt
echo "=== Looking for params.$type or params.createdAt patterns ==="
rg -n "params\.\$type|params\.createdAt" packages/sdk-core/src/repository/HypercertOperationsImpl.ts | head -20

Repository: hypercerts-org/hypercerts-sdk

Length of output: 129


🏁 Script executed:

#!/bin/bash
# Let's check the entire types.ts structure around evaluation
echo "=== Context around HypercertEvaluation in types.ts ==="
sed -n '150,170p' packages/sdk-core/src/services/hypercerts/types.ts

Repository: hypercerts-org/hypercerts-sdk

Length of output: 1121


Define CreateEvaluationParams type and refactor addEvaluation to follow SDK patterns.

The addEvaluation method violates the SDK's type definition guidelines:

  1. Missing type definitions: Unlike CreateMeasurementParams and CreateAttachmentParams, there is no CreateEvaluationParams type. According to guidelines, each lexicon entity should define five types: Main, HypercertXxx alias, CreateXxxParams, UpdateXxxParams, and XxxParams union type. Add these missing types to packages/sdk-core/src/services/hypercerts/types.ts.

  2. Incorrect method signature: The method uses an inline params object instead of CreateEvaluationParams. Update the signature to:

    async addEvaluation(params: CreateEvaluationParams): Promise<CreateResult>
  3. Missing nullish coalescing: Per guidelines, use nullish coalescing to preserve caller-supplied $type and createdAt fields when provided:

    $type: params.$type ?? HYPERCERT_COLLECTIONS.EVALUATION,
    createdAt: params.createdAt ?? new Date().toISOString(),
🧰 Tools
🪛 GitHub Actions: Test

[error] 1806-1806: TS2322: Type '{ did: string; }[]' is not assignable to type 'string[]'.

🤖 Prompt for AI Agents
In `@packages/sdk-core/src/repository/HypercertOperationsImpl.ts` around lines
1803 - 1809, Add a new CreateEvaluationParams type (and the required
Main/alias/Update/union types for evaluation) to
packages/sdk-core/src/services/hypercerts/types.ts, then update the
HypercertOperationsImpl.addEvaluation method signature to async
addEvaluation(params: CreateEvaluationParams): Promise<CreateResult>; inside
addEvaluation build the HypercertEvaluation using params and preserve
caller-provided $type and createdAt via nullish coalescing (e.g. $type:
params.$type ?? HYPERCERT_COLLECTIONS.EVALUATION and createdAt: params.createdAt
?? new Date().toISOString()), and keep mapping evaluators from params.evaluators
to { did } objects before returning the CreateResult.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kzoeps I agree with @coderabbitai here - that's why I documented that pattern in AGENTS.md for agents to follow. That said, we need a whole bunch of cleanup on that front anyway, so we could also do it in a follow-up.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aspiers, understood! That makes sense given the broader cleanup work needed. Would you like me to create a follow-up issue to track the refactoring of addEvaluation to follow the CreateXxxParams pattern (along with any other similar methods that need the same treatment)? I can include:

  • Define CreateEvaluationParams, UpdateEvaluationParams, and related types
  • Update the method signature to use the new params type
  • Apply nullish coalescing for $type and createdAt fields
  • Document any other methods that need similar updates

Just let me know if you'd like me to open that issue!


🧠 Learnings used
Learnt from: Kzoeps
Repo: hypercerts-org/hypercerts-sdk PR: 121
File: packages/sdk-core/src/repository/HypercertOperationsImpl.ts:1108-1167
Timestamp: 2026-01-28T10:29:44.154Z
Learning: In `packages/sdk-core/src/repository/HypercertOperationsImpl.ts`, record creation helper methods (e.g., `buildAttachmentRecord`, `createLocationRecord`) should use nullish coalescing to preserve caller-supplied `$type` and `createdAt` fields when provided, defaulting only when missing. Pattern: `$type: $type ?? HYPERCERT_COLLECTIONS.X, createdAt: createdAt ?? new Date().toISOString()`.

Learnt from: CR
Repo: hypercerts-org/hypercerts-sdk PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-29T02:51:21.569Z
Learning: Applies to packages/sdk-core/src/**/*.ts : For each lexicon entity type, define five types: lexicon Main type, HypercertXxx alias, CreateXxxParams, UpdateXxxParams, and XxxParams union type

Learnt from: CR
Repo: hypercerts-org/hypercerts-sdk PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-29T02:51:21.569Z
Learning: Applies to packages/sdk-core/src/**/*.ts : For each SDK method that creates a record, define a corresponding CreateXxxParams type with optional $type and createdAt fields auto-populated

Learnt from: aspiers
Repo: hypercerts-org/hypercerts-sdk PR: 84
File: .changeset/fix-add-evidence.md:1-17
Timestamp: 2026-01-21T04:15:20.542Z
Learning: `hypercerts-org/sdk-core` is currently at version 0.10.0-beta.4. Since it's a 0.x version, breaking changes can be introduced with minor version bumps according to semantic versioning conventions for pre-1.0 releases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aspiers this wasn't part of the changes here so created a new issue for this and will be done as a follow up pr @coderabbitai

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kzoeps, thank you for creating the follow-up issue! That's a great approach—keeping this PR focused on the profile operations changes and addressing the evaluation refactoring separately makes sense.

If you have the issue number handy, feel free to share it so it can be tracked alongside the pattern documentation in AGENTS.md. Looking forward to seeing the refactoring in the follow-up PR! 🚀


🧠 Learnings used
Learnt from: Kzoeps
Repo: hypercerts-org/hypercerts-sdk PR: 121
File: packages/sdk-core/src/repository/HypercertOperationsImpl.ts:1108-1167
Timestamp: 2026-01-28T10:29:44.154Z
Learning: In `packages/sdk-core/src/repository/HypercertOperationsImpl.ts`, record creation helper methods (e.g., `buildAttachmentRecord`, `createLocationRecord`) should use nullish coalescing to preserve caller-supplied `$type` and `createdAt` fields when provided, defaulting only when missing. Pattern: `$type: $type ?? HYPERCERT_COLLECTIONS.X, createdAt: createdAt ?? new Date().toISOString()`.

Learnt from: aspiers
Repo: hypercerts-org/hypercerts-sdk PR: 84
File: .changeset/fix-add-evidence.md:1-17
Timestamp: 2026-01-21T04:15:20.542Z
Learning: `hypercerts-org/sdk-core` is currently at version 0.10.0-beta.4. Since it's a 0.x version, breaking changes can be introduced with minor version bumps according to semantic versioning conventions for pre-1.0 releases.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/sdk-core/src/lib/blob-url.ts`:
- Around line 107-116: The code in blob-url.ts extracts a CID by calling
ref.toString(), but ref is an object like { $link: "bafy..." } so to fix: inside
the conditional handling smallImage/largeImage (check imageType, image.image ->
imageData, and ref) replace the use of ref.toString() with ref.$link (or
String(ref.$link) to be safe) so the real CID is returned; ensure you only
access $link when ref is an object and present to avoid undefined.
🧹 Nitpick comments (1)
packages/sdk-core/src/services/hypercerts/types.ts (1)

738-739: Clarify the relationship between CreateProfileParams and CreateHypercertProfileParams.

CreateProfileParams appears to be a simpler variant (only SetOptional without OverrideProperties for avatar/banner). Consider whether this is intentionally different from CreateHypercertProfileParams or if it should be consolidated. If both are needed, add a doc comment explaining the distinction.

@Kzoeps Kzoeps mentioned this pull request Feb 4, 2026
Comment on lines 1803 to 1809
const evaluationRecord: HypercertEvaluation = {
$type: HYPERCERT_COLLECTIONS.EVALUATION,
subject: { uri: subject.uri, cid: subject.cid },
evaluators: params.evaluators,
evaluators: params.evaluators.map((evaluator) => ({ did: evaluator })),
summary: params.summary,
createdAt,
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kzoeps I agree with @coderabbitai here - that's why I documented that pattern in AGENTS.md for agents to follow. That said, we need a whole bunch of cleanup on that front anyway, so we could also do it in a follow-up.


**Core SDK (`@hypercerts-org/sdk-core`):**

- Switch profile storage from `app.bsky.actor.profile` to `app.certified.profile` collection
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if you've been given the full context of all the discussions we had around profile records, but the strategy we've settled on is to support use of both of these lexicons, not switch entirely from the bsky one to the certified one. And "both" means allowing use of either one, or both together, where we still need to figure out some of the details about which data from which profile would be read/written in any given scenario.

So as written here, this sounds like the approach needs revising.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has now been changed. for now i've kept it as the getCertifiedProfile or getBskyProfile so either one .if we decide to do both together i think we can just reuse these functions and merge the results.

* Implementation of profile operations for user profile management.
*
* Profiles in AT Protocol are stored as records in the `app.bsky.actor.profile`
* Profiles in AT Protocol are stored as records in the `app.certified.profile`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bsky or certified

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here as above two different types and 2 different operations

@Kzoeps
Copy link
Contributor Author

Kzoeps commented Feb 10, 2026

working on this will finish it up tmrw

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
packages/sdk-core/tests/repository/BlobOperationsImpl.test.ts (1)

117-147: ⚠️ Potential issue | 🟡 Minor

Type signature doesn't reflect SDS response flexibility.

The upload() method returns Promise<BlobRef> but the SDS path (line 176) returns result.blob directly without normalization, which can have different ref formats (CID instance, { $link: string }, or plain string). The test at line 131 explicitly validates string ref handling. Consider either:

  • Normalizing SDS responses to match the BlobRef type contract
  • Using a union return type that reflects the actual flexibility: Promise<BlobRef | { ref: string | { $link: string }; mimeType: string; size: number }>

Note: HypercertOperationsImpl:321 already handles this with runtime type checking.

packages/sdk-core/src/repository/HypercertOperationsImpl.ts (1)

2006-2013: ⚠️ Potential issue | 🟡 Minor

Fix malformed JSDoc @example tag.

There’s an extra * that breaks the doc block rendering.

📝 Suggested fix
-   * * `@example`
+   * `@example`
packages/sdk-core/src/repository/BlobOperationsImpl.ts (2)

160-176: ⚠️ Potential issue | 🟡 Minor

Guard against missing blob in SDS upload responses.

The method currently returns result.blob without checking if it exists, which could return undefined despite the Promise<BlobRef> return type contract. This happens if the SDS endpoint returns an ok response with an unexpected payload structure.

🛡️ Suggested guard
-    const result = (await response.json()) 
-    
-    return result.blob
+    const result = await response.json();
+    if (!result?.blob) {
+      throw new NetworkError("SDS blob upload failed: missing blob in response");
+    }
+    return result.blob;

113-133: ⚠️ Potential issue | 🟡 Minor

Update upload() documentation to match the BlobRef return type.

The examples incorrectly show destructuring { ref, mimeType, size }, but upload() returns a BlobRef from @atproto/api. The documentation must be updated to reflect the actual return type. Additionally, the suggested use of blobRef.ref.toString() in the diff may not match the BlobRef API; verify the correct property or method to access the CID (e.g., blobRef.ipld() as used elsewhere in the codebase).

packages/sdk-core/src/repository/interfaces.ts (1)

618-643: ⚠️ Potential issue | 🟡 Minor

Stale doc block references removed methods.

The examples at lines 622–642 still reference repo.profile.get() and repo.profile.update(), which no longer exist. These will confuse consumers. Update or remove this block since each method now has its own doc examples.

Proposed fix — remove the stale block
-/**
- * Profile operations for user profile management.
- *
- * `@example`
- * ```typescript
- * // Get profile
- * const profile = await repo.profile.get();
- * console.log(profile.displayName);
- *
- * // Update profile
- * await repo.profile.update({
- *   displayName: "New Name",
- *   description: "Updated bio",
- * });
- *
- * // Update avatar
- * await repo.profile.update({
- *   avatar: new Blob([avatarData], { type: "image/png" }),
- * });
- *
- * // Clear a field by passing null
- * await repo.profile.update({
- *   website: null,  // Removes website
- * });
- * ```
- */
+/**
+ * Profile operations for Bluesky and Certified profile management.
+ *
+ * Supports dual-profile workflows: Bluesky profiles (app.bsky.actor.profile)
+ * and Certified profiles (app.certified.actor.profile).
+ */
🤖 Fix all issues with AI agents
In `@packages/sdk-core/src/repository/interfaces.ts`:
- Around line 672-675: UpdateBskyProfileParams is incorrectly derived from
AppBskyActorDefs.ProfileViewDetailed (a read-only view) — change it to be
Partial<CreateBskyProfileParams> so it represents selective updates to the
record; replace the current type alias for UpdateBskyProfileParams with a type
based on Partial<CreateBskyProfileParams> and still allow avatar?: Blob | null
and banner?: Blob | null (i.e., derive from CreateBskyProfileParams which itself
is based on AppBskyActorProfile.Record).

In `@packages/sdk-core/src/repository/ProfileOperationsImpl.ts`:
- Around line 94-117: The imageToUrl currently treats only http(s) URIs as
passthrough and converts everything else to a blob URL; update imageToUrl (which
uses extractCidFromImage and getBlobUrl and fields pdsUrl/repoDid) to return any
valid URI scheme unchanged (e.g., detect presence of a scheme like "scheme://",
or more robustly match /^[a-z][a-z0-9+.-]*:\/\//i) instead of only http/https,
and only call getBlobUrl when the result is truly a bare CID (no scheme
present); keep the existing error throw when extractCidFromImage returns falsy.
🧹 Nitpick comments (3)
packages/sdk-core/src/lib/blob-url.ts (1)

49-62: Consider URL-encoding query parameters.

While DIDs and CIDs are typically URL-safe in practice, did and cid are inserted into the query string without encodeURIComponent. This could break with unusual inputs.

♻️ Suggested fix
   const normalizedPdsUrl = pdsUrl.replace(/\/$/, "");
-  return `${normalizedPdsUrl}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${cid}`;
+  return `${normalizedPdsUrl}/xrpc/com.atproto.sync.getBlob?did=${encodeURIComponent(did)}&cid=${encodeURIComponent(cid)}`;
packages/sdk-react/src/hooks/useProfile.ts (1)

105-111: Add pronouns field to ProfileUpdate interface and include it in the updateCertifiedProfile call.

The underlying updateCertifiedProfile API fully supports the pronouns field (via AppCertifiedActorProfile.Main), but this is not exposed through the React hook's ProfileUpdate type or the update mutation. To enable users to update pronouns through the hook, add pronouns?: string | null to the ProfileUpdate interface in packages/sdk-react/src/types.ts and pass it in the mutation call at lines 105-111.

packages/sdk-core/src/repository/interfaces.ts (1)

687-690: UpdateCertifiedProfileParams should derive from CreateCertifiedProfileParams for consistency.

Per the coding guidelines, UpdateXxxParams should be Partial<CreateXxxParams>. While AppCertifiedActorProfile.Main is the record type (so the practical gap is smaller than the Bsky case), deriving from CreateCertifiedProfileParams keeps the two in sync and avoids drift.

Proposed fix
-export type UpdateCertifiedProfileParams = OverrideProperties<
-  Nullable<Except<AppCertifiedActorProfile.Main, "$type" | "createdAt">>,
-  { avatar?: Blob | null; banner?: Blob | null }
->;
+export type UpdateCertifiedProfileParams = Partial<CreateCertifiedProfileParams> & {
+  avatar?: Blob | null;
+  banner?: Blob | null;
+};

As per coding guidelines: "Define UpdateXxxParams as Partial for selective record updates."

This has been done because blobToJsonRef is very flaky. And is an unnecessary step since we can directly use the blobs returned from AtProto instead. This makes the code easier to maintain and also we dont have to manually construct json of blobs which are not only inaccurate but are also wrong in some cases;
…xport

Remove deprecated Hypercert*Profile* type aliases in favor of Certified*Profile* types:
- Remove HypercertProfile (use CertifiedProfileRecord)
- Remove CreateHypercertProfileParams (use CreateCertifiedProfileParams)
- Remove UpdateHypercertProfileParams (use UpdateCertifiedProfileParams)
- Remove HypercertProfileParams union type
- Remove CreateProfileParams alias

Remove JsonBlobRef type export:
- JsonBlobRef was too "snowflaky" - required unnecessary BlobRef to JSON conversion
- BlobRef instances work perfectly fine for all use cases
- Removed flaky test patterns with mock JSON objects
- Users can import from @atproto/lexicon if needed

Additional changes:
- Update exports in src/index.ts and src/types.ts
- Remove blobToJsonRef() method from HypercertOperationsImpl
- Fix typo in ProfileOperationsImpl comment
- Update changeset with migration guide and rationale

BREAKING CHANGE: Users must update imports to use new type names
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants