@choiceapps/communityservices-sdk 0.1.0 → 0.2.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 (62) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +0 -17
  3. package/browser/coordinated-entry-event.d.mts +78 -0
  4. package/browser/coordinated-entry-event.mjs +175 -0
  5. package/browser/current-living-situation.mjs +11 -120
  6. package/browser/enrollment-workflow.d.mts +17 -3
  7. package/browser/enrollment-workflow.mjs +78 -17
  8. package/browser/index.d.mts +1 -0
  9. package/browser/index.mjs +1 -0
  10. package/browser/subassessment-row-recorder.mjs +154 -0
  11. package/dist/cjs/encoding.d.ts.map +1 -1
  12. package/dist/cjs/encoding.js +2 -1
  13. package/dist/cjs/encoding.js.map +1 -1
  14. package/dist/cjs/errors.d.ts +4 -1
  15. package/dist/cjs/errors.d.ts.map +1 -1
  16. package/dist/cjs/errors.js +9 -1
  17. package/dist/cjs/errors.js.map +1 -1
  18. package/dist/cjs/runtime.d.ts +5 -1
  19. package/dist/cjs/runtime.d.ts.map +1 -1
  20. package/dist/cjs/runtime.js +1 -1
  21. package/dist/cjs/runtime.js.map +1 -1
  22. package/dist/cjs/subassessments.d.ts.map +1 -1
  23. package/dist/cjs/subassessments.js +8 -15
  24. package/dist/cjs/subassessments.js.map +1 -1
  25. package/dist/cjs/transport.d.ts.map +1 -1
  26. package/dist/cjs/transport.js +15 -3
  27. package/dist/cjs/transport.js.map +1 -1
  28. package/dist/esm/encoding.d.ts.map +1 -1
  29. package/dist/esm/encoding.js +2 -1
  30. package/dist/esm/encoding.js.map +1 -1
  31. package/dist/esm/errors.d.ts +4 -1
  32. package/dist/esm/errors.d.ts.map +1 -1
  33. package/dist/esm/errors.js +9 -1
  34. package/dist/esm/errors.js.map +1 -1
  35. package/dist/esm/runtime.d.ts +5 -1
  36. package/dist/esm/runtime.d.ts.map +1 -1
  37. package/dist/esm/runtime.js +1 -1
  38. package/dist/esm/runtime.js.map +1 -1
  39. package/dist/esm/subassessments.d.ts.map +1 -1
  40. package/dist/esm/subassessments.js +8 -15
  41. package/dist/esm/subassessments.js.map +1 -1
  42. package/dist/esm/transport.d.ts.map +1 -1
  43. package/dist/esm/transport.js +15 -3
  44. package/dist/esm/transport.js.map +1 -1
  45. package/docs/README.md +1 -0
  46. package/docs/assessments.md +21 -3
  47. package/docs/capabilities.md +2 -0
  48. package/docs/configuration.md +1 -0
  49. package/docs/coordinated-entry-events.md +88 -0
  50. package/docs/current-living-situation.md +1 -1
  51. package/docs/dates-and-answers.md +3 -1
  52. package/docs/enrollments.md +41 -1
  53. package/docs/reference.md +6 -2
  54. package/docs/troubleshooting.md +25 -0
  55. package/docs/using-the-sdk.md +2 -3
  56. package/docs/vendor-issues/README.md +2 -0
  57. package/docs/vendor-issues/date-semantics.md +2 -0
  58. package/docs/vendor-issues/yes-no-answers.md +35 -0
  59. package/examples/README.md +2 -0
  60. package/examples/coordinated-entry-events.mts +37 -0
  61. package/examples/enrollment-workflow.mts +5 -2
  62. package/package.json +1 -1
@@ -6,10 +6,11 @@ import {readProgramCatalog,encodeAnswers,encodeNewSubassessmentRow,toItems} from
6
6
 
7
7
  const object=v=>v!==null&&typeof v==='object'&&!Array.isArray(v);
8
8
  const positive=v=>Number.isSafeInteger(v)&&v>0;
9
+ const entryExitTypes=['HUD','Basic','Standard','RHY','VA','Basic Center Program Entry/Exit','PATH','Quick Call','Transitional Living Program Entry/Exit'];
9
10
  const canonical=v=>JSON.stringify(v,(_,x)=>object(x)?Object.fromEntries(Object.keys(x).sort().map(k=>[k,x[k]])):x);
10
11
  const hash=v=>createHash('sha256').update(canonical(v)).digest('hex');
11
12
  const fail=message=>{throw new EnrollmentWorkflowError(message);};
12
- export class EnrollmentWorkflowError extends Error {constructor(reason){super(`Enrollment workflow: ${reason}`);this.name='EnrollmentWorkflowError';this.reason=reason;}}
13
+ export class EnrollmentWorkflowError extends Error {constructor(reason,options){super(`Enrollment workflow: ${reason}`,options);this.name='EnrollmentWorkflowError';this.reason=reason;if(options?.step!==undefined)this.step=options.step;}}
13
14
  function date(value){
14
15
  if(typeof value!=='string'||!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.000)?(?:Z|[+-]\d{2}:\d{2})$/.test(value)||!Number.isFinite(Date.parse(value))||new Date(value.slice(0,19)+'Z').toISOString().slice(0,19)!==value.slice(0,19))fail('timezone-qualified whole-second date required');
15
16
  return new Date(value).toISOString();
@@ -25,11 +26,13 @@ function sameAnswer(expected,actual){
25
26
  return String(actual)===expected.answer_value;
26
27
  }
27
28
  function questions(response){const rows=toItems(response);if(!Array.isArray(rows)||rows.some(q=>!object(q)||typeof q.virt_field_name!=='string')||new Set(rows.map(q=>q.virt_field_name)).size!==rows.length)fail('ambiguous assessment read');return rows;}
