Skip to content

Commit 0e41da7

Browse files
committed
Clean up supporting files
- Update tests and package import syntax. - Improve readmes
1 parent 419556e commit 0e41da7

40 files changed

+601
-146
lines changed

.github/workflows/stage-1-commit.yaml

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,95 @@ jobs:
199199
idp_aws_report_upload_region: "${{ secrets.IDP_AWS_REPORT_UPLOAD_REGION }}"
200200
idp_aws_report_upload_role_name: "${{ secrets.IDP_AWS_REPORT_UPLOAD_ROLE_NAME }}"
201201
idp_aws_report_upload_bucket_endpoint: "${{ secrets.IDP_AWS_REPORT_UPLOAD_BUCKET_ENDPOINT }}"
202+
203+
detect-event-schema-package-changes:
204+
name: "Check for changes to event schema package compared to main branch"
205+
runs-on: ubuntu-latest
206+
permissions:
207+
contents: read
208+
outputs:
209+
changed: ${{ steps.check.outputs.changed }}
210+
main_version: ${{ steps.check.outputs.main_version }}
211+
212+
steps:
213+
- name: "Checkout code"
214+
uses: actions/checkout@v4
215+
with:
216+
fetch-depth: 0
217+
218+
- name: Detect package changes and current version
219+
id: check
220+
run: |
221+
git fetch origin main
222+
223+
if git diff --quiet origin/main...HEAD -- internal/events; then
224+
echo "No changes in event schemas package"
225+
echo "changed=false" >> $GITHUB_OUTPUT
226+
else
227+
echo "Changes detected in event schemas"
228+
echo "changed=true" >> $GITHUB_OUTPUT
229+
fi
230+
231+
if content=$(git show origin/main:internal/events/schemas/package.json 2>/dev/null); then
232+
version=$(jq -r .version <<< $content);
233+
else
234+
version=null;
235+
fi
236+
237+
echo "Detected package version $version in main branch"
238+
echo "main_version=$version" >> $GITHUB_OUTPUT
239+
240+
check-schemas-generated:
241+
name: Check event schemas have been regenerated
242+
needs: detect-event-schema-package-changes
243+
if: needs.detect-event-schema-package-changes.outputs.changed == 'true'
244+
runs-on: ubuntu-latest
245+
permissions:
246+
contents: read
247+
steps:
248+
- name: "Checkout code"
249+
uses: actions/checkout@v4
250+
251+
# Simplified caching - template management has more complex caching of installed modules from another build step
252+
- name: "Cache node_modules"
253+
uses: actions/cache@v4
254+
with:
255+
path: |
256+
**/node_modules
257+
key: ${{ runner.os }}-node-${{ inputs.nodejs_version }}-${{ hashFiles('**/package-lock.json') }}
258+
restore-keys: |
259+
${{ runner.os }}-node-${{ inputs.nodejs_version }}-
260+
261+
- name: "Re-generate schemas"
262+
run: |
263+
npm ci --workspace internal/events
264+
npm --workspace internal/events run gen:jsonschema
265+
266+
- name: Check for schema changes
267+
run: git diff --quiet internal/events/schemas
268+
269+
check-schema-version-change:
270+
name: Check event schema version has been updated
271+
needs: detect-event-schema-package-changes
272+
if: needs.detect-event-schema-package-changes.outputs.changed == 'true'
273+
runs-on: ubuntu-latest
274+
permissions:
275+
contents: read
276+
steps:
277+
- name: Checkout code
278+
uses: actions/checkout@v4
279+
280+
- name: Check schema versions
281+
run: |
282+
source scripts/is_valid_increment.sh
283+
284+
main_version="${{ needs.detect-event-schema-package-changes.outputs.main_version }}"
285+
echo "Main version: ${{ needs.detect-event-schema-package-changes.outputs.main_version }}"
286+
287+
local_version=$(jq -r '.version' internal/events/package.json)
288+
echo "Local version: $local_version"
289+
290+
if ! is_valid_increment "$main_version" "$local_version" ; then
291+
echo "Error: Event Schema package has changed, but new version ($local_version) is not a valid increment from latest version on main branch ($main_version)."
292+
exit 1
293+
fi

