gdc-common-utils-ts 1.0.1

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 (100) hide show
  1. package/PUBLISHING.md +33 -0
  2. package/__tests__/AesManager.test.ts +53 -0
  3. package/__tests__/CryptographyService.test.ts +194 -0
  4. package/__tests__/bundle.test.ts +29 -0
  5. package/__tests__/content.test.ts +72 -0
  6. package/__tests__/crypto-encode-decode.test.ts +52 -0
  7. package/__tests__/crypto-hmac.test.ts +21 -0
  8. package/__tests__/did-generateServiceId.errors.test.ts +8 -0
  9. package/__tests__/did-generateServiceId.test.ts +18 -0
  10. package/__tests__/models-clinical-sections.test.ts +32 -0
  11. package/__tests__/models-multibase58.test.ts +33 -0
  12. package/__tests__/multibase58.errors.test.ts +7 -0
  13. package/__tests__/multibase58.test.ts +28 -0
  14. package/__tests__/multibasehash.test.ts +25 -0
  15. package/__tests__/utils-actor.test.ts +22 -0
  16. package/__tests__/utils-base-convert.test.ts +57 -0
  17. package/__tests__/utils-baseN.test.ts +40 -0
  18. package/__tests__/utils-did-extra.test.ts +33 -0
  19. package/__tests__/utils-format-converter.test.ts +87 -0
  20. package/__tests__/utils-jwt.test.ts +57 -0
  21. package/__tests__/utils-manager-error.test.ts +11 -0
  22. package/__tests__/utils-normalize.test.ts +15 -0
  23. package/__tests__/utils-object-convert.test.ts +38 -0
  24. package/__tests__/utils-string-convert.test.ts +20 -0
  25. package/__tests__/utils-string-utils.test.ts +25 -0
  26. package/__tests__/utils-url.test.ts +21 -0
  27. package/babel.config.cjs +5 -0
  28. package/jest.config.ts +46 -0
  29. package/package.json +36 -0
  30. package/src/AesManager.ts +82 -0
  31. package/src/CryptographyService.ts +461 -0
  32. package/src/JweManager.ts.txt +365 -0
  33. package/src/KmsService.txt +493 -0
  34. package/src/constants/Schemas.ts +61 -0
  35. package/src/constants/index.ts +1 -0
  36. package/src/constants/schemaorg.ts +193 -0
  37. package/src/cryptoDecode.ts +104 -0
  38. package/src/cryptoEncode.ts +36 -0
  39. package/src/cryptography.abstract.ts +29 -0
  40. package/src/hmac.ts +15 -0
  41. package/src/index.ts +3 -0
  42. package/src/interfaces/Cryptography.types.ts +131 -0
  43. package/src/interfaces/ICryptoHelper.ts +33 -0
  44. package/src/interfaces/ICryptography.ts +177 -0
  45. package/src/interfaces/IWallet.ts +62 -0
  46. package/src/interfaces/MlDsa.ts +25 -0
  47. package/src/interfaces/MlKem.ts +18 -0
  48. package/src/models/aes.ts +93 -0
  49. package/src/models/auth.ts +38 -0
  50. package/src/models/bundle.ts +152 -0
  51. package/src/models/bundle.txt +93 -0
  52. package/src/models/clinical-sections.en.ts +82 -0
  53. package/src/models/clinical-sections.ts +64 -0
  54. package/src/models/comm.ts +63 -0
  55. package/src/models/confidential-job.ts +100 -0
  56. package/src/models/confidential-message.ts +137 -0
  57. package/src/models/confidential-storage.ts +170 -0
  58. package/src/models/consent-rule.ts +141 -0
  59. package/src/models/crypto.ts +43 -0
  60. package/src/models/device-license.ts +161 -0
  61. package/src/models/did.ts +81 -0
  62. package/src/models/index.ts +31 -0
  63. package/src/models/indexing.ts +20 -0
  64. package/src/models/issue.ts +85 -0
  65. package/src/models/jsonapi.ts +19 -0
  66. package/src/models/jwe.ts +132 -0
  67. package/src/models/jwk.ts +50 -0
  68. package/src/models/jws.ts +42 -0
  69. package/src/models/jwt.ts +15 -0
  70. package/src/models/multibase58.ts +46 -0
  71. package/src/models/oidc4ida.common.model.ts +39 -0
  72. package/src/models/oidc4ida.document.model.ts +61 -0
  73. package/src/models/oidc4ida.electronicRecord.model.ts +86 -0
  74. package/src/models/oidc4ida.evidence.model.ts +69 -0
  75. package/src/models/openid-device.ts +146 -0
  76. package/src/models/operation-outcome.ts +34 -0
  77. package/src/models/params.ts +142 -0
  78. package/src/models/resource-document.ts +21 -0
  79. package/src/models/response.ts +5 -0
  80. package/src/models/urlPath.ts +76 -0
  81. package/src/models/verifiable-credential.ts +52 -0
  82. package/src/types/noble-hashes.d.ts +4 -0
  83. package/src/utils/actor.ts +52 -0
  84. package/src/utils/base-convert.ts +77 -0
  85. package/src/utils/baseN.ts +203 -0
  86. package/src/utils/bundle.ts +30 -0
  87. package/src/utils/content.ts +66 -0
  88. package/src/utils/did.ts +155 -0
  89. package/src/utils/format-converter.ts +119 -0
  90. package/src/utils/index.ts +13 -0
  91. package/src/utils/jwt.ts +165 -0
  92. package/src/utils/manager-error.ts +27 -0
  93. package/src/utils/multibase58.ts +46 -0
  94. package/src/utils/multibasehash.ts +28 -0
  95. package/src/utils/normalize.ts +43 -0
  96. package/src/utils/object-convert.ts +57 -0
  97. package/src/utils/string-convert.ts +71 -0
  98. package/src/utils/string-utils.ts +70 -0
  99. package/src/utils/url.ts +46 -0
  100. package/tsconfig.json +13 -0
