@choiceapps/communityservices-sdk 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1 - 2026-11-02
4
+
5
+ - Accept Basic, Standard, RHY, Basic Center Program Entry/Exit, PATH, Quick Call, and Transitional Living Program Entry/Exit in enrollment workflows alongside HUD and VA. Added per-type assessment display location mappings, with current provider discovery and existing journal verification.
6
+
3
7
  ## 0.1.0 - 2026-10-28
4
8
 
5
9
  Initial development release of `@choiceapps/communityservices-sdk`.
package/README.md CHANGED
@@ -20,23 +20,6 @@ npm install @choiceapps/communityservices-sdk
20
20
 
21
21
  The public package uses the default npm registry. No registry token is needed to install it.
22
22
 
23
- ### Install from GitLab
24
-
25
- Add this to your application's `.npmrc`:
26
-
27
- ```ini
28
- @choiceapps:registry=https://gitlab.home.nmbgeek.com/api/v4/projects/48/packages/npm/
29
- //gitlab.home.nmbgeek.com/api/v4/projects/48/packages/npm/:_authToken=${GITLAB_NPM_TOKEN}
30
- ```
31
-
32
- Supply a GitLab token with package-read access through `GITLAB_NPM_TOKEN`, then install:
33
-
34
- ```sh
35
- npm install @choiceapps/communityservices-sdk
36
- ```
37
-
38
- Keep token values out of version control. The GitLab token installs the package; your REST and browser credentials authenticate to Community Services separately.
39
-
40
23
  ## Run an offline example
41
24
 