internal/datastore/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
"name": "@internal/datastore",
2727
"private": true,
2828
"scripts": {
29-
"diagrams": "ts-node src/cli/diagrams.ts",
3029
"lint": "eslint .",
3130
"lint:fix": "eslint . --fix",
3231
"test:unit": "jest",

internal/datastore/tsconfig.json

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
11
{
2-
"compilerOptions": {
3-
"baseUrl": ".",
4-
"isolatedModules": true,
5-
"paths": {
6-
"@internal/helpers": [
7-
"../helpers/src"
8-
]
9-
}
10-
},
11-
"extends": "@tsconfig/node22/tsconfig.json",
2+
"compilerOptions": {},
3+
"extends": "../../tsconfig.base.json",
124
"include": [
135
"src/**/*",
146
"jest.config.ts"

internal/events/README.md

Lines changed: 192 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,213 @@
1-
# NHS Notify Schemas (Supplier API)
1+
# NHS Notify Supplier API Event Schemas
22

3-
This package contains schema definitions and type interfaces for the NHS Notify Supplier API event system.
3+
This internal package defines CloudEvents-compatible schemas (with Zod) for the Supplier API domain – currently focusing on Letter Status Change events. It provides:
44

5-
## Installation
5+
* A reusable CloudEvents envelope profile (`$EnvelopeProfile`)
6+
* Domain model schemas for letter status transitions (`$LetterStatus`, `$LetterStatusChange`)
7+
* Concrete per-status event schemas with strict `type`, `dataschema` URI and semantic version validation
8+
* Utilities to programmatically access all status change event schemas (`statusChangeEvents`)
69

7-
```bash
8-
npm install --save nhs-notify-schemas-supplier-api
10+
> NOTE: This package is private and published only to the internal GitHub Packages registry. Do not reference it externally; consume it within this mono‑repo or via internal pipelines.
11+
12+
---
13+
14+
## Directory Structure
15+
16+
```text
17+
src/
18+
domain/
19+
letter-status-change.ts # Domain model and status enum
20+
events/
21+
envelope-profile.ts # CloudEvents base envelope extensions & constraints
22+
letter-status-change-events.ts # Per status event schema generation
23+
cli/ # CLI scripts for bundling / codegen
24+
index.ts # (re-)exports (not shown above if generated later)
925
```
1026

11-
## Usage
27+
---
1228

13-
```typescript
14-
import { LetterStatusSchema, LetterStatusType } from 'nhs-notify-schemas-supplier-api';
29+
## Concepts
1530

16-
// Validate an incoming event
17-
const result = LetterStatusSchema.safeParse(incomingData);
18-
if (!result.success) {
19-
console.error('Invalid letter status event:', result.error);
20-
return;
21-
}
31+
### 1. Envelope Profile (`$EnvelopeProfile`)
32+
33+
> NB: this will be replaced with a common schema in future published in the nhs-notify-standards repo
34+
35+
Defines the constrained CloudEvents 1.0 envelope used across Notify. It enforces:
36+
37+
* `specversion` fixed to `1.0`
38+
* Reverse‑DNS `type` pattern starting `uk.nhs.notify.` with prohibited ambiguous verbs
39+
* Structured `source` and `subject` path formats (with additional subject shape rules for `/data-plane` sources)
40+
* Trace context (`traceparent`, optional `tracestate`)
41+
* Optional classification / regulation tags
42+
* Consistency rules (e.g. severity text ↔ number mapping)
43+
44+
### 2. Letter Status Domain
45+
46+
`letter-status-change.ts` introduces:
47+
48+
* `$LetterStatus` enumeration covering lifecycle states:
49+
`PENDING | ACCEPTED | REJECTED | PRINTED | ENCLOSED | CANCELLED | DISPATCHED | FAILED | RETURNED | DESTROYED | FORWARDED | DELIVERED`
50+
* `$LetterStatusChange` domain object, extending a `DomainBase('LetterStatusChange')` (see helpers package) with:
51+
* `domainId` (branded identifier)
52+
* `sourceSubject` – original resource subject
53+
* `status` – one of `$LetterStatus`
54+
* Optional `reasonCode`, `reasonText`
55+
56+
### 3. Per‑Status Event Schemas
2257

23-
// Use the validated data with full type safety
24-
const letterEvent: LetterStatusType = result.data;
25-
console.log(`Letter ${letterEvent.letterId} status: ${letterEvent.status}`);
58+
`letter-status-change-events.ts` programmatically creates a schema per status by extending `$EnvelopeProfile` and replacing `data` with the domain payload. Each schema enforces:
59+
60+
* `type = uk.nhs.notify.supplier-api.letter-status.<STATUS>.v1`
61+
* `dataschema` matches: `https://notify.nhs.uk/events/supplier-api/letter-status/<STATUS>/1.<minor>.<patch>.json`
62+
* `dataschemaversion` uses semantic version with major fixed to `1` (`1.x.y`)
63+
* `data.status` literal‑locked to the matching status
64+
65+
The export `statusChangeEvents` is a dictionary keyed by `letter-status.<STATUS>`.
66+
67+
---
68+
69+
## Installation (Internal)
70+
71+
Inside this mono‑repo other internal packages should depend on it by name:
72+
73+
```jsonc
74+
// package.json
75+
"dependencies": {
76+
"@nhsdigital/nhs-notify-event-schemas-supplier-api": "*"
77+
}
2678
```
2779

28-
## Available Schemas
80+
External `npm install` instructions are intentionally omitted (private package).
2981

30-
- `LetterStatusSchema`: For letter status update events
31-
- `LetterCreatedSchema`: For letter creation events
82+
---
3283

33-
## Development
84+
## Usage Examples
3485

35-
### Building
86+
### Validating a Raw Event
3687

37-
```bash
38-
npm run build
88+
```typescript
89+
import { statusChangeEvents } from '@nhsdigital/nhs-notify-event-schemas-supplier-api';
90+
91+
const schema = statusChangeEvents['letter-status.PRINTED'];
92+
const parsed = schema.safeParse(incomingEventJson);
93+
if (!parsed.success) {
94+
// handle validation failure (log / DLQ)
95+
console.error(parsed.error.format());
96+
} else {
97+
const evt = parsed.data; // strongly typed
98+
console.log(`Letter ${evt.data.domainId} moved to ${evt.data.status}`);
99+
}
39100
```
40101

41-
### Testing
102+
### Validating a generic letter-status.* event
42103

43-
```bash
44-
npm test
104+
```typescript
105+
```typescript
106+
import { $LetterStatusChangeEvent } from '@nhsdigital/nhs-notify-event-schemas-supplier-api';
107+
108+
function validateLetterStatusEvent(e: unknown) {
109+
const result = $LetterStatusChangeEvent.safeParse(e);
110+
if (!result.success) {
111+
// handle validation failure (log / DLQ)
112+
return { ok: false as const, error: result.error };
113+
}
114+
// event is strongly typed
115+
return { ok: true as const, event: result.data };
116+
}
45117
```
46118

47-
### Generating Schema Diagrams
119+
### Creating a New Letter Status Event Instance
48120

49-
```bash
50-
npm run generate-diagrams
121+
```typescript
122+
import { statusChangeEvents } from '@nhsdigital/nhs-notify-event-schemas-supplier-api';
123+
import { randomUUID } from 'crypto';
124+
125+
const status = 'ACCEPTED' as const;
126+
const schema = statusChangeEvents[`letter-status.${status}`];
127+
128+
const event = {
129+
specversion: '1.0',
130+
id: randomUUID(),
131+
source: '/data-plane/supplier-api',
132+
subject: 'customer/1b20f918-bb05-4c78-a4aa-5f6a3b8e0c91/letter/4a5a9cb5-1440-4a12-bd72-baa7cfecd111',
133+
type: 'uk.nhs.notify.supplier-api.letter-status.ACCEPTED.v1',
134+
time: new Date().toISOString(),
135+
dataschema: 'https://notify.nhs.uk/events/supplier-api/letter-status/ACCEPTED/1.0.0.json',
136+
dataschemaversion: '1.0.0',
137+
data: {
138+
domainId: 'abc123',
139+
sourceSubject: 'customer/.../letter/...',
140+
status: 'ACCEPTED',
141+
},
142+
traceparent: '00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01',
143+
recordedtime: new Date().toISOString(),
144+
severitytext: 'INFO',
145+
severitynumber: 2,
146+
};
147+
148+
schema.parse(event); // throws if invalid
51149
```
150+
151+
---
152+
153+
## Versioning & Dataschema
154+
155+
* Major version locked at `1` for current lineage.
156+
* Minor + patch increments should reflect additive / backwards compatible changes to the data payload.
157+
* `dataschema` URIs must be updated in lockstep with `dataschemaversion` when publishing new schema variants.
158+
159+
Automated generation tasks (below) assist bundling and JSON Schema emission.
160+
161+
---
162+
163+
## Scripts
164+
165+
| Script | Purpose |
166+
|--------|---------|
167+
| `npm run build` | TypeScript compile to `dist/` |
168+
| `npm test` / `npm run test:unit` | Run Jest unit tests |
169+
| `npm run gen:asyncapi` | Bundle AsyncAPI sources into `dist/asyncapi` |
170+
| `npm run gen:jsonschema` | Emit JSON Schemas derived from Zod definitions |
171+
| `npm run lint` | Lint code + schema (Spectral) |
172+
| `npm run lint:fix` | Auto-fix lint issues |
173+
174+
Execution order helpers:
175+
176+
* `prebuild` ensures a clean `dist` and generates asyncapi bundle
177+
* `prelint:schema` generates JSON prior to Spectral validation
178+
179+
---
180+
181+
## Adding New Event Types (Future)
182+
183+
1. Extend the domain model under `src/domain/`
184+
2. Add a generator similar to `letter-status-change-events.ts`
185+
3. Ensure `type` naming: `uk.nhs.notify.supplier-api.<area>.<action>.v1`
186+
4. Provide deterministic `dataschema` pattern with semantic versioning
187+
5. Export via `src/index.ts`
188+
6. Add unit tests & update documentation
189+
190+
---
191+
192+
## Validation Philosophy
193+
194+
Rules aim for early rejection of:
195+
196+
* Ambiguous or post-hoc semantic verbs ("completed", "updated", etc.) in event `type`
197+
* Poorly structured routing metadata (`source`, `subject`)
198+
* Inconsistent severity pairings
199+
* Non-conformant trace context
200+
201+
This reduces downstream consumer ambiguity and improves observability correlation.
202+
203+
---
204+
205+
## License
206+
207+
MIT (internal usage only)
208+
209+
---
210+
211+
## Support
212+
213+
Raise questions via the repository discussions or internal channel referencing the `events` package.

internal/events/schemas/domain/letter-status-change.schema.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
"type": "object",
33
"properties": {
44
"domainId": {
5+
"title": "LetterStatusChange ID",
6+
"description": "Unique identifier for the LetterStatusChange",
7+
"examples": [
8+
"1y3q9v1zzzz"
9+
],
510
"type": "string"
611
},
712
"sourceSubject": {

internal/events/schemas/events/letter-status.ACCEPTED.schema.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@
8181
"type": "object",
8282
"properties": {
8383
"domainId": {
84+
"title": "LetterStatusChange ID",
85+
"description": "Unique identifier for the LetterStatusChange",
86+
"examples": [
87+
"1y3q9v1zzzz"
88+
],
8489
"type": "string"
8590
},
8691
"sourceSubject": {

0 commit comments

Comments
 (0)