gymmonk-schema 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # gymmonk-schema
2
+
3
+ Shared **Zod** schemas, enums and domain types for **GymMonk** (fitness SaaS).
4
+ Single source of truth (SSOT) consumed by both `gymmonk-backend` and
5
+ `gymmonk-web-client`, so validation, wire types and enums never drift between
6
+ the API and the app.
7
+
8
+ ## What's inside
9
+
10
+ - **Common** (`common.ts`) — reusable primitives: `objectIdSchema`,
11
+ `paginationSchema`, `optionalTrimmedString`, `emptyStringToUndefined`.
12
+ - Domain modules (workouts, training library, membership, check-in, …) are
13
+ added here as they land, each re-exported through `src/index.ts`.
14
+
15
+ ## Build & publish (npm-first)
16
+
17
+ ```bash
18
+ npm install
19
+ npm run build # tsc -> dist/
20
+ npm publish --access public
21
+ ```
22
+
23
+ `prepublishOnly` runs `build` + `check` (type-check + biome) as the publish gate.
24
+ `--access public` is a CLI flag, not a manifest field.
25
+
26
+ ## Consume
27
+
28
+ In `gymmonk-backend` and `gymmonk-web-client`:
29
+
30
+ ```bash
31
+ npm install gymmonk-schema@<version>
32
+ # then delete any nested Zod dup so a single Zod instance is used:
33
+ rm -rf node_modules/gymmonk-schema/node_modules
34
+ ```
35
+
36
+ Bump the caret pin in each consumer's `package.json` on every publish (a `0.x`
37
+ caret locks to the same minor, so `npm install` won't auto-pick a new minor).
38
+
39
+ ## Convention
40
+
41
+ Mirrors the jansathi schema family (`dating-schema`, `near-me-schema`) exactly:
42
+ pure ESM, `tsc`-only build, Zod is a **peer** dependency (`^4.4.0`), NodeNext
43
+ `.js` import specifiers, const-object enums fed to `z.enum(...)`.
@@ -1 +1 @@
1
- {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../src/chat.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../src/chat.ts"],"names":[],"mappings":"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"}
package/dist/chat.js CHANGED
@@ -135,6 +135,9 @@ export const chatConversationSchema = z.object({
135
135
  * History still reads: freezing stops the next message, and deleting what was
136
136
  * already said is a different and much larger decision. The client uses this
137
137
  * to disable its composer rather than letting a send fail at the server.
138
+ *
139
+ * RECOVERED from the published 0.17.0 `.d.ts`, not from its sourcemap — that
140
+ * map's `sourcesContent` predates this field and dropped it silently.
138
141
  */
139
142
  frozen: z.boolean(),
140
143
  });
package/dist/chat.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"chat.js","sourceRoot":"","sources":["../src/chat.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Chat\n * =====================\n * THE wire contract between `gymmonk-web-client` and `gymmonk-chat-server`, and\n * the only place either is allowed to name a socket event.\n *\n * It lives here rather than in the chat server because three codebases need it\n * and none of them owns the other two: the client emits, the chat server\n * handles, and gymmonk-backend mints the tokens that let the two meet. An event\n * name that differs by one character between any pair of them produces a\n * message nobody receives and no error anywhere, which is exactly the class of\n * bug a shared package exists to make impossible.\n *\n * @module gymmonk-schema/chat\n */\n\nimport { z } from 'zod';\nimport { isoDateTimeSchema, objectIdSchema } from './common.js';\n\n// ─── Socket events ───────────────────────────────────────────────────────────\n\n/**\n * Every socket event name.\n *\n * String literals rather than an enum, so both sides put the identical bytes on\n * the wire without either importing the other's runtime.\n */\nexport const CHAT_SOCKET_EVENTS = {\n /** Server → client, on connect. The socket is up and awaiting auth. */\n HELLO: 'hello',\n /** Client → server. `{ token }`. Must arrive inside the auth window. */\n AUTH: 'auth',\n /** Server → client. `{ userId }` on success. */\n AUTH_OK: 'auth:ok',\n /** Server → client. `{ code, message }` for any failure, auth or otherwise. */\n ERROR: 'error',\n\n /** Client → server. Open a conversation and start receiving it live. */\n CONVERSATION_JOIN: 'conversation:join',\n /** Client → server. Leave it (navigated away). */\n CONVERSATION_LEAVE: 'conversation:leave',\n\n /** Client → server. `{ conversationId, clientMessageId, text }`. */\n MESSAGE_SEND: 'message:send',\n /** Server → everyone in the room, and the recipients' own rooms. */\n MESSAGE_NEW: 'message:new',\n /** Server → sender only. Their send, acknowledged and stored. */\n MESSAGE_SENT: 'message:sent',\n /** Client → server. Mark everything up to now as read. */\n MESSAGE_READ: 'message:read',\n /** Server → the room. `{ conversationId, userId, readAt }`. */\n MESSAGE_READ_BY: 'message:read-by',\n\n /** Client → server. `{ conversationId, typing }`. */\n TYPING: 'typing',\n /** Server → the other participants. `{ conversationId, userId, typing }`. */\n TYPING_UPDATE: 'typing:update',\n\n /** Server → the room. Who currently has this conversation open. */\n PRESENCE_UPDATE: 'presence:update',\n} as const;\n\nexport type ChatSocketEvent = (typeof CHAT_SOCKET_EVENTS)[keyof typeof CHAT_SOCKET_EVENTS];\n\n// ─── Error codes ─────────────────────────────────────────────────────────────\n\n/** Stable codes on the `error` event. The client branches on these; the message is for logs. */\nexport const CHAT_ERROR = {\n AUTH_TIMEOUT: 'AUTH_TIMEOUT',\n AUTH_FAILED: 'AUTH_FAILED',\n NOT_AUTHENTICATED: 'NOT_AUTHENTICATED',\n NOT_A_PARTICIPANT: 'NOT_A_PARTICIPANT',\n CONVERSATION_NOT_FOUND: 'CONVERSATION_NOT_FOUND',\n INVALID_PAYLOAD: 'INVALID_PAYLOAD',\n RATE_LIMITED: 'RATE_LIMITED',\n SERVER_ERROR: 'SERVER_ERROR',\n} as const;\n\nexport type ChatErrorCode = (typeof CHAT_ERROR)[keyof typeof CHAT_ERROR];\n\n// ─── Limits ──────────────────────────────────────────────────────────────────\n\n/** Longest message body accepted. Matches the column cap in the model. */\nexport const MAX_MESSAGE_LENGTH = 4000;\n\n/** Messages per page of history. */\nexport const MESSAGE_PAGE_SIZE = 50;\n\n/**\n * Messages one socket may send per minute.\n *\n * Not an anti-abuse boundary on its own: a member can only ever write to a\n * conversation their own backend authorised. This is what stops a broken client\n * looping a send from filling a collection.\n */\nexport const MESSAGE_RATE_PER_MINUTE = 60;\n\n/** How long an unauthenticated socket may hold a connection, in ms. */\nexport const CHAT_AUTH_TIMEOUT_MS = 10_000;\n\n/**\n * How long a typing indicator survives without a refresh, in ms.\n *\n * Someone who closes the app mid-sentence never sends `typing: false`, so the\n * indicator has to expire on its own or it stays on screen forever. The sender\n * re-emits well inside this window while they are still typing.\n */\nexport const TYPING_TIMEOUT_MS = 4_000;\n\n/** How often the sender re-emits `typing: true` while typing, in ms. */\nexport const TYPING_THROTTLE_MS = 2_000;\n\n// ─── Wire shapes ─────────────────────────────────────────────────────────────\n\n/** A participant, denormalised onto the conversation so a list renders without joins. */\nexport const chatParticipantSchema = z.object({\n userId: objectIdSchema,\n displayName: z.string(),\n avatarUrl: z.string().nullable(),\n});\nexport type ChatParticipant = z.infer<typeof chatParticipantSchema>;\n\n/** A message as every client sees it. */\nexport const chatMessageSchema = z.object({\n id: objectIdSchema,\n conversationId: objectIdSchema,\n senderId: objectIdSchema,\n text: z.string(),\n /** Echoed back so the sender can reconcile its optimistic row. */\n clientMessageId: z.string(),\n sentAt: isoDateTimeSchema,\n readAt: isoDateTimeSchema.nullable(),\n /** Deleted for everyone. `text` is then empty. */\n deleted: z.boolean(),\n});\nexport type ChatMessage = z.infer<typeof chatMessageSchema>;\n\n/** The one-line preview a conversation row shows. */\nexport const chatLastMessageSchema = z.object({\n text: z.string(),\n senderId: objectIdSchema,\n sentAt: isoDateTimeSchema,\n});\nexport type ChatLastMessage = z.infer<typeof chatLastMessageSchema>;\n\n/**\n * A conversation as the list screen sees it.\n *\n * `unreadCount` is the CALLER's, not a global figure: the same row means\n * different things to the two people in it.\n */\nexport const chatConversationSchema = z.object({\n id: objectIdSchema,\n participants: z.array(chatParticipantSchema),\n lastMessage: chatLastMessageSchema.nullable(),\n unreadCount: z.number().int().nonnegative(),\n createdAt: isoDateTimeSchema,\n /** Last activity. The inbox sorts on this, newest first. */\n updatedAt: isoDateTimeSchema,\n});\nexport type ChatConversation = z.infer<typeof chatConversationSchema>;\n\n// ─── Event payloads ──────────────────────────────────────────────────────────\n\nexport interface ChatAuthPayload {\n token: string;\n}\n\nexport interface ChatConversationRoomPayload {\n conversationId: string;\n}\n\nexport interface ChatMessageSendPayload {\n conversationId: string;\n /** Client-generated idempotency key. A retry with the same value is not a second message. */\n clientMessageId: string;\n text: string;\n}\n\nexport interface ChatMessageReadPayload {\n conversationId: string;\n}\n\nexport interface ChatTypingPayload {\n conversationId: string;\n typing: boolean;\n}\n\n// ─── Typed event maps ────────────────────────────────────────────────────────\n// Socket.io takes these as generics on both ends, so an event emitted with the\n// wrong payload is a compile error rather than a message nobody receives.\n\nexport interface ChatServerToClientEvents {\n [CHAT_SOCKET_EVENTS.HELLO]: (payload: { message: string }) => void;\n [CHAT_SOCKET_EVENTS.AUTH_OK]: (payload: { userId: string }) => void;\n [CHAT_SOCKET_EVENTS.ERROR]: (payload: { code: string; message: string }) => void;\n [CHAT_SOCKET_EVENTS.MESSAGE_NEW]: (message: ChatMessage) => void;\n [CHAT_SOCKET_EVENTS.MESSAGE_SENT]: (payload: {\n clientMessageId: string;\n message: ChatMessage;\n }) => void;\n [CHAT_SOCKET_EVENTS.MESSAGE_READ_BY]: (payload: {\n conversationId: string;\n userId: string;\n readAt: string;\n }) => void;\n [CHAT_SOCKET_EVENTS.TYPING_UPDATE]: (payload: {\n conversationId: string;\n userId: string;\n typing: boolean;\n }) => void;\n [CHAT_SOCKET_EVENTS.PRESENCE_UPDATE]: (payload: {\n conversationId: string;\n userIds: string[];\n }) => void;\n}\n\nexport interface ChatClientToServerEvents {\n [CHAT_SOCKET_EVENTS.AUTH]: (payload: ChatAuthPayload) => void;\n [CHAT_SOCKET_EVENTS.CONVERSATION_JOIN]: (payload: ChatConversationRoomPayload) => void;\n [CHAT_SOCKET_EVENTS.CONVERSATION_LEAVE]: (payload: ChatConversationRoomPayload) => void;\n [CHAT_SOCKET_EVENTS.MESSAGE_SEND]: (payload: ChatMessageSendPayload) => void;\n [CHAT_SOCKET_EVENTS.MESSAGE_READ]: (payload: ChatMessageReadPayload) => void;\n [CHAT_SOCKET_EVENTS.TYPING]: (payload: ChatTypingPayload) => void;\n}\n\n// ─── Backend bridge ──────────────────────────────────────────────────────────\n\n/** `GET /api/v1/chat/config` — where chat lives, whether it is on, and who the caller is. */\nexport const chatConfigSchema = z.object({\n enabled: z.boolean(),\n url: z.string().nullable(),\n /**\n * The caller's id AS CHAT-SERVER WILL SEE IT.\n *\n * Sourced from the same place that mints the token rather than from the\n * client session, because the two must agree or \"is this message mine\" is\n * wrong for somebody.\n */\n userId: objectIdSchema,\n});\nexport type ChatConfig = z.infer<typeof chatConfigSchema>;\n\n/** `POST /api/v1/chat/auth-token` */\nexport const chatAuthTokenSchema = z.object({\n token: z.string(),\n expiresAt: isoDateTimeSchema,\n /** Seconds, so a client can schedule a refresh without parsing a date. */\n expiresIn: z.number().int().positive(),\n});\nexport type ChatAuthToken = z.infer<typeof chatAuthTokenSchema>;\n\n/** `POST /api/v1/chat/invitation` — permission to open a conversation with someone. */\nexport const chatInvitationBodySchema = z.object({\n userId: objectIdSchema,\n});\nexport type ChatInvitationBody = z.infer<typeof chatInvitationBodySchema>;\n\nexport const chatInvitationSchema = z.object({\n invitationToken: z.string(),\n expiresAt: isoDateTimeSchema,\n});\nexport type ChatInvitation = z.infer<typeof chatInvitationSchema>;\n\n/** `POST /api/v1/chat/notify` — chat-server telling the backend to notify people. */\nexport const chatNotifyBodySchema = z.object({\n recipientIds: z.array(objectIdSchema).min(1),\n conversationId: objectIdSchema,\n senderId: objectIdSchema,\n senderName: z.string(),\n text: z.string(),\n});\nexport type ChatNotifyBody = z.infer<typeof chatNotifyBodySchema>;\n\n/** JWT `aud` on every chat token. chat-server accepts exactly this. */\nexport const CHAT_TOKEN_AUDIENCE = 'gymmonk-chat-server';\n\n/** `purpose` claim marking an invitation. Auth tokens carry no purpose. */\nexport const CHAT_INVITATION_PURPOSE = 'invitation';\n"]}
1
+ {"version":3,"file":"chat.js","sourceRoot":"","sources":["../src/chat.ts"],"names":[],"mappings":"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","sourcesContent":["/**\r\n * gymmonk-schema — Chat\r\n * =====================\r\n * THE wire contract between `gymmonk-web-client` and `gymmonk-chat-server`, and\r\n * the only place either is allowed to name a socket event.\r\n *\r\n * It lives here rather than in the chat server because three codebases need it\r\n * and none of them owns the other two: the client emits, the chat server\r\n * handles, and gymmonk-backend mints the tokens that let the two meet. An event\r\n * name that differs by one character between any pair of them produces a\r\n * message nobody receives and no error anywhere, which is exactly the class of\r\n * bug a shared package exists to make impossible.\r\n *\r\n * @module gymmonk-schema/chat\r\n */\r\n\r\nimport { z } from 'zod';\r\nimport { isoDateTimeSchema, objectIdSchema } from './common.js';\r\n\r\n// ─── Socket events ───────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Every socket event name.\r\n *\r\n * String literals rather than an enum, so both sides put the identical bytes on\r\n * the wire without either importing the other's runtime.\r\n */\r\nexport const CHAT_SOCKET_EVENTS = {\r\n /** Server → client, on connect. The socket is up and awaiting auth. */\r\n HELLO: 'hello',\r\n /** Client → server. `{ token }`. Must arrive inside the auth window. */\r\n AUTH: 'auth',\r\n /** Server → client. `{ userId }` on success. */\r\n AUTH_OK: 'auth:ok',\r\n /** Server → client. `{ code, message }` for any failure, auth or otherwise. */\r\n ERROR: 'error',\r\n\r\n /** Client → server. Open a conversation and start receiving it live. */\r\n CONVERSATION_JOIN: 'conversation:join',\r\n /** Client → server. Leave it (navigated away). */\r\n CONVERSATION_LEAVE: 'conversation:leave',\r\n\r\n /** Client → server. `{ conversationId, clientMessageId, text }`. */\r\n MESSAGE_SEND: 'message:send',\r\n /** Server → everyone in the room, and the recipients' own rooms. */\r\n MESSAGE_NEW: 'message:new',\r\n /** Server → sender only. Their send, acknowledged and stored. */\r\n MESSAGE_SENT: 'message:sent',\r\n /** Client → server. Mark everything up to now as read. */\r\n MESSAGE_READ: 'message:read',\r\n /** Server → the room. `{ conversationId, userId, readAt }`. */\r\n MESSAGE_READ_BY: 'message:read-by',\r\n\r\n /** Client → server. `{ conversationId, typing }`. */\r\n TYPING: 'typing',\r\n /** Server → the other participants. `{ conversationId, userId, typing }`. */\r\n TYPING_UPDATE: 'typing:update',\r\n\r\n /** Server → the room. Who currently has this conversation open. */\r\n PRESENCE_UPDATE: 'presence:update',\r\n} as const;\r\n\r\nexport type ChatSocketEvent = (typeof CHAT_SOCKET_EVENTS)[keyof typeof CHAT_SOCKET_EVENTS];\r\n\r\n// ─── Error codes ─────────────────────────────────────────────────────────────\r\n\r\n/** Stable codes on the `error` event. The client branches on these; the message is for logs. */\r\nexport const CHAT_ERROR = {\r\n AUTH_TIMEOUT: 'AUTH_TIMEOUT',\r\n AUTH_FAILED: 'AUTH_FAILED',\r\n NOT_AUTHENTICATED: 'NOT_AUTHENTICATED',\r\n NOT_A_PARTICIPANT: 'NOT_A_PARTICIPANT',\r\n CONVERSATION_NOT_FOUND: 'CONVERSATION_NOT_FOUND',\r\n INVALID_PAYLOAD: 'INVALID_PAYLOAD',\r\n RATE_LIMITED: 'RATE_LIMITED',\r\n SERVER_ERROR: 'SERVER_ERROR',\r\n} as const;\r\n\r\nexport type ChatErrorCode = (typeof CHAT_ERROR)[keyof typeof CHAT_ERROR];\r\n\r\n// ─── Limits ──────────────────────────────────────────────────────────────────\r\n\r\n/** Longest message body accepted. Matches the column cap in the model. */\r\nexport const MAX_MESSAGE_LENGTH = 4000;\r\n\r\n/** Messages per page of history. */\r\nexport const MESSAGE_PAGE_SIZE = 50;\r\n\r\n/**\r\n * Messages one socket may send per minute.\r\n *\r\n * Not an anti-abuse boundary on its own: a member can only ever write to a\r\n * conversation their own backend authorised. This is what stops a broken client\r\n * looping a send from filling a collection.\r\n */\r\nexport const MESSAGE_RATE_PER_MINUTE = 60;\r\n\r\n/** How long an unauthenticated socket may hold a connection, in ms. */\r\nexport const CHAT_AUTH_TIMEOUT_MS = 10_000;\r\n\r\n/**\r\n * How long a typing indicator survives without a refresh, in ms.\r\n *\r\n * Someone who closes the app mid-sentence never sends `typing: false`, so the\r\n * indicator has to expire on its own or it stays on screen forever. The sender\r\n * re-emits well inside this window while they are still typing.\r\n */\r\nexport const TYPING_TIMEOUT_MS = 4_000;\r\n\r\n/** How often the sender re-emits `typing: true` while typing, in ms. */\r\nexport const TYPING_THROTTLE_MS = 2_000;\r\n\r\n// ─── Wire shapes ─────────────────────────────────────────────────────────────\r\n\r\n/** A participant, denormalised onto the conversation so a list renders without joins. */\r\nexport const chatParticipantSchema = z.object({\r\n userId: objectIdSchema,\r\n displayName: z.string(),\r\n avatarUrl: z.string().nullable(),\r\n});\r\nexport type ChatParticipant = z.infer<typeof chatParticipantSchema>;\r\n\r\n/** A message as every client sees it. */\r\nexport const chatMessageSchema = z.object({\r\n id: objectIdSchema,\r\n conversationId: objectIdSchema,\r\n senderId: objectIdSchema,\r\n text: z.string(),\r\n /** Echoed back so the sender can reconcile its optimistic row. */\r\n clientMessageId: z.string(),\r\n sentAt: isoDateTimeSchema,\r\n readAt: isoDateTimeSchema.nullable(),\r\n /** Deleted for everyone. `text` is then empty. */\r\n deleted: z.boolean(),\r\n});\r\nexport type ChatMessage = z.infer<typeof chatMessageSchema>;\r\n\r\n/** The one-line preview a conversation row shows. */\r\nexport const chatLastMessageSchema = z.object({\r\n text: z.string(),\r\n senderId: objectIdSchema,\r\n sentAt: isoDateTimeSchema,\r\n});\r\nexport type ChatLastMessage = z.infer<typeof chatLastMessageSchema>;\r\n\r\n/**\r\n * A conversation as the list screen sees it.\r\n *\r\n * `unreadCount` is the CALLER's, not a global figure: the same row means\r\n * different things to the two people in it.\r\n */\r\nexport const chatConversationSchema = z.object({\r\n id: objectIdSchema,\r\n participants: z.array(chatParticipantSchema),\r\n lastMessage: chatLastMessageSchema.nullable(),\r\n unreadCount: z.number().int().nonnegative(),\r\n createdAt: isoDateTimeSchema,\r\n /** Last activity. The inbox sorts on this, newest first. */\r\n updatedAt: isoDateTimeSchema,\r\n /**\r\n * Closed to new messages. True once one participant blocks the other.\r\n *\r\n * History still reads: freezing stops the next message, and deleting what was\r\n * already said is a different and much larger decision. The client uses this\r\n * to disable its composer rather than letting a send fail at the server.\r\n *\r\n * RECOVERED from the published 0.17.0 `.d.ts`, not from its sourcemap — that\r\n * map's `sourcesContent` predates this field and dropped it silently.\r\n */\r\n frozen: z.boolean(),\r\n});\r\nexport type ChatConversation = z.infer<typeof chatConversationSchema>;\r\n\r\n// ─── Event payloads ──────────────────────────────────────────────────────────\r\n\r\nexport interface ChatAuthPayload {\r\n token: string;\r\n}\r\n\r\nexport interface ChatConversationRoomPayload {\r\n conversationId: string;\r\n}\r\n\r\nexport interface ChatMessageSendPayload {\r\n conversationId: string;\r\n /** Client-generated idempotency key. A retry with the same value is not a second message. */\r\n clientMessageId: string;\r\n text: string;\r\n}\r\n\r\nexport interface ChatMessageReadPayload {\r\n conversationId: string;\r\n}\r\n\r\nexport interface ChatTypingPayload {\r\n conversationId: string;\r\n typing: boolean;\r\n}\r\n\r\n// ─── Typed event maps ────────────────────────────────────────────────────────\r\n// Socket.io takes these as generics on both ends, so an event emitted with the\r\n// wrong payload is a compile error rather than a message nobody receives.\r\n\r\nexport interface ChatServerToClientEvents {\r\n [CHAT_SOCKET_EVENTS.HELLO]: (payload: { message: string }) => void;\r\n [CHAT_SOCKET_EVENTS.AUTH_OK]: (payload: { userId: string }) => void;\r\n [CHAT_SOCKET_EVENTS.ERROR]: (payload: { code: string; message: string }) => void;\r\n [CHAT_SOCKET_EVENTS.MESSAGE_NEW]: (message: ChatMessage) => void;\r\n [CHAT_SOCKET_EVENTS.MESSAGE_SENT]: (payload: {\r\n clientMessageId: string;\r\n message: ChatMessage;\r\n }) => void;\r\n [CHAT_SOCKET_EVENTS.MESSAGE_READ_BY]: (payload: {\r\n conversationId: string;\r\n userId: string;\r\n readAt: string;\r\n }) => void;\r\n [CHAT_SOCKET_EVENTS.TYPING_UPDATE]: (payload: {\r\n conversationId: string;\r\n userId: string;\r\n typing: boolean;\r\n }) => void;\r\n [CHAT_SOCKET_EVENTS.PRESENCE_UPDATE]: (payload: {\r\n conversationId: string;\r\n userIds: string[];\r\n }) => void;\r\n}\r\n\r\nexport interface ChatClientToServerEvents {\r\n [CHAT_SOCKET_EVENTS.AUTH]: (payload: ChatAuthPayload) => void;\r\n [CHAT_SOCKET_EVENTS.CONVERSATION_JOIN]: (payload: ChatConversationRoomPayload) => void;\r\n [CHAT_SOCKET_EVENTS.CONVERSATION_LEAVE]: (payload: ChatConversationRoomPayload) => void;\r\n [CHAT_SOCKET_EVENTS.MESSAGE_SEND]: (payload: ChatMessageSendPayload) => void;\r\n [CHAT_SOCKET_EVENTS.MESSAGE_READ]: (payload: ChatMessageReadPayload) => void;\r\n [CHAT_SOCKET_EVENTS.TYPING]: (payload: ChatTypingPayload) => void;\r\n}\r\n\r\n// ─── Backend bridge ──────────────────────────────────────────────────────────\r\n\r\n/** `GET /api/v1/chat/config` — where chat lives, whether it is on, and who the caller is. */\r\nexport const chatConfigSchema = z.object({\r\n enabled: z.boolean(),\r\n url: z.string().nullable(),\r\n /**\r\n * The caller's id AS CHAT-SERVER WILL SEE IT.\r\n *\r\n * Sourced from the same place that mints the token rather than from the\r\n * client session, because the two must agree or \"is this message mine\" is\r\n * wrong for somebody.\r\n */\r\n userId: objectIdSchema,\r\n});\r\nexport type ChatConfig = z.infer<typeof chatConfigSchema>;\r\n\r\n/** `POST /api/v1/chat/auth-token` */\r\nexport const chatAuthTokenSchema = z.object({\r\n token: z.string(),\r\n expiresAt: isoDateTimeSchema,\r\n /** Seconds, so a client can schedule a refresh without parsing a date. */\r\n expiresIn: z.number().int().positive(),\r\n});\r\nexport type ChatAuthToken = z.infer<typeof chatAuthTokenSchema>;\r\n\r\n/** `POST /api/v1/chat/invitation` — permission to open a conversation with someone. */\r\nexport const chatInvitationBodySchema = z.object({\r\n userId: objectIdSchema,\r\n});\r\nexport type ChatInvitationBody = z.infer<typeof chatInvitationBodySchema>;\r\n\r\nexport const chatInvitationSchema = z.object({\r\n invitationToken: z.string(),\r\n expiresAt: isoDateTimeSchema,\r\n});\r\nexport type ChatInvitation = z.infer<typeof chatInvitationSchema>;\r\n\r\n/** `POST /api/v1/chat/notify` — chat-server telling the backend to notify people. */\r\nexport const chatNotifyBodySchema = z.object({\r\n recipientIds: z.array(objectIdSchema).min(1),\r\n conversationId: objectIdSchema,\r\n senderId: objectIdSchema,\r\n senderName: z.string(),\r\n text: z.string(),\r\n});\r\nexport type ChatNotifyBody = z.infer<typeof chatNotifyBodySchema>;\r\n\r\n/** JWT `aud` on every chat token. chat-server accepts exactly this. */\r\nexport const CHAT_TOKEN_AUDIENCE = 'gymmonk-chat-server';\r\n\r\n/** `purpose` claim marking an invitation. Auth tokens carry no purpose. */\r\nexport const CHAT_INVITATION_PURPOSE = 'invitation';\r\n"]}
package/dist/index.d.ts CHANGED
@@ -12,6 +12,7 @@ export * from './add-person-form.js';
12
12
  export * from './announcement.js';
