gdc-common-utils-ts 2.9.1 → 2.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -458,6 +458,13 @@ The canonical API contract should live in JSDoc on exported code. The README act
458
458
  - Uses explicit `seedMaterial` for deterministic derivation. Without `seedMaterial`, it defaults to random generation. `mode = deterministic` requires `seedMaterial`.
459
459
  - [`buildOrganizationDidWeb(...)`, `buildProfessionalDidWeb(...)`, `buildIndividualDidWeb(...)`, `buildIndividualMemberDidWebFromPrivateIdentifiers(...)`](src/utils/did.ts)
460
460
  - Build canonical data-space `did:web` identifiers for hosted organizations, professionals, and individuals/family actors.
461
+ - [`buildOrganizationAuthorizationUrnCds(...)`, `buildOrganizationMemberAuthorizationUrnCds(...)`](src/utils/organization-authorization-urn.ts)
462
+ - Build host-independent legal organization/member identities such as
463
+ `urn:cds-es:v1:organization:tax:ES-B00112233` and its
464
+ `:member:<multibase-id>:<role-value>` child.
465
+ - Jurisdiction is mandatory so `BN`, `EIN`, `TAX` and subnational identifier
466
+ schemes cannot collide; `roleType` remains protected while only the compact
467
+ `roleValue` is serialized.
461
468
  - [`buildSmartCompositionReadScope(...)`](src/utils/smart-scope.ts)
462
469
  - Builds the current CORE GW pinned SMART root scope for `organization/Composition...` token requests.
463
470
  - This is the preferred first scope to teach when the backend only needs subject-scoped read access.
