@choiceapps/communityservices-sdk 0.6.0 → 0.7.0

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 (50) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/browser/enrollment-workflow.d.mts +25 -3
  3. package/browser/enrollment-workflow.mjs +75 -29
  4. package/browser/entry-correction.mjs +6 -4
  5. package/browser/entry-row-registration.mjs +63 -0
  6. package/browser/household-enrollment-discovery.mjs +49 -0
  7. package/browser/index.d.mts +3 -1
  8. package/browser/playwright-session.mjs +4 -3
  9. package/browser/workflow-error-codes.mjs +198 -0
  10. package/dist/cjs/mobile-preflight.d.ts +19 -0
  11. package/dist/cjs/mobile-preflight.d.ts.map +1 -0
  12. package/dist/cjs/mobile-preflight.js +75 -0
  13. package/dist/cjs/mobile-preflight.js.map +1 -0
  14. package/dist/cjs/v2.d.ts +2 -0
  15. package/dist/cjs/v2.d.ts.map +1 -1
  16. package/dist/cjs/v2.js +2 -0
  17. package/dist/cjs/v2.js.map +1 -1
  18. package/dist/cjs/v3.d.ts +2 -0
  19. package/dist/cjs/v3.d.ts.map +1 -1
  20. package/dist/cjs/v3.js +2 -0
  21. package/dist/cjs/v3.js.map +1 -1
  22. package/dist/cjs/write-labels.d.ts +13 -0
  23. package/dist/cjs/write-labels.d.ts.map +1 -0
  24. package/dist/cjs/write-labels.js +68 -0
  25. package/dist/cjs/write-labels.js.map +1 -0
  26. package/dist/esm/mobile-preflight.d.ts +19 -0
  27. package/dist/esm/mobile-preflight.d.ts.map +1 -0
  28. package/dist/esm/mobile-preflight.js +70 -0
  29. package/dist/esm/mobile-preflight.js.map +1 -0
  30. package/dist/esm/v2.d.ts +2 -0
  31. package/dist/esm/v2.d.ts.map +1 -1
  32. package/dist/esm/v2.js +2 -0
  33. package/dist/esm/v2.js.map +1 -1
  34. package/dist/esm/v3.d.ts +2 -0
  35. package/dist/esm/v3.d.ts.map +1 -1
  36. package/dist/esm/v3.js +2 -0
  37. package/dist/esm/v3.js.map +1 -1
  38. package/dist/esm/write-labels.d.ts +13 -0
  39. package/dist/esm/write-labels.d.ts.map +1 -0
  40. package/dist/esm/write-labels.js +65 -0
  41. package/dist/esm/write-labels.js.map +1 -0
  42. package/docs/assessments.md +31 -0
  43. package/docs/capabilities.md +13 -0
  44. package/docs/enrollments.md +38 -4
  45. package/docs/reference.md +3 -2
  46. package/docs/troubleshooting.md +17 -0
  47. package/examples/README.md +2 -0
  48. package/examples/checked-assessment-writes.mts +14 -0
  49. package/examples/enrollment-discovery-and-correction.mts +19 -0
  50. package/package.json +1 -1
@@ -111,13 +111,14 @@ export function createPlaywrightGwtSession({page,tenant,moduleBaseUrl}) {
111
111
  page.on('request',listener);
112
112
  try{await openClient(clientId);wire=await captured;}finally{page.off('request',listener);}
113
113
  if(!wire?.policy||Object.values(wire.headers).some(v=>!v))throw Error('Fresh enrollment list context unavailable');
114
- const rows=await mapped(`SELECT e.entryExitId AS enrollmentId, e.clientId AS clientId, e.entryDate AS projectStartDate, e.exitDate AS projectExitDate, e.providerId AS enrollmentProvider, e.typeEntryExit.name AS type FROM SpEntryExit AS e WHERE e.active = TRUE AND e.client.active = TRUE AND e.clientId = ${clientId} ORDER BY e.entryExitId`);
114
+ const rows=await mapped(`SELECT e.entryExitId AS enrollmentId, e.clientId AS clientId, e.entryDate AS projectStartDate, e.exitDate AS projectExitDate, e.providerId AS enrollmentProvider, e.typeEntryExit.name AS type, e.groupId AS groupId FROM SpEntryExit AS e WHERE e.active = TRUE AND e.client.active = TRUE AND e.clientId = ${clientId} ORDER BY e.entryExitId`);
115
115
  const ids=new Set();
116
116
  for(const row of rows){
117
117
  if(!Number.isSafeInteger(row.enrollmentId)||row.enrollmentId<=0||row.clientId!==clientId||!Number.isSafeInteger(row.enrollmentProvider)||row.enrollmentProvider<=0||typeof row.type!=='string'||!row.type.trim()||typeof row.projectStartDate!=='string'||!Number.isFinite(Date.parse(row.projectStartDate))||row.projectExitDate!==null&&(typeof row.projectExitDate!=='string'||!Number.isFinite(Date.parse(row.projectExitDate)))||ids.has(row.enrollmentId))throw Error('Invalid enrollment list');
118
118
  ids.add(row.enrollmentId);
119
+ if(row.groupId!==null){if(typeof row.groupId==='number'&&row.groupId<=0)throw Error('Invalid enrollment group');literal(row.groupId);}
119
120
  }
120
- return {source:'gwt-rpc',tenant,clientId,count:rows.length,values:rows.map(row=>({...row,isActive:true}))};
121
+ return {source:'gwt-rpc',tenant,clientId,count:rows.length,values:rows.map(row=>({...row,isActive:true,grouped:row.groupId!==null}))};
121
122
  };
122
123
  const readEnrollment=async target=>{
123
124
  const model=await openTarget(target),householdClientIds=await householdIds(target);
@@ -149,7 +150,7 @@ export function createPlaywrightGwtSession({page,tenant,moduleBaseUrl}) {
149
150
  reviews.push({reviewId:row.reviewId,type:row.type,reviewDate:row.reviewDate,clientIds:clients.map(c=>c.clientId).sort((a,b)=>a-b)});
150
151
  }
151
152
  reviews.sort((a,b)=>a.reviewId-b.reviewId);
152
- const data={source:'gwt-rpc',tenant,sessionId,...target,projectStartDate:entry.projectStartDate,providerId:entry.providerId,enrollmentType:entry.enrollmentType,householdClientIds,members,exits,reviews};
153
+ const data={source:'gwt-rpc',tenant,sessionId,...target,projectStartDate:entry.projectStartDate,providerId:entry.providerId,enrollmentType:entry.enrollmentType,groupId:entry.groupId,grouped:entry.groupId!==null,householdClientIds,members,exits,reviews};
153
154
  return {...data,revision:digest(model+JSON.stringify({...data,sessionId:undefined}))};
154
155
  };