13
13
  export * from './area.js';
14
14
  export * from './center.js';
15
+ export * from './chat.js';
15
16
  export * from './check-in.js';
16
17
  export * from './common.js';
17
18
  export * from './equipment.js';
@@ -36,10 +37,9 @@ export * from './platform.js';
36
37
  export * from './pricing.js';
37
38
  export * from './session.js';
38
39
  export * from './shared.js';
40
+ export * from './social.js';
39
41
  export * from './staff.js';
40
42
  export * from './subscription.js';
41
43
  export * from './user.js';
42
44
  export * from './workout-plan.js';
43
- //# sourceMappingURL=index.d.ts.map
44
- export * from './chat.js';
45
- export * from './social.js';
45
+ //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAElC,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAE5B,cAAc,eAAe,CAAC;AAE9B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAE/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAE9B,cAAc,WAAW,CAAC;AAE1B,cAAc,uBAAuB,CAAC;AACtC,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAE9B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAEhC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AAExC,cAAc,eAAe,CAAC;AAE9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAEhC,cAAc,sBAAsB,CAAC;AAErC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AAEnC,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAElC,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAE5B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAE9B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAE/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAE9B,cAAc,WAAW,CAAC;AAE1B,cAAc,uBAAuB,CAAC;AACtC,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAE9B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAEhC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AAExC,cAAc,eAAe,CAAC;AAE9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAEhC,cAAc,sBAAsB,CAAC;AAErC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AAEnC,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -15,6 +15,7 @@ export * from './announcement.js';
15
15
  export * from './area.js';
