@choiceapps/communityservices-sdk 0.1.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.
- package/AGENTS.md +43 -0
- package/API.md +86 -0
- package/CHANGELOG.md +12 -0
- package/LICENSE +201 -0
- package/NOTICE +11 -0
- package/README.md +100 -0
- package/SSVF.md +131 -0
- package/browser/attachment-conflict.mjs +6 -0
- package/browser/case-plans-ui.mjs +23 -0
- package/browser/case-plans.d.mts +20 -0
- package/browser/case-plans.mjs +89 -0
- package/browser/client-creation.d.mts +16 -0
- package/browser/client-creation.mjs +60 -0
- package/browser/client-profile-ui.mjs +59 -0
- package/browser/client-profile.d.mts +24 -0
- package/browser/client-profile.mjs +94 -0
- package/browser/client-workflows.mjs +135 -0
- package/browser/connection.d.mts +4 -0
- package/browser/connection.mjs +41 -0
- package/browser/current-living-situation.d.mts +44 -0
- package/browser/current-living-situation.mjs +161 -0
- package/browser/date-of-birth-ui.mjs +68 -0
- package/browser/date-of-birth.d.mts +28 -0
- package/browser/date-of-birth.mjs +112 -0
- package/browser/enrollment-workflow.d.mts +174 -0
- package/browser/enrollment-workflow.mjs +518 -0
- package/browser/exit-details.mjs +21 -0
- package/browser/file-store.mjs +32 -0
- package/browser/gwt-mapped.mjs +46 -0
- package/browser/household-exit.d.mts +34 -0
- package/browser/household-exit.mjs +102 -0
- package/browser/households-ui.mjs +20 -0
- package/browser/households.d.mts +22 -0
- package/browser/households.mjs +115 -0
- package/browser/index.d.mts +78 -0
- package/browser/index.mjs +46 -0
- package/browser/member-entry-date.d.mts +30 -0
- package/browser/member-entry-date.mjs +71 -0
- package/browser/need-recording.d.mts +37 -0
- package/browser/need-recording.mjs +68 -0
- package/browser/need-ui.mjs +138 -0
- package/browser/page-lock.mjs +11 -0
- package/browser/password-recovery.mjs +49 -0
- package/browser/playwright-session.mjs +196 -0
- package/browser/program-discovery.d.mts +22 -0
- package/browser/program-discovery.mjs +80 -0
- package/browser/recovery-diagnostics.mjs +9 -0
- package/browser/service-recording.d.mts +45 -0
- package/browser/service-recording.mjs +148 -0
- package/browser/service-ui.mjs +160 -0
- package/browser/session-authentication.mjs +40 -0
- package/browser/shelter-inventory.d.mts +103 -0
- package/browser/shelter-inventory.mjs +273 -0
- package/browser/shelter-ui.mjs +382 -0
- package/browser/shelters.d.mts +139 -0
- package/browser/shelters.mjs +425 -0
- package/browser/ssvf-rules.d.mts +21 -0
- package/browser/ssvf-rules.mjs +145 -0
- package/browser/ui-login.mjs +135 -0
- package/dist/cjs/bulk.d.ts +33 -0
- package/dist/cjs/bulk.d.ts.map +1 -0
- package/dist/cjs/bulk.js +42 -0
- package/dist/cjs/bulk.js.map +1 -0
- package/dist/cjs/control.d.ts +35 -0
- package/dist/cjs/control.d.ts.map +1 -0
- package/dist/cjs/control.js +141 -0
- package/dist/cjs/control.js.map +1 -0
- package/dist/cjs/effective-date.d.ts +3 -0
- package/dist/cjs/effective-date.d.ts.map +1 -0
- package/dist/cjs/effective-date.js +27 -0
- package/dist/cjs/effective-date.js.map +1 -0
- package/dist/cjs/encoding.d.ts +32 -0
- package/dist/cjs/encoding.d.ts.map +1 -0
- package/dist/cjs/encoding.js +121 -0
- package/dist/cjs/encoding.js.map +1 -0
- package/dist/cjs/enrollment-verifications.d.ts +39 -0
- package/dist/cjs/enrollment-verifications.d.ts.map +1 -0
- package/dist/cjs/enrollment-verifications.js +96 -0
- package/dist/cjs/enrollment-verifications.js.map +1 -0
- package/dist/cjs/errors.d.ts +33 -0
- package/dist/cjs/errors.d.ts.map +1 -0
- package/dist/cjs/errors.js +45 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/generated/v2-models.d.ts +1482 -0
- package/dist/cjs/generated/v2-models.d.ts.map +1 -0
- package/dist/cjs/generated/v2-models.js +3 -0
- package/dist/cjs/generated/v2-models.js.map +1 -0
- package/dist/cjs/generated/v2.d.ts +1149 -0
- package/dist/cjs/generated/v2.d.ts.map +1 -0
- package/dist/cjs/generated/v2.js +252 -0
- package/dist/cjs/generated/v2.js.map +1 -0
- package/dist/cjs/generated/v3-models.d.ts +1420 -0
- package/dist/cjs/generated/v3-models.d.ts.map +1 -0
- package/dist/cjs/generated/v3-models.js +3 -0
- package/dist/cjs/generated/v3-models.js.map +1 -0
- package/dist/cjs/generated/v3.d.ts +1149 -0
- package/dist/cjs/generated/v3.d.ts.map +1 -0
- package/dist/cjs/generated/v3.js +252 -0
- package/dist/cjs/generated/v3.js.map +1 -0
- package/dist/cjs/gwt-enrollments.d.ts +131 -0
- package/dist/cjs/gwt-enrollments.d.ts.map +1 -0
- package/dist/cjs/gwt-enrollments.js +280 -0
- package/dist/cjs/gwt-enrollments.js.map +1 -0
- package/dist/cjs/index.d.ts +3 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +19 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/metadata.d.ts +100 -0
- package/dist/cjs/metadata.d.ts.map +1 -0
- package/dist/cjs/metadata.js +165 -0
- package/dist/cjs/metadata.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/program-discovery.d.ts +28 -0
- package/dist/cjs/program-discovery.d.ts.map +1 -0
- package/dist/cjs/program-discovery.js +58 -0
- package/dist/cjs/program-discovery.js.map +1 -0
- package/dist/cjs/race.d.ts +4 -0
- package/dist/cjs/race.d.ts.map +1 -0
- package/dist/cjs/race.js +40 -0
- package/dist/cjs/race.js.map +1 -0
- package/dist/cjs/runtime.d.ts +108 -0
- package/dist/cjs/runtime.d.ts.map +1 -0
- package/dist/cjs/runtime.js +38 -0
- package/dist/cjs/runtime.js.map +1 -0
- package/dist/cjs/subassessments.d.ts +29 -0
- package/dist/cjs/subassessments.d.ts.map +1 -0
- package/dist/cjs/subassessments.js +107 -0
- package/dist/cjs/subassessments.js.map +1 -0
- package/dist/cjs/testing.d.ts +45 -0
- package/dist/cjs/testing.d.ts.map +1 -0
- package/dist/cjs/testing.js +66 -0
- package/dist/cjs/testing.js.map +1 -0
- package/dist/cjs/transport.d.ts +19 -0
- package/dist/cjs/transport.d.ts.map +1 -0
- package/dist/cjs/transport.js +373 -0
- package/dist/cjs/transport.js.map +1 -0
- package/dist/cjs/v2.d.ts +13 -0
- package/dist/cjs/v2.d.ts.map +1 -0
- package/dist/cjs/v2.js +29 -0
- package/dist/cjs/v2.js.map +1 -0
- package/dist/cjs/v3.d.ts +13 -0
- package/dist/cjs/v3.d.ts.map +1 -0
- package/dist/cjs/v3.js +29 -0
- package/dist/cjs/v3.js.map +1 -0
- package/dist/cjs/validation.d.ts +33 -0
- package/dist/cjs/validation.d.ts.map +1 -0
- package/dist/cjs/validation.js +77 -0
- package/dist/cjs/validation.js.map +1 -0
- package/dist/cjs/verification.d.ts +82 -0
- package/dist/cjs/verification.d.ts.map +1 -0
- package/dist/cjs/verification.js +122 -0
- package/dist/cjs/verification.js.map +1 -0
- package/dist/esm/bulk.d.ts +33 -0
- package/dist/esm/bulk.d.ts.map +1 -0
- package/dist/esm/bulk.js +39 -0
- package/dist/esm/bulk.js.map +1 -0
- package/dist/esm/control.d.ts +35 -0
- package/dist/esm/control.d.ts.map +1 -0
- package/dist/esm/control.js +132 -0
- package/dist/esm/control.js.map +1 -0
- package/dist/esm/effective-date.d.ts +3 -0
- package/dist/esm/effective-date.d.ts.map +1 -0
- package/dist/esm/effective-date.js +24 -0
- package/dist/esm/effective-date.js.map +1 -0
- package/dist/esm/encoding.d.ts +32 -0
- package/dist/esm/encoding.d.ts.map +1 -0
- package/dist/esm/encoding.js +117 -0
- package/dist/esm/encoding.js.map +1 -0
- package/dist/esm/enrollment-verifications.d.ts +39 -0
- package/dist/esm/enrollment-verifications.d.ts.map +1 -0
- package/dist/esm/enrollment-verifications.js +93 -0
- package/dist/esm/enrollment-verifications.js.map +1 -0
- package/dist/esm/errors.d.ts +33 -0
- package/dist/esm/errors.d.ts.map +1 -0
- package/dist/esm/errors.js +33 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/generated/v2-models.d.ts +1482 -0
- package/dist/esm/generated/v2-models.d.ts.map +1 -0
- package/dist/esm/generated/v2-models.js +2 -0
- package/dist/esm/generated/v2-models.js.map +1 -0
- package/dist/esm/generated/v2.d.ts +1149 -0
- package/dist/esm/generated/v2.d.ts.map +1 -0
- package/dist/esm/generated/v2.js +247 -0
- package/dist/esm/generated/v2.js.map +1 -0
- package/dist/esm/generated/v3-models.d.ts +1420 -0
- package/dist/esm/generated/v3-models.d.ts.map +1 -0
- package/dist/esm/generated/v3-models.js +2 -0
- package/dist/esm/generated/v3-models.js.map +1 -0
- package/dist/esm/generated/v3.d.ts +1149 -0
- package/dist/esm/generated/v3.d.ts.map +1 -0
- package/dist/esm/generated/v3.js +247 -0
- package/dist/esm/generated/v3.js.map +1 -0
- package/dist/esm/gwt-enrollments.d.ts +131 -0
- package/dist/esm/gwt-enrollments.d.ts.map +1 -0
- package/dist/esm/gwt-enrollments.js +274 -0
- package/dist/esm/gwt-enrollments.js.map +1 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/metadata.d.ts +100 -0
- package/dist/esm/metadata.d.ts.map +1 -0
- package/dist/esm/metadata.js +157 -0
- package/dist/esm/metadata.js.map +1 -0
- package/dist/esm/program-discovery.d.ts +28 -0
- package/dist/esm/program-discovery.d.ts.map +1 -0
- package/dist/esm/program-discovery.js +55 -0
- package/dist/esm/program-discovery.js.map +1 -0
- package/dist/esm/race.d.ts +4 -0
- package/dist/esm/race.d.ts.map +1 -0
- package/dist/esm/race.js +37 -0
- package/dist/esm/race.js.map +1 -0
- package/dist/esm/runtime.d.ts +108 -0
- package/dist/esm/runtime.d.ts.map +1 -0
- package/dist/esm/runtime.js +33 -0
- package/dist/esm/runtime.js.map +1 -0
- package/dist/esm/subassessments.d.ts +29 -0
- package/dist/esm/subassessments.d.ts.map +1 -0
- package/dist/esm/subassessments.js +103 -0
- package/dist/esm/subassessments.js.map +1 -0
- package/dist/esm/testing.d.ts +45 -0
- package/dist/esm/testing.d.ts.map +1 -0
- package/dist/esm/testing.js +60 -0
- package/dist/esm/testing.js.map +1 -0
- package/dist/esm/transport.d.ts +19 -0
- package/dist/esm/transport.d.ts.map +1 -0
- package/dist/esm/transport.js +369 -0
- package/dist/esm/transport.js.map +1 -0
- package/dist/esm/v2.d.ts +13 -0
- package/dist/esm/v2.d.ts.map +1 -0
- package/dist/esm/v2.js +13 -0
- package/dist/esm/v2.js.map +1 -0
- package/dist/esm/v3.d.ts +13 -0
- package/dist/esm/v3.d.ts.map +1 -0
- package/dist/esm/v3.js +13 -0
- package/dist/esm/v3.js.map +1 -0
- package/dist/esm/validation.d.ts +33 -0
- package/dist/esm/validation.d.ts.map +1 -0
- package/dist/esm/validation.js +72 -0
- package/dist/esm/validation.js.map +1 -0
- package/dist/esm/verification.d.ts +82 -0
- package/dist/esm/verification.d.ts.map +1 -0
- package/dist/esm/verification.js +117 -0
- package/dist/esm/verification.js.map +1 -0
- package/docs/README.md +56 -0
- package/docs/assessments.md +103 -0
- package/docs/browser-session.md +129 -0
- package/docs/capabilities.md +75 -0
- package/docs/configuration.md +81 -0
- package/docs/current-living-situation.md +56 -0
- package/docs/dates-and-answers.md +40 -0
- package/docs/development.md +81 -0
- package/docs/durable-records.md +156 -0
- package/docs/enrollments.md +217 -0
- package/docs/files-and-notes.md +39 -0
- package/docs/getting-started.md +56 -0
- package/docs/independent-needs.md +77 -0
- package/docs/integration-methods.md +60 -0
- package/docs/profiles.md +68 -0
- package/docs/reference.md +64 -0
- package/docs/rest-client.md +97 -0
- package/docs/services.md +66 -0
- package/docs/shelter-inventory.md +64 -0
- package/docs/shelters.md +75 -0
- package/docs/troubleshooting.md +61 -0
- package/docs/using-the-sdk.md +203 -0
- package/docs/vendor-issues/README.md +46 -0
- package/docs/vendor-issues/api-coverage.md +27 -0
- package/docs/vendor-issues/attachment-authentication.md +40 -0
- package/docs/vendor-issues/date-semantics.md +51 -0
- package/docs/vendor-issues/empty-service-list-404.md +51 -0
- package/docs/vendor-issues/enrollment-complete-target-error.md +178 -0
- package/docs/vendor-issues/enrollment-effective-date-column.md +112 -0
- package/docs/vendor-issues/household-head-validation.md +26 -0
- package/docs/vendor-issues/write-contracts.md +51 -0
- package/examples/README.md +41 -0
- package/examples/current-living-situation.mts +27 -0
- package/examples/durable-records.mts +78 -0
- package/examples/enrollment-workflow.mts +46 -0
- package/examples/gwt-enrollment-session.ts +17 -0
- package/examples/gwt-file-store.mjs +2 -0
- package/examples/household-enrollment-workflow.mts +43 -0
- package/examples/household-exit-workflow.mts +35 -0
- package/examples/household-lifecycle-workflow.mts +34 -0
- package/examples/independent-needs.mts +30 -0
- package/examples/program-discovery.ts +19 -0
- package/examples/quickstart.mjs +13 -0
- package/examples/shelter-inventory.mts +30 -0
- package/examples/shelters.mts +33 -0
- package/examples/usage.ts +23 -0
- package/examples/verification-snapshot.ts +9 -0
- package/llms.txt +26 -0
- package/package.json +103 -0
- package/specs/provenance.json +17 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { WellSkyError } from "./runtime.js";
|
|
2
|
+
/** Experimental browser-session operations. REST authentication cannot implement this adapter. */
|
|
3
|
+
export type GwtEnrollmentIntent = {
|
|
4
|
+
kind: "add-household-members";
|
|
5
|
+
clientId: number;
|
|
6
|
+
enrollmentId: number;
|
|
7
|
+
memberClientIds: readonly number[];
|
|
8
|
+
} | {
|
|
9
|
+
kind: "create-interim-update";
|
|
10
|
+
clientId: number;
|
|
11
|
+
enrollmentId: number;
|
|
12
|
+
clientIds: readonly number[];
|
|
13
|
+
reviewDate: string;
|
|
14
|
+
};
|
|
15
|
+
/** Authoritative stored exit state for one enrollment and its complete exit subgroup. */
|
|
16
|
+
export interface GwtEnrollmentExit {
|
|
17
|
+
clientId: number;
|
|
18
|
+
enrollmentId: number;
|
|
19
|
+
projectExitDate: string | null;
|
|
20
|
+
destination: string | null;
|
|
21
|
+
destinationCode: string | null;
|
|
22
|
+
reasonForLeaving: string | null;
|
|
23
|
+
reasonForLeavingCode: string | null;
|
|
24
|
+
exitSubgroupId: string | null;
|
|
25
|
+
/** Exact active membership of the stored exit subgroup, or the single client when unset. */
|
|
26
|
+
clientIds: readonly number[];
|
|
27
|
+
enrollmentIds: readonly number[];
|
|
28
|
+
}
|
|
29
|
+
export interface GwtEnrollmentSnapshot {
|
|
30
|
+
source: "gwt-rpc";
|
|
31
|
+
tenant: string;
|
|
32
|
+
/** Non-secret identifier that changes when the authenticated session changes. */
|
|
33
|
+
sessionId: string;
|
|
34
|
+
/** Version/digest of the fresh enrollment model AND complete membership/review lists. */
|
|
35
|
+
revision: string;
|
|
36
|
+
clientId: number;
|
|
37
|
+
enrollmentId: number;
|
|
38
|
+
projectStartDate: string;
|
|
39
|
+
/** Authoritative program context supplied by the Playwright adapter. Optional for older adapters. */
|
|
40
|
+
providerId?: number;
|
|
41
|
+
enrollmentType?: string;
|
|
42
|
+
householdClientIds: readonly number[];
|
|
43
|
+
members: readonly {
|
|
44
|
+
clientId: number;
|
|
45
|
+
enrollmentId: number;
|
|
46
|
+
}[];
|
|
47
|
+
/** Fresh per-member exit metadata; optional only for older custom adapters. */
|
|
48
|
+
exits?: readonly GwtEnrollmentExit[];
|
|
49
|
+
reviews: readonly {
|
|
50
|
+
reviewId: number;
|
|
51
|
+
type: string;
|
|
52
|
+
reviewDate: string;
|
|
53
|
+
clientIds: readonly number[];
|
|
54
|
+
}[];
|
|
55
|
+
}
|
|
56
|
+
export interface GwtPreparedEnrollmentMutation {
|
|
57
|
+
intent: GwtEnrollmentIntent;
|
|
58
|
+
sessionId: string;
|
|
59
|
+
revision: string;
|
|
60
|
+
/** Milliseconds since epoch; capture must occur during prepare(), not before it. */
|
|
61
|
+
capturedAt: number;
|
|
62
|
+
url: string;
|
|
63
|
+
headers: Readonly<Record<string, string>>;
|
|
64
|
+
body: string;
|
|
65
|
+
}
|
|
66
|
+
export interface GwtEnrollmentSession {
|
|
67
|
+
/** Perform fresh, complete GWT/UI reads. Never turn a failed/partial REST read into empty arrays. */
|
|
68
|
+
readEnrollment(target: {
|
|
69
|
+
clientId: number;
|
|
70
|
+
enrollmentId: number;
|
|
71
|
+
}): Promise<GwtEnrollmentSnapshot>;
|
|
72
|
+
/** Build from current UI models or intercept and ABORT a fresh UI request BEFORE server transmission.
|
|
73
|
+
* Must verify that the serialized DTO targets exactly intent and preserves other stored values.
|
|
74
|
+
* No stored request template or previously submitted payload may be returned. */
|
|
75
|
+
prepare(intent: GwtEnrollmentIntent, snapshot: GwtEnrollmentSnapshot): Promise<GwtPreparedEnrollmentMutation>;
|
|
76
|
+
/** Exactly one authenticated same-origin POST. No retry, redirect, token-refresh replay, or logging of payloads. */
|
|
77
|
+
send(request: {
|
|
78
|
+
url: string;
|
|
79
|
+
headers: Readonly<Record<string, string>>;
|
|
80
|
+
body: string;
|
|
81
|
+
}): Promise<{
|
|
82
|
+
status: number;
|
|
83
|
+
body: string;
|
|
84
|
+
}>;
|
|
85
|
+
}
|
|
86
|
+
export interface GwtMutationAttempt {
|
|
87
|
+
intent: GwtEnrollmentIntent;
|
|
88
|
+
state: "attempted" | "verified";
|
|
89
|
+
}
|
|
90
|
+
/** The store must serialize all cooperating writers for an enrollment, and persist writes before resolving.
|
|
91
|
+
* Retain attempted records after crashes/timeouts. Production implementations need a shared durable store. */
|
|
92
|
+
export interface GwtMutationStore {
|
|
93
|
+
withLock<T>(scope: string, work: () => Promise<T>): Promise<T>;
|
|
94
|
+
read(scope: string): Promise<readonly GwtMutationAttempt[]>;
|
|
95
|
+
write(scope: string, attempts: readonly GwtMutationAttempt[]): Promise<void>;
|
|
96
|
+
}
|
|
97
|
+
export interface GwtEnrollmentOptions {
|
|
98
|
+
tenant: string;
|
|
99
|
+
/** Exact HTTPS web-app module URL, ending in /com.bowmansystems.sp5.core.ServicePoint/. */
|
|
100
|
+
moduleBaseUrl: string;
|
|
101
|
+
session: GwtEnrollmentSession;
|
|
102
|
+
store: GwtMutationStore;
|
|
103
|
+
/** Default 30 seconds, maximum 60 seconds. Includes post-capture readback time. */
|
|
104
|
+
maxPayloadAgeMs?: number;
|
|
105
|
+
}
|
|
106
|
+
export interface GwtEnrollmentMutationResult {
|
|
107
|
+
outcome: "already-present" | "written" | "reconciled";
|
|
108
|
+
snapshot: GwtEnrollmentSnapshot;
|
|
109
|
+
}
|
|
110
|
+
export declare class GwtEnrollmentConflictError extends WellSkyError {
|
|
111
|
+
readonly reason: "concurrent-change" | "review-conflict" | "previously-verified-missing" | "not-household-member" | "not-enrolled" | "review-before-entry";
|
|
112
|
+
constructor(reason: "concurrent-change" | "review-conflict" | "previously-verified-missing" | "not-household-member" | "not-enrolled" | "review-before-entry");
|
|
113
|
+
}
|
|
114
|
+
/** Browser-session helper with read-before-write, freshness checks and no automatic write retries.
|
|
115
|
+
* The adapter owns GWT model serialization and complete authoritative reads. This is not a REST fallback. */
|
|
116
|
+
export declare class GwtEnrollmentHelpers {
|
|
117
|
+
#private;
|
|
118
|
+
constructor(options: GwtEnrollmentOptions);
|
|
119
|
+
get tenant(): string;
|
|
120
|
+
get moduleBaseUrl(): string;
|
|
121
|
+
addHouseholdMembers(input: Omit<Extract<GwtEnrollmentIntent, {
|
|
122
|
+
kind: "add-household-members";
|
|
123
|
+
}>, "kind">): Promise<GwtEnrollmentMutationResult>;
|
|
124
|
+
createInterimUpdate(input: Omit<Extract<GwtEnrollmentIntent, {
|
|
125
|
+
kind: "create-interim-update";
|
|
126
|
+
}>, "kind">): Promise<GwtEnrollmentMutationResult>;
|
|
127
|
+
}
|
|
128
|
+
/** Development only: serializes callers sharing THIS instance. Does not survive process restarts.
|
|
129
|
+
* Pass a durable shared implementation of GwtMutationStore for real integrations. */
|
|
130
|
+
export declare function createInMemoryGwtMutationStore(): GwtMutationStore;
|
|
131
|
+
//# sourceMappingURL=gwt-enrollments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gwt-enrollments.d.ts","sourceRoot":"","sources":["../../src/gwt-enrollments.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { AmbiguousCommitError, ValidationError } from "./errors.js";
|
|
2
|
+
import { correctMobileEffectiveDate } from "./effective-date.js";
|
|
3
|
+
import { WellSkyError } from "./runtime.js";
|
|
4
|
+
export class GwtEnrollmentConflictError extends WellSkyError {
|
|
5
|
+
reason;
|
|
6
|
+
constructor(reason) {
|
|
7
|
+
super("validation", "GWT enrollment mutation");
|
|
8
|
+
this.reason = reason;
|
|
9
|
+
this.name = "GwtEnrollmentConflictError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const operation = "GWT enrollment mutation";
|
|
13
|
+
const moduleSuffix = "/com.bowmansystems.sp5.core.ServicePoint/";
|
|
14
|
+
const signatures = {
|
|
15
|
+
"add-household-members": ["updateEntryExitWithHouseholdMembers", "com.bowmansystems.sp5.model.client.SpEntryExit/3620680162", "java.util.ArrayList/4159755760", "Z"],
|
|
16
|
+
"create-interim-update": ["createEntryExitReviewWithHouseholdMembers", "com.bowmansystems.sp5.model.client.SpEntryExitReview/594863492", "java.util.ArrayList/4159755760"],
|
|
17
|
+
};
|
|
18
|
+
function id(value) {
|
|
19
|
+
if (!Number.isSafeInteger(value) || value <= 0)
|
|
20
|
+
throw new ValidationError(operation, ["id"]);
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
function ids(values) {
|
|
24
|
+
if (!Array.isArray(values) || values.length === 0)
|
|
25
|
+
throw new ValidationError(operation, ["clientIds"]);
|
|
26
|
+
const result = [...new Set(values.map(id))].sort((a, b) => a - b);
|
|
27
|
+
if (result.length !== values.length)
|
|
28
|
+
throw new ValidationError(operation, ["clientIds"]);
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
function instant(value) {
|
|
32
|
+
// Zero correction only validates/canonicalizes the instant. GWT dates are never shifted.
|
|
33
|
+
return correctMobileEffectiveDate(value, 0, operation);
|
|
34
|
+
}
|
|
35
|
+
function normalize(value) {
|
|
36
|
+
const target = { clientId: id(value.clientId), enrollmentId: id(value.enrollmentId) };
|
|
37
|
+
switch (value.kind) {
|
|
38
|
+
case "add-household-members": {
|
|
39
|
+
const memberClientIds = ids(value.memberClientIds);
|
|
40
|
+
if (memberClientIds.includes(target.clientId))
|
|
41
|
+
throw new ValidationError(operation, ["memberClientIds"]);
|
|
42
|
+
return { kind: value.kind, ...target, memberClientIds };
|
|
43
|
+
}
|
|
44
|
+
case "create-interim-update": {
|
|
45
|
+
const clientIds = ids(value.clientIds);
|
|
46
|
+
if (!clientIds.includes(target.clientId))
|
|
47
|
+
throw new ValidationError(operation, ["clientIds"]);
|
|
48
|
+
return { kind: value.kind, ...target, clientIds, reviewDate: instant(value.reviewDate) };
|
|
49
|
+
}
|
|
50
|
+
default: throw new ValidationError(operation, ["kind"]);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function key(intent) { return JSON.stringify(normalize(intent)); }
|
|
54
|
+
function protocol() { return new WellSkyError("protocol", operation); }
|
|
55
|
+
function validateSnapshot(value, intent, tenant) {
|
|
56
|
+
try {
|
|
57
|
+
if (value.source !== "gwt-rpc" || value.tenant !== tenant || value.clientId !== intent.clientId || value.enrollmentId !== intent.enrollmentId || typeof value.sessionId !== "string" || !value.sessionId || typeof value.revision !== "string" || !value.revision)
|
|
58
|
+
throw protocol();
|
|
59
|
+
instant(value.projectStartDate);
|
|
60
|
+
ids(value.householdClientIds);
|
|
61
|
+
ids(value.members.map(m => m.clientId));
|
|
62
|
+
ids(value.members.map(m => m.enrollmentId));
|
|
63
|
+
if (!value.members.some(m => m.clientId === intent.clientId && m.enrollmentId === intent.enrollmentId))
|
|
64
|
+
throw protocol();
|
|
65
|
+
if (!Array.isArray(value.reviews) || new Set(value.reviews.map(r => id(r.reviewId))).size !== value.reviews.length)
|
|
66
|
+
throw protocol();
|
|
67
|
+
for (const review of value.reviews) {
|
|
68
|
+
if (typeof review.type !== "string" || !review.type)
|
|
69
|
+
throw protocol();
|
|
70
|
+
instant(review.reviewDate);
|
|
71
|
+
const reviewClientIds = ids(review.clientIds);
|
|
72
|
+
if (!reviewClientIds.includes(intent.clientId) || reviewClientIds.some(clientId => !value.members.some(m => m.clientId === clientId)))
|
|
73
|
+
throw protocol();
|
|
74
|
+
}
|
|
75
|
+
return structuredClone(value);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
throw protocol();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function present(intent, snapshot) {
|
|
82
|
+
if (intent.kind === "add-household-members")
|
|
83
|
+
return intent.memberClientIds.every(clientId => snapshot.members.some(m => m.clientId === clientId));
|
|
84
|
+
const matches = snapshot.reviews.filter(r => r.type === "Update" && instant(r.reviewDate) === intent.reviewDate);
|
|
85
|
+
if (matches.length > 1 || matches.some(r => !intent.clientIds.every(clientId => r.clientIds.includes(clientId))))
|
|
86
|
+
throw new GwtEnrollmentConflictError("review-conflict");
|
|
87
|
+
return matches.length === 1;
|
|
88
|
+
}
|
|
89
|
+
function validateRequest(value, intent, snapshot, moduleBaseUrl, startedAt, maxAge) {
|
|
90
|
+
try {
|
|
91
|
+
if (key(value.intent) !== key(intent) || value.sessionId !== snapshot.sessionId || value.revision !== snapshot.revision)
|
|
92
|
+
throw protocol();
|
|
93
|
+
if (!Number.isFinite(value.capturedAt) || value.capturedAt < startedAt || value.capturedAt > Date.now() || Date.now() - value.capturedAt > maxAge)
|
|
94
|
+
throw protocol();
|
|
95
|
+
if (value.url !== `${moduleBaseUrl}service/ClientService` || typeof value.body !== "string" || value.body.length > 2_000_000 || !value.body.endsWith("|"))
|
|
96
|
+
throw protocol();
|
|
97
|
+
const headers = {};
|
|
98
|
+
for (const [name, content] of Object.entries(value.headers)) {
|
|
99
|
+
const lower = name.toLowerCase();
|
|
100
|
+
if (!["content-type", "x-gwt-module-base", "x-gwt-permutation"].includes(lower) || lower in headers || typeof content !== "string" || /[\r\n]/.test(content))
|
|
101
|
+
throw protocol();
|
|
102
|
+
headers[lower] = content;
|
|
103
|
+
}
|
|
104
|
+
if (!/^text\/x-gwt-rpc(?:;\s*charset=utf-8)?$/i.test(headers["content-type"] ?? "") || headers["x-gwt-module-base"] !== moduleBaseUrl || !/^[A-F0-9]{32}$/i.test(headers["x-gwt-permutation"] ?? ""))
|
|
105
|
+
throw protocol();
|
|
106
|
+
const tokens = value.body.split("|"), count = Number(tokens[2]);
|
|
107
|
+
if (tokens[0] !== "7" || tokens[1] !== "0" || !Number.isSafeInteger(count) || count < 5 || count > 10_000 || tokens.length <= count + 10)
|
|
108
|
+
throw protocol();
|
|
109
|
+
const strings = tokens.slice(3, 3 + count), data = tokens.slice(3 + count);
|
|
110
|
+
const stringAt = (index) => index && /^[1-9]\d*$/.test(index) ? strings[Number(index) - 1] : undefined;
|
|
111
|
+
let expected = signatures[intent.kind];
|
|
112
|
+
// The UI uses this one-parameter RPC for a client with no household members.
|
|
113
|
+
// Never treat it as a shortcut for a household review or a selected subset.
|
|
114
|
+
if (stringAt(data[3]) === "createEntryExitReview") {
|
|
115
|
+
if (intent.kind !== "create-interim-update" || intent.clientIds.length !== 1 || intent.clientIds[0] !== intent.clientId ||
|
|
116
|
+
snapshot.members.length !== 1 || snapshot.members[0]?.clientId !== intent.clientId || snapshot.members[0]?.enrollmentId !== intent.enrollmentId ||
|
|
117
|
+
snapshot.householdClientIds.length !== 1 || snapshot.householdClientIds[0] !== intent.clientId)
|
|
118
|
+
throw protocol();
|
|
119
|
+
expected = ["createEntryExitReview", "com.bowmansystems.sp5.model.client.SpEntryExitReview/594863492"];
|
|
120
|
+
}
|
|
121
|
+
if (stringAt(data[0]) !== moduleBaseUrl || !/^[A-F0-9]{32}$/i.test(stringAt(data[1]) ?? "") || stringAt(data[2]) !== "com.bowmansystems.sp5.core.client.services.GenericService" || stringAt(data[3]) !== expected[0] || data[4] !== String(expected.length - 1) || expected.slice(1).some((type, index) => stringAt(data[5 + index]) !== type))
|
|
122
|
+
throw protocol();
|
|
123
|
+
return { url: value.url, headers, body: value.body };
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
throw protocol();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** Browser-session helper with read-before-write, freshness checks and no automatic write retries.
|
|
130
|
+
* The adapter owns GWT model serialization and complete authoritative reads. This is not a REST fallback. */
|
|
131
|
+
export class GwtEnrollmentHelpers {
|
|
132
|
+
#options;
|
|
133
|
+
#maxAge;
|
|
134
|
+
constructor(options) {
|
|
135
|
+
const url = new URL(options.moduleBaseUrl);
|
|
136
|
+
if (!/^[A-Za-z0-9_-]+$/.test(options.tenant) || url.protocol !== "https:" || url.username || url.password || url.search || url.hash || url.pathname !== `/${options.tenant}${moduleSuffix}` || url.href !== options.moduleBaseUrl)
|
|
137
|
+
throw new ValidationError(operation, ["configuration"]);
|
|
138
|
+
this.#maxAge = options.maxPayloadAgeMs ?? 30_000;
|
|
139
|
+
if (!Number.isSafeInteger(this.#maxAge) || this.#maxAge < 1 || this.#maxAge > 60_000)
|
|
140
|
+
throw new ValidationError(operation, ["maxPayloadAgeMs"]);
|
|
141
|
+
this.#options = { ...options };
|
|
142
|
+
}
|
|
143
|
+
get tenant() { return this.#options.tenant; }
|
|
144
|
+
get moduleBaseUrl() { return this.#options.moduleBaseUrl; }
|
|
145
|
+
addHouseholdMembers(input) {
|
|
146
|
+
return this.#execute(normalize({ ...input, kind: "add-household-members" }));
|
|
147
|
+
}
|
|
148
|
+
createInterimUpdate(input) {
|
|
149
|
+
return this.#execute(normalize({ ...input, kind: "create-interim-update" }));
|
|
150
|
+
}
|
|
151
|
+
async #read(intent) {
|
|
152
|
+
try {
|
|
153
|
+
return validateSnapshot(await this.#options.session.readEnrollment({ clientId: intent.clientId, enrollmentId: intent.enrollmentId }), intent, this.#options.tenant);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
throw protocol();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async #execute(intent) {
|
|
160
|
+
const { store, session, tenant, moduleBaseUrl } = this.#options;
|
|
161
|
+
// Enrollment-wide lock also prevents overlapping member lists and different review intents racing.
|
|
162
|
+
const scope = JSON.stringify([moduleBaseUrl, tenant, intent.enrollmentId]);
|
|
163
|
+
return store.withLock(scope, async () => {
|
|
164
|
+
let snapshot = await this.#read(intent);
|
|
165
|
+
let attempts;
|
|
166
|
+
try {
|
|
167
|
+
attempts = (await store.read(scope)).map(a => {
|
|
168
|
+
if (!["attempted", "verified"].includes(a.state) || a.intent.clientId !== intent.clientId || a.intent.enrollmentId !== intent.enrollmentId)
|
|
169
|
+
throw protocol();
|
|
170
|
+
return { intent: normalize(a.intent), state: a.state };
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
throw protocol();
|
|
175
|
+
}
|
|
176
|
+
// An uncertain earlier request blocks ALL further writes to this enrollment until reconciled.
|
|
177
|
+
for (const attempt of attempts) {
|
|
178
|
+
if (attempt.state !== "attempted")
|
|
179
|
+
continue;
|
|
180
|
+
try {
|
|
181
|
+
if (!present(attempt.intent, snapshot))
|
|
182
|
+
throw protocol();
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
throw new AmbiguousCommitError(operation);
|
|
186
|
+
}
|
|
187
|
+
attempt.state = "verified";
|
|
188
|
+
await store.write(scope, structuredClone(attempts));
|
|
189
|
+
}
|
|
190
|
+
const previous = attempts.find(a => key(a.intent) === key(intent));
|
|
191
|
+
if (present(intent, snapshot)) {
|
|
192
|
+
// Adopting existing state is also a durable decision. A later missing
|
|
193
|
+
// record must not turn the same operation into permission to recreate it.
|
|
194
|
+
if (!previous) {
|
|
195
|
+
attempts.push({ intent, state: "verified" });
|
|
196
|
+
await store.write(scope, structuredClone(attempts));
|
|
197
|
+
}
|
|
198
|
+
return { outcome: previous ? "reconciled" : "already-present", snapshot };
|
|
199
|
+
}
|
|
200
|
+
if (previous)
|
|
201
|
+
throw new GwtEnrollmentConflictError("previously-verified-missing");
|
|
202
|
+
if (intent.kind === "add-household-members") {
|
|
203
|
+
if (intent.memberClientIds.some(clientId => !snapshot.householdClientIds.includes(clientId)))
|
|
204
|
+
throw new GwtEnrollmentConflictError("not-household-member");
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
if (intent.clientIds.some(clientId => !snapshot.members.some(m => m.clientId === clientId)))
|
|
208
|
+
throw new GwtEnrollmentConflictError("not-enrolled");
|
|
209
|
+
if (Date.parse(intent.reviewDate) < Date.parse(snapshot.projectStartDate))
|
|
210
|
+
throw new GwtEnrollmentConflictError("review-before-entry");
|
|
211
|
+
}
|
|
212
|
+
const startedAt = Date.now();
|
|
213
|
+
let prepared;
|
|
214
|
+
try {
|
|
215
|
+
prepared = structuredClone(await session.prepare(structuredClone(intent), structuredClone(snapshot)));
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
throw protocol();
|
|
219
|
+
}
|
|
220
|
+
validateRequest(prepared, intent, snapshot, moduleBaseUrl, startedAt, this.#maxAge);
|
|
221
|
+
const latest = await this.#read(intent);
|
|
222
|
+
if (latest.sessionId !== snapshot.sessionId || latest.revision !== snapshot.revision)
|
|
223
|
+
throw new GwtEnrollmentConflictError("concurrent-change");
|
|
224
|
+
snapshot = latest;
|
|
225
|
+
const request = validateRequest(prepared, intent, snapshot, moduleBaseUrl, startedAt, this.#maxAge);
|
|
226
|
+
attempts.push({ intent, state: "attempted" });
|
|
227
|
+
await store.write(scope, structuredClone(attempts)); // Must be durable BEFORE send.
|
|
228
|
+
let acknowledged = false;
|
|
229
|
+
try {
|
|
230
|
+
validateRequest(prepared, intent, snapshot, moduleBaseUrl, startedAt, this.#maxAge);
|
|
231
|
+
const response = await session.send(request); // Exactly one call, even on network/auth errors.
|
|
232
|
+
acknowledged = response.status === 200 && response.body.startsWith("//OK[");
|
|
233
|
+
}
|
|
234
|
+
catch { /* The server may have committed; authoritative readback decides. */ }
|
|
235
|
+
try {
|
|
236
|
+
snapshot = await this.#read(intent);
|
|
237
|
+
if (!present(intent, snapshot))
|
|
238
|
+
throw protocol();
|
|
239
|
+
attempts[attempts.length - 1] = { intent, state: "verified" };
|
|
240
|
+
await store.write(scope, structuredClone(attempts));
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
throw new AmbiguousCommitError(operation);
|
|
244
|
+
}
|
|
245
|
+
return { outcome: acknowledged ? "written" : "reconciled", snapshot };
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/** Development only: serializes callers sharing THIS instance. Does not survive process restarts.
|
|
250
|
+
* Pass a durable shared implementation of GwtMutationStore for real integrations. */
|
|
251
|
+
export function createInMemoryGwtMutationStore() {
|
|
252
|
+
const records = new Map();
|
|
253
|
+
const queues = new Map();
|
|
254
|
+
return {
|
|
255
|
+
async withLock(scope, work) {
|
|
256
|
+
const previous = queues.get(scope) ?? Promise.resolve();
|
|
257
|
+
let release = () => { };
|
|
258
|
+
const current = new Promise(resolve => { release = resolve; });
|
|
259
|
+
queues.set(scope, current);
|
|
260
|
+
await previous;
|
|
261
|
+
try {
|
|
262
|
+
return await work();
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
release();
|
|
266
|
+
if (queues.get(scope) === current)
|
|
267
|
+
queues.delete(scope);
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
async read(scope) { return structuredClone(records.get(scope) ?? []); },
|
|
271
|
+
async write(scope, attempts) { records.set(scope, structuredClone(attempts)); },
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
//# sourceMappingURL=gwt-enrollments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gwt-enrollments.js","sourceRoot":"","sources":["../../src/gwt-enrollments.ts"],"names":[],"mappings":"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","sourcesContent":["import { AmbiguousCommitError, ValidationError } from \"./errors.js\";\nimport { correctMobileEffectiveDate } from \"./effective-date.js\";\nimport { WellSkyError } from \"./runtime.js\";\n\n/** Experimental browser-session operations. REST authentication cannot implement this adapter. */\nexport type GwtEnrollmentIntent =\n | { kind: \"add-household-members\"; clientId: number; enrollmentId: number; memberClientIds: readonly number[] }\n | { kind: \"create-interim-update\"; clientId: number; enrollmentId: number; clientIds: readonly number[]; reviewDate: string };\n\n/** Authoritative stored exit state for one enrollment and its complete exit subgroup. */\nexport interface GwtEnrollmentExit {\n clientId: number;\n enrollmentId: number;\n projectExitDate: string | null;\n destination: string | null;\n destinationCode: string | null;\n reasonForLeaving: string | null;\n reasonForLeavingCode: string | null;\n exitSubgroupId: string | null;\n /** Exact active membership of the stored exit subgroup, or the single client when unset. */\n clientIds: readonly number[];\n enrollmentIds: readonly number[];\n}\n\nexport interface GwtEnrollmentSnapshot {\n source: \"gwt-rpc\";\n tenant: string;\n /** Non-secret identifier that changes when the authenticated session changes. */\n sessionId: string;\n /** Version/digest of the fresh enrollment model AND complete membership/review lists. */\n revision: string;\n clientId: number;\n enrollmentId: number;\n projectStartDate: string;\n /** Authoritative program context supplied by the Playwright adapter. Optional for older adapters. */\n providerId?: number;\n enrollmentType?: string;\n householdClientIds: readonly number[];\n members: readonly { clientId: number; enrollmentId: number }[];\n /** Fresh per-member exit metadata; optional only for older custom adapters. */\n exits?: readonly GwtEnrollmentExit[];\n reviews: readonly { reviewId: number; type: string; reviewDate: string; clientIds: readonly number[] }[];\n}\n\nexport interface GwtPreparedEnrollmentMutation {\n intent: GwtEnrollmentIntent;\n sessionId: string;\n revision: string;\n /** Milliseconds since epoch; capture must occur during prepare(), not before it. */\n capturedAt: number;\n url: string;\n headers: Readonly<Record<string, string>>;\n body: string;\n}\n\nexport interface GwtEnrollmentSession {\n /** Perform fresh, complete GWT/UI reads. Never turn a failed/partial REST read into empty arrays. */\n readEnrollment(target: { clientId: number; enrollmentId: number }): Promise<GwtEnrollmentSnapshot>;\n /** Build from current UI models or intercept and ABORT a fresh UI request BEFORE server transmission.\n * Must verify that the serialized DTO targets exactly intent and preserves other stored values.\n * No stored request template or previously submitted payload may be returned. */\n prepare(intent: GwtEnrollmentIntent, snapshot: GwtEnrollmentSnapshot): Promise<GwtPreparedEnrollmentMutation>;\n /** Exactly one authenticated same-origin POST. No retry, redirect, token-refresh replay, or logging of payloads. */\n send(request: { url: string; headers: Readonly<Record<string, string>>; body: string }): Promise<{ status: number; body: string }>;\n}\n\nexport interface GwtMutationAttempt {\n intent: GwtEnrollmentIntent;\n state: \"attempted\" | \"verified\";\n}\n\n/** The store must serialize all cooperating writers for an enrollment, and persist writes before resolving.\n * Retain attempted records after crashes/timeouts. Production implementations need a shared durable store. */\nexport interface GwtMutationStore {\n withLock<T>(scope: string, work: () => Promise<T>): Promise<T>;\n read(scope: string): Promise<readonly GwtMutationAttempt[]>;\n write(scope: string, attempts: readonly GwtMutationAttempt[]): Promise<void>;\n}\n\nexport interface GwtEnrollmentOptions {\n tenant: string;\n /** Exact HTTPS web-app module URL, ending in /com.bowmansystems.sp5.core.ServicePoint/. */\n moduleBaseUrl: string;\n\n session: GwtEnrollmentSession;\n store: GwtMutationStore;\n /** Default 30 seconds, maximum 60 seconds. Includes post-capture readback time. */\n maxPayloadAgeMs?: number;\n}\n\nexport interface GwtEnrollmentMutationResult {\n outcome: \"already-present\" | \"written\" | \"reconciled\";\n snapshot: GwtEnrollmentSnapshot;\n}\n\nexport class GwtEnrollmentConflictError extends WellSkyError {\n constructor(readonly reason: \"concurrent-change\" | \"review-conflict\" | \"previously-verified-missing\" | \"not-household-member\" | \"not-enrolled\" | \"review-before-entry\") {\n super(\"validation\", \"GWT enrollment mutation\");\n this.name = \"GwtEnrollmentConflictError\";\n }\n}\n\nconst operation = \"GWT enrollment mutation\";\nconst moduleSuffix = \"/com.bowmansystems.sp5.core.ServicePoint/\";\nconst signatures = {\n \"add-household-members\": [\"updateEntryExitWithHouseholdMembers\", \"com.bowmansystems.sp5.model.client.SpEntryExit/3620680162\", \"java.util.ArrayList/4159755760\", \"Z\"],\n \"create-interim-update\": [\"createEntryExitReviewWithHouseholdMembers\", \"com.bowmansystems.sp5.model.client.SpEntryExitReview/594863492\", \"java.util.ArrayList/4159755760\"],\n};\n\nfunction id(value: number): number {\n if (!Number.isSafeInteger(value) || value <= 0) throw new ValidationError(operation, [\"id\"]);\n return value;\n}\nfunction ids(values: readonly number[]): number[] {\n if (!Array.isArray(values) || values.length === 0) throw new ValidationError(operation, [\"clientIds\"]);\n const result = [...new Set(values.map(id))].sort((a, b) => a - b);\n if (result.length !== values.length) throw new ValidationError(operation, [\"clientIds\"]);\n return result;\n}\nfunction instant(value: string): string {\n // Zero correction only validates/canonicalizes the instant. GWT dates are never shifted.\n return correctMobileEffectiveDate(value, 0, operation);\n}\nfunction normalize(value: GwtEnrollmentIntent): GwtEnrollmentIntent {\n const target = { clientId: id(value.clientId), enrollmentId: id(value.enrollmentId) };\n switch (value.kind) {\n case \"add-household-members\": {\n const memberClientIds = ids(value.memberClientIds);\n if (memberClientIds.includes(target.clientId)) throw new ValidationError(operation, [\"memberClientIds\"]);\n return { kind: value.kind, ...target, memberClientIds };\n }\n case \"create-interim-update\": {\n const clientIds = ids(value.clientIds);\n if (!clientIds.includes(target.clientId)) throw new ValidationError(operation, [\"clientIds\"]);\n return { kind: value.kind, ...target, clientIds, reviewDate: instant(value.reviewDate) };\n }\n default: throw new ValidationError(operation, [\"kind\"]);\n }\n}\nfunction key(intent: GwtEnrollmentIntent): string { return JSON.stringify(normalize(intent)); }\nfunction protocol(): WellSkyError { return new WellSkyError(\"protocol\", operation); }\n\nfunction validateSnapshot(value: GwtEnrollmentSnapshot, intent: GwtEnrollmentIntent, tenant: string): GwtEnrollmentSnapshot {\n try {\n if (value.source !== \"gwt-rpc\" || value.tenant !== tenant || value.clientId !== intent.clientId || value.enrollmentId !== intent.enrollmentId || typeof value.sessionId !== \"string\" || !value.sessionId || typeof value.revision !== \"string\" || !value.revision) throw protocol();\n instant(value.projectStartDate);\n ids(value.householdClientIds);\n ids(value.members.map(m => m.clientId));\n ids(value.members.map(m => m.enrollmentId));\n if (!value.members.some(m => m.clientId === intent.clientId && m.enrollmentId === intent.enrollmentId)) throw protocol();\n if (!Array.isArray(value.reviews) || new Set(value.reviews.map(r => id(r.reviewId))).size !== value.reviews.length) throw protocol();\n for (const review of value.reviews) {\n if (typeof review.type !== \"string\" || !review.type) throw protocol();\n instant(review.reviewDate); const reviewClientIds = ids(review.clientIds);\n if (!reviewClientIds.includes(intent.clientId) || reviewClientIds.some(clientId => !value.members.some(m => m.clientId === clientId))) throw protocol();\n }\n return structuredClone(value);\n } catch { throw protocol(); }\n}\n\nfunction present(intent: GwtEnrollmentIntent, snapshot: GwtEnrollmentSnapshot): boolean {\n if (intent.kind === \"add-household-members\") return intent.memberClientIds.every(clientId => snapshot.members.some(m => m.clientId === clientId));\n const matches = snapshot.reviews.filter(r => r.type === \"Update\" && instant(r.reviewDate) === intent.reviewDate);\n if (matches.length > 1 || matches.some(r => !intent.clientIds.every(clientId => r.clientIds.includes(clientId)))) throw new GwtEnrollmentConflictError(\"review-conflict\");\n return matches.length === 1;\n}\n\nfunction validateRequest(value: GwtPreparedEnrollmentMutation, intent: GwtEnrollmentIntent, snapshot: GwtEnrollmentSnapshot, moduleBaseUrl: string, startedAt: number, maxAge: number): { url: string; headers: Record<string, string>; body: string } {\n try {\n if (key(value.intent) !== key(intent) || value.sessionId !== snapshot.sessionId || value.revision !== snapshot.revision) throw protocol();\n if (!Number.isFinite(value.capturedAt) || value.capturedAt < startedAt || value.capturedAt > Date.now() || Date.now() - value.capturedAt > maxAge) throw protocol();\n if (value.url !== `${moduleBaseUrl}service/ClientService` || typeof value.body !== \"string\" || value.body.length > 2_000_000 || !value.body.endsWith(\"|\")) throw protocol();\n const headers: Record<string, string> = {};\n for (const [name, content] of Object.entries(value.headers)) {\n const lower = name.toLowerCase();\n if (![\"content-type\", \"x-gwt-module-base\", \"x-gwt-permutation\"].includes(lower) || lower in headers || typeof content !== \"string\" || /[\\r\\n]/.test(content)) throw protocol();\n headers[lower] = content;\n }\n if (!/^text\\/x-gwt-rpc(?:;\\s*charset=utf-8)?$/i.test(headers[\"content-type\"] ?? \"\") || headers[\"x-gwt-module-base\"] !== moduleBaseUrl || !/^[A-F0-9]{32}$/i.test(headers[\"x-gwt-permutation\"] ?? \"\")) throw protocol();\n const tokens = value.body.split(\"|\"), count = Number(tokens[2]);\n if (tokens[0] !== \"7\" || tokens[1] !== \"0\" || !Number.isSafeInteger(count) || count < 5 || count > 10_000 || tokens.length <= count + 10) throw protocol();\n const strings = tokens.slice(3, 3 + count), data = tokens.slice(3 + count);\n const stringAt = (index: string | undefined) => index && /^[1-9]\\d*$/.test(index) ? strings[Number(index) - 1] : undefined;\n let expected = signatures[intent.kind];\n // The UI uses this one-parameter RPC for a client with no household members.\n // Never treat it as a shortcut for a household review or a selected subset.\n if (stringAt(data[3]) === \"createEntryExitReview\") {\n if (intent.kind !== \"create-interim-update\" || intent.clientIds.length !== 1 || intent.clientIds[0] !== intent.clientId ||\n snapshot.members.length !== 1 || snapshot.members[0]?.clientId !== intent.clientId || snapshot.members[0]?.enrollmentId !== intent.enrollmentId ||\n snapshot.householdClientIds.length !== 1 || snapshot.householdClientIds[0] !== intent.clientId) throw protocol();\n expected = [\"createEntryExitReview\", \"com.bowmansystems.sp5.model.client.SpEntryExitReview/594863492\"];\n }\n if (stringAt(data[0]) !== moduleBaseUrl || !/^[A-F0-9]{32}$/i.test(stringAt(data[1]) ?? \"\") || stringAt(data[2]) !== \"com.bowmansystems.sp5.core.client.services.GenericService\" || stringAt(data[3]) !== expected[0] || data[4] !== String(expected.length - 1) || expected.slice(1).some((type, index) => stringAt(data[5 + index]) !== type)) throw protocol();\n return { url: value.url, headers, body: value.body };\n } catch { throw protocol(); }\n}\n\n/** Browser-session helper with read-before-write, freshness checks and no automatic write retries.\n * The adapter owns GWT model serialization and complete authoritative reads. This is not a REST fallback. */\nexport class GwtEnrollmentHelpers {\n readonly #options: GwtEnrollmentOptions;\n readonly #maxAge: number;\n constructor(options: GwtEnrollmentOptions) {\n const url = new URL(options.moduleBaseUrl);\n if (!/^[A-Za-z0-9_-]+$/.test(options.tenant) || url.protocol !== \"https:\" || url.username || url.password || url.search || url.hash || url.pathname !== `/${options.tenant}${moduleSuffix}` || url.href !== options.moduleBaseUrl) throw new ValidationError(operation, [\"configuration\"]);\n this.#maxAge = options.maxPayloadAgeMs ?? 30_000;\n if (!Number.isSafeInteger(this.#maxAge) || this.#maxAge < 1 || this.#maxAge > 60_000) throw new ValidationError(operation, [\"maxPayloadAgeMs\"]);\n this.#options = { ...options };\n }\n\n get tenant(): string { return this.#options.tenant; }\n get moduleBaseUrl(): string { return this.#options.moduleBaseUrl; }\n\n addHouseholdMembers(input: Omit<Extract<GwtEnrollmentIntent, { kind: \"add-household-members\" }>, \"kind\">): Promise<GwtEnrollmentMutationResult> {\n return this.#execute(normalize({ ...input, kind: \"add-household-members\" }));\n }\n createInterimUpdate(input: Omit<Extract<GwtEnrollmentIntent, { kind: \"create-interim-update\" }>, \"kind\">): Promise<GwtEnrollmentMutationResult> {\n return this.#execute(normalize({ ...input, kind: \"create-interim-update\" }));\n }\n\n async #read(intent: GwtEnrollmentIntent): Promise<GwtEnrollmentSnapshot> {\n try {\n return validateSnapshot(await this.#options.session.readEnrollment({ clientId: intent.clientId, enrollmentId: intent.enrollmentId }), intent, this.#options.tenant);\n } catch { throw protocol(); }\n }\n\n async #execute(intent: GwtEnrollmentIntent): Promise<GwtEnrollmentMutationResult> {\n const { store, session, tenant, moduleBaseUrl } = this.#options;\n // Enrollment-wide lock also prevents overlapping member lists and different review intents racing.\n const scope = JSON.stringify([moduleBaseUrl, tenant, intent.enrollmentId]);\n return store.withLock(scope, async () => {\n let snapshot = await this.#read(intent);\n let attempts: GwtMutationAttempt[];\n try {\n attempts = (await store.read(scope)).map(a => {\n if (![\"attempted\", \"verified\"].includes(a.state) || a.intent.clientId !== intent.clientId || a.intent.enrollmentId !== intent.enrollmentId) throw protocol();\n return { intent: normalize(a.intent), state: a.state };\n });\n } catch { throw protocol(); }\n // An uncertain earlier request blocks ALL further writes to this enrollment until reconciled.\n for (const attempt of attempts) {\n if (attempt.state !== \"attempted\") continue;\n try { if (!present(attempt.intent, snapshot)) throw protocol(); } catch { throw new AmbiguousCommitError(operation); }\n attempt.state = \"verified\";\n await store.write(scope, structuredClone(attempts));\n }\n const previous = attempts.find(a => key(a.intent) === key(intent));\n if (present(intent, snapshot)) {\n // Adopting existing state is also a durable decision. A later missing\n // record must not turn the same operation into permission to recreate it.\n if (!previous) {\n attempts.push({ intent, state: \"verified\" });\n await store.write(scope, structuredClone(attempts));\n }\n return { outcome: previous ? \"reconciled\" : \"already-present\", snapshot };\n }\n if (previous) throw new GwtEnrollmentConflictError(\"previously-verified-missing\");\n if (intent.kind === \"add-household-members\") {\n if (intent.memberClientIds.some(clientId => !snapshot.householdClientIds.includes(clientId))) throw new GwtEnrollmentConflictError(\"not-household-member\");\n } else {\n if (intent.clientIds.some(clientId => !snapshot.members.some(m => m.clientId === clientId))) throw new GwtEnrollmentConflictError(\"not-enrolled\");\n if (Date.parse(intent.reviewDate) < Date.parse(snapshot.projectStartDate)) throw new GwtEnrollmentConflictError(\"review-before-entry\");\n }\n const startedAt = Date.now();\n let prepared: GwtPreparedEnrollmentMutation;\n try { prepared = structuredClone(await session.prepare(structuredClone(intent), structuredClone(snapshot))); } catch { throw protocol(); }\n validateRequest(prepared, intent, snapshot, moduleBaseUrl, startedAt, this.#maxAge);\n const latest = await this.#read(intent);\n if (latest.sessionId !== snapshot.sessionId || latest.revision !== snapshot.revision) throw new GwtEnrollmentConflictError(\"concurrent-change\");\n snapshot = latest;\n const request = validateRequest(prepared, intent, snapshot, moduleBaseUrl, startedAt, this.#maxAge);\n attempts.push({ intent, state: \"attempted\" });\n await store.write(scope, structuredClone(attempts)); // Must be durable BEFORE send.\n let acknowledged = false;\n try {\n validateRequest(prepared, intent, snapshot, moduleBaseUrl, startedAt, this.#maxAge);\n const response = await session.send(request); // Exactly one call, even on network/auth errors.\n acknowledged = response.status === 200 && response.body.startsWith(\"//OK[\");\n } catch { /* The server may have committed; authoritative readback decides. */ }\n try {\n snapshot = await this.#read(intent);\n if (!present(intent, snapshot)) throw protocol();\n attempts[attempts.length - 1] = { intent, state: \"verified\" };\n await store.write(scope, structuredClone(attempts));\n } catch { throw new AmbiguousCommitError(operation); }\n return { outcome: acknowledged ? \"written\" : \"reconciled\", snapshot };\n });\n }\n}\n\n/** Development only: serializes callers sharing THIS instance. Does not survive process restarts.\n * Pass a durable shared implementation of GwtMutationStore for real integrations. */\nexport function createInMemoryGwtMutationStore(): GwtMutationStore {\n const records = new Map<string, readonly GwtMutationAttempt[]>();\n const queues = new Map<string, Promise<void>>();\n return {\n async withLock(scope, work) {\n const previous = queues.get(scope) ?? Promise.resolve();\n let release = () => {};\n const current = new Promise<void>(resolve => { release = resolve; });\n queues.set(scope, current);\n await previous;\n try { return await work(); } finally { release(); if (queues.get(scope) === current) queues.delete(scope); }\n },\n async read(scope) { return structuredClone(records.get(scope) ?? []); },\n async write(scope, attempts) { records.set(scope, structuredClone(attempts)); },\n };\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC","sourcesContent":["export * from \"./v3.js\";\nexport * from \"./testing.js\";\n"]}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { RequestOptions } from "./runtime.js";
|
|
2
|
+
export interface AssessmentContext extends RequestOptions {
|
|
3
|
+
clientId: number;
|
|
4
|
+
assessmentCode: string;
|
|
5
|
+
effectiveDate?: string;
|
|
6
|
+
refresh?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface PicklistOption {
|
|
9
|
+
id: number;
|
|
10
|
+
picklistId: number | null;
|
|
11
|
+
code: string | null;
|
|
12
|
+
picklistCode: string | null;
|
|
13
|
+
/** Raw metadata name. Not proof of a valid standalone/enrollment write label. */
|
|
14
|
+
name: string | null;
|
|
15
|
+
displayOrder: number | null;
|
|
16
|
+
hudVisibility: number | null;
|
|
17
|
+
}
|
|
18
|
+
export interface QuestionDefinition {
|
|
19
|
+
questionId: number;
|
|
20
|
+
fieldName: string | null;
|
|
21
|
+
description: string | null;
|
|
22
|
+
questionType: string;
|
|
23
|
+
displayOrder: number | null;
|
|
24
|
+
mandatory: boolean | null;
|
|
25
|
+
showOnPrevious: boolean | null;
|
|
26
|
+
picklistId: number | null;
|
|
27
|
+
options: PicklistOption[];
|
|
28
|
+
children: QuestionDefinition[];
|
|
29
|
+
}
|
|
30
|
+
export interface AssessmentDefinition {
|
|
31
|
+
tenant: string;
|
|
32
|
+
assessmentCode: string;
|
|
33
|
+
effectiveDate: string | null;
|
|
34
|
+
retrievedAt: string;
|
|
35
|
+
/** The mobile metadata endpoint cannot filter its option catalog by date. */
|
|
36
|
+
picklistEffectiveDate: "current";
|
|
37
|
+
questions: QuestionDefinition[];
|
|
38
|
+
unresolvedPicklists: string[];
|
|
39
|
+
}
|
|
40
|
+
export interface QuestionReference {
|
|
41
|
+
fieldName: string;
|
|
42
|
+
parentPath?: readonly string[];
|
|
43
|
+
}
|
|
44
|
+
export interface QuestionEntry {
|
|
45
|
+
path: string[];
|
|
46
|
+
question: QuestionDefinition;
|
|
47
|
+
}
|
|
48
|
+
export interface PicklistDefinition {
|
|
49
|
+
picklistId: number | null;
|
|
50
|
+
questionPath: string[];
|
|
51
|
+
options: PicklistOption[];
|
|
52
|
+
status: "enumerated" | "unresolved";
|
|
53
|
+
}
|
|
54
|
+
export type PicklistSelector = {
|
|
55
|
+
code: string;
|
|
56
|
+
name?: string;
|
|
57
|
+
} | {
|
|
58
|
+
name: string;
|
|
59
|
+
code?: never;
|
|
60
|
+
};
|
|
61
|
+
export interface MetadataSource {
|
|
62
|
+
generic: (context: AssessmentContext) => Promise<unknown>;
|
|
63
|
+
mobile: (context: AssessmentContext) => Promise<unknown>;
|
|
64
|
+
}
|
|
65
|
+
export interface DefinitionExpectation {
|
|
66
|
+
questions: ReadonlyArray<{
|
|
67
|
+
path: readonly string[];
|
|
68
|
+
questionType: string;
|
|
69
|
+
mandatory?: boolean | null;
|
|
70
|
+
}>;
|
|
71
|
+
}
|
|
72
|
+
export interface DefinitionDrift {
|
|
73
|
+
breaking: boolean;
|
|
74
|
+
added: string[];
|
|
75
|
+
removed: string[];
|
|
76
|
+
retyped: string[];
|
|
77
|
+
newlyMandatory: string[];
|
|
78
|
+
}
|
|
79
|
+
export declare function flattenQuestions(questions: readonly QuestionDefinition[], parent?: readonly string[]): QuestionEntry[];
|
|
80
|
+
/** Only metadata fields are copied; no answers, client IDs, recordsets, or staff IDs survive. */
|
|
81
|
+
export declare function buildAssessmentDefinition(input: {
|
|
82
|
+
tenant: string;
|
|
83
|
+
assessmentCode: string;
|
|
84
|
+
effectiveDate?: string;
|
|
85
|
+
mobile: unknown;
|
|
86
|
+
generic: unknown;
|
|
87
|
+
}): AssessmentDefinition;
|
|
88
|
+
export declare function compareDefinition(expected: DefinitionExpectation, definition: AssessmentDefinition): DefinitionDrift;
|
|
89
|
+
export declare function resolvePicklistOption(question: QuestionDefinition, selector: PicklistSelector): PicklistOption;
|
|
90
|
+
export declare class AssessmentMetadata {
|
|
91
|
+
#private;
|
|
92
|
+
constructor(tenant: string, source: MetadataSource, ttlMs?: number, maxEntries?: number);
|
|
93
|
+
getDefinition(context: AssessmentContext): Promise<AssessmentDefinition>;
|
|
94
|
+
listQuestions(context: AssessmentContext): Promise<QuestionEntry[]>;
|
|
95
|
+
getQuestion(context: AssessmentContext & QuestionReference): Promise<QuestionDefinition>;
|
|
96
|
+
listPicklists(context: AssessmentContext): Promise<PicklistDefinition[]>;
|
|
97
|
+
resolve(context: AssessmentContext & QuestionReference & PicklistSelector): Promise<PicklistOption>;
|
|
98
|
+
clearCache(): void;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=metadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../src/metadata.ts"],"names":[],"mappings":"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"}
|