@molecule/api-resource-medication 1.0.0 → 1.0.2

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.
Files changed (2) hide show
  1. package/README.md +402 -0
  2. package/package.json +14 -7
package/README.md ADDED
@@ -0,0 +1,402 @@
1
+ <!--
2
+ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
+ Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
+ Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
+ To change this document, edit the module-level JSDoc in src/index.ts.
6
+ Generated: 2026-08-04T01:49:15.190Z
7
+ -->
8
+
9
+ # @molecule/api-resource-medication
10
+
11
+ > **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
12
+ > It is written to be read by coding agents as much as by people, and is generated from this
13
+ > package's source — edit `src/index.ts` JSDoc, not this file.
14
+
15
+ `@molecule/api-resource-medication` — medication tracking with
16
+ dosing schedule + adherence log + adherence-rate calc.
17
+
18
+ Owner-scoped: every medication and dose log belongs to the authenticated
19
+ user. Medications carry dosage, `frequency` (e.g. `daily`, `twice_daily`),
20
+ `times_of_day`, and active date range; dose logs record
21
+ taken/skipped/late/missed and `adherenceRate` aggregates them over a date
22
+ range.
23
+
24
+ ## Quick Start
25
+
26
+ ```ts
27
+ import { createMedicationRouter } from '@molecule/api-resource-medication'
28
+ app.use('/medications', createMedicationRouter())
29
+ // GET / · POST / · GET /adherence?from=…&to=… · GET|PUT|DELETE /:id
30
+ // GET /:id/logs · POST /:id/logs
31
+ ```
32
+
33
+ ```ts
34
+ import { adherenceRate, createMedicationForOwner, logDose } from '@molecule/api-resource-medication'
35
+
36
+ const med = await createMedicationForOwner(userId, {
37
+ name: 'Metformin',
38
+ dosage: '500 mg',
39
+ frequency: 'twice_daily',
40
+ })
41
+ await logDose(med.id, userId, { status: 'taken' })
42
+ const { rate } = await adherenceRate(userId, '2026-01-01', '2026-01-31')
43
+ ```
44
+
45
+ ## Type
46
+
47
+ `resource`
48
+
49
+ ## Installation
50
+
51
+ ```bash
52
+ npm install @molecule/api-resource-medication @molecule/api-bonds-default-express @molecule/api-database @molecule/api-i18n @molecule/api-middleware-validation express zod
53
+ npm install -D @types/express
54
+ ```
55
+
56
+ ## API
57
+
58
+ ### Interfaces
59
+
60
+ #### `MedicationLogRow`
61
+
62
+ Database row shape for a medication intake log entry.
63
+
64
+ ```typescript
65
+ interface MedicationLogRow {
66
+ id: string
67
+ medication_id: string
68
+ owner_id: string
69
+ taken_at: string | Date
70
+ status: 'taken' | 'skipped' | 'late' | 'missed'
71
+ notes: string | null
72
+ created_at: string | Date
73
+ }
74
+ ```
75
+
76
+ #### `MedicationRow`
77
+
78
+ Database row shape for a medication record.
79
+
80
+ ```typescript
81
+ interface MedicationRow {
82
+ id: string
83
+ owner_id: string
84
+ name: string
85
+ generic_name: string | null
86
+ dosage: string
87
+ unit: string | null
88
+ frequency: MedicationFrequency
89
+ times_of_day: string[]
90
+ start_date: string | Date | null
91
+ end_date: string | Date | null
92
+ notes: string | null
93
+ is_active: boolean
94
+ created_at: string | Date
95
+ updated_at: string | Date
96
+ }
97
+ ```
98
+
99
+ ### Types
100
+
101
+ #### `MedicationFrequency`
102
+
103
+ Describes how often a medication is taken (e.g. daily, twice daily, as needed).
104
+
105
+ ```typescript
106
+ type MedicationFrequency =
107
+ | 'once'
108
+ | 'daily'
109
+ | 'twice_daily'
110
+ | 'three_times_daily'
111
+ | 'four_times_daily'
112
+ | 'as_needed'
113
+ | 'weekly'
114
+ | 'custom'
115
+ ```
116
+
117
+ ### Functions
118
+
119
+ #### `adherenceRate(ownerId, from, to)`
120
+
121
+ Adherence summary: percentage of `taken` logs out of all logs in window.
122
+
123
+ ```typescript
124
+ function adherenceRate(
125
+ ownerId: string,
126
+ from: string,
127
+ to: string,
128
+ ): Promise<{ taken: number; total: number; rate: number }>
129
+ ```
130
+
131
+ #### `createMedicationForOwner(ownerId, data)`
132
+
133
+ Creates a new medication record owned by the given owner and returns the persisted row.
134
+
135
+ ```typescript
136
+ function createMedicationForOwner(
137
+ ownerId: string,
138
+ data: {
139
+ name: string
140
+ generic_name?: string | null
141
+ dosage: string
142
+ unit?: string | null
143
+ frequency?: MedicationFrequency
144
+ times_of_day?: string[]
145
+ start_date?: string | null
146
+ end_date?: string | null
147
+ notes?: string | null
148
+ },
149
+ ): Promise<MedicationRow>
150
+ ```
151
+
152
+ #### `createMedicationRouter()`
153
+
154
+ Creates and returns the Express router for medication CRUD and dose-logging endpoints.
155
+
156
+ ```typescript
157
+ function createMedicationRouter(): Router
158
+ ```
159
+
160
+ #### `deleteMedicationForOwner(medicationId, ownerId)`
161
+
162
+ Deletes a medication by ID, returning true on success or false if not found or not owned.
163
+
164
+ ```typescript
165
+ function deleteMedicationForOwner(medicationId: string, ownerId: string): Promise<boolean>
166
+ ```
167
+
168
+ #### `getMedicationForOwner(medicationId, ownerId)`
169
+
170
+ Fetches a single medication by ID, returning null if not found or not owned by the given owner.
171
+
172
+ ```typescript
173
+ function getMedicationForOwner(medicationId: string, ownerId: string): Promise<MedicationRow | null>
174
+ ```
175
+
176
+ #### `listLogs(medicationId, ownerId, opts?)`
177
+
178
+ Returns dose logs for a medication within an optional time window, or null if the medication is not found or not owned.
179
+
180
+ ```typescript
181
+ function listLogs(
182
+ medicationId: string,
183
+ ownerId: string,
184
+ opts?: { from?: string; to?: string; limit?: number },
185
+ ): Promise<MedicationLogRow[] | null>
186
+ ```
187
+
188
+ #### `listMedicationsForOwner(ownerId, opts?)`
189
+
190
+ Returns all medications belonging to the given owner, optionally including inactive ones.
191
+
192
+ ```typescript
193
+ function listMedicationsForOwner(
194
+ ownerId: string,
195
+ opts?: { include_inactive?: boolean },
196
+ ): Promise<MedicationRow[]>
197
+ ```
198
+
199
+ #### `logDose(medicationId, ownerId, data)`
200
+
201
+ Log a dose. If `status` not provided, infers from `taken_at` vs scheduled times.
202
+
203
+ ```typescript
204
+ function logDose(
205
+ medicationId: string,
206
+ ownerId: string,
207
+ data: {
208
+ taken_at?: string
209
+ status?: 'taken' | 'skipped' | 'late' | 'missed'
210
+ notes?: string | null
211
+ },
212
+ ): Promise<MedicationLogRow | null>
213
+ ```
214
+
215
+ #### `updateMedicationForOwner(medicationId, ownerId, patch)`
216
+
217
+ Applies a partial patch to a medication, returning the updated row or null if not found or not owned.
218
+
219
+ ```typescript
220
+ function updateMedicationForOwner(
221
+ medicationId: string,
222
+ ownerId: string,
223
+ patch: Partial<MedicationRow>,
224
+ ): Promise<MedicationRow | null>
225
+ ```
226
+
227
+ ### Constants
228
+
229
+ #### `LOG_STATUSES`
230
+
231
+ Valid status values for a medication dose log entry.
232
+
233
+ ```typescript
234
+ const LOG_STATUSES: readonly ['taken', 'skipped', 'late', 'missed']
235
+ ```
236
+
237
+ #### `logCreateSchema`
238
+
239
+ Zod schema for validating medication dose log creation request payloads.
240
+
241
+ ```typescript
242
+ const logCreateSchema: z.ZodObject<
243
+ {
244
+ taken_at: z.ZodOptional<z.ZodString>
245
+ status: z.ZodOptional<
246
+ z.ZodEnum<{ taken: 'taken'; skipped: 'skipped'; late: 'late'; missed: 'missed' }>
247
+ >
248
+ notes: z.ZodOptional<z.ZodNullable<z.ZodString>>
249
+ },
250
+ z.core.$strip
251
+ >
252
+ ```
253
+
254
+ #### `MEDICATION_FREQUENCIES`
255
+
256
+ Valid dosing frequency options for a medication schedule.
257
+
258
+ ```typescript
259
+ const MEDICATION_FREQUENCIES: readonly [
260
+ 'once',
261
+ 'daily',
262
+ 'twice_daily',
263
+ 'three_times_daily',
264
+ 'four_times_daily',
265
+ 'as_needed',
266
+ 'weekly',
267
+ 'custom',
268
+ ]
269
+ ```
270
+
271
+ #### `medicationCreateSchema`
272
+
273
+ Zod schema for validating medication creation request payloads.
274
+
275
+ ```typescript
276
+ const medicationCreateSchema: z.ZodObject<
277
+ {
278
+ name: z.ZodString
279
+ generic_name: z.ZodOptional<z.ZodNullable<z.ZodString>>
280
+ dosage: z.ZodString
281
+ unit: z.ZodOptional<z.ZodNullable<z.ZodString>>
282
+ frequency: z.ZodOptional<
283
+ z.ZodEnum<{
284
+ once: 'once'
285
+ daily: 'daily'
286
+ twice_daily: 'twice_daily'
287
+ three_times_daily: 'three_times_daily'
288
+ four_times_daily: 'four_times_daily'
289
+ as_needed: 'as_needed'
290
+ weekly: 'weekly'
291
+ custom: 'custom'
292
+ }>
293
+ >
294
+ times_of_day: z.ZodOptional<z.ZodArray<z.ZodString>>
295
+ start_date: z.ZodOptional<z.ZodNullable<z.ZodString>>
296
+ end_date: z.ZodOptional<z.ZodNullable<z.ZodString>>
297
+ notes: z.ZodOptional<z.ZodNullable<z.ZodString>>
298
+ },
299
+ z.core.$strip
300
+ >
301
+ ```
302
+
303
+ #### `medicationUpdateSchema`
304
+
305
+ Zod schema for validating medication update request payloads; all create fields become optional and `is_active` is added.
306
+
307
+ ```typescript
308
+ const medicationUpdateSchema: z.ZodObject<
309
+ {
310
+ name: z.ZodOptional<z.ZodString>
311
+ generic_name: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>
312
+ dosage: z.ZodOptional<z.ZodString>
313
+ unit: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>
314
+ frequency: z.ZodOptional<
315
+ z.ZodOptional<
316
+ z.ZodEnum<{
317
+ once: 'once'
318
+ daily: 'daily'
319
+ twice_daily: 'twice_daily'
320
+ three_times_daily: 'three_times_daily'
321
+ four_times_daily: 'four_times_daily'
322
+ as_needed: 'as_needed'
323
+ weekly: 'weekly'
324
+ custom: 'custom'
325
+ }>
326
+ >
327
+ >
328
+ times_of_day: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>
329
+ start_date: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>
330
+ end_date: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>
331
+ notes: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>
332
+ is_active: z.ZodOptional<z.ZodBoolean>
333
+ },
334
+ z.core.$strip
335
+ >
336
+ ```
337
+
338
+ ## Injection Notes
339
+
340
+ ### Requirements
341
+
342
+ Peer dependencies:
343
+
344
+ - `@molecule/api-bonds-default-express` ^1.0.1
345
+ - `@molecule/api-database` ^1.0.1
346
+ - `@molecule/api-i18n` ^1.0.1
347
+ - `@molecule/api-middleware-validation` ^1.0.1
348
+ - `express` ^5.0.0
349
+ - `zod` ^4.0.0
350
+
351
+ ### Runtime Dependencies
352
+
353
+ - `@molecule/api-bonds-default-express`
354
+ - `@molecule/api-database`
355
+ - `@molecule/api-i18n`
356
+ - `@molecule/api-middleware-validation`
357
+ - `express`
358
+ - `zod`
359
+
360
+ Tables: `src/__setup__/medications.sql` creates `medications` +
361
+ `medication_logs`. An mlcl-scaffolded API replays `__setup__/*.sql`
362
+ automatically on migrate; anywhere else run it once — nothing at runtime
363
+ creates them.
364
+
365
+ The router does not authenticate — it reads the caller from
366
+ `res.locals.session` (populated by your global auth middleware) and 401s
367
+ without a session. All service functions are `…ForOwner(…, ownerId)` and
368
+ return `null` for rows the caller doesn't own — always pass the
369
+ AUTHENTICATED user's id, never a client-sent one.
370
+
371
+ ## E2E Tests
372
+
373
+ Integration checklist — drive the real UI (live preview, no mocks), adapt
374
+ each item to this app's actual screens/flows, and check every box off one by
375
+ one. This is HEALTH data: a box you can't check is a correctness or privacy
376
+ bug to fix, never a skip:
377
+
378
+ - [ ] Adding a medication persists its real fields — `name`, `dosage`,
379
+ `frequency`, `times_of_day` — and the med then appears in the user's list
380
+ (`GET /`) carrying those exact values, not a truncated or defaulted copy.
381
+ - [ ] The schedule matches the frequency: a `twice_daily` med carries two
382
+ `times_of_day` entries and the UI shows two dose slots for the day — not one
383
+ and not three; a `daily` med shows exactly one. (Nothing derives the times
384
+ from `frequency` automatically, so a mismatch is a real bug to catch here.)
385
+ - [ ] Logging a dose records it against the right medication and time:
386
+ `POST /:id/logs` with `status: 'taken'` (and its `taken_at`) then shows in
387
+ `GET /:id/logs`, and the adherence figure (`GET /adherence`) moves.
388
+ - [ ] A skipped or missed dose is reflected HONESTLY — logged with
389
+ `status: 'missed'`/`'skipped'` it counts toward `total` but NOT `taken`, so
390
+ it lowers the adherence rate; it is never silently counted as taken (the
391
+ default status is `taken`, so a miss must be logged as a miss, not omitted).
392
+ - [ ] CORRECTNESS — adherence is a true ratio of logged doses: a brand-new
393
+ med with zero logged doses reads 0% (`rate` 0, `total` 0), never 100%; a
394
+ partial day (some doses logged, some not yet) is never shown as complete.
395
+ - [ ] If the app tracks refills/supply on top of this resource (the core does
396
+ not model one), recording a fill decrements the refills-remaining count and
397
+ a low-supply threshold raises a visible refill flag — verify both live.
398
+ - [ ] PRIVACY/AUTHORIZATION — medication data is strictly per-user: signed in
399
+ as user B, guessing user A's medication id on `GET /:id`, `PUT /:id`,
400
+ `DELETE /:id`, or `/:id/logs` returns 404 (owner-scoped), never A's row; an
401
+ unauthenticated request 401s. Confirm PHI (name, dosage, notes) is never
402
+ written to server logs in the clear.
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@molecule/api-resource-medication",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Medication tracking + dosing schedule + adherence log",
5
+ "homepage": "https://www.molecule.dev/packages/api-resource-medication",
5
6
  "type": "module",