16
16
  export * from './center.js';
17
17
  // ─── Check-in QR (poster payload shared by owner, member and backend) ────────
18
+ export * from './chat.js';
18
19
  export * from './check-in.js';
19
20
  // ─── Foundation ──────────────────────────────────────────────────────────────
20
21
  export * from './common.js';
@@ -50,10 +51,9 @@ export * from './platform.js';
50
51
  export * from './pricing.js';
51
52
  export * from './session.js';
52
53
  export * from './shared.js';
54
+ export * from './social.js';
53
55
  export * from './staff.js';
54
56
  export * from './subscription.js';
55
57
  export * from './user.js';
56
58
  export * from './workout-plan.js';
57
- //# sourceMappingURL=index.js.map
58
- export * from './chat.js';
59
- export * from './social.js';
59
+ //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,gFAAgF;AAChF,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,6EAA6E;AAC7E,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,WAAW,CAAC;AAC1B,gFAAgF;AAChF,cAAc,uBAAuB,CAAC;AACtC,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,gFAAgF;AAChF,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,gFAAgF;AAChF,cAAc,sBAAsB,CAAC;AACrC,gFAAgF;AAChF,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC","sourcesContent":["/**\n * gymmonk-schema — Public API\n * ===========================\n * Shared Zod schemas, enums and domain types for GymMonk (fitness SaaS).\n * Single source of truth (SSOT) for data shapes, TypeScript types and\n * validation, consumed by both gymmonk-backend and gymmonk-web-client.\n *\n * Every domain module is re-exported through this barrel. Import names are\n * globally unique across modules, so `export *` composes without collisions.\n */\n\n// ─── Owner desk-entry FORM validation (client-side working shapes) ───────────\nexport * from './add-person-form.js';\nexport * from './announcement.js';\n// ─── Location master data (the address picker's lookup API) ───────────────\nexport * from './area.js';\nexport * from './center.js';\n// ─── Check-in QR (poster payload shared by owner, member and backend) ────────\nexport * from './check-in.js';\n// ─── Foundation ──────────────────────────────────────────────────────────────\nexport * from './common.js';\nexport * from './equipment.js';\n// ─── Training ────────────────────────────────────────────────────────────────\nexport * from './exercise.js';\nexport * from './feedback.js';\n// ─── Uploads ─────────────────────────────────────────────────────────────────\nexport * from './file.js';\n// ─── \"List your gym\" — a member registers the business they run ──────────────\nexport * from './gym-registration.js';\nexport * from './home.js';\nexport * from './insights.js';\n// ─── People ──────────────────────────────────────────────────────────────────\nexport * from './member.js';\nexport * from './member-profile.js';\nexport * from './membership.js';\n// ─── Membership ──────────────────────────────────────────────────────────────\nexport * from './membership-plan.js';\nexport * from './membership-request.js';\n// ─── API result messages + error codes ───────────────────────────────────────\nexport * from './messages.js';\n// ─── Engagement & analytics ──────────────────────────────────────────────────\nexport * from './notification.js';\nexport * from './onboarding.js';\n// ─── Onboarding wizard FORM validation (client-side working shapes) ──────────\nexport * from './onboarding-form.js';\n// ─── Gym structure & onboarding ──────────────────────────────────────────────\nexport * from './organisation.js';\nexport * from './owner-profile.js';\n// ─── Platform console (people + gyms directories, role keys) ─────────────────\nexport * from './platform.js';\nexport * from './pricing.js';\nexport * from './session.js';\nexport * from './shared.js';\nexport * from './staff.js';\nexport * from './subscription.js';\nexport * from './user.js';\nexport * from './workout-plan.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,gFAAgF;AAChF,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,6EAA6E;AAC7E,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,gFAAgF;AAChF,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,WAAW,CAAC;AAC1B,gFAAgF;AAChF,cAAc,uBAAuB,CAAC;AACtC,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,gFAAgF;AAChF,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,gFAAgF;AAChF,cAAc,sBAAsB,CAAC;AACrC,gFAAgF;AAChF,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC","sourcesContent":["/**\r\n * gymmonk-schema — Public API\r\n * ===========================\r\n * Shared Zod schemas, enums and domain types for GymMonk (fitness SaaS).\r\n * Single source of truth (SSOT) for data shapes, TypeScript types and\r\n * validation, consumed by both gymmonk-backend and gymmonk-web-client.\r\n *\r\n * Every domain module is re-exported through this barrel. Import names are\r\n * globally unique across modules, so `export *` composes without collisions.\r\n */\r\n\r\n// ─── Owner desk-entry FORM validation (client-side working shapes) ───────────\r\nexport * from './add-person-form.js';\r\nexport * from './announcement.js';\r\n// ─── Location master data (the address picker's lookup API) ───────────────\r\nexport * from './area.js';\r\nexport * from './center.js';\r\n// ─── Check-in QR (poster payload shared by owner, member and backend) ────────\r\nexport * from './chat.js';\r\nexport * from './check-in.js';\r\n// ─── Foundation ──────────────────────────────────────────────────────────────\r\nexport * from './common.js';\r\nexport * from './equipment.js';\r\n// ─── Training ────────────────────────────────────────────────────────────────\r\nexport * from './exercise.js';\r\nexport * from './feedback.js';\r\n// ─── Uploads ─────────────────────────────────────────────────────────────────\r\nexport * from './file.js';\r\n// ─── \"List your gym\" — a member registers the business they run ──────────────\r\nexport * from './gym-registration.js';\r\nexport * from './home.js';\r\nexport * from './insights.js';\r\n// ─── People ──────────────────────────────────────────────────────────────────\r\nexport * from './member.js';\r\nexport * from './member-profile.js';\r\nexport * from './membership.js';\r\n// ─── Membership ──────────────────────────────────────────────────────────────\r\nexport * from './membership-plan.js';\r\nexport * from './membership-request.js';\r\n// ─── API result messages + error codes ───────────────────────────────────────\r\nexport * from './messages.js';\r\n// ─── Engagement & analytics ──────────────────────────────────────────────────\r\nexport * from './notification.js';\r\nexport * from './onboarding.js';\r\n// ─── Onboarding wizard FORM validation (client-side working shapes) ──────────\r\nexport * from './onboarding-form.js';\r\n// ─── Gym structure & onboarding ──────────────────────────────────────────────\r\nexport * from './organisation.js';\r\nexport * from './owner-profile.js';\r\n// ─── Platform console (people + gyms directories, role keys) ─────────────────\r\nexport * from './platform.js';\r\nexport * from './pricing.js';\r\nexport * from './session.js';\r\nexport * from './shared.js';\r\nexport * from './social.js';\r\nexport * from './staff.js';\r\nexport * from './subscription.js';\r\nexport * from './user.js';\r\nexport * from './workout-plan.js';\r\n"]}
package/dist/social.d.ts CHANGED
@@ -28,13 +28,8 @@ export type ViewerSocialState = z.infer<typeof viewerSocialStateSchema>;
28
28
  /** Nobody follows anybody. The shape a missing lookup falls back to. */
