feat: add resolve, resolveRepresentation and dereference functions#87
Conversation
WalkthroughRe-exports in did-web index now include resolve, resolveRepresentation, and dereference. The wellKnown/query module implements these functions with content-negotiated DID resolution and dereferencing, adding helpers/constants. Tests expanded to cover resolution, representation handling, dereferencing, and updated DID Document fixtures. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Client
participant did-web API
participant Resolver
participant WebDIDResolver
rect rgba(230,245,255,0.6)
note over Client,did-web API: Resolve DID
Client->>did-web API: resolve(did)
did-web API->>Resolver: getResolver()
Resolver->>WebDIDResolver: resolve(did)
WebDIDResolver-->>Resolver: DIDResolutionResult
Resolver-->>did-web API: result (normalized metadata)
did-web API-->>Client: DIDResolutionResult
end
rect rgba(240,255,230,0.6)
note over Client,did-web API: Resolve Representation (content-negotiation)
Client->>did-web API: resolveRepresentation(did, { accept })
did-web API->>Resolver: resolve(did)
alt accept application/did+json
did-web API->>did-web API: strip @context
else accept application/did+ld+json
did-web API->>did-web API: include @context
else unsupported
did-web API-->>Client: { didResolutionMetadata: representationNotSupported }
end
did-web API-->>Client: { didDocumentStream, didResolutionMetadata, didDocumentMetadata }
end
rect rgba(255,245,230,0.6)
note over Client,did-web API: Dereference DID URL (fragment or full DID)
Client->>did-web API: dereference(didUrl, { accept? })
did-web API->>Resolver: resolve(did)
alt has fragment
did-web API->>did-web API: locate verificationMethod by fragment
did-web API-->>Client: { contentStream: verificationMethod, dereferencingMetadata }
else no fragment
did-web API->>did-web API: serialize DID Document per accept
did-web API-->>Client: { contentStream, dereferencingMetadata }
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
packages/w3c-issuer/src/did-web/wellKnown/query.test.ts (1)
206-210: Reduce brittleness: compare objects, not exact JSON string ordering.Property order in JSON.stringify can vary. Parsing and comparing object fields will be more robust.
Apply this diff:
- expect(result.contentStream).toBe( - '{"id":"did:web:trustvc.github.io:did:1#multikey-1","type":"Multikey","controller":"did:web:trustvc.github.io:did:1","publicKeyMultibase":"zDnaemDNwi4G5eTzGfRooFFu5Kns3be6yfyVNtiaMhWkZbwtc"}', - ); + const vm = JSON.parse(result.contentStream); + expect(vm).toMatchObject({ + id: 'did:web:trustvc.github.io:did:1#multikey-1', + type: 'Multikey', + controller: 'did:web:trustvc.github.io:did:1', + publicKeyMultibase: 'zDnaemDNwi4G5eTzGfRooFFu5Kns3be6yfyVNtiaMhWkZbwtc', + });packages/w3c-issuer/src/did-web/wellKnown/query.ts (3)
41-45: Reuse getResolver() for consistency and single instantiation path.Avoid duplicating Resolver construction here; use the helper like elsewhere.
Apply this diff:
- const resolver = new Resolver({ - ...webGetResolver(), - }); + const resolver = getResolver();
134-143: Return representationNotSupported for invalid accept in dereference.Align dereference with resolveRepresentation by using representationNotSupported for unsupported accept, while keeping notFound for path/query cases.
Apply this diff:
- const isInvalidRequest = - (accept && !SUPPORTED_CONTENT_TYPES.includes(accept)) || did.includes('?') || did.includes('/'); - - if (isInvalidRequest) { + if (accept && !SUPPORTED_CONTENT_TYPES.includes(accept)) { + return { + dereferencingMetadata: { error: 'representationNotSupported' }, + contentStream: '', + contentMetadata: {}, + }; + } + + if (did.includes('?') || did.includes('/')) { return { dereferencingMetadata: { error: 'notFound' }, contentStream: '', contentMetadata: {}, }; }
59-72: Minor: prefer structured error result over throwing.Spec-style operations generally return result objects with error metadata instead of throwing. Consider returning a DIDResolutionResult with didResolutionMetadata.error on failure for consistency (non-blocking).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
packages/w3c-issuer/src/did-web/index.ts(1 hunks)packages/w3c-issuer/src/did-web/wellKnown/query.test.ts(3 hunks)packages/w3c-issuer/src/did-web/wellKnown/query.ts(2 hunks)
🧰 Additional context used
🪛 GitHub Check: Tests / Run Tests (18.x)
packages/w3c-issuer/src/did-web/wellKnown/query.test.ts
[failure] 167-167: src/did-web/wellKnown/query.test.ts > query > resolveRepresentation > Should return correct contentType for requested accept value
AssertionError: expected '' to be truthy
- Expected:
""
- Received:
false
❯ src/did-web/wellKnown/query.test.ts:167:40
🔇 Additional comments (1)
packages/w3c-issuer/src/did-web/index.ts (1)
3-3: Public API re-exports look good.Adding resolve, resolveRepresentation, and dereference to the public surface is consistent and non-breaking.
Also applies to: 7-7
| it('Should return correct contentType for requested accept value', async () => { | ||
| const did = 'did:web:trustvc.github.io:did:1'; | ||
| const result = await resolveRepresentation(did, { accept: 'application/did+json' }); | ||
|
|
||
| expect(result.didDocumentStream).toBeTruthy(); | ||
| expect(typeof result.didDocumentStream).toBe('string'); | ||
| const parsedDocument = JSON.parse(result.didDocumentStream); | ||
| expect(parsedDocument).not.toHaveProperty('@context'); | ||
| expect(result.didResolutionMetadata).toHaveProperty('contentType', 'application/did+json'); | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
CI failure points to implementation gap in resolveRepresentation.
The test is correct; result.didDocumentStream should not be empty for accept: 'application/did+json'. Current implementation can return '' when the resolver provides didDocumentStream instead of didDocument. See proposed fix in query.ts to support both shapes.
Please rerun tests after applying the query.ts fix I suggested.
🏁 Script executed:
#!/bin/bash
# Inspect implementation and tests referenced in the review comment
echo "Searching for resolveRepresentation symbol..."
rg -n "resolveRepresentation" || true
echo
echo "Showing packages/w3c-issuer/src/did-web/wellKnown/query.ts (if present):"
sed -n '1,240p' packages/w3c-issuer/src/did-web/wellKnown/query.ts || true
echo
echo "Showing packages/w3c-issuer/src/did-web/wellKnown/query.test.ts (if present):"
sed -n '1,240p' packages/w3c-issuer/src/did-web/wellKnown/query.test.ts || true
echo
echo "Searching for didDocumentStream / didDocument usages in that folder:"
rg -n "didDocumentStream|didDocument" packages/w3c-issuer/src/did-web/wellKnown || trueLength of output: 19706
resolveRepresentation ignores resolver's didDocumentStream — fix required
resolver.resolve may return didDocumentStream (string) instead of didDocument; current code only reads didDocument and sets didDocumentStream: '' when didDocument is absent, causing the test failure.
Location: packages/w3c-issuer/src/did-web/wellKnown/query.ts (destructuring around line ~102, response construction around line ~109).
Fix: prefer doc.didDocumentStream || (doc.didDocument ? JSON.stringify(doc.didDocument) : ''), and ensure didResolutionMetadata.contentType = accept ?? didResolutionMetadata.contentType. Rerun tests after applying.
🧰 Tools
🪛 GitHub Check: Tests / Run Tests (18.x)
[failure] 167-167: src/did-web/wellKnown/query.test.ts > query > resolveRepresentation > Should return correct contentType for requested accept value
AssertionError: expected '' to be truthy
- Expected:
""
- Received:
false
❯ src/did-web/wellKnown/query.test.ts:167:40
🤖 Prompt for AI Agents
In packages/w3c-issuer/src/did-web/wellKnown/query.ts around lines ~100-110, the
current response creation ignores a resolver-returned didDocumentStream and
always sets didDocumentStream to '' when didDocument is absent; update the
response to set didDocumentStream = doc.didDocumentStream || (doc.didDocument ?
JSON.stringify(doc.didDocument) : '') so it uses the stream when provided, and
set didResolutionMetadata.contentType = accept ??
doc.didResolutionMetadata?.contentType (or existing metadata) so the requested
Accept is honored; apply these changes and rerun tests.
| export const resolveRepresentation = async ( | ||
| did: string, | ||
| resolutionOptions?: DIDResolutionOptions, | ||
| ) => { | ||
| const { accept } = resolutionOptions || {}; | ||
|
|
||
| if (accept && !SUPPORTED_CONTENT_TYPES.includes(accept)) { | ||
| return { | ||
| didResolutionMetadata: { | ||
| error: 'representationNotSupported', | ||
| message: `Content type ${accept} is not supported.`, | ||
| }, | ||
| didDocumentStream: '', | ||
| didDocumentMetadata: {}, | ||
| }; | ||
| } | ||
|
|
||
| try { | ||
| const resolver = getResolver(); | ||
| const doc = await resolver.resolve(did, { accept }); | ||
| const { didDocument, didResolutionMetadata, ...rest } = doc; | ||
| if (didDocument && accept === 'application/did+json') { | ||
| delete didDocument['@context']; | ||
| } | ||
|
|
||
| const response = { | ||
| ...rest, | ||
| didDocumentStream: didDocument ? JSON.stringify(didDocument) : '', | ||
| didResolutionMetadata, | ||
| }; | ||
|
|
||
| if (!didResolutionMetadata.error) { | ||
| response.didResolutionMetadata.contentType = accept ?? didResolutionMetadata.contentType; | ||
| } | ||
|
|
||
| return response; | ||
| } catch { | ||
| throw new Error('Failed to resolve did'); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Fix resolveRepresentation to support didDocumentStream and unblock failing test.
When passing accept, some resolvers return didDocumentStream (representation) instead of didDocument (data model). Current code only serializes didDocument, causing empty streams and the CI failure. Prefer resolver.resolveRepresentation when available and fall back to resolve; always build the stream; strip @context for application/did+json.
Apply this diff:
try {
- const resolver = getResolver();
- const doc = await resolver.resolve(did, { accept });
- const { didDocument, didResolutionMetadata, ...rest } = doc;
- if (didDocument && accept === 'application/did+json') {
- delete didDocument['@context'];
- }
-
- const response = {
- ...rest,
- didDocumentStream: didDocument ? JSON.stringify(didDocument) : '',
- didResolutionMetadata,
- };
-
- if (!didResolutionMetadata.error) {
- response.didResolutionMetadata.contentType = accept ?? didResolutionMetadata.contentType;
- }
-
- return response;
+ const resolver = getResolver();
+ // Prefer native resolveRepresentation if available; otherwise fallback to resolve.
+ const doc: any =
+ typeof (resolver as any).resolveRepresentation === 'function'
+ ? await (resolver as any).resolveRepresentation(did, { accept })
+ : await resolver.resolve(did, { accept });
+
+ const { didDocument, didResolutionMetadata = {}, didDocumentMetadata, didDocumentStream } = doc;
+
+ // Build the stream from whichever shape we got back.
+ let stream: string =
+ typeof didDocumentStream === 'string'
+ ? didDocumentStream
+ : didDocument
+ ? JSON.stringify(didDocument)
+ : '';
+
+ // Ensure JSON (non‑LD) representation excludes @context.
+ const effectiveContentType = accept ?? didResolutionMetadata.contentType;
+ if (stream && effectiveContentType === 'application/did+json') {
+ try {
+ const parsed = JSON.parse(stream);
+ delete parsed['@context'];
+ stream = JSON.stringify(parsed);
+ } catch {
+ // leave as-is if parsing fails
+ }
+ }
+
+ // Attach/override contentType when there is no error.
+ if (!didResolutionMetadata.error) {
+ didResolutionMetadata.contentType = effectiveContentType;
+ }
+
+ return {
+ didDocumentStream: stream,
+ didResolutionMetadata,
+ didDocumentMetadata: didDocumentMetadata ?? {},
+ };
} catch {
throw new Error('Failed to resolve did');
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const resolveRepresentation = async ( | |
| did: string, | |
| resolutionOptions?: DIDResolutionOptions, | |
| ) => { | |
| const { accept } = resolutionOptions || {}; | |
| if (accept && !SUPPORTED_CONTENT_TYPES.includes(accept)) { | |
| return { | |
| didResolutionMetadata: { | |
| error: 'representationNotSupported', | |
| message: `Content type ${accept} is not supported.`, | |
| }, | |
| didDocumentStream: '', | |
| didDocumentMetadata: {}, | |
| }; | |
| } | |
| try { | |
| const resolver = getResolver(); | |
| const doc = await resolver.resolve(did, { accept }); | |
| const { didDocument, didResolutionMetadata, ...rest } = doc; | |
| if (didDocument && accept === 'application/did+json') { | |
| delete didDocument['@context']; | |
| } | |
| const response = { | |
| ...rest, | |
| didDocumentStream: didDocument ? JSON.stringify(didDocument) : '', | |
| didResolutionMetadata, | |
| }; | |
| if (!didResolutionMetadata.error) { | |
| response.didResolutionMetadata.contentType = accept ?? didResolutionMetadata.contentType; | |
| } | |
| return response; | |
| } catch { | |
| throw new Error('Failed to resolve did'); | |
| } | |
| }; | |
| export const resolveRepresentation = async ( | |
| did: string, | |
| resolutionOptions?: DIDResolutionOptions, | |
| ) => { | |
| const { accept } = resolutionOptions || {}; | |
| if (accept && !SUPPORTED_CONTENT_TYPES.includes(accept)) { | |
| return { | |
| didResolutionMetadata: { | |
| error: 'representationNotSupported', | |
| message: `Content type ${accept} is not supported.`, | |
| }, | |
| didDocumentStream: '', | |
| didDocumentMetadata: {}, | |
| }; | |
| } | |
| try { | |
| const resolver = getResolver(); | |
| // Prefer native resolveRepresentation if available; otherwise fallback to resolve. | |
| const doc: any = | |
| typeof (resolver as any).resolveRepresentation === 'function' | |
| ? await (resolver as any).resolveRepresentation(did, { accept }) | |
| : await resolver.resolve(did, { accept }); | |
| const { didDocument, didResolutionMetadata = {}, didDocumentMetadata, didDocumentStream } = doc; | |
| // Build the stream from whichever shape we got back. | |
| let stream: string = | |
| typeof didDocumentStream === 'string' | |
| ? didDocumentStream | |
| : didDocument | |
| ? JSON.stringify(didDocument) | |
| : ''; | |
| // Ensure JSON (non-LD) representation excludes @context. | |
| const effectiveContentType = accept ?? didResolutionMetadata.contentType; | |
| if (stream && effectiveContentType === 'application/did+json') { | |
| try { | |
| const parsed = JSON.parse(stream); | |
| delete parsed['@context']; | |
| stream = JSON.stringify(parsed); | |
| } catch { | |
| // leave as-is if parsing fails | |
| } | |
| } | |
| // Attach/override contentType when there is no error. | |
| if (!didResolutionMetadata.error) { | |
| didResolutionMetadata.contentType = effectiveContentType; | |
| } | |
| return { | |
| didDocumentStream: stream, | |
| didResolutionMetadata, | |
| didDocumentMetadata: didDocumentMetadata ?? {}, | |
| }; | |
| } catch { | |
| throw new Error('Failed to resolve did'); | |
| } | |
| }; |
🤖 Prompt for AI Agents
In packages/w3c-issuer/src/did-web/wellKnown/query.ts around lines 82-121, the
code only handles didDocument (data model) and serializes it to
didDocumentStream, which breaks resolvers that return a didDocumentStream
(representation). Update to prefer calling resolver.resolveRepresentation(did, {
accept }) when the resolver exposes that method, falling back to
resolver.resolve(did, { accept }) otherwise; extract didResolutionMetadata,
didDocumentStream, and didDocument from the result; if didDocumentStream is
present use it as the stream, else build the stream by
JSON.stringify(didDocument) when didDocument exists; for accept ===
'application/did+json' ensure you strip '@context' from the JSON object (parse
stream or didDocument to an object, delete '@context', then stringify back)
before returning; set response.didResolutionMetadata.contentType = accept ??
didResolutionMetadata.contentType; preserve other metadata/rest fields; keep the
same error handling but make the thrown message clear.
# [1.3.0-alpha.9](https://github.com/TrustVC/w3c/compare/@trustvc/w3c-issuer@1.3.0-alpha.8...@trustvc/w3c-issuer@1.3.0-alpha.9) (2025-09-24) ### Features * add resolve, resolveRepresentation and dereference functions ([#87](#87)) ([493a248](493a248))
|
🎉 This PR is included in version 1.3.0-alpha.9 🎉 The release is available on: Your semantic-release bot 📦🚀 |
# [1.3.0-alpha.11](https://github.com/TrustVC/w3c/compare/@trustvc/w3c-context@1.3.0-alpha.10...@trustvc/w3c-context@1.3.0-alpha.11) (2025-09-24) ### Features * add resolve, resolveRepresentation and dereference functions ([#87](#87)) ([493a248](493a248))
|
🎉 This PR is included in version 1.3.0-alpha.11 🎉 The release is available on: Your semantic-release bot 📦🚀 |
# [1.3.0-alpha.11](https://github.com/TrustVC/w3c/compare/@trustvc/w3c-credential-status@1.3.0-alpha.10...@trustvc/w3c-credential-status@1.3.0-alpha.11) (2025-09-24) ### Features * add resolve, resolveRepresentation and dereference functions ([#87](#87)) ([493a248](493a248))
|
🎉 This PR is included in version 1.3.0-alpha.11 🎉 The release is available on: Your semantic-release bot 📦🚀 |
# [1.3.0-alpha.12](https://github.com/TrustVC/w3c/compare/@trustvc/w3c-vc@1.3.0-alpha.11...@trustvc/w3c-vc@1.3.0-alpha.12) (2025-09-24) ### Features * add resolve, resolveRepresentation and dereference functions ([#87](#87)) ([493a248](493a248))
|
🎉 This PR is included in version 1.3.0-alpha.12 🎉 The release is available on: Your semantic-release bot 📦🚀 |
# [1.3.0-alpha.12](https://github.com/TrustVC/w3c/compare/@trustvc/w3c-cli@1.3.0-alpha.11...@trustvc/w3c-cli@1.3.0-alpha.12) (2025-09-24) ### Features * add resolve, resolveRepresentation and dereference functions ([#87](#87)) ([493a248](493a248))
|
🎉 This PR is included in version 1.3.0-alpha.12 🎉 The release is available on: Your semantic-release bot 📦🚀 |
# [1.3.0-alpha.12](https://github.com/TrustVC/w3c/compare/@trustvc/w3c@1.3.0-alpha.11...@trustvc/w3c@1.3.0-alpha.12) (2025-09-24) ### Features * add resolve, resolveRepresentation and dereference functions ([#87](#87)) ([493a248](493a248))
|
🎉 This PR is included in version 1.3.0-alpha.12 🎉 The release is available on: Your semantic-release bot 📦🚀 |



Summary
Added resolve, resolveRepresentation and dereference function as per w3c spec
Summary by CodeRabbit
New Features
Tests