vet-sdk-core-ts 0.4.15 → 0.4.17
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 +8 -0
- package/dist/research-study.d.ts +22 -0
- package/dist/research-study.js +50 -1
- package/docs/101-RESEARCH-STUDY-INVITATIONS.md +20 -8
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,6 +8,14 @@ owns gateway request construction. GW VET remains the policy authority.
|
|
|
8
8
|
|
|
9
9
|
## Research studies
|
|
10
10
|
|
|
11
|
+
`buildVeterinaryResearchStudyCreateWorkflowIds` accepts a client-generated
|
|
12
|
+
operation UUID and the exact `PractitionerRole/...` references. It reuses the
|
|
13
|
+
operation UUID as `researchStudyId` and deterministically derives distinct
|
|
14
|
+
UUIDv5 values for the Group and every Communication/draft Consent pair. A
|
|
15
|
+
retry therefore addresses the same resources and a partially completed saga
|
|
16
|
+
can resume safely. Stable identifiers do not make separate service calls
|
|
17
|
+
atomic and confer no membership, Consent or SMART authority.
|
|
18
|
+
|
|
11
19
|
`buildVeterinaryResearchStudyCreateEntry` accepts ResearchStudy business data
|
|
12
20
|
and projects it into the canonical JSON:API resource object: `resourceType`,
|
|
13
21
|
`id` and flat `resource.meta.claims`. It does not send native `status`, `title`,
|
package/dist/research-study.d.ts
CHANGED
|
@@ -78,11 +78,33 @@ export declare const VeterinaryResearchStudyReviewTeamIdentifierNamespace: "6434
|
|
|
78
78
|
export type VeterinaryResearchStudyReviewTeamGroupSearch = Readonly<{
|
|
79
79
|
identifier: string;
|
|
80
80
|
}>;
|
|
81
|
+
export type VeterinaryResearchStudyCreateWorkflowIds = Readonly<{
|
|
82
|
+
researchStudyId: string;
|
|
83
|
+
groupId: string;
|
|
84
|
+
invitations: readonly Readonly<{
|
|
85
|
+
practitionerRoleReference: string;
|
|
86
|
+
communicationId: string;
|
|
87
|
+
consentId: string;
|
|
88
|
+
}>[];
|
|
89
|
+
}>;
|
|
81
90
|
export type VeterinaryResearchStudySmartAuthorization = Readonly<{
|
|
82
91
|
purpose: typeof HealthcareConsentPurposes.Research;
|
|
83
92
|
researchStudyReference: string;
|
|
84
93
|
scopes: readonly [string];
|
|
85
94
|
}>;
|
|
95
|
+
/**
|
|
96
|
+
* Derives every identifier needed to retry or resume one ResearchStudy create
|
|
97
|
+
* workflow. The client-generated operation UUID is the ResearchStudy id and
|
|
98
|
+
* UUIDv5 names produce a stable Group, Communication and draft Consent id.
|
|
99
|
+
* Reusing these ids supports idempotent saga steps; it does not make separate
|
|
100
|
+
* persistence calls atomic and does not convey Consent or SMART authority.
|
|
101
|
+
*
|
|
102
|
+
* @see https://www.rfc-editor.org/rfc/rfc9562#name-uuid-version-5
|
|
103
|
+
*/
|
|
104
|
+
export declare function buildVeterinaryResearchStudyCreateWorkflowIds(input: Readonly<{
|
|
105
|
+
operationId: string;
|
|
106
|
+
practitionerRoleReferences: readonly string[];
|
|
107
|
+
}>): VeterinaryResearchStudyCreateWorkflowIds;
|
|
86
108
|
export type VeterinaryResearchStudyActiveConsentInput = Readonly<{
|
|
87
109
|
consentId: string;
|
|
88
110
|
researchStudyId: string;
|
package/dist/research-study.js
CHANGED
|
@@ -33,6 +33,47 @@ export const VeterinaryResearchStudyInvitationClaimNames = Object.freeze({
|
|
|
33
33
|
export const VeterinaryResearchStudyReviewTeamIdentifierSystem = 'urn:ietf:rfc:3986';
|
|
34
34
|
/** UUIDv5 namespace governed by this SDK for ResearchStudy review-team identifiers. */
|
|
35
35
|
export const VeterinaryResearchStudyReviewTeamIdentifierNamespace = '6434afe2-d152-5ec2-a88b-ec2a96e0f010';
|
|
36
|
+
/**
|
|
37
|
+
* Derives every identifier needed to retry or resume one ResearchStudy create
|
|
38
|
+
* workflow. The client-generated operation UUID is the ResearchStudy id and
|
|
39
|
+
* UUIDv5 names produce a stable Group, Communication and draft Consent id.
|
|
40
|
+
* Reusing these ids supports idempotent saga steps; it does not make separate
|
|
41
|
+
* persistence calls atomic and does not convey Consent or SMART authority.
|
|
42
|
+
*
|
|
43
|
+
* @see https://www.rfc-editor.org/rfc/rfc9562#name-uuid-version-5
|
|
44
|
+
*/
|
|
45
|
+
export function buildVeterinaryResearchStudyCreateWorkflowIds(input) {
|
|
46
|
+
let operationId;
|
|
47
|
+
try {
|
|
48
|
+
operationId = normalizedUuid(input?.operationId);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
throw new TypeError('veterinary_research_study_create_operation_id_invalid');
|
|
52
|
+
}
|
|
53
|
+
if (!Array.isArray(input?.practitionerRoleReferences)) {
|
|
54
|
+
throw new TypeError('veterinary_research_study_create_practitioner_role_reference_invalid');
|
|
55
|
+
}
|
|
56
|
+
const references = input.practitionerRoleReferences.map(reference => {
|
|
57
|
+
try {
|
|
58
|
+
return boundedReference(reference, 'PractitionerRole');
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
throw new TypeError('veterinary_research_study_create_practitioner_role_reference_invalid');
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
if (new Set(references).size !== references.length) {
|
|
65
|
+
throw new TypeError('veterinary_research_study_create_practitioner_role_duplicate');
|
|
66
|
+
}
|
|
67
|
+
return deepFreeze({
|
|
68
|
+
researchStudyId: operationId,
|
|
69
|
+
groupId: uuidV5('Group', operationId),
|
|
70
|
+
invitations: references.map(practitionerRoleReference => ({
|
|
71
|
+
practitionerRoleReference,
|
|
72
|
+
communicationId: uuidV5(`Communication|${practitionerRoleReference}`, operationId),
|
|
73
|
+
consentId: uuidV5(`Consent|${practitionerRoleReference}`, operationId),
|
|
74
|
+
})),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
36
77
|
/** Builds the exact GW study-pinned ResearchSubject `crus` capability. */
|
|
37
78
|
export function buildVeterinaryResearchStudyAccessScope(researchStudyId) {
|
|
38
79
|
return `organization/ResearchSubject.crus?study=ResearchStudy/${boundedId(researchStudyId)}`;
|
|
@@ -411,9 +452,17 @@ function boundedId(value) {
|
|
|
411
452
|
return id;
|
|
412
453
|
}
|
|
413
454
|
function boundedStudyUuid(value) {
|
|
455
|
+
try {
|
|
456
|
+
return normalizedUuid(value);
|
|
457
|
+
}
|
|
458
|
+
catch {
|
|
459
|
+
throw new TypeError('veterinary_research_study_review_team_study_id_invalid');
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
function normalizedUuid(value) {
|
|
414
463
|
const uuid = String(value || '').trim().toLowerCase();
|
|
415
464
|
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(uuid)) {
|
|
416
|
-
throw new TypeError('
|
|
465
|
+
throw new TypeError('uuid_invalid');
|
|
417
466
|
}
|
|
418
467
|
return uuid;
|
|
419
468
|
}
|
|
@@ -18,6 +18,7 @@ related to the exact ResearchStudy. Whole-twin delete is absent.
|
|
|
18
18
|
```ts
|
|
19
19
|
import {
|
|
20
20
|
buildVeterinaryResearchStudyActiveConsentBatch,
|
|
21
|
+
buildVeterinaryResearchStudyCreateWorkflowIds,
|
|
21
22
|
buildVeterinaryResearchStudyGroupBatch,
|
|
22
23
|
buildVeterinaryResearchStudyGroupCreateEntry,
|
|
23
24
|
buildVeterinaryResearchStudyGroupMemberPatchEntries,
|
|
@@ -25,9 +26,13 @@ import {
|
|
|
25
26
|
buildVeterinaryResearchStudySmartAuthorization,
|
|
26
27
|
} from 'vet-sdk-core-ts/research-study'
|
|
27
28
|
|
|
28
|
-
const
|
|
29
|
+
const workflowIds = buildVeterinaryResearchStudyCreateWorkflowIds({
|
|
30
|
+
operationId: crypto.randomUUID(),
|
|
31
|
+
practitionerRoleReferences: associatedParties.map(({ party }) => party.reference),
|
|
32
|
+
})
|
|
33
|
+
const researchGroupId = workflowIds.groupId
|
|
29
34
|
const groupCreate = buildVeterinaryResearchStudyGroupCreateEntry({
|
|
30
|
-
researchStudyId:
|
|
35
|
+
researchStudyId: workflowIds.researchStudyId,
|
|
31
36
|
group: {
|
|
32
37
|
resourceType: 'Group',
|
|
33
38
|
id: researchGroupId,
|
|
@@ -44,13 +49,13 @@ const groupBatch = buildVeterinaryResearchStudyGroupBatch([groupCreate, ...membe
|
|
|
44
49
|
// The protected controller BFF submits `groupBatch`; membership grants no access.
|
|
45
50
|
|
|
46
51
|
const invitations = buildVeterinaryResearchStudyPartyInvitations({
|
|
47
|
-
researchStudyId:
|
|
52
|
+
researchStudyId: workflowIds.researchStudyId,
|
|
48
53
|
controllerReference: organizationReference,
|
|
49
54
|
sentAt: new Date().toISOString(),
|
|
50
55
|
invitations: associatedParties.map((associatedParty, index) => ({
|
|
51
56
|
associatedParty,
|
|
52
|
-
communicationId:
|
|
53
|
-
consentId:
|
|
57
|
+
communicationId: workflowIds.invitations[index].communicationId,
|
|
58
|
+
consentId: workflowIds.invitations[index].consentId,
|
|
54
59
|
})),
|
|
55
60
|
})
|
|
56
61
|
|
|
@@ -64,7 +69,7 @@ for (const communication of invitations) {
|
|
|
64
69
|
// is separate from the draft inside Communication and is the actual grant.
|
|
65
70
|
const activeConsentBatch = buildVeterinaryResearchStudyActiveConsentBatch([{
|
|
66
71
|
consentId: activeConsentId,
|
|
67
|
-
researchStudyId:
|
|
72
|
+
researchStudyId: workflowIds.researchStudyId,
|
|
68
73
|
practitionerRoleId: protectedProfessional.practitionerRoleId,
|
|
69
74
|
actorRole: protectedProfessional.iscoRole,
|
|
70
75
|
date: new Date().toISOString().slice(0, 10),
|
|
@@ -75,13 +80,20 @@ const activeConsentBatch = buildVeterinaryResearchStudyActiveConsentBatch([{
|
|
|
75
80
|
// This succeeds only after protected state proves both prerequisites. Browser
|
|
76
81
|
// input must not be trusted as that proof.
|
|
77
82
|
const authorization = buildVeterinaryResearchStudySmartAuthorization({
|
|
78
|
-
researchStudyId:
|
|
83
|
+
researchStudyId: workflowIds.researchStudyId,
|
|
79
84
|
dcrCompleted: protectedProfessional.dcrCompleted,
|
|
80
85
|
consentStatus: protectedStudyConsent.status,
|
|
81
86
|
})
|
|
82
87
|
await professionalRuntime.requestSmartToken(authorization)
|
|
83
88
|
```
|
|
84
89
|
|
|
90
|
+
Persist the client-generated `operationId` with the UI operation state. A
|
|
91
|
+
retry passes the same UUID and the same set of exact PractitionerRole
|
|
92
|
+
references, producing the same ResearchStudy, Group, Communication and draft
|
|
93
|
+
Consent identifiers. This enables an idempotent, resumable saga after a
|
|
94
|
+
partial failure. It does not create a transaction across services, roll back
|
|
95
|
+
completed steps, or authorize any professional.
|
|
96
|
+
|
|
85
97
|
The controller may independently create a descriptive review team with
|
|
86
98
|
`buildVeterinaryResearchStudyGroupCreateEntry()` and append each registered
|
|
87
99
|
`PractitionerRole` with
|
|
@@ -129,7 +141,7 @@ Consent claims use:
|
|
|
129
141
|
|
|
130
142
|
The Group POST adds a governed business `Group.identifier` derived from the
|
|
131
143
|
ResearchStudy UUID. Use
|
|
132
|
-
`buildVeterinaryResearchStudyReviewTeamGroupSearch(
|
|
144
|
+
`buildVeterinaryResearchStudyReviewTeamGroupSearch(workflowIds.researchStudyId)` to recover the
|
|
133
145
|
same descriptive review-team Group after a refresh. The helper deterministically
|
|
134
146
|
re-derives a UUIDv5 using the SDK-exported, governed namespace and emits
|
|
135
147
|
`urn:ietf:rfc:3986|urn:uuid:<uuidv5>`. This one-way correlation is a standard
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vet-sdk-core-ts",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.17",
|
|
4
4
|
"description": "Browser-safe VetChain core contracts and governed animal species identifiers",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Connecting Solution & Applications Ltd",
|
|
@@ -64,6 +64,6 @@
|
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@noble/hashes": "^2.2.0",
|
|
66
66
|
"gdc-common-utils-ts": "2.9.4",
|
|
67
|
-
"vet-data-utils-ts": "0.5.
|
|
67
|
+
"vet-data-utils-ts": "0.5.3"
|
|
68
68
|
}
|
|
69
69
|
}
|