@choiceapps/communityservices-sdk 0.1.1 → 0.3.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 (65) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/browser/client-workflows.mjs +5 -2
  3. package/browser/coordinated-entry-event.d.mts +81 -0
  4. package/browser/coordinated-entry-event.mjs +175 -0
  5. package/browser/current-living-situation.d.mts +4 -1
  6. package/browser/current-living-situation.mjs +12 -121
  7. package/browser/enrollment-workflow.d.mts +10 -2
  8. package/browser/enrollment-workflow.mjs +65 -13
  9. package/browser/index.d.mts +1 -0
  10. package/browser/index.mjs +1 -0
  11. package/browser/shelters.mjs +2 -2
  12. package/browser/subassessment-row-recorder.mjs +169 -0
  13. package/dist/cjs/encoding.d.ts +5 -0
  14. package/dist/cjs/encoding.d.ts.map +1 -1
  15. package/dist/cjs/encoding.js +12 -1
  16. package/dist/cjs/encoding.js.map +1 -1
  17. package/dist/cjs/errors.d.ts +4 -1
  18. package/dist/cjs/errors.d.ts.map +1 -1
  19. package/dist/cjs/errors.js +9 -1
  20. package/dist/cjs/errors.js.map +1 -1
  21. package/dist/cjs/runtime.d.ts +5 -1
  22. package/dist/cjs/runtime.d.ts.map +1 -1
  23. package/dist/cjs/runtime.js +1 -1
  24. package/dist/cjs/runtime.js.map +1 -1
  25. package/dist/cjs/subassessments.d.ts.map +1 -1
  26. package/dist/cjs/subassessments.js +8 -15
  27. package/dist/cjs/subassessments.js.map +1 -1
  28. package/dist/cjs/transport.d.ts.map +1 -1
  29. package/dist/cjs/transport.js +15 -3
  30. package/dist/cjs/transport.js.map +1 -1
  31. package/dist/esm/encoding.d.ts +5 -0
  32. package/dist/esm/encoding.d.ts.map +1 -1
  33. package/dist/esm/encoding.js +11 -1
  34. package/dist/esm/encoding.js.map +1 -1
  35. package/dist/esm/errors.d.ts +4 -1
  36. package/dist/esm/errors.d.ts.map +1 -1
  37. package/dist/esm/errors.js +9 -1
  38. package/dist/esm/errors.js.map +1 -1
  39. package/dist/esm/runtime.d.ts +5 -1
  40. package/dist/esm/runtime.d.ts.map +1 -1
  41. package/dist/esm/runtime.js +1 -1
  42. package/dist/esm/runtime.js.map +1 -1
  43. package/dist/esm/subassessments.d.ts.map +1 -1
  44. package/dist/esm/subassessments.js +8 -15
  45. package/dist/esm/subassessments.js.map +1 -1
  46. package/dist/esm/transport.d.ts.map +1 -1
  47. package/dist/esm/transport.js +15 -3
  48. package/dist/esm/transport.js.map +1 -1
  49. package/docs/README.md +1 -0
  50. package/docs/assessments.md +22 -3
  51. package/docs/capabilities.md +3 -1
  52. package/docs/configuration.md +1 -0
  53. package/docs/coordinated-entry-events.md +88 -0
  54. package/docs/current-living-situation.md +3 -1
  55. package/docs/dates-and-answers.md +3 -1
  56. package/docs/enrollments.md +21 -0
  57. package/docs/reference.md +6 -2
  58. package/docs/troubleshooting.md +25 -0
  59. package/docs/vendor-issues/README.md +2 -0
  60. package/docs/vendor-issues/date-semantics.md +2 -0
  61. package/docs/vendor-issues/yes-no-answers.md +43 -0
  62. package/examples/README.md +2 -0
  63. package/examples/coordinated-entry-events.mts +37 -0
  64. package/examples/current-living-situation.mts +4 -2
  65. package/package.json +1 -1
@@ -2,7 +2,7 @@ import {apiScope} from './connection.mjs';
2
2
  import {validateBirthDate} from './date-of-birth.mjs';
