vet-sdk-core-ts 0.4.15 → 0.4.16

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
@@ -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`,
@@ -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;
@@ -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('veterinary_research_study_review_team_study_id_invalid');
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 researchGroupId = 'review-team'
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: study.id,
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: study.id,
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: communicationIds[index],
53
- consentId: draftConsentIds[index],
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: study.id,
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: study.id,
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(study.id)` to recover the
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.15",
3
+ "version": "0.4.16",
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",