@tbs-fitness/contract 0.11.0 → 0.12.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/README.md +23 -10
- package/dist/cjs/enums/index.d.ts +31 -0
- package/dist/cjs/enums/index.js +37 -1
- package/dist/cjs/meals/index.d.ts +1 -0
- package/dist/cjs/meals/index.js +17 -0
- package/dist/cjs/meals/meal.dto.d.ts +143 -0
- package/dist/cjs/meals/meal.dto.js +107 -0
- package/dist/cjs/nutrition-plans/index.d.ts +2 -0
- package/dist/cjs/nutrition-plans/index.js +18 -0
- package/dist/cjs/nutrition-plans/nutrition-math.d.ts +115 -0
- package/dist/cjs/nutrition-plans/nutrition-math.js +278 -0
- package/dist/cjs/nutrition-plans/nutrition-plan.dto.d.ts +914 -0
- package/dist/cjs/nutrition-plans/nutrition-plan.dto.js +289 -0
- package/dist/esm/enums/index.d.ts +31 -0
- package/dist/esm/enums/index.js +36 -0
- package/dist/esm/meals/index.d.ts +1 -0
- package/dist/esm/meals/index.js +1 -0
- package/dist/esm/meals/meal.dto.d.ts +143 -0
- package/dist/esm/meals/meal.dto.js +103 -0
- package/dist/esm/nutrition-plans/index.d.ts +2 -0
- package/dist/esm/nutrition-plans/index.js +2 -0
- package/dist/esm/nutrition-plans/nutrition-math.d.ts +115 -0
- package/dist/esm/nutrition-plans/nutrition-math.js +261 -0
- package/dist/esm/nutrition-plans/nutrition-plan.dto.d.ts +914 -0
- package/dist/esm/nutrition-plans/nutrition-plan.dto.js +286 -0
- package/package.json +32 -12
package/README.md
CHANGED
|
@@ -6,16 +6,18 @@ It is published as `@tbs-fitness/contract`. The package holds definitions only:
|
|
|
6
6
|
|
|
7
7
|
## What is in it
|
|
8
8
|
|
|
9
|
-
| Import
|
|
10
|
-
|
|
|
11
|
-
| `@tbs-fitness/contract/enums`
|
|
12
|
-
| `@tbs-fitness/contract/common`
|
|
13
|
-
| `@tbs-fitness/contract/auth`
|
|
14
|
-
| `@tbs-fitness/contract/client`
|
|
15
|
-
| `@tbs-fitness/contract/coach`
|
|
16
|
-
| `@tbs-fitness/contract/audit`
|
|
17
|
-
| `@tbs-fitness/contract/feature-flags`
|
|
18
|
-
| `@tbs-fitness/contract/food`
|
|
9
|
+
| Import | Contents |
|
|
10
|
+
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| `@tbs-fitness/contract/enums` | `ROLES`, `PERMISSIONS`, `CSRF_COOKIE_NAMES`, `CSRF_HEADER`, `ACCOUNT_STATUSES`, `TOKEN_TRANSPORTS`, `AUDIT_OUTCOMES`, `APP_ENVIRONMENTS`, `FEATURE_FLAG_RULE_TYPES` and their types |
|
|
12
|
+
| `@tbs-fitness/contract/common` | Pagination, response envelopes (`{ data }`, `{ data, meta }`, `{ error }`), `emailSchema`, `phoneSchema`, `safeTextSchema` |
|
|
13
|
+
| `@tbs-fitness/contract/auth` | Login, sessions, password changes and resets, account administration |
|
|
14
|
+
| `@tbs-fitness/contract/client` | Client sign-up, one-time code verification and resend, client sign-in and profile, password recovery |
|
|
15
|
+
| `@tbs-fitness/contract/coach` | Coach sign-up, one-time code verification and resend, coach sign-in and profile, password recovery |
|
|
16
|
+
| `@tbs-fitness/contract/audit` | The audit log query and response |
|
|
17
|
+
| `@tbs-fitness/contract/feature-flags` | Feature flag requests and responses |
|
|
18
|
+
| `@tbs-fitness/contract/food` | Food library, metadata, CSV/XLSX import, client food suggestions and their review, and the shared nutrition helpers |
|
|
19
|
+
| `@tbs-fitness/contract/meals` | The meal library: meals of library foods with servings, and their totals |
|
|
20
|
+
| `@tbs-fitness/contract/nutrition-plans` | Nutrition plans: drafts, publishing, a client's active plan, the Snapshot step, and the builder's shared sums and publish rules |
|
|
19
21
|
|
|
20
22
|
Each domain has its own entry point, so names only need to be unique within a domain. `auditLogResponseSchema`, for example, exists in both `/audit` and `/feature-flags`.
|
|
21
23
|
|
|
@@ -43,6 +45,17 @@ const result = createFlagSchema.safeParse(input); // The same rules as the API.
|
|
|
43
45
|
|
|
44
46
|
Requests have two types. Clients use `*Input`, where fields with a default are optional. The backend uses `*Body`, which is the result after validation. List queries come from the URL as strings, so they have no `*Input`: use `Partial<PaginationQuery>` or `Partial<AuditLogQuery>`.
|
|
45
47
|
|
|
48
|
+
## Version 0.12.0
|
|
49
|
+
|
|
50
|
+
The meal library and nutrition plans for the Nutrition Plan Builder. Additive; consumers install `^0.12.0` explicitly.
|
|
51
|
+
|
|
52
|
+
- New `/meals`: `createMealSchema`, `updateMealSchema` (with `version`), `mealListQuerySchema`, `mealResponseSchema` (items with per-serving values and totals), `foodPortionSchema`, `servingsSchema` (0.5 to 10 in half steps), `macroTotalsSchema`, `normalizeMealName`.
|
|
53
|
+
- New `/nutrition-plans`:
|
|
54
|
+
- Requests: `createNutritionPlanSchema` (optional `fromPlanId`), `saveNutritionPlanSchema` (the whole plan with `version`; a half-built plan is valid), `publishNutritionPlanSchema` (`version`, optional `startsOn`), `nutritionPlanListQuerySchema`, `NUTRITION_PLAN_LIMITS`.
|
|
55
|
+
- Responses: `nutritionPlanResponseSchema` (with `dailyCalories`, `targets`, `dayTotals` and `publishIssues`), `currentNutritionPlansResponseSchema`, `nutritionPlanSummarySchema`, `clientNutritionPlanResponseSchema`, `nutritionPlanSnapshotResponseSchema`.
|
|
56
|
+
- Sums and rules shared with the builder: `NUTRITION_GUIDELINES`, `guidelineForGoalIcon`, `dailyCalories`, `macroTargets`, `recommendedWaterMl`, `defaultSlots`, `macroKcal`, `slotTargetKcal`, `addTotals`, `scaleTotals`, and `nutritionPlanPublishIssues` with `PUBLISH_ISSUE_CODES`.
|
|
57
|
+
- `/enums`: `MEAL_SORT_FIELDS`, `NUTRITION_PLAN_STATUSES`, `PLAN_SLOT_KINDS`, `PLAN_PHASES`, `PLAN_BLOCK_WEEKS`, `CHEAT_MEAL_PERIODS`, `NUTRITION_GUIDELINE_IDS`, and the permissions `meals:read`, `meals:create`, `meals:update`, `meals:delete`, `nutrition_plans:read`, `nutrition_plans:write`, `nutrition_plans:publish` and `nutrition_plans:read:self`.
|
|
58
|
+
|
|
46
59
|
## Version 0.11.0
|
|
47
60
|
|
|
48
61
|
Client onboarding and generic documents. Additive; consumers install `^0.11.0` explicitly.
|
|
@@ -48,6 +48,14 @@ export declare const PERMISSIONS: {
|
|
|
48
48
|
readonly documentsReadSelf: "documents:read:self";
|
|
49
49
|
readonly documentsReadAssigned: "documents:read:assigned";
|
|
50
50
|
readonly documentsReadAny: "documents:read:any";
|
|
51
|
+
readonly mealsRead: "meals:read";
|
|
52
|
+
readonly mealsCreate: "meals:create";
|
|
53
|
+
readonly mealsUpdate: "meals:update";
|
|
54
|
+
readonly mealsDelete: "meals:delete";
|
|
55
|
+
readonly nutritionPlansRead: "nutrition_plans:read";
|
|
56
|
+
readonly nutritionPlansWrite: "nutrition_plans:write";
|
|
57
|
+
readonly nutritionPlansPublish: "nutrition_plans:publish";
|
|
58
|
+
readonly nutritionPlansReadSelf: "nutrition_plans:read:self";
|
|
51
59
|
};
|
|
52
60
|
export type Permission = (typeof PERMISSIONS)[keyof typeof PERMISSIONS];
|
|
53
61
|
/**
|
|
@@ -201,3 +209,26 @@ export declare const HEIGHT_UNITS: readonly ["CM", "FT"];
|
|
|
201
209
|
export type HeightUnit = (typeof HEIGHT_UNITS)[number];
|
|
202
210
|
export declare const WEIGHT_UNITS: readonly ["KG", "LB"];
|
|
203
211
|
export type WeightUnit = (typeof WEIGHT_UNITS)[number];
|
|
212
|
+
export declare const MEAL_SORT_FIELDS: readonly ["name", "updatedAt"];
|
|
213
|
+
export type MealSortField = (typeof MEAL_SORT_FIELDS)[number];
|
|
214
|
+
/**
|
|
215
|
+
* A plan is built as a `DRAFT`, becomes `ACTIVE` when published, and is
|
|
216
|
+
* `ARCHIVED` when a newer plan replaces it. A client has at most one of each
|
|
217
|
+
* of the first two.
|
|
218
|
+
*/
|
|
219
|
+
export declare const NUTRITION_PLAN_STATUSES: readonly ["DRAFT", "ACTIVE", "ARCHIVED"];
|
|
220
|
+
export type NutritionPlanStatus = (typeof NUTRITION_PLAN_STATUSES)[number];
|
|
221
|
+
export declare const PLAN_SLOT_KINDS: readonly ["MEAL", "SHAKE"];
|
|
222
|
+
export type PlanSlotKind = (typeof PLAN_SLOT_KINDS)[number];
|
|
223
|
+
/** The stretch of the training block the calorie base is set for. */
|
|
224
|
+
export declare const PLAN_PHASES: readonly ["WEEKS_1_2", "WEEKS_3_4", "WEEKS_5_6", "WEEKS_7_8"];
|
|
225
|
+
export type PlanPhase = (typeof PLAN_PHASES)[number];
|
|
226
|
+
/** How long a published plan runs, in weeks. */
|
|
227
|
+
export declare const PLAN_BLOCK_WEEKS: readonly [8, 12, 16];
|
|
228
|
+
export type PlanBlockWeeks = (typeof PLAN_BLOCK_WEEKS)[number];
|
|
229
|
+
/** The period a cheat-meal allowance resets over. Unused cheat meals do not carry over. */
|
|
230
|
+
export declare const CHEAT_MEAL_PERIODS: readonly ["WEEK", "TWO_WEEKS", "MONTH"];
|
|
231
|
+
export type CheatMealPeriod = (typeof CHEAT_MEAL_PERIODS)[number];
|
|
232
|
+
/** Starting points for the calorie base: calories per pound and a macro split. */
|
|
233
|
+
export declare const NUTRITION_GUIDELINE_IDS: readonly ["AGGRESSIVE_CUT", "MODERATE_CUT", "MAINTENANCE", "MUSCLE_GAIN", "AGGRESSIVE_BULK"];
|
|
234
|
+
export type NutritionGuidelineId = (typeof NUTRITION_GUIDELINE_IDS)[number];
|
package/dist/cjs/enums/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// removing one is a breaking change, so it needs a major version.
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
5
|
exports.SLEEP_DURATIONS = exports.BODY_AREAS = exports.ANABOLIC_USE = exports.WATER_INTAKES = exports.ALLERGENS = exports.DIET_TYPES = exports.ACTIVITY_LEVELS = exports.SESSION_LENGTHS = exports.WEEKDAYS = exports.TRAINING_TIMES = exports.EXPERIENCE_LEVELS = exports.GENDERS = exports.ONBOARDING_SECTIONS = exports.ONBOARDING_STEPS = exports.ONBOARDING_STATUSES = exports.DOCUMENT_STATUSES = exports.DOCUMENT_TYPES = exports.SUPPLEMENT_SOURCES = exports.GOAL_SORT_FIELDS = exports.GOAL_STATUSES = exports.GOAL_ICONS = exports.SORT_ORDERS = exports.FOOD_SORT_FIELDS = exports.FOOD_METADATA_SELECTIONS = exports.FOOD_IMPORT_ISSUE_SEVERITIES = exports.FOOD_IMPORT_FILE_TYPES = exports.FOOD_IMPORT_ROW_STATUSES = exports.FOOD_IMPORT_STATUSES = exports.FOOD_IMPORT_MODES = exports.FOOD_SUGGESTION_RESOLUTIONS = exports.FOOD_SUGGESTION_STATUSES = exports.NUTRITION_BASES = exports.PORTION_UNITS = exports.SERVING_UNITS = exports.FOOD_SOURCES = exports.TEMPLATE_STATUSES = exports.DELIVERY_STATUSES = exports.MESSAGE_TYPES = exports.COMMUNICATION_CHANNELS = exports.FEATURE_FLAG_RULE_TYPES = exports.AUDIT_OUTCOMES = exports.CSRF_HEADER = exports.CSRF_COOKIE_NAMES = exports.TOKEN_TRANSPORTS = exports.TIMEZONE_SOURCES = exports.ADMIN_SETTABLE_ACCOUNT_STATUSES = exports.ACCOUNT_STATUSES = exports.PERMISSIONS = exports.ROLES = exports.APP_ENVIRONMENTS = void 0;
|
|
6
|
-
exports.WEIGHT_UNITS = exports.HEIGHT_UNITS = exports.KIDNEY_TEST_ANSWERS = exports.DIGESTIVE_SYMPTOMS = exports.REFLUX_FREQUENCIES = exports.DIGESTION_QUALITIES = void 0;
|
|
6
|
+
exports.NUTRITION_GUIDELINE_IDS = exports.CHEAT_MEAL_PERIODS = exports.PLAN_BLOCK_WEEKS = exports.PLAN_PHASES = exports.PLAN_SLOT_KINDS = exports.NUTRITION_PLAN_STATUSES = exports.MEAL_SORT_FIELDS = exports.WEIGHT_UNITS = exports.HEIGHT_UNITS = exports.KIDNEY_TEST_ANSWERS = exports.DIGESTIVE_SYMPTOMS = exports.REFLUX_FREQUENCIES = exports.DIGESTION_QUALITIES = void 0;
|
|
7
7
|
exports.APP_ENVIRONMENTS = [
|
|
8
8
|
'development',
|
|
9
9
|
'staging',
|
|
@@ -56,6 +56,14 @@ exports.PERMISSIONS = {
|
|
|
56
56
|
documentsReadSelf: 'documents:read:self',
|
|
57
57
|
documentsReadAssigned: 'documents:read:assigned',
|
|
58
58
|
documentsReadAny: 'documents:read:any',
|
|
59
|
+
mealsRead: 'meals:read',
|
|
60
|
+
mealsCreate: 'meals:create',
|
|
61
|
+
mealsUpdate: 'meals:update',
|
|
62
|
+
mealsDelete: 'meals:delete',
|
|
63
|
+
nutritionPlansRead: 'nutrition_plans:read',
|
|
64
|
+
nutritionPlansWrite: 'nutrition_plans:write',
|
|
65
|
+
nutritionPlansPublish: 'nutrition_plans:publish',
|
|
66
|
+
nutritionPlansReadSelf: 'nutrition_plans:read:self',
|
|
59
67
|
};
|
|
60
68
|
/**
|
|
61
69
|
* `pending_verification` is a self-registered user who has not yet proved they
|
|
@@ -329,3 +337,31 @@ exports.DIGESTIVE_SYMPTOMS = [
|
|
|
329
337
|
exports.KIDNEY_TEST_ANSWERS = ['YES', 'NO', 'NOT_SURE'];
|
|
330
338
|
exports.HEIGHT_UNITS = ['CM', 'FT'];
|
|
331
339
|
exports.WEIGHT_UNITS = ['KG', 'LB'];
|
|
340
|
+
// Meal library and nutrition plans
|
|
341
|
+
exports.MEAL_SORT_FIELDS = ['name', 'updatedAt'];
|
|
342
|
+
/**
|
|
343
|
+
* A plan is built as a `DRAFT`, becomes `ACTIVE` when published, and is
|
|
344
|
+
* `ARCHIVED` when a newer plan replaces it. A client has at most one of each
|
|
345
|
+
* of the first two.
|
|
346
|
+
*/
|
|
347
|
+
exports.NUTRITION_PLAN_STATUSES = ['DRAFT', 'ACTIVE', 'ARCHIVED'];
|
|
348
|
+
exports.PLAN_SLOT_KINDS = ['MEAL', 'SHAKE'];
|
|
349
|
+
/** The stretch of the training block the calorie base is set for. */
|
|
350
|
+
exports.PLAN_PHASES = [
|
|
351
|
+
'WEEKS_1_2',
|
|
352
|
+
'WEEKS_3_4',
|
|
353
|
+
'WEEKS_5_6',
|
|
354
|
+
'WEEKS_7_8',
|
|
355
|
+
];
|
|
356
|
+
/** How long a published plan runs, in weeks. */
|
|
357
|
+
exports.PLAN_BLOCK_WEEKS = [8, 12, 16];
|
|
358
|
+
/** The period a cheat-meal allowance resets over. Unused cheat meals do not carry over. */
|
|
359
|
+
exports.CHEAT_MEAL_PERIODS = ['WEEK', 'TWO_WEEKS', 'MONTH'];
|
|
360
|
+
/** Starting points for the calorie base: calories per pound and a macro split. */
|
|
361
|
+
exports.NUTRITION_GUIDELINE_IDS = [
|
|
362
|
+
'AGGRESSIVE_CUT',
|
|
363
|
+
'MODERATE_CUT',
|
|
364
|
+
'MAINTENANCE',
|
|
365
|
+
'MUSCLE_GAIN',
|
|
366
|
+
'AGGRESSIVE_BULK',
|
|
367
|
+
];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './meal.dto.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./meal.dto.js"), exports);
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const MAX_MEAL_NAME_LENGTH = 80;
|
|
3
|
+
export declare const MAX_MEAL_TIMING_LENGTH = 40;
|
|
4
|
+
export declare const MAX_MEAL_NOTES_LENGTH = 500;
|
|
5
|
+
export declare const MAX_MEAL_SEARCH_LENGTH = 80;
|
|
6
|
+
export declare const MAX_MEAL_ITEMS = 20;
|
|
7
|
+
/** Servings of a food's library serving, in half steps. */
|
|
8
|
+
export declare const SERVINGS: {
|
|
9
|
+
readonly MIN: 0.5;
|
|
10
|
+
readonly MAX: 10;
|
|
11
|
+
readonly STEP: 0.5;
|
|
12
|
+
};
|
|
13
|
+
export declare const mealIdParamSchema: z.ZodUUID;
|
|
14
|
+
/** Lower case with spaces collapsed: what duplicate name checks compare. */
|
|
15
|
+
export declare const normalizeMealName: (name: string) => string;
|
|
16
|
+
/** A number of servings: 0.5 to 10, in steps of 0.5. */
|
|
17
|
+
export declare const servingsSchema: z.ZodNumber;
|
|
18
|
+
/** A food and how many of its servings. */
|
|
19
|
+
export declare const foodPortionSchema: z.ZodObject<{
|
|
20
|
+
foodId: z.ZodUUID;
|
|
21
|
+
servings: z.ZodNumber;
|
|
22
|
+
}, z.core.$strict>;
|
|
23
|
+
export type FoodPortionInput = z.input<typeof foodPortionSchema>;
|
|
24
|
+
export declare const createMealSchema: z.ZodObject<{
|
|
25
|
+
name: z.ZodString;
|
|
26
|
+
timing: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
27
|
+
notes: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
28
|
+
items: z.ZodArray<z.ZodObject<{
|
|
29
|
+
foodId: z.ZodUUID;
|
|
30
|
+
servings: z.ZodNumber;
|
|
31
|
+
}, z.core.$strict>>;
|
|
32
|
+
}, z.core.$strict>;
|
|
33
|
+
export type CreateMealInput = z.input<typeof createMealSchema>;
|
|
34
|
+
export type CreateMealBody = z.output<typeof createMealSchema>;
|
|
35
|
+
export declare const updateMealSchema: z.ZodObject<{
|
|
36
|
+
version: z.ZodNumber;
|
|
37
|
+
name: z.ZodOptional<z.ZodString>;
|
|
38
|
+
timing: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
39
|
+
notes: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
40
|
+
items: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
41
|
+
foodId: z.ZodUUID;
|
|
42
|
+
servings: z.ZodNumber;
|
|
43
|
+
}, z.core.$strict>>>;
|
|
44
|
+
}, z.core.$strict>;
|
|
45
|
+
export type UpdateMealInput = z.input<typeof updateMealSchema>;
|
|
46
|
+
export type UpdateMealBody = z.output<typeof updateMealSchema>;
|
|
47
|
+
export declare const mealListQuerySchema: z.ZodObject<{
|
|
48
|
+
page: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
49
|
+
pageSize: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
50
|
+
search: z.ZodOptional<z.ZodString>;
|
|
51
|
+
sort: z.ZodDefault<z.ZodEnum<{
|
|
52
|
+
name: "name";
|
|
53
|
+
updatedAt: "updatedAt";
|
|
54
|
+
}>>;
|
|
55
|
+
order: z.ZodDefault<z.ZodEnum<{
|
|
56
|
+
asc: "asc";
|
|
57
|
+
desc: "desc";
|
|
58
|
+
}>>;
|
|
59
|
+
}, z.core.$strip>;
|
|
60
|
+
export type MealListQuery = z.output<typeof mealListQuerySchema>;
|
|
61
|
+
/** Calories and the three macros, in kcal and grams. */
|
|
62
|
+
export declare const macroTotalsSchema: z.ZodObject<{
|
|
63
|
+
calories: z.ZodNumber;
|
|
64
|
+
protein: z.ZodNumber;
|
|
65
|
+
carbs: z.ZodNumber;
|
|
66
|
+
fat: z.ZodNumber;
|
|
67
|
+
}, z.core.$strip>;
|
|
68
|
+
export type MacroTotalsResponse = z.output<typeof macroTotalsSchema>;
|
|
69
|
+
/** The library food a portion refers to, as it was when last read or saved. */
|
|
70
|
+
export declare const portionFoodSchema: z.ZodObject<{
|
|
71
|
+
name: z.ZodString;
|
|
72
|
+
servingLabel: z.ZodString;
|
|
73
|
+
deleted: z.ZodBoolean;
|
|
74
|
+
}, z.core.$strip>;
|
|
75
|
+
export declare const mealItemResponseSchema: z.ZodObject<{
|
|
76
|
+
id: z.ZodUUID;
|
|
77
|
+
foodId: z.ZodUUID;
|
|
78
|
+
servings: z.ZodNumber;
|
|
79
|
+
food: z.ZodObject<{
|
|
80
|
+
name: z.ZodString;
|
|
81
|
+
servingLabel: z.ZodString;
|
|
82
|
+
deleted: z.ZodBoolean;
|
|
83
|
+
}, z.core.$strip>;
|
|
84
|
+
perServing: z.ZodObject<{
|
|
85
|
+
calories: z.ZodNumber;
|
|
86
|
+
protein: z.ZodNumber;
|
|
87
|
+
carbs: z.ZodNumber;
|
|
88
|
+
fat: z.ZodNumber;
|
|
89
|
+
}, z.core.$strip>;
|
|
90
|
+
totals: z.ZodObject<{
|
|
91
|
+
calories: z.ZodNumber;
|
|
92
|
+
protein: z.ZodNumber;
|
|
93
|
+
carbs: z.ZodNumber;
|
|
94
|
+
fat: z.ZodNumber;
|
|
95
|
+
}, z.core.$strip>;
|
|
96
|
+
}, z.core.$strip>;
|
|
97
|
+
export type MealItemResponse = z.output<typeof mealItemResponseSchema>;
|
|
98
|
+
export declare const mealResponseSchema: z.ZodObject<{
|
|
99
|
+
id: z.ZodUUID;
|
|
100
|
+
name: z.ZodString;
|
|
101
|
+
timing: z.ZodNullable<z.ZodString>;
|
|
102
|
+
notes: z.ZodNullable<z.ZodString>;
|
|
103
|
+
items: z.ZodArray<z.ZodObject<{
|
|
104
|
+
id: z.ZodUUID;
|
|
105
|
+
foodId: z.ZodUUID;
|
|
106
|
+
servings: z.ZodNumber;
|
|
107
|
+
food: z.ZodObject<{
|
|
108
|
+
name: z.ZodString;
|
|
109
|
+
servingLabel: z.ZodString;
|
|
110
|
+
deleted: z.ZodBoolean;
|
|
111
|
+
}, z.core.$strip>;
|
|
112
|
+
perServing: z.ZodObject<{
|
|
113
|
+
calories: z.ZodNumber;
|
|
114
|
+
protein: z.ZodNumber;
|
|
115
|
+
carbs: z.ZodNumber;
|
|
116
|
+
fat: z.ZodNumber;
|
|
117
|
+
}, z.core.$strip>;
|
|
118
|
+
totals: z.ZodObject<{
|
|
119
|
+
calories: z.ZodNumber;
|
|
120
|
+
protein: z.ZodNumber;
|
|
121
|
+
carbs: z.ZodNumber;
|
|
122
|
+
fat: z.ZodNumber;
|
|
123
|
+
}, z.core.$strip>;
|
|
124
|
+
}, z.core.$strip>>;
|
|
125
|
+
totals: z.ZodObject<{
|
|
126
|
+
calories: z.ZodNumber;
|
|
127
|
+
protein: z.ZodNumber;
|
|
128
|
+
carbs: z.ZodNumber;
|
|
129
|
+
fat: z.ZodNumber;
|
|
130
|
+
}, z.core.$strip>;
|
|
131
|
+
createdBy: z.ZodObject<{
|
|
132
|
+
id: z.ZodUUID;
|
|
133
|
+
fullName: z.ZodNullable<z.ZodString>;
|
|
134
|
+
}, z.core.$strip>;
|
|
135
|
+
updatedBy: z.ZodObject<{
|
|
136
|
+
id: z.ZodUUID;
|
|
137
|
+
fullName: z.ZodNullable<z.ZodString>;
|
|
138
|
+
}, z.core.$strip>;
|
|
139
|
+
version: z.ZodNumber;
|
|
140
|
+
createdAt: z.ZodISODateTime;
|
|
141
|
+
updatedAt: z.ZodISODateTime;
|
|
142
|
+
}, z.core.$strip>;
|
|
143
|
+
export type MealResponse = z.output<typeof mealResponseSchema>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mealResponseSchema = exports.mealItemResponseSchema = exports.portionFoodSchema = exports.macroTotalsSchema = exports.mealListQuerySchema = exports.updateMealSchema = exports.createMealSchema = exports.foodPortionSchema = exports.servingsSchema = exports.normalizeMealName = exports.mealIdParamSchema = exports.SERVINGS = exports.MAX_MEAL_ITEMS = exports.MAX_MEAL_SEARCH_LENGTH = exports.MAX_MEAL_NOTES_LENGTH = exports.MAX_MEAL_TIMING_LENGTH = exports.MAX_MEAL_NAME_LENGTH = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const date_time_schema_js_1 = require("../common/date-time.schema.js");
|
|
6
|
+
const pagination_schema_js_1 = require("../common/pagination.schema.js");
|
|
7
|
+
const safe_text_schema_js_1 = require("../common/safe-text.schema.js");
|
|
8
|
+
const index_js_1 = require("../enums/index.js");
|
|
9
|
+
const food_dto_js_1 = require("../food/food.dto.js");
|
|
10
|
+
// A meal is a named set of library foods with a number of servings each, such
|
|
11
|
+
// as "Oats + Milk + Banana". Coaches keep a library of them and copy one into a
|
|
12
|
+
// slot of a client's nutrition plan, where it can be changed for that client.
|
|
13
|
+
//
|
|
14
|
+
// Each request has two types. `*Input` is what a client sends, where fields with
|
|
15
|
+
// a default are optional. `*Body` is what the API receives after validation.
|
|
16
|
+
exports.MAX_MEAL_NAME_LENGTH = 80;
|
|
17
|
+
exports.MAX_MEAL_TIMING_LENGTH = 40;
|
|
18
|
+
exports.MAX_MEAL_NOTES_LENGTH = 500;
|
|
19
|
+
exports.MAX_MEAL_SEARCH_LENGTH = 80;
|
|
20
|
+
exports.MAX_MEAL_ITEMS = 20;
|
|
21
|
+
/** Servings of a food's library serving, in half steps. */
|
|
22
|
+
exports.SERVINGS = { MIN: 0.5, MAX: 10, STEP: 0.5 };
|
|
23
|
+
exports.mealIdParamSchema = zod_1.z.uuid();
|
|
24
|
+
/** Lower case with spaces collapsed: what duplicate name checks compare. */
|
|
25
|
+
const normalizeMealName = (name) => name.trim().replace(/\s+/g, ' ').toLowerCase();
|
|
26
|
+
exports.normalizeMealName = normalizeMealName;
|
|
27
|
+
/** A number of servings: 0.5 to 10, in steps of 0.5. */
|
|
28
|
+
exports.servingsSchema = zod_1.z
|
|
29
|
+
.number()
|
|
30
|
+
.min(exports.SERVINGS.MIN)
|
|
31
|
+
.max(exports.SERVINGS.MAX)
|
|
32
|
+
.refine((value) => Number.isInteger(value / exports.SERVINGS.STEP), {
|
|
33
|
+
error: `Use steps of ${exports.SERVINGS.STEP}`,
|
|
34
|
+
});
|
|
35
|
+
/** A food and how many of its servings. */
|
|
36
|
+
exports.foodPortionSchema = zod_1.z.strictObject({
|
|
37
|
+
foodId: zod_1.z.uuid(),
|
|
38
|
+
servings: exports.servingsSchema,
|
|
39
|
+
});
|
|
40
|
+
const mealNameSchema = (0, safe_text_schema_js_1.safeTextSchema)(exports.MAX_MEAL_NAME_LENGTH);
|
|
41
|
+
const mealTimingSchema = (0, safe_text_schema_js_1.safeTextSchema)(exports.MAX_MEAL_TIMING_LENGTH);
|
|
42
|
+
const mealNotesSchema = (0, safe_text_schema_js_1.safeTextSchema)(exports.MAX_MEAL_NOTES_LENGTH);
|
|
43
|
+
const mealItemsSchema = zod_1.z.array(exports.foodPortionSchema).min(1).max(exports.MAX_MEAL_ITEMS);
|
|
44
|
+
// Strict objects reject unknown fields, so an attempt to set `createdBy`
|
|
45
|
+
// through the API fails loudly.
|
|
46
|
+
exports.createMealSchema = zod_1.z.strictObject({
|
|
47
|
+
name: mealNameSchema,
|
|
48
|
+
/** When it is usually eaten, such as "Breakfast" or "Post-workout". */
|
|
49
|
+
timing: mealTimingSchema.nullable().optional(),
|
|
50
|
+
notes: mealNotesSchema.nullable().optional(),
|
|
51
|
+
/** In the order shown. A food may appear more than once. */
|
|
52
|
+
items: mealItemsSchema,
|
|
53
|
+
});
|
|
54
|
+
exports.updateMealSchema = zod_1.z
|
|
55
|
+
.strictObject({
|
|
56
|
+
// The version last read, so that a stale edit is refused rather than lost.
|
|
57
|
+
version: zod_1.z.number().int().min(1),
|
|
58
|
+
name: mealNameSchema.optional(),
|
|
59
|
+
timing: mealTimingSchema.nullable().optional(),
|
|
60
|
+
notes: mealNotesSchema.nullable().optional(),
|
|
61
|
+
/** Replaces every item when present. */
|
|
62
|
+
items: mealItemsSchema.optional(),
|
|
63
|
+
})
|
|
64
|
+
.refine(({ version: _version, ...changes }) => Object.values(changes).some((value) => value !== undefined), { error: 'Provide at least one field to change' });
|
|
65
|
+
exports.mealListQuerySchema = pagination_schema_js_1.paginationQuerySchema.extend({
|
|
66
|
+
search: zod_1.z.string().trim().min(1).max(exports.MAX_MEAL_SEARCH_LENGTH).optional(),
|
|
67
|
+
sort: zod_1.z.enum(index_js_1.MEAL_SORT_FIELDS).default('name'),
|
|
68
|
+
order: zod_1.z.enum(index_js_1.SORT_ORDERS).default('asc'),
|
|
69
|
+
});
|
|
70
|
+
// Responses
|
|
71
|
+
/** Calories and the three macros, in kcal and grams. */
|
|
72
|
+
exports.macroTotalsSchema = zod_1.z.object({
|
|
73
|
+
calories: zod_1.z.number(),
|
|
74
|
+
protein: zod_1.z.number(),
|
|
75
|
+
carbs: zod_1.z.number(),
|
|
76
|
+
fat: zod_1.z.number(),
|
|
77
|
+
});
|
|
78
|
+
/** The library food a portion refers to, as it was when last read or saved. */
|
|
79
|
+
exports.portionFoodSchema = zod_1.z.object({
|
|
80
|
+
name: zod_1.z.string(),
|
|
81
|
+
/** Such as "1 bowl (150g)". */
|
|
82
|
+
servingLabel: zod_1.z.string(),
|
|
83
|
+
/** True when the food has since been deleted from the library. */
|
|
84
|
+
deleted: zod_1.z.boolean(),
|
|
85
|
+
});
|
|
86
|
+
exports.mealItemResponseSchema = zod_1.z.object({
|
|
87
|
+
id: zod_1.z.uuid(),
|
|
88
|
+
foodId: zod_1.z.uuid(),
|
|
89
|
+
servings: zod_1.z.number(),
|
|
90
|
+
food: exports.portionFoodSchema,
|
|
91
|
+
perServing: exports.macroTotalsSchema,
|
|
92
|
+
/** `perServing` × `servings`. */
|
|
93
|
+
totals: exports.macroTotalsSchema,
|
|
94
|
+
});
|
|
95
|
+
exports.mealResponseSchema = zod_1.z.object({
|
|
96
|
+
id: zod_1.z.uuid(),
|
|
97
|
+
name: zod_1.z.string(),
|
|
98
|
+
timing: zod_1.z.string().nullable(),
|
|
99
|
+
notes: zod_1.z.string().nullable(),
|
|
100
|
+
items: zod_1.z.array(exports.mealItemResponseSchema),
|
|
101
|
+
totals: exports.macroTotalsSchema,
|
|
102
|
+
createdBy: food_dto_js_1.userRefSchema,
|
|
103
|
+
updatedBy: food_dto_js_1.userRefSchema,
|
|
104
|
+
version: zod_1.z.number().int(),
|
|
105
|
+
createdAt: date_time_schema_js_1.utcTimestampSchema,
|
|
106
|
+
updatedAt: date_time_schema_js_1.utcTimestampSchema,
|
|
107
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./nutrition-math.js"), exports);
|
|
18
|
+
__exportStar(require("./nutrition-plan.dto.js"), exports);
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { type GoalIcon, type NutritionGuidelineId, type PlanSlotKind } from '../enums/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* The Nutrition Plan Builder's sums, shared so that the builder's screens and
|
|
4
|
+
* the API agree on every number and every publish rule. Everything here is
|
|
5
|
+
* pure.
|
|
6
|
+
*/
|
|
7
|
+
export declare const KG_TO_LB = 2.20462;
|
|
8
|
+
export declare const KCAL_PER_GRAM: {
|
|
9
|
+
readonly protein: 4;
|
|
10
|
+
readonly carbs: 4;
|
|
11
|
+
readonly fat: 9;
|
|
12
|
+
};
|
|
13
|
+
export type MacroKey = keyof typeof KCAL_PER_GRAM;
|
|
14
|
+
export declare const MACRO_KEYS: readonly MacroKey[];
|
|
15
|
+
/** A slot's macros must come to its share of calories within this many kcal. */
|
|
16
|
+
export declare const MEAL_KCAL_TOLERANCE = 10;
|
|
17
|
+
/** The day's foods must come within this share of the daily calories. */
|
|
18
|
+
export declare const DAILY_MACRO_TOLERANCE = 0.05;
|
|
19
|
+
/** Recommended water: 35 ml per kg of body weight, rounded to 100 ml. */
|
|
20
|
+
export declare const WATER_ML_PER_KG = 35;
|
|
21
|
+
export declare const GLASS_ML = 250;
|
|
22
|
+
export declare const CHEAT_MEALS: {
|
|
23
|
+
readonly MIN: 1;
|
|
24
|
+
readonly MAX: 3;
|
|
25
|
+
readonly DEFAULT: 1;
|
|
26
|
+
};
|
|
27
|
+
export type MacroSplit = Record<MacroKey, number>;
|
|
28
|
+
export interface MacroTotals {
|
|
29
|
+
calories: number;
|
|
30
|
+
protein: number;
|
|
31
|
+
carbs: number;
|
|
32
|
+
fat: number;
|
|
33
|
+
}
|
|
34
|
+
export interface NutritionGuideline {
|
|
35
|
+
id: NutritionGuidelineId;
|
|
36
|
+
label: string;
|
|
37
|
+
calPerLb: number;
|
|
38
|
+
split: MacroSplit;
|
|
39
|
+
}
|
|
40
|
+
export declare const NUTRITION_GUIDELINES: readonly NutritionGuideline[];
|
|
41
|
+
export declare const DEFAULT_GUIDELINE_ID: NutritionGuidelineId;
|
|
42
|
+
export declare function nutritionGuideline(id: NutritionGuidelineId): NutritionGuideline;
|
|
43
|
+
/** The guideline a goal suggests; maintenance when there is no goal or no match. */
|
|
44
|
+
export declare function guidelineForGoalIcon(icon: GoalIcon | null | undefined): NutritionGuideline;
|
|
45
|
+
export declare function isNutritionGuidelineId(value: string): value is NutritionGuidelineId;
|
|
46
|
+
export declare function poundsOf(kg: number): number;
|
|
47
|
+
/** Weight in pounds × calories per pound, rounded down, unless custom calories are set. */
|
|
48
|
+
export declare function dailyCalories(plan: {
|
|
49
|
+
weightKg: number;
|
|
50
|
+
calPerLb: number;
|
|
51
|
+
customCalories: number | null;
|
|
52
|
+
}): number;
|
|
53
|
+
export interface MacroTarget {
|
|
54
|
+
percent: number;
|
|
55
|
+
kcal: number;
|
|
56
|
+
grams: number;
|
|
57
|
+
}
|
|
58
|
+
export declare function macroTargets(total: number, split: MacroSplit): Record<MacroKey, MacroTarget>;
|
|
59
|
+
export declare function splitTotal(split: MacroSplit): number;
|
|
60
|
+
export declare function recommendedWaterMl(weightKg: number): number;
|
|
61
|
+
/** Calories from macro grams. Unset grams count as zero. */
|
|
62
|
+
export declare function macroKcal(macros: {
|
|
63
|
+
protein: number | null;
|
|
64
|
+
carbs: number | null;
|
|
65
|
+
fat: number | null;
|
|
66
|
+
}): number;
|
|
67
|
+
/** A slot's calories: its share of the day, or null while the share is unset. */
|
|
68
|
+
export declare function slotTargetKcal(share: number | null, total: number): number | null;
|
|
69
|
+
export declare const ZERO_TOTALS: MacroTotals;
|
|
70
|
+
/** Adds macro totals, rounding to one decimal so sums don't drift. */
|
|
71
|
+
export declare function addTotals(...items: readonly MacroTotals[]): MacroTotals;
|
|
72
|
+
/** One serving's values × a number of servings. */
|
|
73
|
+
export declare function scaleTotals(perServing: MacroTotals, servings: number): MacroTotals;
|
|
74
|
+
export interface DefaultSlot {
|
|
75
|
+
kind: PlanSlotKind;
|
|
76
|
+
label: string;
|
|
77
|
+
timing: string;
|
|
78
|
+
}
|
|
79
|
+
/** The labels and timings a new plan starts with for a number of meals and shakes. */
|
|
80
|
+
export declare function defaultSlots(meals: number, shakes: number): DefaultSlot[];
|
|
81
|
+
export declare const PUBLISH_ISSUE_CODES: readonly ["SPLIT_NOT_100", "SLOT_COUNT_MISMATCH", "SLOT_NOT_SET", "SHARES_NOT_100", "SLOT_MACROS_OFF", "SLOT_HAS_NO_FOODS", "DAY_CALORIES_OFF", "CHEAT_COUNT_OUT_OF_RANGE"];
|
|
82
|
+
export type PublishIssueCode = (typeof PUBLISH_ISSUE_CODES)[number];
|
|
83
|
+
export interface PublishIssue {
|
|
84
|
+
code: PublishIssueCode;
|
|
85
|
+
/** The slot it is about, by position from 0, when it is about one slot. */
|
|
86
|
+
slotIndex: number | null;
|
|
87
|
+
message: string;
|
|
88
|
+
}
|
|
89
|
+
/** The parts of a plan the publish rules read. */
|
|
90
|
+
export interface PlanForPublishCheck {
|
|
91
|
+
weightKg: number;
|
|
92
|
+
calPerLb: number;
|
|
93
|
+
customCalories: number | null;
|
|
94
|
+
split: MacroSplit;
|
|
95
|
+
mealsPerDay: number;
|
|
96
|
+
shakesPerDay: number;
|
|
97
|
+
slots: readonly {
|
|
98
|
+
kind: PlanSlotKind;
|
|
99
|
+
share: number | null;
|
|
100
|
+
protein: number | null;
|
|
101
|
+
carbs: number | null;
|
|
102
|
+
fat: number | null;
|
|
103
|
+
/** Each food's totals for its servings. */
|
|
104
|
+
foods: readonly MacroTotals[];
|
|
105
|
+
}[];
|
|
106
|
+
cheat: {
|
|
107
|
+
allowed: boolean;
|
|
108
|
+
count: number;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Why a plan cannot be published yet, in the order the builder's steps check
|
|
113
|
+
* them. An empty list means it can be.
|
|
114
|
+
*/
|
|
115
|
+
export declare function nutritionPlanPublishIssues(plan: PlanForPublishCheck): PublishIssue[];
|