@did-btcr2/common 1.1.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 (75) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +3 -0
  3. package/dist/cjs/canonicalization.js +163 -0
  4. package/dist/cjs/canonicalization.js.map +1 -0
  5. package/dist/cjs/constants.js +116 -0
  6. package/dist/cjs/constants.js.map +1 -0
  7. package/dist/cjs/errors.js +151 -0
  8. package/dist/cjs/errors.js.map +1 -0
  9. package/dist/cjs/exts.js +182 -0
  10. package/dist/cjs/exts.js.map +1 -0
  11. package/dist/cjs/index.js +10 -0
  12. package/dist/cjs/index.js.map +1 -0
  13. package/dist/cjs/interfaces.js +2 -0
  14. package/dist/cjs/interfaces.js.map +1 -0
  15. package/dist/cjs/logger.browser.js +77 -0
  16. package/dist/cjs/logger.browser.js.map +1 -0
  17. package/dist/cjs/logger.js +139 -0
  18. package/dist/cjs/logger.js.map +1 -0
  19. package/dist/cjs/package.json +1 -0
  20. package/dist/cjs/patch.js +163 -0
  21. package/dist/cjs/patch.js.map +1 -0
  22. package/dist/cjs/types.js +20 -0
  23. package/dist/cjs/types.js.map +1 -0
  24. package/dist/esm/canonicalization.js +163 -0
  25. package/dist/esm/canonicalization.js.map +1 -0
  26. package/dist/esm/constants.js +116 -0
  27. package/dist/esm/constants.js.map +1 -0
  28. package/dist/esm/errors.js +151 -0
  29. package/dist/esm/errors.js.map +1 -0
  30. package/dist/esm/exts.js +182 -0
  31. package/dist/esm/exts.js.map +1 -0
  32. package/dist/esm/index.js +10 -0
  33. package/dist/esm/index.js.map +1 -0
  34. package/dist/esm/interfaces.js +2 -0
  35. package/dist/esm/interfaces.js.map +1 -0
  36. package/dist/esm/logger.browser.js +77 -0
  37. package/dist/esm/logger.browser.js.map +1 -0
  38. package/dist/esm/logger.js +139 -0
  39. package/dist/esm/logger.js.map +1 -0
  40. package/dist/esm/patch.js +163 -0
  41. package/dist/esm/patch.js.map +1 -0
  42. package/dist/esm/types.js +20 -0
  43. package/dist/esm/types.js.map +1 -0
  44. package/dist/types/canonicalization.d.ts +106 -0
  45. package/dist/types/canonicalization.d.ts.map +1 -0
  46. package/dist/types/constants.d.ts +103 -0
  47. package/dist/types/constants.d.ts.map +1 -0
  48. package/dist/types/errors.d.ts +113 -0
  49. package/dist/types/errors.d.ts.map +1 -0
  50. package/dist/types/exts.d.ts +82 -0
  51. package/dist/types/exts.d.ts.map +1 -0
  52. package/dist/types/index.d.ts +9 -0
  53. package/dist/types/index.d.ts.map +1 -0
  54. package/dist/types/interfaces.d.ts +282 -0
  55. package/dist/types/interfaces.d.ts.map +1 -0
  56. package/dist/types/logger.browser.d.ts +28 -0
  57. package/dist/types/logger.browser.d.ts.map +1 -0
  58. package/dist/types/logger.d.ts +45 -0
  59. package/dist/types/logger.d.ts.map +1 -0
  60. package/dist/types/patch.d.ts +63 -0
  61. package/dist/types/patch.d.ts.map +1 -0
  62. package/dist/types/types.d.ts +104 -0
  63. package/dist/types/types.d.ts.map +1 -0
  64. package/package.json +109 -0
  65. package/src/canonicalization.ts +180 -0
  66. package/src/constants.ts +123 -0
  67. package/src/errors.ts +224 -0
  68. package/src/exts.ts +293 -0
  69. package/src/index.ts +11 -0
  70. package/src/interfaces.ts +311 -0
  71. package/src/logger.browser.ts +92 -0
  72. package/src/logger.ts +162 -0
  73. package/src/patch.ts +181 -0
  74. package/src/rdf-canonize.d.ts +6 -0
  75. package/src/types.ts +113 -0
