@did-btcr2/common 2.2.2 → 3.0.0

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.
Files changed (85) hide show
  1. package/dist/cjs/canonicalization.js +66 -54
  2. package/dist/cjs/canonicalization.js.map +1 -1
  3. package/dist/cjs/constants.js +17 -11
  4. package/dist/cjs/constants.js.map +1 -1
  5. package/dist/cjs/index.js +5 -3
  6. package/dist/cjs/index.js.map +1 -1
  7. package/dist/cjs/json-patch.js +98 -0
  8. package/dist/cjs/json-patch.js.map +1 -0
  9. package/dist/cjs/logger.js +46 -12
  10. package/dist/cjs/logger.js.map +1 -1
  11. package/dist/cjs/utils/date.js +32 -0
  12. package/dist/cjs/utils/date.js.map +1 -0
  13. package/dist/cjs/utils/json.js +280 -0
  14. package/dist/cjs/utils/json.js.map +1 -0
  15. package/dist/cjs/utils/set.js +23 -0
  16. package/dist/cjs/utils/set.js.map +1 -0
  17. package/dist/cjs/utils/string.js +55 -0
  18. package/dist/cjs/utils/string.js.map +1 -0
  19. package/dist/esm/canonicalization.js +66 -54
  20. package/dist/esm/canonicalization.js.map +1 -1
  21. package/dist/esm/constants.js +17 -11
  22. package/dist/esm/constants.js.map +1 -1
  23. package/dist/esm/index.js +5 -3
  24. package/dist/esm/index.js.map +1 -1
  25. package/dist/esm/json-patch.js +98 -0
  26. package/dist/esm/json-patch.js.map +1 -0
  27. package/dist/esm/logger.js +46 -12
  28. package/dist/esm/logger.js.map +1 -1
  29. package/dist/esm/utils/date.js +32 -0
  30. package/dist/esm/utils/date.js.map +1 -0
  31. package/dist/esm/utils/json.js +280 -0
  32. package/dist/esm/utils/json.js.map +1 -0
  33. package/dist/esm/utils/set.js +23 -0
  34. package/dist/esm/utils/set.js.map +1 -0
  35. package/dist/esm/utils/string.js +55 -0
  36. package/dist/esm/utils/string.js.map +1 -0
  37. package/dist/types/canonicalization.d.ts +38 -29
  38. package/dist/types/canonicalization.d.ts.map +1 -1
  39. package/dist/types/constants.d.ts +6 -8
  40. package/dist/types/constants.d.ts.map +1 -1
  41. package/dist/types/index.d.ts +5 -3
  42. package/dist/types/index.d.ts.map +1 -1
  43. package/dist/types/interfaces.d.ts +2 -2
  44. package/dist/types/interfaces.d.ts.map +1 -1
  45. package/dist/types/json-patch.d.ts +47 -0
  46. package/dist/types/json-patch.d.ts.map +1 -0
  47. package/dist/types/logger.d.ts +31 -8
  48. package/dist/types/logger.d.ts.map +1 -1
  49. package/dist/types/types.d.ts +11 -4
  50. package/dist/types/types.d.ts.map +1 -1
  51. package/dist/types/utils/date.d.ts +19 -0
  52. package/dist/types/utils/date.d.ts.map +1 -0
  53. package/dist/types/utils/json.d.ts +89 -0
  54. package/dist/types/utils/json.d.ts.map +1 -0
  55. package/dist/types/utils/set.d.ts +14 -0
  56. package/dist/types/utils/set.d.ts.map +1 -0
  57. package/dist/types/utils/string.d.ts +39 -0
  58. package/dist/types/utils/string.d.ts.map +1 -0
  59. package/package.json +3 -4
  60. package/src/canonicalization.ts +75 -58
  61. package/src/constants.ts +19 -12
  62. package/src/index.ts +5 -5
  63. package/src/interfaces.ts +2 -2
  64. package/src/json-patch.ts +103 -0
  65. package/src/logger.ts +59 -27
  66. package/src/types.ts +11 -6
  67. package/src/utils/date.ts +32 -0
  68. package/src/utils/json.ts +315 -0
  69. package/src/utils/set.ts +23 -0
  70. package/src/utils/string.ts +59 -0
  71. package/dist/cjs/exts.js +0 -189
  72. package/dist/cjs/exts.js.map +0 -1
  73. package/dist/cjs/patch.js +0 -163
  74. package/dist/cjs/patch.js.map +0 -1
  75. package/dist/esm/exts.js +0 -189
  76. package/dist/esm/exts.js.map +0 -1
  77. package/dist/esm/patch.js +0 -163
  78. package/dist/esm/patch.js.map +0 -1
  79. package/dist/types/exts.d.ts +0 -90
  80. package/dist/types/exts.d.ts.map +0 -1
  81. package/dist/types/patch.d.ts +0 -63
  82. package/dist/types/patch.d.ts.map +0 -1
  83. package/src/exts.ts +0 -310
  84. package/src/patch.ts +0 -181
  85. package/src/rdf-canonize.d.ts +0 -6
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Utility class string-related operations.
3
+ * @name StringUtils
4
+ * @class StringUtils
5
+ */
6
+ export declare class StringUtils {
7
+ /**
8
+ * Escape special characters in a string for use in a regular expression.
9
+ * @param {string} value - The string to escape.
10
+ * @returns {string} The escaped string.
11
+ */
12
+ static escapeRegExp(value: string): string;
13
+ /**
14
+ * Convert a camelCase string to snake_case.
15
+ * @param {string} value - The camelCase string to convert.
16
+ * @returns {string} The converted snake_case string.
17
+ */
18
+ static toSnake(value: string): string;
19
+ /**
20
+ * Convert a string to SNAKE_SCREAMING_CASE.
21
+ * @param {string} value - The string to convert.
22
+ * @returns {string} The converted SNAKE_SCREAMING_CASE string.
23
+ */
24
+ static toSnakeScream(value: string): string;
25
+ /**
26
+ * Remove the last character from a string.
27
+ * @param {string} value - The string to chop.
28
+ * @returns {string} The chopped string.
29
+ */
30
+ static chop(value: string): string;
31
+ /**
32
+ * Replace the end of a string if it matches a given pattern.
33
+ * @param {string} value - The string to modify.
34
+ * @param {string | RegExp} pattern - The pattern to match at the end of the string.
35
+ * @param {string} [replacement=''] - The replacement string.
36
+ * @returns {string} The modified string.
37
+ */
38
+ static replaceEnd(value: string, pattern: string | RegExp, replacement?: string): string;
39
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"string.d.ts","sourceRoot":"","sources":["../../../src/utils/string.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,qBAAa,WAAW;IACtB;;;;OAIG;IACH,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAI1C;;;;OAIG;IACH,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAMrC;;;;OAIG;IACH,MAAM,CAAC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAI3C;;;;OAIG;IACH,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAIlC;;;;;;OAMG;IACH,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,WAAW,GAAE,MAAW,GAAG,MAAM;CAO7F"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@did-btcr2/common",
3
- "version": "2.2.2",
3
+ "version": "3.0.0",
4
4
  "type": "module",
5
5
  "description": "Common utilities, types, interfaces, etc. shared across the did-btcr2-js monorepo packages.",
6
6
  "main": "./dist/cjs/index.js",
@@ -46,11 +46,10 @@
46
46
  "dependencies": {
47
47
  "@noble/hashes": "^1.7.1",
48
48
  "@scure/bip32": "^1.5.0",
49
- "canonicalize": "^2.1.0",
50
49
  "chalk": "^5.4.1",
50
+ "fast-json-patch": "^3.1.1",
51
51
  "json-canonicalize": "^1.0.6",
52
- "multiformats": "^13.3.2",
53
- "rdf-canonize": "^4.0.1"
52
+ "multiformats": "^13.3.2"
54
53
  },
55
54
  "devDependencies": {
56
55
  "@eslint/js": "^9.21.0",
@@ -2,9 +2,8 @@ import { sha256 } from '@noble/hashes/sha2';
2
2
  import { bytesToHex } from '@noble/hashes/utils';
3
3
  import { canonicalize as jcsa } from 'json-canonicalize';
4
4
  import { base58btc } from 'multiformats/bases/base58';
5
- import rdf from 'rdf-canonize';
6
- import { CanonicalizationAlgorithm, HashBytes, JSONObject } from './types.js';
7
5
  import { CanonicalizationError } from './errors.js';
6
+ import { CanonicalizationAlgorithm, CanonicalizationEncoding, HashBytes } from './types.js';
8
7
 
9
8
  /**
10
9
  * Canonicalization class provides methods for canonicalizing JSON objects
@@ -14,39 +13,50 @@ import { CanonicalizationError } from './errors.js';
14
13
  * @type {Canonicalization}
15
14
  */
16
15
  export class Canonicalization {
17
- private _algorithm: CanonicalizationAlgorithm;
16
+ private readonly _defaultAlgorithm: CanonicalizationAlgorithm;
18
17
 
19
18
  /**
20
19
  * Initializes the Canonicalization class with the specified algorithm.
21
- * @param {CanonicalizationAlgorithm} algorithm The canonicalization algorithm to use ('jcs' or 'rdfc').
20
+ * @param {CanonicalizationAlgorithm} algorithm The canonicalization algorithm to use ('jcs').
22
21
  */
23
- // TODO: Need to move to using RDFC by default
24
22
  constructor(algorithm: CanonicalizationAlgorithm = 'jcs') {
25
- this._algorithm = algorithm;
23
+ this._defaultAlgorithm = Canonicalization.normalizeAlgorithm(algorithm);
26
24
  }
27
25
 
28
26
  /**
29
- * Sets the canonicalization algorithm.
30
- * @param {'jcs' | 'rdfc'} algorithm Either 'jcs' or 'rdfc'.
27
+ * Gets the canonicalization algorithm.
28
+ * @returns {CanonicalizationAlgorithm} The current canonicalization algorithm.
31
29
  */
32
- set algorithm(algorithm: 'jcs' | 'rdfc') {
33
- // Normalize the passed algorithm to lowercase
34
- algorithm = algorithm.toLowerCase() as CanonicalizationAlgorithm;
30
+ get algorithm(): CanonicalizationAlgorithm {
31
+ return this._defaultAlgorithm;
32
+ }
35
33
 
36
- // Validate the algorithm is either 'jcs' or 'rdfc'
37
- if(!['jcs', 'rdfc'].includes(algorithm)){
34
+ /**
35
+ * Normalizes the canonicalization algorithm.
36
+ * @param {CanonicalizationAlgorithm} algorithm
37
+ * @returns {CanonicalizationAlgorithm} The normalized algorithm.
38
+ * @throws {CanonicalizationError} If the algorithm is not supported.
39
+ */
40
+ static normalizeAlgorithm(algorithm: CanonicalizationAlgorithm): CanonicalizationAlgorithm {
41
+ const normalized = algorithm.toLowerCase() as CanonicalizationAlgorithm;
42
+ if (normalized !== 'jcs') {
38
43
  throw new CanonicalizationError(`Unsupported algorithm: ${algorithm}`, 'ALGORITHM_ERROR');
39
44
  }
40
- // Set the algorithm
41
- this._algorithm = algorithm;
45
+ return normalized;
42
46
  }
43
47
 
44
48
  /**
45
- * Gets the canonicalization algorithm.
46
- * @returns {CanonicalizationAlgorithm} The current canonicalization algorithm.
49
+ * Normalizes the canonicalization encoding.
50
+ * @param {CanonicalizationEncoding} encoding - The encoding to normalize.
51
+ * @returns {CanonicalizationEncoding} The normalized encoding.
52
+ * @throws {CanonicalizationError} If the encoding is not supported.
47
53
  */
48
- get algorithm(): CanonicalizationAlgorithm {
49
- return this._algorithm;
54
+ static normalizeEncoding(encoding: CanonicalizationEncoding): CanonicalizationEncoding {
55
+ const normalized = encoding.toLowerCase() as CanonicalizationEncoding;
56
+ if (normalized !== 'hex' && normalized !== 'base58') {
57
+ throw new CanonicalizationError(`Unsupported encoding: ${encoding}`, 'ENCODING_ERROR');
58
+ }
59
+ return normalized;
50
60
  }
51
61
 
52
62
  /**
@@ -56,50 +66,56 @@ export class Canonicalization {
56
66
  * Scheme. The function returns the canonicalizedBytes.
57
67
  *
58
68
  * Optionally encodes a sha256 hashed canonicalized JSON object.
59
- * Step 1 Canonicalize (JCS/RDFC) → Step 2 Hash (SHA256) → Step 3 Encode (Hex/Base58).
69
+ * Step 1 Canonicalize (JCS) → Step 2 Hash (SHA256) → Step 3 Encode (Hex/Base58).
60
70
  *
61
- * @param {JSONObject} object The object to process.
62
- * @param {string} encoding The encoding format ('hex' or 'base58').
71
+ * @param {Record<any, any>} object The object to process.
72
+ * @param {Object} [options] Options for processing.
73
+ * @param {CanonicalizationEncoding} [options.encoding='hex'] The encoding format ('hex' or 'base58').
74
+ * @param {CanonicalizationAlgorithm} [options.algorithm] The canonicalization algorithm to use.
63
75
  * @returns {Promise<string>} The final SHA-256 hash bytes as a hex string.
64
76
  */
65
- public async process(object: JSONObject, encoding: string = 'hex'): Promise<string> {
77
+ async process(object: Record<any, any>, options: {
78
+ encoding?: CanonicalizationEncoding;
79
+ algorithm?: CanonicalizationAlgorithm;
80
+ multibase?: boolean;
81
+ } = {}): Promise<string> {
82
+ const algorithm = Canonicalization.normalizeAlgorithm(options.algorithm ?? this._defaultAlgorithm);
83
+ const encoding = Canonicalization.normalizeEncoding(options.encoding ?? 'hex');
84
+
66
85
  // Step 1: Canonicalize
67
- const canonicalized = await this.canonicalize(object);
86
+ const canonicalized = await this.canonicalize(object, algorithm);
68
87
  // Step 2: Hash
69
88
  const hashed = this.hash(canonicalized);
70
89
  // Step 3: Encode
71
- const encoded = this.encode(hashed, encoding);
90
+ const encoded = this.encode(hashed, encoding, options.multibase ?? false);
72
91
  // Return the encoded string
73
92
  return encoded;
74
93
  }
75
94
 
76
95
  /**
77
- * Step 1: Uses this.algorithm to determine the method (JCS/RDFC).
78
- * @param {JSONObject} object The object to canonicalize.
96
+ * Step 1: Uses this.algorithm to determine the method (JCS).
97
+ * @param {Record<any, any>} object The object to canonicalize.
98
+ * @param {CanonicalizationAlgorithm} [algorithm] The algorithm to use.
79
99
  * @returns {Promise<string>} The canonicalized object.
80
100
  */
81
- public async canonicalize(object: JSONObject): Promise<string> {
82
- return await (this[this.algorithm] as (object: JSONObject) => any)(object);
101
+ async canonicalize(object: Record<any, any>, algorithm: CanonicalizationAlgorithm = this._defaultAlgorithm): Promise<string> {
102
+ switch (Canonicalization.normalizeAlgorithm(algorithm)) {
103
+ case 'jcs':
104
+ return this.jcs(object);
105
+ default:
106
+ throw new CanonicalizationError(`Unsupported algorithm: ${algorithm}`, 'ALGORITHM_ERROR');
107
+ }
83
108
  }
84
109
 
85
110
  /**
86
111
  * Step 1: Canonicalizes an object using JCS (JSON Canonicalization Scheme).
87
- * @param {JSONObject} object The object to canonicalize.
112
+ * @param {Record<any, any>} object The object to canonicalize.
88
113
  * @returns {string} The canonicalized object.
89
114
  */
90
- public jcs(object: JSONObject): any {
115
+ jcs(object: Record<any, any>): string {
91
116
  return jcsa(object);
92
117
  }
93
118
 
94
- /**
95
- * Step 1: Canonicalizes an object using RDF Canonicalization (RDFC).
96
- * @param {JSONObject} object The object to canonicalize.
97
- * @returns {Promise<string>} The canonicalized object.
98
- */
99
- public rdfc(object: JSONObject): Promise<string> {
100
- return rdf.canonize([object], { algorithm: 'RDFC-1.0' });
101
- }
102
-
103
119
  /**
104
120
  * Step 2: SHA-256 hashes a canonicalized object.
105
121
  * @param {string} canonicalized The canonicalized object.
@@ -112,19 +128,18 @@ export class Canonicalization {
112
128
  /**
113
129
  * Step 3: Encodes SHA-256 hashed, canonicalized object as a hex or base58 string.
114
130
  * @param {string} canonicalizedhash The canonicalized object to encode.
115
- * @param {string} encoding The encoding format ('hex' or 'base58').
131
+ * @param {CanonicalizationEncoding} encoding The encoding format ('hex' or 'base58').
116
132
  * @throws {CanonicalizationError} If the encoding format is not supported.
117
133
  * @returns {string} The encoded string.
118
134
  */
119
- public encode(canonicalizedhash: HashBytes, encoding: string = 'hex'): string {
120
- switch(encoding) {
121
- case 'hex':
122
- return this.hex(canonicalizedhash);
123
- case 'base58':
124
- return this.base58(canonicalizedhash);
125
- default:
126
- throw new CanonicalizationError(`Unsupported encoding: ${encoding}`, 'ENCODING_ERROR');
135
+ public encode(canonicalizedhash: HashBytes, encoding: CanonicalizationEncoding = 'hex', multibase: boolean = false): string {
136
+ const normalized = Canonicalization.normalizeEncoding(encoding);
137
+ if (normalized === 'hex') return this.hex(canonicalizedhash);
138
+ if (normalized === 'base58') {
139
+ const encoded = this.base58(canonicalizedhash);
140
+ return multibase ? `z${encoded}` : encoded;
127
141
  }
142
+ throw new CanonicalizationError(`Unsupported encoding: ${encoding}`, 'ENCODING_ERROR');
128
143
  }
129
144
 
130
145
  /**
@@ -142,17 +157,21 @@ export class Canonicalization {
142
157
  * @returns {string} The hash as a hex string.
143
158
  */
144
159
  public base58(hashBytes: HashBytes): string {
145
- return base58btc.encode(hashBytes);
160
+ const encoded = base58btc.encode(hashBytes);
161
+ return encoded.startsWith('z') ? encoded.slice(1) : encoded;
146
162
  }
147
163
 
148
164
  /**
149
165
  * Canonicalizes an object, hashes it and returns it as hash bytes.
150
166
  * Step 1-2: Canonicalize → Hash.
151
- * @param {JSONObject} object The object to process.
167
+ * @param {Record<any, any>} object The object to process.
152
168
  * @returns {Promise<HashBytes>} The final SHA-256 hash bytes.
153
169
  */
154
- public async canonicalhash(object: JSONObject): Promise<HashBytes> {
155
- const canonicalized = await this.canonicalize(object);
170
+ public async canonicalhash(
171
+ object: Record<any, any>,
172
+ algorithm: CanonicalizationAlgorithm = this._defaultAlgorithm
173
+ ): Promise<HashBytes> {
174
+ const canonicalized = await this.canonicalize(object, algorithm);
156
175
  return this.hash(canonicalized);
157
176
  }
158
177
 
@@ -163,7 +182,7 @@ export class Canonicalization {
163
182
  * @returns {string} The SHA-256 hash as a hex string.
164
183
  */
165
184
  public hashhex(canonicalized: string): string {
166
- return this.encode(this.hash(canonicalized));
185
+ return this.encode(this.hash(canonicalized), 'hex');
167
186
  }
168
187
 
169
188
  /**
@@ -172,9 +191,7 @@ export class Canonicalization {
172
191
  * @param {string} canonicalized The canonicalized object to hash.
173
192
  * @returns {string} The SHA-256 hash as a base58 string.
174
193
  */
175
- public hashb58(canonicalized: string): string {
176
- return this.encode(this.hash(canonicalized), 'base58');
194
+ public hashbase58(canonicalized: string): string {
195
+ return this.encode(this.hash(canonicalized), 'base58', false);
177
196
  }
178
197
  }
179
-
180
- export const canonicalization = new Canonicalization();
package/src/constants.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { sha256 } from '@noble/hashes/sha2';
2
+ import { bytesToHex } from '@noble/hashes/utils';
2
3
  import { Bytes, HashHex } from './types.js';
3
4
 
4
5
  export const ID_PLACEHOLDER_VALUE = 'did:btcr2:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
@@ -9,32 +10,38 @@ export const MULTIBASE_URI_PREFIX = 'urn:mb:';
9
10
  export const INITIAL_BLOCK_REWARD = 50;
10
11
  export const HALVING_INTERVAL = 150;
11
12
  export const COINBASE_MATURITY_DELAY = 100;
12
- export const POLAR_BOB_CLIENT_CONFIG = {
13
+ export const DEFAULT_POLAR_CONFIG = {
13
14
  username : 'polaruser',
14
15
  password : 'polarpass',
15
16
  host : 'http://127.0.0.1:18443',
16
17
  allowDefaultWallet : true,
17
18
  version : '28.1.0'
18
19
  };
19
- export const POLAR_ALICE_CLIENT_CONFIG = {
20
- username : 'polaruser',
21
- password : 'polarpass',
22
- host : 'http://127.0.0.1:18444',
23
- allowDefaultWallet : true,
24
- version : '28.1.0'
25
- };
26
20
  export const DEFAULT_REST_CONFIG = { host: 'http://localhost:3000' };
27
- export const DEFAULT_RPC_CONFIG = POLAR_BOB_CLIENT_CONFIG;
21
+ export const DEFAULT_RPC_CONFIG = DEFAULT_POLAR_CONFIG;
28
22
  export const DEFAULT_BLOCK_CONFIRMATIONS = 7;
29
23
 
24
+ /**
25
+ * Load a default RPC config, allowing environment overrides to avoid hard-coding credentials/hosts in bundles.
26
+ * @returns {typeof DEFAULT_POLAR_CONFIG} The RPC config.
27
+ */
28
+ export function getDefaultRpcConfig(): typeof DEFAULT_POLAR_CONFIG {
29
+ return {
30
+ ...DEFAULT_POLAR_CONFIG,
31
+ host : process.env.BTCR2_RPC_HOST ?? DEFAULT_POLAR_CONFIG.host,
32
+ username : process.env.BTCR2_RPC_USER ?? DEFAULT_POLAR_CONFIG.username,
33
+ password : process.env.BTCR2_RPC_PASS ?? DEFAULT_POLAR_CONFIG.password,
34
+ };
35
+ }
36
+
30
37
  // Fixed public key header bytes per the Data Integrity BIP340 Cryptosuite spec: [0xe7, 0x01] / [231, 1]
31
38
  export const BIP340_PUBLIC_KEY_MULTIBASE_PREFIX: Bytes = new Uint8Array([0xe7, 0x01]);
32
39
  // Hash of the BIP-340 Multikey prefix
33
- export const BIP340_PUBLIC_KEY_MULTIBASE_PREFIX_HASH: HashHex = Buffer.from(sha256(BIP340_PUBLIC_KEY_MULTIBASE_PREFIX)).toString('hex');
40
+ export const BIP340_PUBLIC_KEY_MULTIBASE_PREFIX_HASH: HashHex = bytesToHex(sha256(BIP340_PUBLIC_KEY_MULTIBASE_PREFIX));
34
41
  // Fixed secret key header bytes per the Data Integrity BIP340 Cryptosuite spec: [0x81, 0x26] / [129, 38]
35
42
  export const BIP340_SECRET_KEY_MULTIBASE_PREFIX: Bytes = new Uint8Array([0x81, 0x26]);
36
43
  // Hash of the BIP-340 Multikey prefix
37
- export const BIP340_SECRET_KEY_MULTIBASE_PREFIX_HASH: HashHex = Buffer.from(sha256(BIP340_SECRET_KEY_MULTIBASE_PREFIX)).toString('hex');
44
+ export const BIP340_SECRET_KEY_MULTIBASE_PREFIX_HASH: HashHex = bytesToHex(sha256(BIP340_SECRET_KEY_MULTIBASE_PREFIX));
38
45
  // curve's field size
39
46
  export const B256 = 2n ** 256n;
40
47
  // curve's field prime
@@ -118,4 +125,4 @@ export const BTCR2_DID_UPDATE_PAYLOAD_CONTEXT = [
118
125
  CONTEXT_URL_MAP.w3c.security.v2,
119
126
  CONTEXT_URL_MAP.w3c.zcap.v1,
120
127
  CONTEXT_URL_MAP.w3c.jsonldpatch.v1,
121
- ];
128
+ ];
package/src/index.ts CHANGED
@@ -1,11 +1,11 @@
1
- import './exts.js';
2
-
3
1
  export * from './canonicalization.js';
4
2
  export * from './constants.js';
5
3
  export * from './errors.js';
6
4
  export * from './interfaces.js';
5
+ export * from './json-patch.js';
7
6
  export * from './logger.js';
8
- export * from './patch.js';
9
7
  export * from './types.js';
10
-
11
- export * from './exts.js';
8
+ export * from './utils/date.js';
9
+ export * from './utils/json.js';
10
+ export * from './utils/set.js';
11
+ export * from './utils/string.js';
package/src/interfaces.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  export type JsonPatch = Array<PatchOperation>;
2
- export type PatchOpCode = 'add' | 'remove' | 'replace' | 'move' | 'copy' | 'test' | string;
2
+ export type PatchOpCode = 'add' | 'remove' | 'replace' | 'move' | 'copy' | 'test' | (string & {});
3
3
  /**
4
4
  * A JSON Patch operation, as defined in {@link https://datatracker.ietf.org/doc/html/rfc6902 | RFC 6902}.
5
5
  */
6
6
  export interface PatchOperation {
7
7
  op: PatchOpCode;
8
8
  path: string;
9
- value?: any; // Required for add, replace, test
9
+ value?: unknown; // Required for add, replace, test
10
10
  from?: string; // Required for move, copy
11
11
  }
12
12
 
@@ -0,0 +1,103 @@
1
+ import jsonPatch, { Operation } from 'fast-json-patch';
2
+ import { MethodError } from './errors.js';
3
+ import { PatchOperation } from './interfaces.js';
4
+ import { JSONObject } from './types.js';
5
+
6
+ const { applyPatch, compare, deepClone } = jsonPatch;
7
+
8
+ /**
9
+ * Thin wrapper around fast-json-patch to keep a stable API within this package.
10
+ * @class JSONPatch
11
+ * @type {JSONPatch}
12
+ */
13
+ export class JSONPatch {
14
+ /**
15
+ * Applies a JSON Patch to a source document and returns the patched document.
16
+ * Does not mutate the input document.
17
+ * @param {JSONObject} sourceDocument - The source JSON document to apply the patch to.
18
+ * @param {PatchOperation[]} operations - The JSON Patch operations to apply.
19
+ * @returns {JSONObject} The patched JSON document.
20
+ */
21
+ static apply(
22
+ sourceDocument: Record<any, any>,
23
+ operations: PatchOperation[],
24
+ options: { mutate?: boolean; clone?: (value: any) => any } = {}
25
+ ): Record<any, any> {
26
+ const mutate = options.mutate ?? false;
27
+ const cloneFn = options.clone ?? deepClone;
28
+ const docClone = mutate ? sourceDocument : cloneFn(sourceDocument);
29
+ const validationError = this.validateOperations(operations);
30
+ if (validationError) {
31
+ throw new MethodError('Invalid JSON Patch operations', 'JSON_PATCH_APPLY_ERROR', { error: validationError });
32
+ }
33
+ try {
34
+ const result = applyPatch(docClone, operations as Operation[], true, mutate);
35
+ if (result.newDocument === undefined) {
36
+ throw new MethodError('JSON Patch application failed', 'JSON_PATCH_APPLY_ERROR', { result });
37
+ }
38
+ return result.newDocument as JSONObject;
39
+ } catch (error) {
40
+ throw new MethodError('JSON Patch application failed', 'JSON_PATCH_APPLY_ERROR', { error });
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Compute a JSON Patch diff from source => target.
46
+ * @param {JSONObject} sourceDocument - The source JSON document.
47
+ * @param {JSONObject} targetDocument - The target JSON document.
48
+ * @param {string} [path] - An optional base path to prefix to each operation.
49
+ * @returns {PatchOperation[]} The computed JSON Patch operations.
50
+ */
51
+ static diff(sourceDocument: JSONObject, targetDocument: JSONObject, path: string = ''): PatchOperation[] {
52
+ const ops = compare(sourceDocument ?? {}, targetDocument ?? {}) as PatchOperation[];
53
+ if (!path) return ops;
54
+
55
+ const prefix = path.endsWith('/') ? path.slice(0, -1) : path;
56
+ return ops.map(op => ({
57
+ ...op,
58
+ path : this.joinPointer(prefix, op.path)
59
+ }));
60
+ }
61
+
62
+ /**
63
+ * Join a base pointer prefix with an operation path ensuring correct escaping.
64
+ * @param {string} prefix - The base pointer prefix.
65
+ * @param {string} opPath - The operation path.
66
+ * @returns {string} The joined pointer.
67
+ */
68
+ static joinPointer(prefix: string, opPath: string): string {
69
+ if (!prefix) return opPath;
70
+ const normalizedPrefix = prefix.startsWith('/') ? prefix : `/${prefix}`;
71
+ return `${this.escapeSegmentPath(normalizedPrefix)}${opPath}`;
72
+ }
73
+
74
+ /**
75
+ * Escape a JSON Pointer segment according to RFC 6901.
76
+ * @param {string} pointer - The JSON Pointer to escape.
77
+ * @returns {string} The escaped JSON Pointer.
78
+ */
79
+ static escapeSegmentPath(pointer: string): string {
80
+ return pointer
81
+ .split('/')
82
+ .map((segment, idx) => idx === 0 ? segment : segment.replace(/~/g, '~0').replace(/\//g, '~1'))
83
+ .join('/');
84
+ }
85
+
86
+ /**
87
+ * Validate JSON Patch operations.
88
+ * @param {PatchOperation[]} operations - The operations to validate.
89
+ * @returns {Error | null} An Error if validation fails, otherwise null.
90
+ */
91
+ static validateOperations(operations: PatchOperation[]): Error | null {
92
+ if (!Array.isArray(operations)) return new Error('Operations must be an array');
93
+ for (const op of operations) {
94
+ if (!op || typeof op !== 'object') return new Error('Operation must be an object');
95
+ if (typeof op.op !== 'string') return new Error('Operation.op must be a string');
96
+ if (typeof op.path !== 'string') return new Error('Operation.path must be a string');
97
+ if ((op.op === 'move' || op.op === 'copy') && typeof op.from !== 'string') {
98
+ return new Error(`Operation.from must be a string for op=${op.op}`);
99
+ }
100
+ }
101
+ return null;
102
+ }
103
+ }
package/src/logger.ts CHANGED
@@ -51,18 +51,35 @@ const LEVEL_METHODS: Record<Level, keyof Console> = {
51
51
  * - File/line tracing
52
52
  * - Timestamps
53
53
  * - Colorized output
54
+ * @class Logger
55
+ * @type {Logger}
54
56
  */
55
57
  export class Logger {
56
58
  private levels: Level[];
57
59
  private namespace?: string;
60
+ private useColors: boolean;
61
+ private static shared: Logger;
58
62
 
59
- constructor(namespace?: string) {
60
- this.levels = LOG_LEVELS[NODE_ENV] || [];
61
- this.namespace = namespace ?? 'did-btcr2-js';
63
+ /**
64
+ * Creates a new Logger instance.
65
+ * @param {string} namespace - Optional namespace for log messages.
66
+ * @param {Object} options - Configuration options.
67
+ * @param {Level[]} options.levels - Log levels to enable.
68
+ * @param {boolean} options.useColors - Whether to use colored output.
69
+ */
70
+ constructor(namespace?: string, options: { levels?: Level[]; useColors?: boolean } = {}) {
71
+ this.levels = options.levels || LOG_LEVELS[NODE_ENV] || [];
72
+ this.namespace = namespace || 'did-btcr2-js';
73
+ const envForce = process.env.LOG_COLORS;
74
+ this.useColors = options.useColors || (envForce ? envForce !== '0' && envForce.toLowerCase() !== 'false' : Boolean(process.stdout.isTTY));
62
75
  }
63
76
 
64
77
  /**
65
78
  * Logs a message with the specified level.
79
+ * @param {Level} level - The log level.
80
+ * @param {unknown} message - The message to log.
81
+ * @param {...unknown[]} args - Additional arguments to log.
82
+ * @returns {void}
66
83
  */
67
84
  private _log(level: Level, message?: unknown, ...args: unknown[]): void {
68
85
  if (!this.levels.includes(level)) return;
@@ -72,70 +89,85 @@ export class Logger {
72
89
 
73
90
  const timestamp = new Date().toISOString();
74
91
  const namespace = this.namespace ? `[${this.namespace}]` : '';
92
+ const render = this.useColors ? color : (v: string) => v;
93
+ const renderGray = this.useColors ? chalk.gray : (v: string) => v;
75
94
 
76
95
  (console[method] as (...args: any[]) => void)(
77
- `${chalk.gray(timestamp)} ${namespace} ${color(level)}: ${chalk.white(message)}`,
96
+ `${renderGray(timestamp)} ${namespace} ${render(level)}: ${message}`,
78
97
  ...args
79
98
  );
80
99
  }
81
100
 
82
- // 🔹 Instance-based logging methods
83
- public debug(message?: unknown, ...args: unknown[]) {
101
+ debug(message?: unknown, ...args: unknown[]): Logger {
84
102
  this._log('debug', message, ...args); return this;
85
103
  }
86
104
 
87
- public error(message?: unknown, ...args: unknown[]) {
105
+ error(message?: unknown, ...args: unknown[]): Logger {
88
106
  this._log('error', message, ...args); return this;
89
107
  }
90
108
 
91
- public info(message?: unknown, ...args: unknown[]) {
109
+ info(message?: unknown, ...args: unknown[]): Logger {
92
110
  this._log('info', message, ...args); return this;
93
111
  }
94
112
 
95
- public warn(message?: unknown, ...args: unknown[]) {
113
+ warn(message?: unknown, ...args: unknown[]): Logger {
96
114
  this._log('warn', message, ...args); return this;
97
115
  }
98
116
 
99
- public security(message?: unknown, ...args: unknown[]) {
117
+ security(message?: unknown, ...args: unknown[]): Logger {
100
118
  this._log('security', message, ...args); return this;
101
119
  }
102
120
 
103
- public log(message?: unknown, ...args: unknown[]) {
121
+ log(message?: unknown, ...args: unknown[]): Logger {
104
122
  this._log('log', message, ...args); return this;
105
123
  }
106
124
 
107
- public newline() {
125
+ newline(): Logger {
108
126
  console.log(); return this;
109
127
  }
110
128
 
111
129
  /**
112
130
  * Static methods for convenience (auto-instantiate).
131
+ * These use a shared singleton instance.
132
+ * @param {unknown} message - The message to log.
133
+ * @param {...unknown[]} args - Additional arguments to log.
134
+ * @returns {void}
113
135
  */
114
- public static debug(message?: unknown, ...args: unknown[]) {
115
- new Logger().debug(message, ...args);
136
+ static debug(message?: unknown, ...args: unknown[]): void {
137
+ Logger.instance().debug(message, ...args);
116
138
  }
117
139
 
118
- public static error(message?: unknown, ...args: unknown[]) {
119
- new Logger().error(message, ...args);
140
+ static error(message?: unknown, ...args: unknown[]): void {
141
+ Logger.instance().error(message, ...args);
120
142
  }
121
143
 
122
- public static info(message?: unknown, ...args: unknown[]) {
123
- new Logger().info(message, ...args);
144
+ static info(message?: unknown, ...args: unknown[]): void {
145
+ Logger.instance().info(message, ...args);
124
146
  }
125
147
 
126
- public static warn(message?: unknown, ...args: unknown[]) {
127
- new Logger().warn(message, ...args);
148
+ static warn(message?: unknown, ...args: unknown[]): void {
149
+ Logger.instance().warn(message, ...args);
128
150
  }
129
151
 
130
- public static security(message?: unknown, ...args: unknown[]) {
131
- new Logger().security(message, ...args);
152
+ static security(message?: unknown, ...args: unknown[]): void {
153
+ Logger.instance().security(message, ...args);
132
154
  }
133
155
 
134
- public static log(message?: unknown, ...args: unknown[]) {
135
- new Logger().log(message, ...args);
156
+ static log(message?: unknown, ...args: unknown[]): void {
157
+ Logger.instance().log(message, ...args);
136
158
  }
137
159
 
138
- public static newline() {
139
- new Logger().newline();
160
+ static newline() {
161
+ Logger.instance().newline();
140
162
  }
141
- }
163
+
164
+ private static instance(levels?: Level[], useColors?: boolean): Logger {
165
+ if (!Logger.shared) {
166
+ Logger.shared = new Logger(undefined, { levels, useColors });
167
+ } else {
168
+ if (levels) Logger.shared.levels = levels;
169
+ if (useColors !== undefined) Logger.shared.useColors = useColors;
170
+ }
171
+ return Logger.shared;
172
+ }
173
+ }