gymmonk-schema 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/common.d.ts CHANGED
@@ -24,6 +24,12 @@ export declare const paginationSchema: z.ZodObject<{
24
24
  limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
25
25
  }, z.core.$strip>;
26
26
  export type Pagination = z.infer<typeof paginationSchema>;
27
+ /**
28
+ * The raw patterns, exported so the onboarding FORM schemas validate a typed
29
+ * value exactly the way the API schemas below do — one rule, both sides.
30
+ */
31
+ export declare const PHONE_REGEX: RegExp;
32
+ export declare const PINCODE_REGEX: RegExp;
27
33
  /**
28
34
  * A bare 10-digit Indian mobile number. Stored RAW (no `+91`) app-wide — the
29
35
  * country code is display-only. Mirrors the jansathi phone-number standard.
@@ -1 +1 @@
1
- {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../src/common.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GAAI,OAAO,OAAO,KAAG,OAGvD,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,qBAAqB,GAAI,kBAAe,gDAC8B,CAAC;AAIpF,kDAAkD;AAClD,eAAO,MAAM,cAAc,aAGgB,CAAC;AAC5C,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAI5D,4DAA4D;AAC5D,eAAO,MAAM,gBAAgB;;;iBAG3B,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAI1D;;;GAGG;AACH,eAAO,MAAM,WAAW,aAGwC,CAAC;AACjE,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEhD,6DAA6D;AAC7D,eAAO,MAAM,mBAAmB,6CAA+D,CAAC;AAEhG,wCAAwC;AACxC,eAAO,MAAM,aAAa,aAG0B,CAAC;AACrD,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD,6EAA6E;AAC7E,eAAO,MAAM,mBAAmB,4CAG/B,CAAC;AAIF,4DAA4D;AAC5D,eAAO,MAAM,aAAa,aAGoC,CAAC;AAC/D,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD,kEAAkE;AAClE,eAAO,MAAM,iBAAiB,kBAAmC,CAAC;AAClE,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,sCAAsC;AACtC,eAAO,MAAM,eAAe,aAGmC,CAAC;AAChE,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAIxD;;;GAGG;AACH,eAAO,MAAM,cAAc,aAA0B,CAAC;AACtD,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC"}
1
+ {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../src/common.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GAAI,OAAO,OAAO,KAAG,OAGvD,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,qBAAqB,GAAI,kBAAe,gDAC8B,CAAC;AAIpF,kDAAkD;AAClD,eAAO,MAAM,cAAc,aAGgB,CAAC;AAC5C,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAI5D,4DAA4D;AAC5D,eAAO,MAAM,gBAAgB;;;iBAG3B,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAI1D;;;GAGG;AACH,eAAO,MAAM,WAAW,QAAiB,CAAC;AAC1C,eAAO,MAAM,aAAa,QAAY,CAAC;AAEvC;;;GAGG;AACH,eAAO,MAAM,WAAW,aAGqC,CAAC;AAC9D,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEhD,6DAA6D;AAC7D,eAAO,MAAM,mBAAmB,6CAA+D,CAAC;AAEhG,wCAAwC;AACxC,eAAO,MAAM,aAAa,aAG8B,CAAC;AACzD,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD,6EAA6E;AAC7E,eAAO,MAAM,mBAAmB,4CAG/B,CAAC;AAIF,4DAA4D;AAC5D,eAAO,MAAM,aAAa,aAGoC,CAAC;AAC/D,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD,kEAAkE;AAClE,eAAO,MAAM,iBAAiB,kBAAmC,CAAC;AAClE,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,sCAAsC;AACtC,eAAO,MAAM,eAAe,aAGmC,CAAC;AAChE,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAIxD;;;GAGG;AACH,eAAO,MAAM,cAAc,aAA0B,CAAC;AACtD,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC"}
package/dist/common.js CHANGED
@@ -33,6 +33,12 @@ export const paginationSchema = z.object({
33
33
  limit: z.coerce.number().int().min(1).max(100).default(20),
34
34
  });
35
35
  // ─── Contact primitives (India) ──────────────────────────────────────────────
36
+ /**
37
+ * The raw patterns, exported so the onboarding FORM schemas validate a typed
38
+ * value exactly the way the API schemas below do — one rule, both sides.
39
+ */
40
+ export const PHONE_REGEX = /^[6-9]\d{9}$/;
41
+ export const PINCODE_REGEX = /^\d{6}$/;
36
42
  /**
37
43
  * A bare 10-digit Indian mobile number. Stored RAW (no `+91`) app-wide — the
38
44
  * country code is display-only. Mirrors the jansathi phone-number standard.
@@ -40,14 +46,14 @@ export const paginationSchema = z.object({
40
46
  export const phoneSchema = z
41
47
  .string()
42
48
  .trim()
43
- .regex(/^[6-9]\d{9}$/, 'Enter a valid 10-digit mobile number');
49
+ .regex(PHONE_REGEX, 'Enter a valid 10-digit mobile number');
44
50
  /** Optional phone — empty/whitespace becomes `undefined`. */
45
51
  export const optionalPhoneSchema = z.preprocess(emptyStringToUndefined, phoneSchema.optional());
46
52
  /** A 6-digit Indian postal PIN code. */
47
53
  export const pincodeSchema = z
48
54
  .string()
49
55
  .trim()
50
- .regex(/^\d{6}$/, 'Enter a valid 6-digit pincode');
56
+ .regex(PINCODE_REGEX, 'Enter a valid 6-digit pincode');
51
57
  /** Optional email — empty/whitespace becomes `undefined`, else validated. */
52
58
  export const optionalEmailSchema = z.preprocess(emptyStringToUndefined, z.email('Enter a valid email').optional());
53
59
  // ─── Date & time primitives ──────────────────────────────────────────────────
