@tbs-fitness/contract 0.9.0 → 0.10.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 CHANGED
@@ -43,6 +43,17 @@ const result = createFlagSchema.safeParse(input); // The same rules as the API.
43
43
 
44
44
  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
45
 
46
+ ## Version 0.10.0
47
+
48
+ The supplement library. Additive; consumers install `^0.10.0` explicitly.
49
+
50
+ - New `/supplements`. It keeps the food library's spreadsheet format, nutrition rules and labels, so most schemas are the food library's own under supplement names:
51
+ - Library: `createSupplementSchema`, `updateSupplementSchema` (with `version`), `supplementListQuerySchema`, `supplementResponseSchema` (no suggestion link; `source` is `ADMIN`, `COACH` or `IMPORT`), `similarSupplementResponseSchema`, `supplementStatsResponseSchema`.
52
+ - Labels: the metadata type and value schemas, re-exported from `/food`.
53
+ - Import: `supplementImportRowSchema`, `commitSupplementImportSchema`, `supplementImportBatchResponseSchema`, `supplementImportRowResponseSchema` (`matchedSupplementId`, `resultSupplementId`), `SUPPLEMENT_IMPORT_LIMITS`.
54
+ - Client: `clientSupplementSearchQuerySchema`, `clientSupplementResponseSchema`.
55
+ - `/enums`: `SUPPLEMENT_SOURCES`, and the permissions `supplements:read`, `supplements:create`, `supplements:update`, `supplements:delete`, `supplements:import`, `supplement_metadata:manage` and `supplements:search:self`.
56
+
46
57
  ## Version 0.9.0
47
58
 
48
59
  The goal library. Additive; consumers install `^0.9.0` explicitly.
@@ -37,6 +37,13 @@ export declare const PERMISSIONS: {
37
37
  readonly goalsUpdate: "goals:update";
38
38
  readonly goalsDelete: "goals:delete";
39
39
  readonly goalsReadSelf: "goals:read:self";
40
+ readonly supplementsRead: "supplements:read";
41
+ readonly supplementsCreate: "supplements:create";
42
+ readonly supplementsUpdate: "supplements:update";
43
+ readonly supplementsDelete: "supplements:delete";
44
+ readonly supplementsImport: "supplements:import";
45
+ readonly supplementMetadataManage: "supplement_metadata:manage";
46
+ readonly supplementsSearchSelf: "supplements:search:self";
40
47
  };
41
48
  export type Permission = (typeof PERMISSIONS)[keyof typeof PERMISSIONS];
42
49
  /**
@@ -137,3 +144,6 @@ export declare const GOAL_STATUSES: readonly ["active", "inactive"];
137
144
  export type GoalStatus = (typeof GOAL_STATUSES)[number];
138
145
  export declare const GOAL_SORT_FIELDS: readonly ["sortOrder", "name", "updatedAt"];
139
146
  export type GoalSortField = (typeof GOAL_SORT_FIELDS)[number];
147
+ /** Where a supplement came from. Clients cannot suggest supplements, unlike foods. */
148
+ export declare const SUPPLEMENT_SOURCES: readonly ["ADMIN", "COACH", "IMPORT"];
149
+ export type SupplementSource = (typeof SUPPLEMENT_SOURCES)[number];
@@ -2,7 +2,7 @@
2
2
  // These values are stored in the database and sent over the API. Renaming or
