gymmonk-schema 0.16.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 +43 -0
- package/dist/chat.d.ts +235 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +185 -0
- package/dist/chat.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/social.d.ts +172 -0
- package/dist/social.d.ts.map +1 -0
- package/dist/social.js +154 -0
- package/dist/social.js.map +1 -0
- package/dist/workout-plan.d.ts +3 -1
- package/dist/workout-plan.d.ts.map +1 -1
- package/dist/workout-plan.js +10 -0
- package/dist/workout-plan.js.map +1 -1
- package/package.json +47 -47
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(...)`.
|
package/dist/chat.d.ts
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gymmonk-schema — Chat
|
|
3
|
+
* =====================
|
|
4
|
+
* THE wire contract between `gymmonk-web-client` and `gymmonk-chat-server`, and
|
|
5
|
+
* the only place either is allowed to name a socket event.
|
|
6
|
+
*
|
|
7
|
+
* It lives here rather than in the chat server because three codebases need it
|
|
8
|
+
* and none of them owns the other two: the client emits, the chat server
|
|
9
|
+
* handles, and gymmonk-backend mints the tokens that let the two meet. An event
|
|
10
|
+
* name that differs by one character between any pair of them produces a
|
|
11
|
+
* message nobody receives and no error anywhere, which is exactly the class of
|
|
12
|
+
* bug a shared package exists to make impossible.
|
|
13
|
+
*
|
|
14
|
+
* @module gymmonk-schema/chat
|
|
15
|
+
*/
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
/**
|
|
18
|
+
* Every socket event name.
|
|
19
|
+
*
|
|
20
|
+
* String literals rather than an enum, so both sides put the identical bytes on
|
|
21
|
+
* the wire without either importing the other's runtime.
|
|
22
|
+
*/
|
|
23
|
+
export declare const CHAT_SOCKET_EVENTS: {
|
|
24
|
+
/** Server → client, on connect. The socket is up and awaiting auth. */
|
|
25
|
+
readonly HELLO: "hello";
|
|
26
|
+
/** Client → server. `{ token }`. Must arrive inside the auth window. */
|
|
27
|
+
readonly AUTH: "auth";
|
|
28
|
+
/** Server → client. `{ userId }` on success. */
|
|
29
|
+
readonly AUTH_OK: "auth:ok";
|
|
30
|
+
/** Server → client. `{ code, message }` for any failure, auth or otherwise. */
|
|
31
|
+
readonly ERROR: "error";
|
|
32
|
+
/** Client → server. Open a conversation and start receiving it live. */
|
|
33
|
+
readonly CONVERSATION_JOIN: "conversation:join";
|
|
34
|
+
/** Client → server. Leave it (navigated away). */
|
|
35
|
+
readonly CONVERSATION_LEAVE: "conversation:leave";
|
|
36
|
+
/** Client → server. `{ conversationId, clientMessageId, text }`. */
|
|
37
|
+
readonly MESSAGE_SEND: "message:send";
|
|
38
|
+
/** Server → everyone in the room, and the recipients' own rooms. */
|
|
39
|
+
readonly MESSAGE_NEW: "message:new";
|
|
40
|
+
/** Server → sender only. Their send, acknowledged and stored. */
|
|
41
|
+
readonly MESSAGE_SENT: "message:sent";
|
|
42
|
+
/** Client → server. Mark everything up to now as read. */
|
|
43
|
+
readonly MESSAGE_READ: "message:read";
|
|
44
|
+
/** Server → the room. `{ conversationId, userId, readAt }`. */
|
|
45
|
+
readonly MESSAGE_READ_BY: "message:read-by";
|
|
46
|
+
/** Client → server. `{ conversationId, typing }`. */
|
|
47
|
+
readonly TYPING: "typing";
|
|
48
|
+
/** Server → the other participants. `{ conversationId, userId, typing }`. */
|
|
49
|
+
readonly TYPING_UPDATE: "typing:update";
|
|
50
|
+
/** Server → the room. Who currently has this conversation open. */
|
|
51
|
+
readonly PRESENCE_UPDATE: "presence:update";
|
|
52
|
+
};
|
|
53
|
+
export type ChatSocketEvent = (typeof CHAT_SOCKET_EVENTS)[keyof typeof CHAT_SOCKET_EVENTS];
|
|
54
|
+
/** Stable codes on the `error` event. The client branches on these; the message is for logs. */
|
|
55
|
+
export declare const CHAT_ERROR: {
|
|
56
|
+
readonly AUTH_TIMEOUT: "AUTH_TIMEOUT";
|
|
57
|
+
readonly AUTH_FAILED: "AUTH_FAILED";
|
|
58
|
+
readonly NOT_AUTHENTICATED: "NOT_AUTHENTICATED";
|
|
59
|
+
readonly NOT_A_PARTICIPANT: "NOT_A_PARTICIPANT";
|
|
60
|
+
readonly CONVERSATION_NOT_FOUND: "CONVERSATION_NOT_FOUND";
|
|
61
|
+
readonly INVALID_PAYLOAD: "INVALID_PAYLOAD";
|
|
62
|
+
readonly RATE_LIMITED: "RATE_LIMITED";
|
|
63
|
+
readonly SERVER_ERROR: "SERVER_ERROR";
|
|
64
|
+
};
|
|
65
|
+
export type ChatErrorCode = (typeof CHAT_ERROR)[keyof typeof CHAT_ERROR];
|
|
66
|
+
/** Longest message body accepted. Matches the column cap in the model. */
|
|
67
|
+
export declare const MAX_MESSAGE_LENGTH = 4000;
|
|
68
|
+
/** Messages per page of history. */
|
|
69
|
+
export declare const MESSAGE_PAGE_SIZE = 50;
|
|
70
|
+
/**
|
|
71
|
+
* Messages one socket may send per minute.
|
|
72
|
+
*
|
|
73
|
+
* Not an anti-abuse boundary on its own: a member can only ever write to a
|
|
74
|
+
* conversation their own backend authorised. This is what stops a broken client
|
|
75
|
+
* looping a send from filling a collection.
|
|
76
|
+
*/
|
|
77
|
+
export declare const MESSAGE_RATE_PER_MINUTE = 60;
|
|
78
|
+
/** How long an unauthenticated socket may hold a connection, in ms. */
|
|
79
|
+
export declare const CHAT_AUTH_TIMEOUT_MS = 10000;
|
|
80
|
+
/**
|
|
81
|
+
* How long a typing indicator survives without a refresh, in ms.
|
|
82
|
+
*
|
|
83
|
+
* Someone who closes the app mid-sentence never sends `typing: false`, so the
|
|
84
|
+
* indicator has to expire on its own or it stays on screen forever. The sender
|
|
85
|
+
* re-emits well inside this window while they are still typing.
|
|
86
|
+
*/
|
|
87
|
+
export declare const TYPING_TIMEOUT_MS = 4000;
|
|
88
|
+
/** How often the sender re-emits `typing: true` while typing, in ms. */
|
|
89
|
+
export declare const TYPING_THROTTLE_MS = 2000;
|
|
90
|
+
/** A participant, denormalised onto the conversation so a list renders without joins. */
|
|
91
|
+
export declare const chatParticipantSchema: z.ZodObject<{
|
|
92
|
+
userId: z.ZodString;
|
|
93
|
+
displayName: z.ZodString;
|
|
94
|
+
avatarUrl: z.ZodNullable<z.ZodString>;
|
|
95
|
+
}, z.core.$strip>;
|
|
96
|
+
export type ChatParticipant = z.infer<typeof chatParticipantSchema>;
|
|
97
|
+
/** A message as every client sees it. */
|
|
98
|
+
export declare const chatMessageSchema: z.ZodObject<{
|
|
99
|
+
id: z.ZodString;
|
|
100
|
+
conversationId: z.ZodString;
|
|
101
|
+
senderId: z.ZodString;
|
|
102
|
+
text: z.ZodString;
|
|
103
|
+
clientMessageId: z.ZodString;
|
|
104
|
+
sentAt: z.ZodISODateTime;
|
|
105
|
+
readAt: z.ZodNullable<z.ZodISODateTime>;
|
|
106
|
+
deleted: z.ZodBoolean;
|
|
107
|
+
}, z.core.$strip>;
|
|
108
|
+
export type ChatMessage = z.infer<typeof chatMessageSchema>;
|
|
109
|
+
/** The one-line preview a conversation row shows. */
|
|
110
|
+
export declare const chatLastMessageSchema: z.ZodObject<{
|
|
111
|
+
text: z.ZodString;
|
|
112
|
+
senderId: z.ZodString;
|
|
113
|
+
sentAt: z.ZodISODateTime;
|
|
114
|
+
}, z.core.$strip>;
|
|
115
|
+
export type ChatLastMessage = z.infer<typeof chatLastMessageSchema>;
|
|
116
|
+
/**
|
|
117
|
+
* A conversation as the list screen sees it.
|
|
118
|
+
*
|
|
119
|
+
* `unreadCount` is the CALLER's, not a global figure: the same row means
|
|
120
|
+
* different things to the two people in it.
|
|
121
|
+
*/
|
|
122
|
+
export declare const chatConversationSchema: z.ZodObject<{
|
|
123
|
+
id: z.ZodString;
|
|
124
|
+
participants: z.ZodArray<z.ZodObject<{
|
|
125
|
+
userId: z.ZodString;
|
|
126
|
+
displayName: z.ZodString;
|
|
127
|
+
avatarUrl: z.ZodNullable<z.ZodString>;
|
|
128
|
+
}, z.core.$strip>>;
|
|
129
|
+
lastMessage: z.ZodNullable<z.ZodObject<{
|
|
130
|
+
text: z.ZodString;
|
|
131
|
+
senderId: z.ZodString;
|
|
132
|
+
sentAt: z.ZodISODateTime;
|
|
133
|
+
}, z.core.$strip>>;
|
|
134
|
+
unreadCount: z.ZodNumber;
|
|
135
|
+
createdAt: z.ZodISODateTime;
|
|
136
|
+
updatedAt: z.ZodISODateTime;
|
|
137
|
+
frozen: z.ZodBoolean;
|
|
138
|
+
}, z.core.$strip>;
|
|
139
|
+
export type ChatConversation = z.infer<typeof chatConversationSchema>;
|
|
140
|
+
export interface ChatAuthPayload {
|
|
141
|
+
token: string;
|
|
142
|
+
}
|
|
143
|
+
export interface ChatConversationRoomPayload {
|
|
144
|
+
conversationId: string;
|
|
145
|
+
}
|
|
146
|
+
export interface ChatMessageSendPayload {
|
|
147
|
+
conversationId: string;
|
|
148
|
+
/** Client-generated idempotency key. A retry with the same value is not a second message. */
|
|
149
|
+
clientMessageId: string;
|
|
150
|
+
text: string;
|
|
151
|
+
}
|
|
152
|
+
export interface ChatMessageReadPayload {
|
|
153
|
+
conversationId: string;
|
|
154
|
+
}
|
|
155
|
+
export interface ChatTypingPayload {
|
|
156
|
+
conversationId: string;
|
|
157
|
+
typing: boolean;
|
|
158
|
+
}
|
|
159
|
+
export interface ChatServerToClientEvents {
|
|
160
|
+
[CHAT_SOCKET_EVENTS.HELLO]: (payload: {
|
|
161
|
+
message: string;
|
|
162
|
+
}) => void;
|
|
163
|
+
[CHAT_SOCKET_EVENTS.AUTH_OK]: (payload: {
|
|
164
|
+
userId: string;
|
|
165
|
+
}) => void;
|
|
166
|
+
[CHAT_SOCKET_EVENTS.ERROR]: (payload: {
|
|
167
|
+
code: string;
|
|
168
|
+
message: string;
|
|
169
|
+
}) => void;
|
|
170
|
+
[CHAT_SOCKET_EVENTS.MESSAGE_NEW]: (message: ChatMessage) => void;
|
|
171
|
+
[CHAT_SOCKET_EVENTS.MESSAGE_SENT]: (payload: {
|
|
172
|
+
clientMessageId: string;
|
|
173
|
+
message: ChatMessage;
|
|
174
|
+
}) => void;
|
|
175
|
+
[CHAT_SOCKET_EVENTS.MESSAGE_READ_BY]: (payload: {
|
|
176
|
+
conversationId: string;
|
|
177
|
+
userId: string;
|
|
178
|
+
readAt: string;
|
|
179
|
+
}) => void;
|
|
180
|
+
[CHAT_SOCKET_EVENTS.TYPING_UPDATE]: (payload: {
|
|
181
|
+
conversationId: string;
|
|
182
|
+
userId: string;
|
|
183
|
+
typing: boolean;
|
|
184
|
+
}) => void;
|
|
185
|
+
[CHAT_SOCKET_EVENTS.PRESENCE_UPDATE]: (payload: {
|
|
186
|
+
conversationId: string;
|
|
187
|
+
userIds: string[];
|
|
188
|
+
}) => void;
|
|
189
|
+
}
|
|
190
|
+
export interface ChatClientToServerEvents {
|
|
191
|
+
[CHAT_SOCKET_EVENTS.AUTH]: (payload: ChatAuthPayload) => void;
|
|
192
|
+
[CHAT_SOCKET_EVENTS.CONVERSATION_JOIN]: (payload: ChatConversationRoomPayload) => void;
|
|
193
|
+
[CHAT_SOCKET_EVENTS.CONVERSATION_LEAVE]: (payload: ChatConversationRoomPayload) => void;
|
|
194
|
+
[CHAT_SOCKET_EVENTS.MESSAGE_SEND]: (payload: ChatMessageSendPayload) => void;
|
|
195
|
+
[CHAT_SOCKET_EVENTS.MESSAGE_READ]: (payload: ChatMessageReadPayload) => void;
|
|
196
|
+
[CHAT_SOCKET_EVENTS.TYPING]: (payload: ChatTypingPayload) => void;
|
|
197
|
+
}
|
|
198
|
+
/** `GET /api/v1/chat/config` — where chat lives, whether it is on, and who the caller is. */
|
|
199
|
+
export declare const chatConfigSchema: z.ZodObject<{
|
|
200
|
+
enabled: z.ZodBoolean;
|
|
201
|
+
url: z.ZodNullable<z.ZodString>;
|
|
202
|
+
userId: z.ZodString;
|
|
203
|
+
}, z.core.$strip>;
|
|
204
|
+
export type ChatConfig = z.infer<typeof chatConfigSchema>;
|
|
205
|
+
/** `POST /api/v1/chat/auth-token` */
|
|
206
|
+
export declare const chatAuthTokenSchema: z.ZodObject<{
|
|
207
|
+
token: z.ZodString;
|
|
208
|
+
expiresAt: z.ZodISODateTime;
|
|
209
|
+
expiresIn: z.ZodNumber;
|
|
210
|
+
}, z.core.$strip>;
|
|
211
|
+
export type ChatAuthToken = z.infer<typeof chatAuthTokenSchema>;
|
|
212
|
+
/** `POST /api/v1/chat/invitation` — permission to open a conversation with someone. */
|
|
213
|
+
export declare const chatInvitationBodySchema: z.ZodObject<{
|
|
214
|
+
userId: z.ZodString;
|
|
215
|
+
}, z.core.$strip>;
|
|
216
|
+
export type ChatInvitationBody = z.infer<typeof chatInvitationBodySchema>;
|
|
217
|
+
export declare const chatInvitationSchema: z.ZodObject<{
|
|
218
|
+
invitationToken: z.ZodString;
|
|
219
|
+
expiresAt: z.ZodISODateTime;
|
|
220
|
+
}, z.core.$strip>;
|
|
221
|
+
export type ChatInvitation = z.infer<typeof chatInvitationSchema>;
|
|
222
|
+
/** `POST /api/v1/chat/notify` — chat-server telling the backend to notify people. */
|
|
223
|
+
export declare const chatNotifyBodySchema: z.ZodObject<{
|
|
224
|
+
recipientIds: z.ZodArray<z.ZodString>;
|
|
225
|
+
conversationId: z.ZodString;
|
|
226
|
+
senderId: z.ZodString;
|
|
227
|
+
senderName: z.ZodString;
|
|
228
|
+
text: z.ZodString;
|
|
229
|
+
}, z.core.$strip>;
|
|
230
|
+
export type ChatNotifyBody = z.infer<typeof chatNotifyBodySchema>;
|
|
231
|
+
/** JWT `aud` on every chat token. chat-server accepts exactly this. */
|
|
232
|
+
export declare const CHAT_TOKEN_AUDIENCE = "gymmonk-chat-server";
|
|
233
|
+
/** `purpose` claim marking an invitation. Auth tokens carry no purpose. */
|
|
234
|
+
export declare const CHAT_INVITATION_PURPOSE = "invitation";
|
|
235
|
+
//# sourceMappingURL=chat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../src/chat.ts"],"names":[],"mappings":"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"}
|
package/dist/chat.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gymmonk-schema — Chat
|
|
3
|
+
* =====================
|
|
4
|
+
* THE wire contract between `gymmonk-web-client` and `gymmonk-chat-server`, and
|
|
5
|
+
* the only place either is allowed to name a socket event.
|
|
6
|
+
*
|
|
7
|
+
* It lives here rather than in the chat server because three codebases need it
|
|
8
|
+
* and none of them owns the other two: the client emits, the chat server
|
|
9
|
+
* handles, and gymmonk-backend mints the tokens that let the two meet. An event
|
|
10
|
+
* name that differs by one character between any pair of them produces a
|
|
11
|
+
* message nobody receives and no error anywhere, which is exactly the class of
|
|
12
|
+
* bug a shared package exists to make impossible.
|
|
13
|
+
*
|
|
14
|
+
* @module gymmonk-schema/chat
|
|
15
|
+
*/
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { isoDateTimeSchema, objectIdSchema } from './common.js';
|
|
18
|
+
// ─── Socket events ───────────────────────────────────────────────────────────
|
|
19
|
+
/**
|
|
20
|
+
* Every socket event name.
|
|
21
|
+
*
|
|
22
|
+
* String literals rather than an enum, so both sides put the identical bytes on
|
|
23
|
+
* the wire without either importing the other's runtime.
|
|
24
|
+
*/
|
|
25
|
+
export const CHAT_SOCKET_EVENTS = {
|
|
26
|
+
/** Server → client, on connect. The socket is up and awaiting auth. */
|
|
27
|
+
HELLO: 'hello',
|
|
28
|
+
/** Client → server. `{ token }`. Must arrive inside the auth window. */
|
|
29
|
+
AUTH: 'auth',
|
|
30
|
+
/** Server → client. `{ userId }` on success. */
|
|
31
|
+
AUTH_OK: 'auth:ok',
|
|
32
|
+
/** Server → client. `{ code, message }` for any failure, auth or otherwise. */
|
|
33
|
+
ERROR: 'error',
|
|
34
|
+
/** Client → server. Open a conversation and start receiving it live. */
|
|
35
|
+
CONVERSATION_JOIN: 'conversation:join',
|
|
36
|
+
/** Client → server. Leave it (navigated away). */
|
|
37
|
+
CONVERSATION_LEAVE: 'conversation:leave',
|
|
38
|
+
/** Client → server. `{ conversationId, clientMessageId, text }`. */
|
|
39
|
+
MESSAGE_SEND: 'message:send',
|
|
40
|
+
/** Server → everyone in the room, and the recipients' own rooms. */
|
|
41
|
+
MESSAGE_NEW: 'message:new',
|
|
42
|
+
/** Server → sender only. Their send, acknowledged and stored. */
|
|
43
|
+
MESSAGE_SENT: 'message:sent',
|
|
44
|
+
/** Client → server. Mark everything up to now as read. */
|
|
45
|
+
MESSAGE_READ: 'message:read',
|
|
46
|
+
/** Server → the room. `{ conversationId, userId, readAt }`. */
|
|
47
|
+
MESSAGE_READ_BY: 'message:read-by',
|
|
48
|
+
/** Client → server. `{ conversationId, typing }`. */
|
|
49
|
+
TYPING: 'typing',
|
|
50
|
+
/** Server → the other participants. `{ conversationId, userId, typing }`. */
|
|
51
|
+
TYPING_UPDATE: 'typing:update',
|
|
52
|
+
/** Server → the room. Who currently has this conversation open. */
|
|
53
|
+
PRESENCE_UPDATE: 'presence:update',
|
|
54
|
+
};
|
|
55
|
+
// ─── Error codes ─────────────────────────────────────────────────────────────
|
|
56
|
+
/** Stable codes on the `error` event. The client branches on these; the message is for logs. */
|
|
57
|
+
export const CHAT_ERROR = {
|
|
58
|
+
AUTH_TIMEOUT: 'AUTH_TIMEOUT',
|
|
59
|
+
AUTH_FAILED: 'AUTH_FAILED',
|
|
60
|
+
NOT_AUTHENTICATED: 'NOT_AUTHENTICATED',
|
|
61
|
+
NOT_A_PARTICIPANT: 'NOT_A_PARTICIPANT',
|
|
62
|
+
CONVERSATION_NOT_FOUND: 'CONVERSATION_NOT_FOUND',
|
|
63
|
+
INVALID_PAYLOAD: 'INVALID_PAYLOAD',
|
|
64
|
+
RATE_LIMITED: 'RATE_LIMITED',
|
|
65
|
+
SERVER_ERROR: 'SERVER_ERROR',
|
|
66
|
+
};
|
|
67
|
+
// ─── Limits ──────────────────────────────────────────────────────────────────
|
|
68
|
+
/** Longest message body accepted. Matches the column cap in the model. */
|
|
69
|
+
export const MAX_MESSAGE_LENGTH = 4000;
|
|
70
|
+
/** Messages per page of history. */
|
|
71
|
+
export const MESSAGE_PAGE_SIZE = 50;
|
|
72
|
+
/**
|
|
73
|
+
* Messages one socket may send per minute.
|
|
74
|
+
*
|
|
75
|
+
* Not an anti-abuse boundary on its own: a member can only ever write to a
|
|
76
|
+
* conversation their own backend authorised. This is what stops a broken client
|
|
77
|
+
* looping a send from filling a collection.
|
|
78
|
+
*/
|
|
79
|
+
export const MESSAGE_RATE_PER_MINUTE = 60;
|
|
80
|
+
/** How long an unauthenticated socket may hold a connection, in ms. */
|
|
81
|
+
export const CHAT_AUTH_TIMEOUT_MS = 10_000;
|
|
82
|
+
/**
|
|
83
|
+
* How long a typing indicator survives without a refresh, in ms.
|
|
84
|
+
*
|
|
85
|
+
* Someone who closes the app mid-sentence never sends `typing: false`, so the
|
|
86
|
+
* indicator has to expire on its own or it stays on screen forever. The sender
|
|
87
|
+
* re-emits well inside this window while they are still typing.
|
|
88
|
+
*/
|
|
89
|
+
export const TYPING_TIMEOUT_MS = 4_000;
|
|
90
|
+
/** How often the sender re-emits `typing: true` while typing, in ms. */
|
|
91
|
+
export const TYPING_THROTTLE_MS = 2_000;
|
|
92
|
+
// ─── Wire shapes ─────────────────────────────────────────────────────────────
|
|
93
|
+
/** A participant, denormalised onto the conversation so a list renders without joins. */
|
|
94
|
+
export const chatParticipantSchema = z.object({
|
|
95
|
+
userId: objectIdSchema,
|
|
96
|
+
displayName: z.string(),
|
|
97
|
+
avatarUrl: z.string().nullable(),
|
|
98
|
+
});
|
|
99
|
+
/** A message as every client sees it. */
|
|
100
|
+
export const chatMessageSchema = z.object({
|
|
101
|
+
id: objectIdSchema,
|
|
102
|
+
conversationId: objectIdSchema,
|
|
103
|
+
senderId: objectIdSchema,
|
|
104
|
+
text: z.string(),
|
|
105
|
+
/** Echoed back so the sender can reconcile its optimistic row. */
|
|
106
|
+
clientMessageId: z.string(),
|
|
107
|
+
sentAt: isoDateTimeSchema,
|
|
108
|
+
readAt: isoDateTimeSchema.nullable(),
|
|
109
|
+
/** Deleted for everyone. `text` is then empty. */
|
|
110
|
+
deleted: z.boolean(),
|
|
111
|
+
});
|
|
112
|
+
/** The one-line preview a conversation row shows. */
|
|
113
|
+
export const chatLastMessageSchema = z.object({
|
|
114
|
+
text: z.string(),
|
|
115
|
+
senderId: objectIdSchema,
|
|
116
|
+
sentAt: isoDateTimeSchema,
|
|
117
|
+
});
|
|
118
|
+
/**
|
|
119
|
+
* A conversation as the list screen sees it.
|
|
120
|
+
*
|
|
121
|
+
* `unreadCount` is the CALLER's, not a global figure: the same row means
|
|
122
|
+
* different things to the two people in it.
|
|
123
|
+
*/
|
|
124
|
+
export const chatConversationSchema = z.object({
|
|
125
|
+
id: objectIdSchema,
|
|
126
|
+
participants: z.array(chatParticipantSchema),
|
|
127
|
+
lastMessage: chatLastMessageSchema.nullable(),
|
|
128
|
+
unreadCount: z.number().int().nonnegative(),
|
|
129
|
+
createdAt: isoDateTimeSchema,
|
|
130
|
+
/** Last activity. The inbox sorts on this, newest first. */
|
|
131
|
+
updatedAt: isoDateTimeSchema,
|
|
132
|
+
/**
|
|
133
|
+
* Closed to new messages. True once one participant blocks the other.
|
|
134
|
+
*
|
|
135
|
+
* History still reads: freezing stops the next message, and deleting what was
|
|
136
|
+
* already said is a different and much larger decision. The client uses this
|
|
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.
|
|
141
|
+
*/
|
|
142
|
+
frozen: z.boolean(),
|
|
143
|
+
});
|
|
144
|
+
// ─── Backend bridge ──────────────────────────────────────────────────────────
|
|
145
|
+
/** `GET /api/v1/chat/config` — where chat lives, whether it is on, and who the caller is. */
|
|
146
|
+
export const chatConfigSchema = z.object({
|
|
147
|
+
enabled: z.boolean(),
|
|
148
|
+
url: z.string().nullable(),
|
|
149
|
+
/**
|
|
150
|
+
* The caller's id AS CHAT-SERVER WILL SEE IT.
|
|
151
|
+
*
|
|
152
|
+
* Sourced from the same place that mints the token rather than from the
|
|
153
|
+
* client session, because the two must agree or "is this message mine" is
|
|
154
|
+
* wrong for somebody.
|
|
155
|
+
*/
|
|
156
|
+
userId: objectIdSchema,
|
|
157
|
+
});
|
|
158
|
+
/** `POST /api/v1/chat/auth-token` */
|
|
159
|
+
export const chatAuthTokenSchema = z.object({
|
|
160
|
+
token: z.string(),
|
|
161
|
+
expiresAt: isoDateTimeSchema,
|
|
162
|
+
/** Seconds, so a client can schedule a refresh without parsing a date. */
|
|
163
|
+
expiresIn: z.number().int().positive(),
|
|
164
|
+
});
|
|
165
|
+
/** `POST /api/v1/chat/invitation` — permission to open a conversation with someone. */
|
|
166
|
+
export const chatInvitationBodySchema = z.object({
|
|
167
|
+
userId: objectIdSchema,
|
|
168
|
+
});
|
|
169
|
+
export const chatInvitationSchema = z.object({
|
|
170
|
+
invitationToken: z.string(),
|
|
171
|
+
expiresAt: isoDateTimeSchema,
|
|
172
|
+
});
|
|
173
|
+
/** `POST /api/v1/chat/notify` — chat-server telling the backend to notify people. */
|
|
174
|
+
export const chatNotifyBodySchema = z.object({
|
|
175
|
+
recipientIds: z.array(objectIdSchema).min(1),
|
|
176
|
+
conversationId: objectIdSchema,
|
|
177
|
+
senderId: objectIdSchema,
|
|
178
|
+
senderName: z.string(),
|
|
179
|
+
text: z.string(),
|
|
180
|
+
});
|
|
181
|
+
/** JWT `aud` on every chat token. chat-server accepts exactly this. */
|
|
182
|
+
export const CHAT_TOKEN_AUDIENCE = 'gymmonk-chat-server';
|
|
183
|
+
/** `purpose` claim marking an invitation. Auth tokens carry no purpose. */
|
|
184
|
+
export const CHAT_INVITATION_PURPOSE = 'invitation';
|
|
185
|
+
//# sourceMappingURL=chat.js.map
|
package/dist/chat.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
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,6 +37,7 @@ 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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,6 +51,7 @@ 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';
|
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
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gymmonk-schema — Social graph (follow)
|
|
3
|
+
* ======================================
|
|
4
|
+
* The follow relationship and the people list it feeds.
|
|
5
|
+
*
|
|
6
|
+
* A follow is UNILATERAL: one row, one direction, no acceptance step. A mutual
|
|
7
|
+
* follow is two independent rows. That is what makes "do I follow them" and
|
|
8
|
+
* "do they follow me" separately answerable, and therefore what makes
|
|
9
|
+
* "Follow back" a state the UI can offer at all.
|
|
10
|
+
*
|
|
11
|
+
* @module gymmonk-schema/social
|
|
12
|
+
*/
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
/**
|
|
15
|
+
* How the viewer stands with one other person.
|
|
16
|
+
*
|
|
17
|
+
* Both directions are carried, and both timestamps with them. `followedYouAt`
|
|
18
|
+
* is what the Followers list shows as "· 2w", and `followsViewer` is the only
|
|
19
|
+
* thing that distinguishes a "Follow" button from a "Follow back" button.
|
|
20
|
+
*/
|
|
21
|
+
export declare const viewerSocialStateSchema: z.ZodObject<{
|
|
22
|
+
following: z.ZodBoolean;
|
|
23
|
+
followsViewer: z.ZodBoolean;
|
|
24
|
+
followedAt: z.ZodNullable<z.ZodISODateTime>;
|
|
25
|
+
followedYouAt: z.ZodNullable<z.ZodISODateTime>;
|
|
26
|
+
}, z.core.$strip>;
|
|
27
|
+
export type ViewerSocialState = z.infer<typeof viewerSocialStateSchema>;
|
|
28
|
+
/** Nobody follows anybody. The shape a missing lookup falls back to. */
|
|
29
|
+
export declare const EMPTY_VIEWER_SOCIAL_STATE: ViewerSocialState;
|
|
30
|
+
/**
|
|
31
|
+
* "Followed by Priya, Rahul and 3 more" — the people the viewer and this person
|
|
32
|
+
* both follow.
|
|
33
|
+
*
|
|
34
|
+
* `count` is the TOTAL; `avatars` and `sampleNames` carry only the handful
|
|
35
|
+
* actually rendered. Sending every mutual so the client can count them would
|
|
36
|
+
* put an unbounded array on a list row for one line of secondary text.
|
|
37
|
+
*/
|
|
38
|
+
export declare const mutualsSchema: z.ZodObject<{
|
|
39
|
+
count: z.ZodNumber;
|
|
40
|
+
avatars: z.ZodArray<z.ZodNullable<z.ZodString>>;
|
|
41
|
+
sampleNames: z.ZodArray<z.ZodString>;
|
|
42
|
+
}, z.core.$strip>;
|
|
43
|
+
export type Mutuals = z.infer<typeof mutualsSchema>;
|
|
44
|
+
/** How many faces and names a mutuals line carries. */
|
|
45
|
+
export declare const MUTUALS_SAMPLE_SIZE = 3;
|
|
46
|
+
/**
|
|
47
|
+
* Why this person is in the viewer's list.
|
|
48
|
+
*
|
|
49
|
+
* `gym` at the viewer's gym, not followed
|
|
50
|
+
* `following` followed by the viewer, and NOT at their gym any more
|
|
51
|
+
* `both` at the gym AND followed
|
|
52
|
+
*
|
|
53
|
+
* The distinction is load-bearing rather than cosmetic. Messaging is gated on
|
|
54
|
+
* sharing a gym, so a `following` row must not offer a Message button the
|
|
55
|
+
* backend will refuse. jansathi has no equivalent because its People tab is the
|
|
56
|
+
* whole community and there is no second axis to be on the wrong side of.
|
|
57
|
+
*/
|
|
58
|
+
export declare const buddyRelationSchema: z.ZodEnum<{
|
|
59
|
+
gym: "gym";
|
|
60
|
+
following: "following";
|
|
61
|
+
both: "both";
|
|
62
|
+
}>;
|
|
63
|
+
export type BuddyRelation = z.infer<typeof buddyRelationSchema>;
|
|
64
|
+
/** Which slice of the list the caller asked for. */
|
|
65
|
+
export declare const buddyFilterSchema: z.ZodEnum<{
|
|
66
|
+
all: "all";
|
|
67
|
+
gym: "gym";
|
|
68
|
+
following: "following";
|
|
69
|
+
followers: "followers";
|
|
70
|
+
}>;
|
|
71
|
+
export type BuddyFilter = z.infer<typeof buddyFilterSchema>;
|
|
72
|
+
export declare const BUDDY_FILTER_VALUES: readonly ["all", "gym", "following", "followers"];
|
|
73
|
+
/**
|
|
74
|
+
* One person as a peer is allowed to see them.
|
|
75
|
+
*
|
|
76
|
+
* Deliberately NOT derived from the owner's member projection. That one carries
|
|
77
|
+
* phone numbers, addresses, emergency contacts, dues and attendance, and the
|
|
78
|
+
* only reliable way to guarantee none of it reaches a peer is for this to be a
|
|
79
|
+
* separate shape rather than the same one with fields removed.
|
|
80
|
+
*/
|
|
81
|
+
export declare const buddySchema: z.ZodObject<{
|
|
82
|
+
userId: z.ZodString;
|
|
83
|
+
name: z.ZodString;
|
|
84
|
+
avatarUrl: z.ZodNullable<z.ZodString>;
|
|
85
|
+
memberSince: z.ZodNullable<z.ZodString>;
|
|
86
|
+
lastActiveAt: z.ZodNullable<z.ZodISODateTime>;
|
|
87
|
+
relation: z.ZodEnum<{
|
|
88
|
+
gym: "gym";
|
|
89
|
+
following: "following";
|
|
90
|
+
both: "both";
|
|
91
|
+
}>;
|
|
92
|
+
viewer: z.ZodObject<{
|
|
93
|
+
following: z.ZodBoolean;
|
|
94
|
+
followsViewer: z.ZodBoolean;
|
|
95
|
+
followedAt: z.ZodNullable<z.ZodISODateTime>;
|
|
96
|
+
followedYouAt: z.ZodNullable<z.ZodISODateTime>;
|
|
97
|
+
}, z.core.$strip>;
|
|
98
|
+
mutuals: z.ZodNullable<z.ZodObject<{
|
|
99
|
+
count: z.ZodNumber;
|
|
100
|
+
avatars: z.ZodArray<z.ZodNullable<z.ZodString>>;
|
|
101
|
+
sampleNames: z.ZodArray<z.ZodString>;
|
|
102
|
+
}, z.core.$strip>>;
|
|
103
|
+
}, z.core.$strip>;
|
|
104
|
+
export type Buddy = z.infer<typeof buddySchema>;
|
|
105
|
+
/** `GET /api/v1/members/at-my-gym?filter=` */
|
|
106
|
+
export declare const buddyListQuerySchema: z.ZodObject<{
|
|
107
|
+
filter: z.ZodDefault<z.ZodEnum<{
|
|
108
|
+
all: "all";
|
|
109
|
+
gym: "gym";
|
|
110
|
+
following: "following";
|
|
111
|
+
followers: "followers";
|
|
112
|
+
}>>;
|
|
113
|
+
}, z.core.$strip>;
|
|
114
|
+
export type BuddyListQuery = z.infer<typeof buddyListQuerySchema>;
|
|
115
|
+
/**
|
|
116
|
+
* The state AFTER a toggle.
|
|
117
|
+
*
|
|
118
|
+
* A toggle rather than separate follow and unfollow endpoints: a button acting
|
|
119
|
+
* on stale state can send the wrong one, and then the server has to decide
|
|
120
|
+
* whether a double unfollow is an error. A toggle has one meaning whatever the
|
|
121
|
+
* client believed, and this response says what is now true.
|
|
122
|
+
*/
|
|
123
|
+
export declare const toggleFollowResultSchema: z.ZodObject<{
|
|
124
|
+
targetUserId: z.ZodString;
|
|
125
|
+
following: z.ZodBoolean;
|
|
126
|
+
followerCount: z.ZodNumber;
|
|
127
|
+
}, z.core.$strip>;
|
|
128
|
+
export type ToggleFollowResult = z.infer<typeof toggleFollowResultSchema>;
|
|
129
|
+
/** `GET /api/v1/users/:id/social` — one person's relationship plus their totals. */
|
|
130
|
+
export declare const socialSnapshotSchema: z.ZodObject<{
|
|
131
|
+
person: z.ZodObject<{
|
|
132
|
+
userId: z.ZodString;
|
|
133
|
+
name: z.ZodString;
|
|
134
|
+
avatarUrl: z.ZodNullable<z.ZodString>;
|
|
135
|
+
memberSince: z.ZodNullable<z.ZodString>;
|
|
136
|
+
lastActiveAt: z.ZodNullable<z.ZodISODateTime>;
|
|
137
|
+
relation: z.ZodEnum<{
|
|
138
|
+
gym: "gym";
|
|
139
|
+
following: "following";
|
|
140
|
+
both: "both";
|
|
141
|
+
}>;
|
|
142
|
+
viewer: z.ZodObject<{
|
|
143
|
+
following: z.ZodBoolean;
|
|
144
|
+
followsViewer: z.ZodBoolean;
|
|
145
|
+
followedAt: z.ZodNullable<z.ZodISODateTime>;
|
|
146
|
+
followedYouAt: z.ZodNullable<z.ZodISODateTime>;
|
|
147
|
+
}, z.core.$strip>;
|
|
148
|
+
mutuals: z.ZodNullable<z.ZodObject<{
|
|
149
|
+
count: z.ZodNumber;
|
|
150
|
+
avatars: z.ZodArray<z.ZodNullable<z.ZodString>>;
|
|
151
|
+
sampleNames: z.ZodArray<z.ZodString>;
|
|
152
|
+
}, z.core.$strip>>;
|
|
153
|
+
}, z.core.$strip>;
|
|
154
|
+
followerCount: z.ZodNumber;
|
|
155
|
+
followingCount: z.ZodNumber;
|
|
156
|
+
}, z.core.$strip>;
|
|
157
|
+
export type SocialSnapshot = z.infer<typeof socialSnapshotSchema>;
|
|
158
|
+
/** One row of the viewer's block list. */
|
|
159
|
+
export declare const blockedUserSchema: z.ZodObject<{
|
|
160
|
+
userId: z.ZodString;
|
|
161
|
+
name: z.ZodString;
|
|
162
|
+
avatarUrl: z.ZodNullable<z.ZodString>;
|
|
163
|
+
blockedAt: z.ZodISODateTime;
|
|
164
|
+
}, z.core.$strip>;
|
|
165
|
+
export type BlockedUser = z.infer<typeof blockedUserSchema>;
|
|
166
|
+
/** `POST /api/v1/users/:id/block` — the state AFTER the toggle. */
|
|
167
|
+
export declare const toggleBlockResultSchema: z.ZodObject<{
|
|
168
|
+
targetUserId: z.ZodString;
|
|
169
|
+
blocked: z.ZodBoolean;
|
|
170
|
+
}, z.core.$strip>;
|
|
171
|
+
export type ToggleBlockResult = z.infer<typeof toggleBlockResultSchema>;
|
|
172
|
+
//# sourceMappingURL=social.d.ts.map
|
|
@@ -0,0 +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;;;;;;;;;;;;;;;;;;;;;;;;;;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
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gymmonk-schema — Social graph (follow)
|
|
3
|
+
* ======================================
|
|
4
|
+
* The follow relationship and the people list it feeds.
|
|
5
|
+
*
|
|
6
|
+
* A follow is UNILATERAL: one row, one direction, no acceptance step. A mutual
|
|
7
|
+
* follow is two independent rows. That is what makes "do I follow them" and
|
|
8
|
+
* "do they follow me" separately answerable, and therefore what makes
|
|
9
|
+
* "Follow back" a state the UI can offer at all.
|
|
10
|
+
*
|
|
11
|
+
* @module gymmonk-schema/social
|
|
12
|
+
*/
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
import { isoDateTimeSchema, objectIdSchema } from './common.js';
|
|
15
|
+
// ─── Viewer state ────────────────────────────────────────────────────────────
|
|
16
|
+
/**
|
|
17
|
+
* How the viewer stands with one other person.
|
|
18
|
+
*
|
|
19
|
+
* Both directions are carried, and both timestamps with them. `followedYouAt`
|
|
20
|
+
* is what the Followers list shows as "· 2w", and `followsViewer` is the only
|
|
21
|
+
* thing that distinguishes a "Follow" button from a "Follow back" button.
|
|
22
|
+
*/
|
|
23
|
+
export const viewerSocialStateSchema = z.object({
|
|
24
|
+
/** The viewer follows them. */
|
|
25
|
+
following: z.boolean(),
|
|
26
|
+
/** They follow the viewer. */
|
|
27
|
+
followsViewer: z.boolean(),
|
|
28
|
+
/** When the viewer started following them. Null when they do not. */
|
|
29
|
+
followedAt: isoDateTimeSchema.nullable(),
|
|
30
|
+
/** When they started following the viewer. Null when they do not. */
|
|
31
|
+
followedYouAt: isoDateTimeSchema.nullable(),
|
|
32
|
+
});
|
|
33
|
+
/** Nobody follows anybody. The shape a missing lookup falls back to. */
|
|
34
|
+
export const EMPTY_VIEWER_SOCIAL_STATE = {
|
|
35
|
+
following: false,
|
|
36
|
+
followsViewer: false,
|
|
37
|
+
followedAt: null,
|
|
38
|
+
followedYouAt: null,
|
|
39
|
+
};
|
|
40
|
+
// ─── Mutuals ─────────────────────────────────────────────────────────────────
|
|
41
|
+
/**
|
|
42
|
+
* "Followed by Priya, Rahul and 3 more" — the people the viewer and this person
|
|
43
|
+
* both follow.
|
|
44
|
+
*
|
|
45
|
+
* `count` is the TOTAL; `avatars` and `sampleNames` carry only the handful
|
|
46
|
+
* actually rendered. Sending every mutual so the client can count them would
|
|
47
|
+
* put an unbounded array on a list row for one line of secondary text.
|
|
48
|
+
*/
|
|
49
|
+
export const mutualsSchema = z.object({
|
|
50
|
+
count: z.number().int().nonnegative(),
|
|
51
|
+
/** Up to three, for the stacked faces. An entry is null when that person has no photo. */
|
|
52
|
+
avatars: z.array(z.string().nullable()),
|
|
53
|
+
/** Up to three names, for the sentence. */
|
|
54
|
+
sampleNames: z.array(z.string()),
|
|
55
|
+
});
|
|
56
|
+
/** How many faces and names a mutuals line carries. */
|
|
57
|
+
export const MUTUALS_SAMPLE_SIZE = 3;
|
|
58
|
+
// ─── People list ─────────────────────────────────────────────────────────────
|
|
59
|
+
/**
|
|
60
|
+
* Why this person is in the viewer's list.
|
|
61
|
+
*
|
|
62
|
+
* `gym` at the viewer's gym, not followed
|
|
63
|
+
* `following` followed by the viewer, and NOT at their gym any more
|
|
64
|
+
* `both` at the gym AND followed
|
|
65
|
+
*
|
|
66
|
+
* The distinction is load-bearing rather than cosmetic. Messaging is gated on
|
|
67
|
+
* sharing a gym, so a `following` row must not offer a Message button the
|
|
68
|
+
* backend will refuse. jansathi has no equivalent because its People tab is the
|
|
69
|
+
* whole community and there is no second axis to be on the wrong side of.
|
|
70
|
+
*/
|
|
71
|
+
export const buddyRelationSchema = z.enum(['gym', 'following', 'both']);
|
|
72
|
+
/** Which slice of the list the caller asked for. */
|
|
73
|
+
export const buddyFilterSchema = z.enum(['all', 'gym', 'following', 'followers']);
|
|
74
|
+
export const BUDDY_FILTER_VALUES = ['all', 'gym', 'following', 'followers'];
|
|
75
|
+
/**
|
|
76
|
+
* One person as a peer is allowed to see them.
|
|
77
|
+
*
|
|
78
|
+
* Deliberately NOT derived from the owner's member projection. That one carries
|
|
79
|
+
* phone numbers, addresses, emergency contacts, dues and attendance, and the
|
|
80
|
+
* only reliable way to guarantee none of it reaches a peer is for this to be a
|
|
81
|
+
* separate shape rather than the same one with fields removed.
|
|
82
|
+
*/
|
|
83
|
+
export const buddySchema = z.object({
|
|
84
|
+
/** The USER id. This is who you follow and message, not a profile row. */
|
|
85
|
+
userId: objectIdSchema,
|
|
86
|
+
name: z.string(),
|
|
87
|
+
avatarUrl: z.string().nullable(),
|
|
88
|
+
/** "Member since Mar 2025", or null for someone with no membership history here. */
|
|
89
|
+
memberSince: z.string().nullable(),
|
|
90
|
+
/**
|
|
91
|
+
* When this person was last seen in the app. Null if never.
|
|
92
|
+
*
|
|
93
|
+
* Carried so the list can offer a "Recently active" sort. It is deliberately
|
|
94
|
+
* a raw instant rather than a rendered "2h ago": the client is the only side
|
|
95
|
+
* that knows what time it is where the reader is sitting, and a string
|
|
96
|
+
* formatted on the server goes stale the moment it is cached.
|
|
97
|
+
*/
|
|
98
|
+
lastActiveAt: isoDateTimeSchema.nullable(),
|
|
99
|
+
relation: buddyRelationSchema,
|
|
100
|
+
viewer: viewerSocialStateSchema,
|
|
101
|
+
mutuals: mutualsSchema.nullable(),
|
|
102
|
+
});
|
|
103
|
+
/** `GET /api/v1/members/at-my-gym?filter=` */
|
|
104
|
+
export const buddyListQuerySchema = z.object({
|
|
105
|
+
filter: buddyFilterSchema.default('all'),
|
|
106
|
+
});
|
|
107
|
+
// ─── Toggle ──────────────────────────────────────────────────────────────────
|
|
108
|
+
/**
|
|
109
|
+
* The state AFTER a toggle.
|
|
110
|
+
*
|
|
111
|
+
* A toggle rather than separate follow and unfollow endpoints: a button acting
|
|
112
|
+
* on stale state can send the wrong one, and then the server has to decide
|
|
113
|
+
* whether a double unfollow is an error. A toggle has one meaning whatever the
|
|
114
|
+
* client believed, and this response says what is now true.
|
|
115
|
+
*/
|
|
116
|
+
export const toggleFollowResultSchema = z.object({
|
|
117
|
+
targetUserId: objectIdSchema,
|
|
118
|
+
following: z.boolean(),
|
|
119
|
+
followerCount: z.number().int().nonnegative(),
|
|
120
|
+
});
|
|
121
|
+
/** `GET /api/v1/users/:id/social` — one person's relationship plus their totals. */
|
|
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
|
+
*/
|
|
134
|
+
person: buddySchema,
|
|
135
|
+
followerCount: z.number().int().nonnegative(),
|
|
136
|
+
followingCount: z.number().int().nonnegative(),
|
|
137
|
+
});
|
|
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. */
|
|
143
|
+
export const blockedUserSchema = z.object({
|
|
144
|
+
userId: objectIdSchema,
|
|
145
|
+
name: z.string(),
|
|
146
|
+
avatarUrl: z.string().nullable(),
|
|
147
|
+
blockedAt: isoDateTimeSchema,
|
|
148
|
+
});
|
|
149
|
+
/** `POST /api/v1/users/:id/block` — the state AFTER the toggle. */
|
|
150
|
+
export const toggleBlockResultSchema = z.object({
|
|
151
|
+
targetUserId: objectIdSchema,
|
|
152
|
+
blocked: z.boolean(),
|
|
153
|
+
});
|
|
154
|
+
//# sourceMappingURL=social.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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"]}
|
package/dist/workout-plan.d.ts
CHANGED
|
@@ -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
|
|
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"}
|
package/dist/workout-plan.js
CHANGED
|
@@ -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,
|
package/dist/workout-plan.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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 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,49 +1,49 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
2
|
+
"name": "gymmonk-schema",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -p tsconfig.json",
|
|
23
|
+
"type-check": "tsc --noEmit",
|
|
24
|
+
"lint": "biome lint .",
|
|
25
|
+
"format": "biome format --write .",
|
|
26
|
+
"check": "npm run type-check && biome check --write .",
|
|
27
|
+
"check:staged": "biome check --staged --write --no-errors-on-unmatched",
|
|
28
|
+
"prepublishOnly": "npm run build && npm run check"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"zod",
|
|
32
|
+
"schema",
|
|
33
|
+
"gymmonk",
|
|
34
|
+
"gym",
|
|
35
|
+
"fitness",
|
|
36
|
+
"typescript"
|
|
37
|
+
],
|
|
38
|
+
"author": "Hari",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"zod": "^4.4.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@biomejs/biome": "^2.4.2",
|
|
45
|
+
"@types/node": "^24.10.1",
|
|
46
|
+
"typescript": "^5.9.3",
|
|
47
|
+
"zod": "^4.4.3"
|
|
48
|
+
}
|
|
49
49
|
}
|