@@ -0,0 +1,203 @@
1
+ /**
2
+ * From https://raw.githubusercontent.com/digitalbazaar/base58-universal/master/baseN.js
3
+ * Base-N/Base-X encoding/decoding functions.
4
+ *
5
+ * Original implementation from base-x:
6
+ * https://github.com/cryptocoinjs/base-x
7
+ *
8
+ * Which is MIT licensed:
9
+ *
10
+ * The MIT License (MIT)
11
+ *
12
+ * Copyright base-x contributors (c) 2016
13
+ *
14
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ * of this software and associated documentation files (the "Software"), to deal
16
+ * in the Software without restriction, including without limitation the rights
17
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ * copies of the Software, and to permit persons to whom the Software is
19
+ * furnished to do so, subject to the following conditions:
20
+ *
21
+ * The above copyright notice and this permission notice shall be included in
22
+ * all copies or substantial portions of the Software.
23
+ *
24
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
29
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
30
+ * DEALINGS IN THE SOFTWARE.
31
+ */
32
+ 'use strict';
33
+
34
+ import { bytesToStringUTF8, stringToBytesUTF8 } from './string-convert';
35
+
36
+ // baseN alphabet indexes
37
+ const _reverseAlphabets: any = [];
38
+
39
+ // base58 characters (Bitcoin alphabet)
40
+ export const alphabetBase58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
41
+
42
+ export class BaseN {
43
+ constructor() { }
44
+
45
+ /** BaseN-encodes a Uint8Array using the given alphabet */
46
+ public static encodeN(input: Uint8Array, alphabet: string, maxline: string | undefined): string {
47
+ return encodeN(input, alphabet, maxline);
48
+ }
49
+
50
+ /** Decodes a baseN-encoded (using the given alphabet) string to a Uint8Array. */
51
+ public static decodeN(input: string, alphabet: string): Uint8Array {
52
+ return decodeN(input, alphabet);
53
+ }
54
+
55
+ /** Base58-encodes a Uint8Array using the Bitcoin alphabet '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' */
56
+ public static encodeBytesToBase58(inputBytes: Uint8Array): string {
57
+ return encodeN(inputBytes, alphabetBase58, undefined);
58
+ }
59
+
60
+ /** Base58-encodes a Uint8Array using the Bitcoin alphabet '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' */
61
+ public static encodeStrToBase58(inputStr: string): string {
62
+ const inputBytes: Uint8Array = stringToBytesUTF8(inputStr);
63
+ return this.encodeBytesToBase58(inputBytes);
64
+ }
65
+
66
+ /** Decodes a base58-encoded string to a Uint8Array (using the Bitcoin alphabet '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz') */
67
+ public static decodeBytesFromBase58(base58Str: string): Uint8Array {
68
+ return decodeN(base58Str, alphabetBase58);
69
+ }
70
+
71
+ /** Decodes a base58-encoded string to a Uint8Array (using the Bitcoin alphabet '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz') */
72
+ public static decodeStrFromBase58(base58Str: string): string {
73
+ const inputBytes: Uint8Array = this.decodeBytesFromBase58(base58Str);
74
+ return bytesToStringUTF8(inputBytes);
75
+ }
76
+
77
+ // decodeToUtf8String = (utf8StringArg: string): string => decodeToUtf8String(utf8StringArg)
78
+
79
+ }
80
+
81
+
82
+ /**
83
+ * BaseN-encodes a Uint8Array using the given alphabet.
84
+ *
85
+ * @param {Uint8Array} input the bytes to encode in a Uint8Array.
86
+ * @param {number} maxline the maximum number of encoded characters per line to
87
+ * use, defaults to none.
88
+ *
89
+ * @return {string} the baseN-encoded output string.
90
+ */
91
+ export function encodeN(input: Uint8Array, alphabet: string, maxline: string | undefined): string {
92
+ if (!(input instanceof Uint8Array)) {
93
+ throw new TypeError('"input" must be a Uint8Array.');
94
+ }
95
+ if (typeof alphabet !== 'string') {
96
+ throw new TypeError('"alphabet" must be a string.');
97
+ }
98
+ if (maxline !== undefined && typeof maxline !== 'number') {
99
+ throw new TypeError('"maxline" must be a number.');
100
+ }
101
+ if (input.length === 0) {
102
+ return '';
103
+ }
104
+
105
+ let output: string = '';
106
+
107
+ let i = 0;
108
+ const base = alphabet.length;
109
+ const first = alphabet.charAt(0);
110
+ const digits = [0];
111
+ for (i = 0; i < input.length; ++i) {
112
+ let carry = input[i];
113
+ for (let j = 0; j < digits.length; ++j) {
114
+ carry += digits[j] << 8;
115
+ digits[j] = carry % base;
116
+ carry = (carry / base) | 0;
117
+ }
118
+
119
+ while (carry > 0) {
120
+ digits.push(carry % base);
121
+ carry = (carry / base) | 0;
122
+ }
123
+ }
124
+
125
+ // deal with leading zeros
126
+ for (i = 0; input[i] === 0 && i < input.length - 1; ++i) {
127
+ output += first;
128
+ }
129
+ // convert digits to a string
130
+ for (i = digits.length - 1; i >= 0; --i) {
131
+ output += alphabet[digits[i]];
132
+ }
133
+
134
+ if (maxline) {
135
+ const match = output.match(new RegExp('.{1,' + maxline + '}', 'g'));
136
+ if (match) {
137
+ output = match.join('\r\n');
138
+ }
139
+ }
140
+
141
+ return output;
142
+ }
143
+
144
+ /**
145
+ * Decodes a baseN-encoded (using the given alphabet) string to a
146
+ * Uint8Array.
147
+ *
148
+ * @param {string} input the baseN-encoded input string.
149
+ *
150
+ * @return {Uint8Array} the decoded bytes in a Uint8Array.
151
+ */
152
+ export function decodeN(input: string, alphabet: string): Uint8Array {
153
+ if (typeof input !== 'string') {
154
+ throw new TypeError('"input" must be a string.');
155
+ }
156
+ if (typeof alphabet !== 'string') {
157
+ throw new TypeError('"alphabet" must be a string.');
158
+ }
159
+ if (input.length === 0) {
160
+ return new Uint8Array(0);
161
+ }
162
+
163
+ let table = _reverseAlphabets[alphabet];
164
+ if (!table) {
165
+ // compute reverse alphabet
166
+ table = _reverseAlphabets[alphabet] = [];
167
+ for (let i = 0; i < alphabet.length; ++i) {
168
+ table[alphabet.charCodeAt(i)] = i;
169
+ }
170
+ }
171
+
172
+ // remove whitespace characters
173
+ input = input.replace(/\s/g, '');
174
+
175
+ const base = alphabet.length;
176
+ const first = alphabet.charAt(0);
177
+ const bytes = [0];
178
+ for (let i = 0; i < input.length; i++) {
179
+ const value = table[input.charCodeAt(i)];
180
+ if (value === undefined) {
181
+ return new Uint8Array(0); // empty
182
+ }
183
+
184
+ let carry = value;
185
+ for (let j = 0; j < bytes.length; ++j) {
186
+ carry += bytes[j] * base;
187
+ bytes[j] = carry & 0xff;
188
+ carry >>= 8;
189
+ }
190
+
191
+ while (carry > 0) {
192
+ bytes.push(carry & 0xff);
193
+ carry >>= 8;
194
+ }
195
+ }
196
+
197
+ // deal with leading zeros
198
+ for (let k = 0; input[k] === first && k < input.length - 1; ++k) {
199
+ bytes.push(0);
200
+ }
201
+
202
+ return new Uint8Array(bytes.reverse());
203
+ }
@@ -0,0 +1,30 @@
1
+ // Copyright 2025 Antifraud Services Inc. under the Apache License, Version 2.0.
2
+
3
+ /**
4
+ * Normalizes either FHIR Bundle (entry[]) or JSON:API bundle (data[])
5
+ * into an array of resources.
6
+ */
7
+ export function extractResources(bundle: any): any[] {
8
+ if (!bundle) return [];
9
+ if (Array.isArray(bundle.entry)) {
10
+ return bundle.entry
11
+ .map((e: any) => e && (e.resource || e))
12
+ .filter(Boolean);
13
+ }
14
+ if (Array.isArray(bundle.data)) {
15
+ return bundle.data
16
+ .map((d: any) => d && (d.resource || d))
17
+ .filter(Boolean);
18
+ }
19
+ if (bundle.resourceType) return [bundle];
20
+ return [];
21
+ }
22
+
23
+ export function getNextLink(bundle: any): string | null {
24
+ if (Array.isArray(bundle?.link)) {
25
+ const next = bundle.link.find((l: any) => l.relation === 'next');
26
+ if (next?.url) return next.url;
27
+ }
28
+ if (bundle?.links?.next) return bundle.links.next;
29
+ return null;
30
+ }
@@ -0,0 +1,66 @@
1
+ // crypto-ts/utils/convert.ts
2
+
3
+ import * as baseConvert from './base-convert';
4
+ import * as objectConvert from './object-convert';
5
+ import * as stringConvert from './string-convert';
6
+ import * as stringUtils from './string-utils';
7
+
8
+ /**
9
+ * A unified facade class for all data conversion and utility functions.
10
+ * This class encapsulates functionality from the various utility modules
11
+ * (base-convert, object-convert, etc.) into a single, consistent interface.
12
+ */
13
+ export class Content {
14
+ // --- String Conversions (from string-convert.ts) ---
15
+ /** Encodes a string into a Uint8Array of strictly-validated UTF-8 bytes. */
16
+ static stringToBytesUTF8 = stringConvert.stringToBytesUTF8;
17
+ /** Decodes a Uint8Array of strictly-validated UTF-8 bytes back into a string. */
18
+ static bytesToStringUTF8 = stringConvert.bytesToStringUTF8;
19
+ /** Decodes a Uint8Array containing binary/ASCII data into a string. */
20
+ static bytesToStringASCII = stringConvert.bytesToStringASCII;
21
+
22
+ // --- String Utilities (from string-utils.ts) ---
23
+ /** Capitalizes the first letter of a string. */
24
+ static capitalize = stringUtils.capitalize;
25
+ /** A simple string sanitizer. */
26
+ static sanitizeString = stringUtils.sanitizeString;
27
+ /** Converts a string to a string of its ASCII character codes. */
28
+ static stringToASCII = stringUtils.stringToASCII;
29
+ /** Converts a string to an array of numbers representing its byte values. */
30
+ static stringToBytesArrayOfNumbers = stringUtils.stringToBytesArrayOfNumbers;
31
+
32
+ // --- Base Conversions (from base-convert.ts) ---
33
+ /** Converts a Uint8Array to a hexadecimal string. */
34
+ static bytesToHexString = baseConvert.bytesToHexString;
35
+ /** Encodes a Uint8Array into a Base58 string. */
36
+ static bytesToBase58 = baseConvert.bytesToBase58;
37
+ /** Decodes a Base58 string into a Uint8Array. */
38
+ static base58ToBytes = baseConvert.base58ToBytes;
39
+ /** Encodes a string into a standard Base64 string (with padding). */
40
+ static stringToStdBase64 = baseConvert.stringToStdBase64;
41
+ /** Converts a standard Base64 string to a Base64URL string. */
42
+ static base64ToBase64Url = baseConvert.base64ToBase64Url;
43
+ /** Encodes a string into a Base64URL string. */
44
+ static stringToBase64Url = baseConvert.stringToBase64Url;
45
+ /** Converts a Base64URL string to a standard Base64 string. */
46
+ static base64UrlToBase64 = baseConvert.base64UrlToBase64;
47
+ /** Decodes a string that is either Base64 or Base64URL into a Uint8Array. */
48
+ static base64ToBytes = baseConvert.base64OrUrlSafeToBytes;
49
+ /** Encodes a Uint8Array into a standard Base64 string (with padding). */
50
+ static bytesToBase64 = baseConvert.bytesToBase64;
51
+ /** Encodes a Uint8Array into a raw Base64URL string (no padding). */
52
+ static bytesToRawBase64UrlSafe = baseConvert.bytesToRawBase64UrlSafe;
53
+
54
+ // --- Object & Array Conversions (from object-convert.ts) ---
55
+ /** Compares two arrays and returns true if they are the same, false otherwise. */
56
+ static arrayCompare = objectConvert.arrayCompare;
57
+ /** Merges two Uint8Arrays into a single Uint8Array. */
58
+ static arrayMerge = objectConvert.arrayMerge;
59
+ /** Serializes a JavaScript object to a Uint8Array of UTF-8 bytes. */
60
+ static objectToBytes = objectConvert.objectToBytes;
61
+ /** Serializes a JavaScript object to a raw Base64URL string. */
62
+ static objectToRawBase64UrlSafe = objectConvert.objectToRawBase64UrlSafe;
63
+ /** Deserializes a Base64URL string back into a JavaScript object. */
64
+ static base64UrlSafeToJSON = objectConvert.base64UrlSafeToJSON;
65
+ }
66
+
@@ -0,0 +1,155 @@
1
+ // crypto-ts/utils/did.ts
2
+ // Copyright 2025 Antifraud Services Inc. under the Apache License, Version 2.0.
3
+
4
+ import { ServiceEndpointSelector } from "../models/did";
5
+
6
+ /**
7
+ * Generates a DID Service ID fragment from a selector object.
8
+ * The format is always `#<section>:<format>:<resourceType>:<action>`.
9
+ *
10
+ * This utility's only job is to correctly assemble the string. It contains no
11
+ * conditional logic about DID types, as that is handled by the data layer.
12
+ *
13
+ * @param selector The structured object containing the endpoint parts.
14
+ * @returns The formatted service ID fragment string.
15
+ */
16
+ export function generateServiceId(selector: ServiceEndpointSelector): string {
17
+ // Canonical casing:
18
+ // - `section`, `format`, `resourceType` are matched case-insensitively by the backend router,
19
+ // and lowercasing them makes DID Document lookup deterministic across SDKs.
20
+ // - `action` is kept as-is (future actions may be case-sensitive identifiers).
21
+ const parts = [
22
+ selector.section?.toLowerCase(),
23
+ selector.format?.toLowerCase(),
24
+ selector.resourceType?.toLowerCase(),
25
+ selector.action,
26
+ ];
27
+
28
+ return `#${parts.filter(p => p).join(':')}`;
29
+ }
30
+
31
+ /**
32
+ * Normalizes a did:web string to a canonical format to prevent resolution errors
33
+ * due to case sensitivity in the path component of the underlying URL.
34
+ *
35
+ * The rule is:
36
+ * - All segments are lowercased, EXCEPT the final segment.
37
+ * - If the final segment represents a `system|code` pair (e.g., for a role),
38
+ * the `system` part is lowercased, but the `code` is preserved as-is.
39
+ * - If the final segment is a unique identifier (like a Tax ID), it is preserved as-is.
40
+ *
41
+ * @param did The did:web string to normalize.
42
+ * @returns The canonicalized did:web string.
43
+ */
44
+ export function normalizeDidWeb(did: string): string {
45
+ const parts = did.split(':');
46
+ if (parts[0] !== 'did' || parts[1] !== 'web' || parts.length < 3) {
47
+ // Not a valid did:web, return as is.
48
+ return did;
49
+ }
50
+
51
+ // Lowercase all parts except the very last one.
52
+ const lowercasedParts = parts.slice(0, -1).map(part => part.toLowerCase());
53
+ let lastPart = parts[parts.length - 1];
54
+
55
+ // Special handling for the last part if it contains a role descriptor.
56
+ if (lastPart.includes('|')) {
57
+ const [system, ...codeParts] = lastPart.split('|');
58
+ const code = codeParts.join('|'); // Re-join in case the code itself has a pipe.
59
+ lastPart = `${system.toLowerCase()}|${code}`;
60
+ }
61
+
62
+ return [...lowercasedParts, lastPart].join(':');
63
+ }
64
+
65
+ /**
66
+ * Encodes a hostname according to did:web spec (percent-encodes port colons).
67
+ * @param apiUrl The full base URL of the API.
68
+ * @returns The percent-encoded hostname.
69
+ */
70
+ function getEncodedHost(apiUrl: string): string {
71
+ try {
72
+ const parsedUrl = new URL(apiUrl);
73
+ return parsedUrl.host.replace(':', '%3A');
74
+ } catch (e) {
75
+ console.error(`[getEncodedHost] Invalid apiUrl provided: ${apiUrl}`);
76
+ return 'localhost'; // Fallback
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Creates the deterministic "hosted" did:web for a tenant (Organization).
82
+ *
83
+ * @param hostDidWeb The DID of the host (e.g., 'did:web:host.example.com').
84
+ * @param tenantAlternateName The alternate name of the tenant (e.g., 'acme').
85
+ * @param context An object containing jurisdiction, version, and sector.
86
+ * @returns The tenant's full, correctly formatted hosted did:web.
87
+ * Example: 'did:web:host.example.com:acme:cds-es:v1:health-care'
88
+ */
89
+ export function createHostedDidWeb(
90
+ hostDidWeb: string,
91
+ tenantAlternateName: string,
92
+ context: { jurisdiction: string; version: string; sector: string }
93
+ ): string {
94
+ const hostPart = hostDidWeb.replace(/^did:web:/, '');
95
+ // The path in a did:web uses colons as separators.
96
+ const didPath = `cds-${context.jurisdiction}:${context.version}:${context.sector}`;
97
+ return `did:web:${hostPart}:${tenantAlternateName}:${didPath}`;
98
+ }
99
+
100
+
101
+ /**
102
+ * Builds the canonical details (URL and did:web URN) for a hosted DID.
103
+ * This is the single source of truth for constructing hosted identifiers in the app.
104
+ *
105
+ * @param options An object with the components of the DID.
106
+ * @param options.host The provider's domain (e.g., 'provider.com').
107
+ * @param options.alternateName The tenant's identifier (e.g., 'acme').
108
+ * @param options.jurisdiction The legal jurisdiction (defaults to 'ES').
109
+ * @param options.sector The business sector (defaults to 'health-care').
110
+ * @returns An object with the full URL (with trailing /) and the canonical did:web.
111
+ */
112
+ export function buildHostedDidDetails({
113
+ host,
114
+ alternateName,
115
+ jurisdiction = 'ES',
116
+ sector = 'health-care',
117
+ }: {
118
+ host: string;
119
+ alternateName: string;
120
+ jurisdiction?: string;
121
+ sector?: string;
122
+ }) {
123
+ // 1. Build the path part of the DID/URL.
124
+ const pathPart = `${alternateName}/cds-${jurisdiction}/v1/${sector}`;
125
+
126
+ // 2. Build the full URL, ensuring a trailing slash.
127
+ const url = `https://${host}/${pathPart}/`;
128
+
129
+ // 3. Build the canonical did:web, replacing / with :.
130
+ const hostAndPath = `${host}/${pathPart}`;
131
+ const did = `did:web:${hostAndPath.replace(/\//g, ':')}`;
132
+
133
+ return { did, url };
134
+ }
135
+
136
+ /**
137
+ * Converts a did:web identifier into a full HTTPS or HTTP base URL with a trailing slash.
138
+ * It correctly decodes percent-encoded ports for local development.
139
+ * @param did The did:web string (e.g., 'did:web:example.com' or 'did:web:localhost%3A3000:acme:cds-es:v1:health-care').
140
+ * @returns The full base URL (e.g., 'https://example.com/acme/cds-es/v1/health-care/').
141
+ */
142
+ export function getBaseUrlFromDidWeb(did: string): string {
143
+ const didParts = did.replace(/^did:web:/, '').split(':');
144
+ const domainPart = didParts[0];
145
+ const pathParts = didParts.slice(1);
146
+
147
+ const decodedDomain = decodeURIComponent(domainPart);
148
+ const protocol = decodedDomain.startsWith('localhost') ? 'http' : 'https';
149
+
150
+ const path = pathParts.join('/').replace(/cds-(es|us|gb)/, (match, p1) => `cds-${p1.toUpperCase()}`);
151
+
152
+ // Ensure a trailing slash for the base URL, without double slashes when no path is present.
153
+ const normalizedPath = path ? `${path}/` : '';
154
+ return `${protocol}://${decodedDomain}/${normalizedPath}`;
155
+ }
@@ -0,0 +1,119 @@
1
+ // crypto-ts/utils/format-converter.ts
2
+ // Copyright 2025 Antifraud Services Inc. under the Apache License, Version 2.0.
3
+
4
+ import { safelyJoinUrl } from "./url";
5
+
6
+ /**
7
+ * Normalizes a FHIR resource or Bundle into an array of entries.
8
+ */
9
+ export const convertResourceDataToArrayOfDataEntries = (inputData: any, requestPath: string, webDomain: string): any[] => {
10
+ if (inputData.resourceType) { // It is FHIR data
11
+ if (inputData.resourceType === 'Bundle') {
12
+ return inputData.entry || [];
13
+ } else {
14
+ const resourceIdentifier = inputData.id || (inputData.identifier && inputData.identifier[0]?.value) || "";
15
+ const fullUrl = safelyJoinUrl(webDomain, safelyJoinUrl(requestPath, resourceIdentifier));
16
+ return [{ fullUrl, resource: inputData }];
17
+ }
18
+ } else { // Assume it's already a JSON:API-like object
19
+ const resourceIdentifier = inputData.id || "";
20
+ // const fullUrl = safelyJoinUrl(webDomain, safelyJoinUrl(requestPath, resourceIdentifier));
21
+ return [inputData];
22
+ }
23
+ };
24
+
25
+ /**
26
+ * Converts a resource or a FHIR Bundle to a JSON:API Primary Document.
27
+ */
28
+ export const convertResourceOrBundleToPrimaryDoc = (resourceData: any, specification: string, webDomain: string, requestPath: string): any => {
29
+ const entries = convertResourceDataToArrayOfDataEntries(resourceData, requestPath, webDomain);
30
+
31
+ // Handle FHIR entry structure vs. JSON:API data structure
32
+ const data = entries.map(entry => {
33
+ if (entry.resource) { // It's a FHIR entry
34
+ return {
35
+ type: `${specification}.${entry.resource.resourceType}`,
36
+ id: entry.resource.id,
37
+ attributes: { ...entry.resource }
38
+ };
39
+ }
40
+ return entry; // Assumes it's already a JSON:API resource object
41
+ });
42
+
43
+ return { data };
44
+ };
45
+
46
+ /**
47
+ * Converts a JSON:API Primary Document back to a FHIR Bundle.
48
+ */
49
+ export const convertPrimaryDocToBundleFHIR = (primaryDocument: any, bundleType: string): any => {
50
+ const entries: any[] = [];
51
+ if (primaryDocument.data) {
52
+ entries.push(...primaryDocument.data.map((jsonApiResourceObject: any) => ({
53
+ // fullUrl might need to be reconstructed based on context
54
+ resource: jsonApiResourceObject.attributes || jsonApiResourceObject
55
+ })));
56
+ }
57
+ if (primaryDocument.errors) {
58
+ entries.push(...primaryDocument.errors.map((errorObject: any) => ({
59
+ resource: {
60
+ resourceType: 'OperationOutcome',
61
+ id: errorObject.id,
62
+ issue: [{
63
+ code: errorObject.status,
64
+ severity: 'error',
65
+ details: { text: errorObject.detail },
66
+ }]
67
+ }
68
+ })));
69
+ }
70
+
71
+ return {
72
+ entry: entries,
73
+ resourceType: 'Bundle',
74
+ total: primaryDocument.data ? primaryDocument.data.length : 0,
75
+ type: bundleType
76
+ };
77
+ };
78
+
79
+
80
+ /**
81
+ * Converts a FHIR Bundle containing one or more OperationOutcome entries into a
82
+ * compliant JSON:API Error Document. Each error entry in the bundle is mapped
83
+ * to a corresponding error object in the JSON:API `errors` array.
84
+ *
85
+ * @param errorBundle The FHIR Bundle representing the error(s).
86
+ * @returns A JSON:API document with a top-level `errors` array.
87
+ */
88
+ export const convertFhirErrorBundleToJsonApiError = (errorBundle: any): any => {
89
+ if (!errorBundle.entry || errorBundle.entry.length === 0) {
90
+ return {
91
+ errors: [{
92
+ status: '500',
93
+ title: 'Unknown Error',
94
+ detail: 'An unspecified error occurred and the error bundle was malformed or empty.'
95
+ }]
96
+ };
97
+ }
98
+
99
+ const errorObjects = errorBundle.entry.map((entry: any) => {
100
+ const resource = entry.resource || {};
101
+ const issue = resource.issue ? resource.issue[0] : {};
102
+ const title = issue.details?.text || 'Processing Error';
103
+
104
+ return {
105
+ id: resource.id,
106
+ status: entry.response?.status?.toString() || '500',
107
+ code: issue.code || 'processing-error',
108
+ title: title,
109
+ detail: issue.diagnostics || 'An error occurred while processing the request.',
110
+ meta: {
111
+ severity: issue.severity || 'error'
112
+ }
113
+ };
114
+ });
115
+
116
+ return {
117
+ errors: errorObjects
118
+ };
119
+ };
@@ -0,0 +1,13 @@
1
+ export * from './actor';
2
+ export * from './base-convert';
3
+ export * from './baseN';
4
+ export * from './bundle';
5
+ export * from './content';
6
+ export * from './did';
7
+ export * from './format-converter';
8
+ export * from './jwt';
9
+ export * from './manager-error';
10
+ export * from './multibase58';
11
+ export * from './multibasehash';
12
+ export * from './normalize';
13
+ export * from './object-convert';