42
25
  ```sh
@@ -30,10 +30,13 @@ export interface EnrollmentAssessmentPlan {
30
30
  notApplicable?:Record<string,string>;
31
31
  carryForward?:string[];
32
32
  }
33
+ export type EntryExitType = 'HUD'|'Basic'|'Standard'|'RHY'|'VA'|'Basic Center Program Entry/Exit'|'PATH'|'Quick Call'|'Transitional Living Program Entry/Exit';
34
+ /** Exact location identifiers returned by getProviderAssessments, not assessment codes. */
35
+ export interface EnrollmentAssessmentLocations {entry:string;interim:string;exit?:string;}
33
36
  export interface EnrollmentDiscoveryInput {
34
37
  clientId:number;
35
38
  providerId:number;
36
- type:'HUD'|'VA';
39
+ type:EntryExitType;
37
40
  /** Timezone-qualified timestamp with whole-second precision. */
38
41
  entryDate:string;
39
42
  }
@@ -152,6 +155,9 @@ export interface HouseholdWorkflowStatus {
152
155
  members:Array<{clientId:number;key:string}&Partial<Omit<InterimWorkflowResult&ExitWorkflowResult,'clientId'|'key'>>>;
153
156
  }
154
157
  export interface EnrollmentWorkflowOptions {
158
+ /** HUD/VA have built-in mappings. Other types require locations from current provider discovery.
159
+ * A mapping replaces all built-in locations for that type. Retain it across retries. */
160
+ assessmentLocations?:Partial<Record<EntryExitType,EnrollmentAssessmentLocations>>;
155
161
  api:Pick<WellSkyClient,'tenant'|'clients'|'providers'|'enrollments'|'assessments'|'assessmentsMobile'|'subassessments'>&Partial<Pick<WellSkyClient,'households'>>;
156
162
  discovery:Pick<ProgramDiscovery,'getProviderAssessments'>;
157
163
  helpers:Pick<GwtEnrollmentHelpers,'createInterimUpdate'>&Partial<Pick<GwtEnrollmentHelpers,'addHouseholdMembers'>>;
@@ -6,6 +6,7 @@ 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);};
@@ -71,17 +72,25 @@ async function enrollments(api,clientId){
71
72
 
72
73
  /** Explicit orchestration for an existing client. All writers must share the durable store.
73
74
  * 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}){
75
+ export function createEnrollmentWorkflow({api,discovery,helpers,store,enrollmentSession,exitUi,memberEntryDatePort,birthDates,assessmentLocations={}}){
75
76
  const tenantScope=apiScope(api, discovery, helpers, enrollmentSession, exitUi);
76
77
  if(typeof discovery?.getProviderAssessments!=='function'||typeof helpers?.createInterimUpdate!=='function'||!store)fail('REST API, discovery, enrollment helpers and durable store required');
78
+ if(!object(assessmentLocations))fail('assessment locations must be a map by Entry/Exit type');
79
+ for(const [type,locations]of Object.entries(assessmentLocations)){
80
+ if(!entryExitTypes.includes(type))fail('unsupported Entry/Exit type in assessment locations');
81
+ fields(locations,['entry','interim','exit']);
82
+ 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');
83
+ }
84
+ const locationsByType={HUD:{entry:'showHudEntry',interim:'showHudInterim',exit:'showHudExit'},VA:{entry:'showVaEntry',interim:'showVaInterim',exit:'showVaExit'},...structuredClone(assessmentLocations)};
77
85
  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);
86
+ 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);
87
+ const locations=locationsByType[input.type];if(!locations||includeExit&&!locations.exit)fail(`assessment locations required for ${input.type}${includeExit?' including exit':''}`);
79
88
  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
89
  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
90
  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');
91
+ const resolved={};
92
+ for(const phase of ['entry','interim',...(includeExit?['exit']:[])]){
93
+ 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
94
  const assessment=matches[0].assessment;const definition=await api.assessments.getDefinition({clientId:input.clientId,assessmentCode:assessment.code,effectiveDate:at,refresh:true});
86
95
  if(definition.tenant!==api.tenant||definition.assessmentCode!==assessment.code||definition.unresolvedPicklists.length)fail('assessment metadata incomplete');
87
96
  resolved[phase]={assessment,definition};
@@ -93,7 +102,7 @@ export function createEnrollmentWorkflow({api,discovery,helpers,store,enrollment
93
102
  const run=async(kind,raw,internal={})=>{
94
103
  fields(raw,kind==='entry'?['key','clientId','providerId','type','entryDate','assessment']:['key','enrollmentKey','clientId',kind==='exit'?'exitDate':'reviewDate','assessment']);
95
104
  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);}
105
+ 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
106
  if(!object(input.assessment))fail('assessment plan required');
98
107
  const locked=internal.locked?async(_scope,work)=>work():store.withLock.bind(store);
99
108
  return locked(scope(input.clientId),async()=>{
@@ -263,7 +272,7 @@ export function createEnrollmentWorkflow({api,discovery,helpers,store,enrollment
263
272
  for(const member of input.members){fields(member,['clientId','assessment']);if(!positive(member.clientId)||!object(member.assessment))fail('member client and assessment required');}
264
273
  input.members.sort((a,b)=>a.clientId-b.clientId);const ids=input.members.map(m=>m.clientId);
265
274
  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);}
275
+ 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
276
  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
277
  // Read the cohort under its journal lock, then acquire all client locks in order.
269
278
  // Recheck under both locks so concurrent membership growth cannot escape protection.
@@ -27,7 +27,26 @@ REST form metadata includes question paths, mandatory flags, current option IDs/
27
27
 
28
28
  ## Enrollment workflow from program discovery
29
29
 
30
- `createEnrollmentWorkflow` composes the provider catalog, current program assessment defaults, question/picklist metadata, REST enrollment and answer writes, and the durable GWT interim helper. It accepts an **existing client ID** and supports HUD and VA program defaults. It does not create clients or infer program eligibility, answers, conditional applicability, or which verification types a program requires.
30
+ `createEnrollmentWorkflow` composes the provider catalog, current program assessment defaults, question/picklist metadata, REST enrollment and answer writes, and the durable GWT interim helper. It accepts an **existing client ID**. It does not create clients or infer program eligibility, answers, conditional applicability, or which verification types a program requires.
31
+
32
+ The workflow's `EntryExitType` accepts `HUD`, `Basic`, `Standard`, `RHY`, `VA`, `Basic Center Program Entry/Exit`, `PATH`, `Quick Call`, and `Transitional Living Program Entry/Exit`. `type` identifies the Entry/Exit category; `assessmentCode` identifies its assessment instrument. The workflow sends the selected type unchanged to REST. The generated REST methods accept a string; neither contract guarantees that the server permits every type for a given provider.
33
+
34
+ HUD and VA retain built-in provider display locations. For another type, supply `assessmentLocations` when constructing the workflow. First inspect `getProviderAssessments({providerId}).defaults`, including each item's `label`, then map the desired type's entry, interim, and optional exit phases to their exact `location` values. These values are display-control identifiers, not assessment names, IDs, or codes. The workflow resolves the instruments from fresh provider discovery on every operation. It rejects missing, duplicate, unset, or unresolved defaults before record writes and never falls back to another type.
35
+
36
+ ```ts
37
+ const workflow = createEnrollmentWorkflow({
38
+ api, discovery, helpers, store,
39
+ assessmentLocations: {
40
+ PATH: {
41
+ entry: selectedPathEntry.location,
42
+ interim: selectedPathInterim.location,
43
+ exit: selectedPathExit.location,
44
+ },
45
+ },
46
+ });
47
+ ```
48
+
49
+ `assessmentLocations` is a partial map keyed by `EntryExitType`. Each supplied mapping requires nonempty `entry` and `interim` locations; `exit` is required for `discoverExit` and household exits. A supplied HUD or VA mapping replaces that type's built-in mapping. Keep the same configuration across retries and use separate workflow instances when providers need different location mappings. Existing journal checks still reject changed instruments or definitions. Additional types have synthetic workflow coverage, not new live acceptance evidence. The November 2, 2026 report of type rejection was traced to the SDK's former HUD/VA restriction, not a reproduced WellSky server failure.
31
50
 
32
51
  ```ts
