@arcblock/did 1.29.17 → 1.29.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/entity.d.mts +28 -0
- package/esm/entity.mjs +39 -0
- package/esm/index.d.mts +9 -1
- package/esm/index.mjs +10 -2
- package/esm/method.d.mts +30 -0
- package/esm/method.mjs +32 -0
- package/esm/name-registry.d.mts +31 -0
- package/esm/name-registry.mjs +37 -0
- package/esm/name-resolver.d.mts +41 -0
- package/esm/name-resolver.mjs +97 -0
- package/esm/name.d.mts +62 -0
- package/esm/name.mjs +73 -0
- package/esm/parse.d.mts +50 -0
- package/esm/parse.mjs +88 -0
- package/esm/short-form.d.mts +30 -0
- package/esm/short-form.mjs +46 -0
- package/esm/util.mjs +1 -1
- package/esm/validate.d.mts +23 -0
- package/esm/validate.mjs +36 -0
- package/lib/entity.cjs +40 -0
- package/lib/entity.d.cts +28 -0
- package/lib/index.cjs +31 -1
- package/lib/index.d.cts +9 -1
- package/lib/method.cjs +39 -0
- package/lib/method.d.cts +30 -0
- package/lib/name-registry.cjs +38 -0
- package/lib/name-registry.d.cts +31 -0
- package/lib/name-resolver.cjs +97 -0
- package/lib/name-resolver.d.cts +41 -0
- package/lib/name.cjs +77 -0
- package/lib/name.d.cts +62 -0
- package/lib/parse.cjs +91 -0
- package/lib/parse.d.cts +50 -0
- package/lib/short-form.cjs +47 -0
- package/lib/short-form.d.cts +30 -0
- package/lib/util.cjs +1 -1
- package/lib/validate.cjs +36 -0
- package/lib/validate.d.cts +23 -0
- package/package.json +3 -3
package/esm/entity.d.mts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region src/entity.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Cross-Method Entity Association
|
|
4
|
+
*
|
|
5
|
+
* Functions for comparing DID identity across different methods.
|
|
6
|
+
* Two DIDs with different methods but the same identifier represent the same entity.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Get the entity identifier from a DID (strips method prefix).
|
|
10
|
+
* This is the canonical identity across methods.
|
|
11
|
+
*
|
|
12
|
+
* @param did - A full DID or bare address
|
|
13
|
+
* @returns The entity identifier
|
|
14
|
+
*/
|
|
15
|
+
declare function getEntityId(did: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Check if two DIDs represent the same entity.
|
|
18
|
+
* Compares the identifier portion (case-insensitive), ignoring the method prefix.
|
|
19
|
+
*
|
|
20
|
+
* For did:name DIDs, performs exact string comparison (names are case-insensitive).
|
|
21
|
+
*
|
|
22
|
+
* @param a - First DID or address
|
|
23
|
+
* @param b - Second DID or address
|
|
24
|
+
* @returns true if both DIDs refer to the same entity
|
|
25
|
+
*/
|
|
26
|
+
declare function isSameEntity(a: unknown, b: unknown): boolean;
|
|
27
|
+
//#endregion
|
|
28
|
+
export { getEntityId, isSameEntity };
|
package/esm/entity.mjs
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { extractIdentifier } from "./parse.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/entity.ts
|
|
4
|
+
/**
|
|
5
|
+
* Cross-Method Entity Association
|
|
6
|
+
*
|
|
7
|
+
* Functions for comparing DID identity across different methods.
|
|
8
|
+
* Two DIDs with different methods but the same identifier represent the same entity.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Get the entity identifier from a DID (strips method prefix).
|
|
12
|
+
* This is the canonical identity across methods.
|
|
13
|
+
*
|
|
14
|
+
* @param did - A full DID or bare address
|
|
15
|
+
* @returns The entity identifier
|
|
16
|
+
*/
|
|
17
|
+
function getEntityId(did) {
|
|
18
|
+
return extractIdentifier(did);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Check if two DIDs represent the same entity.
|
|
22
|
+
* Compares the identifier portion (case-insensitive), ignoring the method prefix.
|
|
23
|
+
*
|
|
24
|
+
* For did:name DIDs, performs exact string comparison (names are case-insensitive).
|
|
25
|
+
*
|
|
26
|
+
* @param a - First DID or address
|
|
27
|
+
* @param b - Second DID or address
|
|
28
|
+
* @returns true if both DIDs refer to the same entity
|
|
29
|
+
*/
|
|
30
|
+
function isSameEntity(a, b) {
|
|
31
|
+
if (typeof a !== "string" || typeof b !== "string" || !a || !b) return false;
|
|
32
|
+
const idA = extractIdentifier(a);
|
|
33
|
+
const idB = extractIdentifier(b);
|
|
34
|
+
if (!idA || !idB) return false;
|
|
35
|
+
return idA.toLowerCase() === idB.toLowerCase();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
export { getEntityId, isSameEntity };
|
package/esm/index.d.mts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
import { getEntityId, isSameEntity } from "./entity.mjs";
|
|
1
2
|
import { DIDType, DIDTypeArg, DIDTypeShortcut, DIDTypeStr, DID_TYPE_ARCBLOCK, DID_TYPE_ETHEREUM, DID_TYPE_PASSKEY, DidType, RequiredDIDType, fromTypeInfo, isEthereumDid, isEthereumType, toTypeInfo, toTypeInfoStr } from "./type.mjs";
|
|
2
3
|
import { DID_PREFIX, toStrictHex } from "./util.mjs";
|
|
4
|
+
import { ALIAS_METHODS, ALL_METHODS, AliasMethod, CRYPTO_METHODS, CryptoMethod, DEFAULT_METHOD, DIDMethod, isAliasMethod, isCryptoMethod, isKnownMethod } from "./method.mjs";
|
|
5
|
+
import { ParsedDID, extractIdentifier, extractMethod, formatDid, parse } from "./parse.mjs";
|
|
6
|
+
import { isKnownDid } from "./validate.mjs";
|
|
7
|
+
import { expandShortForm, toShortForm } from "./short-form.mjs";
|
|
8
|
+
import { NameHierarchy, ResolveRoute, getResolveRoute, isGlobalName, isLocalName, parseNameHierarchy } from "./name.mjs";
|
|
9
|
+
import { InMemoryNameRegistry, NameRegistry } from "./name-registry.mjs";
|
|
10
|
+
import { DIDNameResolver, DIDNameResolverOptions, ResolveTraceResult } from "./name-resolver.mjs";
|
|
3
11
|
import { types } from "@ocap/mcrypto";
|
|
4
12
|
import { BytesType, toAddress, toDid } from "@ocap/util";
|
|
5
13
|
|
|
@@ -58,4 +66,4 @@ declare const isFromPublicKey: (did: string, pk: BytesType) => boolean;
|
|
|
58
66
|
*/
|
|
59
67
|
declare const isValid: (did: string) => boolean;
|
|
60
68
|
//#endregion
|
|
61
|
-
export { type DIDType, type DIDTypeArg, type DIDTypeShortcut, type DIDTypeStr, DID_PREFIX, DID_TYPE_ARCBLOCK, DID_TYPE_ETHEREUM, DID_TYPE_PASSKEY, DidType, type RequiredDIDType, fromHash, fromPublicKey, fromPublicKeyHash, fromSecretKey, fromTypeInfo, isEthereumDid, isEthereumType, isFromPublicKey, isValid, toAddress, toDid, toStrictHex, toTypeInfo, toTypeInfoStr, types };
|
|
69
|
+
export { ALIAS_METHODS, ALL_METHODS, type AliasMethod, CRYPTO_METHODS, type CryptoMethod, DEFAULT_METHOD, type DIDMethod, DIDNameResolver, type DIDNameResolverOptions, type DIDType, type DIDTypeArg, type DIDTypeShortcut, type DIDTypeStr, DID_PREFIX, DID_TYPE_ARCBLOCK, DID_TYPE_ETHEREUM, DID_TYPE_PASSKEY, DidType, InMemoryNameRegistry, type NameHierarchy, type NameRegistry, type ParsedDID, type RequiredDIDType, type ResolveRoute, type ResolveTraceResult, expandShortForm, extractIdentifier, extractMethod, formatDid, fromHash, fromPublicKey, fromPublicKeyHash, fromSecretKey, fromTypeInfo, getEntityId, getResolveRoute, isAliasMethod, isCryptoMethod, isEthereumDid, isEthereumType, isFromPublicKey, isGlobalName, isKnownDid, isKnownMethod, isLocalName, isSameEntity, isValid, parse, parseNameHierarchy, toAddress, toDid, toShortForm, toStrictHex, toTypeInfo, toTypeInfoStr, types };
|
package/esm/index.mjs
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
import { ALIAS_METHODS, ALL_METHODS, CRYPTO_METHODS, DEFAULT_METHOD, isAliasMethod, isCryptoMethod, isKnownMethod } from "./method.mjs";
|
|
2
|
+
import { extractIdentifier, extractMethod, formatDid, parse } from "./parse.mjs";
|
|
3
|
+
import { getEntityId, isSameEntity } from "./entity.mjs";
|
|
1
4
|
import { DID_PREFIX, toBytes, toStrictHex } from "./util.mjs";
|
|
2
5
|
import { DID_TYPE_ARCBLOCK, DID_TYPE_ETHEREUM, DID_TYPE_PASSKEY, DidType, fromTypeInfo, isEthereumDid, isEthereumType, toChecksumAddress, toTypeInfo, toTypeInfoStr } from "./type.mjs";
|
|
6
|
+
import { isKnownDid } from "./validate.mjs";
|
|
7
|
+
import { expandShortForm, toShortForm } from "./short-form.mjs";
|
|
8
|
+
import { getResolveRoute, isGlobalName, isLocalName, parseNameHierarchy } from "./name.mjs";
|
|
9
|
+
import { InMemoryNameRegistry } from "./name-registry.mjs";
|
|
10
|
+
import { DIDNameResolver } from "./name-resolver.mjs";
|
|
3
11
|
import { getHasher, getSigner, types } from "@ocap/mcrypto";
|
|
4
12
|
import { stripHexPrefix, toAddress, toBase58, toDid } from "@ocap/util";
|
|
5
13
|
|
|
@@ -69,7 +77,7 @@ const fromHash = (hash, role = types.RoleType.ROLE_ACCOUNT) => {
|
|
|
69
77
|
*/
|
|
70
78
|
const isFromPublicKey = (did, pk) => {
|
|
71
79
|
if (isValid(did) === false) return false;
|
|
72
|
-
return fromPublicKey(pk, toTypeInfo(did)) === did
|
|
80
|
+
return fromPublicKey(pk, toTypeInfo(did)) === toAddress(did);
|
|
73
81
|
};
|
|
74
82
|
/**
|
|
75
83
|
* Check if a DID string is valid
|
|
@@ -92,4 +100,4 @@ const isValid = (did) => {
|
|
|
92
100
|
};
|
|
93
101
|
|
|
94
102
|
//#endregion
|
|
95
|
-
export { DID_PREFIX, DID_TYPE_ARCBLOCK, DID_TYPE_ETHEREUM, DID_TYPE_PASSKEY, DidType, fromHash, fromPublicKey, fromPublicKeyHash, fromSecretKey, fromTypeInfo, isEthereumDid, isEthereumType, isFromPublicKey, isValid, toAddress, toDid, toStrictHex, toTypeInfo, toTypeInfoStr, types };
|
|
103
|
+
export { ALIAS_METHODS, ALL_METHODS, CRYPTO_METHODS, DEFAULT_METHOD, DIDNameResolver, DID_PREFIX, DID_TYPE_ARCBLOCK, DID_TYPE_ETHEREUM, DID_TYPE_PASSKEY, DidType, InMemoryNameRegistry, expandShortForm, extractIdentifier, extractMethod, formatDid, fromHash, fromPublicKey, fromPublicKeyHash, fromSecretKey, fromTypeInfo, getEntityId, getResolveRoute, isAliasMethod, isCryptoMethod, isEthereumDid, isEthereumType, isFromPublicKey, isGlobalName, isKnownDid, isKnownMethod, isLocalName, isSameEntity, isValid, parse, parseNameHierarchy, toAddress, toDid, toShortForm, toStrictHex, toTypeInfo, toTypeInfoStr, types };
|
package/esm/method.d.mts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/method.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* DID Method Registry
|
|
4
|
+
*
|
|
5
|
+
* Defines supported DID methods, their categories, and helper functions.
|
|
6
|
+
* Crypto methods (abt, afs, aos, spaces) share a unified identifier encoding.
|
|
7
|
+
* Alias methods (name) use human-readable identifiers with DNS-style hierarchy.
|
|
8
|
+
*/
|
|
9
|
+
/** Crypto methods share unified identifier encoding */
|
|
10
|
+
type CryptoMethod = 'abt' | 'afs' | 'aos' | 'spaces';
|
|
11
|
+
/** Alias methods use human-readable identifiers */
|
|
12
|
+
type AliasMethod = 'name';
|
|
13
|
+
/** All supported DID methods */
|
|
14
|
+
type DIDMethod = CryptoMethod | AliasMethod;
|
|
15
|
+
/** Crypto methods: share unified identifier encoding (base58 checksum / ethereum / base16) */
|
|
16
|
+
declare const CRYPTO_METHODS: readonly CryptoMethod[];
|
|
17
|
+
/** Alias methods: human-readable identifiers */
|
|
18
|
+
declare const ALIAS_METHODS: readonly AliasMethod[];
|
|
19
|
+
/** All supported methods */
|
|
20
|
+
declare const ALL_METHODS: readonly DIDMethod[];
|
|
21
|
+
/** Default method when none is specified */
|
|
22
|
+
declare const DEFAULT_METHOD: CryptoMethod;
|
|
23
|
+
/** Check if a value is a known crypto method */
|
|
24
|
+
declare function isCryptoMethod(method: unknown): method is CryptoMethod;
|
|
25
|
+
/** Check if a value is a known alias method */
|
|
26
|
+
declare function isAliasMethod(method: unknown): method is AliasMethod;
|
|
27
|
+
/** Check if a value is any known method */
|
|
28
|
+
declare function isKnownMethod(method: unknown): method is DIDMethod;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { ALIAS_METHODS, ALL_METHODS, AliasMethod, CRYPTO_METHODS, CryptoMethod, DEFAULT_METHOD, DIDMethod, isAliasMethod, isCryptoMethod, isKnownMethod };
|
package/esm/method.mjs
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/method.ts
|
|
2
|
+
/** Crypto methods: share unified identifier encoding (base58 checksum / ethereum / base16) */
|
|
3
|
+
const CRYPTO_METHODS = Object.freeze([
|
|
4
|
+
"abt",
|
|
5
|
+
"afs",
|
|
6
|
+
"aos",
|
|
7
|
+
"spaces"
|
|
8
|
+
]);
|
|
9
|
+
/** Alias methods: human-readable identifiers */
|
|
10
|
+
const ALIAS_METHODS = Object.freeze(["name"]);
|
|
11
|
+
/** All supported methods */
|
|
12
|
+
const ALL_METHODS = Object.freeze([...CRYPTO_METHODS, ...ALIAS_METHODS]);
|
|
13
|
+
/** Default method when none is specified */
|
|
14
|
+
const DEFAULT_METHOD = "abt";
|
|
15
|
+
/** Check if a value is a known crypto method */
|
|
16
|
+
function isCryptoMethod(method) {
|
|
17
|
+
if (typeof method !== "string") return false;
|
|
18
|
+
return CRYPTO_METHODS.includes(method);
|
|
19
|
+
}
|
|
20
|
+
/** Check if a value is a known alias method */
|
|
21
|
+
function isAliasMethod(method) {
|
|
22
|
+
if (typeof method !== "string") return false;
|
|
23
|
+
return ALIAS_METHODS.includes(method);
|
|
24
|
+
}
|
|
25
|
+
/** Check if a value is any known method */
|
|
26
|
+
function isKnownMethod(method) {
|
|
27
|
+
if (typeof method !== "string") return false;
|
|
28
|
+
return ALL_METHODS.includes(method);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
export { ALIAS_METHODS, ALL_METHODS, CRYPTO_METHODS, DEFAULT_METHOD, isAliasMethod, isCryptoMethod, isKnownMethod };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//#region src/name-registry.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* NameRegistry Interface and InMemoryNameRegistry Implementation
|
|
4
|
+
*
|
|
5
|
+
* Provides a synchronous interface for name→DID registration and lookup.
|
|
6
|
+
* InMemoryNameRegistry is a Map-based implementation for testing.
|
|
7
|
+
*/
|
|
8
|
+
/** Name registry interface (synchronous) */
|
|
9
|
+
interface NameRegistry {
|
|
10
|
+
resolve(name: string): string | null;
|
|
11
|
+
register(name: string, did: string): void;
|
|
12
|
+
unregister(name: string): void;
|
|
13
|
+
has(name: string): boolean;
|
|
14
|
+
list(): string[];
|
|
15
|
+
clear(): void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* In-memory name registry implementation.
|
|
19
|
+
* Uses upsert semantics: re-registering a name overwrites the previous value.
|
|
20
|
+
*/
|
|
21
|
+
declare class InMemoryNameRegistry implements NameRegistry {
|
|
22
|
+
private store;
|
|
23
|
+
resolve(name: string): string | null;
|
|
24
|
+
register(name: string, did: string): void;
|
|
25
|
+
unregister(name: string): void;
|
|
26
|
+
has(name: string): boolean;
|
|
27
|
+
list(): string[];
|
|
28
|
+
clear(): void;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { InMemoryNameRegistry, NameRegistry };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//#region src/name-registry.ts
|
|
2
|
+
const MAX_NAME_LENGTH = 1024;
|
|
3
|
+
/**
|
|
4
|
+
* In-memory name registry implementation.
|
|
5
|
+
* Uses upsert semantics: re-registering a name overwrites the previous value.
|
|
6
|
+
*/
|
|
7
|
+
var InMemoryNameRegistry = class {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.store = /* @__PURE__ */ new Map();
|
|
10
|
+
}
|
|
11
|
+
resolve(name) {
|
|
12
|
+
return this.store.get(name) ?? null;
|
|
13
|
+
}
|
|
14
|
+
register(name, did) {
|
|
15
|
+
if (!name || typeof name !== "string") throw new Error("Name must be a non-empty string");
|
|
16
|
+
if (name.length > MAX_NAME_LENGTH) throw new Error("Name exceeds maximum length");
|
|
17
|
+
if (name.startsWith(".") || name.endsWith(".")) throw new Error("Name cannot start or end with a dot");
|
|
18
|
+
if (!did || typeof did !== "string") throw new Error("DID must be a non-empty string");
|
|
19
|
+
if (!did.startsWith("did:")) throw new Error("DID must have a valid did: prefix");
|
|
20
|
+
this.store.set(name, did);
|
|
21
|
+
}
|
|
22
|
+
unregister(name) {
|
|
23
|
+
this.store.delete(name);
|
|
24
|
+
}
|
|
25
|
+
has(name) {
|
|
26
|
+
return this.store.has(name);
|
|
27
|
+
}
|
|
28
|
+
list() {
|
|
29
|
+
return [...this.store.keys()];
|
|
30
|
+
}
|
|
31
|
+
clear() {
|
|
32
|
+
this.store.clear();
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
export { InMemoryNameRegistry };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { NameRegistry } from "./name-registry.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/name-resolver.d.ts
|
|
4
|
+
|
|
5
|
+
interface DIDNameResolverOptions {
|
|
6
|
+
global?: NameRegistry;
|
|
7
|
+
local?: NameRegistry;
|
|
8
|
+
maxDepth?: number;
|
|
9
|
+
}
|
|
10
|
+
interface ResolveTraceResult {
|
|
11
|
+
result: string | null;
|
|
12
|
+
trace: string[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* DID Name Resolver.
|
|
16
|
+
*
|
|
17
|
+
* Routes name resolution to the appropriate registry based on the name structure:
|
|
18
|
+
* - Global names (single segment) → global registry
|
|
19
|
+
* - .local names → local registry
|
|
20
|
+
* - Delegated names (multi-segment) → resolve TLD first, then delegate
|
|
21
|
+
*/
|
|
22
|
+
declare class DIDNameResolver {
|
|
23
|
+
private global?;
|
|
24
|
+
private local?;
|
|
25
|
+
private maxDepth;
|
|
26
|
+
constructor(opts?: DIDNameResolverOptions);
|
|
27
|
+
/**
|
|
28
|
+
* Resolve a name to a DID.
|
|
29
|
+
*
|
|
30
|
+
* @param name - The name to resolve (can be prefixed with 'did:name:')
|
|
31
|
+
* @returns The resolved DID, or null if not found
|
|
32
|
+
*/
|
|
33
|
+
resolve(name: string): Promise<string | null>;
|
|
34
|
+
/**
|
|
35
|
+
* Resolve a name with full trace for debugging.
|
|
36
|
+
*/
|
|
37
|
+
resolveWithTrace(name: string): Promise<ResolveTraceResult>;
|
|
38
|
+
private _resolve;
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { DIDNameResolver, DIDNameResolverOptions, ResolveTraceResult };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { getResolveRoute } from "./name.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/name-resolver.ts
|
|
4
|
+
/**
|
|
5
|
+
* DID Name Resolver
|
|
6
|
+
*
|
|
7
|
+
* Orchestrates name resolution across different registries (global, local).
|
|
8
|
+
* Supports delegation chains with cycle detection and depth limiting.
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_MAX_DEPTH = 10;
|
|
11
|
+
/**
|
|
12
|
+
* DID Name Resolver.
|
|
13
|
+
*
|
|
14
|
+
* Routes name resolution to the appropriate registry based on the name structure:
|
|
15
|
+
* - Global names (single segment) → global registry
|
|
16
|
+
* - .local names → local registry
|
|
17
|
+
* - Delegated names (multi-segment) → resolve TLD first, then delegate
|
|
18
|
+
*/
|
|
19
|
+
var DIDNameResolver = class {
|
|
20
|
+
constructor(opts = {}) {
|
|
21
|
+
this.global = opts.global;
|
|
22
|
+
this.local = opts.local;
|
|
23
|
+
this.maxDepth = opts.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolve a name to a DID.
|
|
27
|
+
*
|
|
28
|
+
* @param name - The name to resolve (can be prefixed with 'did:name:')
|
|
29
|
+
* @returns The resolved DID, or null if not found
|
|
30
|
+
*/
|
|
31
|
+
async resolve(name) {
|
|
32
|
+
return (await this.resolveWithTrace(name)).result;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Resolve a name with full trace for debugging.
|
|
36
|
+
*/
|
|
37
|
+
async resolveWithTrace(name) {
|
|
38
|
+
if (!name || typeof name !== "string") throw new Error("Name must be a non-empty string");
|
|
39
|
+
const cleanName = name.replace(/^did:name:/, "");
|
|
40
|
+
if (!cleanName) throw new Error("Name cannot be empty after stripping prefix");
|
|
41
|
+
const visited = /* @__PURE__ */ new Set();
|
|
42
|
+
return this._resolve(cleanName, visited, [], 0);
|
|
43
|
+
}
|
|
44
|
+
async _resolve(name, visited, trace, depth) {
|
|
45
|
+
if (visited.has(name)) {
|
|
46
|
+
trace.push(`cycle_detected:${name}`);
|
|
47
|
+
return {
|
|
48
|
+
result: null,
|
|
49
|
+
trace
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (depth >= this.maxDepth) {
|
|
53
|
+
trace.push(`max_depth:${name}`);
|
|
54
|
+
return {
|
|
55
|
+
result: null,
|
|
56
|
+
trace
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
visited.add(name);
|
|
60
|
+
const route = getResolveRoute(name);
|
|
61
|
+
let resolved = null;
|
|
62
|
+
if (route.type === "local") {
|
|
63
|
+
trace.push(`local:${name}`);
|
|
64
|
+
if (this.local) resolved = this.local.resolve(route.name);
|
|
65
|
+
} else if (route.type === "global") {
|
|
66
|
+
trace.push(`global:${name}`);
|
|
67
|
+
if (this.global) resolved = this.global.resolve(route.name);
|
|
68
|
+
} else {
|
|
69
|
+
trace.push(`delegated:${name}`);
|
|
70
|
+
if (this.global) {
|
|
71
|
+
resolved = this.global.resolve(name);
|
|
72
|
+
if (!resolved) {
|
|
73
|
+
const tldResult = this.global.resolve(route.tld);
|
|
74
|
+
if (tldResult) resolved = tldResult;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!resolved) {
|
|
79
|
+
trace.push(`not_found:${name}`);
|
|
80
|
+
return {
|
|
81
|
+
result: null,
|
|
82
|
+
trace
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (resolved.startsWith("did:name:")) {
|
|
86
|
+
const nextName = resolved.replace(/^did:name:/, "");
|
|
87
|
+
return this._resolve(nextName, visited, trace, depth + 1);
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
result: resolved,
|
|
91
|
+
trace
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
//#endregion
|
|
97
|
+
export { DIDNameResolver };
|
package/esm/name.d.mts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//#region src/name.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* DID Name Hierarchy Parser
|
|
4
|
+
*
|
|
5
|
+
* DNS-style right-to-left name resolution for did:name identifiers.
|
|
6
|
+
* Supports global names, delegated sub-names, and .local domains.
|
|
7
|
+
*/
|
|
8
|
+
/** Parsed name hierarchy */
|
|
9
|
+
interface NameHierarchy {
|
|
10
|
+
/** Name segments split by '.' */
|
|
11
|
+
segments: string[];
|
|
12
|
+
/** Top-level domain (rightmost segment) */
|
|
13
|
+
tld: string;
|
|
14
|
+
/** Whether this is a .local name */
|
|
15
|
+
isLocal: boolean;
|
|
16
|
+
/** Whether this is a global name (single segment, not .local) */
|
|
17
|
+
isGlobal: boolean;
|
|
18
|
+
/** Number of segments */
|
|
19
|
+
depth: number;
|
|
20
|
+
}
|
|
21
|
+
/** Resolve route types */
|
|
22
|
+
type ResolveRoute = {
|
|
23
|
+
type: 'global';
|
|
24
|
+
name: string;
|
|
25
|
+
} | {
|
|
26
|
+
type: 'local';
|
|
27
|
+
name: string;
|
|
28
|
+
} | {
|
|
29
|
+
type: 'delegated';
|
|
30
|
+
tld: string;
|
|
31
|
+
subName: string;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Parse a name identifier into its hierarchy components.
|
|
35
|
+
*
|
|
36
|
+
* @param name - The name to parse (e.g. 'robertmao', 'bob.myorg', 'anything.local')
|
|
37
|
+
* @returns Parsed hierarchy
|
|
38
|
+
* @throws If name is invalid
|
|
39
|
+
*/
|
|
40
|
+
declare function parseNameHierarchy(name: unknown): NameHierarchy;
|
|
41
|
+
/**
|
|
42
|
+
* Determine the resolve route for a name.
|
|
43
|
+
*
|
|
44
|
+
* - Single segment (e.g. 'robertmao') → global route
|
|
45
|
+
* - Ends with '.local' → local route
|
|
46
|
+
* - Multi-segment (e.g. 'bob.myorg') → delegated route
|
|
47
|
+
*
|
|
48
|
+
* @param name - The name to route
|
|
49
|
+
* @returns The resolve route
|
|
50
|
+
* @throws If name is invalid
|
|
51
|
+
*/
|
|
52
|
+
declare function getResolveRoute(name: string): ResolveRoute;
|
|
53
|
+
/**
|
|
54
|
+
* Check if a name is a .local name.
|
|
55
|
+
*/
|
|
56
|
+
declare function isLocalName(name: string): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Check if a name is a global name (single segment, not .local).
|
|
59
|
+
*/
|
|
60
|
+
declare function isGlobalName(name: string): boolean;
|
|
61
|
+
//#endregion
|
|
62
|
+
export { NameHierarchy, ResolveRoute, getResolveRoute, isGlobalName, isLocalName, parseNameHierarchy };
|
package/esm/name.mjs
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
//#region src/name.ts
|
|
2
|
+
/**
|
|
3
|
+
* Parse a name identifier into its hierarchy components.
|
|
4
|
+
*
|
|
5
|
+
* @param name - The name to parse (e.g. 'robertmao', 'bob.myorg', 'anything.local')
|
|
6
|
+
* @returns Parsed hierarchy
|
|
7
|
+
* @throws If name is invalid
|
|
8
|
+
*/
|
|
9
|
+
function parseNameHierarchy(name) {
|
|
10
|
+
if (typeof name !== "string") throw new Error("Name must be a string");
|
|
11
|
+
if (!name) throw new Error("Name cannot be empty");
|
|
12
|
+
if (name.startsWith(".")) throw new Error("Name cannot start with a dot");
|
|
13
|
+
if (name.endsWith(".")) throw new Error("Name cannot end with a dot");
|
|
14
|
+
if (name.includes("..")) throw new Error("Name cannot contain consecutive dots");
|
|
15
|
+
const segments = name.split(".");
|
|
16
|
+
if (segments.some((s) => !s)) throw new Error("Name contains empty segments");
|
|
17
|
+
const tld = segments[segments.length - 1];
|
|
18
|
+
const isLocal = tld === "local";
|
|
19
|
+
const isGlobal = segments.length === 1 && !isLocal;
|
|
20
|
+
const depth = segments.length;
|
|
21
|
+
return {
|
|
22
|
+
segments: [...segments],
|
|
23
|
+
tld,
|
|
24
|
+
isLocal,
|
|
25
|
+
isGlobal,
|
|
26
|
+
depth
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Determine the resolve route for a name.
|
|
31
|
+
*
|
|
32
|
+
* - Single segment (e.g. 'robertmao') → global route
|
|
33
|
+
* - Ends with '.local' → local route
|
|
34
|
+
* - Multi-segment (e.g. 'bob.myorg') → delegated route
|
|
35
|
+
*
|
|
36
|
+
* @param name - The name to route
|
|
37
|
+
* @returns The resolve route
|
|
38
|
+
* @throws If name is invalid
|
|
39
|
+
*/
|
|
40
|
+
function getResolveRoute(name) {
|
|
41
|
+
const hierarchy = parseNameHierarchy(name);
|
|
42
|
+
if (hierarchy.isLocal) return {
|
|
43
|
+
type: "local",
|
|
44
|
+
name: hierarchy.segments.slice(0, -1).join(".")
|
|
45
|
+
};
|
|
46
|
+
if (hierarchy.isGlobal) return {
|
|
47
|
+
type: "global",
|
|
48
|
+
name
|
|
49
|
+
};
|
|
50
|
+
const subName = hierarchy.segments.slice(0, -1).join(".");
|
|
51
|
+
return {
|
|
52
|
+
type: "delegated",
|
|
53
|
+
tld: hierarchy.tld,
|
|
54
|
+
subName
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Check if a name is a .local name.
|
|
59
|
+
*/
|
|
60
|
+
function isLocalName(name) {
|
|
61
|
+
if (!name || !name.includes(".")) return false;
|
|
62
|
+
return name.endsWith(".local");
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Check if a name is a global name (single segment, not .local).
|
|
66
|
+
*/
|
|
67
|
+
function isGlobalName(name) {
|
|
68
|
+
if (!name) return false;
|
|
69
|
+
return !name.includes(".");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
//#endregion
|
|
73
|
+
export { getResolveRoute, isGlobalName, isLocalName, parseNameHierarchy };
|
package/esm/parse.d.mts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { DIDMethod } from "./method.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/parse.d.ts
|
|
4
|
+
|
|
5
|
+
/** Parsed DID structure */
|
|
6
|
+
interface ParsedDID {
|
|
7
|
+
/** The method component (e.g. 'abt', 'afs', 'name') */
|
|
8
|
+
method: DIDMethod;
|
|
9
|
+
/** The identifier component (everything after did:{method}:) */
|
|
10
|
+
identifier: string;
|
|
11
|
+
/** The full DID string */
|
|
12
|
+
full: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Parse a full DID string into its components.
|
|
16
|
+
*
|
|
17
|
+
* @param did - Full DID string (e.g. 'did:abt:z1abc...')
|
|
18
|
+
* @returns Parsed DID object
|
|
19
|
+
* @throws If the input is not a valid DID string
|
|
20
|
+
*/
|
|
21
|
+
declare function parse(did: unknown): ParsedDID;
|
|
22
|
+
/**
|
|
23
|
+
* Format an identifier and method into a full DID string.
|
|
24
|
+
*
|
|
25
|
+
* Handles idempotency: if the identifier already has a `did:{method}:` prefix
|
|
26
|
+
* matching the target method, returns it as-is.
|
|
27
|
+
*
|
|
28
|
+
* @param identifier - The identifier (address or name)
|
|
29
|
+
* @param method - The DID method (default: 'abt')
|
|
30
|
+
* @returns Full DID string
|
|
31
|
+
* @throws If the method is unknown or identifier is empty
|
|
32
|
+
*/
|
|
33
|
+
declare function formatDid(identifier: string, method?: DIDMethod): string;
|
|
34
|
+
/**
|
|
35
|
+
* Extract the method from a DID string.
|
|
36
|
+
*
|
|
37
|
+
* @param did - A DID string or bare address
|
|
38
|
+
* @returns The method string, or null if no prefix found
|
|
39
|
+
*/
|
|
40
|
+
declare function extractMethod(did: unknown): DIDMethod | null;
|
|
41
|
+
/**
|
|
42
|
+
* Extract the identifier from a DID string, stripping the `did:{method}:` prefix.
|
|
43
|
+
* If the input has no prefix, returns it as-is.
|
|
44
|
+
*
|
|
45
|
+
* @param did - A DID string or bare address
|
|
46
|
+
* @returns The identifier portion
|
|
47
|
+
*/
|
|
48
|
+
declare function extractIdentifier(did: unknown): string;
|
|
49
|
+
//#endregion
|
|
50
|
+
export { ParsedDID, extractIdentifier, extractMethod, formatDid, parse };
|
package/esm/parse.mjs
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { DEFAULT_METHOD, isKnownMethod } from "./method.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/parse.ts
|
|
4
|
+
/**
|
|
5
|
+
* Universal DID Parser
|
|
6
|
+
*
|
|
7
|
+
* Parses, formats, and extracts components from DID strings.
|
|
8
|
+
* Supports all registered methods (abt, afs, aos, spaces, name).
|
|
9
|
+
*/
|
|
10
|
+
/** DID format regex: did:{method}:{identifier} where method is [a-z0-9]+ */
|
|
11
|
+
const DID_REGEX = /^did:([a-z0-9]+):(.+)$/;
|
|
12
|
+
/**
|
|
13
|
+
* Parse a full DID string into its components.
|
|
14
|
+
*
|
|
15
|
+
* @param did - Full DID string (e.g. 'did:abt:z1abc...')
|
|
16
|
+
* @returns Parsed DID object
|
|
17
|
+
* @throws If the input is not a valid DID string
|
|
18
|
+
*/
|
|
19
|
+
function parse(did) {
|
|
20
|
+
if (typeof did !== "string") throw new Error("DID must be a string");
|
|
21
|
+
if (!did) throw new Error("DID string cannot be empty");
|
|
22
|
+
const match = did.match(DID_REGEX);
|
|
23
|
+
if (!match) throw new Error("Invalid DID format");
|
|
24
|
+
const [, method, identifier] = match;
|
|
25
|
+
if (!method) throw new Error("Invalid DID format");
|
|
26
|
+
if (!identifier) throw new Error("Invalid DID format");
|
|
27
|
+
if (!isKnownMethod(method)) throw new Error(`Unknown DID method: ${method}`);
|
|
28
|
+
return {
|
|
29
|
+
method,
|
|
30
|
+
identifier,
|
|
31
|
+
full: did
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Format an identifier and method into a full DID string.
|
|
36
|
+
*
|
|
37
|
+
* Handles idempotency: if the identifier already has a `did:{method}:` prefix
|
|
38
|
+
* matching the target method, returns it as-is.
|
|
39
|
+
*
|
|
40
|
+
* @param identifier - The identifier (address or name)
|
|
41
|
+
* @param method - The DID method (default: 'abt')
|
|
42
|
+
* @returns Full DID string
|
|
43
|
+
* @throws If the method is unknown or identifier is empty
|
|
44
|
+
*/
|
|
45
|
+
function formatDid(identifier, method = DEFAULT_METHOD) {
|
|
46
|
+
if (!identifier) throw new Error("Identifier cannot be empty");
|
|
47
|
+
if (!isKnownMethod(method)) throw new Error(`Unknown DID method: ${method}`);
|
|
48
|
+
const prefix = `did:${method}:`;
|
|
49
|
+
if (identifier.startsWith(prefix)) return identifier;
|
|
50
|
+
if (identifier.startsWith("did:")) {
|
|
51
|
+
const match = identifier.match(DID_REGEX);
|
|
52
|
+
if (match) {
|
|
53
|
+
if (match[1] === method) return identifier;
|
|
54
|
+
return `did:${method}:${match[2]}`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return `did:${method}:${identifier}`;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Extract the method from a DID string.
|
|
61
|
+
*
|
|
62
|
+
* @param did - A DID string or bare address
|
|
63
|
+
* @returns The method string, or null if no prefix found
|
|
64
|
+
*/
|
|
65
|
+
function extractMethod(did) {
|
|
66
|
+
if (typeof did !== "string" || !did) return null;
|
|
67
|
+
const match = did.match(DID_REGEX);
|
|
68
|
+
if (!match) return null;
|
|
69
|
+
const method = match[1];
|
|
70
|
+
if (isKnownMethod(method)) return method;
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Extract the identifier from a DID string, stripping the `did:{method}:` prefix.
|
|
75
|
+
* If the input has no prefix, returns it as-is.
|
|
76
|
+
*
|
|
77
|
+
* @param did - A DID string or bare address
|
|
78
|
+
* @returns The identifier portion
|
|
79
|
+
*/
|
|
80
|
+
function extractIdentifier(did) {
|
|
81
|
+
if (typeof did !== "string" || !did) return "";
|
|
82
|
+
const match = did.match(/^did:[a-z0-9]+:(.+)$/);
|
|
83
|
+
if (match) return match[1];
|
|
84
|
+
return did;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
//#endregion
|
|
88
|
+
export { extractIdentifier, extractMethod, formatDid, parse };
|