@@ -1 +1 @@
1
- {"version":3,"file":"common.js","sourceRoot":"","sources":["../src/common.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,gFAAgF;AAEhF;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,KAAc,EAAW,EAAE;IAChE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IACvE,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF,yEAAyE;AACzE,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,SAAS,GAAG,GAAG,EAAE,EAAE,CACvD,CAAC,CAAC,UAAU,CAAC,sBAAsB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;AAEpF,gFAAgF;AAEhF,kDAAkD;AAClD,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC;KAC5B,MAAM,EAAE;KACR,IAAI,EAAE;KACN,KAAK,CAAC,mBAAmB,EAAE,YAAY,CAAC,CAAC;AAG5C,gFAAgF;AAEhF,4DAA4D;AAC5D,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/C,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;CAC3D,CAAC,CAAC;AAGH,gFAAgF;AAEhF;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC;KACzB,MAAM,EAAE;KACR,IAAI,EAAE;KACN,KAAK,CAAC,cAAc,EAAE,sCAAsC,CAAC,CAAC;AAGjE,6DAA6D;AAC7D,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,UAAU,CAAC,sBAAsB,EAAE,WAAW,CAAC,QAAQ,EAAE,CAAC,CAAC;AAEhG,wCAAwC;AACxC,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC;KAC3B,MAAM,EAAE;KACR,IAAI,EAAE;KACN,KAAK,CAAC,SAAS,EAAE,+BAA+B,CAAC,CAAC;AAGrD,6EAA6E;AAC7E,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,UAAU,CAC7C,sBAAsB,EACtB,CAAC,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC,QAAQ,EAAE,CAC1C,CAAC;AAEF,gFAAgF;AAEhF,4DAA4D;AAC5D,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC;KAC3B,MAAM,EAAE;KACR,IAAI,EAAE;KACN,KAAK,CAAC,qBAAqB,EAAE,6BAA6B,CAAC,CAAC;AAG/D,kEAAkE;AAClE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;AAGlE,sCAAsC;AACtC,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC;KAC7B,MAAM,EAAE;KACR,IAAI,EAAE;KACN,KAAK,CAAC,2BAA2B,EAAE,wBAAwB,CAAC,CAAC;AAGhE,gFAAgF;AAEhF;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC","sourcesContent":["/**\n * gymmonk-schema — Shared helpers and primitives\n * ==============================================\n * The stable building blocks reused across every GymMonk domain schema.\n * Mirrors the `dating-schema` / `near-me-schema` idiom: pure Zod, const-object\n * enums fed to `z.enum(...)`, NodeNext `.js` import specifiers.\n *\n * @module gymmonk-schema/common\n */\n\nimport { z } from 'zod';\n\n// ─── String helpers ──────────────────────────────────────────────────────────\n\n/**\n * Converts empty / whitespace-only strings to `undefined`\n * so Zod `.optional()` treats them as \"not provided\".\n */\nexport const emptyStringToUndefined = (value: unknown): unknown => {\n if (typeof value === 'string' && value.trim() === '') return undefined;\n return value;\n};\n\n/** Optional trimmed string. Empty strings are coerced to `undefined`. */\nexport const optionalTrimmedString = (maxLength = 100) =>\n z.preprocess(emptyStringToUndefined, z.string().trim().max(maxLength).optional());\n\n// ─── Identifiers ─────────────────────────────────────────────────────────────\n\n/** A 24-character hex MongoDB ObjectId string. */\nexport const objectIdSchema = z\n .string()\n .trim()\n .regex(/^[0-9a-fA-F]{24}$/, 'Invalid id');\nexport type ObjectIdString = z.infer<typeof objectIdSchema>;\n\n// ─── Pagination ──────────────────────────────────────────────────────────────\n\n/** Pagination query params shared across list endpoints. */\nexport const paginationSchema = z.object({\n page: z.coerce.number().int().min(1).default(1),\n limit: z.coerce.number().int().min(1).max(100).default(20),\n});\nexport type Pagination = z.infer<typeof paginationSchema>;\n\n// ─── Contact primitives (India) ──────────────────────────────────────────────\n\n/**\n * A bare 10-digit Indian mobile number. Stored RAW (no `+91`) app-wide — the\n * country code is display-only. Mirrors the jansathi phone-number standard.\n */\nexport const phoneSchema = z\n .string()\n .trim()\n .regex(/^[6-9]\\d{9}$/, 'Enter a valid 10-digit mobile number');\nexport type Phone = z.infer<typeof phoneSchema>;\n\n/** Optional phone — empty/whitespace becomes `undefined`. */\nexport const optionalPhoneSchema = z.preprocess(emptyStringToUndefined, phoneSchema.optional());\n\n/** A 6-digit Indian postal PIN code. */\nexport const pincodeSchema = z\n .string()\n .trim()\n .regex(/^\\d{6}$/, 'Enter a valid 6-digit pincode');\nexport type Pincode = z.infer<typeof pincodeSchema>;\n\n/** Optional email — empty/whitespace becomes `undefined`, else validated. */\nexport const optionalEmailSchema = z.preprocess(\n emptyStringToUndefined,\n z.email('Enter a valid email').optional(),\n);\n\n// ─── Date & time primitives ──────────────────────────────────────────────────\n\n/** A calendar date with no time component, `YYYY-MM-DD`. */\nexport const isoDateSchema = z\n .string()\n .trim()\n .regex(/^\\d{4}-\\d{2}-\\d{2}$/, 'Expected an YYYY-MM-DD date');\nexport type IsoDate = z.infer<typeof isoDateSchema>;\n\n/** A full ISO-8601 timestamp, e.g. `2026-08-06T09:30:00.000Z`. */\nexport const isoDateTimeSchema = z.iso.datetime({ offset: true });\nexport type IsoDateTime = z.infer<typeof isoDateTimeSchema>;\n\n/** Time of day in 24-hour `HH:MM`. */\nexport const timeOfDaySchema = z\n .string()\n .trim()\n .regex(/^([01]\\d|2[0-3]):[0-5]\\d$/, 'Expected an HH:MM time');\nexport type TimeOfDay = z.infer<typeof timeOfDaySchema>;\n\n// ─── Money ───────────────────────────────────────────────────────────────────\n\n/**\n * A whole-rupee (INR) amount. GymMonk prices are whole rupees; format with a\n * `₹` and grouping at the edge, never store the formatted string.\n */\nexport const priceInrSchema = z.number().int().min(0);\nexport type PriceInr = z.infer<typeof priceInrSchema>;\n"]}
1
+ {"version":3,"file":"common.js","sourceRoot":"","sources":["../src/common.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Shared helpers and primitives\n * ==============================================\n * The stable building blocks reused across every GymMonk domain schema.\n * Mirrors the `dating-schema` / `near-me-schema` idiom: pure Zod, const-object\n * enums fed to `z.enum(...)`, NodeNext `.js` import specifiers.\n *\n * @module gymmonk-schema/common\n */\n\nimport { z } from 'zod';\n\n// ─── String helpers ──────────────────────────────────────────────────────────\n\n/**\n * Converts empty / whitespace-only strings to `undefined`\n * so Zod `.optional()` treats them as \"not provided\".\n */\nexport const emptyStringToUndefined = (value: unknown): unknown => {\n if (typeof value === 'string' && value.trim() === '') return undefined;\n return value;\n};\n\n/** Optional trimmed string. Empty strings are coerced to `undefined`. */\nexport const optionalTrimmedString = (maxLength = 100) =>\n z.preprocess(emptyStringToUndefined, z.string().trim().max(maxLength).optional());\n\n// ─── Identifiers ─────────────────────────────────────────────────────────────\n\n/** A 24-character hex MongoDB ObjectId string. */\nexport const objectIdSchema = z\n .string()\n .trim()\n .regex(/^[0-9a-fA-F]{24}$/, 'Invalid id');\nexport type ObjectIdString = z.infer<typeof objectIdSchema>;\n\n// ─── Pagination ──────────────────────────────────────────────────────────────\n\n/** Pagination query params shared across list endpoints. */\nexport const paginationSchema = z.object({\n page: z.coerce.number().int().min(1).default(1),\n limit: z.coerce.number().int().min(1).max(100).default(20),\n});\nexport type Pagination = z.infer<typeof paginationSchema>;\n\n// ─── Contact primitives (India) ──────────────────────────────────────────────\n\n/**\n * The raw patterns, exported so the onboarding FORM schemas validate a typed\n * value exactly the way the API schemas below do — one rule, both sides.\n */\nexport const PHONE_REGEX = /^[6-9]\\d{9}$/;\nexport const PINCODE_REGEX = /^\\d{6}$/;\n\n/**\n * A bare 10-digit Indian mobile number. Stored RAW (no `+91`) app-wide — the\n * country code is display-only. Mirrors the jansathi phone-number standard.\n */\nexport const phoneSchema = z\n .string()\n .trim()\n .regex(PHONE_REGEX, 'Enter a valid 10-digit mobile number');\nexport type Phone = z.infer<typeof phoneSchema>;\n\n/** Optional phone — empty/whitespace becomes `undefined`. */\nexport const optionalPhoneSchema = z.preprocess(emptyStringToUndefined, phoneSchema.optional());\n\n/** A 6-digit Indian postal PIN code. */\nexport const pincodeSchema = z\n .string()\n .trim()\n .regex(PINCODE_REGEX, 'Enter a valid 6-digit pincode');\nexport type Pincode = z.infer<typeof pincodeSchema>;\n\n/** Optional email — empty/whitespace becomes `undefined`, else validated. */\nexport const optionalEmailSchema = z.preprocess(\n emptyStringToUndefined,\n z.email('Enter a valid email').optional(),\n);\n\n// ─── Date & time primitives ──────────────────────────────────────────────────\n\n/** A calendar date with no time component, `YYYY-MM-DD`. */\nexport const isoDateSchema = z\n .string()\n .trim()\n .regex(/^\\d{4}-\\d{2}-\\d{2}$/, 'Expected an YYYY-MM-DD date');\nexport type IsoDate = z.infer<typeof isoDateSchema>;\n\n/** A full ISO-8601 timestamp, e.g. `2026-08-06T09:30:00.000Z`. */\nexport const isoDateTimeSchema = z.iso.datetime({ offset: true });\nexport type IsoDateTime = z.infer<typeof isoDateTimeSchema>;\n\n/** Time of day in 24-hour `HH:MM`. */\nexport const timeOfDaySchema = z\n .string()\n .trim()\n .regex(/^([01]\\d|2[0-3]):[0-5]\\d$/, 'Expected an HH:MM time');\nexport type TimeOfDay = z.infer<typeof timeOfDaySchema>;\n\n// ─── Money ───────────────────────────────────────────────────────────────────\n\n/**\n * A whole-rupee (INR) amount. GymMonk prices are whole rupees; format with a\n * `₹` and grouping at the edge, never store the formatted string.\n */\nexport const priceInrSchema = z.number().int().min(0);\nexport type PriceInr = z.infer<typeof priceInrSchema>;\n"]}
package/dist/file.d.ts ADDED
@@ -0,0 +1,116 @@
1
+ /**
2
+ * gymmonk-schema — File uploads
3
+ * =============================
4
+ * Contracts for the signed-URL upload flow. GymMonk never streams file bytes
5
+ * through the API: the browser asks the backend for a short-lived v4 signed
6
+ * PUT URL, uploads straight to Google Cloud Storage, and stores the returned
7
+ * PERMANENT public URL.
8
+ *
9
+ * The download URL is deliberately NOT signed. A signed read URL expires,
10
+ * which would rot every image reference already written to Mongo; the bucket
11
+ * instead grants `allUsers` object-viewer and we store the plain public URL.
12
+ *
13
+ * Mirrors reform-backend's `shared/schemas/file.schema.ts`.
14
+ *
15
+ * @module gymmonk-schema/file
16
+ */
17
+ import { z } from 'zod';
18
+ /**
19
+ * Every folder an upload may target. This is an allow-list, not a hint: the
20
+ * backend rejects anything outside it, so a compromised client cannot scatter
21
+ * objects across the bucket. Adding a new upload surface means adding it here
22
+ * FIRST, then publishing the package.
23
+ *
24
+ * `userId` is appended by the backend, so the effective path is
25
+ * `<folder>/<userId>/<timestamp>-<random><ext>`. That per-user prefix is what
26
+ * makes ownership checkable on cleanup.
27
+ */
28
+ export declare const UPLOAD_FOLDERS: readonly ["profile-photos", "center-photos", "logos", "member-documents", "equipment-photos", "announcement-media", "exercise-media", "progress-photos", "feedback-media"];
29
+ export declare const uploadFolderSchema: z.ZodEnum<{
30
+ "profile-photos": "profile-photos";
31
+ "center-photos": "center-photos";
32
+ logos: "logos";
33
+ "member-documents": "member-documents";
34
+ "equipment-photos": "equipment-photos";
35
+ "announcement-media": "announcement-media";
36
+ "exercise-media": "exercise-media";
37
+ "progress-photos": "progress-photos";
38
+ "feedback-media": "feedback-media";
39
+ }>;
40
+ export type UploadFolder = z.infer<typeof uploadFolderSchema>;
41
+ /** MIME types the signed-URL endpoint will sign for. */
42
+ export declare const ALLOWED_UPLOAD_MIME_TYPES: readonly ["image/jpeg", "image/png", "image/webp", "image/gif", "application/pdf"];
43
+ export declare const uploadMimeTypeSchema: z.ZodEnum<{
44
+ "image/jpeg": "image/jpeg";
45
+ "image/png": "image/png";
46
+ "image/webp": "image/webp";
47
+ "image/gif": "image/gif";
48
+ "application/pdf": "application/pdf";
49
+ }>;
50
+ export type UploadMimeType = z.infer<typeof uploadMimeTypeSchema>;
51
+ /**
52
+ * Default per-file ceiling, in MB. Validation runs on the RAW pick, before the
53
+ * client-side optimizer shrinks it — a modern phone camera produces 8-12 MB
54
+ * JPEGs that compress to well under 1 MB, so a tighter cap would reject
55
+ * perfectly ordinary photos.
56
+ */
57
+ export declare const MAX_UPLOAD_SIZE_MB = 10;
58
+ /** Per-folder overrides. Documents may be larger scans than a photo. */
59
+ export declare const FOLDER_MAX_SIZE_MB: Partial<Record<UploadFolder, number>>;
60
+ /**
61
+ * Request body for `POST /api/v1/files/signed-url`.
62
+ *
63
+ * `fileName` is restricted to a safe charset because it becomes part of the GCS
64
+ * object path — the client sanitises before sending, and this rejects anything
65
+ * that slipped through (path separators, control characters, unicode tricks).
66
+ */
67
+ export declare const signedUploadUrlBodySchema: z.ZodObject<{
68
+ fileName: z.ZodString;
69
+ contentType: z.ZodEnum<{
70
+ "image/jpeg": "image/jpeg";
71
+ "image/png": "image/png";
72
+ "image/webp": "image/webp";
73
+ "image/gif": "image/gif";
74
+ "application/pdf": "application/pdf";
75
+ }>;
76
+ folder: z.ZodEnum<{
77
+ "profile-photos": "profile-photos";
78
+ "center-photos": "center-photos";
79
+ logos: "logos";
80
+ "member-documents": "member-documents";
81
+ "equipment-photos": "equipment-photos";
82
+ "announcement-media": "announcement-media";
83
+ "exercise-media": "exercise-media";
84
+ "progress-photos": "progress-photos";
85
+ "feedback-media": "feedback-media";
86
+ }>;
87
+ fileSize: z.ZodNumber;
88
+ }, z.core.$strip>;
89
+ export type SignedUploadUrlBody = z.infer<typeof signedUploadUrlBodySchema>;
90
+ /** Response of `POST /api/v1/files/signed-url`. */
91
+ export declare const signedUploadUrlResponseSchema: z.ZodObject<{
92
+ uploadUrl: z.ZodString;
93
+ downloadUrl: z.ZodString;
94
+ filePath: z.ZodString;
95
+ expiresAt: z.ZodISODateTime;
96
+ }, z.core.$strip>;
97
+ export type SignedUploadUrlResponse = z.infer<typeof signedUploadUrlResponseSchema>;
98
+ /**
99
+ * Body for `POST /api/v1/files/cleanup-uploads`. Called when a user removes a
100
+ * photo they uploaded but never saved, so the orphaned object does not sit in
101
+ * the bucket forever.
102
+ *
103
+ * Ownership is enforced per-path from the `userId` segment the backend embedded
104
+ * at signing time. Paths belonging to someone else are skipped silently rather
105
+ * than 403-ing, so one foreign entry cannot void a legitimate batch.
106
+ */
107
+ export declare const cleanupUploadsBodySchema: z.ZodObject<{
108
+ filePaths: z.ZodArray<z.ZodString>;
109
+ }, z.core.$strip>;
110
+ export type CleanupUploadsBody = z.infer<typeof cleanupUploadsBodySchema>;
111
+ export declare const cleanupUploadsResponseSchema: z.ZodObject<{
112
+ deleted: z.ZodNumber;
113
+ skipped: z.ZodNumber;
114
+ }, z.core.$strip>;
115
+ export type CleanupUploadsResponse = z.infer<typeof cleanupUploadsResponseSchema>;
116
+ //# sourceMappingURL=file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../src/file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,4KAmBjB,CAAC;AAEX,eAAO,MAAM,kBAAkB;;;;;;;;;;EAAyB,CAAC;AACzD,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAI9D,wDAAwD;AACxD,eAAO,MAAM,yBAAyB,oFAM5B,CAAC;AAEX,eAAO,MAAM,oBAAoB;;;;;;EAAoC,CAAC;AACtE,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAIlE;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,wEAAwE;AACxE,eAAO,MAAM,kBAAkB,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAEpE,CAAC;AAMF;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;iBA0BlC,CAAC;AAEL,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,mDAAmD;AACnD,eAAO,MAAM,6BAA6B;;;;;iBAUxC,CAAC;AAEH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAIpF;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB;;iBAEnC,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,eAAO,MAAM,4BAA4B;;;iBAGvC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC"}
package/dist/file.js ADDED
@@ -0,0 +1,137 @@
1
+ /**
2
+ * gymmonk-schema — File uploads
3
+ * =============================
4
+ * Contracts for the signed-URL upload flow. GymMonk never streams file bytes
5
+ * through the API: the browser asks the backend for a short-lived v4 signed
6
+ * PUT URL, uploads straight to Google Cloud Storage, and stores the returned
7
+ * PERMANENT public URL.
8
+ *
9
+ * The download URL is deliberately NOT signed. A signed read URL expires,
10
+ * which would rot every image reference already written to Mongo; the bucket
11
+ * instead grants `allUsers` object-viewer and we store the plain public URL.
12
+ *
13
+ * Mirrors reform-backend's `shared/schemas/file.schema.ts`.
14
+ *
15
+ * @module gymmonk-schema/file
16
+ */
17
+ import { z } from 'zod';
18
+ // ─── Folders ─────────────────────────────────────────────────────────────────
19
+ /**
20
+ * Every folder an upload may target. This is an allow-list, not a hint: the
21
+ * backend rejects anything outside it, so a compromised client cannot scatter
22
+ * objects across the bucket. Adding a new upload surface means adding it here
23
+ * FIRST, then publishing the package.
24
+ *
25
+ * `userId` is appended by the backend, so the effective path is
26
+ * `<folder>/<userId>/<timestamp>-<random><ext>`. That per-user prefix is what
27
+ * makes ownership checkable on cleanup.
28
+ */
29
+ export const UPLOAD_FOLDERS = [
30
+ /** Member + owner + staff avatars. Square, cropped round in the UI. */
31
+ 'profile-photos',
32
+ /** Gym/center gallery photos. Multi-select, uncropped. */
33
+ 'center-photos',
34
+ /** Organisation and center logos. */
35
+ 'logos',
36
+ /** Member ID proof / address proof captured at the front desk. */
37
+ 'member-documents',
38
+ /** Equipment photos in the gym inventory. */
39
+ 'equipment-photos',
40
+ /** Images attached to an announcement. */
41
+ 'announcement-media',
42
+ /** Custom exercise thumbnails added by a gym. */
43
+ 'exercise-media',
44
+ /** Progress photos a member uploads against a measurement entry. */
45
+ 'progress-photos',
46
+ /** Screenshots and photos attached to feedback or a complaint. */
47
+ 'feedback-media',
48
+ ];
49
+ export const uploadFolderSchema = z.enum(UPLOAD_FOLDERS);
50
+ // ─── Content types ───────────────────────────────────────────────────────────
51
+ /** MIME types the signed-URL endpoint will sign for. */
52
+ export const ALLOWED_UPLOAD_MIME_TYPES = [
53
+ 'image/jpeg',
54
+ 'image/png',
55
+ 'image/webp',
56
+ 'image/gif',
57
+ 'application/pdf',
58
+ ];
59
+ export const uploadMimeTypeSchema = z.enum(ALLOWED_UPLOAD_MIME_TYPES);
60
+ // ─── Size limits ─────────────────────────────────────────────────────────────
61
+ /**
62
+ * Default per-file ceiling, in MB. Validation runs on the RAW pick, before the
63
+ * client-side optimizer shrinks it — a modern phone camera produces 8-12 MB
64
+ * JPEGs that compress to well under 1 MB, so a tighter cap would reject
65
+ * perfectly ordinary photos.
66
+ */
67
+ export const MAX_UPLOAD_SIZE_MB = 10;
68
+ /** Per-folder overrides. Documents may be larger scans than a photo. */
69
+ export const FOLDER_MAX_SIZE_MB = {
70
+ 'member-documents': 20,
71
+ };
72
+ const HARD_CAP_MB = Math.max(MAX_UPLOAD_SIZE_MB, ...Object.values(FOLDER_MAX_SIZE_MB));
73
+ // ─── Signed upload URL ───────────────────────────────────────────────────────
74
+ /**
75
+ * Request body for `POST /api/v1/files/signed-url`.
76
+ *
77
+ * `fileName` is restricted to a safe charset because it becomes part of the GCS
78
+ * object path — the client sanitises before sending, and this rejects anything
79
+ * that slipped through (path separators, control characters, unicode tricks).
80
+ */
81
+ export const signedUploadUrlBodySchema = z
82
+ .object({
83
+ fileName: z
84
+ .string()
85
+ .min(1)
86
+ .max(255)
87
+ .regex(/^[a-zA-Z0-9_.-]+$/, 'Invalid file name'),
88
+ contentType: uploadMimeTypeSchema,
89
+ folder: uploadFolderSchema,
90
+ fileSize: z
91
+ .number()
92
+ .int()
93
+ .min(1)
94
+ .max(HARD_CAP_MB * 1024 * 1024, `File must be smaller than ${HARD_CAP_MB}MB`),
95
+ })
96
+ .superRefine((data, ctx) => {
97
+ // Tighten to the per-folder cap. The outer `.max` is only the widest
98
+ // ceiling any folder allows; this is the one that actually applies.
99
+ const capMb = FOLDER_MAX_SIZE_MB[data.folder] ?? MAX_UPLOAD_SIZE_MB;
100
+ if (data.fileSize > capMb * 1024 * 1024) {
101
+ ctx.addIssue({
102
+ code: 'custom',
103
+ path: ['fileSize'],
104
+ message: `File must be smaller than ${capMb}MB for ${data.folder}`,
105
+ });
106
+ }
107
+ });
108
+ /** Response of `POST /api/v1/files/signed-url`. */
109
+ export const signedUploadUrlResponseSchema = z.object({
110
+ /** Short-lived v4 signed URL — PUT the bytes here. */
111
+ uploadUrl: z.string(),
112
+ /** Permanent public URL. This is what gets stored in Mongo. */
113
+ downloadUrl: z.string(),
114
+ /** Object path inside the bucket, without the bucket prefix. Used to
115
+ * delete an object the user abandoned before saving. */
116
+ filePath: z.string(),
117
+ /** When `uploadUrl` stops working. */
118
+ expiresAt: z.iso.datetime(),
119
+ });
120
+ // ─── Cleanup ─────────────────────────────────────────────────────────────────
121
+ /**
122
+ * Body for `POST /api/v1/files/cleanup-uploads`. Called when a user removes a
123
+ * photo they uploaded but never saved, so the orphaned object does not sit in
124
+ * the bucket forever.
125
+ *
126
+ * Ownership is enforced per-path from the `userId` segment the backend embedded
127
+ * at signing time. Paths belonging to someone else are skipped silently rather
128
+ * than 403-ing, so one foreign entry cannot void a legitimate batch.
129
+ */
130
+ export const cleanupUploadsBodySchema = z.object({
131
+ filePaths: z.array(z.string().min(1)).min(1).max(20),
132
+ });
133
+ export const cleanupUploadsResponseSchema = z.object({
134
+ deleted: z.number().int().min(0),
135
+ skipped: z.number().int().min(0),
136
+ });
137
+ //# sourceMappingURL=file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.js","sourceRoot":"","sources":["../src/file.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — File uploads\n * =============================\n * Contracts for the signed-URL upload flow. GymMonk never streams file bytes\n * through the API: the browser asks the backend for a short-lived v4 signed\n * PUT URL, uploads straight to Google Cloud Storage, and stores the returned\n * PERMANENT public URL.\n *\n * The download URL is deliberately NOT signed. A signed read URL expires,\n * which would rot every image reference already written to Mongo; the bucket\n * instead grants `allUsers` object-viewer and we store the plain public URL.\n *\n * Mirrors reform-backend's `shared/schemas/file.schema.ts`.\n *\n * @module gymmonk-schema/file\n */\n\nimport { z } from 'zod';\n\n// ─── Folders ─────────────────────────────────────────────────────────────────\n\n/**\n * Every folder an upload may target. This is an allow-list, not a hint: the\n * backend rejects anything outside it, so a compromised client cannot scatter\n * objects across the bucket. Adding a new upload surface means adding it here\n * FIRST, then publishing the package.\n *\n * `userId` is appended by the backend, so the effective path is\n * `<folder>/<userId>/<timestamp>-<random><ext>`. That per-user prefix is what\n * makes ownership checkable on cleanup.\n */\nexport const UPLOAD_FOLDERS = [\n /** Member + owner + staff avatars. Square, cropped round in the UI. */\n 'profile-photos',\n /** Gym/center gallery photos. Multi-select, uncropped. */\n 'center-photos',\n /** Organisation and center logos. */\n 'logos',\n /** Member ID proof / address proof captured at the front desk. */\n 'member-documents',\n /** Equipment photos in the gym inventory. */\n 'equipment-photos',\n /** Images attached to an announcement. */\n 'announcement-media',\n /** Custom exercise thumbnails added by a gym. */\n 'exercise-media',\n /** Progress photos a member uploads against a measurement entry. */\n 'progress-photos',\n /** Screenshots and photos attached to feedback or a complaint. */\n 'feedback-media',\n] as const;\n\nexport const uploadFolderSchema = z.enum(UPLOAD_FOLDERS);\nexport type UploadFolder = z.infer<typeof uploadFolderSchema>;\n\n// ─── Content types ───────────────────────────────────────────────────────────\n\n/** MIME types the signed-URL endpoint will sign for. */\nexport const ALLOWED_UPLOAD_MIME_TYPES = [\n 'image/jpeg',\n 'image/png',\n 'image/webp',\n 'image/gif',\n 'application/pdf',\n] as const;\n\nexport const uploadMimeTypeSchema = z.enum(ALLOWED_UPLOAD_MIME_TYPES);\nexport type UploadMimeType = z.infer<typeof uploadMimeTypeSchema>;\n\n// ─── Size limits ─────────────────────────────────────────────────────────────\n\n/**\n * Default per-file ceiling, in MB. Validation runs on the RAW pick, before the\n * client-side optimizer shrinks it — a modern phone camera produces 8-12 MB\n * JPEGs that compress to well under 1 MB, so a tighter cap would reject\n * perfectly ordinary photos.\n */\nexport const MAX_UPLOAD_SIZE_MB = 10;\n\n/** Per-folder overrides. Documents may be larger scans than a photo. */\nexport const FOLDER_MAX_SIZE_MB: Partial<Record<UploadFolder, number>> = {\n 'member-documents': 20,\n};\n\nconst HARD_CAP_MB = Math.max(MAX_UPLOAD_SIZE_MB, ...Object.values(FOLDER_MAX_SIZE_MB));\n\n// ─── Signed upload URL ───────────────────────────────────────────────────────\n\n/**\n * Request body for `POST /api/v1/files/signed-url`.\n *\n * `fileName` is restricted to a safe charset because it becomes part of the GCS\n * object path — the client sanitises before sending, and this rejects anything\n * that slipped through (path separators, control characters, unicode tricks).\n */\nexport const signedUploadUrlBodySchema = z\n .object({\n fileName: z\n .string()\n .min(1)\n .max(255)\n .regex(/^[a-zA-Z0-9_.-]+$/, 'Invalid file name'),\n contentType: uploadMimeTypeSchema,\n folder: uploadFolderSchema,\n fileSize: z\n .number()\n .int()\n .min(1)\n .max(HARD_CAP_MB * 1024 * 1024, `File must be smaller than ${HARD_CAP_MB}MB`),\n })\n .superRefine((data, ctx) => {\n // Tighten to the per-folder cap. The outer `.max` is only the widest\n // ceiling any folder allows; this is the one that actually applies.\n const capMb = FOLDER_MAX_SIZE_MB[data.folder] ?? MAX_UPLOAD_SIZE_MB;\n if (data.fileSize > capMb * 1024 * 1024) {\n ctx.addIssue({\n code: 'custom',\n path: ['fileSize'],\n message: `File must be smaller than ${capMb}MB for ${data.folder}`,\n });\n }\n });\n\nexport type SignedUploadUrlBody = z.infer<typeof signedUploadUrlBodySchema>;\n\n/** Response of `POST /api/v1/files/signed-url`. */\nexport const signedUploadUrlResponseSchema = z.object({\n /** Short-lived v4 signed URL — PUT the bytes here. */\n uploadUrl: z.string(),\n /** Permanent public URL. This is what gets stored in Mongo. */\n downloadUrl: z.string(),\n /** Object path inside the bucket, without the bucket prefix. Used to\n * delete an object the user abandoned before saving. */\n filePath: z.string(),\n /** When `uploadUrl` stops working. */\n expiresAt: z.iso.datetime(),\n});\n\nexport type SignedUploadUrlResponse = z.infer<typeof signedUploadUrlResponseSchema>;\n\n// ─── Cleanup ─────────────────────────────────────────────────────────────────\n\n/**\n * Body for `POST /api/v1/files/cleanup-uploads`. Called when a user removes a\n * photo they uploaded but never saved, so the orphaned object does not sit in\n * the bucket forever.\n *\n * Ownership is enforced per-path from the `userId` segment the backend embedded\n * at signing time. Paths belonging to someone else are skipped silently rather\n * than 403-ing, so one foreign entry cannot void a legitimate batch.\n */\nexport const cleanupUploadsBodySchema = z.object({\n filePaths: z.array(z.string().min(1)).min(1).max(20),\n});\n\nexport type CleanupUploadsBody = z.infer<typeof cleanupUploadsBodySchema>;\n\nexport const cleanupUploadsResponseSchema = z.object({\n deleted: z.number().int().min(0),\n skipped: z.number().int().min(0),\n});\n\nexport type CleanupUploadsResponse = z.infer<typeof cleanupUploadsResponseSchema>;\n"]}
package/dist/index.d.ts CHANGED
@@ -14,14 +14,17 @@ export * from './common.js';
14
14
  export * from './equipment.js';
15
15
  export * from './exercise.js';
16
16
  export * from './feedback.js';
17
+ export * from './file.js';
17
18
  export * from './home.js';
18
19
  export * from './insights.js';
19
20
  export * from './member.js';
20
21
  export * from './member-profile.js';
21
22
  export * from './membership.js';
22
23
  export * from './membership-plan.js';
24
+ export * from './messages.js';
23
25
  export * from './notification.js';
24
26
  export * from './onboarding.js';
27
+ export * from './onboarding-form.js';
25
28
  export * from './organisation.js';
26
29
  export * from './owner-profile.js';
27
30
  export * from './pricing.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAE5B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAE/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAE9B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAEhC,cAAc,sBAAsB,CAAC;AAErC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAEhC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,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;AAEH,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAE5B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAE/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAE9B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAE9B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAEhC,cAAc,sBAAsB,CAAC;AAErC,cAAc,eAAe,CAAC;AAE9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAEhC,cAAc,sBAAsB,CAAC;AAErC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,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"}
package/dist/index.js CHANGED
@@ -16,6 +16,8 @@ export * from './equipment.js';
16
16
  // ─── Training ────────────────────────────────────────────────────────────────
17
17
  export * from './exercise.js';
18
18
  export * from './feedback.js';
19
+ // ─── Uploads ─────────────────────────────────────────────────────────────────
20
+ export * from './file.js';
19
21
  export * from './home.js';
20
22
  export * from './insights.js';
21
23
  // ─── People ──────────────────────────────────────────────────────────────────
@@ -24,9 +26,13 @@ export * from './member-profile.js';
24
26
  export * from './membership.js';
25
27
  // ─── Membership ──────────────────────────────────────────────────────────────
26
28
  export * from './membership-plan.js';
29
+ // ─── API result messages + error codes ───────────────────────────────────────
30
+ export * from './messages.js';
27
31
  // ─── Engagement & analytics ──────────────────────────────────────────────────
28
32
  export * from './notification.js';
29
33
  export * from './onboarding.js';
34
+ // ─── Onboarding wizard FORM validation (client-side working shapes) ──────────
35
+ export * from './onboarding-form.js';
30
36
  // ─── Gym structure & onboarding ──────────────────────────────────────────────
31
37
  export * from './organisation.js';
32
38
  export * from './owner-profile.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,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,gFAAgF;AAChF,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,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,gFAAgF;AAChF,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,gFAAgF;AAChF,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,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\nexport * from './announcement.js';\nexport * from './center.js';\n// ─── Foundation ──────────────────────────────────────────────────────────────\nexport * from './common.js';\nexport * from './equipment.js';\n// ─── Training ────────────────────────────────────────────────────────────────\nexport * from './exercise.js';\nexport * from './feedback.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';\n// ─── Engagement & analytics ──────────────────────────────────────────────────\nexport * from './notification.js';\nexport * from './onboarding.js';\n// ─── Gym structure & onboarding ──────────────────────────────────────────────\nexport * from './organisation.js';\nexport * from './owner-profile.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,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,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,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,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,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\nexport * from './announcement.js';\nexport * from './center.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';\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';\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';\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"]}
@@ -0,0 +1,184 @@
1
+ /**
2
+ * gymmonk-schema — API result messages
3
+ * ====================================
4
+ * Single source of truth for every user-facing SUCCESS and ERROR message the
5
+ * product shows, plus the API error codes both sides key off.
6
+ *
7
+ * Why it lives here: the schema package is already the SSOT for data shapes,
8
+ * types and validation. Result copy is part of that contract — the backend
9
+ * raises a message, the client shows it, and neither should invent its own
10
+ * wording. Both repos import from here, so copy changes land in one place and
11
+ * can never drift between server and client.
12
+ *
13
+ * Usage:
14
+ * backend → `throw new BadRequestError(ERROR_MESSAGE.centerAddressRequired)`
15
+ * frontend → `showSuccess(...toast(SUCCESS_MESSAGE.profileUpdated))`
16
+ *
17
+ * @module gymmonk-schema/messages
18
+ */
19
+ /**
20
+ * Machine-readable error codes returned in the API envelope
21
+ * (`{ success: false, error: { code, message } }`). The client can branch on a
22
+ * code; the message is what a human reads.
23
+ */
24
+ export declare const ERROR_CODE: {
25
+ readonly INTERNAL_ERROR: "INTERNAL_ERROR";
26
+ readonly VALIDATION_ERROR: "VALIDATION_ERROR";
27
+ readonly NOT_FOUND: "NOT_FOUND";
28
+ readonly INVALID_TOKEN: "INVALID_TOKEN";
29
+ readonly TOKEN_EXPIRED: "TOKEN_EXPIRED";
30
+ readonly UNAUTHORIZED: "UNAUTHORIZED";
31
+ readonly FORBIDDEN: "FORBIDDEN";
32
+ readonly SESSION_INVALIDATED: "SESSION_INVALIDATED";
33
+ readonly USER_NOT_FOUND: "USER_NOT_FOUND";
34
+ readonly USER_ALREADY_EXISTS: "USER_ALREADY_EXISTS";
35
+ readonly INVALID_ROLE: "INVALID_ROLE";
36
+ readonly RATE_LIMITED: "RATE_LIMITED";
37
+ readonly RATE_LIMIT_EXCEEDED: "RATE_LIMIT_EXCEEDED";
38
+ readonly SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE";
39
+ };
40
+ export type ErrorCode = (typeof ERROR_CODE)[keyof typeof ERROR_CODE];
41
+ /** A result message: a headline and an optional quieter second line. */
42
+ export interface ResultMessage {
43
+ title: string;
44
+ description?: string;
45
+ }
46
+ /**
47
+ * Confirmation shown after a write succeeds, keyed by action. Interpolating
48
+ * entries are functions so the caller supplies the value without re-wording.
49
+ */
50
+ export declare const SUCCESS_MESSAGE: {
51
+ readonly memberOnboardingComplete: {
52
+ readonly title: "You're all set";
53
+ readonly description: "Welcome to GymMonk.";
54
+ };
55
+ readonly ownerOnboardingComplete: {
56
+ readonly title: "Your gym is ready";
57
+ readonly description: "Welcome to GymMonk.";
58
+ };
59
+ readonly profileUpdated: {
60
+ readonly title: "Profile updated";
61
+ };
62
+ readonly organisationUpdated: {
63
+ readonly title: "Organisation updated";
64
+ };
65
+ readonly centerAdded: {
66
+ readonly title: "Center added";
67
+ };
68
+ readonly centerUpdated: {
69
+ readonly title: "Center updated";
70
+ };
71
+ readonly membershipPlanCreated: {
72
+ readonly title: "Membership plan created";
73
+ };
74
+ readonly membershipUpdated: {
75
+ readonly title: "Membership updated";
76
+ };
77
+ readonly membershipRenewed: {
78
+ readonly title: "Membership renewed";
79
+ };
80
+ readonly membershipCancelled: {
81
+ readonly title: "Membership cancelled";
82
+ };
83
+ readonly membershipPauseRequested: {
84
+ readonly title: "Pause request sent";
85
+ readonly description: "Your gym will confirm the pause.";
86
+ };
87
+ readonly subscriptionUpdated: {
88
+ readonly title: "Subscription updated";
89
+ };
90
+ readonly workoutPlanCreated: {
91
+ readonly title: "Workout plan created";
92
+ };
93
+ readonly workoutCreated: {
94
+ readonly title: "Workout created";
95
+ };
96
+ readonly workoutPlanAssigned: {
97
+ readonly title: "Workout plan assigned";
98
+ };
99
+ readonly checkedIn: {
100
+ readonly title: "Checked in";
101
+ readonly description: "Your session has started.";
102
+ };
103
+ readonly checkedOut: {
104
+ readonly title: "Checked out";
105
+ readonly description: "Session saved.";
106
+ };
107
+ readonly feedbackSubmitted: {
108
+ readonly title: "Feedback sent";
109
+ readonly description: "Thank you for helping us improve.";
110
+ };
111
+ readonly complaintSubmitted: {
112
+ readonly title: "Complaint submitted";
113
+ readonly description: "Your gym will look into it.";
114
+ };
115
+ readonly announcementSent: {
116
+ readonly title: "Announcement sent";
117
+ };
118
+ readonly announcementScheduled: {
119
+ readonly title: "Announcement scheduled";
120
+ };
121
+ readonly announcementDraftSaved: {
122
+ readonly title: "Draft saved";
123
+ };
124
+ readonly mpinSet: {
125
+ readonly title: "PIN set";
126
+ };
127
+ readonly mpinReset: {
128
+ readonly title: "Your PIN has been reset.";
129
+ };
130
+ readonly mpinResetLinkSent: {
131
+ readonly title: "We've emailed you a reset link";
132
+ readonly description: "Check your inbox.";
133
+ };
134
+ };
135
+ export type SuccessMessageKey = keyof typeof SUCCESS_MESSAGE;
136
+ /** Success messages that name the thing they acted on. */
137
+ export declare const SUCCESS_MESSAGE_FOR: {
138
+ readonly memberAdded: (name: string) => ResultMessage;
139
+ readonly staffAdded: (name: string) => ResultMessage;
140
+ readonly inviteClaimed: (gymName: string) => ResultMessage;
141
+ readonly roleSwitched: (roleLabel: string) => ResultMessage;
142
+ };
143
+ /**
144
+ * Failure copy. Transport entries describe a request that never reached the
145
+ * API; the rest are domain rules the backend enforces and the client can also
146
+ * check up-front — same wording either way.
147
+ */
148
+ export declare const ERROR_MESSAGE: {
149
+ readonly generic: "Something went wrong. Please try again.";
150
+ readonly network: "Network error. Check your connection and try again.";
151
+ readonly timeout: "That took too long. Please try again.";
152
+ readonly sessionExpired: "Your session expired. Please sign in again.";
153
+ readonly formInvalid: "Please check the highlighted fields.";
154
+ readonly roleRequired: "Choose a role to continue";
155
+ readonly memberOnboardingFailed: "We could not save your details. Please try again.";
156
+ readonly ownerOnboardingFailed: "We could not create your gym. Please try again.";
157
+ readonly centerRequiredForPlan: "Pick the center this plan belongs to.";
158
+ readonly organisationAddressRequired: "Add your organisation address before creating a center.";
159
+ readonly noCenterForPlans: "No gym center found. Add a center before creating plans.";
160
+ readonly noCenterForWorkouts: "No gym center found. Add a center before creating workouts.";
161
+ readonly noCenterForAnnouncements: "No gym center found. Add a center before sending announcements.";
162
+ readonly planUnavailable: "That plan is not available right now.";
163
+ readonly workoutPlanRequired: "Choose a plan to assign";
164
+ readonly checkInFailed: "Could not check you in. Please try again.";
165
+ readonly checkOutFailed: "Could not check you out. Please try again.";
166
+ readonly feedbackRatingRequired: "Choose a rating to submit your feedback";
167
+ readonly complaintIssueRequired: "Choose the issue you are reporting";
168
+ readonly mpinIncorrect: "Incorrect PIN.";
169
+ readonly mpinVerifyFailed: "Couldn't verify. Try again.";
170
+ readonly mpinSetFailed: "Couldn't set your PIN. Try again.";
171
+ readonly mpinMismatch: "Those didn't match. Try again.";
172
+ readonly mpinResetLinkFailed: "Couldn't send the reset link. Try again.";
173
+ readonly mpinResetFailed: "Couldn't reset your PIN. Try again.";
174
+ readonly imageUploadFailed: "Could not upload that image. Please try again.";
175
+ readonly imageProcessFailed: "Could not process that image. Please try again.";
176
+ };
177
+ export type ErrorMessageKey = keyof typeof ERROR_MESSAGE;
178
+ /** Error messages that name a limit or value. */
179
+ export declare const ERROR_MESSAGE_FOR: {
180
+ readonly maxPhotos: (max: number) => string;
181
+ /** Role-switch PIN lockout, in whole minutes remaining. */
182
+ readonly mpinLockedOut: (minutes: number) => string;
183
+ };
184
+ //# sourceMappingURL=messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH;;;;GAIG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;CAwBb,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,OAAO,UAAU,CAAC,CAAC;AAIrE,wEAAwE;AACxE,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAID;;;GAGG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiDsB,CAAC;AAEnD,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,eAAe,CAAC;AAE7D,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB;iCACV,MAAM,KAAG,aAAa;gCAIvB,MAAM,KAAG,aAAa;sCAIhB,MAAM,KAAG,aAAa;uCAIrB,MAAM,KAAG,aAAa;CAGxC,CAAC;AAIX;;;;GAIG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+CiB,CAAC;AAE5C,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,aAAa,CAAC;AAEzD,iDAAiD;AACjD,eAAO,MAAM,iBAAiB;8BACX,MAAM,KAAG,MAAM;IAChC,2DAA2D;sCAClC,MAAM,KAAG,MAAM;CAEhC,CAAC"}