33
52
  import {
package/docs/reference.md CHANGED
@@ -36,7 +36,7 @@ Runtime exports: `AttachmentConflictError`, `EnrollmentWorkflowError`, `LoginErr
36
36
  | Profile fields and Sex | [Client profile](../browser/client-profile.d.mts) | `read`, `update` |
37
37
  | Calendar DOB | [Birth dates](../browser/date-of-birth.d.mts) | `read`, `validate`, `verify`, `update` |
38
38
  | Provider assessment defaults | [Discovery](../browser/program-discovery.d.mts) | `listAssessments`, `getProviderAssessments` |
39
- | Enrollment and household lifecycle | [Workflow](../browser/enrollment-workflow.d.mts) | Discovery, entry, member additions, interim, exit, journal status |
39
+ | Enrollment and household lifecycle | [Workflow](../browser/enrollment-workflow.d.mts) | Discovery, entry, member additions, interim, exit, journal status; `EntryExitType` and per-type `assessmentLocations` in [enrollments](enrollments.md) |
40
40
  | Entry-date and exit UI ports | [Member entry](../browser/member-entry-date.d.mts), [exit](../browser/household-exit.d.mts) | Fresh reads and targeted completion |
41
41
  | Services and recovery diagnostics | [Service recording](../browser/service-recording.d.mts) | `record`, `recordBulk`, Funding Sources, `RecoveryDiagnostic` |
42
42
  | SSVF conditional rules | [SSVF types](../browser/ssvf-rules.d.mts) | [Field rules](../SSVF.md) and dated option snapshots |
@@ -6,7 +6,7 @@ This guide takes an application from installation to its first REST read and bro
6
6
 
7
7
  ## 1. Install the package
8
8
 
9
- Install from the public npm registry. No registry token is required. For the private registry, follow [Install from GitLab](../README.md#install-from-gitlab).
9
+ Install from the public npm registry. No registry token is required.
10
10
 
11
11
  ```sh
12
12
  npm install @choiceapps/communityservices-sdk
@@ -46,11 +46,10 @@ This runs the real SDK validation and transport through a fake that cannot conta
46
46
 
47
47
  The application supplies the tenant. Every tenant uses the same SDK operations; no environment switch or separate helper names are needed. Tenant IDs, provider IDs, client IDs, and picklist choices must come from the deployment you are connecting to.
48
48
 
49
- REST and browser credentials are separate. A registry token is only needed if you choose the private GitLab registry:
49
+ REST and browser credentials are separate:
50
50
 
51
51
  | Credential | Used for |
52
52
  | --- | --- |
53
- | `GITLAB_NPM_TOKEN` | Downloading the package from GitLab |
54
53
  | REST username/password, token, or token provider | Authenticating `WellSkyClient` API requests |
55
54
  | `HMIS_UI_USER` and `HMIS_UI_PASSWORD` | Signing in to the web application through a browser helper |
56
55
 
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  createEnrollmentWorkflow,
3
+ type EntryExitType,
3
4
  type EnrollmentAssessmentPlan,
4
5
  type EnrollmentWorkflowDiscovery,
5
6
  type EnrollmentWorkflowResult,
@@ -7,13 +8,15 @@ import {
7
8
  } from '@choiceapps/communityservices-sdk/browser';
8
9
 
9
10
  /** Call with a REST client, authenticated discovery/enrollment adapters,
10
- * a birthDates adapter for DOB plans, and the same durable store on every run. This example does not log in or run itself. */
11
+ * a birthDates adapter for DOB plans, and the same durable store on every run.
12
+ * Types other than HUD/VA require dependencies.assessmentLocations from current provider discovery.
13
+ * This example does not log in or run itself. */
11
14
  export async function enrollAndReview(
12
15
  dependencies:EnrollmentWorkflowOptions,
13
16
  input:{
14
17
  clientId:number;
15
18
  providerId:number;
16
- type:'HUD'|'VA';
19
+ type:EntryExitType;
17
20
  /** Stable application record ID, not a new UUID on each retry. */
18
21
  sourceId:string;
19
22
  entryDate:string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@choiceapps/communityservices-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "SDK for WellSky Community Services (HMIS), formerly ServicePoint, Client API V2 and V3",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",