28
- function compile(definition,input){
29
+ function compile(definition,input,{yesNoWrites,writeLabel}={}){
29
30
  fields(input,['answers','subassessments','endDates','notApplicable','carryForward']);
30
31
  const answers=input.answers??{},excluded=input.notApplicable??{},carry=input.carryForward??[],rows=input.subassessments??[],ends=input.endDates??[];
31
32
  if(!object(answers)||!object(excluded)||!Array.isArray(carry)||!Array.isArray(rows)||!Array.isArray(ends)||new Set(carry).size!==carry.length)fail('invalid assessment plan');
32
- const scalar=encodeAnswers(definition.questions,answers,{endpoint:'mobile'}),index=new Map(definition.questions.map(q=>[q.fieldName,q]));
33
+ const encoded=encodeAnswers(definition.questions,answers,{endpoint:'mobile'}),index=new Map(definition.questions.map(q=>[q.fieldName,q]));
34
+ // Mobile writes store empty yes/no values, so the standard endpoint can own them instead.
35
+ const yesNo=yesNoWrites==='standard'?encoded.filter(q=>q.question_type==='yes_no'):[],scalar=encoded.filter(q=>!yesNo.includes(q));
33
36
  for(const [field,reason]of Object.entries(excluded))if(!index.has(field)||typeof reason!=='string'||!reason.trim())fail('not-applicable explanation required');
34
37
  for(const field of carry)if(typeof field!=='string'||!index.has(field))fail('unknown carry-forward field');
35
38
  const keys=new Set(),semantics=new Set();
@@ -57,7 +60,23 @@ function compile(definition,input){
57
60
  const birthDate=scalar.find(q=>q.virt_field_name==='SVPPROFDOB');
58
61
  if(birthDate){if(birthDate.question_type!=='date')fail('DOB question type changed');validateBirthDate(birthDate.answer_value);}
59
62
  for(const q of scalar)if(q.virt_field_name!=='SVPPROFDOB'&&q.question_type==='date'&&q.answer_value)date(q.answer_value);
60
- return {scalar:scalar.filter(q=>q.virt_field_name!=='SVPPROFDOB'),dateOfBirth:birthDate?.answer_value,rows:compiledRows,ends:compiledEnds,carry,excluded};
63
+ // The standard endpoint rejects a request that omits any mandatory answer, even one already
64
+ // recorded at that date. Resolve every label now so no write precedes a missing label.
65
+ const mandatory=[];
66
+ if(yesNo.length)for(const q of definition.questions){
67
+ if(!q.mandatory||['blank','label','sub_assessment'].includes(q.questionType)||yesNo.some(y=>y.virt_field_name===q.fieldName))continue;
68
+ // Only mandatory lookups were verified live; this also keeps DOB inside its own helper.
69
+ if(q.questionType!=='lookup')fail(`standard yes/no writes support mandatory lookups only; ${q.fieldName} is ${q.questionType}`);
70
+ const base={question_id:q.questionId,question_type:q.questionType,virt_field_name:q.fieldName};
71
+ if(q.fieldName in answers){
72
+ const [label]=encodeAnswers(definition.questions,{[q.fieldName]:answers[q.fieldName]},{endpoint:'standalone',writeLabel});
73
+ const [stored]=encodeAnswers(definition.questions,{[q.fieldName]:answers[q.fieldName]},{endpoint:'mobile'});
74
+ if(!label||!stored)fail(`mandatory answer ${q.fieldName} has no standard encoding`);
75
+ mandatory.push({...base,answer_value:label.answer_value,expected:stored.answer_value});
76
+ }else if(q.fieldName in excluded)mandatory.push({...base,answer_value:'',expected:''});
77
+ else if(carry.includes(q.fieldName))mandatory.push({...base,carry:true});
78
+ }
79
+ return {scalar:scalar.filter(q=>q.virt_field_name!=='SVPPROFDOB'),yesNo,mandatory,dateOfBirth:birthDate?.answer_value,rows:compiledRows,ends:compiledEnds,carry,excluded};
61
80
  }
62
81
  async function enrollments(api,clientId){
63
82
  const all=[],ids=new Set();let count;
@@ -71,17 +90,26 @@ async function enrollments(api,clientId){
71
90
 
72
91
  /** Explicit orchestration for an existing client. All writers must share the durable store.
73
92
  * No automatic retries of uncertain REST writes. This is not a cross-system transaction. */
74
- export function createEnrollmentWorkflow({api,discovery,helpers,store,enrollmentSession,exitUi,memberEntryDatePort,birthDates}){
93
+ export function createEnrollmentWorkflow({api,discovery,helpers,store,enrollmentSession,exitUi,memberEntryDatePort,birthDates,assessmentLocations={},yesNoWrites='mobile',writeLabel}){
75
94
  const tenantScope=apiScope(api, discovery, helpers, enrollmentSession, exitUi);
76
95
  if(typeof discovery?.getProviderAssessments!=='function'||typeof helpers?.createInterimUpdate!=='function'||!store)fail('REST API, discovery, enrollment helpers and durable store required');
96
+ if(!['mobile','standard'].includes(yesNoWrites)||yesNoWrites==='standard'&&(typeof writeLabel!=='function'||typeof api.assessments?.create!=='function'))fail('yesNoWrites must be mobile, or standard with writeLabel and assessments.create');
97
+ if(!object(assessmentLocations))fail('assessment locations must be a map by Entry/Exit type');
98
+ for(const [type,locations]of Object.entries(assessmentLocations)){
99
+ if(!entryExitTypes.includes(type))fail('unsupported Entry/Exit type in assessment locations');
100
+ fields(locations,['entry','interim','exit']);
101
+ if(['entry','interim'].some(phase=>typeof locations[phase]!=='string'||!locations[phase].trim())||Object.values(locations).some(location=>typeof location!=='string'||!location.trim()))fail('entry and interim assessment locations required; exit location must be nonempty when supplied');
102
+ }
103
+ const locationsByType={HUD:{entry:'showHudEntry',interim:'showHudInterim',exit:'showHudExit'},VA:{entry:'showVaEntry',interim:'showVaInterim',exit:'showVaExit'},...structuredClone(assessmentLocations)};
77
104
  const discover=async(input,includeExit=false)=>{
78
- fields(input,['clientId','providerId','type','entryDate']);if(!positive(input.clientId)||!positive(input.providerId)||!['HUD','VA'].includes(input.type))fail('client, provider and HUD/VA type required');const at=date(input.entryDate);
105
+ fields(input,['clientId','providerId','type','entryDate']);if(!positive(input.clientId)||!positive(input.providerId)||!entryExitTypes.includes(input.type))fail('client, provider and supported Entry/Exit type required');const at=date(input.entryDate);
106
+ const locations=locationsByType[input.type];if(!locations||includeExit&&!locations.exit)fail(`assessment locations required for ${input.type}${includeExit?' including exit':''}`);
79
107
  const client=toItems(await api.clients.get({path:{clientId:input.clientId}}));if(client.length!==1||client[0].clientId!==input.clientId||client[0].isActive===false)fail('active client required');
80
108
  const catalog=await readProgramCatalog(api);const providers=catalog.providers.filter(p=>p.providerId===input.providerId);if(providers.length!==1)fail('active operational program required');
81
109
  const configuration=await discovery.getProviderAssessments({providerId:input.providerId});if(configuration.providerId!==input.providerId)fail('provider discovery mismatch');
82
- const prefix=input.type==='HUD'?'showHud':'showVa',resolved={};
83
- for(const [phase,suffix]of [['entry','Entry'],['interim','Interim'],...(includeExit?[['exit','Exit']]:[])]){
84
- const matches=configuration.defaults.filter(d=>d.location===prefix+suffix);if(matches.length!==1||matches[0].resolution!=='resolved'||!matches[0].assessment?.code)fail('program assessment default unresolved');
110
+ const resolved={};
111
+ for(const phase of ['entry','interim',...(includeExit?['exit']:[])]){
112
+ const matches=configuration.defaults.filter(d=>d.location===locations[phase]);if(matches.length!==1||matches[0].resolution!=='resolved'||!matches[0].assessment?.code)fail('program assessment default unresolved');
85
113
  const assessment=matches[0].assessment;const definition=await api.assessments.getDefinition({clientId:input.clientId,assessmentCode:assessment.code,effectiveDate:at,refresh:true});
86
114
  if(definition.tenant!==api.tenant||definition.assessmentCode!==assessment.code||definition.unresolvedPicklists.length)fail('assessment metadata incomplete');
87
115
  resolved[phase]={assessment,definition};
@@ -93,7 +121,7 @@ export function createEnrollmentWorkflow({api,discovery,helpers,store,enrollment
93
121
  const run=async(kind,raw,internal={})=>{
94
122
  fields(raw,kind==='entry'?['key','clientId','providerId','type','entryDate','assessment']:['key','enrollmentKey','clientId',kind==='exit'?'exitDate':'reviewDate','assessment']);
95
123
  const input=structuredClone(raw);key(input.key);if(!positive(input.clientId))fail('client ID required');
96
- if(kind==='entry'){if(!positive(input.providerId)||!['HUD','VA'].includes(input.type))fail('program and HUD/VA type required');input.entryDate=date(input.entryDate);}else{key(input.enrollmentKey);if(kind==='addition')input.entryDate=date(input.entryDate);else if(kind==='exit')input.exitDate=date(input.exitDate);else input.reviewDate=date(input.reviewDate);}
124
+ if(kind==='entry'){if(!positive(input.providerId)||!entryExitTypes.includes(input.type))fail('program and supported Entry/Exit type required');input.entryDate=date(input.entryDate);}else{key(input.enrollmentKey);if(kind==='addition')input.entryDate=date(input.entryDate);else if(kind==='exit')input.exitDate=date(input.exitDate);else input.reviewDate=date(input.reviewDate);}
97
125
  if(!object(input.assessment))fail('assessment plan required');
98
126
  const locked=internal.locked?async(_scope,work)=>work():store.withLock.bind(store);
99
127
  return locked(scope(input.clientId),async()=>{
@@ -103,6 +131,8 @@ export function createEnrollmentWorkflow({api,discovery,helpers,store,enrollment
103
131
  if(records.some(r=>r.stage!=='verified'&&r.key!==input.key))fail('another unfinished workflow must be reconciled first');
104
132
  const matches=records.filter(r=>r.key===input.key);if(matches.length>1)fail('duplicate journal keys');let record=matches[0];
105
133
  const intentHash=hash({kind,input,...(internal.owner?{household:internal.owner}:{})});if(record&&record.intentHash!==intentHash)fail('key conflicts with previous intent');
134
+ // Changing the yes/no endpoint after an attempt would be an endpoint switch, not reconciliation.
135
+ if(record&&record.stage!=='verified'&&(record.yesNoWrites??'mobile')!==yesNoWrites)fail('yes/no write mode changed since this workflow started');
106
136
  const entry=kind!=='entry'?records.find(r=>r.key===input.enrollmentKey&&r.kind==='entry'&&r.stage==='verified'):null;
107
137
  if(kind!=='entry'&&!entry)fail('verified enrollment workflow required');
108
138
  const context=kind==='entry'?input:kind==='addition'?{...entry.context,entryDate:input.entryDate}:entry.context;
@@ -113,7 +143,7 @@ export function createEnrollmentWorkflow({api,discovery,helpers,store,enrollment
113
143
  const definition=discovered[kind].definition,code=definition.assessmentCode,at=kind==='entry'?input.entryDate:(input.exitDate??input.reviewDate);
114
144
  const exitSchemaHash=kind==='exit'?hash({assessment:discovered.exit.assessment,questions:definition.questions}):undefined;
115
145
  if(record&&kind==='exit'&&record.exitSchemaHash!==exitSchemaHash)fail('exit assessment configuration changed');
116
- const plan=compile(definition,input.assessment);
146
+ const plan=compile(definition,input.assessment,{yesNoWrites,writeLabel});
117
147
  const carriesBirthDate=plan.carry.includes('SVPPROFDOB'),hasBirthDate=plan.dateOfBirth!==undefined||carriesBirthDate;
118
148
  let carriedBirthDate;
119
149
  if(hasBirthDate){
@@ -142,10 +172,28 @@ export function createEnrollmentWorkflow({api,discovery,helpers,store,enrollment
142
172
  if(proposedHead)checkHeadAnswer(proposedHead.answer_value);
143
173
  if(plan.ends.length&&(kind==='entry'||plan.ends.some(e=>Date.parse(e.endDate)>=Date.parse(at)||Date.parse(e.endDate)<Date.parse(context.entryDate))))fail('row closure must fall between entry and assessment');
144
174
  const read=async()=>questions(await api.assessments.get({path:{clientId:input.clientId,assessmentCode:code},query:{dateEffective:at}}));
145
- const readCurrent=async()=>questions(await api.assessments.get({path:{clientId:input.clientId,assessmentCode:code}}));
175
+ // An undated read omits rows dated after the server's current time, which can include this
176
+ // assessment's own rows. Read at the later of now and the assessment instant.
177
+ const readCurrent=async()=>questions(await api.assessments.get({path:{clientId:input.clientId,assessmentCode:code},query:{dateEffective:new Date(Math.max(Date.now(),Date.parse(at))).toISOString().slice(0,19)+'Z'}}));
178
+ // A generic read at exactly the effective instant returns null for yes/no answers saved at
179
+ // that instant; one second later returns them. Only the yes/no step needs this.
180
+ const readSaved=async()=>questions(await api.assessments.get({path:{clientId:input.clientId,assessmentCode:code},query:{dateEffective:new Date(Date.parse(at)+1000).toISOString()}}));
146
181
  const initial=await read();
147
182
  if(expectedHead===input.clientId&&headQuestion)checkHeadAnswer(proposedHead?.answer_value??initial.find(q=>q.virt_field_name==='HUD_RELATIONTOHOH')?.answer_value);
148
183
  for(const field of plan.carry.filter(field=>field!=='SVPPROFDOB')){const q=initial.find(q=>q.virt_field_name===field);if(!q||(q.question_type==='sub_assessment'?!q.sub_assessment_answer_list?.length:!q.answer_value))fail('carry-forward field is empty');}
184
+ // Resend mandatory answers with the standard yes/no write. A blank write must not hide a recorded value.
185
+ const mandatory=plan.mandatory.map(m=>{
186
+ const recorded=initial.find(q=>q.virt_field_name===m.virt_field_name)?.answer_value;
187
+ if(m.carry){
188
+ const question=definition.questions.find(q=>q.fieldName===m.virt_field_name),options=question.options.filter(o=>String(o.id)===String(recorded));
189
+ const label=options.length===1?writeLabel(question,options[0]):undefined;
190
+ if(!label)fail(`carried mandatory answer ${m.virt_field_name} has no verified write label`);
191
+ if(m.virt_field_name==='HUD_RELATIONTOHOH')checkHeadAnswer(recorded);
192
+ return {question_id:m.question_id,question_type:m.question_type,virt_field_name:m.virt_field_name,answer_value:label,expected:String(recorded)};
193
+ }
194
+ if(m.expected===''&&recorded!==null&&recorded!==undefined&&recorded!=='')fail(`not-applicable mandatory field ${m.virt_field_name} has a recorded value; answer it explicitly`);
195
+ return m;
196
+ });
149
197
  // Validate closure targets before creating enrollment/review or submitting any answers.
150
198
  const current=await readCurrent();
151
199
  const currentHead=current.find(q=>q.virt_field_name==='HUD_RELATIONTOHOH');
@@ -161,15 +209,22 @@ export function createEnrollmentWorkflow({api,discovery,helpers,store,enrollment
161
209
  }
162
210
  if(internal.preflight)return {assessmentCode:code,...(internal.pinSchema?{schemaHash}:{}),...(exitSchemaHash?{exitSchemaHash}:{})};
163
211
  const wasVerified=record?.stage==='verified';
164
- if(!record){record={version:1,key:input.key,kind,intentHash,schemaHash,...(exitSchemaHash?{exitSchemaHash}:{}),context:{clientId:input.clientId,providerId:context.providerId,type:context.type,entryDate:context.entryDate},assessmentCode:code,stage:'pending',steps:{},rows:{},ends:plan.ends};records.push(record);}
212
+ if(!record){record={version:1,key:input.key,kind,intentHash,schemaHash,...(exitSchemaHash?{exitSchemaHash}:{}),context:{clientId:input.clientId,providerId:context.providerId,type:context.type,entryDate:context.entryDate},assessmentCode:code,...(yesNoWrites==='standard'?{yesNoWrites}:{}),stage:'pending',steps:{},rows:{},ends:plan.ends};records.push(record);}
165
213
  const persist=()=>store.write(scope(input.clientId),structuredClone(records));await persist();
166
214
  const step=async(name,inspect,submit)=>{
167
215
  let result=await inspect();const previous=record.steps[name];
168
216
  if(result.found){record.steps[name]={state:'verified',value:result.value};await persist();return result.value;}
169
- if(previous)fail(previous.state==='verified'?'verified step changed':'uncertain write requires reconciliation');
217
+ if(previous)throw new EnrollmentWorkflowError(previous.state==='verified'?'verified step changed':'uncertain write requires reconciliation',{step:name});
170
218
  record.steps[name]={state:'attempted'};await persist();
171
- try{await submit(result);}catch{/* A write response alone never establishes success or absence. */}
172
- result=await inspect();if(!result.found)fail('uncertain write requires reconciliation');
219
+ let writeError;
220
+ try{await submit(result);}catch(error){writeError=error;}
221
+ // A write response alone never establishes success or absence. Preserve its
222
+ // diagnostic cause in memory while reconciliation remains authoritative.
223
+ try{result=await inspect();}catch(readError){
224
+ if(writeError===undefined)throw readError;
225
+ throw new EnrollmentWorkflowError(readError instanceof EnrollmentWorkflowError?readError.reason:'uncertain write requires reconciliation',{step:name,cause:new AggregateError([writeError,readError],'Write and reconciliation failed')});
226
+ }
227
+ if(!result.found)throw new EnrollmentWorkflowError('uncertain write requires reconciliation',{step:name,...(writeError===undefined?{}:{cause:writeError})});
173
228
  record.steps[name]={state:'verified',value:result.value};await persist();return result.value;
174
229
  };
175
230
  const findEnrollment=async()=>{
@@ -217,6 +272,12 @@ export function createEnrollmentWorkflow({api,discovery,helpers,store,enrollment
217
272
  if(plan.scalar.length)await step('scalars',async()=>{
218
273
  const source=await read();return {found:plan.scalar.every(q=>{const r=source.find(r=>r.virt_field_name===q.virt_field_name);return r&&sameAnswer(q,r.answer_value)&&restDate(r.date_effective)===restDate(at);}),value:plan.scalar.map(q=>q.virt_field_name)};
219
274
  },()=>api.assessmentsMobile.addAnswer({path:{clientId:input.clientId,assessmentCode:code},query:{dateEffective:at},body:plan.scalar}));
275
+ // Standard writes update existing dated rows and change only the answers sent.
276
+ if(plan.yesNo.length){const body=[...plan.yesNo,...mandatory.map(({expected,...q})=>q)],expected=[...plan.yesNo,...mandatory.map(({expected,...q})=>({...q,answer_value:expected}))];
277
+ await step('yesno',async()=>{
278
+ const source=await readSaved();return {found:expected.every(q=>{const r=source.find(r=>r.virt_field_name===q.virt_field_name);return r&&sameAnswer(q,r.answer_value)&&restDate(r.date_effective)===restDate(at);}),value:plan.yesNo.map(q=>q.virt_field_name)};
279
+ },()=>api.assessments.create({path:{clientId:input.clientId,assessmentCode:code},query:{dateEffective:at},body}));
280
+ }
220
281
  // Group rows into one creation request. Partial success blocks resubmission until all rows reconcile.
221
282
  if(plan.rows.length){const rowIds=await step('rows',async()=>{
222
283
  const source=await readCurrent(),matched=Object.create(null),missing=[],matchedIds=new Set();
@@ -263,7 +324,7 @@ export function createEnrollmentWorkflow({api,discovery,helpers,store,enrollment
263
324
  for(const member of input.members){fields(member,['clientId','assessment']);if(!positive(member.clientId)||!object(member.assessment))fail('member client and assessment required');}
264
325
  input.members.sort((a,b)=>a.clientId-b.clientId);const ids=input.members.map(m=>m.clientId);
265
326
  if(new Set(ids).size!==ids.length||kind==='entry'&&!ids.includes(input.headClientId))fail('unique members including head required');
266
- if(kind==='entry'){if(!positive(input.householdId)||!positive(input.providerId)||!['HUD','VA'].includes(input.type))fail('household and program required');input.entryDate=date(input.entryDate);}
327
+ if(kind==='entry'){if(!positive(input.householdId)||!positive(input.providerId)||!entryExitTypes.includes(input.type))fail('household, program and supported Entry/Exit type required');input.entryDate=date(input.entryDate);}
267
328
  else{key(input.enrollmentKey);if(kind==='addition')input.entryDate=date(input.entryDate);else if(kind==='exit')input.exitDate=date(input.exitDate);else input.reviewDate=date(input.reviewDate);}
268
329
  // Read the cohort under its journal lock, then acquire all client locks in order.
269
330
  // Recheck under both locks so concurrent membership growth cannot escape protection.
@@ -73,6 +73,7 @@ export * from './shelters.mjs';
73
73
  export * from './shelter-inventory.mjs';
74
74
 
75
75
  export * from './current-living-situation.mjs';
76
+ export * from './coordinated-entry-event.mjs';
76
77
 
77
78
  export {resolveUiConnection} from './connection.mjs';
78
79
  export type {UiConnection,UiConnectionOptions} from './connection.mjs';
package/browser/index.mjs CHANGED
@@ -42,5 +42,6 @@ export {createShelterUi} from './shelter-ui.mjs';
42
42
  export {createShelterInventory, ShelterInventoryError} from './shelter-inventory.mjs';
43
43
 
44
44
  export * from './current-living-situation.mjs';
45
+ export * from './coordinated-entry-event.mjs';
45
46
 
46
47
  export {resolveUiConnection} from './connection.mjs';
@@ -0,0 +1,154 @@
1
+ // Internal: shared journal, snapshot and reconciliation flow for recorders that add one
2
+ // subassessment row per upstream event key. Not a public entry point.
3
+ import {apiScope} from './connection.mjs';
4
+ import {toItems, semanticHash} from '../dist/esm/index.js';
5
+
6
+ export const object = value => value !== null && typeof value === 'object' && !Array.isArray(value);
7
+ export const positive = value => Number.isSafeInteger(value) && value > 0;
8
+ export function nonempty(value) { return typeof value === 'string' && value.trim().length > 0 && value.length <= 200; }
9
+
10
+ /** Shape check for a recorder journal, including an optional in-place row update. */
11
+ export function checkJournal(entries, fail) {
12
+ const update = u => u === undefined || object(u) && ['attempted', 'verified'].includes(u.stage) && typeof u.intentHash === 'string' && (u.stage === 'attempted') === Array.isArray(u.before);
13
+ if (!Array.isArray(entries) || new Set(entries.map(e => e?.key)).size !== entries.length || entries.some(e => !object(e) || e.version !== 1 || !nonempty(e.key) || !['attempted', 'verified'].includes(e.stage) || typeof e.intentHash !== 'string' || !Array.isArray(e.baseline) || e.stage === 'verified' && !positive(e.recordsetId) || !update(e.update))) fail('invalid-journal');
14
+ }
15
+
16
+ export function rowHelpers(fail) {
17
+ function instant(value, stored = false) {
18
+ if (typeof value !== 'string') fail('invalid-date');
19
+ // Enrollment/assessment reads emit UTC without a suffix or with a numeric hour offset.
20
+ const text = stored ? value.replace(' ', 'T').replace(/([+-]\d\d)$/, '$1:00') + (/(?:Z|[+-]\d\d(?::\d\d)?)$/.test(value) ? '' : 'Z') : value;
21
+ if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.000)?(?:Z|[+-]\d{2}:\d{2})$/.test(text) || !Number.isFinite(Date.parse(text)) || new Date(text.slice(0, 19) + 'Z').toISOString().slice(0, 19) !== text.slice(0, 19)) fail('invalid-date');
22
+ return new Date(text).toISOString();
23
+ }
24
+ function exactFields(value, allowed) {
25
+ if (!object(value) || Object.keys(value).some(key => !allowed.includes(key))) fail('invalid-input');
26
+ }
27
+ function valueOf(question) {
28
+ if (question.answer_value === null || question.answer_value === undefined || question.answer_value === '') return null;
29
+ if (question.question_type === 'date') return instant(question.answer_value, true);
30
+ if (typeof question.answer_value !== 'string') fail('invalid-answer-read');
31
+ return question.answer_value;
32
+ }
33
+ /** Every non-label child of the mapped parent, by field name, or a conflict. */
34
+ function childIndex(definition, assessmentCode, parentField, tenant) {
35
+ if (definition?.tenant !== tenant || definition.assessmentCode !== assessmentCode || !Array.isArray(definition.questions)) fail('invalid-definition');
36
+ const parents = definition.questions.filter(q => q.fieldName === parentField && q.questionType === 'sub_assessment');
37
+ if (parents.length !== 1 || !positive(parents[0].questionId) || !Array.isArray(parents[0].children)) fail('parent-unavailable');
38
+ const parent = parents[0], index = new Map();
39
+ for (const q of parent.children) {
40
+ if (['label', 'blank'].includes(q.questionType)) continue;
41
+ if (!nonempty(q.fieldName) || !positive(q.questionId) || index.has(q.fieldName)) fail('ambiguous-definition');
42
+ index.set(q.fieldName, q);
43
+ }
44
+ return {parent, index};
45
+ }
46
+ return {instant, exactFields, valueOf, childIndex};
47
+ }
48
+
49
+ /**
50
+ * kind: journal scope name; noun: used in event-specific conflict reasons; dateKey: the input's
51
+ * event instant. compile(definition, input, enrollment) returns {expected, empty, intent, body}:
52
+ * answers that must read back, fields that must read back empty, extra intent-hash content and
53
+ * the write request. write(input, body) sends it once.
54
+ */
55
+ export function createRowRecorder({api, store, fail, kind, noun, dateKey, parentField, normalize, compile, write}) {
56
+ const tenantScope = apiScope(api);
57
+ const {instant, valueOf} = rowHelpers(fail);
58
+ async function readEnrollment(input) {
59
+ const rows = [], ids = new Set(); let total;
60
+ for (let page = 1; page <= 100; page++) {
61
+ const response = await api.enrollments.listForClient({path: {clientId: input.clientId}, query: {perPage: 100, startPage: page, total_count: true}});
62
+ if (!Number.isSafeInteger(response?.count) || response.count < 0 || !Array.isArray(response.values) || total !== undefined && total !== response.count) fail('enrollment-count-unavailable');
63
+ total = response.count;
64
+ for (const row of response.values) {
65
+ if (!object(row) || !positive(row.enrollmentId) || row.clientId !== input.clientId || ids.has(row.enrollmentId)) fail('invalid-enrollment-read');
66
+ ids.add(row.enrollmentId); rows.push(row);
67
+ }
68
+ if (rows.length === total) {
69
+ const row = rows.find(row => row.enrollmentId === input.enrollmentId);
70
+ if (!row || row.isActive !== true || !positive(row.enrollmentProvider)) fail('enrollment-unavailable');
71
+ const entryDate = instant(row.projectStartDate, true);
72
+ if (input[dateKey] < entryDate || row.projectExitDate && input[dateKey] > instant(row.projectExitDate, true)) fail(`${noun}-outside-enrollment`);
73
+ return {entryDate, providerId: row.enrollmentProvider};
74
+ }
75
+ if (rows.length > total || !response.values.length) fail('incomplete-enrollment-read');
76
+ }
77
+ fail('enrollment-page-limit');
78
+ }
79
+ async function readRows(input, instant = input[dateKey]) {
80
+ // An undated read omits rows dated after the server's current time, and a read at exactly
81
+ // a row's instant omits its yes/no answers. Read one second after the later of the two.
82
+ const at = new Date(Math.max(Date.now(), Date.parse(instant)) + 1000).toISOString().slice(0, 19) + 'Z';
83
+ const response = await api.assessments.get({path: {clientId: input.clientId, assessmentCode: input.assessmentCode}, query: {dateEffective: at}});
84
+ const questions = toItems(response);
85
+ if (!Array.isArray(questions) || object(response) && 'count' in response && response.count !== questions.length) fail('incomplete-assessment-read');
86
+ const parents = questions.filter(q => q?.virt_field_name === parentField && q.question_type === 'sub_assessment');
87
+ if (parents.length !== 1 || !Array.isArray(parents[0].sub_assessment_answer_list)) fail('rows-unavailable');
88
+ const rows = parents[0].sub_assessment_answer_list, ids = new Set();
89
+ if (rows.length > 10000) fail('row-limit');
90
+ return rows.map(row => {
91
+ if (!positive(row.recordset_id) || ids.has(row.recordset_id) || !Array.isArray(row.sub_assessment_list)) fail('ambiguous-row-read');
92
+ ids.add(row.recordset_id);
93
+ const fields = {};
94
+ for (const q of row.sub_assessment_list) {
95
+ if (['label', 'blank'].includes(q.question_type)) continue;
96
+ if (!nonempty(q.virt_field_name) || Object.hasOwn(fields, q.virt_field_name) || typeof q.question_type !== 'string') fail('ambiguous-answer-read');
97
+ Object.defineProperty(fields, q.virt_field_name, {value: {type: q.question_type, value: valueOf(q)}, enumerable: true});
98
+ }
99
+ return {id: row.recordset_id, fields};
100
+ });
101
+ }
102
+ const answered = (row, {expected}) => expected.every(q => row.fields[q.virt_field_name]?.type === q.question_type && row.fields[q.virt_field_name]?.value === valueOf(q));
103
+ // `empty` fields must be empty only on the new row; a later end date does not undo the event.
104
+ const matches = (row, compiled) => compiled.empty.every(field => (row.fields[field]?.value ?? null) === null) && answered(row, compiled);
105
+
106
+ const scopeOf = clientId => JSON.stringify([tenantScope, kind, clientId]);
107
+ return {readRows, scopeOf, async record(raw) {
108
+ const input = normalize(raw), scope = scopeOf(input.clientId);
109
+ return store.withLock(scope, async () => {
110
+ const entries = await store.read(scope);
111
+ checkJournal(entries, fail);
112
+ let entry = entries.find(e => e.key === input.key);
113
+ // A pending in-place update of an earlier row is unfinished work too.
114
+ if (entries.some(e => (e.stage === 'attempted' || e.update?.stage === 'attempted') && e.key !== input.key)) fail(`unfinished-${noun}`);
115
+ const enrollment = await readEnrollment(input);
116
+ const definition = await api.assessments.getDefinition({clientId: input.clientId, assessmentCode: input.assessmentCode, effectiveDate: input[dateKey], refresh: true});
117
+ const compiled = compile(definition, input, enrollment);
118
+ const intentHash = await semanticHash({input, enrollment, ...compiled.intent});
119
+ if (entry && entry.intentHash !== intentHash) fail('intent-or-schema-changed');
120
+ let rows = await readRows(input);
121
+ const result = (row, outcome) => ({key: input.key, clientId: input.clientId, enrollmentId: input.enrollmentId, recordsetId: row.id, startDate: enrollment.entryDate, endDate: null, [dateKey]: input[dateKey], outcome});
122
+ if (entry?.stage === 'verified') {
123
+ const row = rows.find(row => row.id === entry.recordsetId);
124
+ if (!row || !answered(row, compiled)) fail(`verified-${noun}-changed`);
125
+ return result(row, 'already-present');
126
+ }
127
+ const reconcile = async () => {
128
+ for (const before of entry.baseline) {
129
+ const after = rows.find(row => row.id === before.id);
130
+ if (!after || await semanticHash(after) !== await semanticHash(before)) fail(`existing-${noun}-changed`);
131
+ }
132
+ const added = rows.filter(row => !entry.baseline.some(before => before.id === row.id));
133
+ if (added.length !== 1 || !matches(added[0], compiled)) fail(`uncertain-${noun}-save`);
134
+ return added[0];
135
+ };
136
+ if (entry) {
137
+ const row = await reconcile();
138
+ entry.stage = 'verified'; entry.recordsetId = row.id;
139
+ await store.write(scope, structuredClone(entries));
140
+ return result(row, 'reconciled');
141
+ }
142
+ entry = {version: 1, key: input.key, intentHash, stage: 'attempted', baseline: rows};
143
+ entries.push(entry);
144
+ await store.write(scope, structuredClone(entries));
145
+ let failed = false;
146
+ try { await write(input, compiled.body); } catch { failed = true; }
147
+ rows = await readRows(input);
148
+ const row = await reconcile();
149
+ entry.stage = 'verified'; entry.recordsetId = row.id;
150
+ await store.write(scope, structuredClone(entries));
151
+ return result(row, failed ? 'reconciled' : 'verified');
152
+ });
153
+ }};
154
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"encoding.d.ts","sourceRoot":"","sources":["../../src/encoding.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAK1F,MAAM,MAAM,WAAW,GAAG,oBAAoB,GAAG,gBAAgB,GAAG,oBAAoB,CAAC;AACzF,MAAM,MAAM,WAAW,GAAG;IAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,WAAW,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,gBAAgB,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAA;CAAE,CAAC;AAC7K,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAClD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,YAAY,GAAG,YAAY,GAAG,QAAQ,CAAC;IACjD,0GAA0G;IAC1G,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,kBAAkB,EAAE,MAAM,EAAE,cAAc,KAAK,MAAM,GAAG,SAAS,CAAC;IAC1F,wGAAwG;IACxG,UAAU,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;IAC7D,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,kBAAkB,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,KAAK,MAAM,CAAC;CAClF;AACD,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB;AA2DD,wBAAgB,aAAa,CAAC,SAAS,EAAE,SAAS,kBAAkB,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,GAAG,eAAe,EAAE,CAgBrI;AAED,iFAAiF;AACjF,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,kBAAkB,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,GAAG;IAAE,cAAc,EAAE,eAAe,EAAE,CAAA;CAAE,CAUvJ"}
1
+ {"version":3,"file":"encoding.d.ts","sourceRoot":"","sources":["../../src/encoding.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAK1F,MAAM,MAAM,WAAW,GAAG,oBAAoB,GAAG,gBAAgB,GAAG,oBAAoB,CAAC;AACzF,MAAM,MAAM,WAAW,GAAG;IAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,WAAW,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,gBAAgB,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAA;CAAE,CAAC;AAC7K,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAClD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,YAAY,GAAG,YAAY,GAAG,QAAQ,CAAC;IACjD,0GAA0G;IAC1G,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,kBAAkB,EAAE,MAAM,EAAE,cAAc,KAAK,MAAM,GAAG,SAAS,CAAC;IAC1F,wGAAwG;IACxG,UAAU,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;IAC7D,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,kBAAkB,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,KAAK,MAAM,CAAC;CAClF;AACD,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB;AA2DD,wBAAgB,aAAa,CAAC,SAAS,EAAE,SAAS,kBAAkB,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,GAAG,eAAe,EAAE,CAgBrI;AAED,iFAAiF;AACjF,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,kBAAkB,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,GAAG;IAAE,cAAc,EAAE,eAAe,EAAE,CAAA;CAAE,CAWvJ"}
@@ -110,10 +110,11 @@ function encodeNewSubassessmentRow(parent, answers, options) {
110
110
  const encoded = encodeAnswers(parent.children, answers, options);
111
111
  const included = new Set(encoded.map(q => q.virt_field_name));
112
112
  for (const question of parent.children) {
113
+ // An unanswered provider child is omitted; answered ones are still refused for mobile in encodeValue.
113
114
  if (!question.fieldName || included.has(question.fieldName) || ["label", "blank", "yes_no"].includes(question.questionType))
114
115
  continue;
115
116
  if (question.questionType === "provider" && options.endpoint === "mobile")
116
- throw new errors_js_1.ValidationError("mobile provider answers are unsupported", [question.fieldName]);
117
+ continue;
117
118
  encoded.push({ question_id: question.questionId, question_type: question.questionType, virt_field_name: question.fieldName, answer_value: "" });
118
119
  }
119
120
  return { subAssessments: encoded };
@@ -1 +1 @@
1
- {"version":3,"file":"encoding.js","sourceRoot":"","sources":["../../src/encoding.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PicklistOption, PicklistSelector, QuestionDefinition } from \"./metadata.js\";\nimport { resolvePicklistOption } from \"./metadata.js\";\nimport { ValidationError } from \"./errors.js\";\nimport { resolveRaceCombination } from \"./race.js\";\n\nexport type AnswerState = \"client_doesnt_know\" | \"client_refused\" | \"data_not_collected\";\nexport type AnswerValue = { value: string | number | boolean | string[] } | { state: AnswerState } | { option: PicklistSelector } | { options: readonly PicklistSelector[] };\nexport type Answers = Record<string, AnswerValue>;\nexport interface EncodingOptions {\n endpoint: \"standalone\" | \"enrollment\" | \"mobile\";\n /** Tenant-verified display labels, including exact whitespace/HUD suffixes, for label-based endpoints. */\n writeLabel?: (question: QuestionDefinition, option: PicklistOption) => string | undefined;\n /** Application configuration supplies the relevant option codes. No answer-state labels are guessed. */\n stateCodes?: Partial<Record<AnswerState, readonly string[]>>;\n multiValue?: (question: QuestionDefinition, values: readonly string[]) => string;\n}\nexport interface EncodedQuestion {\n question_id: number;\n question_type: string;\n virt_field_name: string;\n answer_value: string;\n}\n\nfunction encodeValue(question: QuestionDefinition, answer: AnswerValue, options: EncodingOptions): string | undefined {\n const field = question.fieldName ?? `#${question.questionId}`;\n if ([\"label\", \"blank\", \"sub_assessment\"].includes(question.questionType)) throw new ValidationError(\"assessment encoding\", [field]);\n if (question.questionType === \"provider\" && options.endpoint === \"mobile\") throw new ValidationError(\"mobile provider answers are unsupported\", [field]);\n if (\"state\" in answer) {\n if (question.questionType !== \"lookup\") return undefined;\n const codes = options.stateCodes?.[answer.state];\n if (!codes) throw new ValidationError(\"answer-state code configuration required\", [field]);\n const found = question.options.filter(option => option.code !== null && codes.includes(option.code));\n if (!found.length) return undefined;\n if (found.length !== 1 || !found[0]) throw new ValidationError(\"ambiguous answer-state option\", [field]);\n const option = question.fieldName === \"HUD_RACEETHNICITY\" && found[0].code !== null\n ? resolveRaceCombination(question, [{ code: found[0].code }]) : found[0];\n return lookupLabel(question, option, options);\n }\n if (question.questionType === \"lookup\") {\n if (question.fieldName === \"HUD_RACEETHNICITY\") {\n const selectors = \"options\" in answer ? answer.options : \"option\" in answer ? [answer.option]\n : typeof answer.value === \"string\" ? [{ name: answer.value }]\n : Array.isArray(answer.value) ? answer.value.map(name => ({ name })) : undefined;\n if (!selectors) throw new ValidationError(\"race requires category selectors\", [field]);\n return lookupLabel(question, resolveRaceCombination(question, selectors), options);\n }\n if (\"options\" in answer) throw new ValidationError(\"multiple options require a supported combination field\", [field]);\n if (\"option\" in answer) return lookupLabel(question, resolvePicklistOption(question, answer.option), options);\n if (typeof answer.value !== \"string\") throw new ValidationError(\"lookup requires a code or name selector\", [field]);\n return lookupLabel(question, resolvePicklistOption(question, { name: answer.value }), options);\n }\n if (!(\"value\" in answer)) throw new ValidationError(\"picklist selector on a non-lookup question\", [field]);\n const value = answer.value;\n if (Array.isArray(value)) {\n if (!options.multiValue) throw new ValidationError(\"multi-value encoding requires an explicit encoder\", [field]);\n return options.multiValue(question, value);\n }\n switch (question.questionType) {\n case \"yes_no\":\n if (value === true || value === \"Yes\") return \"Yes\";\n if (value === false || value === \"No\") return \"No\";\n throw new ValidationError(\"yes_no encoding\", [field]);\n case \"int\": case \"provider\":\n if ((typeof value !== \"number\" && typeof value !== \"string\") || value === \"\" || !Number.isSafeInteger(Number(value))) throw new ValidationError(\"integer encoding\", [field]);\n return String(value);\n case \"money\":\n if ((typeof value !== \"number\" && typeof value !== \"string\") || value === \"\" || !Number.isFinite(Number(value))) throw new ValidationError(\"money encoding\", [field]);\n return String(value);\n case \"date\": case \"textbox\": case \"textarea\": return String(value);\n default: throw new ValidationError(\"unsupported question type\", [field]);\n }\n}\n\nfunction lookupLabel(question: QuestionDefinition, option: PicklistOption, options: EncodingOptions): string {\n if (options.endpoint === \"mobile\") return String(option.id);\n const label = options.writeLabel?.(question, option);\n if (!label) throw new ValidationError(\"verified lookup write label required\", [question.fieldName ?? `#${question.questionId}`]);\n return label;\n}\n\nexport function encodeAnswers(questions: readonly QuestionDefinition[], answers: Answers, options: EncodingOptions): EncodedQuestion[] {\n const index = new Map<string, QuestionDefinition>();\n for (const question of questions) if (question.fieldName) {\n if (index.has(question.fieldName)) throw new ValidationError(\"ambiguous question field\", [question.fieldName]);\n index.set(question.fieldName, question);\n }\n const unknown = Object.keys(answers).filter(field => !index.has(field));\n if (unknown.length) throw new ValidationError(\"unknown assessment fields\", unknown);\n const result: EncodedQuestion[] = [];\n for (const [field, answer] of Object.entries(answers)) {\n const question = index.get(field);\n if (!question) throw new ValidationError(\"unknown assessment fields\", [field]);\n const value = encodeValue(question, answer, options);\n if (value !== undefined) result.push({ question_id: question.questionId, question_type: question.questionType, virt_field_name: field, answer_value: value });\n }\n return result;\n}\n\n/** Builds one new row only. Never accepts or echoes an existing recordset ID. */\nexport function encodeNewSubassessmentRow(parent: QuestionDefinition, answers: Answers, options: EncodingOptions): { subAssessments: EncodedQuestion[] } {\n if (parent.questionType !== \"sub_assessment\") throw new ValidationError(\"subassessment parent required\");\n const encoded = encodeAnswers(parent.children, answers, options);\n const included = new Set(encoded.map(q => q.virt_field_name));\n for (const question of parent.children) {\n if (!question.fieldName || included.has(question.fieldName) || [\"label\", \"blank\", \"yes_no\"].includes(question.questionType)) continue;\n if (question.questionType === \"provider\" && options.endpoint === \"mobile\") throw new ValidationError(\"mobile provider answers are unsupported\", [question.fieldName]);\n encoded.push({ question_id: question.questionId, question_type: question.questionType, virt_field_name: question.fieldName, answer_value: \"\" });\n }\n return { subAssessments: encoded };\n}\n"]}
1
+ {"version":3,"file":"encoding.js","sourceRoot":"","sources":["../../src/encoding.ts"],"names":[],"mappings":";;AAgFA,sCAgBC;AAGD,8DAWC;AA7GD,+CAAsD;AACtD,2CAA8C;AAC9C,uCAAmD;AAoBnD,SAAS,WAAW,CAAC,QAA4B,EAAE,MAAmB,EAAE,OAAwB;IAC9F,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,IAAI,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;IAC9D,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,gBAAgB,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC;QAAE,MAAM,IAAI,2BAAe,CAAC,qBAAqB,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IACpI,IAAI,QAAQ,CAAC,YAAY,KAAK,UAAU,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAAE,MAAM,IAAI,2BAAe,CAAC,yCAAyC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IACzJ,IAAI,OAAO,IAAI,MAAM,EAAE,CAAC;QACtB,IAAI,QAAQ,CAAC,YAAY,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QACzD,MAAM,KAAK,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACjD,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,2BAAe,CAAC,0CAA0C,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3F,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QACrG,IAAI,CAAC,KAAK,CAAC,MAAM;YAAE,OAAO,SAAS,CAAC;QACpC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;YAAE,MAAM,IAAI,2BAAe,CAAC,+BAA+B,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;QACzG,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,KAAK,mBAAmB,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI;YACjF,CAAC,CAAC,IAAA,gCAAsB,EAAC,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC3E,OAAO,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;IAChD,CAAC;IACD,IAAI,QAAQ,CAAC,YAAY,KAAK,QAAQ,EAAE,CAAC;QACvC,IAAI,QAAQ,CAAC,SAAS,KAAK,mBAAmB,EAAE,CAAC;YAC/C,MAAM,SAAS,GAAG,SAAS,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;gBAC3F,CAAC,CAAC,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;oBAC7D,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YACnF,IAAI,CAAC,SAAS;gBAAE,MAAM,IAAI,2BAAe,CAAC,kCAAkC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;YACvF,OAAO,WAAW,CAAC,QAAQ,EAAE,IAAA,gCAAsB,EAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,OAAO,CAAC,CAAC;QACrF,CAAC;QACD,IAAI,SAAS,IAAI,MAAM;YAAE,MAAM,IAAI,2BAAe,CAAC,wDAAwD,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;QACtH,IAAI,QAAQ,IAAI,MAAM;YAAE,OAAO,WAAW,CAAC,QAAQ,EAAE,IAAA,mCAAqB,EAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;QAC9G,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;YAAE,MAAM,IAAI,2BAAe,CAAC,yCAAyC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;QACpH,OAAO,WAAW,CAAC,QAAQ,EAAE,IAAA,mCAAqB,EAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC;IACjG,CAAC;IACD,IAAI,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC;QAAE,MAAM,IAAI,2BAAe,CAAC,4CAA4C,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3G,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAC3B,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,IAAI,CAAC,OAAO,CAAC,UAAU;YAAE,MAAM,IAAI,2BAAe,CAAC,mDAAmD,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;QACjH,OAAO,OAAO,CAAC,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC7C,CAAC;IACD,QAAQ,QAAQ,CAAC,YAAY,EAAE,CAAC;QAC9B,KAAK,QAAQ;YACX,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,KAAK;gBAAE,OAAO,KAAK,CAAC;YACpD,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,IAAI;gBAAE,OAAO,IAAI,CAAC;YACnD,MAAM,IAAI,2BAAe,CAAC,iBAAiB,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;QACxD,KAAK,KAAK,CAAC;QAAC,KAAK,UAAU;YACzB,IAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,IAAI,KAAK,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBAAE,MAAM,IAAI,2BAAe,CAAC,kBAAkB,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;YAC7K,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;QACvB,KAAK,OAAO;YACV,IAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,IAAI,KAAK,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBAAE,MAAM,IAAI,2BAAe,CAAC,gBAAgB,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;YACtK,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;QACvB,KAAK,MAAM,CAAC;QAAC,KAAK,SAAS,CAAC;QAAC,KAAK,UAAU,CAAC,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;QACnE,OAAO,CAAC,CAAC,MAAM,IAAI,2BAAe,CAAC,2BAA2B,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3E,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,QAA4B,EAAE,MAAsB,EAAE,OAAwB;IACjG,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACrD,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,2BAAe,CAAC,sCAAsC,EAAE,CAAC,QAAQ,CAAC,SAAS,IAAI,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;IACjI,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAgB,aAAa,CAAC,SAAwC,EAAE,OAAgB,EAAE,OAAwB;IAChH,MAAM,KAAK,GAAG,IAAI,GAAG,EAA8B,CAAC;IACpD,KAAK,MAAM,QAAQ,IAAI,SAAS;QAAE,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;YACzD,IAAI,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC;gBAAE,MAAM,IAAI,2BAAe,CAAC,0BAA0B,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;YAC/G,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAC1C,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;IACxE,IAAI,OAAO,CAAC,MAAM;QAAE,MAAM,IAAI,2BAAe,CAAC,2BAA2B,EAAE,OAAO,CAAC,CAAC;IACpF,MAAM,MAAM,GAAsB,EAAE,CAAC;IACrC,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACtD,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAClC,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,2BAAe,CAAC,2BAA2B,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;QAC/E,MAAM,KAAK,GAAG,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;QACrD,IAAI,KAAK,KAAK,SAAS;YAAE,MAAM,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,UAAU,EAAE,aAAa,EAAE,QAAQ,CAAC,YAAY,EAAE,eAAe,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC;IAChK,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,iFAAiF;AACjF,SAAgB,yBAAyB,CAAC,MAA0B,EAAE,OAAgB,EAAE,OAAwB;IAC9G,IAAI,MAAM,CAAC,YAAY,KAAK,gBAAgB;QAAE,MAAM,IAAI,2BAAe,CAAC,+BAA+B,CAAC,CAAC;IACzG,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACjE,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC;IAC9D,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACvC,sGAAsG;QACtG,IAAI,CAAC,QAAQ,CAAC,SAAS,IAAI,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC;YAAE,SAAS;QACtI,IAAI,QAAQ,CAAC,YAAY,KAAK,UAAU,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ;YAAE,SAAS;QACpF,OAAO,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,UAAU,EAAE,aAAa,EAAE,QAAQ,CAAC,YAAY,EAAE,eAAe,EAAE,QAAQ,CAAC,SAAS,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;IAClJ,CAAC;IACD,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC;AACrC,CAAC","sourcesContent":["import type { PicklistOption, PicklistSelector, QuestionDefinition } from \"./metadata.js\";\nimport { resolvePicklistOption } from \"./metadata.js\";\nimport { ValidationError } from \"./errors.js\";\nimport { resolveRaceCombination } from \"./race.js\";\n\nexport type AnswerState = \"client_doesnt_know\" | \"client_refused\" | \"data_not_collected\";\nexport type AnswerValue = { value: string | number | boolean | string[] } | { state: AnswerState } | { option: PicklistSelector } | { options: readonly PicklistSelector[] };\nexport type Answers = Record<string, AnswerValue>;\nexport interface EncodingOptions {\n endpoint: \"standalone\" | \"enrollment\" | \"mobile\";\n /** Tenant-verified display labels, including exact whitespace/HUD suffixes, for label-based endpoints. */\n writeLabel?: (question: QuestionDefinition, option: PicklistOption) => string | undefined;\n /** Application configuration supplies the relevant option codes. No answer-state labels are guessed. */\n stateCodes?: Partial<Record<AnswerState, readonly string[]>>;\n multiValue?: (question: QuestionDefinition, values: readonly string[]) => string;\n}\nexport interface EncodedQuestion {\n question_id: number;\n question_type: string;\n virt_field_name: string;\n answer_value: string;\n}\n\nfunction encodeValue(question: QuestionDefinition, answer: AnswerValue, options: EncodingOptions): string | undefined {\n const field = question.fieldName ?? `#${question.questionId}`;\n if ([\"label\", \"blank\", \"sub_assessment\"].includes(question.questionType)) throw new ValidationError(\"assessment encoding\", [field]);\n if (question.questionType === \"provider\" && options.endpoint === \"mobile\") throw new ValidationError(\"mobile provider answers are unsupported\", [field]);\n if (\"state\" in answer) {\n if (question.questionType !== \"lookup\") return undefined;\n const codes = options.stateCodes?.[answer.state];\n if (!codes) throw new ValidationError(\"answer-state code configuration required\", [field]);\n const found = question.options.filter(option => option.code !== null && codes.includes(option.code));\n if (!found.length) return undefined;\n if (found.length !== 1 || !found[0]) throw new ValidationError(\"ambiguous answer-state option\", [field]);\n const option = question.fieldName === \"HUD_RACEETHNICITY\" && found[0].code !== null\n ? resolveRaceCombination(question, [{ code: found[0].code }]) : found[0];\n return lookupLabel(question, option, options);\n }\n if (question.questionType === \"lookup\") {\n if (question.fieldName === \"HUD_RACEETHNICITY\") {\n const selectors = \"options\" in answer ? answer.options : \"option\" in answer ? [answer.option]\n : typeof answer.value === \"string\" ? [{ name: answer.value }]\n : Array.isArray(answer.value) ? answer.value.map(name => ({ name })) : undefined;\n if (!selectors) throw new ValidationError(\"race requires category selectors\", [field]);\n return lookupLabel(question, resolveRaceCombination(question, selectors), options);\n }\n if (\"options\" in answer) throw new ValidationError(\"multiple options require a supported combination field\", [field]);\n if (\"option\" in answer) return lookupLabel(question, resolvePicklistOption(question, answer.option), options);\n if (typeof answer.value !== \"string\") throw new ValidationError(\"lookup requires a code or name selector\", [field]);\n return lookupLabel(question, resolvePicklistOption(question, { name: answer.value }), options);\n }\n if (!(\"value\" in answer)) throw new ValidationError(\"picklist selector on a non-lookup question\", [field]);\n const value = answer.value;\n if (Array.isArray(value)) {\n if (!options.multiValue) throw new ValidationError(\"multi-value encoding requires an explicit encoder\", [field]);\n return options.multiValue(question, value);\n }\n switch (question.questionType) {\n case \"yes_no\":\n if (value === true || value === \"Yes\") return \"Yes\";\n if (value === false || value === \"No\") return \"No\";\n throw new ValidationError(\"yes_no encoding\", [field]);\n case \"int\": case \"provider\":\n if ((typeof value !== \"number\" && typeof value !== \"string\") || value === \"\" || !Number.isSafeInteger(Number(value))) throw new ValidationError(\"integer encoding\", [field]);\n return String(value);\n case \"money\":\n if ((typeof value !== \"number\" && typeof value !== \"string\") || value === \"\" || !Number.isFinite(Number(value))) throw new ValidationError(\"money encoding\", [field]);\n return String(value);\n case \"date\": case \"textbox\": case \"textarea\": return String(value);\n default: throw new ValidationError(\"unsupported question type\", [field]);\n }\n}\n\nfunction lookupLabel(question: QuestionDefinition, option: PicklistOption, options: EncodingOptions): string {\n if (options.endpoint === \"mobile\") return String(option.id);\n const label = options.writeLabel?.(question, option);\n if (!label) throw new ValidationError(\"verified lookup write label required\", [question.fieldName ?? `#${question.questionId}`]);\n return label;\n}\n\nexport function encodeAnswers(questions: readonly QuestionDefinition[], answers: Answers, options: EncodingOptions): EncodedQuestion[] {\n const index = new Map<string, QuestionDefinition>();\n for (const question of questions) if (question.fieldName) {\n if (index.has(question.fieldName)) throw new ValidationError(\"ambiguous question field\", [question.fieldName]);\n index.set(question.fieldName, question);\n }\n const unknown = Object.keys(answers).filter(field => !index.has(field));\n if (unknown.length) throw new ValidationError(\"unknown assessment fields\", unknown);\n const result: EncodedQuestion[] = [];\n for (const [field, answer] of Object.entries(answers)) {\n const question = index.get(field);\n if (!question) throw new ValidationError(\"unknown assessment fields\", [field]);\n const value = encodeValue(question, answer, options);\n if (value !== undefined) result.push({ question_id: question.questionId, question_type: question.questionType, virt_field_name: field, answer_value: value });\n }\n return result;\n}\n\n/** Builds one new row only. Never accepts or echoes an existing recordset ID. */\nexport function encodeNewSubassessmentRow(parent: QuestionDefinition, answers: Answers, options: EncodingOptions): { subAssessments: EncodedQuestion[] } {\n if (parent.questionType !== \"sub_assessment\") throw new ValidationError(\"subassessment parent required\");\n const encoded = encodeAnswers(parent.children, answers, options);\n const included = new Set(encoded.map(q => q.virt_field_name));\n for (const question of parent.children) {\n // An unanswered provider child is omitted; answered ones are still refused for mobile in encodeValue.\n if (!question.fieldName || included.has(question.fieldName) || [\"label\", \"blank\", \"yes_no\"].includes(question.questionType)) continue;\n if (question.questionType === \"provider\" && options.endpoint === \"mobile\") continue;\n encoded.push({ question_id: question.questionId, question_type: question.questionType, virt_field_name: question.fieldName, answer_value: \"\" });\n }\n return { subAssessments: encoded };\n}\n"]}
@@ -17,7 +17,10 @@ export declare class ValidationError extends WellSkyError {
17
17
  constructor(operation: string, fields?: readonly string[]);
18
18
  }
19
19
  export declare class UpstreamError extends WellSkyError {
20
- constructor(operation: string, status: number, requestId?: string, retryAfterMs?: number);
20
+ /** Opt-in private diagnostics; non-enumerable and absent by default. */
21
+ readonly responseBody?: string;
22
+ readonly responseBodyTruncated?: boolean;
23
+ constructor(operation: string, status: number, requestId?: string, retryAfterMs?: number, responseBody?: string);
21
24
  }
22
25
  export declare class AmbiguousCommitError extends WellSkyError {
23
26
  constructor(operation: string);
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,qBAAa,mBAAoB,SAAQ,YAAY;gBACvC,SAAS,EAAE,MAAM;CAC9B;AACD,qBAAa,SAAU,SAAQ,YAAY;gBAC7B,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAC/C;AACD,qBAAa,cAAe,SAAQ,YAAY;gBAClC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,SAAM,EAAE,SAAS,CAAC,EAAE,MAAM;CACtF;AACD,qBAAa,aAAc,SAAQ,YAAY;IAC7C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBACX,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;CAC7C;AACD,qBAAa,eAAgB,SAAQ,YAAY;IAC/C,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;gBACvB,SAAS,EAAE,MAAM,EAAE,MAAM,GAAE,SAAS,MAAM,EAAO;CAC9D;AACD,qBAAa,aAAc,SAAQ,YAAY;gBACjC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM;CACzF;AACD,qBAAa,oBAAqB,SAAQ,YAAY;gBACxC,SAAS,EAAE,MAAM;CAC9B;AACD,qBAAa,oBAAqB,SAAQ,YAAY;IACpD,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;gBACvB,MAAM,EAAE,SAAS,MAAM,EAAE;CACtC;AACD,qBAAa,yBAA0B,SAAQ,YAAY;IACzD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBACX,KAAK,EAAE,MAAM;CAC1B"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,qBAAa,mBAAoB,SAAQ,YAAY;gBACvC,SAAS,EAAE,MAAM;CAC9B;AACD,qBAAa,SAAU,SAAQ,YAAY;gBAC7B,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAC/C;AACD,qBAAa,cAAe,SAAQ,YAAY;gBAClC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,SAAM,EAAE,SAAS,CAAC,EAAE,MAAM;CACtF;AACD,qBAAa,aAAc,SAAQ,YAAY;IAC7C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBACX,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;CAC7C;AACD,qBAAa,eAAgB,SAAQ,YAAY;IAC/C,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;gBACvB,SAAS,EAAE,MAAM,EAAE,MAAM,GAAE,SAAS,MAAM,EAAO;CAC9D;AACD,qBAAa,aAAc,SAAQ,YAAY;IAC7C,wEAAwE;IACxE,SAAiB,YAAY,CAAC,EAAE,MAAM,CAAC;IACvC,SAAiB,qBAAqB,CAAC,EAAE,OAAO,CAAC;gBACrC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM;CAOhH;AACD,qBAAa,oBAAqB,SAAQ,YAAY;gBACxC,SAAS,EAAE,MAAM;CAC9B;AACD,qBAAa,oBAAqB,SAAQ,YAAY;IACpD,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;gBACvB,MAAM,EAAE,SAAS,MAAM,EAAE;CACtC;AACD,qBAAa,yBAA0B,SAAQ,YAAY;IACzD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBACX,KAAK,EAAE,MAAM;CAC1B"}
@@ -25,7 +25,15 @@ class ValidationError extends runtime_js_1.WellSkyError {
25
25
  }
26
26
  exports.ValidationError = ValidationError;
27
27
  class UpstreamError extends runtime_js_1.WellSkyError {
28
- constructor(operation, status, requestId, retryAfterMs) { super("http", operation, { status, ...(requestId === undefined ? {} : { requestId }), ...(retryAfterMs === undefined ? {} : { retryAfterMs }) }); this.name = "UpstreamError"; }
28
+ constructor(operation, status, requestId, retryAfterMs, responseBody) {
29
+ super("http", operation, { status, ...(requestId === undefined ? {} : { requestId }), ...(retryAfterMs === undefined ? {} : { retryAfterMs }) });
30
+ this.name = "UpstreamError";
31
+ if (responseBody !== undefined)
32
+ Object.defineProperties(this, {
33
+ responseBody: { value: responseBody.slice(0, 16384), enumerable: false },
34
+ responseBodyTruncated: { value: responseBody.length > 16384, enumerable: false },
35
+ });
36
+ }
29
37
  }
30
38
  exports.UpstreamError = UpstreamError;
31
39
  class AmbiguousCommitError extends runtime_js_1.WellSkyError {
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":";;;AAAA,6CAA4C;AAE5C,MAAa,mBAAoB,SAAQ,yBAAY;IACnD,YAAY,SAAiB,IAAI,KAAK,CAAC,iBAAiB,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC,CAAC,CAAC;CAC3G;AAFD,kDAEC;AACD,MAAa,SAAU,SAAQ,yBAAY;IACzC,YAAY,SAAiB,EAAE,MAAe,IAAI,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC,CAAC,CAAC;CAC/I;AAFD,8BAEC;AACD,MAAa,cAAe,SAAQ,yBAAY;IAC9C,YAAY,SAAiB,EAAE,YAAoB,EAAE,MAAM,GAAG,GAAG,EAAE,SAAkB,IAAI,KAAK,CAAC,WAAW,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC,CAAC,CAAC;CACrO;AAFD,wCAEC;AACD,MAAa,aAAc,SAAQ,yBAAY;IACpC,KAAK,CAAS;IACvB,YAAY,SAAiB,EAAE,KAAa,IAAI,KAAK,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;CACzJ;AAHD,sCAGC;AACD,MAAa,eAAgB,SAAQ,yBAAY;IACtC,MAAM,CAAoB;IACnC,YAAY,SAAiB,EAAE,SAA4B,EAAE,IAAI,KAAK,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;CAC7J;AAHD,0CAGC;AACD,MAAa,aAAc,SAAQ,yBAAY;IAC7C,YAAY,SAAiB,EAAE,MAAc,EAAE,SAAkB,EAAE,YAAqB,IAAI,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,GAAG,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC,CAAC,CAAC;CAC7Q;AAFD,sCAEC;AACD,MAAa,oBAAqB,SAAQ,yBAAY;IACpD,YAAY,SAAiB,IAAI,KAAK,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC,CAAC,CAAC;CAC7G;AAFD,oDAEC;AACD,MAAa,oBAAqB,SAAQ,yBAAY;IAC3C,MAAM,CAAoB;IACnC,YAAY,MAAyB,IAAI,KAAK,CAAC,kBAAkB,EAAE,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;CAC5J;AAHD,oDAGC;AACD,MAAa,yBAA0B,SAAQ,yBAAY;IAChD,KAAK,CAAS;IACvB,YAAY,KAAa,IAAI,KAAK,CAAC,wBAAwB,EAAE,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC;CACpJ;AAHD,8DAGC","sourcesContent":["import { WellSkyError } from \"./runtime.js\";\n\nexport class TenantMismatchError extends WellSkyError {\n constructor(operation: string) { super(\"tenant_mismatch\", operation); this.name = \"TenantMismatchError\"; }\n}\nexport class AuthError extends WellSkyError {\n constructor(operation: string, status?: number) { super(\"auth\", operation, status === undefined ? {} : { status }); this.name = \"AuthError\"; }\n}\nexport class ThrottledError extends WellSkyError {\n constructor(operation: string, retryAfterMs: number, status = 429, requestId?: string) { super(\"throttled\", operation, { status, retryAfterMs, ...(requestId === undefined ? {} : { requestId }) }); this.name = \"ThrottledError\"; }\n}\nexport class CooldownError extends WellSkyError {\n readonly until: string;\n constructor(operation: string, until: number) { super(\"cooldown\", operation); this.name = \"CooldownError\"; this.until = new Date(until).toISOString(); }\n}\nexport class ValidationError extends WellSkyError {\n readonly fields: readonly string[];\n constructor(operation: string, fields: readonly string[] = []) { super(\"validation\", operation); this.name = \"ValidationError\"; this.fields = [...fields]; }\n}\nexport class UpstreamError extends WellSkyError {\n constructor(operation: string, status: number, requestId?: string, retryAfterMs?: number) { super(\"http\", operation, { status, ...(requestId === undefined ? {} : { requestId }), ...(retryAfterMs === undefined ? {} : { retryAfterMs }) }); this.name = \"UpstreamError\"; }\n}\nexport class AmbiguousCommitError extends WellSkyError {\n constructor(operation: string) { super(\"ambiguous_commit\", operation); this.name = \"AmbiguousCommitError\"; }\n}\nexport class DefinitionDriftError extends WellSkyError {\n readonly fields: readonly string[];\n constructor(fields: readonly string[]) { super(\"definition_drift\", \"assessment metadata\"); this.name = \"DefinitionDriftError\"; this.fields = [...fields]; }\n}\nexport class UnknownPicklistValueError extends WellSkyError {\n readonly field: string;\n constructor(field: string) { super(\"unknown_picklist_value\", \"picklist resolution\"); this.name = \"UnknownPicklistValueError\"; this.field = field; }\n}\n"]}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"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","sourcesContent":["import { WellSkyError } from \"./runtime.js\";\n\nexport class TenantMismatchError extends WellSkyError {\n constructor(operation: string) { super(\"tenant_mismatch\", operation); this.name = \"TenantMismatchError\"; }\n}\nexport class AuthError extends WellSkyError {\n constructor(operation: string, status?: number) { super(\"auth\", operation, status === undefined ? {} : { status }); this.name = \"AuthError\"; }\n}\nexport class ThrottledError extends WellSkyError {\n constructor(operation: string, retryAfterMs: number, status = 429, requestId?: string) { super(\"throttled\", operation, { status, retryAfterMs, ...(requestId === undefined ? {} : { requestId }) }); this.name = \"ThrottledError\"; }\n}\nexport class CooldownError extends WellSkyError {\n readonly until: string;\n constructor(operation: string, until: number) { super(\"cooldown\", operation); this.name = \"CooldownError\"; this.until = new Date(until).toISOString(); }\n}\nexport class ValidationError extends WellSkyError {\n readonly fields: readonly string[];\n constructor(operation: string, fields: readonly string[] = []) { super(\"validation\", operation); this.name = \"ValidationError\"; this.fields = [...fields]; }\n}\nexport class UpstreamError extends WellSkyError {\n /** Opt-in private diagnostics; non-enumerable and absent by default. */\n declare readonly responseBody?: string;\n declare readonly responseBodyTruncated?: boolean;\n constructor(operation: string, status: number, requestId?: string, retryAfterMs?: number, responseBody?: string) {\n super(\"http\", operation, { status, ...(requestId === undefined ? {} : { requestId }), ...(retryAfterMs === undefined ? {} : { retryAfterMs }) }); this.name = \"UpstreamError\";\n if (responseBody !== undefined) Object.defineProperties(this, {\n responseBody: { value: responseBody.slice(0, 16384), enumerable: false },\n responseBodyTruncated: { value: responseBody.length > 16384, enumerable: false },\n });\n }\n}\nexport class AmbiguousCommitError extends WellSkyError {\n constructor(operation: string) { super(\"ambiguous_commit\", operation); this.name = \"AmbiguousCommitError\"; }\n}\nexport class DefinitionDriftError extends WellSkyError {\n readonly fields: readonly string[];\n constructor(fields: readonly string[]) { super(\"definition_drift\", \"assessment metadata\"); this.name = \"DefinitionDriftError\"; this.fields = [...fields]; }\n}\nexport class UnknownPicklistValueError extends WellSkyError {\n readonly field: string;\n constructor(field: string) { super(\"unknown_picklist_value\", \"picklist resolution\"); this.name = \"UnknownPicklistValueError\"; this.field = field; }\n}\n"]}
@@ -33,6 +33,10 @@ export interface ClientOptions {
33
33
  * Added to the requested instant on POST AddAssessmentAnswer only. No default timezone is assumed.
34
34
  * Revalidate against saved effective dates when the server's offset changes. */
35
35
  mobileAssessmentDateCorrectionMinutes?: number;
36
+ /** Opt in to retaining up to 16384 characters of an UpstreamError response body.
37
+ * May contain private data. Excluded from JSON serialization and ordinary telemetry.
38
+ * Authentication and throttle error bodies are never retained. */
39
+ captureErrorBody?: boolean;
36
40
  /** Defaults to the public production API URL. Supply the target environment explicitly when needed. */
37
41
  baseUrl?: string;
38
42
  /** Inject a fetch-compatible transport for tests or network routing. */
@@ -91,7 +95,7 @@ export interface CountedCollection<T> {
91
95
  export type ApiResponse<T> = T | T[] | ODataCollection<T> | CountedCollection<T> | null;
92
96
  export declare function toItems<T>(response: ApiResponse<T>): T[];
93
97
  export type ErrorKind = "http" | "network" | "timeout" | "aborted" | "validation" | "protocol" | "pagination" | "tenant_mismatch" | "auth" | "throttled" | "cooldown" | "ambiguous_commit" | "definition_drift" | "unknown_picklist_value";
94
- /** Deliberately contains no credentials, query values, response bodies, or client IDs. */
98
+ /** Deliberately contains no credentials, query values, or client IDs, and no response body unless `captureErrorBody` is enabled (then non-enumerable). */
95
99
  export declare class WellSkyError extends Error {
96
100
  readonly kind: ErrorKind;
97
101
  readonly status: number | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/runtime.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/runtime.ts"],"names":[],"mappings":"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"}
@@ -16,7 +16,7 @@ function toItems(response) {
16
16
  // The envelope branch above has been checked at the boundary.
17
17
  return [response];
18
18
  }
19
- /** Deliberately contains no credentials, query values, response bodies, or client IDs. */
19
+ /** Deliberately contains no credentials, query values, or client IDs, and no response body unless `captureErrorBody` is enabled (then non-enumerable). */
20
20
  class WellSkyError extends Error {
21
21
  kind;
22
22
  status;