Skip to content

Commit 6959abf

Browse files
committed
Plan 46 Slice 4: HDSItemDef.matchesEvent + ItemCustomization.context
Two small additions consumed by hds-forms-js Slice 4: - HDSItemDef.matchesEvent(event) — D3-aware match returning true when an event resolves to this itemDef via forEvent (covers both direct (streamId, eventType) match and the closest-ancestor walk-up). Falls back to plain (streamId-in-streamIds, type-equals-eventType) if the model handle isn't available. - ItemCustomization.context?: string — per-item context streamId on CollectorRequest sections. Lets a section emit events placed at a descendant streamId of the itemDef's canonical home (e.g. treatment itemDef → events at treatment-fertility), threaded through to eventTemplate({ context }) at submit time. 4 new [CTXR] tests; 484 total lib tests passing (the two pre-existing [MODSA]/[MODSB] datasource failures relate to demo.datasafe.dev migration — see _plans/TODO-ASAP.md). Tracked: healthdatasafe/data-model#2.
1 parent eac5e0b commit 6959abf

3 files changed

Lines changed: 62 additions & 0 deletions

File tree

tests/contextResolution.test.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,37 @@ describe('[CTXR] Context-via-substream (Plan 46 D3)', () => {
116116
assert.equal(itemDef.key, 'treatment-basic');
117117
});
118118
});
119+
120+
describe('matchesEvent (D3-aware event matching)', () => {
121+
it('[CTXR-L] direct (streamId, eventType) match returns true', () => {
122+
const itemDef = model.itemsDefs.forKey('treatment-basic');
123+
assert.equal(itemDef.matchesEvent({
124+
type: 'treatment/basic',
125+
streamIds: ['treatment']
126+
}), true);
127+
});
128+
129+
it('[CTXR-M] descendant context resolves via walk-up returns true', () => {
130+
const itemDef = model.itemsDefs.forKey('treatment-coded');
131+
assert.equal(itemDef.matchesEvent({
132+
type: 'treatment/coded-v1',
133+
streamIds: ['treatment-fertility']
134+
}), true);
135+
});
136+
137+
it('[CTXR-N] cross-tree mismatch returns false', () => {
138+
const itemDef = model.itemsDefs.forKey('treatment-basic');
139+
assert.equal(itemDef.matchesEvent({
140+
type: 'procedure/basic',
141+
streamIds: ['procedure-fertility']
142+
}), false);
143+
});
144+
145+
it('[CTXR-O] missing streamIds or type returns false', () => {
146+
const itemDef = model.itemsDefs.forKey('treatment-basic');
147+
assert.equal(itemDef.matchesEvent({}), false);
148+
assert.equal(itemDef.matchesEvent({ type: 'treatment/basic' }), false);
149+
assert.equal(itemDef.matchesEvent({ streamIds: ['treatment'] }), false);
150+
});
151+
});
119152
});

ts/HDSModel/HDSItemDef.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,25 @@ export class HDSItemDef {
109109
throw new Error(`Context streamId "${candidate}" is not a descendant of itemDef "${this.#key}" streamId "${ancestor}"`);
110110
}
111111
}
112+
113+
/**
114+
* D3-aware event-matching. Returns true if the given event resolves to
115+
* this itemDef via `model.itemsDefs.forEvent(event)` — covering both the
116+
* direct (streamId, eventType) match and the closest-ancestor walk-up.
117+
*
118+
* Useful in form-engine code that needs to check whether an event belongs
119+
* to a particular itemDef without re-implementing the resolution rule.
120+
* Falls back to plain `(streamId in event.streamIds, type === eventType)`
121+
* if the model handle isn't available.
122+
*/
123+
matchesEvent (event: { type?: string; streamIds?: string[] }): boolean {
124+
if (!event || event.type == null || !Array.isArray(event.streamIds)) return false;
125+
if (this.#model) {
126+
const resolved = this.#model.itemsDefs.forEvent(event, false);
127+
return resolved !== null && resolved.key === this.#key;
128+
}
129+
// Fallback: direct match against this itemDef's streamId + eventType.
130+
if (!this.eventTypes.includes(event.type)) return false;
131+
return event.streamIds.includes(this.#data.streamId);
132+
}
112133
}

ts/appTemplates/itemLabels.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ export interface ItemCustomization {
3333
repeatable?: string;
3434
reminder?: Record<string, unknown>;
3535
labels?: ItemLabels;
36+
/**
37+
* Plan 46 (D3) — optional context streamId for events created from this
38+
* item in this section. Must be `itemDef.streamId` or a descendant; the
39+
* itemDef's `eventTemplate({ context })` enforces the constraint.
40+
* Lets a single itemDef registered at e.g. `treatment` produce events
41+
* placed at `treatment-fertility`, `treatment-oncology`, … per section.
42+
*/
43+
context?: string;
3644
[key: string]: unknown;
3745
}
3846

0 commit comments

Comments
 (0)