3
3
  // removing one is a breaking change, so it needs a major version.
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
- 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;
5
+ 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
6
  exports.APP_ENVIRONMENTS = [
7
7
  'development',
8
8
  'staging',
@@ -44,6 +44,13 @@ exports.PERMISSIONS = {
44
44
  goalsUpdate: 'goals:update',
45
45
  goalsDelete: 'goals:delete',
46
46
  goalsReadSelf: 'goals:read:self',
47
+ supplementsRead: 'supplements:read',
48
+ supplementsCreate: 'supplements:create',
49
+ supplementsUpdate: 'supplements:update',
50
+ supplementsDelete: 'supplements:delete',
51
+ supplementsImport: 'supplements:import',
52
+ supplementMetadataManage: 'supplement_metadata:manage',
53
+ supplementsSearchSelf: 'supplements:search:self',
47
54
  };
48
55
  /**
49
56
  * `pending_verification` is a self-registered user who has not yet proved they
@@ -177,3 +184,5 @@ exports.GOAL_ICONS = [
177
184
  /** Whether clients can pick a goal: live goals are either shown or hidden. */
178
185
  exports.GOAL_STATUSES = ['active', 'inactive'];
179
186
  exports.GOAL_SORT_FIELDS = ['sortOrder', 'name', 'updatedAt'];
187
+ /** Where a supplement came from. Clients cannot suggest supplements, unlike foods. */
188
+ exports.SUPPLEMENT_SOURCES = ['ADMIN', 'COACH', 'IMPORT'];
@@ -0,0 +1 @@
1
+ export * from './supplement.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("./supplement.dto.js"), exports);
@@ -0,0 +1,386 @@
1
+ import { z } from 'zod';
2
+ export declare const supplementIdParamSchema: z.ZodUUID;
3
+ export declare const createSupplementSchema: z.ZodObject<{
4
+ name: z.ZodString;
5
+ nameHi: z.ZodOptional<z.ZodNullable<z.ZodString>>;
6
+ namePa: z.ZodOptional<z.ZodNullable<z.ZodString>>;
7
+ englishAlias: z.ZodOptional<z.ZodNullable<z.ZodString>>;
8
+ serving: z.ZodObject<{
9
+ label: z.ZodString;
10
+ quantity: z.ZodNumber;
11
+ unit: z.ZodEnum<{
12
+ g: "g";
13
+ ml: "ml";
14
+ }>;
15
+ }, z.core.$strict>;
16
+ nutrition: z.ZodObject<{
17
+ basis: z.ZodDefault<z.ZodEnum<{
18
+ PER_100: "PER_100";
19
+ PER_SERVING: "PER_SERVING";
20
+ }>>;
21
+ calories: z.ZodNumber;
22
+ protein: z.ZodNumber;
23
+ carbs: z.ZodNumber;
24
+ fat: z.ZodNumber;
25
+ sugar: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
26
+ fiber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
27
+ solubleFiber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
28
+ insolubleFiber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
29
+ }, z.core.$strict>;
30
+ metadataValueIds: z.ZodDefault<z.ZodArray<z.ZodUUID>>;
31
+ notes: z.ZodOptional<z.ZodNullable<z.ZodString>>;
32
+ }, z.core.$strict>;
33
+ export type CreateSupplementInput = z.input<typeof createSupplementSchema>;
34
+ export type CreateSupplementBody = z.output<typeof createSupplementSchema>;
35
+ export declare const updateSupplementSchema: z.ZodObject<{
36
+ version: z.ZodNumber;
37
+ name: z.ZodOptional<z.ZodString>;
38
+ nameHi: z.ZodOptional<z.ZodNullable<z.ZodString>>;
39
+ namePa: z.ZodOptional<z.ZodNullable<z.ZodString>>;
40
+ englishAlias: z.ZodOptional<z.ZodNullable<z.ZodString>>;
41
+ serving: z.ZodOptional<z.ZodObject<{
42
+ label: z.ZodString;
43
+ quantity: z.ZodNumber;
44
+ unit: z.ZodEnum<{
45
+ g: "g";
46
+ ml: "ml";
47
+ }>;
48
+ }, z.core.$strict>>;
49
+ nutrition: z.ZodOptional<z.ZodObject<{
50
+ basis: z.ZodDefault<z.ZodEnum<{
51
+ PER_100: "PER_100";
52
+ PER_SERVING: "PER_SERVING";
53
+ }>>;
54
+ calories: z.ZodNumber;
55
+ protein: z.ZodNumber;
56
+ carbs: z.ZodNumber;
57
+ fat: z.ZodNumber;
58
+ sugar: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
59
+ fiber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
60
+ solubleFiber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
61
+ insolubleFiber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
62
+ }, z.core.$strict>>;
63
+ metadataValueIds: z.ZodOptional<z.ZodArray<z.ZodUUID>>;
64
+ notes: z.ZodOptional<z.ZodNullable<z.ZodString>>;
65
+ }, z.core.$strict>;
66
+ export type UpdateSupplementInput = z.input<typeof updateSupplementSchema>;
67
+ export type UpdateSupplementBody = z.output<typeof updateSupplementSchema>;
68
+ /**
69
+ * `metadata` filters by value ids: a supplement matches when it has at least
70
+ * one of the listed values of each type that appears in the list.
71
+ */
72
+ export declare const supplementListQuerySchema: z.ZodObject<{
73
+ page: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
74
+ pageSize: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
75
+ search: z.ZodOptional<z.ZodString>;
76
+ metadata: z.ZodPreprocess<z.ZodOptional<z.ZodArray<z.ZodUUID>>, unknown>;
77
+ source: z.ZodOptional<z.ZodEnum<{
78
+ ADMIN: "ADMIN";
79
+ COACH: "COACH";
80
+ IMPORT: "IMPORT";
81
+ }>>;
82
+ sort: z.ZodDefault<z.ZodEnum<{
83
+ name: "name";
84
+ updatedAt: "updatedAt";
85
+ calories: "calories";
86
+ protein: "protein";
87
+ }>>;
88
+ order: z.ZodDefault<z.ZodEnum<{
89
+ asc: "asc";
90
+ desc: "desc";
91
+ }>>;
92
+ }, z.core.$strip>;
93
+ export type SupplementListQuery = z.output<typeof supplementListQuerySchema>;
94
+ export declare const similarSupplementQuerySchema: z.ZodObject<{
95
+ name: z.ZodString;
96
+ }, z.core.$strip>;
97
+ export type SimilarSupplementQuery = z.output<typeof similarSupplementQuerySchema>;
98
+ export declare const supplementResponseSchema: z.ZodObject<{
99
+ metadata: z.ZodArray<z.ZodObject<{
100
+ id: z.ZodUUID;
101
+ typeKey: z.ZodString;
102
+ key: z.ZodString;
103
+ label: z.ZodString;
104
+ isActive: z.ZodBoolean;
105
+ }, z.core.$strip>>;
106
+ name: z.ZodString;
107
+ updatedAt: z.ZodISODateTime;
108
+ id: z.ZodUUID;
109
+ version: z.ZodNumber;
110
+ createdAt: z.ZodISODateTime;
111
+ createdBy: z.ZodObject<{
112
+ id: z.ZodUUID;
113
+ fullName: z.ZodNullable<z.ZodString>;
114
+ }, z.core.$strip>;
115
+ nameHi: z.ZodNullable<z.ZodString>;
116
+ namePa: z.ZodNullable<z.ZodString>;
117
+ englishAlias: z.ZodNullable<z.ZodString>;
118
+ serving: z.ZodObject<{
119
+ label: z.ZodString;
120
+ quantity: z.ZodNumber;
121
+ unit: z.ZodEnum<{
122
+ g: "g";
123
+ ml: "ml";
124
+ }>;
125
+ }, z.core.$strip>;
126
+ notes: z.ZodNullable<z.ZodString>;
127
+ per100: z.ZodObject<{
128
+ calories: z.ZodNumber;
129
+ protein: z.ZodNumber;
130
+ carbs: z.ZodNumber;
131
+ fat: z.ZodNumber;
132
+ sugar: z.ZodNullable<z.ZodNumber>;
133
+ fiber: z.ZodNullable<z.ZodNumber>;
134
+ solubleFiber: z.ZodNullable<z.ZodNumber>;
135
+ insolubleFiber: z.ZodNullable<z.ZodNumber>;
136
+ }, z.core.$strip>;
137
+ perServing: z.ZodObject<{
138
+ calories: z.ZodNumber;
139
+ protein: z.ZodNumber;
140
+ carbs: z.ZodNumber;
141
+ fat: z.ZodNumber;
142
+ sugar: z.ZodNullable<z.ZodNumber>;
143
+ fiber: z.ZodNullable<z.ZodNumber>;
144
+ solubleFiber: z.ZodNullable<z.ZodNumber>;
145
+ insolubleFiber: z.ZodNullable<z.ZodNumber>;
146
+ }, z.core.$strip>;
147
+ formulaCalories: z.ZodNumber;
148
+ importBatchId: z.ZodNullable<z.ZodUUID>;
149
+ source: z.ZodEnum<{
150
+ ADMIN: "ADMIN";
151
+ COACH: "COACH";
152
+ IMPORT: "IMPORT";
153
+ }>;
154
+ }, z.core.$strip>;
155
+ export type SupplementResponse = z.output<typeof supplementResponseSchema>;
156
+ export declare const similarSupplementResponseSchema: z.ZodObject<{
157
+ id: z.ZodUUID;
158
+ name: z.ZodString;
159
+ serving: z.ZodObject<{
160
+ label: z.ZodString;
161
+ quantity: z.ZodNumber;
162
+ unit: z.ZodEnum<{
163
+ g: "g";
164
+ ml: "ml";
165
+ }>;
166
+ }, z.core.$strip>;
167
+ per100: z.ZodObject<{
168
+ calories: z.ZodNumber;
169
+ protein: z.ZodNumber;
170
+ carbs: z.ZodNumber;
171
+ fat: z.ZodNumber;
172
+ sugar: z.ZodNullable<z.ZodNumber>;
173
+ fiber: z.ZodNullable<z.ZodNumber>;
174
+ solubleFiber: z.ZodNullable<z.ZodNumber>;
175
+ insolubleFiber: z.ZodNullable<z.ZodNumber>;
176
+ }, z.core.$strip>;
177
+ similarity: z.ZodNumber;
178
+ }, z.core.$strip>;
179
+ export type SimilarSupplementResponse = z.output<typeof similarSupplementResponseSchema>;
180
+ export declare const supplementStatsResponseSchema: z.ZodObject<{
181
+ totalSupplements: z.ZodNumber;
182
+ categories: z.ZodArray<z.ZodObject<{
183
+ valueId: z.ZodUUID;
184
+ key: z.ZodString;
185
+ label: z.ZodString;
186
+ count: z.ZodNumber;
187
+ }, z.core.$strip>>;
188
+ }, z.core.$strip>;
189
+ export type SupplementStatsResponse = z.output<typeof supplementStatsResponseSchema>;
190
+ export { type CreateMetadataValueBody, type CreateMetadataValueInput, createMetadataValueSchema, MAX_METADATA_LABEL_LENGTH, metadataTypeIdParamSchema, metadataTypeListResponseSchema, type MetadataTypeResponse, metadataTypeResponseSchema, metadataValueIdParamSchema, type MetadataValueListQuery, metadataValueListQuerySchema, metadataValueListResponseSchema, type MetadataValueResponse, metadataValueResponseSchema, type UpdateMetadataValueBody, type UpdateMetadataValueInput, updateMetadataValueSchema, } from '../food/food.dto.js';
191
+ export { type NutrientValuesResponse, nutrientValuesResponseSchema, type ServingResponse, servingResponseSchema, type FoodMetadataRef as SupplementMetadataRef, foodMetadataRefSchema as supplementMetadataRefSchema, type UserRef, userRefSchema, } from '../food/food.dto.js';
192
+ export { importIdParamSchema, foodImportListQuerySchema as supplementImportListQuerySchema, } from '../food/food-import.dto.js';
193
+ export declare const SUPPLEMENT_IMPORT_LIMITS: {
194
+ readonly maxFileBytes: number;
195
+ readonly maxRows: 5000;
196
+ readonly extensions: readonly [".csv", ".xlsx"];
197
+ };
198
+ export declare const supplementImportRowSchema: z.ZodObject<{
199
+ name: z.ZodString;
200
+ nameHi: z.ZodNullable<z.ZodString>;
201
+ namePa: z.ZodNullable<z.ZodString>;
202
+ englishAlias: z.ZodNullable<z.ZodString>;
203
+ category: z.ZodString;
204
+ subcategory: z.ZodNullable<z.ZodString>;
205
+ servingLabel: z.ZodString;
206
+ servingQuantity: z.ZodNumber;
207
+ servingUnit: z.ZodEnum<{
208
+ g: "g";
209
+ ml: "ml";
210
+ }>;
211
+ calories: z.ZodNumber;
212
+ protein: z.ZodNumber;
213
+ carbs: z.ZodNumber;
214
+ fat: z.ZodNumber;
215
+ sugar: z.ZodNullable<z.ZodNumber>;
216
+ fiber: z.ZodNullable<z.ZodNumber>;
217
+ solubleFiber: z.ZodNullable<z.ZodNumber>;
218
+ insolubleFiber: z.ZodNullable<z.ZodNumber>;
219
+ tags: z.ZodArray<z.ZodString>;
220
+ mealTimings: z.ZodArray<z.ZodString>;
221
+ notes: z.ZodNullable<z.ZodString>;
222
+ }, z.core.$strict>;
223
+ export type SupplementImportRow = z.output<typeof supplementImportRowSchema>;
224
+ export declare const supplementImportIssueSchema: z.ZodObject<{
225
+ column: z.ZodNullable<z.ZodString>;
226
+ code: z.ZodString;
227
+ message: z.ZodString;
228
+ severity: z.ZodEnum<{
229
+ error: "error";
230
+ warning: "warning";
231
+ }>;
232
+ }, z.core.$strip>;
233
+ export type SupplementImportIssue = z.output<typeof supplementImportIssueSchema>;
234
+ export declare const commitSupplementImportSchema: z.ZodObject<{
235
+ mode: z.ZodDefault<z.ZodEnum<{
236
+ SKIP_DUPLICATES: "SKIP_DUPLICATES";
237
+ UPDATE_EXISTING: "UPDATE_EXISTING";
238
+ }>>;
239
+ createMissingMetadata: z.ZodDefault<z.ZodBoolean>;
240
+ }, z.core.$strict>;
241
+ export type CommitSupplementImportInput = z.input<typeof commitSupplementImportSchema>;
242
+ export type CommitSupplementImportBody = z.output<typeof commitSupplementImportSchema>;
243
+ export declare const supplementImportRowsQuerySchema: z.ZodObject<{
244
+ page: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
245
+ pageSize: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
246
+ status: z.ZodOptional<z.ZodEnum<{
247
+ valid: "valid";
248
+ invalid: "invalid";
249
+ duplicate_in_file: "duplicate_in_file";
250
+ duplicate_existing: "duplicate_existing";
251
+ }>>;
252
+ }, z.core.$strip>;
253
+ export type SupplementImportRowsQuery = z.output<typeof supplementImportRowsQuerySchema>;
254
+ export declare const supplementImportBatchResponseSchema: z.ZodObject<{
255
+ id: z.ZodUUID;
256
+ fileName: z.ZodString;
257
+ fileType: z.ZodEnum<{
258
+ csv: "csv";
259
+ xlsx: "xlsx";
260
+ }>;
261
+ sheetName: z.ZodNullable<z.ZodString>;
262
+ status: z.ZodEnum<{
263
+ previewed: "previewed";
264
+ completed: "completed";
265
+ expired: "expired";
266
+ }>;
267
+ mode: z.ZodNullable<z.ZodEnum<{
268
+ SKIP_DUPLICATES: "SKIP_DUPLICATES";
269
+ UPDATE_EXISTING: "UPDATE_EXISTING";
270
+ }>>;
271
+ createMissingMetadata: z.ZodBoolean;
272
+ totalRows: z.ZodNumber;
273
+ validRows: z.ZodNumber;
274
+ invalidRows: z.ZodNumber;
275
+ duplicateRows: z.ZodNumber;
276
+ emptyRows: z.ZodNumber;
277
+ createdCount: z.ZodNumber;
278
+ updatedCount: z.ZodNumber;
279
+ skippedCount: z.ZodNumber;
280
+ unknownMetadata: z.ZodArray<z.ZodObject<{
281
+ typeKey: z.ZodString;
282
+ values: z.ZodArray<z.ZodString>;
283
+ }, z.core.$strip>>;
284
+ ignoredColumns: z.ZodArray<z.ZodString>;
285
+ ignoredSheets: z.ZodArray<z.ZodString>;
286
+ uploadedBy: z.ZodUUID;
287
+ expiresAt: z.ZodISODateTime;
288
+ committedAt: z.ZodNullable<z.ZodISODateTime>;
289
+ createdAt: z.ZodISODateTime;
290
+ }, z.core.$strip>;
291
+ export type SupplementImportBatchResponse = z.output<typeof supplementImportBatchResponseSchema>;
292
+ export declare const supplementImportRowResponseSchema: z.ZodObject<{
293
+ id: z.ZodUUID;
294
+ rowNumber: z.ZodNumber;
295
+ status: z.ZodEnum<{
296
+ valid: "valid";
297
+ invalid: "invalid";
298
+ duplicate_in_file: "duplicate_in_file";
299
+ duplicate_existing: "duplicate_existing";
300
+ }>;
301
+ raw: z.ZodRecord<z.ZodString, z.ZodString>;
302
+ parsed: z.ZodNullable<z.ZodObject<{
303
+ name: z.ZodString;
304
+ nameHi: z.ZodNullable<z.ZodString>;
305
+ namePa: z.ZodNullable<z.ZodString>;
306
+ englishAlias: z.ZodNullable<z.ZodString>;
307
+ category: z.ZodString;
308
+ subcategory: z.ZodNullable<z.ZodString>;
309
+ servingLabel: z.ZodString;
310
+ servingQuantity: z.ZodNumber;
311
+ servingUnit: z.ZodEnum<{
312
+ g: "g";
313
+ ml: "ml";
314
+ }>;
315
+ calories: z.ZodNumber;
316
+ protein: z.ZodNumber;
317
+ carbs: z.ZodNumber;
318
+ fat: z.ZodNumber;
319
+ sugar: z.ZodNullable<z.ZodNumber>;
320
+ fiber: z.ZodNullable<z.ZodNumber>;
321
+ solubleFiber: z.ZodNullable<z.ZodNumber>;
322
+ insolubleFiber: z.ZodNullable<z.ZodNumber>;
323
+ tags: z.ZodArray<z.ZodString>;
324
+ mealTimings: z.ZodArray<z.ZodString>;
325
+ notes: z.ZodNullable<z.ZodString>;
326
+ }, z.core.$strict>>;
327
+ issues: z.ZodArray<z.ZodObject<{
328
+ column: z.ZodNullable<z.ZodString>;
329
+ code: z.ZodString;
330
+ message: z.ZodString;
331
+ severity: z.ZodEnum<{
332
+ error: "error";
333
+ warning: "warning";
334
+ }>;
335
+ }, z.core.$strip>>;
336
+ matchedSupplementId: z.ZodNullable<z.ZodUUID>;
337
+ resultSupplementId: z.ZodNullable<z.ZodUUID>;
338
+ }, z.core.$strip>;
339
+ export type SupplementImportRowResponse = z.output<typeof supplementImportRowResponseSchema>;
340
+ export declare const clientSupplementSearchQuerySchema: z.ZodObject<{
341
+ page: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
342
+ pageSize: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
343
+ search: z.ZodString;
344
+ }, z.core.$strip>;
345
+ export type ClientSupplementSearchQuery = z.output<typeof clientSupplementSearchQuerySchema>;
346
+ /** A library supplement as a client sees it: what they need to log it, and nothing about who added it. */
347
+ export declare const clientSupplementResponseSchema: z.ZodObject<{
348
+ id: z.ZodUUID;
349
+ name: z.ZodString;
350
+ nameHi: z.ZodNullable<z.ZodString>;
351
+ namePa: z.ZodNullable<z.ZodString>;
352
+ englishAlias: z.ZodNullable<z.ZodString>;
353
+ serving: z.ZodObject<{
354
+ label: z.ZodString;
355
+ quantity: z.ZodNumber;
356
+ unit: z.ZodEnum<{
357
+ g: "g";
358
+ ml: "ml";
359
+ }>;
360
+ }, z.core.$strip>;
361
+ per100: z.ZodObject<{
362
+ calories: z.ZodNumber;
363
+ protein: z.ZodNumber;
364
+ carbs: z.ZodNumber;
365
+ fat: z.ZodNumber;
366
+ sugar: z.ZodNullable<z.ZodNumber>;
367
+ fiber: z.ZodNullable<z.ZodNumber>;
368
+ solubleFiber: z.ZodNullable<z.ZodNumber>;
369
+ insolubleFiber: z.ZodNullable<z.ZodNumber>;
370
+ }, z.core.$strip>;
371
+ perServing: z.ZodObject<{
372
+ calories: z.ZodNumber;
373
+ protein: z.ZodNumber;
374
+ carbs: z.ZodNumber;
375
+ fat: z.ZodNumber;
376
+ sugar: z.ZodNullable<z.ZodNumber>;
377
+ fiber: z.ZodNullable<z.ZodNumber>;
378
+ solubleFiber: z.ZodNullable<z.ZodNumber>;
379
+ insolubleFiber: z.ZodNullable<z.ZodNumber>;
380
+ }, z.core.$strip>;
381
+ tags: z.ZodArray<z.ZodObject<{
382
+ typeKey: z.ZodString;
383
+ label: z.ZodString;
384
+ }, z.core.$strip>>;
385
+ }, z.core.$strip>;
386
+ export type ClientSupplementResponse = z.output<typeof clientSupplementResponseSchema>;
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.clientSupplementResponseSchema = exports.clientSupplementSearchQuerySchema = exports.supplementImportRowResponseSchema = exports.supplementImportBatchResponseSchema = exports.supplementImportRowsQuerySchema = exports.commitSupplementImportSchema = exports.supplementImportIssueSchema = exports.supplementImportRowSchema = exports.SUPPLEMENT_IMPORT_LIMITS = exports.supplementImportListQuerySchema = exports.importIdParamSchema = exports.userRefSchema = exports.supplementMetadataRefSchema = exports.servingResponseSchema = exports.nutrientValuesResponseSchema = exports.updateMetadataValueSchema = exports.metadataValueResponseSchema = exports.metadataValueListResponseSchema = exports.metadataValueListQuerySchema = exports.metadataValueIdParamSchema = exports.metadataTypeResponseSchema = exports.metadataTypeListResponseSchema = exports.metadataTypeIdParamSchema = exports.MAX_METADATA_LABEL_LENGTH = exports.createMetadataValueSchema = exports.supplementStatsResponseSchema = exports.similarSupplementResponseSchema = exports.supplementResponseSchema = exports.similarSupplementQuerySchema = exports.supplementListQuerySchema = exports.updateSupplementSchema = exports.createSupplementSchema = exports.supplementIdParamSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ const pagination_schema_js_1 = require("../common/pagination.schema.js");
6
+ const index_js_1 = require("../enums/index.js");
7
+ const client_food_dto_js_1 = require("../food/client-food.dto.js");
8
+ const food_dto_js_1 = require("../food/food.dto.js");
9
+ const food_import_dto_js_1 = require("../food/food-import.dto.js");
10
+ // The supplement library keeps the food library's spreadsheet format, nutrition
11
+ // rules and labels (category, subcategory, tags, meal timing), so its requests
12
+ // and most of its responses are the food library's own shapes. What differs:
13
+ // supplements come from an admin, a coach or an import (clients cannot suggest
14
+ // them), and responses name supplements rather than foods.
15
+ exports.supplementIdParamSchema = zod_1.z.uuid();
16
+ // Requests: the same fields and nutrition rules as a food.
17
+ exports.createSupplementSchema = food_dto_js_1.createFoodSchema;
18
+ exports.updateSupplementSchema = food_dto_js_1.updateFoodSchema;
19
+ /** Accepts `a,b` or a repeated parameter, as query strings carry either. */
20
+ const idListQuerySchema = zod_1.z.preprocess((value) => {
21
+ if (typeof value === 'string') {
22
+ return value === ''
23
+ ? undefined
24
+ : value
25
+ .split(',')
26
+ .map((part) => part.trim())
27
+ .filter(Boolean);
28
+ }
29
+ return value;
30
+ }, zod_1.z.array(zod_1.z.uuid()).max(50).optional());
31
+ /**
32
+ * `metadata` filters by value ids: a supplement matches when it has at least
33
+ * one of the listed values of each type that appears in the list.
34
+ */
35
+ exports.supplementListQuerySchema = pagination_schema_js_1.paginationQuerySchema.extend({
36
+ search: zod_1.z.string().trim().min(1).max(food_dto_js_1.MAX_FOOD_SEARCH_LENGTH).optional(),
37
+ metadata: idListQuerySchema,
38
+ source: zod_1.z.enum(index_js_1.SUPPLEMENT_SOURCES).optional(),
39
+ sort: zod_1.z.enum(index_js_1.FOOD_SORT_FIELDS).default('name'),
40
+ order: zod_1.z.enum(index_js_1.SORT_ORDERS).default('asc'),
41
+ });
42
+ exports.similarSupplementQuerySchema = zod_1.z.object({
43
+ name: food_dto_js_1.createFoodSchema.shape.name,
44
+ });
45
+ // Responses
46
+ exports.supplementResponseSchema = food_dto_js_1.foodResponseSchema
47
+ .omit({ suggestionId: true })
48
+ .extend({ source: zod_1.z.enum(index_js_1.SUPPLEMENT_SOURCES) });
49
+ exports.similarSupplementResponseSchema = zod_1.z.object({
50
+ id: zod_1.z.uuid(),
51
+ name: zod_1.z.string(),
52
+ serving: food_dto_js_1.servingResponseSchema,
53
+ per100: food_dto_js_1.nutrientValuesResponseSchema,
54
+ /** 0 to 1; 1 is the same name. */
55
+ similarity: zod_1.z.number(),
56
+ });
57
+ exports.supplementStatsResponseSchema = zod_1.z.object({
58
+ totalSupplements: zod_1.z.number().int(),
59
+ categories: zod_1.z.array(zod_1.z.object({
60
+ valueId: zod_1.z.uuid(),
61
+ key: zod_1.z.string(),
62
+ label: zod_1.z.string(),
63
+ count: zod_1.z.number().int(),
64
+ })),
65
+ });
66
+ // Labels: the same metadata shapes as the food library, kept in their own tables.
67
+ var food_dto_js_2 = require("../food/food.dto.js");
68
+ Object.defineProperty(exports, "createMetadataValueSchema", { enumerable: true, get: function () { return food_dto_js_2.createMetadataValueSchema; } });
69
+ Object.defineProperty(exports, "MAX_METADATA_LABEL_LENGTH", { enumerable: true, get: function () { return food_dto_js_2.MAX_METADATA_LABEL_LENGTH; } });
70
+ Object.defineProperty(exports, "metadataTypeIdParamSchema", { enumerable: true, get: function () { return food_dto_js_2.metadataTypeIdParamSchema; } });
71
+ Object.defineProperty(exports, "metadataTypeListResponseSchema", { enumerable: true, get: function () { return food_dto_js_2.metadataTypeListResponseSchema; } });
72
+ Object.defineProperty(exports, "metadataTypeResponseSchema", { enumerable: true, get: function () { return food_dto_js_2.metadataTypeResponseSchema; } });
73
+ Object.defineProperty(exports, "metadataValueIdParamSchema", { enumerable: true, get: function () { return food_dto_js_2.metadataValueIdParamSchema; } });
74
+ Object.defineProperty(exports, "metadataValueListQuerySchema", { enumerable: true, get: function () { return food_dto_js_2.metadataValueListQuerySchema; } });
75
+ Object.defineProperty(exports, "metadataValueListResponseSchema", { enumerable: true, get: function () { return food_dto_js_2.metadataValueListResponseSchema; } });
76
+ Object.defineProperty(exports, "metadataValueResponseSchema", { enumerable: true, get: function () { return food_dto_js_2.metadataValueResponseSchema; } });
77
+ Object.defineProperty(exports, "updateMetadataValueSchema", { enumerable: true, get: function () { return food_dto_js_2.updateMetadataValueSchema; } });
78
+ // Shapes inside a supplement response, shared with the food library.
79
+ var food_dto_js_3 = require("../food/food.dto.js");
80
+ Object.defineProperty(exports, "nutrientValuesResponseSchema", { enumerable: true, get: function () { return food_dto_js_3.nutrientValuesResponseSchema; } });
81
+ Object.defineProperty(exports, "servingResponseSchema", { enumerable: true, get: function () { return food_dto_js_3.servingResponseSchema; } });
82
+ Object.defineProperty(exports, "supplementMetadataRefSchema", { enumerable: true, get: function () { return food_dto_js_3.foodMetadataRefSchema; } });
83
+ Object.defineProperty(exports, "userRefSchema", { enumerable: true, get: function () { return food_dto_js_3.userRefSchema; } });
84
+ // Import: the food library's spreadsheet format and limits.
85
+ var food_import_dto_js_2 = require("../food/food-import.dto.js");
86
+ Object.defineProperty(exports, "importIdParamSchema", { enumerable: true, get: function () { return food_import_dto_js_2.importIdParamSchema; } });
87
+ Object.defineProperty(exports, "supplementImportListQuerySchema", { enumerable: true, get: function () { return food_import_dto_js_2.foodImportListQuerySchema; } });
88
+ exports.SUPPLEMENT_IMPORT_LIMITS = food_import_dto_js_1.FOOD_IMPORT_LIMITS;
89
+ exports.supplementImportRowSchema = food_import_dto_js_1.foodImportRowSchema;
90
+ exports.supplementImportIssueSchema = food_import_dto_js_1.foodImportIssueSchema;
91
+ exports.commitSupplementImportSchema = food_import_dto_js_1.commitFoodImportSchema;
92
+ exports.supplementImportRowsQuerySchema = food_import_dto_js_1.foodImportRowsQuerySchema;
93
+ exports.supplementImportBatchResponseSchema = food_import_dto_js_1.foodImportBatchResponseSchema;
94
+ exports.supplementImportRowResponseSchema = zod_1.z.object({
95
+ id: zod_1.z.uuid(),
96
+ /** The row number as the spreadsheet shows it, counting the header as row 1. */
97
+ rowNumber: zod_1.z.number().int(),
98
+ status: zod_1.z.enum(index_js_1.FOOD_IMPORT_ROW_STATUSES),
99
+ /** The cells as read, by header. */
100
+ raw: zod_1.z.record(zod_1.z.string(), zod_1.z.string()),
101
+ parsed: exports.supplementImportRowSchema.nullable(),
102
+ issues: zod_1.z.array(exports.supplementImportIssueSchema),
103
+ matchedSupplementId: zod_1.z.uuid().nullable(),
104
+ resultSupplementId: zod_1.z.uuid().nullable(),
105
+ });
106
+ // Client: search only.
107
+ exports.clientSupplementSearchQuerySchema = client_food_dto_js_1.clientFoodSearchQuerySchema;
108
+ /** A library supplement as a client sees it: what they need to log it, and nothing about who added it. */
109
+ exports.clientSupplementResponseSchema = client_food_dto_js_1.clientFoodResponseSchema;
@@ -37,6 +37,13 @@ export declare const PERMISSIONS: {
37
37
  readonly goalsUpdate: "goals:update";
38
38
  readonly goalsDelete: "goals:delete";
39
39
  readonly goalsReadSelf: "goals:read:self";
40
+ readonly supplementsRead: "supplements:read";
41
+ readonly supplementsCreate: "supplements:create";
42
+ readonly supplementsUpdate: "supplements:update";
43
+ readonly supplementsDelete: "supplements:delete";
44
+ readonly supplementsImport: "supplements:import";
45
+ readonly supplementMetadataManage: "supplement_metadata:manage";
46
+ readonly supplementsSearchSelf: "supplements:search:self";
40
47
  };
41
48
  export type Permission = (typeof PERMISSIONS)[keyof typeof PERMISSIONS];
42
49
  /**
@@ -137,3 +144,6 @@ export declare const GOAL_STATUSES: readonly ["active", "inactive"];
137
144
  export type GoalStatus = (typeof GOAL_STATUSES)[number];
138
145
  export declare const GOAL_SORT_FIELDS: readonly ["sortOrder", "name", "updatedAt"];
139
146
  export type GoalSortField = (typeof GOAL_SORT_FIELDS)[number];
147
+ /** Where a supplement came from. Clients cannot suggest supplements, unlike foods. */
148
+ export declare const SUPPLEMENT_SOURCES: readonly ["ADMIN", "COACH", "IMPORT"];
149
+ export type SupplementSource = (typeof SUPPLEMENT_SOURCES)[number];
@@ -41,6 +41,13 @@ export const PERMISSIONS = {
41
41
  goalsUpdate: 'goals:update',
42
42
  goalsDelete: 'goals:delete',
43
43
  goalsReadSelf: 'goals:read:self',
44
+ supplementsRead: 'supplements:read',
45
+ supplementsCreate: 'supplements:create',
46
+ supplementsUpdate: 'supplements:update',
47
+ supplementsDelete: 'supplements:delete',
48
+ supplementsImport: 'supplements:import',
49
+ supplementMetadataManage: 'supplement_metadata:manage',
50
+ supplementsSearchSelf: 'supplements:search:self',
44
51
  };
45
52
  /**
46
53
  * `pending_verification` is a self-registered user who has not yet proved they
@@ -174,3 +181,5 @@ export const GOAL_ICONS = [
174
181
  /** Whether clients can pick a goal: live goals are either shown or hidden. */
175
182
  export const GOAL_STATUSES = ['active', 'inactive'];
176
183
  export const GOAL_SORT_FIELDS = ['sortOrder', 'name', 'updatedAt'];
184
+ /** Where a supplement came from. Clients cannot suggest supplements, unlike foods. */
185
+ export const SUPPLEMENT_SOURCES = ['ADMIN', 'COACH', 'IMPORT'];
@@ -0,0 +1 @@
1
+ export * from './supplement.dto.js';
@@ -0,0 +1 @@
1
+ export * from './supplement.dto.js';
@@ -0,0 +1,386 @@
1
+ import { z } from 'zod';
2
+ export declare const supplementIdParamSchema: z.ZodUUID;
3
+ export declare const createSupplementSchema: z.ZodObject<{
4
+ name: z.ZodString;
5
+ nameHi: z.ZodOptional<z.ZodNullable<z.ZodString>>;
6
+ namePa: z.ZodOptional<z.ZodNullable<z.ZodString>>;
7
+ englishAlias: z.ZodOptional<z.ZodNullable<z.ZodString>>;
8
+ serving: z.ZodObject<{
9
+ label: z.ZodString;
10
+ quantity: z.ZodNumber;
11
+ unit: z.ZodEnum<{
12
+ g: "g";
13
+ ml: "ml";
14
+ }>;
15
+ }, z.core.$strict>;
16
+ nutrition: z.ZodObject<{
17
+ basis: z.ZodDefault<z.ZodEnum<{
18
+ PER_100: "PER_100";
19
+ PER_SERVING: "PER_SERVING";
20
+ }>>;
21
+ calories: z.ZodNumber;
22
+ protein: z.ZodNumber;
23
+ carbs: z.ZodNumber;
24
+ fat: z.ZodNumber;
25
+ sugar: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
26
+ fiber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
27
+ solubleFiber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
28
+ insolubleFiber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
29
+ }, z.core.$strict>;
30
+ metadataValueIds: z.ZodDefault<z.ZodArray<z.ZodUUID>>;
31
+ notes: z.ZodOptional<z.ZodNullable<z.ZodString>>;
32
+ }, z.core.$strict>;
33
+ export type CreateSupplementInput = z.input<typeof createSupplementSchema>;
34
+ export type CreateSupplementBody = z.output<typeof createSupplementSchema>;
35
+ export declare const updateSupplementSchema: z.ZodObject<{
36
+ version: z.ZodNumber;
37
+ name: z.ZodOptional<z.ZodString>;
38
+ nameHi: z.ZodOptional<z.ZodNullable<z.ZodString>>;
39
+ namePa: z.ZodOptional<z.ZodNullable<z.ZodString>>;
40
+ englishAlias: z.ZodOptional<z.ZodNullable<z.ZodString>>;
41
+ serving: z.ZodOptional<z.ZodObject<{
42
+ label: z.ZodString;
43
+ quantity: z.ZodNumber;
44
+ unit: z.ZodEnum<{
45
+ g: "g";
46
+ ml: "ml";
47
+ }>;
48
+ }, z.core.$strict>>;
49
+ nutrition: z.ZodOptional<z.ZodObject<{
50
+ basis: z.ZodDefault<z.ZodEnum<{
51
+ PER_100: "PER_100";
52
+ PER_SERVING: "PER_SERVING";
53
+ }>>;
54
+ calories: z.ZodNumber;
55
+ protein: z.ZodNumber;
56
+ carbs: z.ZodNumber;
57
+ fat: z.ZodNumber;
58
+ sugar: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
59
+ fiber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
60
+ solubleFiber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
61
+ insolubleFiber: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
62
+ }, z.core.$strict>>;
63
+ metadataValueIds: z.ZodOptional<z.ZodArray<z.ZodUUID>>;
64
+ notes: z.ZodOptional<z.ZodNullable<z.ZodString>>;
65
+ }, z.core.$strict>;
66
+ export type UpdateSupplementInput = z.input<typeof updateSupplementSchema>;
67
+ export type UpdateSupplementBody = z.output<typeof updateSupplementSchema>;
68
+ /**
69
+ * `metadata` filters by value ids: a supplement matches when it has at least
70
+ * one of the listed values of each type that appears in the list.
71
+ */
72
+ export declare const supplementListQuerySchema: z.ZodObject<{
73
+ page: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
74
+ pageSize: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
75
+ search: z.ZodOptional<z.ZodString>;
76
+ metadata: z.ZodPreprocess<z.ZodOptional<z.ZodArray<z.ZodUUID>>, unknown>;
77
+ source: z.ZodOptional<z.ZodEnum<{
78
+ ADMIN: "ADMIN";
79
+ COACH: "COACH";
80
+ IMPORT: "IMPORT";
81
+ }>>;
82
+ sort: z.ZodDefault<z.ZodEnum<{
83
+ name: "name";
84
+ updatedAt: "updatedAt";
85
+ calories: "calories";
86
+ protein: "protein";
87
+ }>>;
88
+ order: z.ZodDefault<z.ZodEnum<{
89
+ asc: "asc";
90
+ desc: "desc";
91
+ }>>;
92
+ }, z.core.$strip>;
93
+ export type SupplementListQuery = z.output<typeof supplementListQuerySchema>;
94
+ export declare const similarSupplementQuerySchema: z.ZodObject<{
95
+ name: z.ZodString;
96
+ }, z.core.$strip>;
97
+ export type SimilarSupplementQuery = z.output<typeof similarSupplementQuerySchema>;
98
+ export declare const supplementResponseSchema: z.ZodObject<{
99
+ metadata: z.ZodArray<z.ZodObject<{
100
+ id: z.ZodUUID;
101
+ typeKey: z.ZodString;
102
+ key: z.ZodString;
103
+ label: z.ZodString;
104
+ isActive: z.ZodBoolean;
105
+ }, z.core.$strip>>;
106
+ name: z.ZodString;
107
+ updatedAt: z.ZodISODateTime;
108
+ id: z.ZodUUID;
109
+ version: z.ZodNumber;
110
+ createdAt: z.ZodISODateTime;
111
+ createdBy: z.ZodObject<{
112
+ id: z.ZodUUID;
113
+ fullName: z.ZodNullable<z.ZodString>;
114
+ }, z.core.$strip>;
115
+ nameHi: z.ZodNullable<z.ZodString>;
116
+ namePa: z.ZodNullable<z.ZodString>;
117
+ englishAlias: z.ZodNullable<z.ZodString>;
118
+ serving: z.ZodObject<{
119
+ label: z.ZodString;
120
+ quantity: z.ZodNumber;
121
+ unit: z.ZodEnum<{
122
+ g: "g";
123
+ ml: "ml";
124
+ }>;
125
+ }, z.core.$strip>;
126
+ notes: z.ZodNullable<z.ZodString>;
127
+ per100: z.ZodObject<{
128
+ calories: z.ZodNumber;
129
+ protein: z.ZodNumber;
130
+ carbs: z.ZodNumber;
131
+ fat: z.ZodNumber;
132
+ sugar: z.ZodNullable<z.ZodNumber>;
133
+ fiber: z.ZodNullable<z.ZodNumber>;
134
+ solubleFiber: z.ZodNullable<z.ZodNumber>;
135
+ insolubleFiber: z.ZodNullable<z.ZodNumber>;
136
+ }, z.core.$strip>;
137
+ perServing: z.ZodObject<{
138
+ calories: z.ZodNumber;
139
+ protein: z.ZodNumber;
140
+ carbs: z.ZodNumber;
141
+ fat: z.ZodNumber;
142
+ sugar: z.ZodNullable<z.ZodNumber>;
143
+ fiber: z.ZodNullable<z.ZodNumber>;
144
+ solubleFiber: z.ZodNullable<z.ZodNumber>;
145
+ insolubleFiber: z.ZodNullable<z.ZodNumber>;
146
+ }, z.core.$strip>;
147
+ formulaCalories: z.ZodNumber;
148
+ importBatchId: z.ZodNullable<z.ZodUUID>;
149
+ source: z.ZodEnum<{
150
+ ADMIN: "ADMIN";
151
+ COACH: "COACH";
152
+ IMPORT: "IMPORT";
153
+ }>;
154
+ }, z.core.$strip>;
155
+ export type SupplementResponse = z.output<typeof supplementResponseSchema>;
156
+ export declare const similarSupplementResponseSchema: z.ZodObject<{
157
+ id: z.ZodUUID;
158
+ name: z.ZodString;
159
+ serving: z.ZodObject<{
160
+ label: z.ZodString;
161
+ quantity: z.ZodNumber;
162
+ unit: z.ZodEnum<{
163
+ g: "g";
164
+ ml: "ml";
165
+ }>;
166
+ }, z.core.$strip>;
167
+ per100: z.ZodObject<{
168
+ calories: z.ZodNumber;
169
+ protein: z.ZodNumber;
170
+ carbs: z.ZodNumber;
171
+ fat: z.ZodNumber;
172
+ sugar: z.ZodNullable<z.ZodNumber>;
173
+ fiber: z.ZodNullable<z.ZodNumber>;
174
+ solubleFiber: z.ZodNullable<z.ZodNumber>;
175
+ insolubleFiber: z.ZodNullable<z.ZodNumber>;
176
+ }, z.core.$strip>;
177
+ similarity: z.ZodNumber;
178
+ }, z.core.$strip>;
179
+ export type SimilarSupplementResponse = z.output<typeof similarSupplementResponseSchema>;
180
+ export declare const supplementStatsResponseSchema: z.ZodObject<{
181
+ totalSupplements: z.ZodNumber;
182
+ categories: z.ZodArray<z.ZodObject<{
183
+ valueId: z.ZodUUID;
184
+ key: z.ZodString;
185
+ label: z.ZodString;
186
+ count: z.ZodNumber;
187
+ }, z.core.$strip>>;
188
+ }, z.core.$strip>;
189
+ export type SupplementStatsResponse = z.output<typeof supplementStatsResponseSchema>;
190
+ export { type CreateMetadataValueBody, type CreateMetadataValueInput, createMetadataValueSchema, MAX_METADATA_LABEL_LENGTH, metadataTypeIdParamSchema, metadataTypeListResponseSchema, type MetadataTypeResponse, metadataTypeResponseSchema, metadataValueIdParamSchema, type MetadataValueListQuery, metadataValueListQuerySchema, metadataValueListResponseSchema, type MetadataValueResponse, metadataValueResponseSchema, type UpdateMetadataValueBody, type UpdateMetadataValueInput, updateMetadataValueSchema, } from '../food/food.dto.js';
191
+ export { type NutrientValuesResponse, nutrientValuesResponseSchema, type ServingResponse, servingResponseSchema, type FoodMetadataRef as SupplementMetadataRef, foodMetadataRefSchema as supplementMetadataRefSchema, type UserRef, userRefSchema, } from '../food/food.dto.js';
192
+ export { importIdParamSchema, foodImportListQuerySchema as supplementImportListQuerySchema, } from '../food/food-import.dto.js';
193
+ export declare const SUPPLEMENT_IMPORT_LIMITS: {
194
+ readonly maxFileBytes: number;
195
+ readonly maxRows: 5000;
196
+ readonly extensions: readonly [".csv", ".xlsx"];
197
+ };
198
+ export declare const supplementImportRowSchema: z.ZodObject<{
199
+ name: z.ZodString;
200
+ nameHi: z.ZodNullable<z.ZodString>;
201
+ namePa: z.ZodNullable<z.ZodString>;
202
+ englishAlias: z.ZodNullable<z.ZodString>;
203
+ category: z.ZodString;
204
+ subcategory: z.ZodNullable<z.ZodString>;
205
+ servingLabel: z.ZodString;
206
+ servingQuantity: z.ZodNumber;
207
+ servingUnit: z.ZodEnum<{
208
+ g: "g";
209
+ ml: "ml";
210
+ }>;
211
+ calories: z.ZodNumber;
212
+ protein: z.ZodNumber;
213
+ carbs: z.ZodNumber;
214
+ fat: z.ZodNumber;
215
+ sugar: z.ZodNullable<z.ZodNumber>;
216
+ fiber: z.ZodNullable<z.ZodNumber>;
217
+ solubleFiber: z.ZodNullable<z.ZodNumber>;
218
+ insolubleFiber: z.ZodNullable<z.ZodNumber>;
219
+ tags: z.ZodArray<z.ZodString>;
220
+ mealTimings: z.ZodArray<z.ZodString>;
221
+ notes: z.ZodNullable<z.ZodString>;
222
+ }, z.core.$strict>;
223
+ export type SupplementImportRow = z.output<typeof supplementImportRowSchema>;
224
+ export declare const supplementImportIssueSchema: z.ZodObject<{
225
+ column: z.ZodNullable<z.ZodString>;
226
+ code: z.ZodString;
227
+ message: z.ZodString;
228
+ severity: z.ZodEnum<{
229
+ error: "error";
230
+ warning: "warning";
231
+ }>;
232
+ }, z.core.$strip>;
233
+ export type SupplementImportIssue = z.output<typeof supplementImportIssueSchema>;
234
+ export declare const commitSupplementImportSchema: z.ZodObject<{
235
+ mode: z.ZodDefault<z.ZodEnum<{
236
+ SKIP_DUPLICATES: "SKIP_DUPLICATES";
237
+ UPDATE_EXISTING: "UPDATE_EXISTING";
238
+ }>>;
239
+ createMissingMetadata: z.ZodDefault<z.ZodBoolean>;
240
+ }, z.core.$strict>;
241
+ export type CommitSupplementImportInput = z.input<typeof commitSupplementImportSchema>;
242
+ export type CommitSupplementImportBody = z.output<typeof commitSupplementImportSchema>;
243
+ export declare const supplementImportRowsQuerySchema: z.ZodObject<{
244
+ page: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
245
+ pageSize: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
246
+ status: z.ZodOptional<z.ZodEnum<{
247
+ valid: "valid";
248
+ invalid: "invalid";
249
+ duplicate_in_file: "duplicate_in_file";
250
+ duplicate_existing: "duplicate_existing";
251
+ }>>;
252
+ }, z.core.$strip>;
253
+ export type SupplementImportRowsQuery = z.output<typeof supplementImportRowsQuerySchema>;
254
+ export declare const supplementImportBatchResponseSchema: z.ZodObject<{
255
+ id: z.ZodUUID;
256
+ fileName: z.ZodString;
257
+ fileType: z.ZodEnum<{
258
+ csv: "csv";
259
+ xlsx: "xlsx";
260
+ }>;
261
+ sheetName: z.ZodNullable<z.ZodString>;
262
+ status: z.ZodEnum<{
263
+ previewed: "previewed";
264
+ completed: "completed";
265
+ expired: "expired";
266
+ }>;
267
+ mode: z.ZodNullable<z.ZodEnum<{
268
+ SKIP_DUPLICATES: "SKIP_DUPLICATES";
269
+ UPDATE_EXISTING: "UPDATE_EXISTING";
270
+ }>>;
271
+ createMissingMetadata: z.ZodBoolean;
272
+ totalRows: z.ZodNumber;
273
+ validRows: z.ZodNumber;
274
+ invalidRows: z.ZodNumber;
275
+ duplicateRows: z.ZodNumber;
276
+ emptyRows: z.ZodNumber;
277
+ createdCount: z.ZodNumber;
278
+ updatedCount: z.ZodNumber;
279
+ skippedCount: z.ZodNumber;
280
+ unknownMetadata: z.ZodArray<z.ZodObject<{
281
+ typeKey: z.ZodString;
282
+ values: z.ZodArray<z.ZodString>;
283
+ }, z.core.$strip>>;
284
+ ignoredColumns: z.ZodArray<z.ZodString>;
285
+ ignoredSheets: z.ZodArray<z.ZodString>;
286
+ uploadedBy: z.ZodUUID;
287
+ expiresAt: z.ZodISODateTime;
288
+ committedAt: z.ZodNullable<z.ZodISODateTime>;
289
+ createdAt: z.ZodISODateTime;
290
+ }, z.core.$strip>;
291
+ export type SupplementImportBatchResponse = z.output<typeof supplementImportBatchResponseSchema>;
292
+ export declare const supplementImportRowResponseSchema: z.ZodObject<{
293
+ id: z.ZodUUID;
294
+ rowNumber: z.ZodNumber;
295
+ status: z.ZodEnum<{
296
+ valid: "valid";
297
+ invalid: "invalid";
298
+ duplicate_in_file: "duplicate_in_file";
299
+ duplicate_existing: "duplicate_existing";
300
+ }>;
301
+ raw: z.ZodRecord<z.ZodString, z.ZodString>;
302
+ parsed: z.ZodNullable<z.ZodObject<{
303
+ name: z.ZodString;
304
+ nameHi: z.ZodNullable<z.ZodString>;
305
+ namePa: z.ZodNullable<z.ZodString>;
306
+ englishAlias: z.ZodNullable<z.ZodString>;
307
+ category: z.ZodString;
308
+ subcategory: z.ZodNullable<z.ZodString>;
309
+ servingLabel: z.ZodString;
310
+ servingQuantity: z.ZodNumber;
311
+ servingUnit: z.ZodEnum<{
312
+ g: "g";
313
+ ml: "ml";
314
+ }>;
315
+ calories: z.ZodNumber;
316
+ protein: z.ZodNumber;
317
+ carbs: z.ZodNumber;
318
+ fat: z.ZodNumber;
319
+ sugar: z.ZodNullable<z.ZodNumber>;
320
+ fiber: z.ZodNullable<z.ZodNumber>;
321
+ solubleFiber: z.ZodNullable<z.ZodNumber>;
322
+ insolubleFiber: z.ZodNullable<z.ZodNumber>;
323
+ tags: z.ZodArray<z.ZodString>;
324
+ mealTimings: z.ZodArray<z.ZodString>;
325
+ notes: z.ZodNullable<z.ZodString>;
326
+ }, z.core.$strict>>;
327
+ issues: z.ZodArray<z.ZodObject<{
328
+ column: z.ZodNullable<z.ZodString>;
329
+ code: z.ZodString;
330
+ message: z.ZodString;
331
+ severity: z.ZodEnum<{
332
+ error: "error";
333
+ warning: "warning";
334
+ }>;
335
+ }, z.core.$strip>>;
336
+ matchedSupplementId: z.ZodNullable<z.ZodUUID>;
337
+ resultSupplementId: z.ZodNullable<z.ZodUUID>;
338
+ }, z.core.$strip>;
339
+ export type SupplementImportRowResponse = z.output<typeof supplementImportRowResponseSchema>;
340
+ export declare const clientSupplementSearchQuerySchema: z.ZodObject<{
341
+ page: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
342
+ pageSize: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
343
+ search: z.ZodString;
344
+ }, z.core.$strip>;
345
+ export type ClientSupplementSearchQuery = z.output<typeof clientSupplementSearchQuerySchema>;
346
+ /** A library supplement as a client sees it: what they need to log it, and nothing about who added it. */
347
+ export declare const clientSupplementResponseSchema: z.ZodObject<{
348
+ id: z.ZodUUID;
349
+ name: z.ZodString;
350
+ nameHi: z.ZodNullable<z.ZodString>;
351
+ namePa: z.ZodNullable<z.ZodString>;
352
+ englishAlias: z.ZodNullable<z.ZodString>;
353
+ serving: z.ZodObject<{
354
+ label: z.ZodString;
355
+ quantity: z.ZodNumber;
356
+ unit: z.ZodEnum<{
357
+ g: "g";
358
+ ml: "ml";
359
+ }>;
360
+ }, z.core.$strip>;
361
+ per100: z.ZodObject<{
362
+ calories: z.ZodNumber;
363
+ protein: z.ZodNumber;
364
+ carbs: z.ZodNumber;
365
+ fat: z.ZodNumber;
366
+ sugar: z.ZodNullable<z.ZodNumber>;
367
+ fiber: z.ZodNullable<z.ZodNumber>;
368
+ solubleFiber: z.ZodNullable<z.ZodNumber>;
369
+ insolubleFiber: z.ZodNullable<z.ZodNumber>;
370
+ }, z.core.$strip>;
371
+ perServing: z.ZodObject<{
372
+ calories: z.ZodNumber;
373
+ protein: z.ZodNumber;
374
+ carbs: z.ZodNumber;
375
+ fat: z.ZodNumber;
376
+ sugar: z.ZodNullable<z.ZodNumber>;
377
+ fiber: z.ZodNullable<z.ZodNumber>;
378
+ solubleFiber: z.ZodNullable<z.ZodNumber>;
379
+ insolubleFiber: z.ZodNullable<z.ZodNumber>;
380
+ }, z.core.$strip>;
381
+ tags: z.ZodArray<z.ZodObject<{
382
+ typeKey: z.ZodString;
383
+ label: z.ZodString;
384
+ }, z.core.$strip>>;
385
+ }, z.core.$strip>;
386
+ export type ClientSupplementResponse = z.output<typeof clientSupplementResponseSchema>;
@@ -0,0 +1,90 @@
1
+ import { z } from 'zod';
2
+ import { paginationQuerySchema } from '../common/pagination.schema.js';
3
+ import { FOOD_IMPORT_ROW_STATUSES, FOOD_SORT_FIELDS, SORT_ORDERS, SUPPLEMENT_SOURCES, } from '../enums/index.js';
4
+ import { clientFoodResponseSchema, clientFoodSearchQuerySchema, } from '../food/client-food.dto.js';
5
+ import { createFoodSchema, foodResponseSchema, MAX_FOOD_SEARCH_LENGTH, nutrientValuesResponseSchema, servingResponseSchema, updateFoodSchema, } from '../food/food.dto.js';
6
+ import { commitFoodImportSchema, FOOD_IMPORT_LIMITS, foodImportBatchResponseSchema, foodImportIssueSchema, foodImportRowSchema, foodImportRowsQuerySchema, } from '../food/food-import.dto.js';
7
+ // The supplement library keeps the food library's spreadsheet format, nutrition
8
+ // rules and labels (category, subcategory, tags, meal timing), so its requests
9
+ // and most of its responses are the food library's own shapes. What differs:
10
+ // supplements come from an admin, a coach or an import (clients cannot suggest
11
+ // them), and responses name supplements rather than foods.
12
+ export const supplementIdParamSchema = z.uuid();
13
+ // Requests: the same fields and nutrition rules as a food.
14
+ export const createSupplementSchema = createFoodSchema;
15
+ export const updateSupplementSchema = updateFoodSchema;
16
+ /** Accepts `a,b` or a repeated parameter, as query strings carry either. */
17
+ const idListQuerySchema = z.preprocess((value) => {
18
+ if (typeof value === 'string') {
19
+ return value === ''
20
+ ? undefined
21
+ : value
22
+ .split(',')
23
+ .map((part) => part.trim())
24
+ .filter(Boolean);
25
+ }
26
+ return value;
27
+ }, z.array(z.uuid()).max(50).optional());
28
+ /**
29
+ * `metadata` filters by value ids: a supplement matches when it has at least
30
+ * one of the listed values of each type that appears in the list.
31
+ */
32
+ export const supplementListQuerySchema = paginationQuerySchema.extend({
33
+ search: z.string().trim().min(1).max(MAX_FOOD_SEARCH_LENGTH).optional(),
34
+ metadata: idListQuerySchema,
35
+ source: z.enum(SUPPLEMENT_SOURCES).optional(),
36
+ sort: z.enum(FOOD_SORT_FIELDS).default('name'),
37
+ order: z.enum(SORT_ORDERS).default('asc'),
38
+ });
39
+ export const similarSupplementQuerySchema = z.object({
40
+ name: createFoodSchema.shape.name,
41
+ });
42
+ // Responses
43
+ export const supplementResponseSchema = foodResponseSchema
44
+ .omit({ suggestionId: true })
45
+ .extend({ source: z.enum(SUPPLEMENT_SOURCES) });
46
+ export const similarSupplementResponseSchema = z.object({
47
+ id: z.uuid(),
48
+ name: z.string(),
49
+ serving: servingResponseSchema,
50
+ per100: nutrientValuesResponseSchema,
51
+ /** 0 to 1; 1 is the same name. */
52
+ similarity: z.number(),
53
+ });
54
+ export const supplementStatsResponseSchema = z.object({
55
+ totalSupplements: z.number().int(),
56
+ categories: z.array(z.object({
57
+ valueId: z.uuid(),
58
+ key: z.string(),
59
+ label: z.string(),
60
+ count: z.number().int(),
61
+ })),
62
+ });
63
+ // Labels: the same metadata shapes as the food library, kept in their own tables.
64
+ export { createMetadataValueSchema, MAX_METADATA_LABEL_LENGTH, metadataTypeIdParamSchema, metadataTypeListResponseSchema, metadataTypeResponseSchema, metadataValueIdParamSchema, metadataValueListQuerySchema, metadataValueListResponseSchema, metadataValueResponseSchema, updateMetadataValueSchema, } from '../food/food.dto.js';
65
+ // Shapes inside a supplement response, shared with the food library.
66
+ export { nutrientValuesResponseSchema, servingResponseSchema, foodMetadataRefSchema as supplementMetadataRefSchema, userRefSchema, } from '../food/food.dto.js';
67
+ // Import: the food library's spreadsheet format and limits.
68
+ export { importIdParamSchema, foodImportListQuerySchema as supplementImportListQuerySchema, } from '../food/food-import.dto.js';
69
+ export const SUPPLEMENT_IMPORT_LIMITS = FOOD_IMPORT_LIMITS;
70
+ export const supplementImportRowSchema = foodImportRowSchema;
71
+ export const supplementImportIssueSchema = foodImportIssueSchema;
72
+ export const commitSupplementImportSchema = commitFoodImportSchema;
73
+ export const supplementImportRowsQuerySchema = foodImportRowsQuerySchema;
74
+ export const supplementImportBatchResponseSchema = foodImportBatchResponseSchema;
75
+ export const supplementImportRowResponseSchema = z.object({
76
+ id: z.uuid(),
77
+ /** The row number as the spreadsheet shows it, counting the header as row 1. */
78
+ rowNumber: z.number().int(),
79
+ status: z.enum(FOOD_IMPORT_ROW_STATUSES),
80
+ /** The cells as read, by header. */
81
+ raw: z.record(z.string(), z.string()),
82
+ parsed: supplementImportRowSchema.nullable(),
83
+ issues: z.array(supplementImportIssueSchema),
84
+ matchedSupplementId: z.uuid().nullable(),
85
+ resultSupplementId: z.uuid().nullable(),
86
+ });
87
+ // Client: search only.
88
+ export const clientSupplementSearchQuerySchema = clientFoodSearchQuerySchema;
89
+ /** A library supplement as a client sees it: what they need to log it, and nothing about who added it. */
90
+ export const clientSupplementResponseSchema = clientFoodResponseSchema;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tbs-fitness/contract",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "TBS API contract: Zod schemas, DTO types and enums shared by the backend, web and mobile apps",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -123,6 +123,16 @@
123
123
  "default": "./dist/cjs/goals/index.js"
124
124
  }
125
125
  },
126
+ "./supplements": {
127
+ "import": {
128
+ "types": "./dist/esm/supplements/index.d.ts",
129
+ "default": "./dist/esm/supplements/index.js"
130
+ },
131
+ "require": {
132
+ "types": "./dist/cjs/supplements/index.d.ts",
133
+ "default": "./dist/cjs/supplements/index.js"
134
+ }
135
+ },
126
136
  "./package.json": "./package.json"
127
137
  },
128
138
  "scripts": {