155
156
  const chooseMembers=async(desired,implicitClientId,dialogButton)=>{
@@ -0,0 +1,198 @@
1
+ // Stable public codes. Keep code values when changing their associated messages.
2
+ const codes = new Map([
3
+ ["DOB and household head corrections require their dedicated workflows", "DOB_AND_HOUSEHOLD_HEAD_CORRECTIONS_REQUIRE_THEIR_DEDICATED_WORKFLOWS"],
4
+ ["DOB helper required for birth-date answers and carry-forward", "DOB_HELPER_REQUIRED_FOR_BIRTH_DATE_ANSWERS_AND_CARRY_FORWARD"],
5
+ ["DOB question type changed", "DOB_QUESTION_TYPE_CHANGED"],
6
+ ["REST API, discovery, enrollment helpers and durable store required", "REST_API_DISCOVERY_ENROLLMENT_HELPERS_AND_DURABLE_STORE_REQUIRED"],
7
+ ["active client required", "ACTIVE_CLIENT_REQUIRED"],
8
+ ["active household required", "ACTIVE_HOUSEHOLD_REQUIRED"],
9
+ ["active household required for adoption", "ACTIVE_HOUSEHOLD_REQUIRED_FOR_ADOPTION"],
10
+ ["active operational program required", "ACTIVE_OPERATIONAL_PROGRAM_REQUIRED"],
11
+ ["added enrollment identity changed", "ADDED_ENROLLMENT_IDENTITY_CHANGED"],
12
+ ["addition requires new household members", "ADDITION_REQUIRES_NEW_HOUSEHOLD_MEMBERS"],
13
+ ["additional member already has an independent enrollment", "ADDITIONAL_MEMBER_ALREADY_HAS_AN_INDEPENDENT_ENROLLMENT"],
14
+ ["additional member assessment configuration changed", "ADDITIONAL_MEMBER_ASSESSMENT_CONFIGURATION_CHANGED"],
15
+ ["additional member entry must follow original entry", "ADDITIONAL_MEMBER_ENTRY_MUST_FOLLOW_ORIGINAL_ENTRY"],
16
+ ["adopted enrollment identity or configuration changed", "ADOPTED_ENROLLMENT_IDENTITY_OR_CONFIGURATION_CHANGED"],
17
+ ["adoption enrollment context mismatch", "ADOPTION_ENROLLMENT_CONTEXT_MISMATCH"],
18
+ ["adoption exit precedes entry", "ADOPTION_EXIT_PRECEDES_ENTRY"],
19
+ ["adoption household head mismatch", "ADOPTION_HOUSEHOLD_HEAD_MISMATCH"],
20
+ ["adoption household membership mismatch", "ADOPTION_HOUSEHOLD_MEMBERSHIP_MISMATCH"],
21
+ ["adoption key conflicts with previous intent", "ADOPTION_KEY_CONFLICTS_WITH_PREVIOUS_INTENT"],
22
+ ["adoption requires the original enrollment head relationship", "ADOPTION_REQUIRES_THE_ORIGINAL_ENROLLMENT_HEAD_RELATIONSHIP"],
23
+ ["ambiguous assessment read", "AMBIGUOUS_ASSESSMENT_READ"],
24
+ ["ambiguous closure snapshot", "AMBIGUOUS_CLOSURE_SNAPSHOT"],
25
+ ["ambiguous correction assessment read", "AMBIGUOUS_CORRECTION_ASSESSMENT_READ"],
26
+ ["ambiguous correction row", "AMBIGUOUS_CORRECTION_ROW"],
27
+ ["ambiguous correction row cell", "AMBIGUOUS_CORRECTION_ROW_CELL"],
28
+ ["ambiguous household membership", "AMBIGUOUS_HOUSEHOLD_MEMBERSHIP"],
29
+ ["another unfinished household workflow must be reconciled first", "ANOTHER_UNFINISHED_HOUSEHOLD_WORKFLOW_MUST_BE_RECONCILED_FIRST"],
30
+ ["another unfinished workflow must be reconciled first", "ANOTHER_UNFINISHED_WORKFLOW_MUST_BE_RECONCILED_FIRST"],
31
+ ["assessment locations must be a map by Entry/Exit type", "ASSESSMENT_LOCATIONS_MUST_BE_A_MAP_BY_ENTRY_EXIT_TYPE"],
32
+ ["assessment metadata incomplete", "ASSESSMENT_METADATA_INCOMPLETE"],
33
+ ["assessment plan required", "ASSESSMENT_PLAN_REQUIRED"],
34
+ ["attempted correction required for resolution", "ATTEMPTED_CORRECTION_REQUIRED_FOR_RESOLUTION"],
35
+ ["authoritative adoption exit date required", "AUTHORITATIVE_ADOPTION_EXIT_DATE_REQUIRED"],
36
+ ["authoritative enrollment session required for adoption", "AUTHORITATIVE_ENROLLMENT_SESSION_REQUIRED_FOR_ADOPTION"],
37
+ ["authoritative exit details reader required", "AUTHORITATIVE_EXIT_DETAILS_READER_REQUIRED"],
38
+ ["authoritative exit read required", "AUTHORITATIVE_EXIT_READ_REQUIRED"],
39
+ ["authoritative head of household exit dates required", "AUTHORITATIVE_HEAD_OF_HOUSEHOLD_EXIT_DATES_REQUIRED"],
40
+ ["authoritative individual exit read required", "AUTHORITATIVE_INDIVIDUAL_EXIT_READ_REQUIRED"],
41
+ ["carry-forward birth date changed since enrollment", "CARRY_FORWARD_BIRTH_DATE_CHANGED_SINCE_ENROLLMENT"],
42
+ ["carry-forward birth date is empty or follows assessment date", "CARRY_FORWARD_BIRTH_DATE_IS_EMPTY_OR_FOLLOWS_ASSESSMENT_DATE"],
43
+ ["carry-forward field is empty", "CARRY_FORWARD_FIELD_IS_EMPTY"],
44
+ ["client ID required", "CLIENT_ID_REQUIRED"],
45
+ ["client, provider and supported Entry/Exit type required", "CLIENT_PROVIDER_AND_SUPPORTED_ENTRY_EXIT_TYPE_REQUIRED"],
46
+ ["conflicting field coverage", "CONFLICTING_FIELD_COVERAGE"],
47
+ ["correction answer could not be encoded", "CORRECTION_ANSWER_COULD_NOT_BE_ENCODED"],
48
+ ["correction key conflicts with previous intent", "CORRECTION_KEY_CONFLICTS_WITH_PREVIOUS_INTENT"],
49
+ ["correction lookup could not be resolved", "CORRECTION_LOOKUP_COULD_NOT_BE_RESOLVED"],
50
+ ["correction requires an existing entry row key and matching parent", "CORRECTION_REQUIRES_AN_EXISTING_ENTRY_ROW_KEY_AND_MATCHING_PARENT"],
51
+ ["correction row metadata incomplete", "CORRECTION_ROW_METADATA_INCOMPLETE"],
52
+ ["correction scalar read missing", "CORRECTION_SCALAR_READ_MISSING"],
53
+ ["correction state changed during resolution", "CORRECTION_STATE_CHANGED_DURING_RESOLUTION"],
54
+ ["correction state differs from its pre-write snapshot; non-application cannot be resolved", "CORRECTION_STATE_DIFFERS_FROM_ITS_PRE_WRITE_SNAPSHOT_NON_APPLICATION_CANNOT_BE_RESOLVED"],
55
+ ["correction subassessment read missing", "CORRECTION_SUBASSESSMENT_READ_MISSING"],
56
+ ["correction was resolved as not applied; use a new key for a corrected intent", "CORRECTION_WAS_RESOLVED_AS_NOT_APPLIED_USE_A_NEW_KEY_FOR_A_CORRECTED_INTENT"],
57
+ ["destination and reason for leaving required", "DESTINATION_AND_REASON_FOR_LEAVING_REQUIRED"],
58
+ ["duplicate adoption targets", "DUPLICATE_ADOPTION_TARGETS"],
59
+ ["duplicate correction journal keys", "DUPLICATE_CORRECTION_JOURNAL_KEYS"],
60
+ ["duplicate household journal keys", "DUPLICATE_HOUSEHOLD_JOURNAL_KEYS"],
61
+ ["duplicate household member enrollment", "DUPLICATE_HOUSEHOLD_MEMBER_ENROLLMENT"],
62
+ ["duplicate household review IDs", "DUPLICATE_HOUSEHOLD_REVIEW_IDS"],
63
+ ["duplicate journal keys", "DUPLICATE_JOURNAL_KEYS"],
64
+ ["duplicate matching enrollments", "DUPLICATE_MATCHING_ENROLLMENTS"],
65
+ ["duplicate matching subassessment rows", "DUPLICATE_MATCHING_SUBASSESSMENT_ROWS"],
66
+ ["duplicate semantic subassessment rows", "DUPLICATE_SEMANTIC_SUBASSESSMENT_ROWS"],
67
+ ["duplicate subassessment key", "DUPLICATE_SUBASSESSMENT_KEY"],
68
+ ["empty entry correction", "EMPTY_ENTRY_CORRECTION"],
69
+ ["empty row correction", "EMPTY_ROW_CORRECTION"],
70
+ ["end date precedes record start", "END_DATE_PRECEDES_RECORD_START"],
71
+ ["end-date target interval missing", "END_DATE_TARGET_INTERVAL_MISSING"],
72
+ ["end-date target missing", "END_DATE_TARGET_MISSING"],
73
+ ["enrollment already journaled; use its existing entry key", "ENROLLMENT_ALREADY_JOURNALED_USE_ITS_EXISTING_ENTRY_KEY"],
74
+ ["enrollment count unavailable or changed", "ENROLLMENT_COUNT_UNAVAILABLE_OR_CHANGED"],
75
+ ["enrollment list context mismatch", "ENROLLMENT_LIST_CONTEXT_MISMATCH"],
76
+ ["enrollment list exceeds limit", "ENROLLMENT_LIST_EXCEEDS_LIMIT"],
77
+ ["entry and interim assessment locations required; exit location must be nonempty when supplied", "ENTRY_AND_INTERIM_ASSESSMENT_LOCATIONS_REQUIRED_EXIT_LOCATION_MUST_BE_NONEMPTY_WHEN_SUPPLIED"],
78
+ ["entry changed since correction preflight", "ENTRY_CHANGED_SINCE_CORRECTION_PREFLIGHT"],
79
+ ["entry correction did not verify", "ENTRY_CORRECTION_DID_NOT_VERIFY"],
80
+ ["entry correction enrollment context mismatch", "ENTRY_CORRECTION_ENROLLMENT_CONTEXT_MISMATCH"],
81
+ ["entry correction must precede interim or exit workflows", "ENTRY_CORRECTION_MUST_PRECEDE_INTERIM_OR_EXIT_WORKFLOWS"],
82
+ ["entry correction requires an open enrollment without reviews", "ENTRY_CORRECTION_REQUIRES_AN_OPEN_ENROLLMENT_WITHOUT_REVIEWS"],
83
+ ["existing exit conflicts with requested exit", "EXISTING_EXIT_CONFLICTS_WITH_REQUESTED_EXIT"],
84
+ ["existing household review conflict", "EXISTING_HOUSEHOLD_REVIEW_CONFLICT"],
85
+ ["exit UI and authoritative enrollment session required", "EXIT_UI_AND_AUTHORITATIVE_ENROLLMENT_SESSION_REQUIRED"],
86
+ ["exit UI, destination and reason for leaving required", "EXIT_UI_DESTINATION_AND_REASON_FOR_LEAVING_REQUIRED"],
87
+ ["exit assessment configuration changed", "EXIT_ASSESSMENT_CONFIGURATION_CHANGED"],
88
+ ["exit identity changed", "EXIT_IDENTITY_CHANGED"],
89
+ ["exit identity missing", "EXIT_IDENTITY_MISSING"],
90
+ ["exit precedes an existing review", "EXIT_PRECEDES_AN_EXISTING_REVIEW"],
91
+ ["exit subgroup identity changed", "EXIT_SUBGROUP_IDENTITY_CHANGED"],
92
+ ["exit subgroup membership mismatch", "EXIT_SUBGROUP_MEMBERSHIP_MISMATCH"],
93
+ ["explicit enrollment targets required", "EXPLICIT_ENROLLMENT_TARGETS_REQUIRED"],
94
+ ["head and household members required", "HEAD_AND_HOUSEHOLD_MEMBERS_REQUIRED"],
95
+ ["head client ID required", "HEAD_CLIENT_ID_REQUIRED"],
96
+ ["head of household assessment option unresolved", "HEAD_OF_HOUSEHOLD_ASSESSMENT_OPTION_UNRESOLVED"],
97
+ ["head of household change is a red flag; use a separate retroactive correction for a member with the same first entry date, or exit all enrollments and create a fresh enrollment", "HEAD_OF_HOUSEHOLD_CHANGE_IS_A_RED_FLAG_USE_A_SEPARATE_RETROACTIVE_CORRECTION_FOR_A_MEMBER_WITH"],
98
+ ["household API and authoritative enrollment session required", "HOUSEHOLD_API_AND_AUTHORITATIVE_ENROLLMENT_SESSION_REQUIRED"],
99
+ ["household enrollment context mismatch", "HOUSEHOLD_ENROLLMENT_CONTEXT_MISMATCH"],
100
+ ["household enrollment identity changed", "HOUSEHOLD_ENROLLMENT_IDENTITY_CHANGED"],
101
+ ["household enrollment members changed", "HOUSEHOLD_ENROLLMENT_MEMBERS_CHANGED"],
102
+ ["household exits are not linked", "HOUSEHOLD_EXITS_ARE_NOT_LINKED"],
103
+ ["household head mismatch; head changes are a red flag and require separate retroactive correction or a fresh enrollment", "HOUSEHOLD_HEAD_MISMATCH_HEAD_CHANGES_ARE_A_RED_FLAG_AND_REQUIRE_SEPARATE_RETROACTIVE"],
104
+ ["household key conflicts with previous intent", "HOUSEHOLD_KEY_CONFLICTS_WITH_PREVIOUS_INTENT"],
105
+ ["household members unavailable", "HOUSEHOLD_MEMBERS_UNAVAILABLE"],
106
+ ["household membership changed while acquiring locks; retry", "HOUSEHOLD_MEMBERSHIP_CHANGED_WHILE_ACQUIRING_LOCKS_RETRY"],
107
+ ["household review identity changed", "HOUSEHOLD_REVIEW_IDENTITY_CHANGED"],
108
+ ["household review membership mismatch", "HOUSEHOLD_REVIEW_MEMBERSHIP_MISMATCH"],
109
+ ["household reviews unavailable", "HOUSEHOLD_REVIEWS_UNAVAILABLE"],
110
+ ["household, original head and complete enrollment cohort required", "HOUSEHOLD_ORIGINAL_HEAD_AND_COMPLETE_ENROLLMENT_COHORT_REQUIRED"],
111
+ ["household, program and supported Entry/Exit type required", "HOUSEHOLD_PROGRAM_AND_SUPPORTED_ENTRY_EXIT_TYPE_REQUIRED"],
112
+ ["incomplete enrollment list", "INCOMPLETE_ENROLLMENT_LIST"],
113
+ ["incomplete existing exit metadata", "INCOMPLETE_EXISTING_EXIT_METADATA"],
114
+ ["independent non-application confirmation required", "INDEPENDENT_CONFIRMATION_REQUIRED"],
115
+ ["individual enrollment context mismatch", "INDIVIDUAL_ENROLLMENT_CONTEXT_MISMATCH"],
116
+ ["individual exit membership mismatch", "INDIVIDUAL_EXIT_MEMBERSHIP_MISMATCH"],
117
+ ["interim did not verify", "INTERIM_DID_NOT_VERIFY"],
118
+ ["invalid assessment plan", "INVALID_ASSESSMENT_PLAN"],
119
+ ["invalid correction client or rows", "INVALID_CORRECTION_CLIENT_OR_ROWS"],
120
+ ["invalid correction date", "INVALID_CORRECTION_DATE"],
121
+ ["invalid correction effective date", "INVALID_CORRECTION_EFFECTIVE_DATE"],
122
+ ["invalid enrollment data: adopted members require matching program and head coverage", "INVALID_ENROLLMENT_DATA_ADOPTED_MEMBERS_REQUIRE_MATCHING_PROGRAM_AND_HEAD_COVERAGE"],
123
+ ["invalid enrollment data: enrollment has no head of household", "INVALID_ENROLLMENT_DATA_ENROLLMENT_HAS_NO_HEAD_OF_HOUSEHOLD"],
124
+ ["invalid enrollment data: head of household relationship is missing or cleared", "INVALID_ENROLLMENT_DATA_HEAD_OF_HOUSEHOLD_RELATIONSHIP_IS_MISSING_OR_CLEARED"],
125
+ ["invalid enrollment data: household has no active head of household", "INVALID_ENROLLMENT_DATA_HOUSEHOLD_HAS_NO_ACTIVE_HEAD_OF_HOUSEHOLD"],
126
+ ["invalid enrollment data: member enrollment would exist without an enrolled head of household; the original head must remain enrolled through every member exit; reconcile the data or exit all members and create a fresh enrollment", "INVALID_ENROLLMENT_DATA_MEMBER_ENROLLMENT_WOULD_EXIST_WITHOUT_AN_ENROLLED_HEAD_OF_HOUSEHOLD"],
127
+ ["invalid enrollment list", "INVALID_ENROLLMENT_LIST"],
128
+ ["invalid household enrollment members", "INVALID_HOUSEHOLD_ENROLLMENT_MEMBERS"],
129
+ ["invalid household journal", "INVALID_HOUSEHOLD_JOURNAL"],
130
+ ["invalid or conflicting correction answers and clears", "INVALID_OR_CONFLICTING_CORRECTION_ANSWERS_AND_CLEARS"],
131
+ ["invalid or duplicate end-date target", "INVALID_OR_DUPLICATE_END_DATE_TARGET"],
132
+ ["invalid or reused subassessment ID", "INVALID_OR_REUSED_SUBASSESSMENT_ID"],
133
+ ["invalid stored correction date", "INVALID_STORED_CORRECTION_DATE"],
134
+ ["invalid stored correction number", "INVALID_STORED_CORRECTION_NUMBER"],
135
+ ["invalid subassessment plan", "INVALID_SUBASSESSMENT_PLAN"],
136
+ ["invalid workflow journal", "INVALID_WORKFLOW_JOURNAL"],
137
+ ["key conflicts with previous intent", "KEY_CONFLICTS_WITH_PREVIOUS_INTENT"],
138
+ ["linked household enrollment requires exitHousehold", "LINKED_HOUSEHOLD_ENROLLMENT_REQUIRES_EXITHOUSEHOLD"],
139
+ ["mandatory correction field could not be encoded", "MANDATORY_CORRECTION_FIELD_COULD_NOT_BE_ENCODED"],
140
+ ["mandatory correction field requires an explicit answer or clear", "MANDATORY_CORRECTION_FIELD_REQUIRES_AN_EXPLICIT_ANSWER_OR_CLEAR"],
141
+ ["mandatory correction lookup label unavailable", "MANDATORY_CORRECTION_LOOKUP_LABEL_UNAVAILABLE"],
142
+ ["matching correction journal required", "MATCHING_CORRECTION_JOURNAL_REQUIRED"],
143
+ ["member already has an independent matching enrollment", "MEMBER_ALREADY_HAS_AN_INDEPENDENT_MATCHING_ENROLLMENT"],
144
+ ["member client and assessment required", "MEMBER_CLIENT_AND_ASSESSMENT_REQUIRED"],
145
+ ["member enrollment has not been attached", "MEMBER_ENROLLMENT_HAS_NOT_BEEN_ATTACHED"],
146
+ ["member entry date port required", "MEMBER_ENTRY_DATE_PORT_REQUIRED"],
147
+ ["member exit identity missing", "MEMBER_EXIT_IDENTITY_MISSING"],
148
+ ["member is not active in household at entry", "MEMBER_IS_NOT_ACTIVE_IN_HOUSEHOLD_AT_ENTRY"],
149
+ ["multiple end-date overrides", "MULTIPLE_END_DATE_OVERRIDES"],
150
+ ["new member must remain enrolled", "NEW_MEMBER_MUST_REMAIN_ENROLLED"],
151
+ ["not-applicable explanation required", "NOT_APPLICABLE_EXPLANATION_REQUIRED"],
152
+ ["open household enrollment required to add a member", "OPEN_HOUSEHOLD_ENROLLMENT_REQUIRED_TO_ADD_A_MEMBER"],
153
+ ["partial existing household exit requires reconciliation", "PARTIAL_EXISTING_HOUSEHOLD_EXIT_REQUIRES_RECONCILIATION"],
154
+ ["partial household review requires reconciliation", "PARTIAL_HOUSEHOLD_REVIEW_REQUIRES_RECONCILIATION"],
155
+ ["program and supported Entry/Exit type required", "PROGRAM_AND_SUPPORTED_ENTRY_EXIT_TYPE_REQUIRED"],
156
+ ["program assessment configuration changed", "PROGRAM_ASSESSMENT_CONFIGURATION_CHANGED"],
157
+ ["program assessment default unresolved", "PROGRAM_ASSESSMENT_DEFAULT_UNRESOLVED"],
158
+ ["provider discovery mismatch", "PROVIDER_DISCOVERY_MISMATCH"],
159
+ ["record already has a different end date", "RECORD_ALREADY_HAS_A_DIFFERENT_END_DATE"],
160
+ ["resolution evidence reference changed", "RESOLUTION_EVIDENCE_REFERENCE_CHANGED"],
161
+ ["review follows project exit", "REVIEW_FOLLOWS_PROJECT_EXIT"],
162
+ ["review precedes entry", "REVIEW_PRECEDES_ENTRY"],
163
+ ["row closure must fall between entry and assessment", "ROW_CLOSURE_MUST_FALL_BETWEEN_ENTRY_AND_ASSESSMENT"],
164
+ ["saved enrollment missing", "SAVED_ENROLLMENT_MISSING"],
165
+ ["saved enrollment missing or changed", "SAVED_ENROLLMENT_MISSING_OR_CHANGED"],
166
+ ["stable correction key required", "STABLE_CORRECTION_KEY_REQUIRED"],
167
+ ["stable key required", "STABLE_KEY_REQUIRED"],
168
+ ["standard assessment writes and authoritative enrollment reader required for corrections", "STANDARD_ASSESSMENT_WRITES_AND_AUTHORITATIVE_ENROLLMENT_READER_REQUIRED_FOR_CORRECTIONS"],
169
+ ["stored exit details conflict with requested exit", "STORED_EXIT_DETAILS_CONFLICT_WITH_REQUESTED_EXIT"],
170
+ ["subassessment read missing", "SUBASSESSMENT_READ_MISSING"],
171
+ ["timezone-qualified whole-second date required", "TIMEZONE_QUALIFIED_WHOLE_SECOND_DATE_REQUIRED"],
172
+ ["uncertain entry correction requires reconciliation", "UNCERTAIN_ENTRY_CORRECTION_REQUIRES_RECONCILIATION"],
173
+ ["uncertain household exit requires reconciliation", "UNCERTAIN_HOUSEHOLD_EXIT_REQUIRES_RECONCILIATION"],
174
+ ["uncertain member entry date requires reconciliation", "UNCERTAIN_MEMBER_ENTRY_DATE_REQUIRES_RECONCILIATION"],
175
+ ["uncertain write requires reconciliation", "UNCERTAIN_WRITE_REQUIRES_RECONCILIATION"],
176
+ ["unfinished household workflow must be reconciled first", "UNFINISHED_HOUSEHOLD_WORKFLOW_MUST_BE_RECONCILED_FIRST"],
177
+ ["unique members including head required", "UNIQUE_MEMBERS_INCLUDING_HEAD_REQUIRED"],
178
+ ["unknown carry-forward field", "UNKNOWN_CARRY_FORWARD_FIELD"],
179
+ ["unknown or invalid correction fields", "UNKNOWN_OR_INVALID_CORRECTION_FIELDS"],
180
+ ["unknown or invalid input fields", "UNKNOWN_OR_INVALID_INPUT_FIELDS"],
181
+ ["unselected household member changed", "UNSELECTED_HOUSEHOLD_MEMBER_CHANGED"],
182
+ ["unselected member exit identity missing", "UNSELECTED_MEMBER_EXIT_IDENTITY_MISSING"],
183
+ ["unsupported Entry/Exit type in assessment locations", "UNSUPPORTED_ENTRY_EXIT_TYPE_IN_ASSESSMENT_LOCATIONS"],
184
+ ["unsupported correction field type", "UNSUPPORTED_CORRECTION_FIELD_TYPE"],
185
+ ["unsupported mandatory correction field", "UNSUPPORTED_MANDATORY_CORRECTION_FIELD"],
186
+ ["verified correction cannot be rejected", "VERIFIED_CORRECTION_CANNOT_BE_REJECTED"],
187
+ ["verified enrollment workflow required", "VERIFIED_ENROLLMENT_WORKFLOW_REQUIRED"],
188
+ ["verified entry correction changed", "VERIFIED_ENTRY_CORRECTION_CHANGED"],
189
+ ["verified exit missing", "VERIFIED_EXIT_MISSING"],
190
+ ["verified household entry required", "VERIFIED_HOUSEHOLD_ENTRY_REQUIRED"],
191
+ ["verified household entry with identical members required; unknown selected member", "VERIFIED_HOUSEHOLD_ENTRY_WITH_IDENTICAL_MEMBERS_REQUIRED_UNKNOWN_SELECTED_MEMBER"],
192
+ ["verified row correction changed", "VERIFIED_ROW_CORRECTION_CHANGED"],
193
+ ["yes/no answers cannot be cleared", "YES_NO_ANSWERS_CANNOT_BE_CLEARED"],
194
+ ["yes/no write mode changed since this workflow started", "YES_NO_WRITE_MODE_CHANGED_SINCE_THIS_WORKFLOW_STARTED"],
195
+ ["yesNoWrites must be mobile, or standard with writeLabel and assessments.create", "YESNOWRITES_MUST_BE_MOBILE_OR_STANDARD_WITH_WRITELABEL_AND_ASSESSMENTS_CREATE"]
196
+ ]);
197
+
198
+ export const workflowErrorCode = reason => codes.get(reason) ?? "WORKFLOW_FAILED";
@@ -0,0 +1,19 @@
1
+ import type { WellSkyClient } from './generated/v3.js';
2
+ import { ValidationError } from './errors.js';
3
+ type RawMobileWrite = Parameters<WellSkyClient['assessmentsMobile']['addAnswer']>[0];
4
+ export type CheckedMobileWriteOptions = Omit<RawMobileWrite, 'query'> & {
5
+ query: {
6
+ dateEffective: string;
7
+ };
8
+ };
9
+ export type MobilePreflightCode = 'MOBILE_PREFLIGHT_INPUT_INVALID' | 'MOBILE_PREFLIGHT_READ_FAILED' | 'MOBILE_PREFLIGHT_UNAVAILABLE' | 'MOBILE_ANSWER_EXISTS' | 'MOBILE_PREFLIGHT_CANCELLED';
10
+ export declare class MobileAnswerPreflightError extends ValidationError {
11
+ readonly code: MobilePreflightCode;
12
+ readonly committed = false;
13
+ constructor(code: MobilePreflightCode, fields?: readonly string[], cause?: unknown);
14
+ }
15
+ /** Read-before-write protection for scalar inserts, including questions shared by instruments.
16
+ * Does not journal, retry writes, or make the read/write pair atomic. */
17
+ export declare function writeMobileAnswersChecked(api: Pick<WellSkyClient, 'assessments' | 'assessmentsMobile'>, options: CheckedMobileWriteOptions): ReturnType<WellSkyClient['assessmentsMobile']['addAnswer']>;
18
+ export {};
19
+ //# sourceMappingURL=mobile-preflight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mobile-preflight.d.ts","sourceRoot":"","sources":["../../src/mobile-preflight.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAY,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAAC,eAAe,EAAC,MAAM,aAAa,CAAC;AAK5C,KAAK,cAAc,GAAG,UAAU,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrF,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,cAAc,EAAC,OAAO,CAAC,GAAG;IAAC,KAAK,EAAC;QAAC,aAAa,EAAC,MAAM,CAAA;KAAC,CAAA;CAAC,CAAC;AACtG,MAAM,MAAM,mBAAmB,GAAG,gCAAgC,GAAC,8BAA8B,GAAC,8BAA8B,GAAC,sBAAsB,GAAC,4BAA4B,CAAC;AACrL,qBAAa,0BAA2B,SAAQ,eAAe;IAElD,QAAQ,CAAC,IAAI,EAAC,mBAAmB;IAD7C,QAAQ,CAAC,SAAS,SAAO;gBACJ,IAAI,EAAC,mBAAmB,EAAC,MAAM,GAAC,SAAS,MAAM,EAAK,EAAC,KAAK,CAAC,EAAC,OAAO;CAIxF;AAED;yEACyE;AACzE,wBAAsB,yBAAyB,CAC9C,GAAG,EAAC,IAAI,CAAC,aAAa,EAAC,aAAa,GAAC,mBAAmB,CAAC,EACzD,OAAO,EAAC,yBAAyB,GAChC,UAAU,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAC,WAAW,CAAC,CAAC,CAkC5D"}
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MobileAnswerPreflightError = void 0;
4
+ exports.writeMobileAnswersChecked = writeMobileAnswersChecked;
5
+ const errors_js_1 = require("./errors.js");
6
+ const encoding_js_1 = require("./encoding.js");
7
+ const runtime_js_1 = require("./runtime.js");
8
+ const validation_js_1 = require("./validation.js");
9
+ class MobileAnswerPreflightError extends errors_js_1.ValidationError {
10
+ code;
11
+ committed = false;
12
+ constructor(code, fields = [], cause) {
13
+ super('mobile assessment preflight', fields);
14
+ this.code = code;
15
+ this.name = 'MobileAnswerPreflightError';
16
+ if (cause !== undefined)
17
+ Object.defineProperty(this, 'cause', { value: cause, enumerable: false });
18
+ }
19
+ }
20
+ exports.MobileAnswerPreflightError = MobileAnswerPreflightError;
21
+ /** Read-before-write protection for scalar inserts, including questions shared by instruments.
22
+ * Does not journal, retry writes, or make the read/write pair atomic. */
23
+ async function writeMobileAnswersChecked(api, options) {
24
+ function fail(code, fields = [], cause) { throw new MobileAnswerPreflightError(code, fields, cause); }
25
+ const checkSignal = () => { if (options.signal?.aborted)
26
+ fail('MOBILE_PREFLIGHT_CANCELLED', [], options.signal.reason); };
27
+ if (!options?.path || !Number.isSafeInteger(options.path.clientId) || options.path.clientId <= 0 || typeof options.path.assessmentCode !== 'string' || !options.path.assessmentCode.trim() || !Array.isArray(options.body) || !options.body.length || typeof options.query?.dateEffective !== 'string')
28
+ fail('MOBILE_PREFLIGHT_INPUT_INVALID');
29
+ const instant = options.query.dateEffective;
30
+ if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.000)?(?:Z|[+-]\d{2}:\d{2})$/.test(instant) || !Number.isFinite((0, encoding_js_1.parseRestInstant)(instant)) || new Date(instant.slice(0, 19) + 'Z').toISOString().slice(0, 19) !== instant.slice(0, 19))
31
+ fail('MOBILE_PREFLIGHT_INPUT_INVALID', ['dateEffective']);
32
+ checkSignal();
33
+ const captured = { ...options, path: { ...options.path }, query: { ...options.query }, body: structuredClone(options.body) };
34
+ const scalars = [], seen = new Set(), ids = new Set();
35
+ for (const q of captured.body) {
36
+ if (!(0, validation_js_1.isRecord)(q) || typeof q.question_type !== 'string' || typeof q.virt_field_name !== 'string' || !q.virt_field_name || typeof q.question_id !== 'number' || !Number.isSafeInteger(q.question_id) || q.question_id <= 0 || seen.has(q.virt_field_name) || ids.has(q.question_id))
37
+ fail('MOBILE_PREFLIGHT_INPUT_INVALID');
38
+ seen.add(q.virt_field_name);
39
+ ids.add(q.question_id);
40
+ if (q.question_type !== 'sub_assessment')
41
+ scalars.push({ id: q.question_id, field: q.virt_field_name, type: q.question_type });
42
+ }
43
+ if (scalars.length) {
44
+ let response;
45
+ try {
46
+ response = await api.assessments.get({ path: captured.path, query: { dateEffective: new Date((0, encoding_js_1.parseRestInstant)(instant) + 1000).toISOString() }, ...(options.signal ? { signal: options.signal } : {}), ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }), ...(options.correlationId === undefined ? {} : { correlationId: options.correlationId }) });
47
+ }
48
+ catch (cause) {
49
+ fail('MOBILE_PREFLIGHT_READ_FAILED', scalars.map(q => q.field), cause);
50
+ }
51
+ const rows = (0, runtime_js_1.toItems)(response), conflicts = [];
52
+ for (const q of scalars) {
53
+ const found = rows.filter(r => r.virt_field_name === q.field);
54
+ if (found.length !== 1 || found[0]?.question_id !== q.id || found[0]?.question_type !== q.type)
55
+ fail('MOBILE_PREFLIGHT_UNAVAILABLE', [q.field]);
56
+ const row = found[0];
57
+ if (!row)
58
+ fail('MOBILE_PREFLIGHT_UNAVAILABLE', [q.field]);
59
+ if (row.date_effective !== null && row.date_effective !== undefined && row.date_effective !== '') {
60
+ const recorded = (0, encoding_js_1.parseRestInstant)(row.date_effective);
61
+ if (!Number.isFinite(recorded) || recorded > (0, encoding_js_1.parseRestInstant)(instant))
62
+ fail('MOBILE_PREFLIGHT_UNAVAILABLE', [q.field]);
63
+ if (recorded === (0, encoding_js_1.parseRestInstant)(instant))
64
+ conflicts.push(q.field);
65
+ }
66
+ else if (row.answer_value !== null && row.answer_value !== undefined && row.answer_value !== '')
67
+ fail('MOBILE_PREFLIGHT_UNAVAILABLE', [q.field]);
68
+ }
69
+ if (conflicts.length)
70
+ fail('MOBILE_ANSWER_EXISTS', conflicts);
71
+ }
72
+ checkSignal();
73
+ return api.assessmentsMobile.addAnswer(captured);
74
+ }
75
+ //# sourceMappingURL=mobile-preflight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mobile-preflight.js","sourceRoot":"","sources":["../../src/mobile-preflight.ts"],"names":[],"mappings":"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","sourcesContent":["import type {WellSkyClient,Operations} from './generated/v3.js';\nimport {ValidationError} from './errors.js';\nimport {parseRestInstant} from './encoding.js';\nimport {toItems} from './runtime.js';\nimport {isRecord} from './validation.js';\n\ntype RawMobileWrite = Parameters<WellSkyClient['assessmentsMobile']['addAnswer']>[0];\nexport type CheckedMobileWriteOptions = Omit<RawMobileWrite,'query'> & {query:{dateEffective:string}};\nexport type MobilePreflightCode = 'MOBILE_PREFLIGHT_INPUT_INVALID'|'MOBILE_PREFLIGHT_READ_FAILED'|'MOBILE_PREFLIGHT_UNAVAILABLE'|'MOBILE_ANSWER_EXISTS'|'MOBILE_PREFLIGHT_CANCELLED';\nexport class MobileAnswerPreflightError extends ValidationError {\n readonly committed=false;\n constructor(readonly code:MobilePreflightCode,fields:readonly string[]=[],cause?:unknown){\n super('mobile assessment preflight',fields);this.name='MobileAnswerPreflightError';\n if(cause!==undefined)Object.defineProperty(this,'cause',{value:cause,enumerable:false});\n }\n}\n\n/** Read-before-write protection for scalar inserts, including questions shared by instruments.\n * Does not journal, retry writes, or make the read/write pair atomic. */\nexport async function writeMobileAnswersChecked(\n api:Pick<WellSkyClient,'assessments'|'assessmentsMobile'>,\n options:CheckedMobileWriteOptions,\n):ReturnType<WellSkyClient['assessmentsMobile']['addAnswer']>{\n function fail(code:MobilePreflightCode,fields:readonly string[]=[],cause?:unknown):never{throw new MobileAnswerPreflightError(code,fields,cause);}\n const checkSignal=()=>{if(options.signal?.aborted)fail('MOBILE_PREFLIGHT_CANCELLED',[],options.signal.reason);};\n if(!options?.path||!Number.isSafeInteger(options.path.clientId)||options.path.clientId<=0||typeof options.path.assessmentCode!=='string'||!options.path.assessmentCode.trim()||!Array.isArray(options.body)||!options.body.length||typeof options.query?.dateEffective!=='string')fail('MOBILE_PREFLIGHT_INPUT_INVALID');\n const instant=options.query.dateEffective;\n if(!/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.000)?(?:Z|[+-]\\d{2}:\\d{2})$/.test(instant)||!Number.isFinite(parseRestInstant(instant))||new Date(instant.slice(0,19)+'Z').toISOString().slice(0,19)!==instant.slice(0,19))fail('MOBILE_PREFLIGHT_INPUT_INVALID',['dateEffective']);\n checkSignal();\n const captured={...options,path:{...options.path},query:{...options.query},body:structuredClone(options.body)};\n const scalars:Array<{id:number;field:string;type:string}>=[],seen=new Set<string>(),ids=new Set<number>();\n for(const q of captured.body){\n if(!isRecord(q)||typeof q.question_type!=='string'||typeof q.virt_field_name!=='string'||!q.virt_field_name||typeof q.question_id!=='number'||!Number.isSafeInteger(q.question_id)||q.question_id<=0||seen.has(q.virt_field_name)||ids.has(q.question_id))fail('MOBILE_PREFLIGHT_INPUT_INVALID');\n seen.add(q.virt_field_name);ids.add(q.question_id);\n if(q.question_type!=='sub_assessment')scalars.push({id:q.question_id,field:q.virt_field_name,type:q.question_type});\n }\n if(scalars.length){\n let response:Awaited<ReturnType<WellSkyClient['assessments']['get']>>;\n try{response=await api.assessments.get({path:captured.path,query:{dateEffective:new Date(parseRestInstant(instant)+1000).toISOString()},...(options.signal?{signal:options.signal}:{}),...(options.timeoutMs===undefined?{}:{timeoutMs:options.timeoutMs}),...(options.correlationId===undefined?{}:{correlationId:options.correlationId})});}\n catch(cause){fail('MOBILE_PREFLIGHT_READ_FAILED',scalars.map(q=>q.field),cause);}\n const rows=toItems<Operations['GET /assessments/{clientId}/{assessmentCode}']['item']>(response),conflicts:string[]=[];\n for(const q of scalars){\n const found=rows.filter(r=>r.virt_field_name===q.field);\n if(found.length!==1||found[0]?.question_id!==q.id||found[0]?.question_type!==q.type)fail('MOBILE_PREFLIGHT_UNAVAILABLE',[q.field]);\n const row=found[0];\n if(!row)fail('MOBILE_PREFLIGHT_UNAVAILABLE',[q.field]);\n if(row.date_effective!==null&&row.date_effective!==undefined&&row.date_effective!==''){\n const recorded=parseRestInstant(row.date_effective);\n if(!Number.isFinite(recorded)||recorded>parseRestInstant(instant))fail('MOBILE_PREFLIGHT_UNAVAILABLE',[q.field]);\n if(recorded===parseRestInstant(instant))conflicts.push(q.field);\n }else if(row.answer_value!==null&&row.answer_value!==undefined&&row.answer_value!=='')fail('MOBILE_PREFLIGHT_UNAVAILABLE',[q.field]);\n }\n if(conflicts.length)fail('MOBILE_ANSWER_EXISTS',conflicts);\n }\n checkSignal();\n return api.assessmentsMobile.addAnswer(captured);\n}\n"]}
package/dist/cjs/v2.d.ts CHANGED
@@ -10,4 +10,6 @@ export * from "./subassessments.js";
10
10
  export * from "./enrollment-verifications.js";