6
7
  "main": "dist/index.js",
7
8
  "types": "dist/index.d.ts",
@@ -17,7 +18,8 @@
17
18
  }
18
19
  },
19
20
  "files": [
20
- "dist"
21
+ "dist",
22
+ "README.md"
21
23
  ],
22
24
  "keywords": [
23
25
  "molecule",
@@ -27,19 +29,24 @@
27
29
  "resource"
28
30
  ],
29
31
  "license": "Apache-2.0",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/molecule-dev/molecule.git",
35
+ "directory": "packages/api/resources/medication"
36
+ },
30
37
  "devDependencies": {
31
38
  "@types/express": "5.0.6",
32
39
  "@types/node": "26.1.2",
33
40
  "express": "5.2.1",
34
41
  "typescript": "6.0.3",
35
- "vitest": "4.1.10",
42
+ "vitest": "4.1.11",
36
43
  "zod": "4.4.3"
37
44
  },
38
45
  "peerDependencies": {
39
- "@molecule/api-bonds-default-express": "^1.0.0",
40
- "@molecule/api-database": "^1.0.0",
41
- "@molecule/api-i18n": "^1.0.0",
42
- "@molecule/api-middleware-validation": "^1.0.0",
46
+ "@molecule/api-bonds-default-express": "^1.0.1",
47
+ "@molecule/api-database": "^1.0.1",
48
+ "@molecule/api-i18n": "^1.0.1",
49
+ "@molecule/api-middleware-validation": "^1.0.1",
43
50
  "express": "^5.0.0",
44
51
  "zod": "^4.0.0"
45
52
  }