package/src/logger.ts ADDED
@@ -0,0 +1,162 @@
1
+ import chalk, { ChalkInstance } from 'chalk';
2
+ import path from 'path';
3
+
4
+ export enum Env {
5
+ Development = 'development',
6
+ Production = 'production',
7
+ Test = 'test',
8
+ }
9
+
10
+ export type Level = 'debug' | 'error' | 'info' | 'log' | 'warn' | 'security';
11
+
12
+ export const NODE_ENV = (process.env.NODE_ENV as Env) || Env.Development;
13
+
14
+ /**
15
+ * Mapping of log levels to colors for cleaner, more flexible logging.
16
+ */
17
+ const LOG_LEVELS: Record<Env, Level[]> = {
18
+ development : ['debug', 'info', 'log', 'warn', 'security'],
19
+ test : ['log', 'info', 'error'],
20
+ production : ['error'],
21
+ };
22
+
23
+ /**
24
+ * Colors associated with each log level.
25
+ */
26
+ const LEVEL_STYLES: Record<Level, ChalkInstance> = {
27
+ debug : chalk.green,
28
+ error : chalk.red,
29
+ info : chalk.blue,
30
+ log : chalk.gray,
31
+ warn : chalk.yellow,
32
+ security : chalk.magenta,
33
+ };
34
+
35
+ /**
36
+ * Defines the method mapping for console methods.
37
+ */
38
+ // eslint-disable-next-line no-undef
39
+ const LEVEL_METHODS: Record<Level, keyof Console> = {
40
+ debug : 'debug',
41
+ error : 'error',
42
+ info : 'info',
43
+ log : 'log',
44
+ warn : 'warn',
45
+ security : 'warn',
46
+ };
47
+
48
+ /**
49
+ * A flexible, feature-rich logger with:
50
+ * - Environment-based filtering
51
+ * - Namespacing
52
+ * - File/line tracing
53
+ * - Timestamps
54
+ * - Colorized output
55
+ */
56
+ export class Logger {
57
+ private levels: Level[];
58
+ private namespace?: string;
59
+
60
+ constructor(namespace?: string) {
61
+ this.levels = LOG_LEVELS[NODE_ENV] || [];
62
+ this.namespace = namespace ?? 'did-btcr2-js';
63
+ }
64
+
65
+ /**
66
+ * Logs a message with the specified level.
67
+ */
68
+ private _log(level: Level, message?: unknown, ...args: unknown[]): void {
69
+ if (!this.levels.includes(level)) return;
70
+
71
+ const color = LEVEL_STYLES[level];
72
+ const method = LEVEL_METHODS[level];
73
+
74
+ const timestamp = new Date().toISOString();
75
+ const namespace = this.namespace ? `[${this.namespace}]` : '';
76
+
77
+ (console[method] as (...args: any[]) => void)(
78
+ `${chalk.gray(timestamp)} ${namespace} ${color(level)}: ${chalk.white(message)}`,
79
+ ...args
80
+ );
81
+ }
82
+
83
+ // 🔹 Instance-based logging methods
84
+ public debug(message?: unknown, ...args: unknown[]) {
85
+ this._log('debug', message, ...args); return this;
86
+ }
87
+
88
+ public error(message?: unknown, ...args: unknown[]) {
89
+ this._log('error', message, ...args); return this;
90
+ }
91
+
92
+ public info(message?: unknown, ...args: unknown[]) {
93
+ this._log('info', message, ...args); return this;
94
+ }
95
+
96
+ public warn(message?: unknown, ...args: unknown[]) {
97
+ this._log('warn', message, ...args); return this;
98
+ }
99
+
100
+ public security(message?: unknown, ...args: unknown[]) {
101
+ this._log('security', message, ...args); return this;
102
+ }
103
+
104
+ public log(message?: unknown, ...args: unknown[]) {
105
+ this._log('log', message, ...args); return this;
106
+ }
107
+
108
+ public newline() {
109
+ console.log(); return this;
110
+ }
111
+
112
+ /**
113
+ * Static methods for convenience (auto-instantiate).
114
+ */
115
+ public static debug(message?: unknown, ...args: unknown[]) {
116
+ new Logger().debug(message, ...args);
117
+ }
118
+
119
+ public static error(message?: unknown, ...args: unknown[]) {
120
+ new Logger().error(message, ...args);
121
+ }
122
+
123
+ public static info(message?: unknown, ...args: unknown[]) {
124
+ new Logger().info(message, ...args);
125
+ }
126
+
127
+ public static warn(message?: unknown, ...args: unknown[]) {
128
+ new Logger().warn(message, ...args);
129
+ }
130
+
131
+ public static security(message?: unknown, ...args: unknown[]) {
132
+ new Logger().security(message, ...args);
133
+ }
134
+
135
+ public static log(message?: unknown, ...args: unknown[]) {
136
+ new Logger().log(message, ...args);
137
+ }
138
+
139
+ public static newline() {
140
+ new Logger().newline();
141
+ }
142
+
143
+ /**
144
+ * Returns the caller's file and line number.
145
+ */
146
+ private static getCallerLocation(): string {
147
+ const stack = new Error().stack?.split('\n');
148
+ if (!stack) return '';
149
+
150
+ // The first lines are irrelevant, we want the caller of the logger
151
+ const callerLine = stack[3] || stack[2] || '';
152
+
153
+ // Extract file path and line number
154
+ const match = callerLine.match(/\((.*):(\d+):(\d+)\)/);
155
+ if (!match) return '';
156
+
157
+ const filePath = match[1];
158
+ const lineNumber = match[2];
159
+
160
+ return `${path.basename(filePath)}:${lineNumber}`;
161
+ }
162
+ }
package/src/patch.ts ADDED
@@ -0,0 +1,181 @@
1
+ import { PatchOperation } from './interfaces.js';
2
+ import { JSONObject } from './types.js';
3
+ import { Btc1Error } from './errors.js';
4
+
5
+ /**
6
+ * Implementation of {@link https://datatracker.ietf.org/doc/html/rfc6902 | IETF RFC 6902 JSON Patch}.
7
+ *
8
+ * JavaScript Object Notation (JSON) Patch defines a JSON document structure for expressing a sequence of operations to
9
+ * apply to a JavaScript Object Notation (JSON) document; it is suitable for use with the HTTP PATCH method. The
10
+ * "application/json-patch+json" media type is used to identify such patch documents.
11
+ *
12
+ * @class Patch
13
+ * @type {Patch}
14
+ */
15
+ export class Patch {
16
+ /**
17
+ * Applies a JSON Patch to a source document and returns the patched document.
18
+ * @param {JSONObject} sourceDocument The source document to patch.
19
+ * @param {PatchOperation[]} operations The JSON Patch operations to apply.
20
+ * @returns {JSONObject} The patched document.
21
+ * @throws {Error} If an unsupported operation is provided.
22
+ */
23
+ public apply(sourceDocument: JSONObject, operations: PatchOperation[]): JSONObject {
24
+ const patchedDocument = JSON.normalize(sourceDocument);
25
+
26
+ for (const operation of operations) {
27
+ const { op, path, value, from } = operation;
28
+
29
+ const segments = path.split('/').slice(1);
30
+
31
+ switch (op) {
32
+ case 'add':
33
+ this.setValue(patchedDocument, segments, value);
34
+ break;
35
+
36
+ case 'remove':
37
+ this.removeValue(patchedDocument, segments);
38
+ break;
39
+
40
+ case 'replace':
41
+ this.setValue(patchedDocument, segments, value);
42
+ break;
43
+
44
+ case 'move':{
45
+ if (!from) throw new Error('Missing \'from\' in move operation');
46
+ const fromSegments = from.split('/').slice(1);
47
+ const movedValue = this.getValue(patchedDocument, fromSegments);
48
+ this.removeValue(patchedDocument, fromSegments);
49
+ this.setValue(patchedDocument, segments, movedValue);
50
+ break;
51
+ }
52
+ case 'copy':{
53
+ if (!from) throw new Error('Missing \'from\' in copy operation');
54
+ const copiedValue = this.getValue(patchedDocument, from.split('/').slice(1));
55
+ this.setValue(patchedDocument, segments, copiedValue);
56
+ break;
57
+
58
+ }
59
+ case 'test':{
60
+ const existingValue = this.getValue(patchedDocument, segments);
61
+ if (JSON.stringify(existingValue) !== JSON.stringify(value)) {
62
+ throw new Btc1Error(`Test operation failed at path`, 'JSON_PATCH_APPLY_ERROR', { path });
63
+ }
64
+ break;
65
+ }
66
+ default:
67
+ throw new Btc1Error(`Unsupported JSON Patch operation`, 'JSON_PATCH_APPLY_ERROR', { op });
68
+ }
69
+ }
70
+
71
+ return patchedDocument;
72
+ }
73
+
74
+ /**
75
+ * Constructs a JSON Patch with a single operation (e.g. add service).
76
+ * @param {PatchOperation} patches - The patch operation to create.
77
+ * @param {string} patch.op - The patch operation type (e.g. 'add').
78
+ * @param {string} patch.path - The JSON Pointer path to apply the operation.
79
+ * @param {*} patch.value - The value to apply (if applicable).
80
+ * @returns {PatchOperation[]} A single-entry JSON Patch array.
81
+ */
82
+ public create(patches: PatchOperation[]): PatchOperation[] {
83
+ return patches.map(({ op, path, value, from }) => {
84
+ const operation: PatchOperation = { op, path };
85
+
86
+ if (value !== undefined) {
87
+ operation.value = value;
88
+ }
89
+
90
+ if (from !== undefined) {
91
+ operation.from = from;
92
+ }
93
+
94
+ return operation;
95
+ });
96
+ }
97
+
98
+ /**
99
+ * Find the diff between a source and target document constructing the patch operations from source => target.
100
+ * @param {JSONObject} sourceDocument The original JSON object.
101
+ * @param {JSONObject} targetDocument The target JSON object to transform into.
102
+ * @returns {PatchOperation[]} An array of JSON Patch operations.
103
+ */
104
+ public diff(sourceDocument: JSONObject, targetDocument: JSONObject, path: string): PatchOperation[] {
105
+ const operations: Array<PatchOperation> = [];
106
+ const sourceKeys = new Set(Object.keys(sourceDocument || {}));
107
+ const targetKeys = new Set(Object.keys(targetDocument || {}));
108
+
109
+ // Handle removed keys
110
+ for (const key of sourceKeys) {
111
+ if (!targetKeys.has(key)) {
112
+ operations.push({ op: 'remove', path: `${path}/${key}` });
113
+ }
114
+ }
115
+
116
+ // Handle added or updated keys
117
+ for (const key of targetKeys) {
118
+ const sourceVal = sourceDocument?.[key];
119
+ const targetVal = targetDocument?.[key];
120
+ const currentPath = `${path}/${key}`;
121
+
122
+ if (!(key in (sourceDocument || {}))) {
123
+ operations.push({ op: 'add', path: currentPath, value: targetVal });
124
+ } else if (typeof sourceVal === 'object' && sourceVal !== null && typeof targetVal === 'object' && targetVal !== null) {
125
+ operations.push(...this.diff(sourceVal, targetVal, currentPath));
126
+ } else if (JSON.stringify(sourceVal) !== JSON.stringify(targetVal)) {
127
+ operations.push({ op: 'replace', path: currentPath, value: targetVal });
128
+ }
129
+ }
130
+
131
+ return operations;
132
+ }
133
+
134
+ /**
135
+ * Gets the value at a given path in an object.
136
+ * @private
137
+ * @param {*} obj The object to get the value from.
138
+ * @param {string[]} path The path to the value.
139
+ * @returns {*} The value at the given path.
140
+ */
141
+ private getValue(obj: any, path: string[]): any {
142
+ return path.reduce((acc, key) => (acc && acc[key] !== undefined ? acc[key] : undefined), obj);
143
+ }
144
+
145
+
146
+ /**
147
+ * Sets the value at a given path in an object.
148
+ * @private
149
+ * @param {*} obj The object to set the value in.
150
+ * @param {string[]} path The path to the value.
151
+ * @param {*} value The value to set.
152
+ * @returns {*} The object with the value set.
153
+ */
154
+ private setValue(obj: any, path: string[], value: any): void {
155
+ let current = obj;
156
+ for (let i = 0; i < path.length - 1; i++) {
157
+ const key = path[i];
158
+ if (!(key in current)) current[key] = {};
159
+ current = current[key];
160
+ }
161
+ current[path[path.length - 1]] = value;
162
+ }
163
+
164
+
165
+ /**
166
+ * Removes the value at a given path in an object.
167
+ * @private
168
+ * @param {*} obj The object to remove the value from.
169
+ * @param {string[]} path The path to the value.
170
+ * @returns {*} The object with the value removed.
171
+ */
172
+ private removeValue(obj: any, path: string[]): void {
173
+ let current = obj;
174
+ for (let i = 0; i < path.length - 1; i++) {
175
+ const key = path[i];
176
+ if (!(key in current)) return;
177
+ current = current[key];
178
+ }
179
+ delete current[path[path.length - 1]];
180
+ }
181
+ }
@@ -0,0 +1,6 @@
1
+ // rdf-canonize.d.ts
2
+ declare module 'rdf-canonize' {
3
+ const rdfc: any;
4
+ export default rdfc;
5
+ }
6
+
package/src/types.ts ADDED
@@ -0,0 +1,113 @@
1
+ import { HDKey } from '@scure/bip32';
2
+
3
+ /* Crypto Types */
4
+ export type Hex = Uint8Array | string;
5
+ export type SignatureHex = Hex;
6
+ export type HashHex = Hex;
7
+
8
+ export type Bytes = Uint8Array;
9
+ export type DocumentBytes = Bytes;
10
+ export type SignatureBytes = Bytes;
11
+ export type ProofBytes = Bytes;
12
+ export type HashBytes = Bytes;
13
+ export type MessageBytes = Bytes;
14
+ export type Entropy = Bytes | bigint;
15
+
16
+ export type CompressedPublicKeyParityByte = 0x02 | 0x03;
17
+ export type Bip340Encoding = string;
18
+ export type Base58BtcPrefix = 'z';
19
+
20
+ export type KeyBytes = Bytes;
21
+ export type Point = {
22
+ x: Array<number>;
23
+ y: Array<number>;
24
+ parity: number;
25
+ }
26
+ export type PublicKeyObject = {
27
+ point: Point;
28
+ hex: Hex;
29
+ multibase: MultibaseObject;
30
+ };
31
+ export type SecretKeyObject = {
32
+ bytes: Array<number>;
33
+ seed?: string;
34
+ hex?: Hex;
35
+ };
36
+ export type SchnorrKeyPair = {
37
+ secretKey: KeyBytes;
38
+ publicKey: KeyBytes;
39
+ };
40
+ export type SchnorrKeyPairObject = {
41
+ secretKey: SecretKeyObject;
42
+ publicKey: PublicKeyObject;
43
+ };
44
+ export type MultibaseObject = {
45
+ address: string;
46
+ prefix: Bytes;
47
+ key: Array<number>;
48
+ };
49
+ export type HdWallet = {
50
+ mnemonic: string;
51
+ hdkey: HDKey
52
+ };
53
+
54
+ /* DID Types */
55
+ export type DID = 'did';
56
+ export type MethodName = string;
57
+ export type MethodSpecificId = string;
58
+ export type DecentralizedIdentifierExplicit = `${DID}:${MethodName}:${MethodSpecificId}`;
59
+ export type DecentralizedIdentifier = string;
60
+ export type Btc1MethodName = 'btcr2';
61
+ export type Btc1DeterministicPrefix = 'k';
62
+ export type Btc1ExternalPrefix = 'x';
63
+ export type Btc1Prefix = `${Btc1DeterministicPrefix | Btc1ExternalPrefix}1`;
64
+ export type Bech32Id = string;
65
+ export type Btc1Id = `${Btc1Prefix}${Bech32Id}`
66
+ export type Btc1IdentifierExplicit = `${DID}:${Btc1MethodName}:${Btc1Id}`;
67
+ export type Btc1Identifier = string;
68
+ export type Controller = Btc1Identifier;
69
+ export type Id = 'initialKey';
70
+ export type FullId = `${Controller}#${Id}`;
71
+ export enum Btc1IdentifierTypes {
72
+ KEY = 'KEY',
73
+ EXTERNAL = 'EXTERNAL'
74
+ }
75
+ export enum Btc1IdentifierHrp {
76
+ k = 'k',
77
+ x = 'x'
78
+ }
79
+ export enum BitcoinNetworkNames {
80
+ bitcoin = 0,
81
+ signet = 1,
82
+ regtest = 2,
83
+ testnet3 = 3,
84
+ testnet4 = 4,
85
+ mutinynet = 5
86
+ }
87
+ export type KeyIdentifier = string;
88
+ export type BeaconUri = string;
89
+ export type DidPlaceholder = 'did:btcr2:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
90
+ export type CanonicalizedProofConfig = string;
91
+ export type CryptosuiteName = 'bip340-jcs-2025' | 'bip340-rdfc-2025';
92
+ export type ContextObject = Record<string | number | symbol, any>;
93
+ export type Context = string | string[] | ContextObject | ContextObject[]
94
+
95
+ /* General Types */
96
+ export type Maybe<T> = T | any;
97
+ export type JSONObject = Record<string | number | symbol, any>; // JSON object: prototyped or unprototyped
98
+ export type Prototyped = JSONObject;
99
+ export type Unprototyped = JSONObject;
100
+ export type TwoDigits = `${number}${number}`;
101
+ export type ThreeDigits = `${number}${number}${number}`;
102
+ export type Year = `${1 | 2}${ThreeDigits}`;
103
+ export type Month = TwoDigits;
104
+ export type Day = TwoDigits;
105
+ export type Hours = TwoDigits;
106
+ export type Minutes = TwoDigits;
107
+ export type Seconds = TwoDigits;
108
+ export type UtcTimestamp = `${Year}-${Month}-${Day}T${Hours}:${Minutes}:${Seconds}`;
109
+ export type TzOffset = `${Hours}:${Minutes}`;
110
+ export type DateTimestamp = `${UtcTimestamp}Z` | `${UtcTimestamp}-${TzOffset}`;
111
+ export type CanonicalizableObject = Record<string, any>;
112
+ export type CanonicalizationAlgorithm = 'jcs' | 'rdfc';
113
+ export type UnixTimestamp = number;