3
3
  import {expectedExitDetails} from './exit-details.mjs';
4
4
  import {createHash} from 'node:crypto';
5
- import {readProgramCatalog,encodeAnswers,encodeNewSubassessmentRow,toItems} from '../dist/esm/index.js';
5
+ import {readProgramCatalog,encodeAnswers,encodeNewSubassessmentRow,toItems,parseRestInstant} from '../dist/esm/index.js';
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;
@@ -10,13 +10,13 @@ const entryExitTypes=['HUD','Basic','Standard','RHY','VA','Basic Center Program
10
10
  const canonical=v=>JSON.stringify(v,(_,x)=>object(x)?Object.fromEntries(Object.keys(x).sort().map(k=>[k,x[k]])):x);
11
11
  const hash=v=>createHash('sha256').update(canonical(v)).digest('hex');
12
12
  const fail=message=>{throw new EnrollmentWorkflowError(message);};
13
- 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;}}
14
14
  function date(value){
15
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');
16
16
  return new Date(value).toISOString();
17
17
  }
18
18
  // REST scalar/enrollment timestamps are emitted as UTC without a suffix.
19
- const restDate=value=>typeof value==='string'?Date.parse(value.replace(' ','T').replace(/([+-]\d\d)$/,'$1:00')+(/(?:Z|[+-]\d\d(?::?\d\d)?)$/.test(value)?'':'Z')):NaN;
19
+ const restDate=parseRestInstant;
20
20
  function key(value){if(typeof value!=='string'||!value.trim()||value.length>200)fail('stable key required');return value;}
21
21
  function fields(value,allowed){if(!object(value)||Object.keys(value).some(k=>!allowed.includes(k)))fail('unknown or invalid input fields');}
22
22
  function sameAnswer(expected,actual){
@@ -26,11 +26,13 @@ function sameAnswer(expected,actual){
26
26
  return String(actual)===expected.answer_value;
27
27
  }
28
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;}
29
- function compile(definition,input){
29
+ function compile(definition,input,{yesNoWrites,writeLabel}={}){
30
30
  fields(input,['answers','subassessments','endDates','notApplicable','carryForward']);
31
31
  const answers=input.answers??{},excluded=input.notApplicable??{},carry=input.carryForward??[],rows=input.subassessments??[],ends=input.endDates??[];
32
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');
33
- 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));
34
36
  for(const [field,reason]of Object.entries(excluded))if(!index.has(field)||typeof reason!=='string'||!reason.trim())fail('not-applicable explanation required');
35
37
  for(const field of carry)if(typeof field!=='string'||!index.has(field))fail('unknown carry-forward field');
36
38
  const keys=new Set(),semantics=new Set();
@@ -58,7 +60,23 @@ function compile(definition,input){
58
60
  const birthDate=scalar.find(q=>q.virt_field_name==='SVPPROFDOB');
59
61
  if(birthDate){if(birthDate.question_type!=='date')fail('DOB question type changed');validateBirthDate(birthDate.answer_value);}
60
62
  for(const q of scalar)if(q.virt_field_name!=='SVPPROFDOB'&&q.question_type==='date'&&q.answer_value)date(q.answer_value);
61
- 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};
62
80
  }