29
29
  export declare const EMPTY_VIEWER_SOCIAL_STATE: ViewerSocialState;
30
30
  /**
31
- * "Followed by Priya, Rahul + 3 more" — this person's MUTUAL FOLLOWERS.
32
- *
33
- * The direction is easy to get backwards and the label depends on it: a mutual
34
- * is someone the VIEWER FOLLOWS who ALSO FOLLOWS this person. "Followed by
35
- * Priya" says Priya follows them, and Priya is worth naming because the viewer
36
- * follows Priya and will recognise her. The inverse set (people they both
37
- * follow) is a different thing and reads as a lie under this label.
31
+ * "Followed by Priya, Rahul and 3 more" — the people the viewer and this person
32
+ * both follow.
38
33
  *
39
34
  * `count` is the TOTAL; `avatars` and `sampleNames` carry only the handful
40
35
  * actually rendered. Sending every mutual so the client can count them would
@@ -131,15 +126,7 @@ export declare const toggleFollowResultSchema: z.ZodObject<{
131
126
  followerCount: z.ZodNumber;
132
127
  }, z.core.$strip>;
133
128
  export type ToggleFollowResult = z.infer<typeof toggleFollowResultSchema>;
134
- /**
135
- * `GET /api/v1/users/:id/social` — who this person is, and where the viewer
136
- * stands with them.
137
- *
138
- * `person` is the SAME shape as a row in the people list, deliberately. The
139
- * profile screen and the list row show the same facts and offer the same
140
- * actions, so giving them two shapes would mean two places to add a field and
141
- * one of them being forgotten.
142
- */
129
+ /** `GET /api/v1/users/:id/social` — one person's relationship plus their totals. */
143
130
  export declare const socialSnapshotSchema: z.ZodObject<{
144
131
  person: z.ZodObject<{
145
132
  userId: z.ZodString;
@@ -168,19 +155,7 @@ export declare const socialSnapshotSchema: z.ZodObject<{
168
155
  followingCount: z.ZodNumber;
169
156
  }, z.core.$strip>;
170
157
  export type SocialSnapshot = z.infer<typeof socialSnapshotSchema>;
171
- /**
172
- * Blocking is ONE-WAY in storage and BIDIRECTIONAL in effect.
173
- *
174
- * One row records that A blocked B. Nothing records the reverse, because B has
175
- * not blocked anybody. But every gate that consults it looks BOTH ways: B must
176
- * not be able to follow, message or even see A, or the block would only work
177
- * against the person who asked for it.
178
- *
179
- * Blocking is also a reset, not a mute: it tears down any follow in either
180
- * direction and freezes any conversation the two had. Leaving a follow in place
181
- * would keep the blocked person in the blocker's follower count, which is
182
- * exactly the connection they just severed.
183
- */
158
+ /** One row of the viewer's block list. */
184
159
  export declare const blockedUserSchema: z.ZodObject<{
185
160
  userId: z.ZodString;
186
161
  name: z.ZodString;
@@ -1 +1 @@
1
- {"version":3,"file":"social.d.ts","sourceRoot":"","sources":["../src/social.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB;;;;;iBASlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,wEAAwE;AACxE,eAAO,MAAM,yBAAyB,EAAE,iBAKvC,CAAC;AAIF;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa;;;;iBAMxB,CAAC;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD,uDAAuD;AACvD,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAIrC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB;;;;EAAuC,CAAC;AACxE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,oDAAoD;AACpD,eAAO,MAAM,iBAAiB;;;;;EAAmD,CAAC;AAClF,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,eAAO,MAAM,mBAAmB,mDAAoD,CAAC;AAErF;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;iBAmBtB,CAAC;AACH,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEhD,8CAA8C;AAC9C,eAAO,MAAM,oBAAoB;;;;;;;iBAE/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAIlE;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB;;;;iBAInC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,oFAAoF;AACpF,eAAO,MAAM,oBAAoB;;;;;;;;;iBAI/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
1
+ {"version":3,"file":"social.d.ts","sourceRoot":"","sources":["../src/social.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB;;;;;iBASlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,wEAAwE;AACxE,eAAO,MAAM,yBAAyB,EAAE,iBAKvC,CAAC;AAIF;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa;;;;iBAMxB,CAAC;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD,uDAAuD;AACvD,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAIrC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB;;;;EAAuC,CAAC;AACxE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE,oDAAoD;AACpD,eAAO,MAAM,iBAAiB;;;;;EAAmD,CAAC;AAClF,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,eAAO,MAAM,mBAAmB,mDAAoD,CAAC;AAErF;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;iBAmBtB,CAAC;AACH,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEhD,8CAA8C;AAC9C,eAAO,MAAM,oBAAoB;;;;;;;iBAE/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAIlE;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB;;;;iBAInC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,oFAAoF;AACpF,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;iBAe/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAOlE,0CAA0C;AAC1C,eAAO,MAAM,iBAAiB;;;;;iBAK5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,mEAAmE;AACnE,eAAO,MAAM,uBAAuB;;;iBAGlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC"}
package/dist/social.js CHANGED
@@ -39,13 +39,8 @@ export const EMPTY_VIEWER_SOCIAL_STATE = {
39
39
  };
40
40
  // ─── Mutuals ─────────────────────────────────────────────────────────────────
41
41
  /**
42
- * "Followed by Priya, Rahul + 3 more" — this person's MUTUAL FOLLOWERS.
43
- *
44
- * The direction is easy to get backwards and the label depends on it: a mutual
45
- * is someone the VIEWER FOLLOWS who ALSO FOLLOWS this person. "Followed by
46
- * Priya" says Priya follows them, and Priya is worth naming because the viewer
47
- * follows Priya and will recognise her. The inverse set (people they both
48
- * follow) is a different thing and reads as a lie under this label.
42
+ * "Followed by Priya, Rahul and 3 more" — the people the viewer and this person
43
+ * both follow.
49
44
  *
50
45
  * `count` is the TOTAL; `avatars` and `sampleNames` carry only the handful
51
46
  * actually rendered. Sending every mutual so the client can count them would
@@ -123,34 +118,28 @@ export const toggleFollowResultSchema = z.object({
123
118
  following: z.boolean(),
124
119
  followerCount: z.number().int().nonnegative(),
125
120
  });
126
- /**
127
- * `GET /api/v1/users/:id/social` — who this person is, and where the viewer
128
- * stands with them.
129
- *
130
- * `person` is the SAME shape as a row in the people list, deliberately. The
131
- * profile screen and the list row show the same facts and offer the same
132
- * actions, so giving them two shapes would mean two places to add a field and
133
- * one of them being forgotten.
134
- */
121
+ /** `GET /api/v1/users/:id/social` — one person's relationship plus their totals. */
135
122
  export const socialSnapshotSchema = z.object({
123
+ /**
124
+ * The person themselves, in the SAME shape as a people-list row.
125
+ *
126
+ * The profile screen and the row show the same facts and offer the same
127
+ * actions, so two shapes would be two places to add a field and one of them
128
+ * being forgotten. `viewer` lives inside it.
129
+ *
130
+ * RECOVERED from the published 0.17.0 `.d.ts`, not from its sourcemap — that
131
+ * map's `sourcesContent` predates this field and dropped it silently, which
132
+ * would have broken the peer-profile screen on the next publish.
133
+ */
136
134
  person: buddySchema,
137
135
  followerCount: z.number().int().nonnegative(),
138
136
  followingCount: z.number().int().nonnegative(),
139
137
  });
140
- // ─── Block ───────────────────────────────────────────────────────────────────
141
- /**
142
- * Blocking is ONE-WAY in storage and BIDIRECTIONAL in effect.
143
- *
144
- * One row records that A blocked B. Nothing records the reverse, because B has
145
- * not blocked anybody. But every gate that consults it looks BOTH ways: B must
146
- * not be able to follow, message or even see A, or the block would only work
147
- * against the person who asked for it.
148
- *
149
- * Blocking is also a reset, not a mute: it tears down any follow in either
150
- * direction and freezes any conversation the two had. Leaving a follow in place
151
- * would keep the blocked person in the blocker's follower count, which is
152
- * exactly the connection they just severed.
153
- */
138
+ // ─── Blocking ────────────────────────────────────────────────────────────────
139
+ //
140
+ // RECOVERED from the published 0.17.0 build, not from its sourcemap: that map's
141
+ // `sourcesContent` predates these two and would have dropped them silently.
142
+ /** One row of the viewer's block list. */
154
143
  export const blockedUserSchema = z.object({
155
144
  userId: objectIdSchema,
156
145
  name: z.string(),
@@ -1 +1 @@
1
- {"version":3,"file":"social.js","sourceRoot":"","sources":["../src/social.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Social graph (follow)\n * ======================================\n * The follow relationship and the people list it feeds.\n *\n * A follow is UNILATERAL: one row, one direction, no acceptance step. A mutual\n * follow is two independent rows. That is what makes \"do I follow them\" and\n * \"do they follow me\" separately answerable, and therefore what makes\n * \"Follow back\" a state the UI can offer at all.\n *\n * @module gymmonk-schema/social\n */\n\nimport { z } from 'zod';\nimport { isoDateTimeSchema, objectIdSchema } from './common.js';\n\n// ─── Viewer state ────────────────────────────────────────────────────────────\n\n/**\n * How the viewer stands with one other person.\n *\n * Both directions are carried, and both timestamps with them. `followedYouAt`\n * is what the Followers list shows as \"· 2w\", and `followsViewer` is the only\n * thing that distinguishes a \"Follow\" button from a \"Follow back\" button.\n */\nexport const viewerSocialStateSchema = z.object({\n /** The viewer follows them. */\n following: z.boolean(),\n /** They follow the viewer. */\n followsViewer: z.boolean(),\n /** When the viewer started following them. Null when they do not. */\n followedAt: isoDateTimeSchema.nullable(),\n /** When they started following the viewer. Null when they do not. */\n followedYouAt: isoDateTimeSchema.nullable(),\n});\nexport type ViewerSocialState = z.infer<typeof viewerSocialStateSchema>;\n\n/** Nobody follows anybody. The shape a missing lookup falls back to. */\nexport const EMPTY_VIEWER_SOCIAL_STATE: ViewerSocialState = {\n following: false,\n followsViewer: false,\n followedAt: null,\n followedYouAt: null,\n};\n\n// ─── Mutuals ─────────────────────────────────────────────────────────────────\n\n/**\n * \"Followed by Priya, Rahul and 3 more\" — the people the viewer and this person\n * both follow.\n *\n * `count` is the TOTAL; `avatars` and `sampleNames` carry only the handful\n * actually rendered. Sending every mutual so the client can count them would\n * put an unbounded array on a list row for one line of secondary text.\n */\nexport const mutualsSchema = z.object({\n count: z.number().int().nonnegative(),\n /** Up to three, for the stacked faces. An entry is null when that person has no photo. */\n avatars: z.array(z.string().nullable()),\n /** Up to three names, for the sentence. */\n sampleNames: z.array(z.string()),\n});\nexport type Mutuals = z.infer<typeof mutualsSchema>;\n\n/** How many faces and names a mutuals line carries. */\nexport const MUTUALS_SAMPLE_SIZE = 3;\n\n// ─── People list ─────────────────────────────────────────────────────────────\n\n/**\n * Why this person is in the viewer's list.\n *\n * `gym` at the viewer's gym, not followed\n * `following` followed by the viewer, and NOT at their gym any more\n * `both` at the gym AND followed\n *\n * The distinction is load-bearing rather than cosmetic. Messaging is gated on\n * sharing a gym, so a `following` row must not offer a Message button the\n * backend will refuse. jansathi has no equivalent because its People tab is the\n * whole community and there is no second axis to be on the wrong side of.\n */\nexport const buddyRelationSchema = z.enum(['gym', 'following', 'both']);\nexport type BuddyRelation = z.infer<typeof buddyRelationSchema>;\n\n/** Which slice of the list the caller asked for. */\nexport const buddyFilterSchema = z.enum(['all', 'gym', 'following', 'followers']);\nexport type BuddyFilter = z.infer<typeof buddyFilterSchema>;\n\nexport const BUDDY_FILTER_VALUES = ['all', 'gym', 'following', 'followers'] as const;\n\n/**\n * One person as a peer is allowed to see them.\n *\n * Deliberately NOT derived from the owner's member projection. That one carries\n * phone numbers, addresses, emergency contacts, dues and attendance, and the\n * only reliable way to guarantee none of it reaches a peer is for this to be a\n * separate shape rather than the same one with fields removed.\n */\nexport const buddySchema = z.object({\n /** The USER id. This is who you follow and message, not a profile row. */\n userId: objectIdSchema,\n name: z.string(),\n avatarUrl: z.string().nullable(),\n /** \"Member since Mar 2025\", or null for someone with no membership history here. */\n memberSince: z.string().nullable(),\n /**\n * When this person was last seen in the app. Null if never.\n *\n * Carried so the list can offer a \"Recently active\" sort. It is deliberately\n * a raw instant rather than a rendered \"2h ago\": the client is the only side\n * that knows what time it is where the reader is sitting, and a string\n * formatted on the server goes stale the moment it is cached.\n */\n lastActiveAt: isoDateTimeSchema.nullable(),\n relation: buddyRelationSchema,\n viewer: viewerSocialStateSchema,\n mutuals: mutualsSchema.nullable(),\n});\nexport type Buddy = z.infer<typeof buddySchema>;\n\n/** `GET /api/v1/members/at-my-gym?filter=` */\nexport const buddyListQuerySchema = z.object({\n filter: buddyFilterSchema.default('all'),\n});\nexport type BuddyListQuery = z.infer<typeof buddyListQuerySchema>;\n\n// ─── Toggle ──────────────────────────────────────────────────────────────────\n\n/**\n * The state AFTER a toggle.\n *\n * A toggle rather than separate follow and unfollow endpoints: a button acting\n * on stale state can send the wrong one, and then the server has to decide\n * whether a double unfollow is an error. A toggle has one meaning whatever the\n * client believed, and this response says what is now true.\n */\nexport const toggleFollowResultSchema = z.object({\n targetUserId: objectIdSchema,\n following: z.boolean(),\n followerCount: z.number().int().nonnegative(),\n});\nexport type ToggleFollowResult = z.infer<typeof toggleFollowResultSchema>;\n\n/** `GET /api/v1/users/:id/social` — one person's relationship plus their totals. */\nexport const socialSnapshotSchema = z.object({\n viewer: viewerSocialStateSchema,\n followerCount: z.number().int().nonnegative(),\n followingCount: z.number().int().nonnegative(),\n});\nexport type SocialSnapshot = z.infer<typeof socialSnapshotSchema>;\n"]}
1
+ {"version":3,"file":"social.js","sourceRoot":"","sources":["../src/social.ts"],"names":[],"mappings":"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","sourcesContent":["/**\r\n * gymmonk-schema — Social graph (follow)\r\n * ======================================\r\n * The follow relationship and the people list it feeds.\r\n *\r\n * A follow is UNILATERAL: one row, one direction, no acceptance step. A mutual\r\n * follow is two independent rows. That is what makes \"do I follow them\" and\r\n * \"do they follow me\" separately answerable, and therefore what makes\r\n * \"Follow back\" a state the UI can offer at all.\r\n *\r\n * @module gymmonk-schema/social\r\n */\r\n\r\nimport { z } from 'zod';\r\nimport { isoDateTimeSchema, objectIdSchema } from './common.js';\r\n\r\n// ─── Viewer state ────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * How the viewer stands with one other person.\r\n *\r\n * Both directions are carried, and both timestamps with them. `followedYouAt`\r\n * is what the Followers list shows as \"· 2w\", and `followsViewer` is the only\r\n * thing that distinguishes a \"Follow\" button from a \"Follow back\" button.\r\n */\r\nexport const viewerSocialStateSchema = z.object({\r\n /** The viewer follows them. */\r\n following: z.boolean(),\r\n /** They follow the viewer. */\r\n followsViewer: z.boolean(),\r\n /** When the viewer started following them. Null when they do not. */\r\n followedAt: isoDateTimeSchema.nullable(),\r\n /** When they started following the viewer. Null when they do not. */\r\n followedYouAt: isoDateTimeSchema.nullable(),\r\n});\r\nexport type ViewerSocialState = z.infer<typeof viewerSocialStateSchema>;\r\n\r\n/** Nobody follows anybody. The shape a missing lookup falls back to. */\r\nexport const EMPTY_VIEWER_SOCIAL_STATE: ViewerSocialState = {\r\n following: false,\r\n followsViewer: false,\r\n followedAt: null,\r\n followedYouAt: null,\r\n};\r\n\r\n// ─── Mutuals ─────────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * \"Followed by Priya, Rahul and 3 more\" — the people the viewer and this person\r\n * both follow.\r\n *\r\n * `count` is the TOTAL; `avatars` and `sampleNames` carry only the handful\r\n * actually rendered. Sending every mutual so the client can count them would\r\n * put an unbounded array on a list row for one line of secondary text.\r\n */\r\nexport const mutualsSchema = z.object({\r\n count: z.number().int().nonnegative(),\r\n /** Up to three, for the stacked faces. An entry is null when that person has no photo. */\r\n avatars: z.array(z.string().nullable()),\r\n /** Up to three names, for the sentence. */\r\n sampleNames: z.array(z.string()),\r\n});\r\nexport type Mutuals = z.infer<typeof mutualsSchema>;\r\n\r\n/** How many faces and names a mutuals line carries. */\r\nexport const MUTUALS_SAMPLE_SIZE = 3;\r\n\r\n// ─── People list ─────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Why this person is in the viewer's list.\r\n *\r\n * `gym` at the viewer's gym, not followed\r\n * `following` followed by the viewer, and NOT at their gym any more\r\n * `both` at the gym AND followed\r\n *\r\n * The distinction is load-bearing rather than cosmetic. Messaging is gated on\r\n * sharing a gym, so a `following` row must not offer a Message button the\r\n * backend will refuse. jansathi has no equivalent because its People tab is the\r\n * whole community and there is no second axis to be on the wrong side of.\r\n */\r\nexport const buddyRelationSchema = z.enum(['gym', 'following', 'both']);\r\nexport type BuddyRelation = z.infer<typeof buddyRelationSchema>;\r\n\r\n/** Which slice of the list the caller asked for. */\r\nexport const buddyFilterSchema = z.enum(['all', 'gym', 'following', 'followers']);\r\nexport type BuddyFilter = z.infer<typeof buddyFilterSchema>;\r\n\r\nexport const BUDDY_FILTER_VALUES = ['all', 'gym', 'following', 'followers'] as const;\r\n\r\n/**\r\n * One person as a peer is allowed to see them.\r\n *\r\n * Deliberately NOT derived from the owner's member projection. That one carries\r\n * phone numbers, addresses, emergency contacts, dues and attendance, and the\r\n * only reliable way to guarantee none of it reaches a peer is for this to be a\r\n * separate shape rather than the same one with fields removed.\r\n */\r\nexport const buddySchema = z.object({\r\n /** The USER id. This is who you follow and message, not a profile row. */\r\n userId: objectIdSchema,\r\n name: z.string(),\r\n avatarUrl: z.string().nullable(),\r\n /** \"Member since Mar 2025\", or null for someone with no membership history here. */\r\n memberSince: z.string().nullable(),\r\n /**\r\n * When this person was last seen in the app. Null if never.\r\n *\r\n * Carried so the list can offer a \"Recently active\" sort. It is deliberately\r\n * a raw instant rather than a rendered \"2h ago\": the client is the only side\r\n * that knows what time it is where the reader is sitting, and a string\r\n * formatted on the server goes stale the moment it is cached.\r\n */\r\n lastActiveAt: isoDateTimeSchema.nullable(),\r\n relation: buddyRelationSchema,\r\n viewer: viewerSocialStateSchema,\r\n mutuals: mutualsSchema.nullable(),\r\n});\r\nexport type Buddy = z.infer<typeof buddySchema>;\r\n\r\n/** `GET /api/v1/members/at-my-gym?filter=` */\r\nexport const buddyListQuerySchema = z.object({\r\n filter: buddyFilterSchema.default('all'),\r\n});\r\nexport type BuddyListQuery = z.infer<typeof buddyListQuerySchema>;\r\n\r\n// ─── Toggle ──────────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * The state AFTER a toggle.\r\n *\r\n * A toggle rather than separate follow and unfollow endpoints: a button acting\r\n * on stale state can send the wrong one, and then the server has to decide\r\n * whether a double unfollow is an error. A toggle has one meaning whatever the\r\n * client believed, and this response says what is now true.\r\n */\r\nexport const toggleFollowResultSchema = z.object({\r\n targetUserId: objectIdSchema,\r\n following: z.boolean(),\r\n followerCount: z.number().int().nonnegative(),\r\n});\r\nexport type ToggleFollowResult = z.infer<typeof toggleFollowResultSchema>;\r\n\r\n/** `GET /api/v1/users/:id/social` — one person's relationship plus their totals. */\r\nexport const socialSnapshotSchema = z.object({\r\n /**\r\n * The person themselves, in the SAME shape as a people-list row.\r\n *\r\n * The profile screen and the row show the same facts and offer the same\r\n * actions, so two shapes would be two places to add a field and one of them\r\n * being forgotten. `viewer` lives inside it.\r\n *\r\n * RECOVERED from the published 0.17.0 `.d.ts`, not from its sourcemap — that\r\n * map's `sourcesContent` predates this field and dropped it silently, which\r\n * would have broken the peer-profile screen on the next publish.\r\n */\r\n person: buddySchema,\r\n followerCount: z.number().int().nonnegative(),\r\n followingCount: z.number().int().nonnegative(),\r\n});\r\nexport type SocialSnapshot = z.infer<typeof socialSnapshotSchema>;\r\n\r\n// ─── Blocking ────────────────────────────────────────────────────────────────\r\n//\r\n// RECOVERED from the published 0.17.0 build, not from its sourcemap: that map's\r\n// `sourcesContent` predates these two and would have dropped them silently.\r\n\r\n/** One row of the viewer's block list. */\r\nexport const blockedUserSchema = z.object({\r\n userId: objectIdSchema,\r\n name: z.string(),\r\n avatarUrl: z.string().nullable(),\r\n blockedAt: isoDateTimeSchema,\r\n});\r\nexport type BlockedUser = z.infer<typeof blockedUserSchema>;\r\n\r\n/** `POST /api/v1/users/:id/block` — the state AFTER the toggle. */\r\nexport const toggleBlockResultSchema = z.object({\r\n targetUserId: objectIdSchema,\r\n blocked: z.boolean(),\r\n});\r\nexport type ToggleBlockResult = z.infer<typeof toggleBlockResultSchema>;\r\n"]}
@@ -100,6 +100,7 @@ export declare const workoutPlanSummarySchema: z.ZodObject<{
100
100
  centerId: z.ZodString;
101
101
  name: z.ZodString;
102
102
  description: z.ZodNullable<z.ZodString>;
103
+ goals: z.ZodNullable<z.ZodString>;
103
104
  focusTags: z.ZodArray<z.ZodString>;
104
105
  createdAt: z.ZodISODateTime;
105
106
  updatedAt: z.ZodISODateTime;
@@ -110,6 +111,7 @@ export declare const workoutPlanSchema: z.ZodObject<{
110
111
  centerId: z.ZodString;
111
112
  name: z.ZodString;
112
113
  description: z.ZodNullable<z.ZodString>;
114
+ goals: z.ZodNullable<z.ZodString>;
113
115
  focusTags: z.ZodArray<z.ZodString>;
114
116
  createdAt: z.ZodISODateTime;
115
117
  updatedAt: z.ZodISODateTime;
@@ -177,6 +179,7 @@ export type CreateWorkoutPlanBody = z.infer<typeof createWorkoutPlanBodySchema>;
177
179
  export declare const updateWorkoutPlanBodySchema: z.ZodObject<{
178
180
  name: z.ZodOptional<z.ZodString>;
179
181
  description: z.ZodOptional<z.ZodPreprocess<z.ZodOptional<z.ZodString>>>;
182
+ goals: z.ZodOptional<z.ZodPreprocess<z.ZodOptional<z.ZodString>>>;
180
183
  focusTags: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
181
184
  days: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodObject<{
182
185
  weekday: z.ZodEnum<{
@@ -195,7 +198,6 @@ export declare const updateWorkoutPlanBodySchema: z.ZodObject<{
195
198
  restSec: z.ZodNumber;
196
199
  }, z.core.$strip>>>;
197
200
  }, z.core.$strip>>>>;
198
- goals: z.ZodOptional<z.ZodPreprocess<z.ZodOptional<z.ZodString>>>;
199
201
  }, z.core.$strip>;
200
202
  export type UpdateWorkoutPlanBody = z.infer<typeof updateWorkoutPlanBodySchema>;
201
203
  //# sourceMappingURL=workout-plan.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"workout-plan.d.ts","sourceRoot":"","sources":["../src/workout-plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB,uEAAuE;AACvE,eAAO,MAAM,uBAAuB;;;;;iBAKlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;iBAGpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAI5E,6EAA6E;AAC7E,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;iBAY7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAGxB,CAAC;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAIpD,eAAO,MAAM,wBAAwB;;;;;;;;iBAQnC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAE5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAI5D,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;iBAOtC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEhF,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;iBAE5B,CAAC;AACb,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC"}
1
+ {"version":3,"file":"workout-plan.d.ts","sourceRoot":"","sources":["../src/workout-plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB,uEAAuE;AACvE,eAAO,MAAM,uBAAuB;;;;;iBAKlC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;iBAGpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAI5E,6EAA6E;AAC7E,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;iBAY7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAGxB,CAAC;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAIpD,eAAO,MAAM,wBAAwB;;;;;;;;;iBAkBnC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAE5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAI5D,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;iBAOtC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEhF,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;iBAE5B,CAAC;AACb,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC"}
@@ -49,6 +49,16 @@ export const workoutPlanSummarySchema = z.object({
49
49
  centerId: objectIdSchema,
50
50
  name: z.string(),
51
51
  description: z.string().nullable(),
52
+ /**
53
+ * What the plan is FOR ("Strength, Endurance"), as distinct from
54
+ * `description`, the prose blurb on the plan card.
55
+ *
56
+ * `createWorkoutPlanBodySchema` has accepted `goals` from the start but no
57
+ * read shape carried it, so the owner's Goals / Focus field was write-only —
58
+ * and, until the Mongoose model gained the path, not even written. Reading it
59
+ * back is what lets the plan be edited without silently clearing the field.
60
+ */
61
+ goals: z.string().nullable(),
52
62
  focusTags: z.array(z.string()),
53
63
  createdAt: isoDateTimeSchema,
54
64
  updatedAt: isoDateTimeSchema,
@@ -1 +1 @@
1
- {"version":3,"file":"workout-plan.js","sourceRoot":"","sources":["../src/workout-plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACvF,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,gFAAgF;AAEhF,uEAAuE;AACvE,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,UAAU,EAAE,cAAc;IAC1B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IACrC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IACtC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;CAC3C,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,OAAO,EAAE,aAAa;IACtB,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;CAChE,CAAC,CAAC;AAGH,gFAAgF;AAEhF,6EAA6E;AAC7E,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,UAAU,EAAE,cAAc;IAC1B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACnC,UAAU,EAAE,gBAAgB;IAC5B,QAAQ,EAAE,sBAAsB;IAChC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IACtB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;CAC1B,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC,OAAO,EAAE,aAAa;IACtB,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,kBAAkB,CAAC;CACvC,CAAC,CAAC;AAGH,gFAAgF;AAEhF,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,EAAE,EAAE,cAAc;IAClB,QAAQ,EAAE,cAAc;IACxB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC9B,SAAS,EAAE,iBAAiB;IAC5B,SAAS,EAAE,iBAAiB;CAC7B,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,iBAAiB,GAAG,wBAAwB,CAAC,MAAM,CAAC;IAC/D,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC;CAC7B,CAAC,CAAC;AAGH,gFAAgF;AAEhF,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC,MAAM,CAAC;IAClD,QAAQ,EAAE,cAAc;IACxB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,uBAAuB,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IAChE,WAAW,EAAE,qBAAqB,CAAC,IAAI,CAAC;IACxC,KAAK,EAAE,qBAAqB,CAAC,GAAG,CAAC;IACjC,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACjE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;CAC5D,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,2BAA2B,GAAG,2BAA2B;KACnE,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;KACxB,OAAO,EAAE,CAAC","sourcesContent":["/**\n * gymmonk-schema — Workout plans (routines)\n * ==========================================\n * A weekly training routine an owner builds from library exercises and assigns\n * to members. Each day carries prescribed exercises (sets / reps / rest). The\n * prescription is stored by exercise ref; the API populates a summary for\n * rendering.\n *\n * @module gymmonk-schema/workout-plan\n */\n\nimport { z } from 'zod';\nimport { isoDateTimeSchema, objectIdSchema, optionalTrimmedString } from './common.js';\nimport { difficultySchema, exerciseCategorySchema } from './exercise.js';\nimport { weekdaySchema } from './shared.js';\n\n// ─── Prescription (stored input) ─────────────────────────────────────────────\n\n/** One exercise slot in a plan day, referencing a library exercise. */\nexport const planExerciseInputSchema = z.object({\n exerciseId: objectIdSchema,\n sets: z.number().int().min(1).max(20),\n reps: z.string().trim().min(1).max(40),\n restSec: z.number().int().min(0).max(3600),\n});\nexport type PlanExerciseInput = z.infer<typeof planExerciseInputSchema>;\n\nexport const workoutPlanDayInputSchema = z.object({\n weekday: weekdaySchema,\n exercises: z.array(planExerciseInputSchema).max(30).default([]),\n});\nexport type WorkoutPlanDayInput = z.infer<typeof workoutPlanDayInputSchema>;\n\n// ─── Populated response shapes ───────────────────────────────────────────────\n\n/** A plan-day exercise with its populated library summary + prescription. */\nexport const planExerciseSchema = z.object({\n exerciseId: objectIdSchema,\n slug: z.string(),\n name: z.string(),\n description: z.string(),\n thumbnailUrl: z.string().nullable(),\n difficulty: difficultySchema,\n category: exerciseCategorySchema,\n targetArea: z.string(),\n sets: z.number().int(),\n reps: z.string(),\n restSec: z.number().int(),\n});\nexport type PlanExercise = z.infer<typeof planExerciseSchema>;\n\nexport const planDaySchema = z.object({\n weekday: weekdaySchema,\n exercises: z.array(planExerciseSchema),\n});\nexport type PlanDay = z.infer<typeof planDaySchema>;\n\n// ─── Entity ──────────────────────────────────────────────────────────────────\n\nexport const workoutPlanSummarySchema = z.object({\n id: objectIdSchema,\n centerId: objectIdSchema,\n name: z.string(),\n description: z.string().nullable(),\n focusTags: z.array(z.string()),\n createdAt: isoDateTimeSchema,\n updatedAt: isoDateTimeSchema,\n});\nexport type WorkoutPlanSummary = z.infer<typeof workoutPlanSummarySchema>;\n\nexport const workoutPlanSchema = workoutPlanSummarySchema.extend({\n days: z.array(planDaySchema),\n});\nexport type WorkoutPlan = z.infer<typeof workoutPlanSchema>;\n\n// ─── Create / update body ────────────────────────────────────────────────────\n\nexport const createWorkoutPlanBodySchema = z.object({\n centerId: objectIdSchema,\n name: z.string().trim().min(1, 'Plan name is required').max(120),\n description: optionalTrimmedString(2000),\n goals: optionalTrimmedString(400),\n focusTags: z.array(z.string().trim().max(40)).max(20).default([]),\n days: z.array(workoutPlanDayInputSchema).max(7).default([]),\n});\nexport type CreateWorkoutPlanBody = z.infer<typeof createWorkoutPlanBodySchema>;\n\nexport const updateWorkoutPlanBodySchema = createWorkoutPlanBodySchema\n .omit({ centerId: true })\n .partial();\nexport type UpdateWorkoutPlanBody = z.infer<typeof updateWorkoutPlanBodySchema>;\n"]}
1
+ {"version":3,"file":"workout-plan.js","sourceRoot":"","sources":["../src/workout-plan.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Workout plans (routines)\n * ==========================================\n * A weekly training routine an owner builds from library exercises and assigns\n * to members. Each day carries prescribed exercises (sets / reps / rest). The\n * prescription is stored by exercise ref; the API populates a summary for\n * rendering.\n *\n * @module gymmonk-schema/workout-plan\n */\n\nimport { z } from 'zod';\nimport { isoDateTimeSchema, objectIdSchema, optionalTrimmedString } from './common.js';\nimport { difficultySchema, exerciseCategorySchema } from './exercise.js';\nimport { weekdaySchema } from './shared.js';\n\n// ─── Prescription (stored input) ─────────────────────────────────────────────\n\n/** One exercise slot in a plan day, referencing a library exercise. */\nexport const planExerciseInputSchema = z.object({\n exerciseId: objectIdSchema,\n sets: z.number().int().min(1).max(20),\n reps: z.string().trim().min(1).max(40),\n restSec: z.number().int().min(0).max(3600),\n});\nexport type PlanExerciseInput = z.infer<typeof planExerciseInputSchema>;\n\nexport const workoutPlanDayInputSchema = z.object({\n weekday: weekdaySchema,\n exercises: z.array(planExerciseInputSchema).max(30).default([]),\n});\nexport type WorkoutPlanDayInput = z.infer<typeof workoutPlanDayInputSchema>;\n\n// ─── Populated response shapes ───────────────────────────────────────────────\n\n/** A plan-day exercise with its populated library summary + prescription. */\nexport const planExerciseSchema = z.object({\n exerciseId: objectIdSchema,\n slug: z.string(),\n name: z.string(),\n description: z.string(),\n thumbnailUrl: z.string().nullable(),\n difficulty: difficultySchema,\n category: exerciseCategorySchema,\n targetArea: z.string(),\n sets: z.number().int(),\n reps: z.string(),\n restSec: z.number().int(),\n});\nexport type PlanExercise = z.infer<typeof planExerciseSchema>;\n\nexport const planDaySchema = z.object({\n weekday: weekdaySchema,\n exercises: z.array(planExerciseSchema),\n});\nexport type PlanDay = z.infer<typeof planDaySchema>;\n\n// ─── Entity ──────────────────────────────────────────────────────────────────\n\nexport const workoutPlanSummarySchema = z.object({\n id: objectIdSchema,\n centerId: objectIdSchema,\n name: z.string(),\n description: z.string().nullable(),\n /**\n * What the plan is FOR (\"Strength, Endurance\"), as distinct from\n * `description`, the prose blurb on the plan card.\n *\n * `createWorkoutPlanBodySchema` has accepted `goals` from the start but no\n * read shape carried it, so the owner's Goals / Focus field was write-only —\n * and, until the Mongoose model gained the path, not even written. Reading it\n * back is what lets the plan be edited without silently clearing the field.\n */\n goals: z.string().nullable(),\n focusTags: z.array(z.string()),\n createdAt: isoDateTimeSchema,\n updatedAt: isoDateTimeSchema,\n});\nexport type WorkoutPlanSummary = z.infer<typeof workoutPlanSummarySchema>;\n\nexport const workoutPlanSchema = workoutPlanSummarySchema.extend({\n days: z.array(planDaySchema),\n});\nexport type WorkoutPlan = z.infer<typeof workoutPlanSchema>;\n\n// ─── Create / update body ────────────────────────────────────────────────────\n\nexport const createWorkoutPlanBodySchema = z.object({\n centerId: objectIdSchema,\n name: z.string().trim().min(1, 'Plan name is required').max(120),\n description: optionalTrimmedString(2000),\n goals: optionalTrimmedString(400),\n focusTags: z.array(z.string().trim().max(40)).max(20).default([]),\n days: z.array(workoutPlanDayInputSchema).max(7).default([]),\n});\nexport type CreateWorkoutPlanBody = z.infer<typeof createWorkoutPlanBodySchema>;\n\nexport const updateWorkoutPlanBodySchema = createWorkoutPlanBodySchema\n .omit({ centerId: true })\n .partial();\nexport type UpdateWorkoutPlanBody = z.infer<typeof updateWorkoutPlanBodySchema>;\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gymmonk-schema",
3
- "version": "0.17.0",
4
- "description": "Shared Zod schemas, enums and domain types for GymMonk (fitness SaaS) single source of truth (SSOT) consumed by gymmonk-backend and gymmonk-web-client.",
3
+ "version": "0.18.0",
4
+ "description": "Shared Zod schemas, enums and domain types for GymMonk (fitness SaaS) — single source of truth (SSOT) consumed by gymmonk-backend and gymmonk-web-client.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",
@@ -46,4 +46,4 @@
46
46
  "typescript": "^5.9.3",
47
47
  "zod": "^4.4.3"
48
48
  }
49
- }
49
+ }