|
| 1 | +# NHS Notify Helpers (Internal) |
| 2 | + |
| 3 | +Utility primitives shared across internal Supplier API packages. These helpers provide: |
| 4 | + |
| 5 | +* Lightweight domain modelling conventions (branded IDs) |
| 6 | +* Type-safe relationship references |
| 7 | +* Common scalar validators (Version, Environment) |
| 8 | + |
| 9 | +The goal is to keep domain packages small and consistent – not to become a general utility grab‑bag. |
| 10 | + |
| 11 | +--- |
| 12 | + |
| 13 | +## Exports Overview |
| 14 | + |
| 15 | +| Export | Kind | Purpose | |
| 16 | +|--------|------|---------| |
| 17 | +| `DomainBase(name)` | Function | Builds a base Zod object with a branded `domainId` for the named aggregate/entity | |
| 18 | +| `idRef(schema, idField?, entityName?)` | Function | Creates a reference field mirroring the ID field type of another schema | |
| 19 | +| `$Version` / `Version` | Zod schema / Type | Semantic version (major.minor.patch) branded as `Version` (from `version.ts`) | |
| 20 | +| `$Environment` | Zod schema | Environment identifier (e.g. `dev`, `int`, `prod`) (from `environment.ts`) | |
| 21 | + |
| 22 | +--- |
| 23 | + |
| 24 | +## Installation (Internal Only) |
| 25 | + |
| 26 | +Other workspaces in the mono‑repo reference the helpers via package name (preferred) or relative path. Example: |
| 27 | + |
| 28 | +```jsonc |
| 29 | +// package.json |
| 30 | +"dependencies": { |
| 31 | + "@internal/helpers": "*" |
| 32 | +} |
| 33 | +``` |
| 34 | + |
| 35 | +No external publication is intended. |
| 36 | + |
| 37 | +--- |
| 38 | + |
| 39 | +## Usage |
| 40 | + |
| 41 | +### 1. Defining a Domain Model Base |
| 42 | + |
| 43 | +```typescript |
| 44 | +import { DomainBase } from '@internal/helpers'; |
| 45 | +import { z } from 'zod'; |
| 46 | + |
| 47 | +// Creates { domainId: BrandedString<'Letter'> } |
| 48 | +const $Letter = DomainBase('Letter').extend({ |
| 49 | + createdAt: z.string().datetime(), |
| 50 | +}); |
| 51 | +``` |
| 52 | + |
| 53 | +The branded ID prevents accidental mixing of IDs across entity types while remaining a plain string at runtime. |
| 54 | + |
| 55 | +### 2. Referencing Another Entity |
| 56 | + |
| 57 | +```typescript |
| 58 | +import { idRef, DomainBase } from '@internal/helpers'; |
| 59 | +import { z } from 'zod'; |
| 60 | + |
| 61 | +const $Customer = DomainBase('Customer'); |
| 62 | +const $Order = DomainBase('Order').extend({ |
| 63 | + customerId: idRef($Customer), // Inherits type & metadata from Customer.domainId |
| 64 | +}); |
| 65 | +``` |
| 66 | + |
| 67 | +You can supply a custom ID field name if the target schema uses something other than `domainId`. |
| 68 | + |
| 69 | +### 3. Version & Environment Scalars |
| 70 | + |
| 71 | +```typescript |
| 72 | +import { $Version, $Environment } from '@internal/helpers'; |
| 73 | + |
| 74 | +const cfg = { |
| 75 | + version: $Version.parse('1.2.3'), |
| 76 | + environment: $Environment.parse('dev'), |
| 77 | +}; |
| 78 | +``` |
| 79 | + |
| 80 | +--- |
| 81 | + |
| 82 | +## API Details |
| 83 | + |
| 84 | +### `DomainBase(name: string)` |
| 85 | + |
| 86 | +Returns a Zod object: `{ domainId: BrandedString<name> }` with metadata describing the ID. |
| 87 | + |
| 88 | +### `idRef(schema, idFieldName = 'domainId', entityName?)` |
| 89 | + |
| 90 | +Clones the ID field schema from `schema.shape[idFieldName]`, re‑annotating metadata to describe the target entity reference. Throws if the field is absent. |
| 91 | + |
| 92 | +### `$Version` |
| 93 | + |
| 94 | +Defined in `src/version.ts`. |
| 95 | + |
| 96 | +Regex: `^\d+\.\d+\.\d+$` – no pre-release/build metadata (keep simple for now). Consider extending when semver nuance is required. |
| 97 | + |
| 98 | +### `$Environment` |
| 99 | + |
| 100 | +Defined in `src/environment.ts`. |
| 101 | + |
| 102 | +String with metadata; callers typically restrict further via union if they need a constrained set. |
| 103 | + |
| 104 | +--- |
| 105 | + |
| 106 | +## Design Notes |
| 107 | + |
| 108 | +* Zod is used for runtime validation + static type inference; no dual maintenance. |
| 109 | +* Branded string IDs provide nominal typing without runtime overhead. |
| 110 | +* Helpers avoid opinionated persistence or transport logic – they stay schema-focused. |
| 111 | + |
| 112 | +--- |
| 113 | + |
| 114 | +## Scripts |
| 115 | + |
| 116 | +| Script | Purpose | |
| 117 | +|--------|---------| |
| 118 | +| `npm run build` | Compile TypeScript to `dist/` | |
| 119 | +| `npm test` | Run unit tests (Jest) | |
| 120 | +| `npm run lint` | Lint sources | |
| 121 | +| `npm run lint:fix` | Auto-fix lint issues | |
| 122 | +| `npm run typecheck` | Type-only compile (no emit) | |
| 123 | + |
| 124 | +--- |
| 125 | + |
| 126 | +## Conventions & Guidelines |
| 127 | + |
| 128 | +1. Prefer composing small schemas over adding conditional logic inside helpers. |
| 129 | +2. Avoid adding business logic – keep to typing / structural utilities. |
| 130 | +3. If adding a new scalar helper, provide: regex (if applicable), examples, and rationale. |
| 131 | +4. Update this README when exports change. |
| 132 | + |
| 133 | +--- |
| 134 | + |
| 135 | +## Extending |
| 136 | + |
| 137 | +When introducing a new shared primitive: |
| 138 | + |
| 139 | +1. Implement under `src/` with clear JSDoc. |
| 140 | +2. Export from `src/index.ts`. |
| 141 | +3. Add a focused test in `__tests__` (create folder if absent). |
| 142 | +4. Document in the Exports Overview table. |
| 143 | + |
| 144 | +--- |
| 145 | + |
| 146 | +## License |
| 147 | + |
| 148 | +MIT (internal usage only) |
| 149 | + |
| 150 | +--- |
| 151 | + |
| 152 | +## Support |
| 153 | + |
| 154 | +Use internal channel or repository discussions referencing the `helpers` package. |
0 commit comments