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 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>;
@@ -1 +1 @@
1
- {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../src/common.ts"],"names":[],"mappings":"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"}
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
@@ -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"]}
@@ -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;
@@ -1 +1 @@
1
- {"version":3,"file":"diet-chart.d.ts","sourceRoot":"","sources":["../src/diet-chart.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"diet-chart.d.ts","sourceRoot":"","sources":["../src/diet-chart.ts"],"names":[],"mappings":"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"}
@@ -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
  *
@@ -1 +1 @@
1
- {"version":3,"file":"exercise.d.ts","sourceRoot":"","sources":["../src/exercise.ts"],"names":[],"mappings":"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"}
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.optional(),
291
- workoutType: workoutTypeSchema.optional(),
292
- equipment: equipmentTypeSchema.optional(),
293
- muscle: muscleGroupSchema.optional(),
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
  /**