@openagentid/oas-resolve
v1.0.2
Published
Async DID resolution interfaces and utility implementations for OAS
Readme
@openagentid/oas-resolve
Async DID resolution interfaces and utility implementations for OAS.
This package defines the Resolver interface -- the agnosticism boundary between the OAS SDK and your infrastructure. It also provides three ready-to-use implementations: InMemoryResolver for testing, CachingResolver for TTL-based caching, and FallbackResolver for multi-source resolution with priority ordering.
Installation
npm i @openagentid/oas-resolveRequirements: Node.js >= 20, ESM-only.
Peer dependencies: @openagentid/oas-document.
Usage
In-memory resolver (testing)
import { InMemoryResolver } from '@openagentid/oas-resolve';
const resolver = new InMemoryResolver();
resolver.register(aliceDocument);
resolver.register(botDocument);
const doc = await resolver.resolve('did:oas:l1fe:hmr:alice');
console.log(doc.id); // 'did:oas:l1fe:hmr:alice'
// Throws ResolveError with code NOT_FOUND if not registered
await resolver.resolve('did:oas:l1fe:agent:unknown'); // throwsCaching resolver
import { CachingResolver, InMemoryResolver } from '@openagentid/oas-resolve';
const inner = new InMemoryResolver();
inner.register(aliceDocument);
const cached = new CachingResolver(inner, 60_000); // 60-second TTL
const doc = await cached.resolve('did:oas:l1fe:hmr:alice'); // cache miss, fetches from inner
const doc2 = await cached.resolve('did:oas:l1fe:hmr:alice'); // cache hit
cached.invalidate('did:oas:l1fe:hmr:alice'); // remove one entry
cached.clear(); // remove all entriesFallback resolver
import { FallbackResolver, InMemoryResolver } from '@openagentid/oas-resolve';
const primary = new InMemoryResolver();
const secondary = new InMemoryResolver();
secondary.register(aliceDocument);
const resolver = new FallbackResolver([primary, secondary]);
// primary fails (not found), falls back to secondary
const doc = await resolver.resolve('did:oas:l1fe:hmr:alice');
console.log(doc.id); // 'did:oas:l1fe:hmr:alice'Implement a custom resolver
import type { Resolver } from '@openagentid/oas-resolve';
import type { OasDocument } from '@openagentid/oas-document';
import { ResolveError, ResolveErrorCode } from '@openagentid/oas-resolve';
class HttpResolver implements Resolver {
constructor(private readonly baseUrl: string) {}
async resolve(did: string): Promise<OasDocument> {
const resp = await fetch(`${this.baseUrl}/resolve/${encodeURIComponent(did)}`);
if (resp.status === 404) {
throw new ResolveError(ResolveErrorCode.NotFound, `DID '${did}' not found`);
}
if (!resp.ok) {
throw new ResolveError(ResolveErrorCode.NetworkError, `HTTP ${resp.status}`);
}
return resp.json() as Promise<OasDocument>;
}
}Compose resolvers
import { CachingResolver, FallbackResolver } from '@openagentid/oas-resolve';
const resolver = new CachingResolver(
new FallbackResolver([httpResolver, dhtResolver]),
300_000, // 5-minute cache
);API Reference
Resolver (interface)
The core resolution interface. Implementations resolve did:oas:* strings to OAS Identity Documents.
interface Resolver {
/**
* Resolves a DID string to its OAS Identity Document.
* @param did - The DID string to resolve.
* @returns The resolved document.
* @throws ResolveError if resolution fails.
*/
resolve(did: string): Promise<OasDocument>;
}InMemoryResolver (class)
In-memory DID resolver for testing and local usage.
class InMemoryResolver implements Resolver {
/** Registers a document. The DID is extracted from doc.id. */
register(doc: OasDocument): void;
/** Removes a document. Returns true if found and removed. */
remove(did: string): boolean;
/**
* Resolves a DID to its document.
* @throws ResolveError with code NOT_FOUND if not registered.
*/
async resolve(did: string): Promise<OasDocument>;
/** Returns the number of registered documents. */
get size(): number;
/** Returns true if no documents are registered. */
get isEmpty(): boolean;
}CachingResolver (class)
Caching wrapper for any Resolver. Caches resolved documents with a configurable TTL.
class CachingResolver implements Resolver {
/**
* @param inner - The underlying resolver to cache results from.
* @param ttlMs - Cache TTL in milliseconds (default: 300_000 = 5 minutes).
*/
constructor(inner: Resolver, ttlMs?: number);
/**
* Resolves a DID, returning cached results when available and not expired.
* @throws Same errors as the inner resolver on cache miss.
*/
async resolve(did: string): Promise<OasDocument>;
/** Invalidates a specific cached entry. */
invalidate(did: string): void;
/** Clears all cached entries. */
clear(): void;
/** Returns the number of cached entries (including expired). */
get size(): number;
/** Returns true if the cache is empty. */
get isEmpty(): boolean;
}FallbackResolver (class)
Tries multiple resolvers in priority order. Returns the first successful result.
class FallbackResolver implements Resolver {
/**
* @param resolvers - The resolvers to try, in priority order. Must have at least one.
* @throws ResolveError if resolvers array is empty.
*/
constructor(resolvers: ReadonlyArray<Resolver>);
/**
* Resolves a DID by trying each resolver in order.
* @throws ResolveError with code ALL_RESOLVERS_FAILED if all resolvers fail.
*/
async resolve(did: string): Promise<OasDocument>;
}Error classes
class ResolveError extends Error {
readonly code: ResolveErrorCode;
readonly details: Readonly<Record<string, unknown>>;
constructor(code: ResolveErrorCode, message: string, details?: Record<string, unknown>);
}
const ResolveErrorCode = {
NotFound: 'NOT_FOUND',
InvalidSignature: 'INVALID_SIGNATURE',
Revoked: 'REVOKED',
LineageInvalid: 'LINEAGE_INVALID',
Timeout: 'TIMEOUT',
NetworkError: 'NETWORK_ERROR',
Unsupported: 'UNSUPPORTED',
InternalError: 'INTERNAL_ERROR',
InvalidDid: 'INVALID_DID',
RateLimited: 'RATE_LIMITED',
AllResolversFailed: 'ALL_RESOLVERS_FAILED',
} as const;
type ResolveErrorCode = (typeof ResolveErrorCode)[keyof typeof ResolveErrorCode];Dependencies
| Package | Purpose |
|---------|---------|
| @openagentid/oas-document | OasDocument type |
License
Copyright © 2026 L1fe Labs, Inc.
Licensed under either of Apache License 2.0 or MIT license, at your option.
