gymmonk-schema 0.56.0 → 0.57.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.
Files changed (60) hide show
  1. package/dist/center.d.ts +1 -1
  2. package/dist/center.d.ts.map +1 -1
  3. package/dist/center.js +16 -3
  4. package/dist/center.js.map +1 -1
  5. package/dist/diet-chart.d.ts +9 -4
  6. package/dist/diet-chart.d.ts.map +1 -1
  7. package/dist/diet-chart.js +24 -4
  8. package/dist/diet-chart.js.map +1 -1
  9. package/dist/equipment.d.ts +2 -2
  10. package/dist/equipment.d.ts.map +1 -1
  11. package/dist/equipment.js +16 -5
  12. package/dist/equipment.js.map +1 -1
  13. package/dist/exercise.d.ts +11 -11
  14. package/dist/exercise.d.ts.map +1 -1
  15. package/dist/exercise.js +17 -2
  16. package/dist/exercise.js.map +1 -1
  17. package/dist/food.d.ts +284 -13
  18. package/dist/food.d.ts.map +1 -1
  19. package/dist/food.js +247 -4
  20. package/dist/food.js.map +1 -1
  21. package/dist/gym-registration.d.ts +1 -0
  22. package/dist/gym-registration.d.ts.map +1 -1
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +2 -0
  26. package/dist/index.js.map +1 -1
  27. package/dist/member-profile.d.ts +4 -8
  28. package/dist/member-profile.d.ts.map +1 -1
  29. package/dist/member-profile.js +16 -2
  30. package/dist/member-profile.js.map +1 -1
  31. package/dist/member.d.ts +7 -7
  32. package/dist/member.d.ts.map +1 -1
  33. package/dist/member.js +15 -3
  34. package/dist/member.js.map +1 -1
  35. package/dist/membership-plan.d.ts +25 -15
  36. package/dist/membership-plan.d.ts.map +1 -1
  37. package/dist/membership-plan.js +16 -3
  38. package/dist/membership-plan.js.map +1 -1
  39. package/dist/membership-request.d.ts.map +1 -1
  40. package/dist/membership-request.js +4 -2
  41. package/dist/membership-request.js.map +1 -1
  42. package/dist/nutrition.d.ts +49 -0
  43. package/dist/nutrition.d.ts.map +1 -1
  44. package/dist/nutrition.js +61 -0
  45. package/dist/nutrition.js.map +1 -1
  46. package/dist/onboarding.d.ts +1 -0
  47. package/dist/onboarding.d.ts.map +1 -1
  48. package/dist/organisation.d.ts +4 -0
  49. package/dist/organisation.d.ts.map +1 -1
  50. package/dist/organisation.js +18 -0
  51. package/dist/organisation.js.map +1 -1
  52. package/dist/upi.d.ts +166 -0
  53. package/dist/upi.d.ts.map +1 -0
  54. package/dist/upi.js +200 -0
  55. package/dist/upi.js.map +1 -0
  56. package/dist/workout-plan.d.ts +3 -3
  57. package/dist/workout-plan.d.ts.map +1 -1
  58. package/dist/workout-plan.js +18 -2
  59. package/dist/workout-plan.js.map +1 -1
  60. package/package.json +1 -1
package/dist/center.d.ts CHANGED
@@ -311,7 +311,7 @@ export declare const updateCenterBodySchema: z.ZodObject<{
311
311
  to: z.ZodString;
312
312
  }, z.core.$strip>;
313
313
  }, z.core.$strip>>;
314
- photos: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
314
+ photos: z.ZodOptional<z.ZodArray<z.ZodString>>;
315
315
  }, z.core.$strip>;
316
316
  export type UpdateCenterBody = z.infer<typeof updateCenterBodySchema>;
317
317
  export declare const centerSchema: z.ZodObject<{
@@ -1 +1 @@
1
- {"version":3,"file":"center.d.ts","sourceRoot":"","sources":["../src/center.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAexB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,eAAO,MAAM,YAAY,oTAgCf,CAAC;AACX,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;EAAuB,CAAC;AACrD,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAI1D,eAAO,MAAM,kBAAkB;;;EAAiC,CAAC;AACjE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAM9D,OAAO,EAAE,KAAK,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAI/D,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAqCjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAmC,CAAC;AACvE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAItE,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAqDvB,CAAC;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAIlD;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB;;;;iBAI5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAI5D,yEAAyE;AACzE,eAAO,MAAM,eAAe;;;;;;;;;iBAkB1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAE9C,eAAO,MAAM,oBAAoB;;;;;;;;;iBAwC9B,CAAC;AACJ,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;iBAIhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAKpE,eAAO,MAAM,qBAAqB;;;;iBAIhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
1
+ {"version":3,"file":"center.d.ts","sourceRoot":"","sources":["../src/center.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAexB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,eAAO,MAAM,YAAY,oTAgCf,CAAC;AACX,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;EAAuB,CAAC;AACrD,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAI1D,eAAO,MAAM,kBAAkB;;;EAAiC,CAAC;AACjE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAM9D,OAAO,EAAE,KAAK,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAqD/D,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAEjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAiC,CAAC;AACrE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAItE,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAqDvB,CAAC;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAIlD;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB;;;;iBAI5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAI5D,yEAAyE;AACzE,eAAO,MAAM,eAAe;;;;;;;;;iBAkB1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAE9C,eAAO,MAAM,oBAAoB;;;;;;;;;iBAwC9B,CAAC;AACJ,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;iBAIhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAKpE,eAAO,MAAM,qBAAqB;;;;iBAIhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
package/dist/center.js CHANGED
@@ -99,7 +99,17 @@ export const centerStatusSchema = z.enum(['active', 'inactive']);
99
99
  // because a centre's code is a fact about a centre.
100
100
  export { centerCodeSchema } from './codes.js';
101
101
  // ─── Create / update bodies ──────────────────────────────────────────────────
102
- export const createCenterBodySchema = z.object({
102
+ /**
103
+ * ⚠️ NO `.default()` IN THIS BASE — the update schema is derived from it.
104
+ *
105
+ * `.partial()` makes a key optional; it does NOT switch off that key's
106
+ * `.default()`. A base carrying a default therefore parses a partial update
107
+ * body into an object containing that key, and the update services `$set` the
108
+ * parsed body straight through — so editing one field silently erases another
109
+ * the caller never mentioned. Defaults live on the CREATE export below.
110
+ * Pinned by `update-body-clobber.test.ts`.
111
+ */
112
+ const centerBodyBaseSchema = z.object({
103
113
  name: z.string().trim().min(1, 'Center name is required').max(160),
104
114
  description: optionalTrimmedString(2000),
105
115
  types: z.array(centerTypeSchema).min(1, 'Pick at least one'),
@@ -121,7 +131,7 @@ export const createCenterBodySchema = z.object({
121
131
  website: optionalTrimmedString(200),
122
132
  hours: weeklyHoursSchema,
123
133
  /** Uploaded gallery image URLs. */
124
- photos: z.array(z.string().trim().max(2000)).max(20).default([]),
134
+ photos: z.array(z.string().trim().max(2000)).max(20),
125
135
  /**
126
136
  * A pasted Google Maps link. The server reads the coordinates out of it; the
127
137
  * client never sends them.
@@ -137,7 +147,10 @@ export const createCenterBodySchema = z.object({
137
147
  */
138
148
  checkInRadiusM: z.number().int().min(50).max(5000).optional(),
139
149
  });
140
- export const updateCenterBodySchema = createCenterBodySchema.partial();
150
+ export const createCenterBodySchema = centerBodyBaseSchema.extend({
151
+ photos: z.array(z.string().trim().max(2000)).max(20).default([]),
152
+ });
153
+ export const updateCenterBodySchema = centerBodyBaseSchema.partial();
141
154
  // ─── Entity (API response) ───────────────────────────────────────────────────
142
155
  export const centerSchema = z.object({
143
156
  id: objectIdSchema,
@@ -1 +1 @@
1
- {"version":3,"file":"center.js","sourceRoot":"","sources":["../src/center.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Center (branch)\n * ================================\n * A physical gym location belonging to an organisation. Carries the public gym\n * code members use to join, opening hours, equipment scope, and contact info.\n * v1 memberships are single-center, so a Center is the unit a member joins.\n *\n * @module gymmonk-schema/center\n */\n\nimport { z } from 'zod';\nimport { centerCodeSchema } from './codes.js';\nimport {\n isoDateTimeSchema,\n objectIdSchema,\n optionalEmailSchema,\n optionalPhoneSchema,\n optionalTrimmedString,\n phoneSchema,\n} from './common.js';\nimport { mapLinkInputSchema, placePinSchema, timezoneSchema } from './location.js';\nimport { addressSchema, weeklyHoursSchema } from './shared.js';\n\n// ─── Center type ─────────────────────────────────────────────────────────────\n\n/**\n * What a centre OFFERS, multi-select.\n *\n * ── The axis, and why `personal-training` is gone ───────────────────────────\n * Every value here answers one question: what does somebody DO at this place.\n * That makes them disciplines and training modalities, so an owner ticking\n * three of them describes a real branch and a member reading them knows what\n * they are walking into.\n *\n * `personal-training` answered a different question — HOW coaching is\n * delivered, one-to-one instead of in a class — and nearly every gym in the\n * list offers it. A value almost everyone can tick separates nobody from\n * anybody, which is the opposite of what a type is for. Business model\n * (big-box, boutique, 24-hour) is a third axis again, and one this product\n * already answers better elsewhere: floor area and opening hours are recorded\n * fields, not a guess from a chip.\n *\n * ── Where the list comes from ───────────────────────────────────────────────\n * Cross-referenced rather than invented: Google Business Profile's fitness\n * categories (what gyms actually file themselves under, and therefore what\n * members search), ClassPass and Mindbody's activity taxonomies, and the\n * formats the Indian market actually runs — cult.fit's S&C, HRX, boxing, dance\n * fitness and yoga, plus the bodybuilding and martial-arts traditions that a\n * list drawn only from US sources would miss entirely.\n *\n * Deliberately NOT included, so the next person does not re-litigate it:\n * rock climbing, gymnastics and aerial/pole are real facility categories but\n * not gyms in the sense this product manages; women-only and kids' fitness\n * describe an AUDIENCE, not an activity; 24-hour and big-box describe hours\n * and size, both of which are already their own fields.\n *\n * Values are stable slugs — the LABELS are the frontend's business, so\n * rewording a chip never rewrites stored data.\n *\n * ── The field is called `types`; the screen says \"What this center offers\" ──\n * That divergence is deliberate, not an oversight. \"Type\" reads as one thing a\n * place IS, and a real branch runs several — a weights floor, a yoga room and\n * Zumba on weekends — so a singular-sounding label pushed owners to pick one\n * and misdescribe themselves. (The picker was once a single-select with a\n * \"Mixed\" option that saved as plain `gym`, which is how a CrossFit-and-boxing\n * branch came back as neither.) The label was changed to match what is stored;\n * the identifiers were not, because the values are a wire contract and\n * renaming them would rewrite data to fix a word.\n */\nexport const CENTER_TYPES = [\n // General\n 'gym',\n // Strength\n 'strength',\n 'crossfit',\n 'functional',\n 'bodybuilding',\n 'powerlifting',\n 'calisthenics',\n // Cardio & group training\n 'cardio',\n 'hiit',\n 'spinning',\n 'bootcamp',\n 'aerobics',\n 'sports-conditioning',\n // Mind & body\n 'yoga',\n 'pilates',\n 'meditation',\n // Dance\n 'zumba',\n 'dance',\n // Combat\n 'boxing',\n 'kickboxing',\n 'mma',\n 'martial-arts',\n // Facilities\n 'swimming',\n 'physiotherapy',\n] as const;\nexport const centerTypeSchema = z.enum(CENTER_TYPES);\nexport type CenterType = z.infer<typeof centerTypeSchema>;\n\n// ─── Status ──────────────────────────────────────────────────────────────────\n\nexport const centerStatusSchema = z.enum(['active', 'inactive']);\nexport type CenterStatus = z.infer<typeof centerStatusSchema>;\n\n// The public gym code (`IRN-1-7K2M`) is defined in `codes.ts` alongside the\n// member ids built on the same stem, so the four identifier shapes this product\n// issues can be read — and kept from colliding — in one place. Re-exported here\n// because a centre's code is a fact about a centre.\nexport { type CenterCode, centerCodeSchema } from './codes.js';\n\n// ─── Create / update bodies ──────────────────────────────────────────────────\n\nexport const createCenterBodySchema = z.object({\n name: z.string().trim().min(1, 'Center name is required').max(160),\n description: optionalTrimmedString(2000),\n types: z.array(centerTypeSchema).min(1, 'Pick at least one'),\n address: addressSchema,\n /** Usable floor area in square feet. */\n floorAreaSqft: z.number().int().min(0).max(1_000_000).optional(),\n /**\n * How many people the branch holds at once.\n *\n * The denominator behind \"how full are we\" — the centre detail screen shows\n * it beside the active-member count, and crowd-load has no meaning without\n * it. Optional: a gym that has never measured it should not be blocked from\n * saving the rest of its details.\n */\n capacity: z.number().int().min(1).max(100_000).optional(),\n primaryPhone: phoneSchema,\n secondaryPhone: optionalPhoneSchema,\n email: optionalEmailSchema,\n website: optionalTrimmedString(200),\n hours: weeklyHoursSchema,\n /** Uploaded gallery image URLs. */\n photos: z.array(z.string().trim().max(2000)).max(20).default([]),\n /**\n * A pasted Google Maps link. The server reads the coordinates out of it; the\n * client never sends them.\n *\n * This is the pin attendance is measured against, and it is NOT the same as\n * `address.coords`: that is an area centroid resolved from master data, which\n * can be a whole district's midpoint and is useless for proximity.\n */\n ...mapLinkInputSchema.shape,\n /**\n * How close a member must be for a check-in to count, metres. Optional — the\n * server applies its own default when a gym has not chosen one.\n */\n checkInRadiusM: z.number().int().min(50).max(5000).optional(),\n});\nexport type CreateCenterBody = z.infer<typeof createCenterBodySchema>;\n\nexport const updateCenterBodySchema = createCenterBodySchema.partial();\nexport type UpdateCenterBody = z.infer<typeof updateCenterBodySchema>;\n\n// ─── Entity (API response) ───────────────────────────────────────────────────\n\nexport const centerSchema = z.object({\n id: objectIdSchema,\n organisationId: objectIdSchema,\n /** Public join code, `IRN-1-7K2M`. */\n code: centerCodeSchema,\n /**\n * Which centre this is within its organisation — 1 for the first branch the\n * owner opened, 2 for the next.\n *\n * Stored rather than counted on read, because it is baked into this centre's\n * code AND into every member id the centre issues. Deleting branch 2 must not\n * renumber branch 3 into codes that are already printed.\n */\n sequence: z.number().int().positive(),\n name: z.string(),\n description: z.string().nullable(),\n types: z.array(centerTypeSchema),\n address: addressSchema,\n floorAreaSqft: z.number().int().nullable(),\n capacity: z.number().int().nullable(),\n primaryPhone: z.string(),\n secondaryPhone: z.string().nullable(),\n email: z.string().nullable(),\n website: z.string().nullable(),\n hours: weeklyHoursSchema,\n photos: z.array(z.string()),\n status: centerStatusSchema,\n /** Denormalised active-member count for roster headers. */\n memberCount: z.number().int().nonnegative(),\n // The resolved pin. Absent until an owner pastes a Maps link, which is why\n // attendance is unenforceable rather than failing for a gym without one.\n ...placePinSchema.shape,\n checkInRadiusM: z.number().int().nullable().optional(),\n /**\n * The centre's own timezone, as an IANA identifier (\"Asia/Kolkata\").\n *\n * WHY THE CENTRE'S AND NOT THE VIEWER'S. Attendance is filed under a calendar\n * DAY, and a day is not timezone-free — whichever zone derives it defines when\n * it starts. Deriving it from whoever is looking would put one visit on\n * different days for the member and the owner, and would make an owner's daily\n * roll-up change depending on where they opened the app. A visit belongs to the\n * gym's operating day; only the display of times follows the reader.\n *\n * An IANA NAME, never a UTC offset. Offsets are a property of an instant, not\n * of a place: they move with DST, so a stored \"+05:30\" is already wrong for\n * half the year anywhere that observes it.\n *\n * Server-derived from the centre's pin, never accepted from a client — the\n * same rule the coordinates follow, and for the same reason.\n */\n timezone: timezoneSchema,\n createdAt: isoDateTimeSchema,\n updatedAt: isoDateTimeSchema,\n});\nexport type Center = z.infer<typeof centerSchema>;\n\n// ─── Center stats (owner detail header) ──────────────────────────────────────\n\n/**\n * The three-up header on a centre's detail screen.\n *\n * Computed, never stored — `activeMembers` counts live memberships and\n * `monthlyRevenueInr` sums this month's successful payments, so the card can\n * never drift from the ledger the way a denormalised total would. `capacity`\n * is the one stored value, and is null until the owner fills it in.\n */\nexport const centerStatsSchema = z.object({\n capacity: z.number().int().nullable(),\n activeMembers: z.number().int().nonnegative(),\n monthlyRevenueInr: z.number().nonnegative(),\n});\nexport type CenterStats = z.infer<typeof centerStatsSchema>;\n\n// ─── Gym directory (member \"find a gym\") ─────────────────────────────────────\n\n/** A lightweight public directory result for the \"find a gym\" screen. */\nexport const nearbyGymSchema = z.object({\n id: objectIdSchema,\n code: centerCodeSchema,\n name: z.string(),\n /** The narrowest place name we hold (locality → district → state). */\n area: z.string(),\n district: z.string().nullable(),\n state: z.string().nullable(),\n /**\n * Kilometres from the coordinates the caller supplied.\n *\n * NULL, not 0, when it cannot be computed — the caller sent no position, or\n * the gym's address carries no coordinates. Zero is a real distance (\"you are\n * standing in it\") and using it as the unknown value put every un-geocoded\n * gym at the top of a nearest-first list.\n */\n distanceKm: z.number().nonnegative().nullable(),\n memberCount: z.number().int().nonnegative().optional(),\n});\nexport type NearbyGym = z.infer<typeof nearbyGymSchema>;\n\n/**\n * The gym directory has THREE ways in, and they differ in how far they reach.\n *\n * `code` exact public gym id — matches anywhere in the country, because\n * a member holding the id already knows which gym they mean and\n * may well be joining one in another state.\n * `q` name search — deliberately narrowed to ONE STATE. Gym names\n * repeat heavily (\"Fitness First\"), so a nationwide name search\n * returns a list nobody can pick from.\n * `districtId` browse — the gyms around them, nearest first when they share\n * their position.\n *\n * `stateId` / `districtId` are master-area ids (`area.ts`), matched against the\n * address `ancestry` array every center stores.\n */\nexport const GYM_SEARCH_MAX_RESULTS = 20;\n\n/**\n * How far \"near me\" reaches, in kilometres.\n *\n * A bound rather than \"the nearest 20 wherever they are\". Without one, a member\n * in a town with no gyms is shown the nearest one 400km away as though it were\n * a suggestion — a list that is technically sorted and practically useless. The\n * empty state points them at a different location or a gym id instead, which\n * are the two things that can actually help.\n */\nexport const GYM_SEARCH_NEARBY_RADIUS_KM = 50;\n\nexport const gymSearchQuerySchema = z\n .object({\n /** Exact public gym id, e.g. `GYM-2025-001`. Unscoped by geography. */\n code: optionalTrimmedString(40),\n /** Name fragment. Requires `stateId`. */\n q: optionalTrimmedString(120),\n stateId: optionalTrimmedString(40),\n districtId: optionalTrimmedString(40),\n /**\n * Any master-area id — state, district, or one of the eight sub-district\n * types the location drawer can reach.\n *\n * Matched against the `ancestry` every centre stores, exactly as\n * `stateId` / `districtId` are, so one parameter covers every depth the\n * picker offers. Those two stay because callers already send them and they\n * read more clearly at the two levels that have their own control.\n */\n areaId: optionalTrimmedString(40),\n lat: z.coerce.number().min(-90).max(90).optional(),\n lng: z.coerce.number().min(-180).max(180).optional(),\n limit: z.coerce\n .number()\n .int()\n .min(1)\n .max(GYM_SEARCH_MAX_RESULTS)\n .default(GYM_SEARCH_MAX_RESULTS),\n })\n .refine((v) => !v.q || Boolean(v.stateId), {\n message: 'Pick a state to search gyms by name',\n path: ['stateId'],\n })\n .refine(\n (v) =>\n Boolean(\n v.code || v.q || v.stateId || v.districtId || v.areaId || (v.lat != null && v.lng != null),\n ),\n {\n message: 'Enter a gym id, or pick where to look',\n path: ['stateId'],\n },\n );\nexport type GymSearchQuery = z.infer<typeof gymSearchQuerySchema>;\n\n/**\n * The directory response.\n *\n * `truncated` says the district holds more gyms than were returned, which is\n * what the screen turns into \"showing the 20 nearest — enter a gym id if yours\n * is not here\" rather than letting the member assume their gym is absent.\n */\nexport const gymSearchResultSchema = z.object({\n items: z.array(nearbyGymSchema),\n total: z.number().int().nonnegative(),\n truncated: z.boolean(),\n});\nexport type GymSearchResult = z.infer<typeof gymSearchResultSchema>;\n\n// ─── Retained: nearby-gyms query ─────────────────────────────────────────────\n// Kept for the gym-search route the owner console's repo still serves. See the\n// note in `onboarding.ts`.\nexport const nearbyGymsQuerySchema = z.object({\n lat: z.coerce.number().min(-90).max(90).optional(),\n lng: z.coerce.number().min(-180).max(180).optional(),\n q: optionalTrimmedString(120),\n});\nexport type NearbyGymsQuery = z.infer<typeof nearbyGymsQuerySchema>;\n"]}
1
+ {"version":3,"file":"center.js","sourceRoot":"","sources":["../src/center.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Center (branch)\n * ================================\n * A physical gym location belonging to an organisation. Carries the public gym\n * code members use to join, opening hours, equipment scope, and contact info.\n * v1 memberships are single-center, so a Center is the unit a member joins.\n *\n * @module gymmonk-schema/center\n */\n\nimport { z } from 'zod';\nimport { centerCodeSchema } from './codes.js';\nimport {\n isoDateTimeSchema,\n objectIdSchema,\n optionalEmailSchema,\n optionalPhoneSchema,\n optionalTrimmedString,\n phoneSchema,\n} from './common.js';\nimport { mapLinkInputSchema, placePinSchema, timezoneSchema } from './location.js';\nimport { addressSchema, weeklyHoursSchema } from './shared.js';\n\n// ─── Center type ─────────────────────────────────────────────────────────────\n\n/**\n * What a centre OFFERS, multi-select.\n *\n * ── The axis, and why `personal-training` is gone ───────────────────────────\n * Every value here answers one question: what does somebody DO at this place.\n * That makes them disciplines and training modalities, so an owner ticking\n * three of them describes a real branch and a member reading them knows what\n * they are walking into.\n *\n * `personal-training` answered a different question — HOW coaching is\n * delivered, one-to-one instead of in a class — and nearly every gym in the\n * list offers it. A value almost everyone can tick separates nobody from\n * anybody, which is the opposite of what a type is for. Business model\n * (big-box, boutique, 24-hour) is a third axis again, and one this product\n * already answers better elsewhere: floor area and opening hours are recorded\n * fields, not a guess from a chip.\n *\n * ── Where the list comes from ───────────────────────────────────────────────\n * Cross-referenced rather than invented: Google Business Profile's fitness\n * categories (what gyms actually file themselves under, and therefore what\n * members search), ClassPass and Mindbody's activity taxonomies, and the\n * formats the Indian market actually runs — cult.fit's S&C, HRX, boxing, dance\n * fitness and yoga, plus the bodybuilding and martial-arts traditions that a\n * list drawn only from US sources would miss entirely.\n *\n * Deliberately NOT included, so the next person does not re-litigate it:\n * rock climbing, gymnastics and aerial/pole are real facility categories but\n * not gyms in the sense this product manages; women-only and kids' fitness\n * describe an AUDIENCE, not an activity; 24-hour and big-box describe hours\n * and size, both of which are already their own fields.\n *\n * Values are stable slugs — the LABELS are the frontend's business, so\n * rewording a chip never rewrites stored data.\n *\n * ── The field is called `types`; the screen says \"What this center offers\" ──\n * That divergence is deliberate, not an oversight. \"Type\" reads as one thing a\n * place IS, and a real branch runs several — a weights floor, a yoga room and\n * Zumba on weekends — so a singular-sounding label pushed owners to pick one\n * and misdescribe themselves. (The picker was once a single-select with a\n * \"Mixed\" option that saved as plain `gym`, which is how a CrossFit-and-boxing\n * branch came back as neither.) The label was changed to match what is stored;\n * the identifiers were not, because the values are a wire contract and\n * renaming them would rewrite data to fix a word.\n */\nexport const CENTER_TYPES = [\n // General\n 'gym',\n // Strength\n 'strength',\n 'crossfit',\n 'functional',\n 'bodybuilding',\n 'powerlifting',\n 'calisthenics',\n // Cardio & group training\n 'cardio',\n 'hiit',\n 'spinning',\n 'bootcamp',\n 'aerobics',\n 'sports-conditioning',\n // Mind & body\n 'yoga',\n 'pilates',\n 'meditation',\n // Dance\n 'zumba',\n 'dance',\n // Combat\n 'boxing',\n 'kickboxing',\n 'mma',\n 'martial-arts',\n // Facilities\n 'swimming',\n 'physiotherapy',\n] as const;\nexport const centerTypeSchema = z.enum(CENTER_TYPES);\nexport type CenterType = z.infer<typeof centerTypeSchema>;\n\n// ─── Status ──────────────────────────────────────────────────────────────────\n\nexport const centerStatusSchema = z.enum(['active', 'inactive']);\nexport type CenterStatus = z.infer<typeof centerStatusSchema>;\n\n// The public gym code (`IRN-1-7K2M`) is defined in `codes.ts` alongside the\n// member ids built on the same stem, so the four identifier shapes this product\n// issues can be read — and kept from colliding — in one place. Re-exported here\n// because a centre's code is a fact about a centre.\nexport { type CenterCode, centerCodeSchema } from './codes.js';\n\n// ─── Create / update bodies ──────────────────────────────────────────────────\n\n/**\n * ⚠️ NO `.default()` IN THIS BASE — the update schema is derived from it.\n *\n * `.partial()` makes a key optional; it does NOT switch off that key's\n * `.default()`. A base carrying a default therefore parses a partial update\n * body into an object containing that key, and the update services `$set` the\n * parsed body straight through — so editing one field silently erases another\n * the caller never mentioned. Defaults live on the CREATE export below.\n * Pinned by `update-body-clobber.test.ts`.\n */\nconst centerBodyBaseSchema = z.object({\n name: z.string().trim().min(1, 'Center name is required').max(160),\n description: optionalTrimmedString(2000),\n types: z.array(centerTypeSchema).min(1, 'Pick at least one'),\n address: addressSchema,\n /** Usable floor area in square feet. */\n floorAreaSqft: z.number().int().min(0).max(1_000_000).optional(),\n /**\n * How many people the branch holds at once.\n *\n * The denominator behind \"how full are we\" — the centre detail screen shows\n * it beside the active-member count, and crowd-load has no meaning without\n * it. Optional: a gym that has never measured it should not be blocked from\n * saving the rest of its details.\n */\n capacity: z.number().int().min(1).max(100_000).optional(),\n primaryPhone: phoneSchema,\n secondaryPhone: optionalPhoneSchema,\n email: optionalEmailSchema,\n website: optionalTrimmedString(200),\n hours: weeklyHoursSchema,\n /** Uploaded gallery image URLs. */\n photos: z.array(z.string().trim().max(2000)).max(20),\n /**\n * A pasted Google Maps link. The server reads the coordinates out of it; the\n * client never sends them.\n *\n * This is the pin attendance is measured against, and it is NOT the same as\n * `address.coords`: that is an area centroid resolved from master data, which\n * can be a whole district's midpoint and is useless for proximity.\n */\n ...mapLinkInputSchema.shape,\n /**\n * How close a member must be for a check-in to count, metres. Optional — the\n * server applies its own default when a gym has not chosen one.\n */\n checkInRadiusM: z.number().int().min(50).max(5000).optional(),\n});\n\nexport const createCenterBodySchema = centerBodyBaseSchema.extend({\n photos: z.array(z.string().trim().max(2000)).max(20).default([]),\n});\nexport type CreateCenterBody = z.infer<typeof createCenterBodySchema>;\n\nexport const updateCenterBodySchema = centerBodyBaseSchema.partial();\nexport type UpdateCenterBody = z.infer<typeof updateCenterBodySchema>;\n\n// ─── Entity (API response) ───────────────────────────────────────────────────\n\nexport const centerSchema = z.object({\n id: objectIdSchema,\n organisationId: objectIdSchema,\n /** Public join code, `IRN-1-7K2M`. */\n code: centerCodeSchema,\n /**\n * Which centre this is within its organisation — 1 for the first branch the\n * owner opened, 2 for the next.\n *\n * Stored rather than counted on read, because it is baked into this centre's\n * code AND into every member id the centre issues. Deleting branch 2 must not\n * renumber branch 3 into codes that are already printed.\n */\n sequence: z.number().int().positive(),\n name: z.string(),\n description: z.string().nullable(),\n types: z.array(centerTypeSchema),\n address: addressSchema,\n floorAreaSqft: z.number().int().nullable(),\n capacity: z.number().int().nullable(),\n primaryPhone: z.string(),\n secondaryPhone: z.string().nullable(),\n email: z.string().nullable(),\n website: z.string().nullable(),\n hours: weeklyHoursSchema,\n photos: z.array(z.string()),\n status: centerStatusSchema,\n /** Denormalised active-member count for roster headers. */\n memberCount: z.number().int().nonnegative(),\n // The resolved pin. Absent until an owner pastes a Maps link, which is why\n // attendance is unenforceable rather than failing for a gym without one.\n ...placePinSchema.shape,\n checkInRadiusM: z.number().int().nullable().optional(),\n /**\n * The centre's own timezone, as an IANA identifier (\"Asia/Kolkata\").\n *\n * WHY THE CENTRE'S AND NOT THE VIEWER'S. Attendance is filed under a calendar\n * DAY, and a day is not timezone-free — whichever zone derives it defines when\n * it starts. Deriving it from whoever is looking would put one visit on\n * different days for the member and the owner, and would make an owner's daily\n * roll-up change depending on where they opened the app. A visit belongs to the\n * gym's operating day; only the display of times follows the reader.\n *\n * An IANA NAME, never a UTC offset. Offsets are a property of an instant, not\n * of a place: they move with DST, so a stored \"+05:30\" is already wrong for\n * half the year anywhere that observes it.\n *\n * Server-derived from the centre's pin, never accepted from a client — the\n * same rule the coordinates follow, and for the same reason.\n */\n timezone: timezoneSchema,\n createdAt: isoDateTimeSchema,\n updatedAt: isoDateTimeSchema,\n});\nexport type Center = z.infer<typeof centerSchema>;\n\n// ─── Center stats (owner detail header) ──────────────────────────────────────\n\n/**\n * The three-up header on a centre's detail screen.\n *\n * Computed, never stored — `activeMembers` counts live memberships and\n * `monthlyRevenueInr` sums this month's successful payments, so the card can\n * never drift from the ledger the way a denormalised total would. `capacity`\n * is the one stored value, and is null until the owner fills it in.\n */\nexport const centerStatsSchema = z.object({\n capacity: z.number().int().nullable(),\n activeMembers: z.number().int().nonnegative(),\n monthlyRevenueInr: z.number().nonnegative(),\n});\nexport type CenterStats = z.infer<typeof centerStatsSchema>;\n\n// ─── Gym directory (member \"find a gym\") ─────────────────────────────────────\n\n/** A lightweight public directory result for the \"find a gym\" screen. */\nexport const nearbyGymSchema = z.object({\n id: objectIdSchema,\n code: centerCodeSchema,\n name: z.string(),\n /** The narrowest place name we hold (locality → district → state). */\n area: z.string(),\n district: z.string().nullable(),\n state: z.string().nullable(),\n /**\n * Kilometres from the coordinates the caller supplied.\n *\n * NULL, not 0, when it cannot be computed — the caller sent no position, or\n * the gym's address carries no coordinates. Zero is a real distance (\"you are\n * standing in it\") and using it as the unknown value put every un-geocoded\n * gym at the top of a nearest-first list.\n */\n distanceKm: z.number().nonnegative().nullable(),\n memberCount: z.number().int().nonnegative().optional(),\n});\nexport type NearbyGym = z.infer<typeof nearbyGymSchema>;\n\n/**\n * The gym directory has THREE ways in, and they differ in how far they reach.\n *\n * `code` exact public gym id — matches anywhere in the country, because\n * a member holding the id already knows which gym they mean and\n * may well be joining one in another state.\n * `q` name search — deliberately narrowed to ONE STATE. Gym names\n * repeat heavily (\"Fitness First\"), so a nationwide name search\n * returns a list nobody can pick from.\n * `districtId` browse — the gyms around them, nearest first when they share\n * their position.\n *\n * `stateId` / `districtId` are master-area ids (`area.ts`), matched against the\n * address `ancestry` array every center stores.\n */\nexport const GYM_SEARCH_MAX_RESULTS = 20;\n\n/**\n * How far \"near me\" reaches, in kilometres.\n *\n * A bound rather than \"the nearest 20 wherever they are\". Without one, a member\n * in a town with no gyms is shown the nearest one 400km away as though it were\n * a suggestion — a list that is technically sorted and practically useless. The\n * empty state points them at a different location or a gym id instead, which\n * are the two things that can actually help.\n */\nexport const GYM_SEARCH_NEARBY_RADIUS_KM = 50;\n\nexport const gymSearchQuerySchema = z\n .object({\n /** Exact public gym id, e.g. `GYM-2025-001`. Unscoped by geography. */\n code: optionalTrimmedString(40),\n /** Name fragment. Requires `stateId`. */\n q: optionalTrimmedString(120),\n stateId: optionalTrimmedString(40),\n districtId: optionalTrimmedString(40),\n /**\n * Any master-area id — state, district, or one of the eight sub-district\n * types the location drawer can reach.\n *\n * Matched against the `ancestry` every centre stores, exactly as\n * `stateId` / `districtId` are, so one parameter covers every depth the\n * picker offers. Those two stay because callers already send them and they\n * read more clearly at the two levels that have their own control.\n */\n areaId: optionalTrimmedString(40),\n lat: z.coerce.number().min(-90).max(90).optional(),\n lng: z.coerce.number().min(-180).max(180).optional(),\n limit: z.coerce\n .number()\n .int()\n .min(1)\n .max(GYM_SEARCH_MAX_RESULTS)\n .default(GYM_SEARCH_MAX_RESULTS),\n })\n .refine((v) => !v.q || Boolean(v.stateId), {\n message: 'Pick a state to search gyms by name',\n path: ['stateId'],\n })\n .refine(\n (v) =>\n Boolean(\n v.code || v.q || v.stateId || v.districtId || v.areaId || (v.lat != null && v.lng != null),\n ),\n {\n message: 'Enter a gym id, or pick where to look',\n path: ['stateId'],\n },\n );\nexport type GymSearchQuery = z.infer<typeof gymSearchQuerySchema>;\n\n/**\n * The directory response.\n *\n * `truncated` says the district holds more gyms than were returned, which is\n * what the screen turns into \"showing the 20 nearest — enter a gym id if yours\n * is not here\" rather than letting the member assume their gym is absent.\n */\nexport const gymSearchResultSchema = z.object({\n items: z.array(nearbyGymSchema),\n total: z.number().int().nonnegative(),\n truncated: z.boolean(),\n});\nexport type GymSearchResult = z.infer<typeof gymSearchResultSchema>;\n\n// ─── Retained: nearby-gyms query ─────────────────────────────────────────────\n// Kept for the gym-search route the owner console's repo still serves. See the\n// note in `onboarding.ts`.\nexport const nearbyGymsQuerySchema = z.object({\n lat: z.coerce.number().min(-90).max(90).optional(),\n lng: z.coerce.number().min(-180).max(180).optional(),\n q: optionalTrimmedString(120),\n});\nexport type NearbyGymsQuery = z.infer<typeof nearbyGymsQuerySchema>;\n"]}
@@ -137,6 +137,7 @@ export declare const dietItemSchema: z.ZodObject<{
137
137
  label: z.ZodString;
138
138
  grams: z.ZodNumber;
139
139
  }, z.core.$strip>>;
140
+ isRecipe: z.ZodBoolean;
140
141
  }, z.core.$strip>;
141
142
  quantityGrams: z.ZodNumber;
142
143
  note: z.ZodNullable<z.ZodString>;
@@ -201,6 +202,7 @@ export declare const dietMealSchema: z.ZodObject<{
201
202
  label: z.ZodString;
202
203
  grams: z.ZodNumber;
203
204
  }, z.core.$strip>>;
205
+ isRecipe: z.ZodBoolean;
204
206
  }, z.core.$strip>;
205
207
  quantityGrams: z.ZodNumber;
206
208
  note: z.ZodNullable<z.ZodString>;
@@ -293,6 +295,7 @@ export declare const dietChartDaySchema: z.ZodObject<{
293
295
  label: z.ZodString;
294
296
  grams: z.ZodNumber;
295
297
  }, z.core.$strip>>;
298
+ isRecipe: z.ZodBoolean;
296
299
  }, z.core.$strip>;
297
300
  quantityGrams: z.ZodNumber;
298
301
  note: z.ZodNullable<z.ZodString>;
@@ -425,6 +428,7 @@ export declare const dietChartSchema: z.ZodObject<{
425
428
  label: z.ZodString;
426
429
  grams: z.ZodNumber;
427
430
  }, z.core.$strip>>;
431
+ isRecipe: z.ZodBoolean;
428
432
  }, z.core.$strip>;
429
433
  quantityGrams: z.ZodNumber;
430
434
  note: z.ZodNullable<z.ZodString>;
@@ -530,9 +534,9 @@ export declare const updateDietChartBodySchema: z.ZodObject<{
530
534
  name: z.ZodOptional<z.ZodString>;
531
535
  description: z.ZodOptional<z.ZodPreprocess<z.ZodOptional<z.ZodString>>>;
532
536
  goals: z.ZodOptional<z.ZodPreprocess<z.ZodOptional<z.ZodString>>>;
533
- tags: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
534
- targetCalories: z.ZodOptional<z.ZodDefault<z.ZodNullable<z.ZodNumber>>>;
535
- days: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodObject<{
537
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
538
+ targetCalories: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
539
+ days: z.ZodOptional<z.ZodArray<z.ZodObject<{
536
540
  weekday: z.ZodEnum<{
537
541
  mon: "mon";
538
542
  tue: "tue";
@@ -560,7 +564,7 @@ export declare const updateDietChartBodySchema: z.ZodObject<{
560
564
  note: z.ZodPreprocess<z.ZodOptional<z.ZodString>>;
561
565
  }, z.core.$strip>>>;
562
566
  }, z.core.$strip>>>;
563
- }, z.core.$strip>>>>;
567
+ }, z.core.$strip>>>;
564
568
  }, z.core.$strip>;
565
569
  export type UpdateDietChartBody = z.infer<typeof updateDietChartBodySchema>;
566
570
  export declare const dietChartQuerySchema: z.ZodObject<{
@@ -646,6 +650,7 @@ export declare const myDietChartSchema: z.ZodObject<{
646
650
  label: z.ZodString;
647
651
  grams: z.ZodNumber;
648
652
  }, z.core.$strip>>;
653
+ isRecipe: z.ZodBoolean;
649
654
  }, z.core.$strip>;
650
655
  quantityGrams: z.ZodNumber;
651
656
  note: z.ZodNullable<z.ZodString>;
@@ -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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"}
@@ -155,19 +155,39 @@ export const dietChartSchema = dietChartSummarySchema.extend({
155
155
  days: z.array(dietChartDaySchema),
156
156
  });
157
157
  // ─── Create / update body ────────────────────────────────────────────────────
158
- export const createDietChartBodySchema = z.object({
158
+ /**
159
+ * ⚠️ NO `.default()` IN THIS BASE. It carried three, and one of them lost data.
160
+ *
161
+ * `updateDietChartBodySchema` is this object `.omit().partial()`-ed, and in Zod
162
+ * a `.default()` STILL FIRES under `.partial()` when the key is absent. So
163
+ * `PUT /diet-charts/:id` with `{ name: 'Renamed' }` parsed to
164
+ * `{ name, tags: [], targetCalories: null, days: [] }`, and `updateDietChart`
165
+ * does `$set: body`.
166
+ *
167
+ * **Renaming a chart erased every meal in it**, and every member assigned to it
168
+ * silently got an empty diet. Nothing in the request said so and nothing in the
169
+ * response looked wrong.
170
+ *
171
+ * The defaults live on the CREATE export below, which is the only place a
172
+ * missing key genuinely means "empty". Pinned by a test that parses `{}` and
173
+ * asserts nothing comes back.
174
+ */
175
+ const dietChartBodyBaseSchema = z.object({
159
176
  /** Nullable, never optional — see `createFoodBodySchema.centerId`. */
160
177
  centerId: objectIdSchema.nullable(),
161
178
  name: z.string().trim().min(1, 'Chart name is required').max(120),
162
179
  description: optionalTrimmedString(2000),
163
180
  goals: optionalTrimmedString(400),
181
+ tags: z.array(z.string().trim().max(40)).max(20),
182
+ targetCalories: z.number().min(0).max(20000).nullable(),
183
+ days: z.array(dietChartDayInputSchema).max(7),
184
+ });
185
+ export const createDietChartBodySchema = dietChartBodyBaseSchema.extend({
164
186
  tags: z.array(z.string().trim().max(40)).max(20).default([]),
165
187
  targetCalories: z.number().min(0).max(20000).nullable().default(null),
166
188
  days: z.array(dietChartDayInputSchema).max(7).default([]),
167
189
  });
168
- export const updateDietChartBodySchema = createDietChartBodySchema
169
- .omit({ centerId: true })
170
- .partial();
190
+ export const updateDietChartBodySchema = dietChartBodyBaseSchema.omit({ centerId: true }).partial();
171
191
  // ─── Library query ───────────────────────────────────────────────────────────
172
192
  export const dietChartQuerySchema = z.object({
173
193
  centerId: objectIdSchema.optional(),
@@ -1 +1 @@
1
- {"version":3,"file":"diet-chart.js","sourceRoot":"","sources":["../src/diet-chart.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Diet charts\n * =============================\n * A weekly eating plan a gym builds from library foods and assigns to a member.\n * The counterpart to `workout-plan`, and deliberately the same shape: a chart\n * scoped by `centerId`, days keyed by weekday, and a single assignment ref on\n * the member. Whoever has understood one understands the other.\n *\n * chart → days (weekday) → meals (type + time) → items (food + grams)\n *\n * That is the structure PT software converges on, and it is the smallest one\n * that expresses what gyms actually write: a plan that differs on training days\n * and rest days, five or six eating occasions rather than three, and a portion\n * per food.\n *\n * ── Nothing nutritional is stored on a chart ────────────────────────────────\n * An item stores a food ref and a quantity in grams. Every calorie and every\n * macro on every response is derived by the API through `scaleNutrition` and\n * `sumNutrition`. Storing totals would freeze them: correcting a food's protein\n * would leave every chart built from it quoting the old figure, which is the\n * exact failure the shared `displayName` column taught this codebase to avoid.\n *\n * @module gymmonk-schema/diet-chart\n */\n\nimport { z } from 'zod';\nimport { isoDateTimeSchema, objectIdSchema, optionalTrimmedString } from './common.js';\nimport { foodServingSchema, foodSummarySchema } from './food.js';\nimport { scaledNutritionSchema } from './nutrition.js';\nimport { weekdaySchema } from './shared.js';\n\n// ─── Meal vocabulary ─────────────────────────────────────────────────────────\n\n/**\n * The eating occasions a plan is written in.\n *\n * A fixed list rather than free text, because the member's day is RENDERED in\n * this order and a typed name cannot be sorted: \"Evening Snack\" and \"evening\n * snack\" would become two meals, and nothing could tell whether a pre-workout\n * shake comes before or after lunch. Seven covers the three-meal plan and the\n * six-meal one without the desk person inventing labels.\n */\nexport const mealTypeSchema = z.enum([\n 'early-morning',\n 'breakfast',\n 'mid-morning',\n 'pre-workout',\n 'lunch',\n 'post-workout',\n 'evening-snack',\n 'dinner',\n]);\nexport type MealType = z.infer<typeof mealTypeSchema>;\n\n/** Render order. The enum's own order is the day's order; this makes that explicit. */\nexport const MEAL_TYPE_ORDER: readonly MealType[] = [\n 'early-morning',\n 'breakfast',\n 'mid-morning',\n 'pre-workout',\n 'lunch',\n 'post-workout',\n 'evening-snack',\n 'dinner',\n] as const;\n\n/** Display labels, defined once so the two clients cannot title-case differently. */\nexport const MEAL_TYPE_LABELS: Record<MealType, string> = {\n 'early-morning': 'Early morning',\n breakfast: 'Breakfast',\n 'mid-morning': 'Mid morning',\n 'pre-workout': 'Pre workout',\n lunch: 'Lunch',\n 'post-workout': 'Post workout',\n 'evening-snack': 'Evening snack',\n dinner: 'Dinner',\n};\n\n// ─── Prescription (stored input) ─────────────────────────────────────────────\n\n/**\n * One food in a meal, as stored.\n *\n * `quantityGrams` is the ONLY quantity. A serving label (\"2 rotis\") is resolved\n * to grams by the client at entry using the food's own `serving`, so the record\n * holds one unambiguous number and a food whose serving weight is later\n * corrected does not silently change what an existing chart prescribes.\n */\nexport const dietItemInputSchema = z.object({\n foodId: objectIdSchema,\n quantityGrams: z.number().min(1).max(5000),\n /** \"cook without oil\", \"with skin\". Not nutritional; guidance for the member. */\n note: optionalTrimmedString(200),\n});\nexport type DietItemInput = z.infer<typeof dietItemInputSchema>;\n\nexport const dietMealInputSchema = z.object({\n mealType: mealTypeSchema,\n /**\n * \"07:30\", or absent. A gym that runs to the clock writes times; one that\n * does not writes \"breakfast\" and leaves when to the member. Free text rather\n * than a time type because \"within 30 min of training\" is a real instruction\n * and no time field can hold it.\n */\n timeLabel: optionalTrimmedString(40),\n items: z.array(dietItemInputSchema).max(20).default([]),\n});\nexport type DietMealInput = z.infer<typeof dietMealInputSchema>;\n\nexport const dietChartDayInputSchema = z.object({\n weekday: weekdaySchema,\n meals: z.array(dietMealInputSchema).max(8).default([]),\n});\nexport type DietChartDayInput = z.infer<typeof dietChartDayInputSchema>;\n\n// ─── Populated response shapes ───────────────────────────────────────────────\n\n/** A chart item with its food summary and the nutrition FOR THIS QUANTITY. */\nexport const dietItemSchema = z.object({\n foodId: objectIdSchema,\n food: foodSummarySchema,\n quantityGrams: z.number(),\n note: z.string().nullable(),\n /**\n * How the quantity reads to a person: \"2 rotis (80 g)\" rather than \"80 g\".\n * Null when the food has no serving, in which case grams IS the human form.\n */\n serving: foodServingSchema.nullable(),\n /** `scaleNutrition(food.nutrition, quantityGrams)`. Derived, never stored. */\n nutrition: scaledNutritionSchema,\n});\nexport type DietItem = z.infer<typeof dietItemSchema>;\n\nexport const dietMealSchema = z.object({\n mealType: mealTypeSchema,\n timeLabel: z.string().nullable(),\n items: z.array(dietItemSchema),\n /** Sum of the items. Derived. */\n totals: scaledNutritionSchema,\n});\nexport type DietMeal = z.infer<typeof dietMealSchema>;\n\nexport const dietChartDaySchema = z.object({\n weekday: weekdaySchema,\n meals: z.array(dietMealSchema),\n /** Sum of the day's meals. Derived. */\n totals: scaledNutritionSchema,\n});\nexport type DietChartDay = z.infer<typeof dietChartDaySchema>;\n\n// ─── Entity ──────────────────────────────────────────────────────────────────\n\nexport const dietChartSummarySchema = z.object({\n id: objectIdSchema,\n /**\n * `null` = a PLATFORM template offered to every gym; a center id = that gym's\n * own chart, visible only to its people. Same rule as `WorkoutPlan`.\n */\n centerId: objectIdSchema.nullable(),\n name: z.string(),\n description: z.string().nullable(),\n /** What the chart is FOR — \"Fat loss, 1800 kcal\" — as distinct from the blurb. */\n goals: z.string().nullable(),\n tags: z.array(z.string()),\n /**\n * The daily calorie target this chart was written to, if the author stated\n * one. It is the AUTHOR'S INTENT and is not computed from the meals: the\n * difference between what a chart aims at and what it actually adds up to is\n * the single most useful thing on the builder screen, and deriving this would\n * destroy it by making the two equal by construction.\n */\n targetCalories: z.number().nullable(),\n createdAt: isoDateTimeSchema,\n updatedAt: isoDateTimeSchema,\n});\nexport type DietChartSummary = z.infer<typeof dietChartSummarySchema>;\n\nexport const dietChartSchema = dietChartSummarySchema.extend({\n days: z.array(dietChartDaySchema),\n});\nexport type DietChart = z.infer<typeof dietChartSchema>;\n\n// ─── Create / update body ────────────────────────────────────────────────────\n\nexport const createDietChartBodySchema = z.object({\n /** Nullable, never optional — see `createFoodBodySchema.centerId`. */\n centerId: objectIdSchema.nullable(),\n name: z.string().trim().min(1, 'Chart name is required').max(120),\n description: optionalTrimmedString(2000),\n goals: optionalTrimmedString(400),\n tags: z.array(z.string().trim().max(40)).max(20).default([]),\n targetCalories: z.number().min(0).max(20000).nullable().default(null),\n days: z.array(dietChartDayInputSchema).max(7).default([]),\n});\nexport type CreateDietChartBody = z.infer<typeof createDietChartBodySchema>;\n\nexport const updateDietChartBodySchema = createDietChartBodySchema\n .omit({ centerId: true })\n .partial();\nexport type UpdateDietChartBody = z.infer<typeof updateDietChartBodySchema>;\n\n// ─── Library query ───────────────────────────────────────────────────────────\n\nexport const dietChartQuerySchema = z.object({\n centerId: objectIdSchema.optional(),\n q: optionalTrimmedString(120),\n});\nexport type DietChartQuery = z.infer<typeof dietChartQuerySchema>;\n\n// ─── Assignment ──────────────────────────────────────────────────────────────\n\n/**\n * Assign a chart to a member, or clear it with `null`.\n *\n * Nullable rather than a separate DELETE endpoint so \"change this member's\n * chart\" and \"take it away\" are one operation with one permission check, which\n * is how `assignedWorkoutPlanId` already behaves.\n */\nexport const assignDietChartBodySchema = z.object({\n dietChartId: objectIdSchema.nullable(),\n});\nexport type AssignDietChartBody = z.infer<typeof assignDietChartBodySchema>;\n\n/**\n * What the member's own Diet tab reads.\n *\n * `chart` is null for a member whose gym has not written them one, which is the\n * ordinary state for most members most of the time and therefore not an error.\n * `assignedAt` is what lets the screen say \"since 3 March\" rather than implying\n * the plan is new.\n */\nexport const myDietChartSchema = z.object({\n chart: dietChartSchema.nullable(),\n assignedAt: isoDateTimeSchema.nullable(),\n});\nexport type MyDietChart = z.infer<typeof myDietChartSchema>;\n"]}
1
+ {"version":3,"file":"diet-chart.js","sourceRoot":"","sources":["../src/diet-chart.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Diet charts\n * =============================\n * A weekly eating plan a gym builds from library foods and assigns to a member.\n * The counterpart to `workout-plan`, and deliberately the same shape: a chart\n * scoped by `centerId`, days keyed by weekday, and a single assignment ref on\n * the member. Whoever has understood one understands the other.\n *\n * chart → days (weekday) → meals (type + time) → items (food + grams)\n *\n * That is the structure PT software converges on, and it is the smallest one\n * that expresses what gyms actually write: a plan that differs on training days\n * and rest days, five or six eating occasions rather than three, and a portion\n * per food.\n *\n * ── Nothing nutritional is stored on a chart ────────────────────────────────\n * An item stores a food ref and a quantity in grams. Every calorie and every\n * macro on every response is derived by the API through `scaleNutrition` and\n * `sumNutrition`. Storing totals would freeze them: correcting a food's protein\n * would leave every chart built from it quoting the old figure, which is the\n * exact failure the shared `displayName` column taught this codebase to avoid.\n *\n * @module gymmonk-schema/diet-chart\n */\n\nimport { z } from 'zod';\nimport { isoDateTimeSchema, objectIdSchema, optionalTrimmedString } from './common.js';\nimport { foodServingSchema, foodSummarySchema } from './food.js';\nimport { scaledNutritionSchema } from './nutrition.js';\nimport { weekdaySchema } from './shared.js';\n\n// ─── Meal vocabulary ─────────────────────────────────────────────────────────\n\n/**\n * The eating occasions a plan is written in.\n *\n * A fixed list rather than free text, because the member's day is RENDERED in\n * this order and a typed name cannot be sorted: \"Evening Snack\" and \"evening\n * snack\" would become two meals, and nothing could tell whether a pre-workout\n * shake comes before or after lunch. Seven covers the three-meal plan and the\n * six-meal one without the desk person inventing labels.\n */\nexport const mealTypeSchema = z.enum([\n 'early-morning',\n 'breakfast',\n 'mid-morning',\n 'pre-workout',\n 'lunch',\n 'post-workout',\n 'evening-snack',\n 'dinner',\n]);\nexport type MealType = z.infer<typeof mealTypeSchema>;\n\n/** Render order. The enum's own order is the day's order; this makes that explicit. */\nexport const MEAL_TYPE_ORDER: readonly MealType[] = [\n 'early-morning',\n 'breakfast',\n 'mid-morning',\n 'pre-workout',\n 'lunch',\n 'post-workout',\n 'evening-snack',\n 'dinner',\n] as const;\n\n/** Display labels, defined once so the two clients cannot title-case differently. */\nexport const MEAL_TYPE_LABELS: Record<MealType, string> = {\n 'early-morning': 'Early morning',\n breakfast: 'Breakfast',\n 'mid-morning': 'Mid morning',\n 'pre-workout': 'Pre workout',\n lunch: 'Lunch',\n 'post-workout': 'Post workout',\n 'evening-snack': 'Evening snack',\n dinner: 'Dinner',\n};\n\n// ─── Prescription (stored input) ─────────────────────────────────────────────\n\n/**\n * One food in a meal, as stored.\n *\n * `quantityGrams` is the ONLY quantity. A serving label (\"2 rotis\") is resolved\n * to grams by the client at entry using the food's own `serving`, so the record\n * holds one unambiguous number and a food whose serving weight is later\n * corrected does not silently change what an existing chart prescribes.\n */\nexport const dietItemInputSchema = z.object({\n foodId: objectIdSchema,\n quantityGrams: z.number().min(1).max(5000),\n /** \"cook without oil\", \"with skin\". Not nutritional; guidance for the member. */\n note: optionalTrimmedString(200),\n});\nexport type DietItemInput = z.infer<typeof dietItemInputSchema>;\n\nexport const dietMealInputSchema = z.object({\n mealType: mealTypeSchema,\n /**\n * \"07:30\", or absent. A gym that runs to the clock writes times; one that\n * does not writes \"breakfast\" and leaves when to the member. Free text rather\n * than a time type because \"within 30 min of training\" is a real instruction\n * and no time field can hold it.\n */\n timeLabel: optionalTrimmedString(40),\n items: z.array(dietItemInputSchema).max(20).default([]),\n});\nexport type DietMealInput = z.infer<typeof dietMealInputSchema>;\n\nexport const dietChartDayInputSchema = z.object({\n weekday: weekdaySchema,\n meals: z.array(dietMealInputSchema).max(8).default([]),\n});\nexport type DietChartDayInput = z.infer<typeof dietChartDayInputSchema>;\n\n// ─── Populated response shapes ───────────────────────────────────────────────\n\n/** A chart item with its food summary and the nutrition FOR THIS QUANTITY. */\nexport const dietItemSchema = z.object({\n foodId: objectIdSchema,\n food: foodSummarySchema,\n quantityGrams: z.number(),\n note: z.string().nullable(),\n /**\n * How the quantity reads to a person: \"2 rotis (80 g)\" rather than \"80 g\".\n * Null when the food has no serving, in which case grams IS the human form.\n */\n serving: foodServingSchema.nullable(),\n /** `scaleNutrition(food.nutrition, quantityGrams)`. Derived, never stored. */\n nutrition: scaledNutritionSchema,\n});\nexport type DietItem = z.infer<typeof dietItemSchema>;\n\nexport const dietMealSchema = z.object({\n mealType: mealTypeSchema,\n timeLabel: z.string().nullable(),\n items: z.array(dietItemSchema),\n /** Sum of the items. Derived. */\n totals: scaledNutritionSchema,\n});\nexport type DietMeal = z.infer<typeof dietMealSchema>;\n\nexport const dietChartDaySchema = z.object({\n weekday: weekdaySchema,\n meals: z.array(dietMealSchema),\n /** Sum of the day's meals. Derived. */\n totals: scaledNutritionSchema,\n});\nexport type DietChartDay = z.infer<typeof dietChartDaySchema>;\n\n// ─── Entity ──────────────────────────────────────────────────────────────────\n\nexport const dietChartSummarySchema = z.object({\n id: objectIdSchema,\n /**\n * `null` = a PLATFORM template offered to every gym; a center id = that gym's\n * own chart, visible only to its people. Same rule as `WorkoutPlan`.\n */\n centerId: objectIdSchema.nullable(),\n name: z.string(),\n description: z.string().nullable(),\n /** What the chart is FOR — \"Fat loss, 1800 kcal\" — as distinct from the blurb. */\n goals: z.string().nullable(),\n tags: z.array(z.string()),\n /**\n * The daily calorie target this chart was written to, if the author stated\n * one. It is the AUTHOR'S INTENT and is not computed from the meals: the\n * difference between what a chart aims at and what it actually adds up to is\n * the single most useful thing on the builder screen, and deriving this would\n * destroy it by making the two equal by construction.\n */\n targetCalories: z.number().nullable(),\n createdAt: isoDateTimeSchema,\n updatedAt: isoDateTimeSchema,\n});\nexport type DietChartSummary = z.infer<typeof dietChartSummarySchema>;\n\nexport const dietChartSchema = dietChartSummarySchema.extend({\n days: z.array(dietChartDaySchema),\n});\nexport type DietChart = z.infer<typeof dietChartSchema>;\n\n// ─── Create / update body ────────────────────────────────────────────────────\n\n/**\n * ⚠️ NO `.default()` IN THIS BASE. It carried three, and one of them lost data.\n *\n * `updateDietChartBodySchema` is this object `.omit().partial()`-ed, and in Zod\n * a `.default()` STILL FIRES under `.partial()` when the key is absent. So\n * `PUT /diet-charts/:id` with `{ name: 'Renamed' }` parsed to\n * `{ name, tags: [], targetCalories: null, days: [] }`, and `updateDietChart`\n * does `$set: body`.\n *\n * **Renaming a chart erased every meal in it**, and every member assigned to it\n * silently got an empty diet. Nothing in the request said so and nothing in the\n * response looked wrong.\n *\n * The defaults live on the CREATE export below, which is the only place a\n * missing key genuinely means \"empty\". Pinned by a test that parses `{}` and\n * asserts nothing comes back.\n */\nconst dietChartBodyBaseSchema = z.object({\n /** Nullable, never optional — see `createFoodBodySchema.centerId`. */\n centerId: objectIdSchema.nullable(),\n name: z.string().trim().min(1, 'Chart name is required').max(120),\n description: optionalTrimmedString(2000),\n goals: optionalTrimmedString(400),\n tags: z.array(z.string().trim().max(40)).max(20),\n targetCalories: z.number().min(0).max(20000).nullable(),\n days: z.array(dietChartDayInputSchema).max(7),\n});\n\nexport const createDietChartBodySchema = dietChartBodyBaseSchema.extend({\n tags: z.array(z.string().trim().max(40)).max(20).default([]),\n targetCalories: z.number().min(0).max(20000).nullable().default(null),\n days: z.array(dietChartDayInputSchema).max(7).default([]),\n});\nexport type CreateDietChartBody = z.infer<typeof createDietChartBodySchema>;\n\nexport const updateDietChartBodySchema = dietChartBodyBaseSchema.omit({ centerId: true }).partial();\nexport type UpdateDietChartBody = z.infer<typeof updateDietChartBodySchema>;\n\n// ─── Library query ───────────────────────────────────────────────────────────\n\nexport const dietChartQuerySchema = z.object({\n centerId: objectIdSchema.optional(),\n q: optionalTrimmedString(120),\n});\nexport type DietChartQuery = z.infer<typeof dietChartQuerySchema>;\n\n// ─── Assignment ──────────────────────────────────────────────────────────────\n\n/**\n * Assign a chart to a member, or clear it with `null`.\n *\n * Nullable rather than a separate DELETE endpoint so \"change this member's\n * chart\" and \"take it away\" are one operation with one permission check, which\n * is how `assignedWorkoutPlanId` already behaves.\n */\nexport const assignDietChartBodySchema = z.object({\n dietChartId: objectIdSchema.nullable(),\n});\nexport type AssignDietChartBody = z.infer<typeof assignDietChartBodySchema>;\n\n/**\n * What the member's own Diet tab reads.\n *\n * `chart` is null for a member whose gym has not written them one, which is the\n * ordinary state for most members most of the time and therefore not an error.\n * `assignedAt` is what lets the screen say \"since 3 March\" rather than implying\n * the plan is new.\n */\nexport const myDietChartSchema = z.object({\n chart: dietChartSchema.nullable(),\n assignedAt: isoDateTimeSchema.nullable(),\n});\nexport type MyDietChart = z.infer<typeof myDietChartSchema>;\n"]}
@@ -60,8 +60,8 @@ export declare const createEquipmentBodySchema: z.ZodObject<{
60
60
  recovery: "recovery";
61
61
  }>;
62
62
  name: z.ZodString;
63
- quantity: z.ZodDefault<z.ZodNumber>;
64
63
  notes: z.ZodPreprocess<z.ZodOptional<z.ZodString>>;
64
+ quantity: z.ZodDefault<z.ZodNumber>;
65
65
  }, z.core.$strip>;
66
66
  export type CreateEquipmentBody = z.infer<typeof createEquipmentBodySchema>;
67
67
  export declare const updateEquipmentBodySchema: z.ZodObject<{
@@ -74,7 +74,7 @@ export declare const updateEquipmentBodySchema: z.ZodObject<{
74
74
  training: "training";
75
75
  recovery: "recovery";
76
76
  }>>;
77
- quantity: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
77
+ quantity: z.ZodOptional<z.ZodNumber>;
78
78
  notes: z.ZodOptional<z.ZodPreprocess<z.ZodOptional<z.ZodString>>>;
79
79
  }, z.core.$strip>;
80
80
  export type UpdateEquipmentBody = z.infer<typeof updateEquipmentBodySchema>;
@@ -1 +1 @@
1
- {"version":3,"file":"equipment.d.ts","sourceRoot":"","sources":["../src/equipment.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,oBAAoB,6EAOvB,CAAC;AACX,eAAO,MAAM,uBAAuB;;;;;;;EAA+B,CAAC;AACpE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAIxE,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;iBAS1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,4DAA4D;AAC5D,eAAO,MAAM,8BAA8B;;;;;;;;;;;iBAIzC,CAAC;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAItF,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;iBAMpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,eAAO,MAAM,yBAAyB;;;;;;;;;;;;iBAE1B,CAAC;AACb,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAI5E;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CA6B1E,CAAC;AAIF;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;iBAIvC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;iBAG3C,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gCAAgC,CAAC,CAAC;AAI1F,eAAO,MAAM,oBAAoB;;;;;;;;;;iBAG/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
1
+ {"version":3,"file":"equipment.d.ts","sourceRoot":"","sources":["../src/equipment.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,oBAAoB,6EAOvB,CAAC;AACX,eAAO,MAAM,uBAAuB;;;;;;;EAA+B,CAAC;AACpE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAIxE,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;iBAS1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,4DAA4D;AAC5D,eAAO,MAAM,8BAA8B;;;;;;;;;;;iBAIzC,CAAC;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAsBtF,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;iBAEpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,eAAO,MAAM,yBAAyB;;;;;;;;;;;;iBAA6D,CAAC;AACpG,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAI5E;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CA6B1E,CAAC;AAIF;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;iBAIvC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;iBAG3C,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gCAAgC,CAAC,CAAC;AAI1F,eAAO,MAAM,oBAAoB;;;;;;;;;;iBAG/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
package/dist/equipment.js CHANGED
@@ -36,16 +36,27 @@ export const equipmentCategorySummarySchema = z.object({
36
36
  count: z.number().int().nonnegative(),
37
37
  });
38
38
  // ─── Create / update body ────────────────────────────────────────────────────
39
- export const createEquipmentBodySchema = z.object({
39
+ /**
40
+ * ⚠️ NO `.default()` IN THIS BASE — the update schema is derived from it.
41
+ *
42
+ * `.partial()` makes a key optional; it does NOT switch off that key's
43
+ * `.default()`. A base carrying a default therefore parses a partial update
44
+ * body into an object containing that key, and the update services `$set` the
45
+ * parsed body straight through — so editing one field silently erases another
46
+ * the caller never mentioned. Defaults live on the CREATE export below.
47
+ * Pinned by `update-body-clobber.test.ts`.
48
+ */
49
+ const equipmentBodyBaseSchema = z.object({
40
50
  centerId: objectIdSchema,
41
51
  category: equipmentCategorySchema,
42
52
  name: z.string().trim().min(1, 'Name is required').max(120),
43
- quantity: z.number().int().min(1).max(100000).default(1),
53
+ quantity: z.number().int().min(1).max(100000),
44
54
  notes: optionalTrimmedString(500),
45
55
  });
46
- export const updateEquipmentBodySchema = createEquipmentBodySchema
47
- .omit({ centerId: true })
48
- .partial();
56
+ export const createEquipmentBodySchema = equipmentBodyBaseSchema.extend({
57
+ quantity: z.number().int().min(1).max(100000).default(1),
58
+ });
59
+ export const updateEquipmentBodySchema = equipmentBodyBaseSchema.omit({ centerId: true }).partial();
49
60
  // ─── Catalog ─────────────────────────────────────────────────────────────────
50
61
  /**
51
62
  * The equipment an owner can add by tapping, per category.
@@ -1 +1 @@
1
- {"version":3,"file":"equipment.js","sourceRoot":"","sources":["../src/equipment.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Equipment\n * ==========================\n * A center's equipment inventory. The owner UI groups items by category and\n * shows per-category counts; the entity is a per-item record scoped to a center.\n *\n * @module gymmonk-schema/equipment\n */\n\nimport { z } from 'zod';\nimport { isoDateTimeSchema, objectIdSchema, optionalTrimmedString } from './common.js';\n\n// ─── Category ────────────────────────────────────────────────────────────────\n\nexport const EQUIPMENT_CATEGORIES = [\n 'cardio',\n 'strength',\n 'weight',\n 'training',\n 'recovery',\n 'custom',\n] as const;\nexport const equipmentCategorySchema = z.enum(EQUIPMENT_CATEGORIES);\nexport type EquipmentCategory = z.infer<typeof equipmentCategorySchema>;\n\n// ─── Entity ──────────────────────────────────────────────────────────────────\n\nexport const equipmentSchema = z.object({\n id: objectIdSchema,\n centerId: objectIdSchema,\n category: equipmentCategorySchema,\n name: z.string(),\n quantity: z.number().int().nonnegative(),\n notes: z.string().nullable(),\n createdAt: isoDateTimeSchema,\n updatedAt: isoDateTimeSchema,\n});\nexport type Equipment = z.infer<typeof equipmentSchema>;\n\n/** Per-category rollup the owner equipment list renders. */\nexport const equipmentCategorySummarySchema = z.object({\n category: equipmentCategorySchema,\n label: z.string(),\n count: z.number().int().nonnegative(),\n});\nexport type EquipmentCategorySummary = z.infer<typeof equipmentCategorySummarySchema>;\n\n// ─── Create / update body ────────────────────────────────────────────────────\n\nexport const createEquipmentBodySchema = z.object({\n centerId: objectIdSchema,\n category: equipmentCategorySchema,\n name: z.string().trim().min(1, 'Name is required').max(120),\n quantity: z.number().int().min(1).max(100000).default(1),\n notes: optionalTrimmedString(500),\n});\nexport type CreateEquipmentBody = z.infer<typeof createEquipmentBodySchema>;\n\nexport const updateEquipmentBodySchema = createEquipmentBodySchema\n .omit({ centerId: true })\n .partial();\nexport type UpdateEquipmentBody = z.infer<typeof updateEquipmentBodySchema>;\n\n// ─── Catalog ─────────────────────────────────────────────────────────────────\n\n/**\n * The equipment an owner can add by tapping, per category.\n *\n * A SHARED vocabulary, not a UI list: an owner who types \"Treadmill\" at one\n * gym and \"treadmill\" at another produces two rows that never roll up, and the\n * usage analytics that key off equipment name would split in half. The picker\n * offers these; anything genuinely unusual is still addable as a free-text\n * `custom` item, which is what that category is for.\n */\nexport const EQUIPMENT_CATALOG: Record<EquipmentCategory, readonly string[]> = {\n cardio: [\n 'Treadmill',\n 'Elliptical',\n 'Stationary Bike',\n 'Rowing Machine',\n 'Stair Climber',\n 'Spin Bike',\n ],\n strength: [\n 'Chest Press',\n 'Lat Pulldown',\n 'Leg Press',\n 'Shoulder Press',\n 'Cable Crossover',\n 'Smith Machine',\n 'Seated Row',\n ],\n weight: ['Dumbbells', 'Barbells', 'Weight Plates', 'Kettlebells', 'Bench Press', 'Squat Rack'],\n training: [\n 'Battle Ropes',\n 'Plyo Box',\n 'Medicine Ball',\n 'TRX Straps',\n 'Agility Ladder',\n 'Punching Bag',\n ],\n recovery: ['Foam Roller', 'Massage Gun', 'Stretching Mat', 'Ice Bath', 'Sauna', 'Steam Room'],\n custom: [],\n};\n\n// ─── Bulk quantity set (the \"Add New Equipment\" screen) ──────────────────────\n\n/**\n * Set a center's inventory for a set of items in one call.\n *\n * The add screen is a grid of steppers the owner nudges up and down, and it\n * saves once at the end. Sending each nudge as its own create/update/delete\n * would make a half-saved screen the normal outcome of a flaky connection, so\n * the whole intent goes over as one idempotent statement: after this call,\n * these items are at these quantities. A quantity of ZERO removes the item,\n * which is how the stepper's \"back to none\" is expressed.\n */\nexport const equipmentQuantityEntrySchema = z.object({\n category: equipmentCategorySchema,\n name: z.string().trim().min(1).max(120),\n quantity: z.number().int().min(0).max(100000),\n});\nexport type EquipmentQuantityEntry = z.infer<typeof equipmentQuantityEntrySchema>;\n\nexport const setEquipmentQuantitiesBodySchema = z.object({\n centerId: objectIdSchema,\n items: z.array(equipmentQuantityEntrySchema).min(1).max(200),\n});\nexport type SetEquipmentQuantitiesBody = z.infer<typeof setEquipmentQuantitiesBodySchema>;\n\n// ─── List query ──────────────────────────────────────────────────────────────\n\nexport const equipmentQuerySchema = z.object({\n centerId: objectIdSchema,\n category: equipmentCategorySchema.optional(),\n});\nexport type EquipmentQuery = z.infer<typeof equipmentQuerySchema>;\n"]}
1
+ {"version":3,"file":"equipment.js","sourceRoot":"","sources":["../src/equipment.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Equipment\n * ==========================\n * A center's equipment inventory. The owner UI groups items by category and\n * shows per-category counts; the entity is a per-item record scoped to a center.\n *\n * @module gymmonk-schema/equipment\n */\n\nimport { z } from 'zod';\nimport { isoDateTimeSchema, objectIdSchema, optionalTrimmedString } from './common.js';\n\n// ─── Category ────────────────────────────────────────────────────────────────\n\nexport const EQUIPMENT_CATEGORIES = [\n 'cardio',\n 'strength',\n 'weight',\n 'training',\n 'recovery',\n 'custom',\n] as const;\nexport const equipmentCategorySchema = z.enum(EQUIPMENT_CATEGORIES);\nexport type EquipmentCategory = z.infer<typeof equipmentCategorySchema>;\n\n// ─── Entity ──────────────────────────────────────────────────────────────────\n\nexport const equipmentSchema = z.object({\n id: objectIdSchema,\n centerId: objectIdSchema,\n category: equipmentCategorySchema,\n name: z.string(),\n quantity: z.number().int().nonnegative(),\n notes: z.string().nullable(),\n createdAt: isoDateTimeSchema,\n updatedAt: isoDateTimeSchema,\n});\nexport type Equipment = z.infer<typeof equipmentSchema>;\n\n/** Per-category rollup the owner equipment list renders. */\nexport const equipmentCategorySummarySchema = z.object({\n category: equipmentCategorySchema,\n label: z.string(),\n count: z.number().int().nonnegative(),\n});\nexport type EquipmentCategorySummary = z.infer<typeof equipmentCategorySummarySchema>;\n\n// ─── Create / update body ────────────────────────────────────────────────────\n\n/**\n * ⚠️ NO `.default()` IN THIS BASE — the update schema is derived from it.\n *\n * `.partial()` makes a key optional; it does NOT switch off that key's\n * `.default()`. A base carrying a default therefore parses a partial update\n * body into an object containing that key, and the update services `$set` the\n * parsed body straight through — so editing one field silently erases another\n * the caller never mentioned. Defaults live on the CREATE export below.\n * Pinned by `update-body-clobber.test.ts`.\n */\nconst equipmentBodyBaseSchema = z.object({\n centerId: objectIdSchema,\n category: equipmentCategorySchema,\n name: z.string().trim().min(1, 'Name is required').max(120),\n quantity: z.number().int().min(1).max(100000),\n notes: optionalTrimmedString(500),\n});\n\nexport const createEquipmentBodySchema = equipmentBodyBaseSchema.extend({\n quantity: z.number().int().min(1).max(100000).default(1),\n});\nexport type CreateEquipmentBody = z.infer<typeof createEquipmentBodySchema>;\n\nexport const updateEquipmentBodySchema = equipmentBodyBaseSchema.omit({ centerId: true }).partial();\nexport type UpdateEquipmentBody = z.infer<typeof updateEquipmentBodySchema>;\n\n// ─── Catalog ─────────────────────────────────────────────────────────────────\n\n/**\n * The equipment an owner can add by tapping, per category.\n *\n * A SHARED vocabulary, not a UI list: an owner who types \"Treadmill\" at one\n * gym and \"treadmill\" at another produces two rows that never roll up, and the\n * usage analytics that key off equipment name would split in half. The picker\n * offers these; anything genuinely unusual is still addable as a free-text\n * `custom` item, which is what that category is for.\n */\nexport const EQUIPMENT_CATALOG: Record<EquipmentCategory, readonly string[]> = {\n cardio: [\n 'Treadmill',\n 'Elliptical',\n 'Stationary Bike',\n 'Rowing Machine',\n 'Stair Climber',\n 'Spin Bike',\n ],\n strength: [\n 'Chest Press',\n 'Lat Pulldown',\n 'Leg Press',\n 'Shoulder Press',\n 'Cable Crossover',\n 'Smith Machine',\n 'Seated Row',\n ],\n weight: ['Dumbbells', 'Barbells', 'Weight Plates', 'Kettlebells', 'Bench Press', 'Squat Rack'],\n training: [\n 'Battle Ropes',\n 'Plyo Box',\n 'Medicine Ball',\n 'TRX Straps',\n 'Agility Ladder',\n 'Punching Bag',\n ],\n recovery: ['Foam Roller', 'Massage Gun', 'Stretching Mat', 'Ice Bath', 'Sauna', 'Steam Room'],\n custom: [],\n};\n\n// ─── Bulk quantity set (the \"Add New Equipment\" screen) ──────────────────────\n\n/**\n * Set a center's inventory for a set of items in one call.\n *\n * The add screen is a grid of steppers the owner nudges up and down, and it\n * saves once at the end. Sending each nudge as its own create/update/delete\n * would make a half-saved screen the normal outcome of a flaky connection, so\n * the whole intent goes over as one idempotent statement: after this call,\n * these items are at these quantities. A quantity of ZERO removes the item,\n * which is how the stepper's \"back to none\" is expressed.\n */\nexport const equipmentQuantityEntrySchema = z.object({\n category: equipmentCategorySchema,\n name: z.string().trim().min(1).max(120),\n quantity: z.number().int().min(0).max(100000),\n});\nexport type EquipmentQuantityEntry = z.infer<typeof equipmentQuantityEntrySchema>;\n\nexport const setEquipmentQuantitiesBodySchema = z.object({\n centerId: objectIdSchema,\n items: z.array(equipmentQuantityEntrySchema).min(1).max(200),\n});\nexport type SetEquipmentQuantitiesBody = z.infer<typeof setEquipmentQuantitiesBodySchema>;\n\n// ─── List query ──────────────────────────────────────────────────────────────\n\nexport const equipmentQuerySchema = z.object({\n centerId: objectIdSchema,\n category: equipmentCategorySchema.optional(),\n});\nexport type EquipmentQuery = z.infer<typeof equipmentQuerySchema>;\n"]}
@@ -366,6 +366,11 @@ export type CreateExerciseBody = z.infer<typeof createExerciseBodySchema>;
366
366
  export declare const updateExerciseBodySchema: z.ZodObject<{
367
367
  name: z.ZodOptional<z.ZodString>;
368
368
  description: z.ZodOptional<z.ZodPreprocess<z.ZodOptional<z.ZodString>>>;
369
+ difficulty: z.ZodOptional<z.ZodEnum<{
370
+ beginner: "beginner";
371
+ intermediate: "intermediate";
372
+ advanced: "advanced";
373
+ }>>;
369
374
  category: z.ZodOptional<z.ZodEnum<{
370
375
  cardio: "cardio";
371
376
  "upper-body": "upper-body";
@@ -373,12 +378,6 @@ export declare const updateExerciseBodySchema: z.ZodObject<{
373
378
  core: "core";
374
379
  "full-body": "full-body";
375
380
  }>>;
376
- thumbnailUrl: z.ZodOptional<z.ZodPreprocess<z.ZodOptional<z.ZodString>>>;
377
- difficulty: z.ZodOptional<z.ZodEnum<{
378
- beginner: "beginner";
379
- intermediate: "intermediate";
380
- advanced: "advanced";
381
- }>>;
382
381
  workoutType: z.ZodOptional<z.ZodOptional<z.ZodEnum<{
383
382
  functional: "functional";
384
383
  cardio: "cardio";
@@ -400,7 +399,7 @@ export declare const updateExerciseBodySchema: z.ZodObject<{
400
399
  bench: "bench";
401
400
  }>>>>;
402
401
  durationMinutes: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
403
- targetMuscles: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodEnum<{
402
+ targetMuscles: z.ZodOptional<z.ZodArray<z.ZodEnum<{
404
403
  chest: "chest";
405
404
  cardio: "cardio";
406
405
  core: "core";
@@ -418,7 +417,8 @@ export declare const updateExerciseBodySchema: z.ZodObject<{
418
417
  legs: "legs";
419
418
  obliques: "obliques";
420
419
  "lower-back": "lower-back";
421
- }>>>>;
420
+ }>>>;
421
+ thumbnailUrl: z.ZodOptional<z.ZodPreprocess<z.ZodOptional<z.ZodString>>>;
422
422
  videos: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodObject<{
423
423
  lang: z.ZodEnum<{
424
424
  en: "en";
@@ -429,13 +429,13 @@ export declare const updateExerciseBodySchema: z.ZodObject<{
429
429
  title: z.ZodDefault<z.ZodString>;
430
430
  }, z.core.$strip>>>>;
431
431
  posterUrl: z.ZodOptional<z.ZodPreprocess<z.ZodOptional<z.ZodString>>>;
432
- sets: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodObject<{
432
+ sets: z.ZodOptional<z.ZodArray<z.ZodObject<{
433
433
  order: z.ZodNumber;
434
434
  reps: z.ZodString;
435
435
  weight: z.ZodString;
436
436
  tempo: z.ZodString;
437
- }, z.core.$strip>>>>;
438
- formTips: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
437
+ }, z.core.$strip>>>;
438
+ formTips: z.ZodOptional<z.ZodArray<z.ZodString>>;
439
439
  }, z.core.$strip>;
440
440
  export type UpdateExerciseBody = z.infer<typeof updateExerciseBodySchema>;
441
441
  export declare const exerciseLibraryQuerySchema: z.ZodObject<{
@@ -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
@@ -217,7 +217,17 @@ export const exerciseSchema = exerciseSummarySchema.extend({
217
217
  updatedAt: isoDateTimeSchema,
218
218
  });
219
219
  // ─── Create / update body ────────────────────────────────────────────────────
220
- export const createExerciseBodySchema = z.object({
220
+ /**
221
+ * ⚠️ NO `.default()` IN THIS BASE — the update schema is derived from it.
222
+ *
223
+ * `.partial()` makes a key optional; it does NOT switch off that key's
224
+ * `.default()`. A base carrying a default therefore parses a partial update
225
+ * body into an object containing that key, and the update services `$set` the
226
+ * parsed body straight through — so editing one field silently erases another
227
+ * the caller never mentioned. Defaults live on the CREATE export below.
228
+ * Pinned by `update-body-clobber.test.ts`.
229
+ */
230
+ const exerciseBodyBaseSchema = z.object({
221
231
  /**
222
232
  * Which library this entry joins. `null` is the PLATFORM library — content
223
233
  * the product publishes to every gym — and only platform staff may send it.
@@ -263,11 +273,16 @@ export const createExerciseBodySchema = z.object({
263
273
  videos: exerciseVideosSchema.optional(),
264
274
  thumbnailUrl: optionalTrimmedString(2000),
265
275
  posterUrl: optionalTrimmedString(2000),
276
+ targetMuscles: z.array(muscleGroupSchema).max(16),
277
+ sets: z.array(exerciseSetSchema).max(20),
278
+ formTips: z.array(z.string().trim().max(400)).max(20),
279
+ });
280
+ export const createExerciseBodySchema = exerciseBodyBaseSchema.extend({
266
281
  targetMuscles: z.array(muscleGroupSchema).max(16).default([]),
267
282
  sets: z.array(exerciseSetSchema).max(20).default([]),
268
283
  formTips: z.array(z.string().trim().max(400)).max(20).default([]),
269
284
  });
270
- export const updateExerciseBodySchema = createExerciseBodySchema.omit({ centerId: true }).partial();
285
+ export const updateExerciseBodySchema = exerciseBodyBaseSchema.omit({ centerId: true }).partial();
271
286
  // ─── Library query ───────────────────────────────────────────────────────────
272
287
  export const exerciseLibraryQuerySchema = z.object({
273
288
  centerId: objectIdSchema.optional(),