@@ -159,7 +159,7 @@ export declare const EXAMPLE_VERIFY_RESPONSE_ORG_CREDENTIAL: Readonly<{
159
159
  id: "did:web:globaldatacare.es:onehealth:organization:taxid:VATES-B00112233";
160
160
  '@type': "Organization";
161
161
  legalName: "ACME Health Provider";
162
- taxID: "VATES-B00112233";
162
+ taxID: "ES-B00112233";
163
163
  sameAs: "did:web:provider.example.org";
164
164
  url: "health-care.provider.example.org";
165
165
  alternateName: string;
@@ -210,7 +210,7 @@ export declare const EXAMPLE_VERIFY_RESPONSE_PERSON_CREDENTIAL: Readonly<{
210
210
  memberOf: {
211
211
  '@type': "Organization";
212
212
  legalName: "ACME Health Provider";
213
- taxID: "VATES-B00112233";
213
+ taxID: "ES-B00112233";
214
214
  };
215
215
  alternateName: "controller-es384-001";
216
216
  additionalType: "ES384";
@@ -240,7 +240,7 @@ export declare const EXAMPLE_VERIFY_RESPONSE_CONTROLLER_CREDENTIAL: Readonly<{
240
240
  provider: {
241
241
  '@type': "Organization";
242
242
  legalName: "ACME Health Provider";
243
- taxID: "VATES-B00112233";
243
+ taxID: "ES-B00112233";
244
244
  };
245
245
  owner: {
246
246
  '@type': "Person";
@@ -144,7 +144,10 @@ export declare const EXAMPLE_HOST_PUBLIC_HOSTNAME: "host.example.com";
144
144
  export declare const EXAMPLE_HOST_PUBLIC_DID: "did:web:host.example.com";
145
145
  export declare const EXAMPLE_PERSON_CARD_URI: "urn:example:card:person:subject-001";
146
146
  export declare const EXAMPLE_ANIMAL_CARD_URI: "urn:example:card:animal:subject-002";
147
- export declare const EXAMPLE_PROVIDER_TAX_ID: "VATES-B00112233";
147
+ export declare const EXAMPLE_PROVIDER_TAX_ID: "ES-B00112233";
148
+ /** Jurisdiction-scoped synthetic legal identifier used by stable CDS URNs. */
149
+ export declare const EXAMPLE_PROVIDER_LEGAL_IDENTIFIER_VALUE_CDS: "ES-B00112233";
150
+ export declare const EXAMPLE_PROVIDER_ORGANIZATION_AUTHORIZATION_URN_CDS: string;
148
151
  export declare const EXAMPLE_PROVIDER_DOMAIN: "health-care.provider.example.org";
149
152
  /** Synthetic public portal domain used to derive organization path DIDs. */
150
153
  export declare const EXAMPLE_PUBLIC_PORTAL_DOMAIN: "portal.example.org";
@@ -216,6 +219,7 @@ export declare const EXAMPLE_HEALTHCARE_ACTOR_ROLE_RECEPTIONIST: "ISCO-08|4226";
216
219
  export declare const EXAMPLE_HEALTHCARE_ROLE_PHYSICIAN_TEXT: "physician";
217
220
  export declare const EXAMPLE_INDIVIDUAL_CONTROLLER_ROLE_TYPE = "http://terminology.hl7.org/CodeSystem/v3-RoleCode";
218
221
  export declare const EXAMPLE_INDIVIDUAL_CONTROLLER_ROLE_VALUE: "RESPRSN";
222
+ export declare const EXAMPLE_PROFESSIONAL_AUTHORIZATION_URN_CDS: string;
219
223
  export declare const EXAMPLE_RELATED_PERSON_ROLE: "v3-RoleCode|RESPRSN";
220
224
  export declare const EXAMPLE_SECURE_ID_VALUE_INDIVIDUAL: string;
221
225
  export declare const EXAMPLE_SECURE_ID_VALUE_INDIVIDUAL_CONTROLLER: string;
@@ -8,12 +8,13 @@ import { ResourceTypesFhirR4 } from '../constants/fhir-resource-types.js';
8
8
  import { BirthSex, GenderIdentity } from '../constants/identity-gender.js';
9
9
  import { IdKind, SecureIdTypesIndividual } from '../constants/identity-identifiers.js';
10
10
  import { HL7_CODING_SYSTEM_V3_ROLE_CODE } from '../constants/hl7-roles.js';
11
- import { HealthcareActorRoles, HealthcareBasicSections, HealthcareConsentPurposes, HealthcareDocumentTypes, } from '../constants/healthcare.js';
11
+ import { HealthcareActorRoles, HealthcareBasicSections, HealthcareConsentPurposes, HealthcareDocumentTypes, ISCO08_CODING_SYSTEM, } from '../constants/healthcare.js';
12
12
  import { CommunicationCategoryCodes } from '../constants/communication.js';
13
13
  import { LOINC_SYSTEM_URL } from '../models/clinical-sections.js';
14
14
  import { CommunicationClaim } from '../models/interoperable-claims/communication-claims.js';
15
15
  import { buildHostedProviderDidWeb, buildIndividualDidWeb, buildIndividualMemberDidWeb, buildSecureIdValueIndividual, buildSecureIdValueMember, buildProfessionalDidWeb, buildProviderSectorDidWeb, } from '../utils/did.js';
16
16
  import { encodeHexToMultibase58btc } from '../utils/multibase58.js';
17
+ import { buildOrganizationAuthorizationUrnCds, buildOrganizationMemberAuthorizationUrnCds, } from '../utils/organization-authorization-urn.js';
17
18
  import { MedicationStatementClaim, MedicationStatementClaimsFhirApiExtended, } from '../models/interoperable-claims/medication-statement-claims.js';
18
19
  import { medicationStatementFlatToFhirR4 } from '../utils/clinical-resource-converters.js';
19
20
  /**
@@ -163,7 +164,15 @@ export const EXAMPLE_HOST_PUBLIC_HOSTNAME = 'host.example.com';
163
164
  export const EXAMPLE_HOST_PUBLIC_DID = `did:web:${EXAMPLE_HOST_PUBLIC_HOSTNAME}`;
164
165
  export const EXAMPLE_PERSON_CARD_URI = 'urn:example:card:person:subject-001';
165
166
  export const EXAMPLE_ANIMAL_CARD_URI = 'urn:example:card:animal:subject-002';
166
- export const EXAMPLE_PROVIDER_TAX_ID = 'VATES-B00112233';
167
+ export const EXAMPLE_PROVIDER_TAX_ID = 'ES-B00112233';
168
+ /** Jurisdiction-scoped synthetic legal identifier used by stable CDS URNs. */
169
+ export const EXAMPLE_PROVIDER_LEGAL_IDENTIFIER_VALUE_CDS = 'ES-B00112233';
170
+ export const EXAMPLE_PROVIDER_ORGANIZATION_AUTHORIZATION_URN_CDS = buildOrganizationAuthorizationUrnCds({
171
+ jurisdiction: EXAMPLE_JURISDICTION,
172
+ version: EXAMPLE_ROUTE_VERSION,
173
+ identifierType: 'TAX',
174
+ identifierValue: EXAMPLE_PROVIDER_LEGAL_IDENTIFIER_VALUE_CDS,
175
+ });
167
176
  export const EXAMPLE_PROVIDER_DOMAIN = 'health-care.provider.example.org';
168
177
  /** Synthetic public portal domain used to derive organization path DIDs. */
169
178
  export const EXAMPLE_PUBLIC_PORTAL_DOMAIN = 'portal.example.org';
@@ -256,6 +265,15 @@ export const EXAMPLE_HEALTHCARE_ACTOR_ROLE_RECEPTIONIST = 'ISCO-08|4226';
256
265
  export const EXAMPLE_HEALTHCARE_ROLE_PHYSICIAN_TEXT = 'physician';
257
266
  export const EXAMPLE_INDIVIDUAL_CONTROLLER_ROLE_TYPE = HL7_CODING_SYSTEM_V3_ROLE_CODE;
258
267
  export const EXAMPLE_INDIVIDUAL_CONTROLLER_ROLE_VALUE = EXAMPLE_ORGANIZATION_CONTROLLER_ROLE;
268
+ export const EXAMPLE_PROFESSIONAL_AUTHORIZATION_URN_CDS = buildOrganizationMemberAuthorizationUrnCds({
269
+ organizationUrn: EXAMPLE_PROVIDER_ORGANIZATION_AUTHORIZATION_URN_CDS,
270
+ memberId: buildSecureIdValueMember({
271
+ secureIdTypeMember: SecureIdTypesIndividual.Email,
272
+ privateIdValueMember: EXAMPLE_EMAIL_PROFESSIONAL,
273
+ }),
274
+ roleType: ISCO08_CODING_SYSTEM,
275
+ roleValue: HealthcareActorRoles.GeneralistMedicalPractitioner,
276
+ });
259
277
  export const EXAMPLE_RELATED_PERSON_ROLE = 'v3-RoleCode|RESPRSN';
260
278
  export const EXAMPLE_SECURE_ID_VALUE_INDIVIDUAL = buildSecureIdValueIndividual({
261
279
  secureIdTypeIndividual: SecureIdTypesIndividual.Uuid,
@@ -2,6 +2,7 @@
2
2
  import { getHealthcareRoleByClaim } from '../constants/healthcare.js';
3
3
  import { isStableActorIdentifier } from './actor-identifier.js';
4
4
  import { normalizeUuid } from './normalize-uuid.js';
5
+ import { HL7_RELATED_PERSON_FUNCTIONAL_ROLES } from '../constants/hl7-roles.js';
5
6
  import { CompositionAttesterModes, } from '../models/interoperable-claims/composition-claims.js';
6
7
  export const FhirIpsCreatorKinds = Object.freeze({
7
8
  IndividualSubject: 'individual-subject',
@@ -29,9 +30,7 @@ export function buildFhirIpsCreatorProvenance(input) {
29
30
  const defaultAuthor = input.kind === FhirIpsCreatorKinds.Professional
30
31
  ? input.organizationReference
31
32
  : input.subjectReference;
32
- const requestedAuthor = input.compositionAuthorReference === creator.authorReference
33
- ? defaultAuthor
34
- : input.compositionAuthorReference;
33
+ const requestedAuthor = input.compositionAuthorReference;
35
34
  const mode = input.attesterMode || (input.kind === FhirIpsCreatorKinds.Professional
36
35
  ? CompositionAttesterModes.Professional
37
36
  : CompositionAttesterModes.Personal);
@@ -171,12 +170,22 @@ function requireReference(value, label) {
171
170
  return normalized;
172
171
  }
173
172
  function requireRoleCoding(roleClaim) {
174
- const descriptor = getHealthcareRoleByClaim(String(roleClaim || '').trim());
175
- if (!descriptor)
176
- throw new TypeError('role must be a canonical governed healthcare role.');
173
+ const normalizedRole = String(roleClaim || '').trim();
174
+ const descriptor = getHealthcareRoleByClaim(normalizedRole);
175
+ if (descriptor) {
176
+ return {
177
+ system: descriptor.codingSystem,
178
+ code: descriptor.code,
179
+ ...(descriptor.titleEn ? { display: descriptor.titleEn } : {}),
180
+ };
181
+ }
182
+ const functionalRole = HL7_RELATED_PERSON_FUNCTIONAL_ROLES.find((candidate) => normalizedRole === `${candidate.codingSystem}|${candidate.code}`);
183
+ if (!functionalRole) {
184
+ throw new TypeError('role must be a canonical governed healthcare or RelatedPerson functional role.');
185
+ }
177
186
  return {
178
- system: descriptor.codingSystem,
179
- code: descriptor.code,
180
- ...(descriptor.titleEn ? { display: descriptor.titleEn } : {}),
187
+ system: functionalRole.codingSystem,
188
+ code: functionalRole.code,
189
+ display: functionalRole.display,
181
190
  };
182
191
  }
@@ -3,6 +3,25 @@ export type OrganizationAuthorizationUrnInput = Readonly<{
3
3
  identifierType: string;
4
4
  identifierValue: string;
5
5
  }>;
6
+ export type OrganizationAuthorizationUrnCdsInput = Readonly<{
7
+ /** ISO country or governed subdivision such as ES, US, US-WA or CA-BC. */
8
+ jurisdiction: string;
9
+ /** Version of the stable CDS identifier grammar. Defaults to v1. */
10
+ version?: string;
11
+ /** Governed legal identifier type such as TAX, EIN or BN. */
12
+ identifierType: string;
13
+ /** Canonical value issued by that jurisdiction's identifier authority. */
14
+ identifierValue: string;
15
+ }>;
16
+ export type OrganizationMemberAuthorizationUrnCdsInput = Readonly<{
17
+ organizationUrn: string;
18
+ /** Bare base58btc multibase identifier (`z...`); never raw email/phone. */
19
+ memberId: string;
20
+ /** Required protected coding system; deliberately not serialized. */
21
+ roleType: string;
22
+ /** Coded role value serialized as the final compact path segment. */
23
+ roleValue: string;
24
+ }>;
6
25
  export type MemberAuthorizationUrnInput = Readonly<{
7
26
  organizationUrn?: string;
8
27
  identifierType?: string;
@@ -15,6 +34,10 @@ export type MemberAuthorizationUrnInput = Readonly<{
15
34
  * Canonical organization authorization identifier used by ledger, consent-like
16
35
  * rules, and inter-tenant authorization persistence.
17
36
  *
37
+ * @deprecated For new cross-jurisdiction identities use
38
+ * `buildOrganizationAuthorizationUrnCds`, which requires jurisdiction and
39
+ * grammar version. This legacy form remains readable during migration.
40
+ *
18
41
  * Format:
19
42
  * - `urn:org:<identifierType-lowercase>:<identifierValue-as-canonical-input>`
20
43
  *
@@ -23,6 +46,22 @@ export type MemberAuthorizationUrnInput = Readonly<{
23
46
  * - `urn:org:tax:acme-id`
24
47
  */
25
48
  export declare function buildOrganizationAuthorizationUrn(input: OrganizationAuthorizationUrnInput): string;
49
+ /**
50
+ * Builds the globally scoped, host-independent legal organization identifier.
51
+ *
52
+ * Format:
53
+ * `urn:cds-<jurisdiction>:<version>:organization:<identifier-type>:<identifier-value>`
54
+ *
55
+ * Jurisdiction is mandatory because identifier schemes such as BN, EIN and
56
+ * TAX are interpreted by different national/subnational authorities.
57
+ */
58
+ export declare function buildOrganizationAuthorizationUrnCds(input: OrganizationAuthorizationUrnCdsInput): string;
59
+ /**
60
+ * Builds a stable organization-member authorization identifier without a host.
61
+ * `roleType` is mandatory input but only `roleValue` is serialized, matching
62
+ * the compact role convention used by member did:web paths.
63
+ */
64
+ export declare function buildOrganizationMemberAuthorizationUrnCds(input: OrganizationMemberAuthorizationUrnCdsInput): string;
26
65
  /**
27
66
  * Accepts either the legacy `TYPE|VALUE` input or an already-canonical
28
67
  * `urn:org:...` identifier and always returns the canonical URN form.
@@ -1,6 +1,8 @@
1
1
  // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
2
  import { ClaimsOrganizationSchemaorg } from '../constants/schemaorg.js';
3
3
  const ORGANIZATION_URN_PREFIX = 'urn:org:';
4
+ const CDS_ORGANIZATION_URN_PATTERN = /^urn:cds-([a-z]{2}(?:-[a-z0-9]{2,3})?):(v[1-9][0-9]*):organization:([a-z][a-z0-9-]*):([^:]+)$/;
5
+ const BARE_MULTIBASE_PATTERN = /^z[1-9A-HJ-NP-Za-km-z]+$/;
4
6
  function normalizeTypeSegment(identifierType) {
5
7
  return String(identifierType || '').trim().toLowerCase();
6
8
  }
@@ -11,6 +13,10 @@ function normalizeValueSegment(identifierValue) {
11
13
  * Canonical organization authorization identifier used by ledger, consent-like
12
14
  * rules, and inter-tenant authorization persistence.
13
15
  *
16
+ * @deprecated For new cross-jurisdiction identities use
17
+ * `buildOrganizationAuthorizationUrnCds`, which requires jurisdiction and
18
+ * grammar version. This legacy form remains readable during migration.
19
+ *
14
20
  * Format:
15
21
  * - `urn:org:<identifierType-lowercase>:<identifierValue-as-canonical-input>`
16
22
  *
@@ -26,6 +32,52 @@ export function buildOrganizationAuthorizationUrn(input) {
26
32
  }
27
33
  return `${ORGANIZATION_URN_PREFIX}${identifierType}:${identifierValue}`;
28
34
  }
35
+ /**
36
+ * Builds the globally scoped, host-independent legal organization identifier.
37
+ *
38
+ * Format:
39
+ * `urn:cds-<jurisdiction>:<version>:organization:<identifier-type>:<identifier-value>`
40
+ *
41
+ * Jurisdiction is mandatory because identifier schemes such as BN, EIN and
42
+ * TAX are interpreted by different national/subnational authorities.
43
+ */
44
+ export function buildOrganizationAuthorizationUrnCds(input) {
45
+ const jurisdiction = String(input.jurisdiction || '').trim().toLowerCase();
46
+ const version = String(input.version || 'v1').trim().toLowerCase();
47
+ const identifierType = normalizeTypeSegment(input.identifierType);
48
+ const identifierValue = normalizeValueSegment(input.identifierValue);
49
+ if (!/^[a-z]{2}(?:-[a-z0-9]{2,3})?$/.test(jurisdiction)) {
50
+ throw new Error('CDS organization authorization URN requires an ISO jurisdiction such as ES, US or CA-BC.');
51
+ }
52
+ if (!/^v[1-9][0-9]*$/.test(version)) {
53
+ throw new Error('CDS organization authorization URN requires a version such as v1.');
54
+ }
55
+ if (!/^[a-z][a-z0-9-]*$/.test(identifierType) || !identifierValue || identifierValue.includes(':')) {
56
+ throw new Error('CDS organization authorization URN requires a legal identifier type and colon-free value.');
57
+ }
58
+ return `urn:cds-${jurisdiction}:${version}:organization:${identifierType}:${identifierValue}`;
59
+ }
60
+ /**
61
+ * Builds a stable organization-member authorization identifier without a host.
62
+ * `roleType` is mandatory input but only `roleValue` is serialized, matching
63
+ * the compact role convention used by member did:web paths.
64
+ */
65
+ export function buildOrganizationMemberAuthorizationUrnCds(input) {
66
+ const organizationUrn = String(input.organizationUrn || '').trim();
67
+ const memberId = String(input.memberId || '').trim();
68
+ const roleType = String(input.roleType || '').trim();
69
+ const roleValue = String(input.roleValue || '').trim();
70
+ if (!CDS_ORGANIZATION_URN_PATTERN.test(organizationUrn)) {
71
+ throw new Error('CDS organization member authorization URN requires a canonical CDS organization URN.');
72
+ }
73
+ if (!BARE_MULTIBASE_PATTERN.test(memberId)) {
74
+ throw new Error('CDS organization member authorization URN requires memberId as bare base58btc multibase.');
75
+ }
76
+ if (!roleType || !roleValue || roleValue.includes(':')) {
77
+ throw new Error('CDS organization member authorization URN requires roleType and a colon-free roleValue.');
78
+ }
79
+ return `${organizationUrn}:member:${memberId}:${roleValue}`;
80
+ }
29
81
  /**
30
82
  * Accepts either the legacy `TYPE|VALUE` input or an already-canonical
31
83
  * `urn:org:...` identifier and always returns the canonical URN form.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdc-common-utils-ts",
3
- "version": "2.9.1",
3
+ "version": "2.9.3",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },