gymmonk-schema 0.57.0 → 0.58.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/dist/common.d.ts +14 -0
- package/dist/common.d.ts.map +1 -1
- package/dist/common.js +20 -0
- package/dist/common.js.map +1 -1
- package/dist/diet-chart.d.ts +50 -0
- package/dist/diet-chart.d.ts.map +1 -1
- package/dist/exercise.d.ts +142 -8
- package/dist/exercise.d.ts.map +1 -1
- package/dist/exercise.js +74 -5
- package/dist/exercise.js.map +1 -1
- package/dist/food.d.ts +265 -0
- package/dist/food.d.ts.map +1 -1
- package/dist/food.js +286 -2
- package/dist/food.js.map +1 -1
- package/dist/upi.d.ts.map +1 -1
- package/dist/upi.js +1 -1
- package/dist/upi.js.map +1 -1
- package/package.json +1 -1
package/dist/common.d.ts
CHANGED
|
@@ -15,6 +15,20 @@ import { z } from 'zod';
|
|
|
15
15
|
export declare const emptyStringToUndefined: (value: unknown) => unknown;
|
|
16
16
|
/** Optional trimmed string. Empty strings are coerced to `undefined`. */
|
|
17
17
|
export declare const optionalTrimmedString: (maxLength?: number) => z.ZodPreprocess<z.ZodOptional<z.ZodString>>;
|
|
18
|
+
/**
|
|
19
|
+
* A multi-select filter carried in a query string.
|
|
20
|
+
*
|
|
21
|
+
* A URL has no array type, so a multi-select arrives either as
|
|
22
|
+
* `?muscle=chest,back` or, if the client used `URLSearchParams.append`, as
|
|
23
|
+
* repeated keys that Express hands over as a real array. Both are accepted and
|
|
24
|
+
* normalised to one array, because which one a caller produces is an accident
|
|
25
|
+
* of how it built the URL, not a different intent.
|
|
26
|
+
*
|
|
27
|
+
* Unknown values are DROPPED rather than rejected: a filter chip that is
|
|
28
|
+
* renamed or removed should stop matching, not 422 the whole screen for
|
|
29
|
+
* somebody who still has the old URL open.
|
|
30
|
+
*/
|
|
31
|
+
export declare const csvOf: <T extends z.ZodTypeAny>(member: T) => z.ZodPreprocess<z.ZodOptional<z.ZodArray<T>>>;
|
|
18
32
|
/** A 24-character hex MongoDB ObjectId string. */
|
|
19
33
|
export declare const objectIdSchema: z.ZodString;
|
|
20
34
|
export type ObjectIdString = z.infer<typeof objectIdSchema>;
|
package/dist/common.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../src/common.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GAAI,OAAO,OAAO,KAAG,OAGvD,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,qBAAqB,GAAI,kBAAe,gDAC8B,CAAC;
|
|
1
|
+
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../src/common.ts"],"names":[],"mappings":"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"}
|
package/dist/common.js
CHANGED
|
@@ -20,6 +20,26 @@ export const emptyStringToUndefined = (value) => {
|
|
|
20
20
|
};
|
|
21
21
|
/** Optional trimmed string. Empty strings are coerced to `undefined`. */
|
|
22
22
|
export const optionalTrimmedString = (maxLength = 100) => z.preprocess(emptyStringToUndefined, z.string().trim().max(maxLength).optional());
|
|
23
|
+
/**
|
|
24
|
+
* A multi-select filter carried in a query string.
|
|
25
|
+
*
|
|
26
|
+
* A URL has no array type, so a multi-select arrives either as
|
|
27
|
+
* `?muscle=chest,back` or, if the client used `URLSearchParams.append`, as
|
|
28
|
+
* repeated keys that Express hands over as a real array. Both are accepted and
|
|
29
|
+
* normalised to one array, because which one a caller produces is an accident
|
|
30
|
+
* of how it built the URL, not a different intent.
|
|
31
|
+
*
|
|
32
|
+
* Unknown values are DROPPED rather than rejected: a filter chip that is
|
|
33
|
+
* renamed or removed should stop matching, not 422 the whole screen for
|
|
34
|
+
* somebody who still has the old URL open.
|
|
35
|
+
*/
|
|
36
|
+
export const csvOf = (member) => z.preprocess((value) => {
|
|
37
|
+
if (value === undefined || value === null || value === '')
|
|
38
|
+
return undefined;
|
|
39
|
+
const raw = Array.isArray(value) ? value : String(value).split(',');
|
|
40
|
+
const cleaned = raw.map((v) => String(v).trim()).filter(Boolean);
|
|
41
|
+
return cleaned.length > 0 ? cleaned.filter((v) => member.safeParse(v).success) : undefined;
|
|
42
|
+
}, z.array(member).optional());
|
|
23
43
|
// ─── Identifiers ─────────────────────────────────────────────────────────────
|
|
24
44
|
/** A 24-character hex MongoDB ObjectId string. */
|
|
25
45
|
export const objectIdSchema = z
|
package/dist/common.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.js","sourceRoot":"","sources":["../src/common.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Shared helpers and primitives\n * ==============================================\n * The stable building blocks reused across every GymMonk domain schema.\n * Mirrors the `dating-schema` / `near-me-schema` idiom: pure Zod, const-object\n * enums fed to `z.enum(...)`, NodeNext `.js` import specifiers.\n *\n * @module gymmonk-schema/common\n */\n\nimport { z } from 'zod';\n\n// ─── String helpers ──────────────────────────────────────────────────────────\n\n/**\n * Converts empty / whitespace-only strings to `undefined`\n * so Zod `.optional()` treats them as \"not provided\".\n */\nexport const emptyStringToUndefined = (value: unknown): unknown => {\n if (typeof value === 'string' && value.trim() === '') return undefined;\n return value;\n};\n\n/** Optional trimmed string. Empty strings are coerced to `undefined`. */\nexport const optionalTrimmedString = (maxLength = 100) =>\n z.preprocess(emptyStringToUndefined, z.string().trim().max(maxLength).optional());\n\n// ─── Identifiers ─────────────────────────────────────────────────────────────\n\n/** A 24-character hex MongoDB ObjectId string. */\nexport const objectIdSchema = z\n .string()\n .trim()\n .regex(/^[0-9a-fA-F]{24}$/, 'Invalid id');\nexport type ObjectIdString = z.infer<typeof objectIdSchema>;\n\n// ─── Pagination ──────────────────────────────────────────────────────────────\n\n/** Pagination query params shared across list endpoints. */\nexport const paginationSchema = z.object({\n page: z.coerce.number().int().min(1).default(1),\n limit: z.coerce.number().int().min(1).max(100).default(20),\n});\nexport type Pagination = z.infer<typeof paginationSchema>;\n\n// ─── Soft-delete visibility ──────────────────────────────────────────────────\n\n/**\n * How a list query treats soft-deleted rows.\n *\n * THREE states, not a boolean. A boolean can only say \"live\" or \"live +\n * deleted\", which means a console offering a \"Deleted\" filter has to fetch both\n * and drop the live ones client-side — and that breaks the moment the list is\n * paginated: page 1 of \"everything\" may contain no deleted rows at all, so the\n * screen shows an empty list under a filter that matches dozens of records\n * further down. Only the server can answer \"just the deleted ones\".\n *\n * `exclude` the default, and what every ordinary caller wants\n * `include` live and deleted together, for a search that should find both\n * `only` the restore queue\n *\n * Lives here rather than beside one directory because both the people and the\n * gym listings take it, and they must behave identically.\n */\nexport const deletedFilterSchema = z.enum(['exclude', 'include', 'only']);\nexport type DeletedFilter = z.infer<typeof deletedFilterSchema>;\n\n// ─── Contact primitives (India) ──────────────────────────────────────────────\n\n/**\n * The raw patterns, exported so the onboarding FORM schemas validate a typed\n * value exactly the way the API schemas below do — one rule, both sides.\n */\nexport const PHONE_REGEX = /^[6-9]\\d{9}$/;\nexport const PINCODE_REGEX = /^\\d{6}$/;\n\n/**\n * A bare 10-digit Indian mobile number. Stored RAW (no `+91`) app-wide — the\n * country code is display-only. Mirrors the jansathi phone-number standard.\n */\nexport const phoneSchema = z\n .string()\n .trim()\n .regex(PHONE_REGEX, 'Enter a valid 10-digit mobile number');\nexport type Phone = z.infer<typeof phoneSchema>;\n\n/** Optional phone — empty/whitespace becomes `undefined`. */\nexport const optionalPhoneSchema = z.preprocess(emptyStringToUndefined, phoneSchema.optional());\n\n/** A 6-digit Indian postal PIN code. */\nexport const pincodeSchema = z\n .string()\n .trim()\n .regex(PINCODE_REGEX, 'Enter a valid 6-digit pincode');\nexport type Pincode = z.infer<typeof pincodeSchema>;\n\n/** Optional email — empty/whitespace becomes `undefined`, else validated. */\nexport const optionalEmailSchema = z.preprocess(\n emptyStringToUndefined,\n z.email('Enter a valid email').optional(),\n);\n\n// ─── Date & time primitives ──────────────────────────────────────────────────\n\n/** A calendar date with no time component, `YYYY-MM-DD`. */\nexport const isoDateSchema = z\n .string()\n .trim()\n .regex(/^\\d{4}-\\d{2}-\\d{2}$/, 'Expected an YYYY-MM-DD date');\nexport type IsoDate = z.infer<typeof isoDateSchema>;\n\n/** A full ISO-8601 timestamp, e.g. `2026-08-06T09:30:00.000Z`. */\nexport const isoDateTimeSchema = z.iso.datetime({ offset: true });\nexport type IsoDateTime = z.infer<typeof isoDateTimeSchema>;\n\n/** Time of day in 24-hour `HH:MM`. */\nexport const timeOfDaySchema = z\n .string()\n .trim()\n .regex(/^([01]\\d|2[0-3]):[0-5]\\d$/, 'Expected an HH:MM time');\nexport type TimeOfDay = z.infer<typeof timeOfDaySchema>;\n\n/**\n * `Date` rolls overflow forward (Feb 31 → Mar 3), so a real date is one whose\n * parts survive the round-trip.\n *\n * Lives here rather than beside one form's rules because every date-of-birth\n * field in the product needs it — the onboarding wizards and the owner's desk\n * entry forms all check the same thing.\n */\nexport function isRealDate(year: number, month: number, day: number): boolean {\n const date = new Date(Date.UTC(year, month - 1, day));\n return (\n date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day\n );\n}\n\n/** Whole years elapsed since the given Y/M/D, in UTC. Negative when future. */\nexport function ageInYears(year: number, month: number, day: number): number {\n const date = Date.UTC(year, month - 1, day);\n return (Date.now() - date) / (365.25 * 24 * 60 * 60 * 1000);\n}\n\n// ─── Money ───────────────────────────────────────────────────────────────────\n\n/**\n * A whole-rupee (INR) amount. GymMonk prices are whole rupees; format with a\n * `₹` and grouping at the edge, never store the formatted string.\n */\nexport const priceInrSchema = z.number().int().min(0);\nexport type PriceInr = z.infer<typeof priceInrSchema>;\n\n// ─── Names ───────────────────────────────────────────────────────────────────\n\n/**\n * Split one typed name into the two fields a `User` actually stores.\n *\n * Every screen that asks for a name asks for it in ONE box — onboarding step 1\n * and the profile's Personal Details editor — because \"first name / last name\"\n * is a storage detail, not a question a person can answer about their own name.\n * The split therefore has to happen somewhere, and it has to happen the SAME\n * way in both places: if onboarding split on the last space and the editor on\n * the first, a member who opened the editor and pressed Save without touching\n * anything would have their name silently re-cut.\n *\n * Splits on the FIRST space, so \"Ram Kumar Singh\" is `Ram` + `Kumar Singh`.\n * Collapses runs of whitespace, so a double space or a trailing one cannot\n * produce an empty surname or a leading blank.\n *\n * The inverse is the backend's `fullName(first, last)` and the web client's\n * `fullName(user)`, which join the halves back with a single space — so a name\n * that goes through this and back out again is unchanged.\n */\nexport function splitFullName(value: string): { firstName: string; lastName: string } {\n const parts = value.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return { firstName: '', lastName: '' };\n const [firstName, ...rest] = parts;\n return { firstName: firstName as string, lastName: rest.join(' ') };\n}\n\n/**\n * The two stored name halves joined back into one string.\n *\n * The exact inverse of `splitFullName`, and deliberately sitting beside it:\n * a name that goes through one and back through the other must come out\n * unchanged, which is only guaranteed while both rules live in one file.\n *\n * Trims each half and drops an empty one, so a member with no surname reads as\n * `\"Rahul\"` rather than `\"Rahul \"`. Returns `''` when there is nothing to\n * join; callers pick their own fallback (the email, \"Member\", a dash), because\n * the right one differs per screen.\n *\n * Both products call it through a thin local module — gymmonk-backend\n * `shared/utils/user-name` and gymmonk-web-client `shared/lib/user-name` — so\n * the many existing call sites keep their convenient signatures while there is\n * only ONE rule about what a full name is.\n */\nexport function joinFullName(firstName?: string | null, lastName?: string | null): string {\n return [firstName, lastName]\n .map((part) => (part ?? '').trim())\n .filter(Boolean)\n .join(' ');\n}\n\n// ─── Billing cycle ───────────────────────────────────────────────────────────\n\n/**\n * The day of the month a member's fee falls due, e.g. `16` for \"the 16th of\n * every month\".\n *\n * A DAY NUMBER, not a date, and the distinction is the whole point. A member\n * joining through the app on 5 June with a billing day of 26 is billed\n * 26 May → 26 June: the day is an anchor for a repeating cycle, so it may name\n * a date earlier than the one they joined on. Storing a date instead would\n * make \"the 26th of every month\" unanswerable the moment the first term ended.\n *\n * 1–31 rather than 1–28. A gym that bills on the 31st is making a perfectly\n * ordinary statement, and the months that have no 31st are handled where the\n * term is dated — by clamping to the last day the month actually has, which is\n * what `addMonths` in the backend already does for every other term. Refusing\n * 29, 30 and 31 here would reject a real answer to avoid arithmetic that is\n * already written.\n */\nexport const billingDayOfMonthSchema = z\n .number()\n .int('Pick a day of the month')\n .min(1, 'Pick a day between 1 and 31')\n .max(31, 'Pick a day between 1 and 31');\nexport type BillingDayOfMonth = z.infer<typeof billingDayOfMonthSchema>;\n\n/**\n * The most recent occurrence of `day` on or before `onOrBefore`, as `YYYY-MM-DD`.\n *\n * This is the rule that decides which term a joining member's first payment\n * covers: never a period starting after they joined, so the month they are\n * actually training in is always the month they are paying for. Joining on\n * 5 June with day 26 gives 26 May; with day 10 it gives 10 May, because 10 June\n * has not happened yet.\n *\n * Lives in the schema package because BOTH sides need it — the app previews\n * \"your plan will run 26 May to 26 June\" as the member picks the day, and the\n * server dates the term for real. Two copies of this arithmetic is how a member\n * is shown one period and billed for another.\n *\n * Clamped to the last day of the target month, so day 31 in a 30-day month\n * resolves to the 30th rather than rolling into the next month.\n */\nexport function billingAnchorOnOrBefore(day: number, onOrBefore: string): string {\n const [y, m, d] = onOrBefore.split('-').map(Number) as [number, number, number];\n const clampTo = (year: number, month: number): number =>\n Math.min(day, new Date(Date.UTC(year, month, 0)).getUTCDate());\n\n // This month's occurrence, if it has already happened.\n const thisMonth = clampTo(y, m);\n if (thisMonth <= d) {\n return `${String(y).padStart(4, '0')}-${String(m).padStart(2, '0')}-${String(thisMonth).padStart(2, '0')}`;\n }\n\n // Otherwise the previous month's.\n const py = m === 1 ? y - 1 : y;\n const pm = m === 1 ? 12 : m - 1;\n const prev = clampTo(py, pm);\n return `${String(py).padStart(4, '0')}-${String(pm).padStart(2, '0')}-${String(prev).padStart(2, '0')}`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"common.js","sourceRoot":"","sources":["../src/common.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Shared helpers and primitives\n * ==============================================\n * The stable building blocks reused across every GymMonk domain schema.\n * Mirrors the `dating-schema` / `near-me-schema` idiom: pure Zod, const-object\n * enums fed to `z.enum(...)`, NodeNext `.js` import specifiers.\n *\n * @module gymmonk-schema/common\n */\n\nimport { z } from 'zod';\n\n// ─── String helpers ──────────────────────────────────────────────────────────\n\n/**\n * Converts empty / whitespace-only strings to `undefined`\n * so Zod `.optional()` treats them as \"not provided\".\n */\nexport const emptyStringToUndefined = (value: unknown): unknown => {\n if (typeof value === 'string' && value.trim() === '') return undefined;\n return value;\n};\n\n/** Optional trimmed string. Empty strings are coerced to `undefined`. */\nexport const optionalTrimmedString = (maxLength = 100) =>\n z.preprocess(emptyStringToUndefined, z.string().trim().max(maxLength).optional());\n\n/**\n * A multi-select filter carried in a query string.\n *\n * A URL has no array type, so a multi-select arrives either as\n * `?muscle=chest,back` or, if the client used `URLSearchParams.append`, as\n * repeated keys that Express hands over as a real array. Both are accepted and\n * normalised to one array, because which one a caller produces is an accident\n * of how it built the URL, not a different intent.\n *\n * Unknown values are DROPPED rather than rejected: a filter chip that is\n * renamed or removed should stop matching, not 422 the whole screen for\n * somebody who still has the old URL open.\n */\nexport const csvOf = <T extends z.ZodTypeAny>(member: T) =>\n z.preprocess((value) => {\n if (value === undefined || value === null || value === '') return undefined;\n const raw = Array.isArray(value) ? value : String(value).split(',');\n const cleaned = raw.map((v) => String(v).trim()).filter(Boolean);\n return cleaned.length > 0 ? cleaned.filter((v) => member.safeParse(v).success) : undefined;\n }, z.array(member).optional());\n\n// ─── Identifiers ─────────────────────────────────────────────────────────────\n\n/** A 24-character hex MongoDB ObjectId string. */\nexport const objectIdSchema = z\n .string()\n .trim()\n .regex(/^[0-9a-fA-F]{24}$/, 'Invalid id');\nexport type ObjectIdString = z.infer<typeof objectIdSchema>;\n\n// ─── Pagination ──────────────────────────────────────────────────────────────\n\n/** Pagination query params shared across list endpoints. */\nexport const paginationSchema = z.object({\n page: z.coerce.number().int().min(1).default(1),\n limit: z.coerce.number().int().min(1).max(100).default(20),\n});\nexport type Pagination = z.infer<typeof paginationSchema>;\n\n// ─── Soft-delete visibility ──────────────────────────────────────────────────\n\n/**\n * How a list query treats soft-deleted rows.\n *\n * THREE states, not a boolean. A boolean can only say \"live\" or \"live +\n * deleted\", which means a console offering a \"Deleted\" filter has to fetch both\n * and drop the live ones client-side — and that breaks the moment the list is\n * paginated: page 1 of \"everything\" may contain no deleted rows at all, so the\n * screen shows an empty list under a filter that matches dozens of records\n * further down. Only the server can answer \"just the deleted ones\".\n *\n * `exclude` the default, and what every ordinary caller wants\n * `include` live and deleted together, for a search that should find both\n * `only` the restore queue\n *\n * Lives here rather than beside one directory because both the people and the\n * gym listings take it, and they must behave identically.\n */\nexport const deletedFilterSchema = z.enum(['exclude', 'include', 'only']);\nexport type DeletedFilter = z.infer<typeof deletedFilterSchema>;\n\n// ─── Contact primitives (India) ──────────────────────────────────────────────\n\n/**\n * The raw patterns, exported so the onboarding FORM schemas validate a typed\n * value exactly the way the API schemas below do — one rule, both sides.\n */\nexport const PHONE_REGEX = /^[6-9]\\d{9}$/;\nexport const PINCODE_REGEX = /^\\d{6}$/;\n\n/**\n * A bare 10-digit Indian mobile number. Stored RAW (no `+91`) app-wide — the\n * country code is display-only. Mirrors the jansathi phone-number standard.\n */\nexport const phoneSchema = z\n .string()\n .trim()\n .regex(PHONE_REGEX, 'Enter a valid 10-digit mobile number');\nexport type Phone = z.infer<typeof phoneSchema>;\n\n/** Optional phone — empty/whitespace becomes `undefined`. */\nexport const optionalPhoneSchema = z.preprocess(emptyStringToUndefined, phoneSchema.optional());\n\n/** A 6-digit Indian postal PIN code. */\nexport const pincodeSchema = z\n .string()\n .trim()\n .regex(PINCODE_REGEX, 'Enter a valid 6-digit pincode');\nexport type Pincode = z.infer<typeof pincodeSchema>;\n\n/** Optional email — empty/whitespace becomes `undefined`, else validated. */\nexport const optionalEmailSchema = z.preprocess(\n emptyStringToUndefined,\n z.email('Enter a valid email').optional(),\n);\n\n// ─── Date & time primitives ──────────────────────────────────────────────────\n\n/** A calendar date with no time component, `YYYY-MM-DD`. */\nexport const isoDateSchema = z\n .string()\n .trim()\n .regex(/^\\d{4}-\\d{2}-\\d{2}$/, 'Expected an YYYY-MM-DD date');\nexport type IsoDate = z.infer<typeof isoDateSchema>;\n\n/** A full ISO-8601 timestamp, e.g. `2026-08-06T09:30:00.000Z`. */\nexport const isoDateTimeSchema = z.iso.datetime({ offset: true });\nexport type IsoDateTime = z.infer<typeof isoDateTimeSchema>;\n\n/** Time of day in 24-hour `HH:MM`. */\nexport const timeOfDaySchema = z\n .string()\n .trim()\n .regex(/^([01]\\d|2[0-3]):[0-5]\\d$/, 'Expected an HH:MM time');\nexport type TimeOfDay = z.infer<typeof timeOfDaySchema>;\n\n/**\n * `Date` rolls overflow forward (Feb 31 → Mar 3), so a real date is one whose\n * parts survive the round-trip.\n *\n * Lives here rather than beside one form's rules because every date-of-birth\n * field in the product needs it — the onboarding wizards and the owner's desk\n * entry forms all check the same thing.\n */\nexport function isRealDate(year: number, month: number, day: number): boolean {\n const date = new Date(Date.UTC(year, month - 1, day));\n return (\n date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day\n );\n}\n\n/** Whole years elapsed since the given Y/M/D, in UTC. Negative when future. */\nexport function ageInYears(year: number, month: number, day: number): number {\n const date = Date.UTC(year, month - 1, day);\n return (Date.now() - date) / (365.25 * 24 * 60 * 60 * 1000);\n}\n\n// ─── Money ───────────────────────────────────────────────────────────────────\n\n/**\n * A whole-rupee (INR) amount. GymMonk prices are whole rupees; format with a\n * `₹` and grouping at the edge, never store the formatted string.\n */\nexport const priceInrSchema = z.number().int().min(0);\nexport type PriceInr = z.infer<typeof priceInrSchema>;\n\n// ─── Names ───────────────────────────────────────────────────────────────────\n\n/**\n * Split one typed name into the two fields a `User` actually stores.\n *\n * Every screen that asks for a name asks for it in ONE box — onboarding step 1\n * and the profile's Personal Details editor — because \"first name / last name\"\n * is a storage detail, not a question a person can answer about their own name.\n * The split therefore has to happen somewhere, and it has to happen the SAME\n * way in both places: if onboarding split on the last space and the editor on\n * the first, a member who opened the editor and pressed Save without touching\n * anything would have their name silently re-cut.\n *\n * Splits on the FIRST space, so \"Ram Kumar Singh\" is `Ram` + `Kumar Singh`.\n * Collapses runs of whitespace, so a double space or a trailing one cannot\n * produce an empty surname or a leading blank.\n *\n * The inverse is the backend's `fullName(first, last)` and the web client's\n * `fullName(user)`, which join the halves back with a single space — so a name\n * that goes through this and back out again is unchanged.\n */\nexport function splitFullName(value: string): { firstName: string; lastName: string } {\n const parts = value.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return { firstName: '', lastName: '' };\n const [firstName, ...rest] = parts;\n return { firstName: firstName as string, lastName: rest.join(' ') };\n}\n\n/**\n * The two stored name halves joined back into one string.\n *\n * The exact inverse of `splitFullName`, and deliberately sitting beside it:\n * a name that goes through one and back through the other must come out\n * unchanged, which is only guaranteed while both rules live in one file.\n *\n * Trims each half and drops an empty one, so a member with no surname reads as\n * `\"Rahul\"` rather than `\"Rahul \"`. Returns `''` when there is nothing to\n * join; callers pick their own fallback (the email, \"Member\", a dash), because\n * the right one differs per screen.\n *\n * Both products call it through a thin local module — gymmonk-backend\n * `shared/utils/user-name` and gymmonk-web-client `shared/lib/user-name` — so\n * the many existing call sites keep their convenient signatures while there is\n * only ONE rule about what a full name is.\n */\nexport function joinFullName(firstName?: string | null, lastName?: string | null): string {\n return [firstName, lastName]\n .map((part) => (part ?? '').trim())\n .filter(Boolean)\n .join(' ');\n}\n\n// ─── Billing cycle ───────────────────────────────────────────────────────────\n\n/**\n * The day of the month a member's fee falls due, e.g. `16` for \"the 16th of\n * every month\".\n *\n * A DAY NUMBER, not a date, and the distinction is the whole point. A member\n * joining through the app on 5 June with a billing day of 26 is billed\n * 26 May → 26 June: the day is an anchor for a repeating cycle, so it may name\n * a date earlier than the one they joined on. Storing a date instead would\n * make \"the 26th of every month\" unanswerable the moment the first term ended.\n *\n * 1–31 rather than 1–28. A gym that bills on the 31st is making a perfectly\n * ordinary statement, and the months that have no 31st are handled where the\n * term is dated — by clamping to the last day the month actually has, which is\n * what `addMonths` in the backend already does for every other term. Refusing\n * 29, 30 and 31 here would reject a real answer to avoid arithmetic that is\n * already written.\n */\nexport const billingDayOfMonthSchema = z\n .number()\n .int('Pick a day of the month')\n .min(1, 'Pick a day between 1 and 31')\n .max(31, 'Pick a day between 1 and 31');\nexport type BillingDayOfMonth = z.infer<typeof billingDayOfMonthSchema>;\n\n/**\n * The most recent occurrence of `day` on or before `onOrBefore`, as `YYYY-MM-DD`.\n *\n * This is the rule that decides which term a joining member's first payment\n * covers: never a period starting after they joined, so the month they are\n * actually training in is always the month they are paying for. Joining on\n * 5 June with day 26 gives 26 May; with day 10 it gives 10 May, because 10 June\n * has not happened yet.\n *\n * Lives in the schema package because BOTH sides need it — the app previews\n * \"your plan will run 26 May to 26 June\" as the member picks the day, and the\n * server dates the term for real. Two copies of this arithmetic is how a member\n * is shown one period and billed for another.\n *\n * Clamped to the last day of the target month, so day 31 in a 30-day month\n * resolves to the 30th rather than rolling into the next month.\n */\nexport function billingAnchorOnOrBefore(day: number, onOrBefore: string): string {\n const [y, m, d] = onOrBefore.split('-').map(Number) as [number, number, number];\n const clampTo = (year: number, month: number): number =>\n Math.min(day, new Date(Date.UTC(year, month, 0)).getUTCDate());\n\n // This month's occurrence, if it has already happened.\n const thisMonth = clampTo(y, m);\n if (thisMonth <= d) {\n return `${String(y).padStart(4, '0')}-${String(m).padStart(2, '0')}-${String(thisMonth).padStart(2, '0')}`;\n }\n\n // Otherwise the previous month's.\n const py = m === 1 ? y - 1 : y;\n const pm = m === 1 ? 12 : m - 1;\n const prev = clampTo(py, pm);\n return `${String(py).padStart(4, '0')}-${String(pm).padStart(2, '0')}-${String(prev).padStart(2, '0')}`;\n}\n"]}
|
package/dist/diet-chart.d.ts
CHANGED
|
@@ -133,6 +133,16 @@ export declare const dietItemSchema: z.ZodObject<{
|
|
|
133
133
|
thumbnailUrl: z.ZodNullable<z.ZodString>;
|
|
134
134
|
centerId: z.ZodNullable<z.ZodString>;
|
|
135
135
|
calories: z.ZodNumber;
|
|
136
|
+
protein: z.ZodNumber;
|
|
137
|
+
carbs: z.ZodNumber;
|
|
138
|
+
fat: z.ZodNumber;
|
|
139
|
+
fibre: z.ZodNumber;
|
|
140
|
+
dietType: z.ZodEnum<{
|
|
141
|
+
veg: "veg";
|
|
142
|
+
egg: "egg";
|
|
143
|
+
"non-veg": "non-veg";
|
|
144
|
+
}>;
|
|
145
|
+
isVegan: z.ZodBoolean;
|
|
136
146
|
serving: z.ZodNullable<z.ZodObject<{
|
|
137
147
|
label: z.ZodString;
|
|
138
148
|
grams: z.ZodNumber;
|
|
@@ -198,6 +208,16 @@ export declare const dietMealSchema: z.ZodObject<{
|
|
|
198
208
|
thumbnailUrl: z.ZodNullable<z.ZodString>;
|
|
199
209
|
centerId: z.ZodNullable<z.ZodString>;
|
|
200
210
|
calories: z.ZodNumber;
|
|
211
|
+
protein: z.ZodNumber;
|
|
212
|
+
carbs: z.ZodNumber;
|
|
213
|
+
fat: z.ZodNumber;
|
|
214
|
+
fibre: z.ZodNumber;
|
|
215
|
+
dietType: z.ZodEnum<{
|
|
216
|
+
veg: "veg";
|
|
217
|
+
egg: "egg";
|
|
218
|
+
"non-veg": "non-veg";
|
|
219
|
+
}>;
|
|
220
|
+
isVegan: z.ZodBoolean;
|
|
201
221
|
serving: z.ZodNullable<z.ZodObject<{
|
|
202
222
|
label: z.ZodString;
|
|
203
223
|
grams: z.ZodNumber;
|
|
@@ -291,6 +311,16 @@ export declare const dietChartDaySchema: z.ZodObject<{
|
|
|
291
311
|
thumbnailUrl: z.ZodNullable<z.ZodString>;
|
|
292
312
|
centerId: z.ZodNullable<z.ZodString>;
|
|
293
313
|
calories: z.ZodNumber;
|
|
314
|
+
protein: z.ZodNumber;
|
|
315
|
+
carbs: z.ZodNumber;
|
|
316
|
+
fat: z.ZodNumber;
|
|
317
|
+
fibre: z.ZodNumber;
|
|
318
|
+
dietType: z.ZodEnum<{
|
|
319
|
+
veg: "veg";
|
|
320
|
+
egg: "egg";
|
|
321
|
+
"non-veg": "non-veg";
|
|
322
|
+
}>;
|
|
323
|
+
isVegan: z.ZodBoolean;
|
|
294
324
|
serving: z.ZodNullable<z.ZodObject<{
|
|
295
325
|
label: z.ZodString;
|
|
296
326
|
grams: z.ZodNumber;
|
|
@@ -424,6 +454,16 @@ export declare const dietChartSchema: z.ZodObject<{
|
|
|
424
454
|
thumbnailUrl: z.ZodNullable<z.ZodString>;
|
|
425
455
|
centerId: z.ZodNullable<z.ZodString>;
|
|
426
456
|
calories: z.ZodNumber;
|
|
457
|
+
protein: z.ZodNumber;
|
|
458
|
+
carbs: z.ZodNumber;
|
|
459
|
+
fat: z.ZodNumber;
|
|
460
|
+
fibre: z.ZodNumber;
|
|
461
|
+
dietType: z.ZodEnum<{
|
|
462
|
+
veg: "veg";
|
|
463
|
+
egg: "egg";
|
|
464
|
+
"non-veg": "non-veg";
|
|
465
|
+
}>;
|
|
466
|
+
isVegan: z.ZodBoolean;
|
|
427
467
|
serving: z.ZodNullable<z.ZodObject<{
|
|
428
468
|
label: z.ZodString;
|
|
429
469
|
grams: z.ZodNumber;
|
|
@@ -646,6 +686,16 @@ export declare const myDietChartSchema: z.ZodObject<{
|
|
|
646
686
|
thumbnailUrl: z.ZodNullable<z.ZodString>;
|
|
647
687
|
centerId: z.ZodNullable<z.ZodString>;
|
|
648
688
|
calories: z.ZodNumber;
|
|
689
|
+
protein: z.ZodNumber;
|
|
690
|
+
carbs: z.ZodNumber;
|
|
691
|
+
fat: z.ZodNumber;
|
|
692
|
+
fibre: z.ZodNumber;
|
|
693
|
+
dietType: z.ZodEnum<{
|
|
694
|
+
veg: "veg";
|
|
695
|
+
egg: "egg";
|
|
696
|
+
"non-veg": "non-veg";
|
|
697
|
+
}>;
|
|
698
|
+
isVegan: z.ZodBoolean;
|
|
649
699
|
serving: z.ZodNullable<z.ZodObject<{
|
|
650
700
|
label: z.ZodString;
|
|
651
701
|
grams: z.ZodNumber;
|
package/dist/diet-chart.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diet-chart.d.ts","sourceRoot":"","sources":["../src/diet-chart.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc;;;;;;;;;EASzB,CAAC;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEtD,uFAAuF;AACvF,eAAO,MAAM,eAAe,EAAE,SAAS,QAAQ,EASrC,CAAC;AAEX,qFAAqF;AACrF,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CASrD,CAAC;AAIF;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB;;;;iBAK9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;iBAU9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAGlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAIxE,8EAA8E;AAC9E,eAAO,MAAM,cAAc
|
|
1
|
+
{"version":3,"file":"diet-chart.d.ts","sourceRoot":"","sources":["../src/diet-chart.ts"],"names":[],"mappings":"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"}
|
package/dist/exercise.d.ts
CHANGED
|
@@ -438,8 +438,58 @@ export declare const updateExerciseBodySchema: z.ZodObject<{
|
|
|
438
438
|
formTips: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
439
439
|
}, z.core.$strip>;
|
|
440
440
|
export type UpdateExerciseBody = z.infer<typeof updateExerciseBodySchema>;
|
|
441
|
+
/**
|
|
442
|
+
* The Sort sheet's options, in the order the reference design lists them.
|
|
443
|
+
*
|
|
444
|
+
* Sorting is the SERVER's job now. It cannot be done on the client once the
|
|
445
|
+
* list is paginated: sorting one page of twenty is not sorting the library, it
|
|
446
|
+
* just reorders whichever twenty happened to arrive.
|
|
447
|
+
*/
|
|
448
|
+
export declare const exerciseSortSchema: z.ZodEnum<{
|
|
449
|
+
duration: "duration";
|
|
450
|
+
recent: "recent";
|
|
451
|
+
az: "az";
|
|
452
|
+
"most-used": "most-used";
|
|
453
|
+
za: "za";
|
|
454
|
+
level: "level";
|
|
455
|
+
"level-desc": "level-desc";
|
|
456
|
+
"duration-desc": "duration-desc";
|
|
457
|
+
}>;
|
|
458
|
+
export type ExerciseSort = z.infer<typeof exerciseSortSchema>;
|
|
459
|
+
/**
|
|
460
|
+
* The Filters sheet's duration bands.
|
|
461
|
+
*
|
|
462
|
+
* An exercise with no estimate matches NO band, deliberately: filtering to
|
|
463
|
+
* "under 10 minutes" should not hand back everything nobody ever measured.
|
|
464
|
+
*/
|
|
465
|
+
export declare const exerciseDurationBandSchema: z.ZodEnum<{
|
|
466
|
+
"under-10": "under-10";
|
|
467
|
+
"10-20": "10-20";
|
|
468
|
+
"20-30": "20-30";
|
|
469
|
+
"30-plus": "30-plus";
|
|
470
|
+
}>;
|
|
471
|
+
export type ExerciseDurationBand = z.infer<typeof exerciseDurationBandSchema>;
|
|
472
|
+
/** Minutes each band covers. `max: null` is the open-ended top band. */
|
|
473
|
+
export declare const EXERCISE_DURATION_BANDS: Record<ExerciseDurationBand, {
|
|
474
|
+
min: number;
|
|
475
|
+
max: number | null;
|
|
476
|
+
}>;
|
|
477
|
+
/**
|
|
478
|
+
* The library list query.
|
|
479
|
+
*
|
|
480
|
+
* Every filter the Filters sheet offers is here, and every one is a LIST,
|
|
481
|
+
* because the sheet is multi-select. They were single values while the client
|
|
482
|
+
* held the whole catalogue and filtered in memory; that stops working the
|
|
483
|
+
* moment the list is paginated, because filtering a page is not filtering the
|
|
484
|
+
* library.
|
|
485
|
+
*/
|
|
441
486
|
export declare const exerciseLibraryQuerySchema: z.ZodObject<{
|
|
487
|
+
page: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
488
|
+
limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
442
489
|
centerId: z.ZodOptional<z.ZodString>;
|
|
490
|
+
scope: z.ZodOptional<z.ZodEnum<{
|
|
491
|
+
platform: "platform";
|
|
492
|
+
}>>;
|
|
443
493
|
category: z.ZodOptional<z.ZodEnum<{
|
|
444
494
|
cardio: "cardio";
|
|
445
495
|
"upper-body": "upper-body";
|
|
@@ -447,20 +497,20 @@ export declare const exerciseLibraryQuerySchema: z.ZodObject<{
|
|
|
447
497
|
core: "core";
|
|
448
498
|
"full-body": "full-body";
|
|
449
499
|
}>>;
|
|
450
|
-
difficulty: z.ZodOptional<z.ZodEnum<{
|
|
500
|
+
difficulty: z.ZodPreprocess<z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
451
501
|
beginner: "beginner";
|
|
452
502
|
intermediate: "intermediate";
|
|
453
503
|
advanced: "advanced";
|
|
454
|
-
}
|
|
455
|
-
workoutType: z.ZodOptional<z.ZodEnum<{
|
|
504
|
+
}>>>>;
|
|
505
|
+
workoutType: z.ZodPreprocess<z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
456
506
|
functional: "functional";
|
|
457
507
|
cardio: "cardio";
|
|
458
508
|
hiit: "hiit";
|
|
459
509
|
"strength-training": "strength-training";
|
|
460
510
|
flexibility: "flexibility";
|
|
461
511
|
mobility: "mobility";
|
|
462
|
-
}
|
|
463
|
-
equipment: z.ZodOptional<z.ZodEnum<{
|
|
512
|
+
}>>>>;
|
|
513
|
+
equipment: z.ZodPreprocess<z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
464
514
|
other: "other";
|
|
465
515
|
bodyweight: "bodyweight";
|
|
466
516
|
barbell: "barbell";
|
|
@@ -470,8 +520,8 @@ export declare const exerciseLibraryQuerySchema: z.ZodObject<{
|
|
|
470
520
|
kettlebell: "kettlebell";
|
|
471
521
|
"resistance-band": "resistance-band";
|
|
472
522
|
bench: "bench";
|
|
473
|
-
}
|
|
474
|
-
muscle: z.ZodOptional<z.ZodEnum<{
|
|
523
|
+
}>>>>;
|
|
524
|
+
muscle: z.ZodPreprocess<z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
475
525
|
chest: "chest";
|
|
476
526
|
cardio: "cardio";
|
|
477
527
|
core: "core";
|
|
@@ -489,10 +539,94 @@ export declare const exerciseLibraryQuerySchema: z.ZodObject<{
|
|
|
489
539
|
legs: "legs";
|
|
490
540
|
obliques: "obliques";
|
|
491
541
|
"lower-back": "lower-back";
|
|
492
|
-
}
|
|
542
|
+
}>>>>;
|
|
543
|
+
duration: z.ZodPreprocess<z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
544
|
+
"under-10": "under-10";
|
|
545
|
+
"10-20": "10-20";
|
|
546
|
+
"20-30": "20-30";
|
|
547
|
+
"30-plus": "30-plus";
|
|
548
|
+
}>>>>;
|
|
493
549
|
q: z.ZodPreprocess<z.ZodOptional<z.ZodString>>;
|
|
550
|
+
sort: z.ZodDefault<z.ZodEnum<{
|
|
551
|
+
duration: "duration";
|
|
552
|
+
recent: "recent";
|
|
553
|
+
az: "az";
|
|
554
|
+
"most-used": "most-used";
|
|
555
|
+
za: "za";
|
|
556
|
+
level: "level";
|
|
557
|
+
"level-desc": "level-desc";
|
|
558
|
+
"duration-desc": "duration-desc";
|
|
559
|
+
}>>;
|
|
494
560
|
}, z.core.$strip>;
|
|
495
561
|
export type ExerciseLibraryQuery = z.infer<typeof exerciseLibraryQuerySchema>;
|
|
562
|
+
/**
|
|
563
|
+
* Which filter values the caller's library actually contains.
|
|
564
|
+
*
|
|
565
|
+
* The Filters sheet offers a fixed list of chips, but a chip matching nothing
|
|
566
|
+
* is worse than a missing one — it reads as a broken filter rather than as an
|
|
567
|
+
* honest empty result. The grid used to work this out itself, because it held
|
|
568
|
+
* the whole catalogue. Once the list is paged it can only see the rows it has
|
|
569
|
+
* loaded, so the answer has to come from the server or the chips start lying.
|
|
570
|
+
*/
|
|
571
|
+
export declare const exerciseFacetsSchema: z.ZodObject<{
|
|
572
|
+
muscles: z.ZodArray<z.ZodEnum<{
|
|
573
|
+
chest: "chest";
|
|
574
|
+
cardio: "cardio";
|
|
575
|
+
core: "core";
|
|
576
|
+
"full-body": "full-body";
|
|
577
|
+
back: "back";
|
|
578
|
+
traps: "traps";
|
|
579
|
+
shoulders: "shoulders";
|
|
580
|
+
biceps: "biceps";
|
|
581
|
+
triceps: "triceps";
|
|
582
|
+
forearms: "forearms";
|
|
583
|
+
quads: "quads";
|
|
584
|
+
hamstrings: "hamstrings";
|
|
585
|
+
glutes: "glutes";
|
|
586
|
+
calves: "calves";
|
|
587
|
+
legs: "legs";
|
|
588
|
+
obliques: "obliques";
|
|
589
|
+
"lower-back": "lower-back";
|
|
590
|
+
}>>;
|
|
591
|
+
workoutTypes: z.ZodArray<z.ZodEnum<{
|
|
592
|
+
functional: "functional";
|
|
593
|
+
cardio: "cardio";
|
|
594
|
+
hiit: "hiit";
|
|
595
|
+
"strength-training": "strength-training";
|
|
596
|
+
flexibility: "flexibility";
|
|
597
|
+
mobility: "mobility";
|
|
598
|
+
}>>;
|
|
599
|
+
equipment: z.ZodArray<z.ZodEnum<{
|
|
600
|
+
other: "other";
|
|
601
|
+
bodyweight: "bodyweight";
|
|
602
|
+
barbell: "barbell";
|
|
603
|
+
dumbbell: "dumbbell";
|
|
604
|
+
machine: "machine";
|
|
605
|
+
cable: "cable";
|
|
606
|
+
kettlebell: "kettlebell";
|
|
607
|
+
"resistance-band": "resistance-band";
|
|
608
|
+
bench: "bench";
|
|
609
|
+
}>>;
|
|
610
|
+
categories: z.ZodArray<z.ZodEnum<{
|
|
611
|
+
cardio: "cardio";
|
|
612
|
+
"upper-body": "upper-body";
|
|
613
|
+
"lower-body": "lower-body";
|
|
614
|
+
core: "core";
|
|
615
|
+
"full-body": "full-body";
|
|
616
|
+
}>>;
|
|
617
|
+
difficulties: z.ZodArray<z.ZodEnum<{
|
|
618
|
+
beginner: "beginner";
|
|
619
|
+
intermediate: "intermediate";
|
|
620
|
+
advanced: "advanced";
|
|
621
|
+
}>>;
|
|
622
|
+
durations: z.ZodArray<z.ZodEnum<{
|
|
623
|
+
"under-10": "under-10";
|
|
624
|
+
"10-20": "10-20";
|
|
625
|
+
"20-30": "20-30";
|
|
626
|
+
"30-plus": "30-plus";
|
|
627
|
+
}>>;
|
|
628
|
+
}, z.core.$strip>;
|
|
629
|
+
export type ExerciseFacets = z.infer<typeof exerciseFacetsSchema>;
|
|
496
630
|
/**
|
|
497
631
|
* What the shipped content file holds versus what the database holds.
|
|
498
632
|
*
|
package/dist/exercise.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exercise.d.ts","sourceRoot":"","sources":["../src/exercise.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"exercise.d.ts","sourceRoot":"","sources":["../src/exercise.ts"],"names":[],"mappings":"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"}
|
package/dist/exercise.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @module gymmonk-schema/exercise
|
|
9
9
|
*/
|
|
10
10
|
import { z } from 'zod';
|
|
11
|
-
import { isoDateTimeSchema, objectIdSchema, optionalTrimmedString } from './common.js';
|
|
11
|
+
import { csvOf, isoDateTimeSchema, objectIdSchema, optionalTrimmedString, paginationSchema, } from './common.js';
|
|
12
12
|
// ─── Enums ───────────────────────────────────────────────────────────────────
|
|
13
13
|
export const difficultySchema = z.enum(['beginner', 'intermediate', 'advanced']);
|
|
14
14
|
export const exerciseCategorySchema = z.enum([
|
|
@@ -284,14 +284,83 @@ export const createExerciseBodySchema = exerciseBodyBaseSchema.extend({
|
|
|
284
284
|
});
|
|
285
285
|
export const updateExerciseBodySchema = exerciseBodyBaseSchema.omit({ centerId: true }).partial();
|
|
286
286
|
// ─── Library query ───────────────────────────────────────────────────────────
|
|
287
|
+
/**
|
|
288
|
+
* The Sort sheet's options, in the order the reference design lists them.
|
|
289
|
+
*
|
|
290
|
+
* Sorting is the SERVER's job now. It cannot be done on the client once the
|
|
291
|
+
* list is paginated: sorting one page of twenty is not sorting the library, it
|
|
292
|
+
* just reorders whichever twenty happened to arrive.
|
|
293
|
+
*/
|
|
294
|
+
export const exerciseSortSchema = z.enum([
|
|
295
|
+
'recent',
|
|
296
|
+
'most-used',
|
|
297
|
+
'az',
|
|
298
|
+
'za',
|
|
299
|
+
'level',
|
|
300
|
+
'level-desc',
|
|
301
|
+
'duration',
|
|
302
|
+
'duration-desc',
|
|
303
|
+
]);
|
|
304
|
+
/**
|
|
305
|
+
* The Filters sheet's duration bands.
|
|
306
|
+
*
|
|
307
|
+
* An exercise with no estimate matches NO band, deliberately: filtering to
|
|
308
|
+
* "under 10 minutes" should not hand back everything nobody ever measured.
|
|
309
|
+
*/
|
|
310
|
+
export const exerciseDurationBandSchema = z.enum(['under-10', '10-20', '20-30', '30-plus']);
|
|
311
|
+
/** Minutes each band covers. `max: null` is the open-ended top band. */
|
|
312
|
+
export const EXERCISE_DURATION_BANDS = {
|
|
313
|
+
'under-10': { min: 0, max: 10 },
|
|
314
|
+
'10-20': { min: 10, max: 20 },
|
|
315
|
+
'20-30': { min: 20, max: 30 },
|
|
316
|
+
'30-plus': { min: 30, max: null },
|
|
317
|
+
};
|
|
318
|
+
/**
|
|
319
|
+
* The library list query.
|
|
320
|
+
*
|
|
321
|
+
* Every filter the Filters sheet offers is here, and every one is a LIST,
|
|
322
|
+
* because the sheet is multi-select. They were single values while the client
|
|
323
|
+
* held the whole catalogue and filtered in memory; that stops working the
|
|
324
|
+
* moment the list is paginated, because filtering a page is not filtering the
|
|
325
|
+
* library.
|
|
326
|
+
*/
|
|
287
327
|
export const exerciseLibraryQuerySchema = z.object({
|
|
288
328
|
centerId: objectIdSchema.optional(),
|
|
329
|
+
/**
|
|
330
|
+
* `platform` narrows to the product's own catalogue (`centerId: null`).
|
|
331
|
+
*
|
|
332
|
+
* `centerId` cannot express this — there is no id for "no gym". The platform
|
|
333
|
+
* console used to fetch everything and drop gym rows in the browser, which a
|
|
334
|
+
* paged list cannot do: it would filter the page and report a total counting
|
|
335
|
+
* rows it just hid.
|
|
336
|
+
*/
|
|
337
|
+
scope: z.enum(['platform']).optional(),
|
|
289
338
|
category: exerciseCategorySchema.optional(),
|
|
290
|
-
difficulty: difficultySchema
|
|
291
|
-
workoutType: workoutTypeSchema
|
|
292
|
-
equipment: equipmentTypeSchema
|
|
293
|
-
muscle: muscleGroupSchema
|
|
339
|
+
difficulty: csvOf(difficultySchema),
|
|
340
|
+
workoutType: csvOf(workoutTypeSchema),
|
|
341
|
+
equipment: csvOf(equipmentTypeSchema),
|
|
342
|
+
muscle: csvOf(muscleGroupSchema),
|
|
343
|
+
duration: csvOf(exerciseDurationBandSchema),
|
|
294
344
|
q: optionalTrimmedString(120),
|
|
345
|
+
sort: exerciseSortSchema.default('az'),
|
|
346
|
+
...paginationSchema.shape,
|
|
347
|
+
});
|
|
348
|
+
/**
|
|
349
|
+
* Which filter values the caller's library actually contains.
|
|
350
|
+
*
|
|
351
|
+
* The Filters sheet offers a fixed list of chips, but a chip matching nothing
|
|
352
|
+
* is worse than a missing one — it reads as a broken filter rather than as an
|
|
353
|
+
* honest empty result. The grid used to work this out itself, because it held
|
|
354
|
+
* the whole catalogue. Once the list is paged it can only see the rows it has
|
|
355
|
+
* loaded, so the answer has to come from the server or the chips start lying.
|
|
356
|
+
*/
|
|
357
|
+
export const exerciseFacetsSchema = z.object({
|
|
358
|
+
muscles: z.array(muscleGroupSchema),
|
|
359
|
+
workoutTypes: z.array(workoutTypeSchema),
|
|
360
|
+
equipment: z.array(equipmentTypeSchema),
|
|
361
|
+
categories: z.array(exerciseCategorySchema),
|
|
362
|
+
difficulties: z.array(difficultySchema),
|
|
363
|
+
durations: z.array(exerciseDurationBandSchema),
|
|
295
364
|
});
|
|
296
365
|
// ─── Platform library seeding (superadmin console) ───────────────────────────
|
|
297
366
|
/**
|