63
81
  async function enrollments(api,clientId){
64
82
  const all=[],ids=new Set();let count;
@@ -72,9 +90,10 @@ async function enrollments(api,clientId){
72
90
 
73
91
  /** Explicit orchestration for an existing client. All writers must share the durable store.
74
92
  * No automatic retries of uncertain REST writes. This is not a cross-system transaction. */
75
- export function createEnrollmentWorkflow({api,discovery,helpers,store,enrollmentSession,exitUi,memberEntryDatePort,birthDates,assessmentLocations={}}){
93
+ export function createEnrollmentWorkflow({api,discovery,helpers,store,enrollmentSession,exitUi,memberEntryDatePort,birthDates,assessmentLocations={},yesNoWrites='mobile',writeLabel}){
76
94
  const tenantScope=apiScope(api, discovery, helpers, enrollmentSession, exitUi);
77
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');
78
97
  if(!object(assessmentLocations))fail('assessment locations must be a map by Entry/Exit type');
79
98
  for(const [type,locations]of Object.entries(assessmentLocations)){
80
99
  if(!entryExitTypes.includes(type))fail('unsupported Entry/Exit type in assessment locations');
@@ -112,6 +131,8 @@ export function createEnrollmentWorkflow({api,discovery,helpers,store,enrollment
112
131
  if(records.some(r=>r.stage!=='verified'&&r.key!==input.key))fail('another unfinished workflow must be reconciled first');
113
132
  const matches=records.filter(r=>r.key===input.key);if(matches.length>1)fail('duplicate journal keys');let record=matches[0];
114
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');
115
136
  const entry=kind!=='entry'?records.find(r=>r.key===input.enrollmentKey&&r.kind==='entry'&&r.stage==='verified'):null;
116
137
  if(kind!=='entry'&&!entry)fail('verified enrollment workflow required');
117
138
  const context=kind==='entry'?input:kind==='addition'?{...entry.context,entryDate:input.entryDate}:entry.context;
@@ -122,7 +143,7 @@ export function createEnrollmentWorkflow({api,discovery,helpers,store,enrollment
122
143
  const definition=discovered[kind].definition,code=definition.assessmentCode,at=kind==='entry'?input.entryDate:(input.exitDate??input.reviewDate);
123
144
  const exitSchemaHash=kind==='exit'?hash({assessment:discovered.exit.assessment,questions:definition.questions}):undefined;
124
145
  if(record&&kind==='exit'&&record.exitSchemaHash!==exitSchemaHash)fail('exit assessment configuration changed');
125
- const plan=compile(definition,input.assessment);
146
+ const plan=compile(definition,input.assessment,{yesNoWrites,writeLabel});
126
147
  const carriesBirthDate=plan.carry.includes('SVPPROFDOB'),hasBirthDate=plan.dateOfBirth!==undefined||carriesBirthDate;
127
148
  let carriedBirthDate;
128
149
  if(hasBirthDate){
@@ -151,10 +172,28 @@ export function createEnrollmentWorkflow({api,discovery,helpers,store,enrollment
151
172
  if(proposedHead)checkHeadAnswer(proposedHead.answer_value);
152
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');
153
174
  const read=async()=>questions(await api.assessments.get({path:{clientId:input.clientId,assessmentCode:code},query:{dateEffective:at}}));
154
- 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()}}));
155
181
  const initial=await read();
156
182
  if(expectedHead===input.clientId&&headQuestion)checkHeadAnswer(proposedHead?.answer_value??initial.find(q=>q.virt_field_name==='HUD_RELATIONTOHOH')?.answer_value);
157
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
+ });
158
197
  // Validate closure targets before creating enrollment/review or submitting any answers.
159
198
  const current=await readCurrent();
160
199
  const currentHead=current.find(q=>q.virt_field_name==='HUD_RELATIONTOHOH');
@@ -170,15 +209,22 @@ export function createEnrollmentWorkflow({api,discovery,helpers,store,enrollment
170
209
  }
171
210
  if(internal.preflight)return {assessmentCode:code,...(internal.pinSchema?{schemaHash}:{}),...(exitSchemaHash?{exitSchemaHash}:{})};
172
211
  const wasVerified=record?.stage==='verified';
173
- 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);}
174
213
  const persist=()=>store.write(scope(input.clientId),structuredClone(records));await persist();
175
214
  const step=async(name,inspect,submit)=>{
176
215
  let result=await inspect();const previous=record.steps[name];
177
216
  if(result.found){record.steps[name]={state:'verified',value:result.value};await persist();return result.value;}
178
- 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});
179
218
  record.steps[name]={state:'attempted'};await persist();
180
- try{await submit(result);}catch{/* A write response alone never establishes success or absence. */}
181
- 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})});
182
228
  record.steps[name]={state:'verified',value:result.value};await persist();return result.value;
183
229
  };
