@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 +4 -0
- package/README.md +0 -17
- package/browser/enrollment-workflow.d.mts +7 -1
- package/browser/enrollment-workflow.mjs +16 -7
- package/docs/enrollments.md +20 -1
- package/docs/reference.md +1 -1
- package/docs/using-the-sdk.md +2 -3
- package/examples/enrollment-workflow.mts +5 -2
- package/package.json +1 -1
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:
|
|
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)||!
|
|
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
|
|
83
|
-
for(const
|
|
84
|
-
const matches=configuration.defaults.filter(d=>d.location===
|
|
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)||!
|
|
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)||!
|
|
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.
|
package/docs/enrollments.md
CHANGED
|
@@ -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
|
|
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 |
|
package/docs/using-the-sdk.md
CHANGED
|
@@ -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.
|
|
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
|
|
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.
|
|
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:
|
|
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