gdc-common-utils-ts 1.24.3 → 2.0.2
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 +39 -0
- package/dist/constants/index.d.ts +1 -0
- package/dist/constants/index.js +1 -0
- package/dist/constants/profile-runtime.d.ts +33 -0
- package/dist/constants/profile-runtime.js +30 -0
- package/dist/examples/frontend-session.js +2 -1
- package/dist/examples/ica-verify-response.d.ts +225 -0
- package/dist/examples/ica-verify-response.js +257 -0
- package/dist/examples/index.d.ts +2 -0
- package/dist/examples/index.js +2 -0
- package/dist/examples/lifecycle.js +5 -5
- package/dist/examples/organization-controller.d.ts +0 -1
- package/dist/examples/organization-controller.js +0 -1
- package/dist/examples/profile-runtime.d.ts +16 -0
- package/dist/examples/profile-runtime.js +18 -0
- package/dist/interfaces/Cryptography.types.d.ts +15 -0
- package/dist/models/identity-bootstrap.d.ts +9 -0
- package/dist/utils/activation-policy.d.ts +22 -3
- package/dist/utils/activation-policy.js +22 -3
- package/dist/utils/activation-request.d.ts +43 -1
- package/dist/utils/activation-request.js +74 -0
- package/dist/utils/communication-search-editor.d.ts +3 -3
- package/dist/utils/communication-search-editor.js +1 -1
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/individual-organization-lifecycle.d.ts +15 -5
- package/dist/utils/individual-organization-lifecycle.js +53 -6
- package/dist/utils/interoperable-resource-operation.d.ts +4 -4
- package/dist/utils/interoperable-resource-operation.js +22 -22
- package/dist/utils/jwk-thumbprint.d.ts +40 -0
- package/dist/utils/jwk-thumbprint.js +57 -0
- package/dist/utils/legal-organization-onboarding.d.ts +97 -0
- package/dist/utils/legal-organization-onboarding.js +128 -0
- package/dist/utils/license-commercial-search.d.ts +6 -6
- package/dist/utils/license-commercial-search.js +2 -2
- package/dist/utils/license-list-search.d.ts +7 -7
- package/dist/utils/license-list-search.js +23 -23
- package/dist/utils/license-offer-order.d.ts +19 -19
- package/dist/utils/license-offer-order.js +68 -68
- package/dist/utils/organization-lifecycle.d.ts +59 -0
- package/dist/utils/organization-lifecycle.js +155 -0
- package/package.json +2 -2
|
@@ -16,7 +16,7 @@ export declare const InteroperableLifecycleStatuses: Readonly<{
|
|
|
16
16
|
}>;
|
|
17
17
|
export type InteroperableLifecycleStatus = typeof InteroperableLifecycleStatuses[keyof typeof InteroperableLifecycleStatuses];
|
|
18
18
|
export type InteroperableSearchParams = Readonly<Record<string, SearchParameterPrimitive | undefined>>;
|
|
19
|
-
export type
|
|
19
|
+
export type InteroperableResourceOperationState = Readonly<{
|
|
20
20
|
resourceType: string;
|
|
21
21
|
identifierClaimKey: string;
|
|
22
22
|
identifierValue?: string;
|
|
@@ -45,7 +45,7 @@ export interface InteroperableResourceOperationEditor {
|
|
|
45
45
|
mergeClaims(claims: Record<string, unknown>): InteroperableResourceOperationEditor;
|
|
46
46
|
setLifecycleStatus(value: InteroperableLifecycleStatus): InteroperableResourceOperationEditor;
|
|
47
47
|
importFhirResource(resource: FhirResource): InteroperableResourceOperationEditor;
|
|
48
|
-
|
|
48
|
+
getState(): InteroperableResourceOperationState;
|
|
49
49
|
getBusinessIdentifier(): string | undefined;
|
|
50
50
|
getClaims(): Record<string, unknown>;
|
|
51
51
|
buildLifecycleResource(): {
|
|
@@ -140,7 +140,7 @@ export declare function buildInteroperableSearchPath(resourceType: string): stri
|
|
|
140
140
|
* - `resource.meta.status` carries lifecycle state without overloading
|
|
141
141
|
* resource-specific FHIR fields such as `status` or `active`
|
|
142
142
|
*/
|
|
143
|
-
export declare function buildLifecycleOperationResource(
|
|
143
|
+
export declare function buildLifecycleOperationResource(state: InteroperableResourceOperationState): {
|
|
144
144
|
resourceType: string;
|
|
145
145
|
id?: string;
|
|
146
146
|
identifier?: Array<{
|
|
@@ -155,4 +155,4 @@ export declare function buildLifecycleOperationResource(draft: InteroperableReso
|
|
|
155
155
|
/**
|
|
156
156
|
* Creates the shared chainable editor for search/disable/purge request shapes.
|
|
157
157
|
*/
|
|
158
|
-
export declare function createInteroperableResourceOperationEditor(initial?: Partial<
|
|
158
|
+
export declare function createInteroperableResourceOperationEditor(initial?: Partial<InteroperableResourceOperationState>): InteroperableResourceOperationEditor;
|
|
@@ -24,23 +24,23 @@ function cloneClaims(claims) {
|
|
|
24
24
|
function resolveDefaultIdentifierClaimKey(resourceType) {
|
|
25
25
|
return `${String(resourceType || '').trim()}.identifier`;
|
|
26
26
|
}
|
|
27
|
-
function normalizeDraft(
|
|
28
|
-
const resourceType = normalizeText(
|
|
27
|
+
function normalizeDraft(state) {
|
|
28
|
+
const resourceType = normalizeText(state?.resourceType) || 'Resource';
|
|
29
29
|
return {
|
|
30
30
|
resourceType,
|
|
31
|
-
identifierClaimKey: normalizeText(
|
|
32
|
-
identifierValue: normalizeText(
|
|
33
|
-
identifierSystem: normalizeText(
|
|
34
|
-
resourceId: normalizeText(
|
|
35
|
-
claims: cloneClaims(
|
|
36
|
-
lifecycleStatus:
|
|
31
|
+
identifierClaimKey: normalizeText(state?.identifierClaimKey) || resolveDefaultIdentifierClaimKey(resourceType),
|
|
32
|
+
identifierValue: normalizeText(state?.identifierValue),
|
|
33
|
+
identifierSystem: normalizeText(state?.identifierSystem),
|
|
34
|
+
resourceId: normalizeText(state?.resourceId),
|
|
35
|
+
claims: cloneClaims(state?.claims),
|
|
36
|
+
lifecycleStatus: state?.lifecycleStatus,
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
-
function patchDraft(
|
|
39
|
+
function patchDraft(state, patch) {
|
|
40
40
|
return normalizeDraft({
|
|
41
|
-
...
|
|
41
|
+
...state,
|
|
42
42
|
...patch,
|
|
43
|
-
claims: patch.claims ? cloneClaims(patch.claims) : cloneClaims(
|
|
43
|
+
claims: patch.claims ? cloneClaims(patch.claims) : cloneClaims(state.claims),
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
@@ -118,23 +118,23 @@ export function buildInteroperableSearchPath(resourceType) {
|
|
|
118
118
|
* - `resource.meta.status` carries lifecycle state without overloading
|
|
119
119
|
* resource-specific FHIR fields such as `status` or `active`
|
|
120
120
|
*/
|
|
121
|
-
export function buildLifecycleOperationResource(
|
|
122
|
-
const claims = cloneClaims(
|
|
123
|
-
if (
|
|
124
|
-
claims[
|
|
121
|
+
export function buildLifecycleOperationResource(state) {
|
|
122
|
+
const claims = cloneClaims(state.claims);
|
|
123
|
+
if (state.identifierValue && !normalizeText(claims[state.identifierClaimKey])) {
|
|
124
|
+
claims[state.identifierClaimKey] = state.identifierValue;
|
|
125
125
|
}
|
|
126
126
|
return {
|
|
127
|
-
resourceType:
|
|
128
|
-
...(
|
|
129
|
-
...(
|
|
127
|
+
resourceType: state.resourceType,
|
|
128
|
+
...(state.resourceId ? { id: state.resourceId } : {}),
|
|
129
|
+
...(state.identifierValue ? {
|
|
130
130
|
identifier: [{
|
|
131
|
-
value:
|
|
132
|
-
...(
|
|
131
|
+
value: state.identifierValue,
|
|
132
|
+
...(state.identifierSystem ? { system: state.identifierSystem } : {}),
|
|
133
133
|
}],
|
|
134
134
|
} : {}),
|
|
135
135
|
meta: {
|
|
136
136
|
claims,
|
|
137
|
-
...(
|
|
137
|
+
...(state.lifecycleStatus ? { status: state.lifecycleStatus } : {}),
|
|
138
138
|
},
|
|
139
139
|
};
|
|
140
140
|
}
|
|
@@ -192,7 +192,7 @@ export function createInteroperableResourceOperationEditor(initial) {
|
|
|
192
192
|
});
|
|
193
193
|
return editor;
|
|
194
194
|
},
|
|
195
|
-
|
|
195
|
+
getState() {
|
|
196
196
|
return normalizeDraft(draft);
|
|
197
197
|
},
|
|
198
198
|
getBusinessIdentifier() {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { PublicJwk } from '../interfaces/Cryptography.types';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal RSA public JWK shape needed for RFC 7638 thumbprint derivation.
|
|
4
|
+
*/
|
|
5
|
+
export type RsaPublicJwk = {
|
|
6
|
+
kty: 'RSA';
|
|
7
|
+
e: string;
|
|
8
|
+
n: string;
|
|
9
|
+
kid?: string;
|
|
10
|
+
alg?: string;
|
|
11
|
+
use?: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Public JWK shapes supported by the RFC 7638 thumbprint helpers.
|
|
15
|
+
*
|
|
16
|
+
* Notes:
|
|
17
|
+
* - classic EC keys are identified by `kty: 'EC'` and use `crv`, `x`, `y`
|
|
18
|
+
* - this includes `secp256k1`, represented as `crv: 'secp256k1'`
|
|
19
|
+
* - ML-DSA keys are represented in this codebase as `kty: 'AKP'` and use
|
|
20
|
+
* `alg` plus `pub`; they do not use `crv`
|
|
21
|
+
*/
|
|
22
|
+
export type ThumbprintableJwk = PublicJwk | RsaPublicJwk;
|
|
23
|
+
/**
|
|
24
|
+
* Computes the RFC 7638 JWK thumbprint as a bare base64url string.
|
|
25
|
+
*
|
|
26
|
+
* Canonical fields per supported key family:
|
|
27
|
+
* - `EC`: `kty`, `crv`, `x`, `y`
|
|
28
|
+
* - `RSA`: `kty`, `e`, `n`
|
|
29
|
+
* - `OKP`: `kty`, `crv`, `x`
|
|
30
|
+
* - `AKP` / ML-DSA: `kty`, `alg`, `pub`
|
|
31
|
+
*
|
|
32
|
+
* For classical EC keys, the curve is taken from `crv`, so keys such as
|
|
33
|
+
* `secp256k1` are handled naturally through `crv: 'secp256k1'`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function computeRfc7638JwkThumbprint(jwk: ThumbprintableJwk): string;
|
|
36
|
+
/**
|
|
37
|
+
* Returns the RFC 9278 thumbprint URI form:
|
|
38
|
+
* `urn:ietf:params:oauth:jwk-thumbprint:sha-256:<base64url>`
|
|
39
|
+
*/
|
|
40
|
+
export declare function toJwkThumbprintSha256Urn(jwk: ThumbprintableJwk): string;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { UrnPrefixes } from '../constants/urn.js';
|
|
3
|
+
function canonicalizeForThumbprint(jwk) {
|
|
4
|
+
const keys = Object.keys(jwk).sort();
|
|
5
|
+
const parts = keys.map((key) => `"${key}":${JSON.stringify(jwk[key])}`);
|
|
6
|
+
return `{${parts.join(',')}}`;
|
|
7
|
+
}
|
|
8
|
+
function toBaseThumbprintJwk(jwk) {
|
|
9
|
+
if (jwk.kty === 'EC') {
|
|
10
|
+
const { crv, x, y } = jwk;
|
|
11
|
+
if (!crv || !x || !y)
|
|
12
|
+
throw new Error('EC JWK thumbprint requires crv, x and y.');
|
|
13
|
+
return { kty: 'EC', crv, x, y };
|
|
14
|
+
}
|
|
15
|
+
if (jwk.kty === 'RSA') {
|
|
16
|
+
const { e, n } = jwk;
|
|
17
|
+
if (!e || !n)
|
|
18
|
+
throw new Error('RSA JWK thumbprint requires e and n.');
|
|
19
|
+
return { kty: 'RSA', e, n };
|
|
20
|
+
}
|
|
21
|
+
if (jwk.kty === 'OKP') {
|
|
22
|
+
const { crv, x } = jwk;
|
|
23
|
+
if (!crv || !x)
|
|
24
|
+
throw new Error('OKP JWK thumbprint requires crv and x.');
|
|
25
|
+
return { kty: 'OKP', crv, x };
|
|
26
|
+
}
|
|
27
|
+
if (jwk.kty === 'AKP') {
|
|
28
|
+
const { alg, pub } = jwk;
|
|
29
|
+
if (!alg || !pub)
|
|
30
|
+
throw new Error('AKP JWK thumbprint requires alg and pub.');
|
|
31
|
+
return { kty: 'AKP', alg, pub };
|
|
32
|
+
}
|
|
33
|
+
throw new Error(`Unsupported JWK kty for RFC7638 thumbprint: ${jwk.kty || 'unknown'}`);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Computes the RFC 7638 JWK thumbprint as a bare base64url string.
|
|
37
|
+
*
|
|
38
|
+
* Canonical fields per supported key family:
|
|
39
|
+
* - `EC`: `kty`, `crv`, `x`, `y`
|
|
40
|
+
* - `RSA`: `kty`, `e`, `n`
|
|
41
|
+
* - `OKP`: `kty`, `crv`, `x`
|
|
42
|
+
* - `AKP` / ML-DSA: `kty`, `alg`, `pub`
|
|
43
|
+
*
|
|
44
|
+
* For classical EC keys, the curve is taken from `crv`, so keys such as
|
|
45
|
+
* `secp256k1` are handled naturally through `crv: 'secp256k1'`.
|
|
46
|
+
*/
|
|
47
|
+
export function computeRfc7638JwkThumbprint(jwk) {
|
|
48
|
+
const canonical = canonicalizeForThumbprint(toBaseThumbprintJwk(jwk));
|
|
49
|
+
return createHash('sha256').update(canonical).digest('base64url');
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Returns the RFC 9278 thumbprint URI form:
|
|
53
|
+
* `urn:ietf:params:oauth:jwk-thumbprint:sha-256:<base64url>`
|
|
54
|
+
*/
|
|
55
|
+
export function toJwkThumbprintSha256Urn(jwk) {
|
|
56
|
+
return `${UrnPrefixes.JwkThumbprintSha256KeyId}${computeRfc7638JwkThumbprint(jwk)}`;
|
|
57
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { ClaimsOrganizationSchemaorg } from '../constants/schemaorg';
|
|
2
|
+
import { ClaimsRecord } from '../models/resource-document';
|
|
3
|
+
export type LegalOrganizationOnboardingErrorCode = 'MISSING_IDENTIFIER_OR_TAX_ID' | 'EXPLICIT_ALTERNATE_NAME_NOT_ALLOWED';
|
|
4
|
+
export type LegalOrganizationOnboardingValidationError = {
|
|
5
|
+
code: LegalOrganizationOnboardingErrorCode;
|
|
6
|
+
message: string;
|
|
7
|
+
claimPaths: string[];
|
|
8
|
+
};
|
|
9
|
+
export type ValidateLegalOrganizationOnboardingClaimsOptions = {
|
|
10
|
+
/**
|
|
11
|
+
* When `false` (default), `alternateName` is treated as a compatibility alias
|
|
12
|
+
* derived from the canonical legal identifier. Callers may still send an
|
|
13
|
+
* explicit value, but it must match the final normalized
|
|
14
|
+
* `Organization.identifier.value`.
|
|
15
|
+
*
|
|
16
|
+
* When `true`, callers may keep an explicit `alternateName` that differs from
|
|
17
|
+
* `Organization.identifier.value`.
|
|
18
|
+
*/
|
|
19
|
+
allowExplicitAlternateNameForTenantId?: boolean;
|
|
20
|
+
};
|
|
21
|
+
export type ValidateLegalOrganizationOnboardingClaimsResult = {
|
|
22
|
+
ok: boolean;
|
|
23
|
+
errors: LegalOrganizationOnboardingValidationError[];
|
|
24
|
+
missingClaims: string[];
|
|
25
|
+
normalizedClaims: ClaimsRecord;
|
|
26
|
+
derived: {
|
|
27
|
+
identifierValueFromTaxId: boolean;
|
|
28
|
+
alternateNameFromIdentifierValue: boolean;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* JSON Schema for legal-organization onboarding claims used by high-level SDK
|
|
33
|
+
* forms and assistants before they submit anything to GW CORE.
|
|
34
|
+
*
|
|
35
|
+
* Contract:
|
|
36
|
+
* - one of `Organization.identifier.value` or `Organization.taxID` must be
|
|
37
|
+
* provided
|
|
38
|
+
* - `alternateName` is optional at input time because current GW compatibility
|
|
39
|
+
* may derive it from the canonical legal identifier
|
|
40
|
+
* - callers that want stricter tenant-alias behavior must still run the
|
|
41
|
+
* validator below because JSON Schema alone cannot express the
|
|
42
|
+
* runtime option `allowExplicitAlternateNameForTenantId`
|
|
43
|
+
*/
|
|
44
|
+
export declare const LEGAL_ORGANIZATION_ONBOARDING_JSON_SCHEMA: {
|
|
45
|
+
readonly $schema: "https://json-schema.org/draft/2020-12/schema";
|
|
46
|
+
readonly $id: "https://gdc/common-utils/legal-organization-onboarding.schema.json";
|
|
47
|
+
readonly title: "Legal Organization Onboarding Claims";
|
|
48
|
+
readonly type: "object";
|
|
49
|
+
readonly properties: {
|
|
50
|
+
readonly '@context': {
|
|
51
|
+
readonly type: "string";
|
|
52
|
+
readonly enum: readonly ["org.schema"];
|
|
53
|
+
};
|
|
54
|
+
readonly "org.schema.Organization.identifier.value": {
|
|
55
|
+
readonly type: "string";
|
|
56
|
+
readonly minLength: 1;
|
|
57
|
+
readonly description: "Canonical legal identifier used by onboarding and tenant normalization.";
|
|
58
|
+
};
|
|
59
|
+
readonly "org.schema.Organization.taxID": {
|
|
60
|
+
readonly type: "string";
|
|
61
|
+
readonly minLength: 1;
|
|
62
|
+
readonly description: "Compatibility tax identifier that may backfill Organization.identifier.value.";
|
|
63
|
+
};
|
|
64
|
+
readonly "org.schema.Organization.alternateName": {
|
|
65
|
+
readonly type: "string";
|
|
66
|
+
readonly minLength: 1;
|
|
67
|
+
readonly description: "Optional compatibility alias. If omitted, callers may derive it from Organization.identifier.value.";
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
readonly oneOf: readonly [{
|
|
71
|
+
readonly required: readonly [ClaimsOrganizationSchemaorg.identifierValue];
|
|
72
|
+
}, {
|
|
73
|
+
readonly required: readonly [ClaimsOrganizationSchemaorg.taxId];
|
|
74
|
+
}];
|
|
75
|
+
readonly additionalProperties: true;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Validates and normalizes legal-organization onboarding claims for SDKs,
|
|
79
|
+
* forms, and assistant-style data collection flows.
|
|
80
|
+
*
|
|
81
|
+
* Main behavior:
|
|
82
|
+
* - requires at least one of `Organization.identifier.value` or
|
|
83
|
+
* `Organization.taxID`
|
|
84
|
+
* - if `identifier.value` is missing and `taxID` exists, copies
|
|
85
|
+
* `taxID -> identifier.value`
|
|
86
|
+
* - if `alternateName` is missing and a canonical identifier exists, copies
|
|
87
|
+
* `identifier.value -> alternateName`
|
|
88
|
+
* - when `allowExplicitAlternateNameForTenantId` is `false` (default),
|
|
89
|
+
* rejects explicit `alternateName` values that differ from the final
|
|
90
|
+
* canonical `identifier.value`
|
|
91
|
+
*
|
|
92
|
+
* The result shape is intentionally assistant-friendly:
|
|
93
|
+
* - `missingClaims` tells UI/voice flows what is still required
|
|
94
|
+
* - `normalizedClaims` shows the post-derivation claim set
|
|
95
|
+
* - `derived` explains which values were filled automatically
|
|
96
|
+
*/
|
|
97
|
+
export declare function validateLegalOrganizationOnboardingClaims(claims: ClaimsRecord, options?: ValidateLegalOrganizationOnboardingClaimsOptions): ValidateLegalOrganizationOnboardingClaimsResult;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { ClaimsOrganizationSchemaorg } from '../constants/schemaorg.js';
|
|
2
|
+
/**
|
|
3
|
+
* JSON Schema for legal-organization onboarding claims used by high-level SDK
|
|
4
|
+
* forms and assistants before they submit anything to GW CORE.
|
|
5
|
+
*
|
|
6
|
+
* Contract:
|
|
7
|
+
* - one of `Organization.identifier.value` or `Organization.taxID` must be
|
|
8
|
+
* provided
|
|
9
|
+
* - `alternateName` is optional at input time because current GW compatibility
|
|
10
|
+
* may derive it from the canonical legal identifier
|
|
11
|
+
* - callers that want stricter tenant-alias behavior must still run the
|
|
12
|
+
* validator below because JSON Schema alone cannot express the
|
|
13
|
+
* runtime option `allowExplicitAlternateNameForTenantId`
|
|
14
|
+
*/
|
|
15
|
+
export const LEGAL_ORGANIZATION_ONBOARDING_JSON_SCHEMA = {
|
|
16
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
17
|
+
$id: 'https://gdc/common-utils/legal-organization-onboarding.schema.json',
|
|
18
|
+
title: 'Legal Organization Onboarding Claims',
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
'@context': {
|
|
22
|
+
type: 'string',
|
|
23
|
+
enum: ['org.schema'],
|
|
24
|
+
},
|
|
25
|
+
[ClaimsOrganizationSchemaorg.identifierValue]: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
minLength: 1,
|
|
28
|
+
description: 'Canonical legal identifier used by onboarding and tenant normalization.',
|
|
29
|
+
},
|
|
30
|
+
[ClaimsOrganizationSchemaorg.taxId]: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
minLength: 1,
|
|
33
|
+
description: 'Compatibility tax identifier that may backfill Organization.identifier.value.',
|
|
34
|
+
},
|
|
35
|
+
[ClaimsOrganizationSchemaorg.alternateName]: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
minLength: 1,
|
|
38
|
+
description: 'Optional compatibility alias. If omitted, callers may derive it from Organization.identifier.value.',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
oneOf: [
|
|
42
|
+
{ required: [ClaimsOrganizationSchemaorg.identifierValue] },
|
|
43
|
+
{ required: [ClaimsOrganizationSchemaorg.taxId] },
|
|
44
|
+
],
|
|
45
|
+
additionalProperties: true,
|
|
46
|
+
};
|
|
47
|
+
function normalizeOptionalString(value) {
|
|
48
|
+
if (typeof value !== 'string') {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
const trimmed = value.trim();
|
|
52
|
+
return trimmed.length ? trimmed : undefined;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validates and normalizes legal-organization onboarding claims for SDKs,
|
|
56
|
+
* forms, and assistant-style data collection flows.
|
|
57
|
+
*
|
|
58
|
+
* Main behavior:
|
|
59
|
+
* - requires at least one of `Organization.identifier.value` or
|
|
60
|
+
* `Organization.taxID`
|
|
61
|
+
* - if `identifier.value` is missing and `taxID` exists, copies
|
|
62
|
+
* `taxID -> identifier.value`
|
|
63
|
+
* - if `alternateName` is missing and a canonical identifier exists, copies
|
|
64
|
+
* `identifier.value -> alternateName`
|
|
65
|
+
* - when `allowExplicitAlternateNameForTenantId` is `false` (default),
|
|
66
|
+
* rejects explicit `alternateName` values that differ from the final
|
|
67
|
+
* canonical `identifier.value`
|
|
68
|
+
*
|
|
69
|
+
* The result shape is intentionally assistant-friendly:
|
|
70
|
+
* - `missingClaims` tells UI/voice flows what is still required
|
|
71
|
+
* - `normalizedClaims` shows the post-derivation claim set
|
|
72
|
+
* - `derived` explains which values were filled automatically
|
|
73
|
+
*/
|
|
74
|
+
export function validateLegalOrganizationOnboardingClaims(claims, options = {}) {
|
|
75
|
+
const normalizedClaims = { ...(claims || {}) };
|
|
76
|
+
const errors = [];
|
|
77
|
+
const missingClaims = [];
|
|
78
|
+
const allowExplicitAlternateNameForTenantId = options.allowExplicitAlternateNameForTenantId === true;
|
|
79
|
+
const identifierValue = normalizeOptionalString(normalizedClaims[ClaimsOrganizationSchemaorg.identifierValue]);
|
|
80
|
+
const taxId = normalizeOptionalString(normalizedClaims[ClaimsOrganizationSchemaorg.taxId]);
|
|
81
|
+
const explicitAlternateName = normalizeOptionalString(normalizedClaims[ClaimsOrganizationSchemaorg.alternateName]);
|
|
82
|
+
let finalIdentifierValue = identifierValue;
|
|
83
|
+
let identifierValueFromTaxId = false;
|
|
84
|
+
let alternateNameFromIdentifierValue = false;
|
|
85
|
+
if (!finalIdentifierValue && taxId) {
|
|
86
|
+
finalIdentifierValue = taxId;
|
|
87
|
+
normalizedClaims[ClaimsOrganizationSchemaorg.identifierValue] = taxId;
|
|
88
|
+
identifierValueFromTaxId = true;
|
|
89
|
+
}
|
|
90
|
+
if (!finalIdentifierValue) {
|
|
91
|
+
missingClaims.push(ClaimsOrganizationSchemaorg.identifierValue, ClaimsOrganizationSchemaorg.taxId);
|
|
92
|
+
errors.push({
|
|
93
|
+
code: 'MISSING_IDENTIFIER_OR_TAX_ID',
|
|
94
|
+
message: 'Legal organization onboarding requires Organization.identifier.value or Organization.taxID.',
|
|
95
|
+
claimPaths: [
|
|
96
|
+
ClaimsOrganizationSchemaorg.identifierValue,
|
|
97
|
+
ClaimsOrganizationSchemaorg.taxId,
|
|
98
|
+
],
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (!explicitAlternateName && finalIdentifierValue) {
|
|
102
|
+
normalizedClaims[ClaimsOrganizationSchemaorg.alternateName] = finalIdentifierValue;
|
|
103
|
+
alternateNameFromIdentifierValue = true;
|
|
104
|
+
}
|
|
105
|
+
if (explicitAlternateName
|
|
106
|
+
&& finalIdentifierValue
|
|
107
|
+
&& !allowExplicitAlternateNameForTenantId
|
|
108
|
+
&& explicitAlternateName !== finalIdentifierValue) {
|
|
109
|
+
errors.push({
|
|
110
|
+
code: 'EXPLICIT_ALTERNATE_NAME_NOT_ALLOWED',
|
|
111
|
+
message: 'Explicit Organization.alternateName is not allowed unless it matches Organization.identifier.value or the caller enables allowExplicitAlternateNameForTenantId.',
|
|
112
|
+
claimPaths: [
|
|
113
|
+
ClaimsOrganizationSchemaorg.alternateName,
|
|
114
|
+
ClaimsOrganizationSchemaorg.identifierValue,
|
|
115
|
+
],
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
ok: errors.length === 0,
|
|
120
|
+
errors,
|
|
121
|
+
missingClaims,
|
|
122
|
+
normalizedClaims,
|
|
123
|
+
derived: {
|
|
124
|
+
identifierValueFromTaxId,
|
|
125
|
+
alternateNameFromIdentifierValue,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -8,14 +8,14 @@ export declare const LicenseCommercialSearchOperation: Readonly<{
|
|
|
8
8
|
readonly Offer: "Offer:Search";
|
|
9
9
|
readonly Order: "Order:Search";
|
|
10
10
|
}>;
|
|
11
|
-
export type
|
|
11
|
+
export type LicenseOfferSearchState = Readonly<{
|
|
12
12
|
offerId?: string;
|
|
13
13
|
status?: string;
|
|
14
14
|
category?: string;
|
|
15
15
|
customerType?: string;
|
|
16
16
|
additionalClaims: LicenseClaims;
|
|
17
17
|
}>;
|
|
18
|
-
export type
|
|
18
|
+
export type LicenseOrderSearchState = Readonly<{
|
|
19
19
|
acceptedOfferId?: string;
|
|
20
20
|
invoiceId?: string;
|
|
21
21
|
paymentMethod?: string;
|
|
@@ -43,13 +43,13 @@ export type LicenseOrderRecord = Readonly<{
|
|
|
43
43
|
*/
|
|
44
44
|
export declare class LicenseOfferSearchEditor {
|
|
45
45
|
private draft;
|
|
46
|
-
constructor(initial?: Partial<
|
|
46
|
+
constructor(initial?: Partial<LicenseOfferSearchState>);
|
|
47
47
|
setOfferId(value: string): this;
|
|
48
48
|
setStatus(value: string): this;
|
|
49
49
|
setCategory(value: string): this;
|
|
50
50
|
setCustomerType(value: string): this;
|
|
51
51
|
mergeClaims(claims: LicenseClaims): this;
|
|
52
|
-
|
|
52
|
+
getState(): LicenseOfferSearchState;
|
|
53
53
|
buildSearchEntry(): {
|
|
54
54
|
type: string;
|
|
55
55
|
request: {
|
|
@@ -72,13 +72,13 @@ export declare class LicenseOfferSearchEditor {
|
|
|
72
72
|
*/
|
|
73
73
|
export declare class LicenseOrderSearchEditor {
|
|
74
74
|
private draft;
|
|
75
|
-
constructor(initial?: Partial<
|
|
75
|
+
constructor(initial?: Partial<LicenseOrderSearchState>);
|
|
76
76
|
setAcceptedOfferId(value: string): this;
|
|
77
77
|
setInvoiceId(value: string): this;
|
|
78
78
|
setPaymentMethod(value: string): this;
|
|
79
79
|
setStatus(value: string): this;
|
|
80
80
|
mergeClaims(claims: LicenseClaims): this;
|
|
81
|
-
|
|
81
|
+
getState(): LicenseOrderSearchState;
|
|
82
82
|
buildSearchEntry(): {
|
|
83
83
|
type: string;
|
|
84
84
|
request: {
|
|
@@ -68,7 +68,7 @@ export class LicenseOfferSearchEditor {
|
|
|
68
68
|
});
|
|
69
69
|
return this;
|
|
70
70
|
}
|
|
71
|
-
|
|
71
|
+
getState() {
|
|
72
72
|
return cloneOfferSearchDraft(this.draft);
|
|
73
73
|
}
|
|
74
74
|
buildSearchEntry() {
|
|
@@ -131,7 +131,7 @@ export class LicenseOrderSearchEditor {
|
|
|
131
131
|
});
|
|
132
132
|
return this;
|
|
133
133
|
}
|
|
134
|
-
|
|
134
|
+
getState() {
|
|
135
135
|
return cloneOrderSearchDraft(this.draft);
|
|
136
136
|
}
|
|
137
137
|
buildSearchEntry() {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { buildLicenseSearchEntry, type LicenseClaims, type LicenseSearchInput, type LicenseStatus } from './license';
|
|
2
2
|
/**
|
|
3
|
-
* High-level filter
|
|
3
|
+
* High-level filter state for license list/search screens.
|
|
4
4
|
*
|
|
5
|
-
* This
|
|
5
|
+
* This state keeps UI semantics stable even when the runtime search transport
|
|
6
6
|
* still supports only a subset of the final portal-facing filters.
|
|
7
7
|
*/
|
|
8
|
-
export type
|
|
8
|
+
export type LicenseListSearchState = Readonly<{
|
|
9
9
|
serialNumbers?: readonly string[];
|
|
10
10
|
email?: string;
|
|
11
11
|
role?: string;
|
|
@@ -49,18 +49,18 @@ export type LicenseListSummary = Readonly<{
|
|
|
49
49
|
inactive: number;
|
|
50
50
|
}>;
|
|
51
51
|
/**
|
|
52
|
-
* High-level chainable
|
|
52
|
+
* High-level chainable editor state for frontend/backend license list/search filters.
|
|
53
53
|
*
|
|
54
54
|
* Intent:
|
|
55
55
|
* - keep UI-level filters (`active`, `unused`, `assigned`, `period`) visible at
|
|
56
56
|
* the semantic layer
|
|
57
57
|
* - map the currently supported subset to the existing canonical search entry
|
|
58
|
-
* - preserve the rest in a neutral
|
|
58
|
+
* - preserve the rest in a neutral state until the final backend facade is
|
|
59
59
|
* fully converged
|
|
60
60
|
*/
|
|
61
61
|
export declare class LicenseListSearchEditor {
|
|
62
62
|
private draft;
|
|
63
|
-
constructor(initial?: Partial<
|
|
63
|
+
constructor(initial?: Partial<LicenseListSearchState>);
|
|
64
64
|
setSerialNumbers(values: readonly string[]): this;
|
|
65
65
|
setEmail(value: string): this;
|
|
66
66
|
setRole(value: string): this;
|
|
@@ -73,7 +73,7 @@ export declare class LicenseListSearchEditor {
|
|
|
73
73
|
setUnused(value: boolean): this;
|
|
74
74
|
setPeriod(start?: string, end?: string): this;
|
|
75
75
|
mergeClaims(claims: LicenseClaims): this;
|
|
76
|
-
|
|
76
|
+
getState(): LicenseListSearchState;
|
|
77
77
|
/**
|
|
78
78
|
* Returns the currently supported runtime-facing search input subset.
|
|
79
79
|
*/
|
|
@@ -8,42 +8,42 @@ function normalizeText(value) {
|
|
|
8
8
|
function cloneClaims(claims) {
|
|
9
9
|
return { ...(claims || {}) };
|
|
10
10
|
}
|
|
11
|
-
function cloneDraft(
|
|
11
|
+
function cloneDraft(state) {
|
|
12
12
|
return {
|
|
13
|
-
serialNumbers: Array.isArray(
|
|
14
|
-
email: normalizeText(
|
|
15
|
-
role: normalizeText(
|
|
16
|
-
status:
|
|
17
|
-
subjectId: normalizeText(
|
|
18
|
-
userClass: normalizeText(
|
|
19
|
-
type: normalizeText(
|
|
20
|
-
active: typeof
|
|
21
|
-
assigned: typeof
|
|
22
|
-
unused: typeof
|
|
23
|
-
periodStart: normalizeText(
|
|
24
|
-
periodEnd: normalizeText(
|
|
25
|
-
additionalClaims: cloneClaims(
|
|
13
|
+
serialNumbers: Array.isArray(state?.serialNumbers) ? [...state.serialNumbers] : undefined,
|
|
14
|
+
email: normalizeText(state?.email),
|
|
15
|
+
role: normalizeText(state?.role),
|
|
16
|
+
status: state?.status,
|
|
17
|
+
subjectId: normalizeText(state?.subjectId),
|
|
18
|
+
userClass: normalizeText(state?.userClass),
|
|
19
|
+
type: normalizeText(state?.type),
|
|
20
|
+
active: typeof state?.active === 'boolean' ? state.active : undefined,
|
|
21
|
+
assigned: typeof state?.assigned === 'boolean' ? state.assigned : undefined,
|
|
22
|
+
unused: typeof state?.unused === 'boolean' ? state.unused : undefined,
|
|
23
|
+
periodStart: normalizeText(state?.periodStart),
|
|
24
|
+
periodEnd: normalizeText(state?.periodEnd),
|
|
25
|
+
additionalClaims: cloneClaims(state?.additionalClaims),
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
function resolveStatus(
|
|
29
|
-
if (
|
|
30
|
-
return
|
|
31
|
-
if (
|
|
28
|
+
function resolveStatus(state) {
|
|
29
|
+
if (state.status)
|
|
30
|
+
return state.status;
|
|
31
|
+
if (state.active === true)
|
|
32
32
|
return LicenseStatuses.Active;
|
|
33
|
-
if (
|
|
33
|
+
if (state.active === false)
|
|
34
34
|
return LicenseStatuses.Inactive;
|
|
35
|
-
if (
|
|
35
|
+
if (state.unused === true)
|
|
36
36
|
return LicenseStatuses.Available;
|
|
37
37
|
return undefined;
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
40
|
-
* High-level chainable
|
|
40
|
+
* High-level chainable editor state for frontend/backend license list/search filters.
|
|
41
41
|
*
|
|
42
42
|
* Intent:
|
|
43
43
|
* - keep UI-level filters (`active`, `unused`, `assigned`, `period`) visible at
|
|
44
44
|
* the semantic layer
|
|
45
45
|
* - map the currently supported subset to the existing canonical search entry
|
|
46
|
-
* - preserve the rest in a neutral
|
|
46
|
+
* - preserve the rest in a neutral state until the final backend facade is
|
|
47
47
|
* fully converged
|
|
48
48
|
*/
|
|
49
49
|
export class LicenseListSearchEditor {
|
|
@@ -102,7 +102,7 @@ export class LicenseListSearchEditor {
|
|
|
102
102
|
});
|
|
103
103
|
return this;
|
|
104
104
|
}
|
|
105
|
-
|
|
105
|
+
getState() {
|
|
106
106
|
return cloneDraft(this.draft);
|
|
107
107
|
}
|
|
108
108
|
/**
|