184
230
  const findEnrollment=async()=>{
@@ -226,6 +272,12 @@ export function createEnrollmentWorkflow({api,discovery,helpers,store,enrollment
226
272
  if(plan.scalar.length)await step('scalars',async()=>{
227
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)};
228
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
+ }
229
281
  // Group rows into one creation request. Partial success blocks resubmission until all rows reconcile.
230
282
  if(plan.rows.length){const rowIds=await step('rows',async()=>{
231
283
  const source=await readCurrent(),matched=Object.create(null),missing=[],matchedIds=new Set();
@@ -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';
@@ -1,6 +1,6 @@
1
1
  import {apiScope} from './connection.mjs';
2
2
  import { createHash } from 'node:crypto';
3
- import { toItems } from '../dist/esm/index.js';
3
+ import { toItems, semanticHash } from '../dist/esm/index.js';
4
4
  const hash = v => createHash('sha256').update(JSON.stringify(v)).digest('hex');
5
5
  const id = v => {
6
6
  if (!Number.isSafeInteger(v) || v < 1)
@@ -75,7 +75,7 @@ export function createShelters({ api, ui, store, enrollmentHelpers }) {
75
75
  fail('stay-identity-changed');
76
76
  return { serviceId: r.serviceId, clientId: r.clientId, bedId: r.bedId, unitListId: r.unitListId };
77
77
  }).sort((a, b) => a.serviceId - b.serviceId);
78
- if (record.identities && JSON.stringify(normalized) !== JSON.stringify(record.identities))
78
+ if (record.identities && await semanticHash(normalized) !== await semanticHash(record.identities))
79
79
  fail('stay-identity-changed');
80
80
  record.identities = normalized;
81
81
  };
@@ -0,0 +1,169 @@
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, enrollmentSession, fail, kind, noun, dateKey, parentField, normalize, compile, write}) {
56
+ if (enrollmentSession !== undefined && typeof enrollmentSession?.readEnrollment !== 'function') fail('invalid-enrollment-session');
57
+ const tenantScope = apiScope(api, enrollmentSession);
58
+ const {instant, valueOf} = rowHelpers(fail);
59
+ const checkDates = (input, entryDate, exitDate) => {
60
+ if (input[dateKey] < entryDate || exitDate && input[dateKey] > instant(exitDate, true)) fail(`${noun}-outside-enrollment`);
61
+ };
62
+ // REST enrollment reads fail for grouped enrollments (42703), so households need the GWT reader.
63
+ // It reads only active enrollments and throws when the target is missing.
64
+ async function readEnrollment(input) {
65
+ if (enrollmentSession) {
66
+ const target = {clientId: input.clientId, enrollmentId: input.enrollmentId}, snapshot = await enrollmentSession.readEnrollment(target);
67
+ if (snapshot?.source !== 'gwt-rpc' || snapshot.tenant !== api.tenant || snapshot.clientId !== target.clientId || snapshot.enrollmentId !== target.enrollmentId || !positive(snapshot.providerId) || !Array.isArray(snapshot.exits)) fail('enrollment-unavailable');
68
+ const exits = snapshot.exits.filter(e => e?.clientId === target.clientId && e.enrollmentId === target.enrollmentId);
69
+ if (exits.length !== 1 || exits[0].projectExitDate !== null && typeof exits[0].projectExitDate !== 'string') fail('enrollment-unavailable');
70
+ const entryDate = instant(snapshot.projectStartDate, true);
71
+ checkDates(input, entryDate, exits[0].projectExitDate);
72
+ return {entryDate, providerId: snapshot.providerId};
73
+ }
74
+ const rows = [], ids = new Set(); let total;
75
+ for (let page = 1; page <= 100; page++) {
76
+ const response = await api.enrollments.listForClient({path: {clientId: input.clientId}, query: {perPage: 100, startPage: page, total_count: true}});
77
+ if (!Number.isSafeInteger(response?.count) || response.count < 0 || !Array.isArray(response.values) || total !== undefined && total !== response.count) fail('enrollment-count-unavailable');
78
+ total = response.count;
79
+ for (const row of response.values) {
80
+ if (!object(row) || !positive(row.enrollmentId) || row.clientId !== input.clientId || ids.has(row.enrollmentId)) fail('invalid-enrollment-read');
81
+ ids.add(row.enrollmentId); rows.push(row);
82
+ }
83
+ if (rows.length === total) {
84
+ const row = rows.find(row => row.enrollmentId === input.enrollmentId);
85
+ if (!row || row.isActive !== true || !positive(row.enrollmentProvider)) fail('enrollment-unavailable');
86
+ const entryDate = instant(row.projectStartDate, true);
87
+ checkDates(input, entryDate, row.projectExitDate);
88
+ return {entryDate, providerId: row.enrollmentProvider};
89
+ }
90
+ if (rows.length > total || !response.values.length) fail('incomplete-enrollment-read');
91
+ }
92
+ fail('enrollment-page-limit');
93
+ }
94
+ async function readRows(input, instant = input[dateKey]) {
95
+ // An undated read omits rows dated after the server's current time, and a read at exactly
96
+ // a row's instant omits its yes/no answers. Read one second after the later of the two.
97
+ const at = new Date(Math.max(Date.now(), Date.parse(instant)) + 1000).toISOString().slice(0, 19) + 'Z';
98
+ const response = await api.assessments.get({path: {clientId: input.clientId, assessmentCode: input.assessmentCode}, query: {dateEffective: at}});
99
+ const questions = toItems(response);
100
+ if (!Array.isArray(questions) || object(response) && 'count' in response && response.count !== questions.length) fail('incomplete-assessment-read');
101
+ const parents = questions.filter(q => q?.virt_field_name === parentField && q.question_type === 'sub_assessment');
102
+ if (parents.length !== 1 || !Array.isArray(parents[0].sub_assessment_answer_list)) fail('rows-unavailable');
103
+ const rows = parents[0].sub_assessment_answer_list, ids = new Set();
104
+ if (rows.length > 10000) fail('row-limit');
105
+ return rows.map(row => {
106
+ if (!positive(row.recordset_id) || ids.has(row.recordset_id) || !Array.isArray(row.sub_assessment_list)) fail('ambiguous-row-read');
107
+ ids.add(row.recordset_id);
108
+ const fields = {};
109
+ for (const q of row.sub_assessment_list) {
110
+ if (['label', 'blank'].includes(q.question_type)) continue;
111
+ if (!nonempty(q.virt_field_name) || Object.hasOwn(fields, q.virt_field_name) || typeof q.question_type !== 'string') fail('ambiguous-answer-read');
112
+ Object.defineProperty(fields, q.virt_field_name, {value: {type: q.question_type, value: valueOf(q)}, enumerable: true});
113
+ }
114
+ return {id: row.recordset_id, fields};
115
+ });
116
+ }
117
+ 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));
118
+ // `empty` fields must be empty only on the new row; a later end date does not undo the event.
119
+ const matches = (row, compiled) => compiled.empty.every(field => (row.fields[field]?.value ?? null) === null) && answered(row, compiled);
120
+
121
+ const scopeOf = clientId => JSON.stringify([tenantScope, kind, clientId]);
122
+ return {readRows, scopeOf, async record(raw) {
123
+ const input = normalize(raw), scope = scopeOf(input.clientId);
124
+ return store.withLock(scope, async () => {
125
+ const entries = await store.read(scope);
126
+ checkJournal(entries, fail);
127
+ let entry = entries.find(e => e.key === input.key);
128
+ // A pending in-place update of an earlier row is unfinished work too.
129
+ if (entries.some(e => (e.stage === 'attempted' || e.update?.stage === 'attempted') && e.key !== input.key)) fail(`unfinished-${noun}`);
130
+ const enrollment = await readEnrollment(input);
131
+ const definition = await api.assessments.getDefinition({clientId: input.clientId, assessmentCode: input.assessmentCode, effectiveDate: input[dateKey], refresh: true});
132
+ const compiled = compile(definition, input, enrollment);
133
+ const intentHash = await semanticHash({input, enrollment, ...compiled.intent});
134
+ if (entry && entry.intentHash !== intentHash) fail('intent-or-schema-changed');
135
+ let rows = await readRows(input);
136
+ 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});
137
+ if (entry?.stage === 'verified') {
138
+ const row = rows.find(row => row.id === entry.recordsetId);
139
+ if (!row || !answered(row, compiled)) fail(`verified-${noun}-changed`);
140
+ return result(row, 'already-present');
141
+ }
142
+ const reconcile = async () => {
143
+ for (const before of entry.baseline) {
144
+ const after = rows.find(row => row.id === before.id);
145
+ if (!after || await semanticHash(after) !== await semanticHash(before)) fail(`existing-${noun}-changed`);
146
+ }
147
+ const added = rows.filter(row => !entry.baseline.some(before => before.id === row.id));
148
+ if (added.length !== 1 || !matches(added[0], compiled)) fail(`uncertain-${noun}-save`);
149
+ return added[0];
150
+ };
151
+ if (entry) {
152
+ const row = await reconcile();
153
+ entry.stage = 'verified'; entry.recordsetId = row.id;
154
+ await store.write(scope, structuredClone(entries));
155
+ return result(row, 'reconciled');
156
+ }
157
+ entry = {version: 1, key: input.key, intentHash, stage: 'attempted', baseline: rows};
158
+ entries.push(entry);
159
+ await store.write(scope, structuredClone(entries));
160
+ let failed = false;
161
+ try { await write(input, compiled.body); } catch { failed = true; }
162
+ rows = await readRows(input);
163
+ const row = await reconcile();
164
+ entry.stage = 'verified'; entry.recordsetId = row.id;
165
+ await store.write(scope, structuredClone(entries));
166
+ return result(row, failed ? 'reconciled' : 'verified');
167
+ });
168
+ }};
169
+ }
@@ -29,4 +29,9 @@ export declare function encodeAnswers(questions: readonly QuestionDefinition[],
29
29
  export declare function encodeNewSubassessmentRow(parent: QuestionDefinition, answers: Answers, options: EncodingOptions): {
30
30
  subAssessments: EncodedQuestion[];
31
31
  };
32
+ /** Milliseconds since epoch for a REST read timestamp, or NaN. Reads emit forms such as
33
+ * `2026-11-20 10:00:00-07` (space separator, hour-only offset) whose `Date.parse` result is
34
+ * engine-defined: NaN on some runtimes, and host-local time when the offset is missing.
35
+ * A value without an offset is UTC. */
36
+ export declare function parseRestInstant(value: unknown): number;
32
37
  //# sourceMappingURL=encoding.d.ts.map
@@ -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;AAED;;;uCAGuC;AACvC,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGvD"}
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.encodeAnswers = encodeAnswers;
4
4
  exports.encodeNewSubassessmentRow = encodeNewSubassessmentRow;
5
+ exports.parseRestInstant = parseRestInstant;
5
6
  const metadata_js_1 = require("./metadata.js");
6
7
  const errors_js_1 = require("./errors.js");
7
8
  const race_js_1 = require("./race.js");
@@ -110,12 +111,22 @@ function encodeNewSubassessmentRow(parent, answers, options) {
110
111
  const encoded = encodeAnswers(parent.children, answers, options);
111
112
  const included = new Set(encoded.map(q => q.virt_field_name));
112
113
  for (const question of parent.children) {
114
+ // An unanswered provider child is omitted; answered ones are still refused for mobile in encodeValue.
113
115
  if (!question.fieldName || included.has(question.fieldName) || ["label", "blank", "yes_no"].includes(question.questionType))
114
116
  continue;
115
117
  if (question.questionType === "provider" && options.endpoint === "mobile")
116
- throw new errors_js_1.ValidationError("mobile provider answers are unsupported", [question.fieldName]);
118
+ continue;
117
119
  encoded.push({ question_id: question.questionId, question_type: question.questionType, virt_field_name: question.fieldName, answer_value: "" });
118
120
  }
119
121
  return { subAssessments: encoded };
120
122
  }
123
+ /** Milliseconds since epoch for a REST read timestamp, or NaN. Reads emit forms such as
124
+ * `2026-11-20 10:00:00-07` (space separator, hour-only offset) whose `Date.parse` result is
125
+ * engine-defined: NaN on some runtimes, and host-local time when the offset is missing.
126
+ * A value without an offset is UTC. */
127
+ function parseRestInstant(value) {
128
+ if (typeof value !== "string")
129
+ return NaN;
130
+ return Date.parse(value.replace(" ", "T").replace(/([+-]\d\d)$/, "$1:00") + (/(?:Z|[+-]\d\d(?::?\d\d)?)$/.test(value) ? "" : "Z"));
131
+ }
121
132
  //# sourceMappingURL=encoding.js.map
@@ -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":"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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 // 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\n/** Milliseconds since epoch for a REST read timestamp, or NaN. Reads emit forms such as\n * `2026-11-20 10:00:00-07` (space separator, hour-only offset) whose `Date.parse` result is\n * engine-defined: NaN on some runtimes, and host-local time when the offset is missing.\n * A value without an offset is UTC. */\nexport function parseRestInstant(value: unknown): number {\n if (typeof value !== \"string\") return NaN;\n return Date.parse(value.replace(\" \", \"T\").replace(/([+-]\\d\\d)$/, \"$1:00\") + (/(?:Z|[+-]\\d\\d(?::?\\d\\d)?)$/.test(value) ? \"\" : \"Z\"));\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":"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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 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":"AAAA,2GAA2G;AAC3G,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC3D;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,CAAC,OAAO,EAAE;QAAE,YAAY,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,WAAW,CAAA;KAAE,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CAAE,GACrH;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErB,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,cAAc,CAAC;IAErB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IACvE,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,mFAAmF;IACnF,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IACtC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC;;oFAEgF;IAChF,qCAAqC,CAAC,EAAE,MAAM,CAAC;IAC/C,uGAAuG;IACvG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,4FAA4F;IAC5F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,GAAG,UAAU,CAAC;IACrE,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AACD,MAAM,WAAW,WAAW;IAC1B,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IACzC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IAC1C,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IAC3C,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IAC1C,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;CAC3C;AACD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,QAAQ,GAAG,MAAM,CAAC;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,oGAAoG;IACpG,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,uFAAuF;AACvF,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,CAAC,EAAE,CAAC;CACb;AAED,gGAAgG;AAChG,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,eAAe,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;AAExF,wBAAgB,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAWxD;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,YAAY,GAAG,UAAU,GAAG,YAAY,GAAG,iBAAiB,GAAG,MAAM,GAAG,WAAW,GAAG,UAAU,GAAG,kBAAkB,GAAG,kBAAkB,GAAG,wBAAwB,CAAC;AAE3O,0FAA0F;AAC1F,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;gBAEtB,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAO;CAS7H"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/runtime.ts"],"names":[],"mappings":"AAAA,2GAA2G;AAC3G,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC3D;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,CAAC,OAAO,EAAE;QAAE,YAAY,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,WAAW,CAAA;KAAE,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CAAE,GACrH;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErB,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,cAAc,CAAC;IAErB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IACvE,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,mFAAmF;IACnF,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IACtC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC;;oFAEgF;IAChF,qCAAqC,CAAC,EAAE,MAAM,CAAC;IAC/C;;sEAEkE;IAClE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,uGAAuG;IACvG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,4FAA4F;IAC5F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,GAAG,UAAU,CAAC;IACrE,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AACD,MAAM,WAAW,WAAW;IAC1B,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IACzC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IAC1C,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IAC3C,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IAC1C,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;CAC3C;AACD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,QAAQ,GAAG,MAAM,CAAC;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,oGAAoG;IACpG,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,uFAAuF;AACvF,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,CAAC,EAAE,CAAC;CACb;AAED,gGAAgG;AAChG,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,eAAe,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;AAExF,wBAAgB,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAWxD;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,YAAY,GAAG,UAAU,GAAG,YAAY,GAAG,iBAAiB,GAAG,MAAM,GAAG,WAAW,GAAG,UAAU,GAAG,kBAAkB,GAAG,kBAAkB,GAAG,wBAAwB,CAAC;AAE3O,0JAA0J;AAC1J,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;gBAEtB,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAO;CAS7H"}