11
11
  export * from "./gwt-enrollments.js";
12
12
  export * from "./program-discovery.js";
13
+ export * from "./mobile-preflight.js";
14
+ export * from "./write-labels.js";
13
15
  //# sourceMappingURL=v2.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"v2.d.ts","sourceRoot":"","sources":["../../src/v2.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,+BAA+B,CAAC;AAE9C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC"}
1
+ {"version":3,"file":"v2.d.ts","sourceRoot":"","sources":["../../src/v2.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,+BAA+B,CAAC;AAE9C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC"}
package/dist/cjs/v2.js CHANGED
@@ -26,4 +26,6 @@ __exportStar(require("./subassessments.js"), exports);
26
26
  __exportStar(require("./enrollment-verifications.js"), exports);
27
27
  __exportStar(require("./gwt-enrollments.js"), exports);
28
28
  __exportStar(require("./program-discovery.js"), exports);
29
+ __exportStar(require("./mobile-preflight.js"), exports);
30
+ __exportStar(require("./write-labels.js"), exports);
29
31
  //# sourceMappingURL=v2.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"v2.js","sourceRoot":"","sources":["../../src/v2.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,2DAAyC;AACzC,+CAA6B;AAC7B,gDAA8B;AAC9B,8CAA4B;AAC5B,gDAA8B;AAC9B,oDAAkC;AAClC,4CAA0B;AAC1B,sDAAoC;AACpC,gEAA8C;AAE9C,uDAAqC;AACrC,yDAAuC","sourcesContent":["export * from \"./generated/v2.js\";\nexport * from \"./generated/v2-models.js\";\nexport * from \"./runtime.js\";\nexport * from \"./metadata.js\";\nexport * from \"./errors.js\";\nexport * from \"./encoding.js\";\nexport * from \"./verification.js\";\nexport * from \"./bulk.js\";\nexport * from \"./subassessments.js\";\nexport * from \"./enrollment-verifications.js\";\n\nexport * from \"./gwt-enrollments.js\";\nexport * from \"./program-discovery.js\";\n"]}
1
+ {"version":3,"file":"v2.js","sourceRoot":"","sources":["../../src/v2.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,2DAAyC;AACzC,+CAA6B;AAC7B,gDAA8B;AAC9B,8CAA4B;AAC5B,gDAA8B;AAC9B,oDAAkC;AAClC,4CAA0B;AAC1B,sDAAoC;AACpC,gEAA8C;AAE9C,uDAAqC;AACrC,yDAAuC;AACvC,wDAAsC;AACtC,oDAAkC","sourcesContent":["export * from \"./generated/v2.js\";\nexport * from \"./generated/v2-models.js\";\nexport * from \"./runtime.js\";\nexport * from \"./metadata.js\";\nexport * from \"./errors.js\";\nexport * from \"./encoding.js\";\nexport * from \"./verification.js\";\nexport * from \"./bulk.js\";\nexport * from \"./subassessments.js\";\nexport * from \"./enrollment-verifications.js\";\n\nexport * from \"./gwt-enrollments.js\";\nexport * from \"./program-discovery.js\";\nexport * from \"./mobile-preflight.js\";\nexport * from \"./write-labels.js\";\n"]}
package/dist/cjs/v3.d.ts CHANGED
@@ -10,4 +10,6 @@ export * from "./subassessments.js";
10
10
  export * from "./enrollment-verifications.js";
11
11
  export * from "./gwt-enrollments.js";
12
12
  export * from "./program-discovery.js";
13
+ export * from "./mobile-preflight.js";
14
+ export * from "./write-labels.js";
13
15
  //# sourceMappingURL=v3.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"v3.d.ts","sourceRoot":"","sources":["../../src/v3.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,+BAA+B,CAAC;AAE9C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC"}
1
+ {"version":3,"file":"v3.d.ts","sourceRoot":"","sources":["../../src/v3.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,+BAA+B,CAAC;AAE9C,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC"}
package/dist/cjs/v3.js CHANGED
@@ -26,4 +26,6 @@ __exportStar(require("./subassessments.js"), exports);
26
26
  __exportStar(require("./enrollment-verifications.js"), exports);
27
27
  __exportStar(require("./gwt-enrollments.js"), exports);
28
28
  __exportStar(require("./program-discovery.js"), exports);
29
+ __exportStar(require("./mobile-preflight.js"), exports);
30
+ __exportStar(require("./write-labels.js"), exports);
29
31
  //# sourceMappingURL=v3.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"v3.js","sourceRoot":"","sources":["../../src/v3.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,2DAAyC;AACzC,+CAA6B;AAC7B,gDAA8B;AAC9B,8CAA4B;AAC5B,gDAA8B;AAC9B,oDAAkC;AAClC,4CAA0B;AAC1B,sDAAoC;AACpC,gEAA8C;AAE9C,uDAAqC;AACrC,yDAAuC","sourcesContent":["export * from \"./generated/v3.js\";\nexport * from \"./generated/v3-models.js\";\nexport * from \"./runtime.js\";\nexport * from \"./metadata.js\";\nexport * from \"./errors.js\";\nexport * from \"./encoding.js\";\nexport * from \"./verification.js\";\nexport * from \"./bulk.js\";\nexport * from \"./subassessments.js\";\nexport * from \"./enrollment-verifications.js\";\n\nexport * from \"./gwt-enrollments.js\";\nexport * from \"./program-discovery.js\";\n"]}
1
+ {"version":3,"file":"v3.js","sourceRoot":"","sources":["../../src/v3.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,oDAAkC;AAClC,2DAAyC;AACzC,+CAA6B;AAC7B,gDAA8B;AAC9B,8CAA4B;AAC5B,gDAA8B;AAC9B,oDAAkC;AAClC,4CAA0B;AAC1B,sDAAoC;AACpC,gEAA8C;AAE9C,uDAAqC;AACrC,yDAAuC;AACvC,wDAAsC;AACtC,oDAAkC","sourcesContent":["export * from \"./generated/v3.js\";\nexport * from \"./generated/v3-models.js\";\nexport * from \"./runtime.js\";\nexport * from \"./metadata.js\";\nexport * from \"./errors.js\";\nexport * from \"./encoding.js\";\nexport * from \"./verification.js\";\nexport * from \"./bulk.js\";\nexport * from \"./subassessments.js\";\nexport * from \"./enrollment-verifications.js\";\n\nexport * from \"./gwt-enrollments.js\";\nexport * from \"./program-discovery.js\";\nexport * from \"./mobile-preflight.js\";\nexport * from \"./write-labels.js\";\n"]}
@@ -0,0 +1,13 @@
1
+ import type { AssessmentDefinition } from './metadata.js';
2
+ import type { EncodingOptions } from './encoding.js';
3
+ export interface WriteLabelObservation {
4
+ tenant: string;
5
+ response: unknown;
6
+ }
7
+ export type ReadBackWriteLabelResolver = NonNullable<EncodingOptions['writeLabel']> & {
8
+ readonly tenant: string;
9
+ };
10
+ /** Builds labels only from exact readback evidence. It never infers HUD suffixes.
11
+ * Unobserved or changed options return undefined, so the standard encoder refuses them. */
12
+ export declare function createReadBackWriteLabelResolver(definition: AssessmentDefinition, observations: readonly WriteLabelObservation[]): ReadBackWriteLabelResolver;
13
+ //# sourceMappingURL=write-labels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-labels.d.ts","sourceRoot":"","sources":["../../src/write-labels.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,oBAAoB,EAAmC,MAAM,eAAe,CAAC;AAE1F,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,eAAe,CAAC;AAInD,MAAM,WAAW,qBAAqB;IAAE,MAAM,EAAC,MAAM,CAAC;IAAA,QAAQ,EAAC,OAAO,CAAC;CAAC;AACxE,MAAM,MAAM,0BAA0B,GAAG,WAAW,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,GAAG;IAAC,QAAQ,CAAC,MAAM,EAAC,MAAM,CAAA;CAAC,CAAC;AAE/G;2FAC2F;AAC3F,wBAAgB,gCAAgC,CAC/C,UAAU,EAAC,oBAAoB,EAC/B,YAAY,EAAC,SAAS,qBAAqB,EAAE,GAC5C,0BAA0B,CA2C3B"}
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createReadBackWriteLabelResolver = createReadBackWriteLabelResolver;
4
+ const metadata_js_1 = require("./metadata.js");
5
+ const errors_js_1 = require("./errors.js");
6
+ const validation_js_1 = require("./validation.js");
7
+ /** Builds labels only from exact readback evidence. It never infers HUD suffixes.
8
+ * Unobserved or changed options return undefined, so the standard encoder refuses them. */
9
+ function createReadBackWriteLabelResolver(definition, observations) {
10
+ const questions = new Map();
11
+ for (const { question } of (0, metadata_js_1.flattenQuestions)(definition.questions)) {
12
+ if (question.questionType !== 'lookup')
13
+ continue;
14
+ const prior = questions.get(question.questionId);
15
+ if (prior && JSON.stringify(prior) !== JSON.stringify(question))
16
+ throw new errors_js_1.ValidationError('ambiguous write-label question');
17
+ questions.set(question.questionId, structuredClone(question));
18
+ }
19
+ const labels = new Map();
20
+ const identity = (questionId, optionId) => JSON.stringify([questionId, optionId]);
21
+ const visit = (value, depth = 0) => {
22
+ if (depth > 40)
23
+ throw new errors_js_1.ValidationError('write-label observation exceeds nesting limit');
24
+ if (Array.isArray(value)) {
25
+ for (const child of value)
26
+ visit(child, depth + 1);
27
+ return;
28
+ }
29
+ if (!(0, validation_js_1.isRecord)(value))
30
+ return;
31
+ if (value.question_type === 'lookup' && typeof value.question_id === 'number') {
32
+ const question = questions.get(value.question_id);
33
+ if (question) {
34
+ if (value.virt_field_name !== question.fieldName)
35
+ throw new errors_js_1.ValidationError('write-label question identity mismatch');
36
+ const raw = value.answer_value;
37
+ const id = typeof raw === 'number' ? raw : typeof raw === 'string' && /^\d+$/.test(raw) ? Number(raw) : null;
38
+ if (id !== null && Number.isSafeInteger(id) && typeof value.answer_value_text === 'string' && value.answer_value_text.trim()) {
39
+ const options = question.options.filter(option => option.id === id);
40
+ if (options.length !== 1)
41
+ throw new errors_js_1.ValidationError('write-label option identity mismatch', [question.fieldName ?? String(question.questionId)]);
42
+ const key = identity(question.questionId, id), prior = labels.get(key);
43
+ if (prior !== undefined && prior !== value.answer_value_text)
44
+ throw new errors_js_1.ValidationError('conflicting write-label observations', [question.fieldName ?? String(question.questionId)]);
45
+ labels.set(key, value.answer_value_text);
46
+ }
47
+ }
48
+ }
49
+ // Traverse known response/assessment containers only. Do not interpret arbitrary strings as data.
50
+ for (const key of ['value', 'values', 'sub_assessment_question_list', 'sub_assessment_answer_list', 'sub_assessment_list'])
51
+ if (value[key] !== undefined)
52
+ visit(value[key], depth + 1);
53
+ };
54
+ for (const observation of observations) {
55
+ if (observation.tenant !== definition.tenant)
56
+ throw new errors_js_1.ValidationError('write-label observation tenant mismatch');
57
+ visit(observation.response);
58
+ }
59
+ const sameOption = (a, b) => a.id === b.id && a.picklistId === b.picklistId && a.code === b.code && a.picklistCode === b.picklistCode && a.name === b.name && a.hudVisibility === b.hudVisibility;
60
+ const resolver = (question, option) => {
61
+ const known = questions.get(question.questionId);
62
+ if (!known || known.fieldName !== question.fieldName || question.questionType !== 'lookup' || !known.options.some(o => sameOption(o, option)))
63
+ return undefined;
64
+ return labels.get(identity(question.questionId, option.id));
65
+ };
66
+ return Object.freeze(Object.assign(resolver, { tenant: definition.tenant }));
67
+ }
68
+ //# sourceMappingURL=write-labels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-labels.js","sourceRoot":"","sources":["../../src/write-labels.ts"],"names":[],"mappings":"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","sourcesContent":["import type {AssessmentDefinition,PicklistOption,QuestionDefinition} from './metadata.js';\nimport {flattenQuestions} from './metadata.js';\nimport type {EncodingOptions} from './encoding.js';\nimport {ValidationError} from './errors.js';\nimport {isRecord} from './validation.js';\n\nexport interface WriteLabelObservation {tenant:string;response:unknown;}\nexport type ReadBackWriteLabelResolver = NonNullable<EncodingOptions['writeLabel']> & {readonly tenant:string};\n\n/** Builds labels only from exact readback evidence. It never infers HUD suffixes.\n * Unobserved or changed options return undefined, so the standard encoder refuses them. */\nexport function createReadBackWriteLabelResolver(\n definition:AssessmentDefinition,\n observations:readonly WriteLabelObservation[],\n):ReadBackWriteLabelResolver{\n const questions=new Map<number,QuestionDefinition>();\n for(const {question} of flattenQuestions(definition.questions)){\n if(question.questionType!=='lookup')continue;\n const prior=questions.get(question.questionId);\n if(prior&&JSON.stringify(prior)!==JSON.stringify(question))throw new ValidationError('ambiguous write-label question');\n questions.set(question.questionId,structuredClone(question));\n }\n const labels=new Map<string,string>();\n const identity=(questionId:number,optionId:number)=>JSON.stringify([questionId,optionId]);\n const visit=(value:unknown,depth=0):void=>{\n if(depth>40)throw new ValidationError('write-label observation exceeds nesting limit');\n if(Array.isArray(value)){for(const child of value)visit(child,depth+1);return;}\n if(!isRecord(value))return;\n if(value.question_type==='lookup'&&typeof value.question_id==='number'){\n const question=questions.get(value.question_id);\n if(question){\n if(value.virt_field_name!==question.fieldName)throw new ValidationError('write-label question identity mismatch');\n const raw=value.answer_value;\n const id=typeof raw==='number'?raw:typeof raw==='string'&&/^\\d+$/.test(raw)?Number(raw):null;\n if(id!==null&&Number.isSafeInteger(id)&&typeof value.answer_value_text==='string'&&value.answer_value_text.trim()){\n const options=question.options.filter(option=>option.id===id);\n if(options.length!==1)throw new ValidationError('write-label option identity mismatch',[question.fieldName??String(question.questionId)]);\n const key=identity(question.questionId,id),prior=labels.get(key);\n if(prior!==undefined&&prior!==value.answer_value_text)throw new ValidationError('conflicting write-label observations',[question.fieldName??String(question.questionId)]);\n labels.set(key,value.answer_value_text);\n }\n }\n }\n // Traverse known response/assessment containers only. Do not interpret arbitrary strings as data.\n for(const key of ['value','values','sub_assessment_question_list','sub_assessment_answer_list','sub_assessment_list'])if(value[key]!==undefined)visit(value[key],depth+1);\n };\n for(const observation of observations){\n if(observation.tenant!==definition.tenant)throw new ValidationError('write-label observation tenant mismatch');\n visit(observation.response);\n }\n const sameOption=(a:PicklistOption,b:PicklistOption)=>a.id===b.id&&a.picklistId===b.picklistId&&a.code===b.code&&a.picklistCode===b.picklistCode&&a.name===b.name&&a.hudVisibility===b.hudVisibility;\n const resolver=(question:QuestionDefinition,option:PicklistOption)=>{\n const known=questions.get(question.questionId);\n if(!known||known.fieldName!==question.fieldName||question.questionType!=='lookup'||!known.options.some(o=>sameOption(o,option)))return undefined;\n return labels.get(identity(question.questionId,option.id));\n };\n return Object.freeze(Object.assign(resolver,{tenant:definition.tenant}));\n}\n"]}
@@ -0,0 +1,19 @@
1
+ import type { WellSkyClient } from './generated/v3.js';
2
+ import { ValidationError } from './errors.js';
3
+ type RawMobileWrite = Parameters<WellSkyClient['assessmentsMobile']['addAnswer']>[0];
4
+ export type CheckedMobileWriteOptions = Omit<RawMobileWrite, 'query'> & {
5
+ query: {
6
+ dateEffective: string;
7
+ };
8
+ };
9
+ export type MobilePreflightCode = 'MOBILE_PREFLIGHT_INPUT_INVALID' | 'MOBILE_PREFLIGHT_READ_FAILED' | 'MOBILE_PREFLIGHT_UNAVAILABLE' | 'MOBILE_ANSWER_EXISTS' | 'MOBILE_PREFLIGHT_CANCELLED';
10
+ export declare class MobileAnswerPreflightError extends ValidationError {
11
+ readonly code: MobilePreflightCode;
12
+ readonly committed = false;
13
+ constructor(code: MobilePreflightCode, fields?: readonly string[], cause?: unknown);
14
+ }
15
+ /** Read-before-write protection for scalar inserts, including questions shared by instruments.
16
+ * Does not journal, retry writes, or make the read/write pair atomic. */
17
+ export declare function writeMobileAnswersChecked(api: Pick<WellSkyClient, 'assessments' | 'assessmentsMobile'>, options: CheckedMobileWriteOptions): ReturnType<WellSkyClient['assessmentsMobile']['addAnswer']>;
18
+ export {};
19
+ //# sourceMappingURL=mobile-preflight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mobile-preflight.d.ts","sourceRoot":"","sources":["../../src/mobile-preflight.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAY,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAAC,eAAe,EAAC,MAAM,aAAa,CAAC;AAK5C,KAAK,cAAc,GAAG,UAAU,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrF,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,cAAc,EAAC,OAAO,CAAC,GAAG;IAAC,KAAK,EAAC;QAAC,aAAa,EAAC,MAAM,CAAA;KAAC,CAAA;CAAC,CAAC;AACtG,MAAM,MAAM,mBAAmB,GAAG,gCAAgC,GAAC,8BAA8B,GAAC,8BAA8B,GAAC,sBAAsB,GAAC,4BAA4B,CAAC;AACrL,qBAAa,0BAA2B,SAAQ,eAAe;IAElD,QAAQ,CAAC,IAAI,EAAC,mBAAmB;IAD7C,QAAQ,CAAC,SAAS,SAAO;gBACJ,IAAI,EAAC,mBAAmB,EAAC,MAAM,GAAC,SAAS,MAAM,EAAK,EAAC,KAAK,CAAC,EAAC,OAAO;CAIxF;AAED;yEACyE;AACzE,wBAAsB,yBAAyB,CAC9C,GAAG,EAAC,IAAI,CAAC,aAAa,EAAC,aAAa,GAAC,mBAAmB,CAAC,EACzD,OAAO,EAAC,yBAAyB,GAChC,UAAU,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAC,WAAW,CAAC,CAAC,CAkC5D"}