@arcblock/did 1.29.18 → 1.29.20

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.
@@ -0,0 +1,30 @@
1
+ //#region src/short-form.d.ts
2
+ /**
3
+ * Short-form DID Resolution
4
+ *
5
+ * Deterministic expansion of short-form identifiers to full DIDs.
6
+ * Based on base58 checksum validation (not heuristic guessing).
7
+ *
8
+ * Rules:
9
+ * - Already a full DID (starts with "did:") → return as-is
10
+ * - Valid cryptographic address (passes isValid checksum) → did:abt:{address}
11
+ * - Everything else → did:name:{input}
12
+ */
13
+ /**
14
+ * Expand a short-form identifier to a full DID.
15
+ *
16
+ * @param input - Short-form identifier (address, name, or full DID)
17
+ * @returns Full DID string
18
+ * @throws If input is not a non-empty string
19
+ */
20
+ declare function expandShortForm(input: unknown): string;
21
+ /**
22
+ * Convert a full DID to its short form (strip the did:{method}: prefix).
23
+ *
24
+ * @param did - Full DID string
25
+ * @returns Short-form identifier
26
+ * @throws If input is not a non-empty string
27
+ */
28
+ declare function toShortForm(did: unknown): string;
29
+ //#endregion
30
+ export { expandShortForm, toShortForm };
@@ -0,0 +1,46 @@
1
+ import { formatDid } from "./parse.mjs";
2
+ import { isValid } from "./index.mjs";
3
+
4
+ //#region src/short-form.ts
5
+ /**
6
+ * Short-form DID Resolution
7
+ *
8
+ * Deterministic expansion of short-form identifiers to full DIDs.
9
+ * Based on base58 checksum validation (not heuristic guessing).
10
+ *
11
+ * Rules:
12
+ * - Already a full DID (starts with "did:") → return as-is
13
+ * - Valid cryptographic address (passes isValid checksum) → did:abt:{address}
14
+ * - Everything else → did:name:{input}
15
+ */
16
+ /**
17
+ * Expand a short-form identifier to a full DID.
18
+ *
19
+ * @param input - Short-form identifier (address, name, or full DID)
20
+ * @returns Full DID string
21
+ * @throws If input is not a non-empty string
22
+ */
23
+ function expandShortForm(input) {
24
+ if (typeof input !== "string") throw new Error("Input must be a string");
25
+ if (!input) throw new Error("Input cannot be empty");
26
+ if (input.startsWith("did:")) return input;
27
+ if (isValid(input)) return formatDid(input, "abt");
28
+ return formatDid(input, "name");
29
+ }
30
+ /**
31
+ * Convert a full DID to its short form (strip the did:{method}: prefix).
32
+ *
33
+ * @param did - Full DID string
34
+ * @returns Short-form identifier
35
+ * @throws If input is not a non-empty string
36
+ */
37
+ function toShortForm(did) {
38
+ if (typeof did !== "string") throw new Error("Input must be a string");
39
+ if (!did) throw new Error("Input cannot be empty");
40
+ const match = did.match(/^did:[a-z0-9]+:(.+)$/);
41
+ if (match) return match[1];
42
+ return did;
43
+ }
44
+
45
+ //#endregion
46
+ export { expandShortForm, toShortForm };
package/esm/util.mjs CHANGED
@@ -12,7 +12,7 @@ const DID_PREFIX = "did:abt:";
12
12
  const toBytes = (did) => {
13
13
  try {
14
14
  if (isHexStrict(did)) return Buffer.from(stripHexPrefix(did), "hex");
15
- let bytes = fromBase58(did.replace(DID_PREFIX, ""));
15
+ let bytes = fromBase58(did.replace(/^did:[a-z0-9]+:/, ""));
16
16
  while (bytes.length < 26) bytes = Buffer.concat([Uint8Array.from([0]), Uint8Array.from(bytes)]);
17
17
  return bytes;
18
18
  } catch (err) {
@@ -0,0 +1,23 @@
1
+ //#region src/validate.d.ts
2
+ /**
3
+ * DID Validation Functions
4
+ *
5
+ * isValid and isFromPublicKey remain in index.ts (too many dependencies to move).
6
+ * This file provides isKnownDid which covers all methods including did:name.
7
+ */
8
+ /**
9
+ * Check if a DID string is a known valid DID.
10
+ *
11
+ * Unlike `isValid` (which only validates cryptographic addresses),
12
+ * `isKnownDid` covers all known methods:
13
+ * - Crypto methods (abt/afs/aos/spaces): delegates to `isValid` for checksum validation
14
+ * - Alias methods (name): checks for non-empty identifier
15
+ * - Unknown methods: returns false
16
+ * - Bare cryptographic addresses: delegates to `isValid`
17
+ *
18
+ * @param did - DID string to validate
19
+ * @returns true if the DID is valid for its method
20
+ */
21
+ declare function isKnownDid(did: unknown): boolean;
22
+ //#endregion
23
+ export { isKnownDid };
@@ -0,0 +1,36 @@
1
+ import { isAliasMethod, isCryptoMethod } from "./method.mjs";
2
+ import { isValid } from "./index.mjs";
3
+
4
+ //#region src/validate.ts
5
+ /**
6
+ * DID Validation Functions
7
+ *
8
+ * isValid and isFromPublicKey remain in index.ts (too many dependencies to move).
9
+ * This file provides isKnownDid which covers all methods including did:name.
10
+ */
11
+ /**
12
+ * Check if a DID string is a known valid DID.
13
+ *
14
+ * Unlike `isValid` (which only validates cryptographic addresses),
15
+ * `isKnownDid` covers all known methods:
16
+ * - Crypto methods (abt/afs/aos/spaces): delegates to `isValid` for checksum validation
17
+ * - Alias methods (name): checks for non-empty identifier
18
+ * - Unknown methods: returns false
19
+ * - Bare cryptographic addresses: delegates to `isValid`
20
+ *
21
+ * @param did - DID string to validate
22
+ * @returns true if the DID is valid for its method
23
+ */
24
+ function isKnownDid(did) {
25
+ if (typeof did !== "string" || !did) return false;
26
+ const match = did.match(/^did:([a-z0-9]+):(.*)$/);
27
+ if (!match) return isValid(did);
28
+ const [, method, identifier] = match;
29
+ if (!identifier) return false;
30
+ if (isCryptoMethod(method)) return isValid(did);
31
+ if (isAliasMethod(method)) return true;
32
+ return false;
33
+ }
34
+
35
+ //#endregion
36
+ export { isKnownDid };
package/lib/entity.cjs ADDED
@@ -0,0 +1,40 @@
1
+ const require_parse = require('./parse.cjs');
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 require_parse.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 = require_parse.extractIdentifier(a);
33
+ const idB = require_parse.extractIdentifier(b);
34
+ if (!idA || !idB) return false;
35
+ return idA.toLowerCase() === idB.toLowerCase();
36
+ }
37
+
38
+ //#endregion
39
+ exports.getEntityId = getEntityId;
40
+ exports.isSameEntity = isSameEntity;
@@ -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/lib/index.cjs CHANGED
@@ -1,6 +1,14 @@
1
1
  const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
2
+ const require_method = require('./method.cjs');
3
+ const require_parse = require('./parse.cjs');
4
+ const require_entity = require('./entity.cjs');
2
5
  const require_util = require('./util.cjs');
3
6
  const require_type = require('./type.cjs');
7
+ const require_validate = require('./validate.cjs');
8
+ const require_short_form = require('./short-form.cjs');
9
+ const require_name = require('./name.cjs');
10
+ const require_name_registry = require('./name-registry.cjs');
11
+ const require_name_resolver = require('./name-resolver.cjs');
4
12
  let _ocap_mcrypto = require("@ocap/mcrypto");
5
13
  let _ocap_util = require("@ocap/util");
6
14
 
@@ -70,7 +78,7 @@ const fromHash = (hash, role = _ocap_mcrypto.types.RoleType.ROLE_ACCOUNT) => {
70
78
  */
71
79
  const isFromPublicKey = (did, pk) => {
72
80
  if (isValid(did) === false) return false;
73
- return fromPublicKey(pk, require_type.toTypeInfo(did)) === did.replace(require_util.DID_PREFIX, "");
81
+ return fromPublicKey(pk, require_type.toTypeInfo(did)) === (0, _ocap_util.toAddress)(did);
74
82
  };
75
83
  /**
76
84
  * Check if a DID string is valid
@@ -93,22 +101,44 @@ const isValid = (did) => {
93
101
  };
94
102
 
95
103
  //#endregion
104
+ exports.ALIAS_METHODS = require_method.ALIAS_METHODS;
105
+ exports.ALL_METHODS = require_method.ALL_METHODS;
106
+ exports.CRYPTO_METHODS = require_method.CRYPTO_METHODS;
107
+ exports.DEFAULT_METHOD = require_method.DEFAULT_METHOD;
108
+ exports.DIDNameResolver = require_name_resolver.DIDNameResolver;
96
109
  exports.DID_PREFIX = require_util.DID_PREFIX;
97
110
  exports.DID_TYPE_ARCBLOCK = require_type.DID_TYPE_ARCBLOCK;
98
111
  exports.DID_TYPE_ETHEREUM = require_type.DID_TYPE_ETHEREUM;
99
112
  exports.DID_TYPE_PASSKEY = require_type.DID_TYPE_PASSKEY;
100
113
  exports.DidType = require_type.DidType;
114
+ exports.InMemoryNameRegistry = require_name_registry.InMemoryNameRegistry;
115
+ exports.expandShortForm = require_short_form.expandShortForm;
116
+ exports.extractIdentifier = require_parse.extractIdentifier;
117
+ exports.extractMethod = require_parse.extractMethod;
118
+ exports.formatDid = require_parse.formatDid;
101
119
  exports.fromHash = fromHash;
102
120
  exports.fromPublicKey = fromPublicKey;
103
121
  exports.fromPublicKeyHash = fromPublicKeyHash;
104
122
  exports.fromSecretKey = fromSecretKey;
105
123
  exports.fromTypeInfo = require_type.fromTypeInfo;
124
+ exports.getEntityId = require_entity.getEntityId;
125
+ exports.getResolveRoute = require_name.getResolveRoute;
126
+ exports.isAliasMethod = require_method.isAliasMethod;
127
+ exports.isCryptoMethod = require_method.isCryptoMethod;
106
128
  exports.isEthereumDid = require_type.isEthereumDid;
107
129
  exports.isEthereumType = require_type.isEthereumType;
108
130
  exports.isFromPublicKey = isFromPublicKey;
131
+ exports.isGlobalName = require_name.isGlobalName;
132
+ exports.isKnownDid = require_validate.isKnownDid;
133
+ exports.isKnownMethod = require_method.isKnownMethod;
134
+ exports.isLocalName = require_name.isLocalName;
135
+ exports.isSameEntity = require_entity.isSameEntity;
109
136
  exports.isValid = isValid;
137
+ exports.parse = require_parse.parse;
138
+ exports.parseNameHierarchy = require_name.parseNameHierarchy;
110
139
  exports.toAddress = _ocap_util.toAddress;
111
140
  exports.toDid = _ocap_util.toDid;
141
+ exports.toShortForm = require_short_form.toShortForm;
112
142
  exports.toStrictHex = require_util.toStrictHex;
113
143
  exports.toTypeInfo = require_type.toTypeInfo;
114
144
  exports.toTypeInfoStr = require_type.toTypeInfoStr;
package/lib/index.d.cts CHANGED
@@ -1,5 +1,13 @@
1
+ import { getEntityId, isSameEntity } from "./entity.cjs";
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.cjs";
2
3
  import { DID_PREFIX, toStrictHex } from "./util.cjs";
4
+ import { ALIAS_METHODS, ALL_METHODS, AliasMethod, CRYPTO_METHODS, CryptoMethod, DEFAULT_METHOD, DIDMethod, isAliasMethod, isCryptoMethod, isKnownMethod } from "./method.cjs";
5
+ import { ParsedDID, extractIdentifier, extractMethod, formatDid, parse } from "./parse.cjs";
6
+ import { isKnownDid } from "./validate.cjs";
7
+ import { expandShortForm, toShortForm } from "./short-form.cjs";
8
+ import { NameHierarchy, ResolveRoute, getResolveRoute, isGlobalName, isLocalName, parseNameHierarchy } from "./name.cjs";
9
+ import { InMemoryNameRegistry, NameRegistry } from "./name-registry.cjs";
10
+ import { DIDNameResolver, DIDNameResolverOptions, ResolveTraceResult } from "./name-resolver.cjs";
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/lib/method.cjs ADDED
@@ -0,0 +1,39 @@
1
+
2
+ //#region src/method.ts
3
+ /** Crypto methods: share unified identifier encoding (base58 checksum / ethereum / base16) */
4
+ const CRYPTO_METHODS = Object.freeze([
5
+ "abt",
6
+ "afs",
7
+ "aos",
8
+ "spaces"
9
+ ]);
10
+ /** Alias methods: human-readable identifiers */
11
+ const ALIAS_METHODS = Object.freeze(["name"]);
12
+ /** All supported methods */
13
+ const ALL_METHODS = Object.freeze([...CRYPTO_METHODS, ...ALIAS_METHODS]);
14
+ /** Default method when none is specified */
15
+ const DEFAULT_METHOD = "abt";
16
+ /** Check if a value is a known crypto method */
17
+ function isCryptoMethod(method) {
18
+ if (typeof method !== "string") return false;
19
+ return CRYPTO_METHODS.includes(method);
20
+ }
21
+ /** Check if a value is a known alias method */
22
+ function isAliasMethod(method) {
23
+ if (typeof method !== "string") return false;
24
+ return ALIAS_METHODS.includes(method);
25
+ }
26
+ /** Check if a value is any known method */
27
+ function isKnownMethod(method) {
28
+ if (typeof method !== "string") return false;
29
+ return ALL_METHODS.includes(method);
30
+ }
31
+
32
+ //#endregion
33
+ exports.ALIAS_METHODS = ALIAS_METHODS;
34
+ exports.ALL_METHODS = ALL_METHODS;
35
+ exports.CRYPTO_METHODS = CRYPTO_METHODS;
36
+ exports.DEFAULT_METHOD = DEFAULT_METHOD;
37
+ exports.isAliasMethod = isAliasMethod;
38
+ exports.isCryptoMethod = isCryptoMethod;
39
+ exports.isKnownMethod = isKnownMethod;
@@ -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 };
@@ -0,0 +1,38 @@
1
+
2
+ //#region src/name-registry.ts
3
+ const MAX_NAME_LENGTH = 1024;
4
+ /**
5
+ * In-memory name registry implementation.
6
+ * Uses upsert semantics: re-registering a name overwrites the previous value.
7
+ */
8
+ var InMemoryNameRegistry = class {
9
+ constructor() {
10
+ this.store = /* @__PURE__ */ new Map();
11
+ }
12
+ resolve(name) {
13
+ return this.store.get(name) ?? null;
14
+ }
15
+ register(name, did) {
16
+ if (!name || typeof name !== "string") throw new Error("Name must be a non-empty string");
17
+ if (name.length > MAX_NAME_LENGTH) throw new Error("Name exceeds maximum length");
18
+ if (name.startsWith(".") || name.endsWith(".")) throw new Error("Name cannot start or end with a dot");
19
+ if (!did || typeof did !== "string") throw new Error("DID must be a non-empty string");
20
+ if (!did.startsWith("did:")) throw new Error("DID must have a valid did: prefix");
21
+ this.store.set(name, did);
22
+ }
23
+ unregister(name) {
24
+ this.store.delete(name);
25
+ }
26
+ has(name) {
27
+ return this.store.has(name);
28
+ }
29
+ list() {
30
+ return [...this.store.keys()];
31
+ }
32
+ clear() {
33
+ this.store.clear();
34
+ }
35
+ };
36
+
37
+ //#endregion
38
+ exports.InMemoryNameRegistry = InMemoryNameRegistry;
@@ -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,97 @@
1
+ const require_name = require('./name.cjs');
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 = require_name.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
+ exports.DIDNameResolver = DIDNameResolver;
@@ -0,0 +1,41 @@
1
+ import { NameRegistry } from "./name-registry.cjs";
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 };
package/lib/name.cjs ADDED
@@ -0,0 +1,77 @@
1
+
2
+ //#region src/name.ts
3
+ /**
4
+ * Parse a name identifier into its hierarchy components.
5
+ *
6
+ * @param name - The name to parse (e.g. 'robertmao', 'bob.myorg', 'anything.local')
7
+ * @returns Parsed hierarchy
8
+ * @throws If name is invalid
9
+ */
10
+ function parseNameHierarchy(name) {
11
+ if (typeof name !== "string") throw new Error("Name must be a string");
12
+ if (!name) throw new Error("Name cannot be empty");
13
+ if (name.startsWith(".")) throw new Error("Name cannot start with a dot");
14
+ if (name.endsWith(".")) throw new Error("Name cannot end with a dot");
15
+ if (name.includes("..")) throw new Error("Name cannot contain consecutive dots");
16
+ const segments = name.split(".");
17
+ if (segments.some((s) => !s)) throw new Error("Name contains empty segments");
18
+ const tld = segments[segments.length - 1];
19
+ const isLocal = tld === "local";
20
+ const isGlobal = segments.length === 1 && !isLocal;
21
+ const depth = segments.length;
22
+ return {
23
+ segments: [...segments],
24
+ tld,
25
+ isLocal,
26
+ isGlobal,
27
+ depth
28
+ };
29
+ }
30
+ /**
31
+ * Determine the resolve route for a name.
32
+ *
33
+ * - Single segment (e.g. 'robertmao') → global route
34
+ * - Ends with '.local' → local route
35
+ * - Multi-segment (e.g. 'bob.myorg') → delegated route
36
+ *
37
+ * @param name - The name to route
38
+ * @returns The resolve route
39
+ * @throws If name is invalid
40
+ */
41
+ function getResolveRoute(name) {
42
+ const hierarchy = parseNameHierarchy(name);
43
+ if (hierarchy.isLocal) return {
44
+ type: "local",
45
+ name: hierarchy.segments.slice(0, -1).join(".")
46
+ };
47
+ if (hierarchy.isGlobal) return {
48
+ type: "global",
49
+ name
50
+ };
51
+ const subName = hierarchy.segments.slice(0, -1).join(".");
52
+ return {
53
+ type: "delegated",
54
+ tld: hierarchy.tld,
55
+ subName
56
+ };
57
+ }
58
+ /**
59
+ * Check if a name is a .local name.
60
+ */
61
+ function isLocalName(name) {
62
+ if (!name || !name.includes(".")) return false;
63
+ return name.endsWith(".local");
64
+ }
65
+ /**
66
+ * Check if a name is a global name (single segment, not .local).
67
+ */
68
+ function isGlobalName(name) {
69
+ if (!name) return false;
70
+ return !name.includes(".");
71
+ }
72
+
73
+ //#endregion
74
+ exports.getResolveRoute = getResolveRoute;
75
+ exports.isGlobalName = isGlobalName;
76
+ exports.isLocalName = isLocalName;
77
+ exports.parseNameHierarchy = parseNameHierarchy;