dink-pets-shared 1.0.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.
@@ -0,0 +1,43 @@
1
+ import { z } from "zod";
2
+ import { JwtToken, UserId } from "./ids.js";
3
+ import { UserRole } from "./roles.js";
4
+ // =============================================================================
5
+ // OAUTH2 TOKEN RESPONSE
6
+ // =============================================================================
7
+ // Validates the response from Descope's /oauth2/v1/token endpoint.
8
+ // Uses snake_case per OAuth2 spec (RFC 6749).
9
+ //
10
+ // access_token is the JWT we send to our server in Authorization headers,
11
+ // so it's branded as JwtToken for type safety.
12
+ export const OAuthTokenResponse = z.object({
13
+ access_token: JwtToken,
14
+ refresh_token: z.string().optional(),
15
+ id_token: z.string().optional(),
16
+ token_type: z.string().optional(),
17
+ expires_in: z.number().optional(),
18
+ });
19
+ // =============================================================================
20
+ // AUTH INFO SCHEMA (companion object pattern)
21
+ // =============================================================================
22
+ // Validates the claims we expect from a Descope JWT after it's been verified.
23
+ // This is the contract between Descope and our app - if Descope changes their
24
+ // claim structure, this schema will catch it at runtime with a clear error.
25
+ //
26
+ // Uses companion object pattern: AuthInfo is both a type and a Zod schema.
27
+ // - Use as type: `const info: AuthInfo = ...`
28
+ // - Use as schema: `AuthInfo.parse(...)`
29
+ //
30
+ // Standard OIDC claims:
31
+ // - sub: Subject identifier (Descope's user ID)
32
+ // - email: User's email address
33
+ // - name: User's display name (optional - not all auth methods provide it)
34
+ // - picture: User's avatar URL (optional - from OAuth providers like Google)
35
+ export const AuthInfo = z.object({
36
+ sub: UserId,
37
+ email: z.email(),
38
+ name: z.string().optional(),
39
+ picture: z.url().optional(),
40
+ roles: z.array(UserRole).optional(), // Descope sends roles in JWT claims
41
+ });
42
+ // Re-export for convenience
43
+ export { JwtToken, UserId };
@@ -0,0 +1,13 @@
1
+ import { AchievementType, UserTier } from "./enums.js";
2
+ export declare const APP_NAME = "Dink Pets";
3
+ /** Request timeout in milliseconds. Used by both server (Fastify) and mobile (tRPC client). */
4
+ export declare const REQUEST_TIMEOUT_MS = 30000;
5
+ export declare const achievementLabels: Record<AchievementType, string>;
6
+ /** Tiers that grant premium access (paid or gifted) */
7
+ export declare const PREMIUM_TIERS: UserTier[];
8
+ export declare const PAID_TIERS: UserTier[];
9
+ export declare const isPaidTier: (tier: UserTier) => boolean;
10
+ /** Check if a tier has premium access */
11
+ export declare const isAnyPremiumTier: (tier: UserTier) => boolean;
12
+ /** Month names (1-indexed: MONTH_NAMES[0] = January, MONTH_NAMES[11] = December) */
13
+ export declare const MONTH_NAMES: readonly ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
@@ -0,0 +1,41 @@
1
+ import { AchievementType, UserTier } from "./enums.js";
2
+ export const APP_NAME = "Dink Pets";
3
+ /** Request timeout in milliseconds. Used by both server (Fastify) and mobile (tRPC client). */
4
+ export const REQUEST_TIMEOUT_MS = 30000;
5
+ export const achievementLabels = {
6
+ [AchievementType.enum.FIRST_WALK]: "First Walk",
7
+ [AchievementType.enum.FIRST_PHOTO]: "First Photo",
8
+ [AchievementType.enum.DAYS_TOGETHER_30]: "30 Days Together",
9
+ [AchievementType.enum.DAYS_TOGETHER_100]: "100 Days Together",
10
+ [AchievementType.enum.DAYS_TOGETHER_365]: "1 Year Together",
11
+ [AchievementType.enum.WALKS_STREAK_7]: "7-Day Walk Streak",
12
+ [AchievementType.enum.WALKS_STREAK_30]: "30-Day Walk Streak",
13
+ [AchievementType.enum.MEALS_ON_TIME_7]: "7-Day Meals On Time",
14
+ [AchievementType.enum.WALKS_100]: "100 Walks",
15
+ [AchievementType.enum.WALKS_500]: "500 Walks",
16
+ [AchievementType.enum.PHOTOS_50]: "50 Photos",
17
+ };
18
+ /** Tiers that grant premium access (paid or gifted) */
19
+ export const PREMIUM_TIERS = [
20
+ UserTier.enum.PREMIUM,
21
+ UserTier.enum.PREMIUM_GRATIS,
22
+ ];
23
+ export const PAID_TIERS = [UserTier.enum.PREMIUM];
24
+ export const isPaidTier = (tier) => PAID_TIERS.includes(tier);
25
+ /** Check if a tier has premium access */
26
+ export const isAnyPremiumTier = (tier) => PREMIUM_TIERS.includes(tier);
27
+ /** Month names (1-indexed: MONTH_NAMES[0] = January, MONTH_NAMES[11] = December) */
28
+ export const MONTH_NAMES = [
29
+ "January",
30
+ "February",
31
+ "March",
32
+ "April",
33
+ "May",
34
+ "June",
35
+ "July",
36
+ "August",
37
+ "September",
38
+ "October",
39
+ "November",
40
+ "December",
41
+ ];
@@ -0,0 +1,121 @@
1
+ import { z } from "zod";
2
+ export declare const userTiers: readonly ["FREE", "PREMIUM", "PREMIUM_GRATIS"];
3
+ export declare const UserTier: z.ZodEnum<{
4
+ FREE: "FREE";
5
+ PREMIUM: "PREMIUM";
6
+ PREMIUM_GRATIS: "PREMIUM_GRATIS";
7
+ }>;
8
+ export type UserTier = z.infer<typeof UserTier>;
9
+ export declare const petRoles: readonly ["OWNER", "COOWNER", "CARETAKER"];
10
+ export declare const PetRole: z.ZodEnum<{
11
+ OWNER: "OWNER";
12
+ COOWNER: "COOWNER";
13
+ CARETAKER: "CARETAKER";
14
+ }>;
15
+ export type PetRole = z.infer<typeof PetRole>;
16
+ export declare const speciesValues: readonly ["DOG", "CAT", "BIRD", "RABBIT", "FISH", "REPTILE", "OTHER"];
17
+ export declare const Species: z.ZodEnum<{
18
+ DOG: "DOG";
19
+ CAT: "CAT";
20
+ BIRD: "BIRD";
21
+ RABBIT: "RABBIT";
22
+ FISH: "FISH";
23
+ REPTILE: "REPTILE";
24
+ OTHER: "OTHER";
25
+ }>;
26
+ export type Species = z.infer<typeof Species>;
27
+ export declare const genders: readonly ["MALE", "FEMALE"];
28
+ export declare const Gender: z.ZodEnum<{
29
+ MALE: "MALE";
30
+ FEMALE: "FEMALE";
31
+ }>;
32
+ export type Gender = z.infer<typeof Gender>;
33
+ export declare const recurrences: readonly ["DAILY", "WEEKLY", "SPECIFIC_WEEKDAYS", "MONTHLY", "YEARLY", "ONCE"];
34
+ export declare const Recurrence: z.ZodEnum<{
35
+ DAILY: "DAILY";
36
+ WEEKLY: "WEEKLY";
37
+ SPECIFIC_WEEKDAYS: "SPECIFIC_WEEKDAYS";
38
+ MONTHLY: "MONTHLY";
39
+ YEARLY: "YEARLY";
40
+ ONCE: "ONCE";
41
+ }>;
42
+ export type Recurrence = z.infer<typeof Recurrence>;
43
+ export declare const daysOfWeek: readonly ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
44
+ export declare const DayOfWeek: z.ZodEnum<{
45
+ SUN: "SUN";
46
+ MON: "MON";
47
+ TUE: "TUE";
48
+ WED: "WED";
49
+ THU: "THU";
50
+ FRI: "FRI";
51
+ SAT: "SAT";
52
+ }>;
53
+ export type DayOfWeek = z.infer<typeof DayOfWeek>;
54
+ export declare const activityTypes: readonly ["WALK", "MEAL", "BATHROOM", "TREAT", "PLAYTIME", "MEDICATION", "VET_APPOINTMENT", "GROOMING", "BATH", "NAIL_TRIM"];
55
+ export declare const ActivityType: z.ZodEnum<{
56
+ WALK: "WALK";
57
+ MEAL: "MEAL";
58
+ BATHROOM: "BATHROOM";
59
+ TREAT: "TREAT";
60
+ PLAYTIME: "PLAYTIME";
61
+ MEDICATION: "MEDICATION";
62
+ VET_APPOINTMENT: "VET_APPOINTMENT";
63
+ GROOMING: "GROOMING";
64
+ BATH: "BATH";
65
+ NAIL_TRIM: "NAIL_TRIM";
66
+ }>;
67
+ export type ActivityType = z.infer<typeof ActivityType>;
68
+ export declare const healthLogTypes: readonly ["WEIGHT", "VET_VISIT", "SYMPTOM", "CONDITION", "ALLERGY", "VACCINE"];
69
+ export declare const HealthLogType: z.ZodEnum<{
70
+ WEIGHT: "WEIGHT";
71
+ VET_VISIT: "VET_VISIT";
72
+ SYMPTOM: "SYMPTOM";
73
+ CONDITION: "CONDITION";
74
+ ALLERGY: "ALLERGY";
75
+ VACCINE: "VACCINE";
76
+ }>;
77
+ export type HealthLogType = z.infer<typeof HealthLogType>;
78
+ export declare const achievementTypes: readonly ["FIRST_WALK", "FIRST_PHOTO", "DAYS_TOGETHER_30", "DAYS_TOGETHER_100", "DAYS_TOGETHER_365", "WALKS_STREAK_7", "WALKS_STREAK_30", "MEALS_ON_TIME_7", "WALKS_100", "WALKS_500", "PHOTOS_50"];
79
+ export declare const AchievementType: z.ZodEnum<{
80
+ FIRST_WALK: "FIRST_WALK";
81
+ FIRST_PHOTO: "FIRST_PHOTO";
82
+ DAYS_TOGETHER_30: "DAYS_TOGETHER_30";
83
+ DAYS_TOGETHER_100: "DAYS_TOGETHER_100";
84
+ DAYS_TOGETHER_365: "DAYS_TOGETHER_365";
85
+ WALKS_STREAK_7: "WALKS_STREAK_7";
86
+ WALKS_STREAK_30: "WALKS_STREAK_30";
87
+ MEALS_ON_TIME_7: "MEALS_ON_TIME_7";
88
+ WALKS_100: "WALKS_100";
89
+ WALKS_500: "WALKS_500";
90
+ PHOTOS_50: "PHOTOS_50";
91
+ }>;
92
+ export type AchievementType = z.infer<typeof AchievementType>;
93
+ export declare const accessTypes: readonly ["PERMANENT", "DATE_RANGE", "ONE_TIME", "RECURRING"];
94
+ export declare const AccessType: z.ZodEnum<{
95
+ PERMANENT: "PERMANENT";
96
+ DATE_RANGE: "DATE_RANGE";
97
+ ONE_TIME: "ONE_TIME";
98
+ RECURRING: "RECURRING";
99
+ }>;
100
+ export type AccessType = z.infer<typeof AccessType>;
101
+ export declare const inviteTypes: readonly ["PET_ACCESS", "REFERRAL"];
102
+ export declare const InviteType: z.ZodEnum<{
103
+ PET_ACCESS: "PET_ACCESS";
104
+ REFERRAL: "REFERRAL";
105
+ }>;
106
+ export type InviteType = z.infer<typeof InviteType>;
107
+ export declare const updateTypes: readonly ["PET_ACCESS_GRANTED", "ACHIEVEMENT_UNLOCKED", "REFERRAL_ACCEPTED"];
108
+ export declare const UpdateType: z.ZodEnum<{
109
+ PET_ACCESS_GRANTED: "PET_ACCESS_GRANTED";
110
+ ACHIEVEMENT_UNLOCKED: "ACHIEVEMENT_UNLOCKED";
111
+ REFERRAL_ACCEPTED: "REFERRAL_ACCEPTED";
112
+ }>;
113
+ export type UpdateType = z.infer<typeof UpdateType>;
114
+ export declare const reminderAlertStatuses: readonly ["PENDING", "SENT", "FAILED", "SKIPPED"];
115
+ export declare const ReminderAlertStatus: z.ZodEnum<{
116
+ PENDING: "PENDING";
117
+ SENT: "SENT";
118
+ FAILED: "FAILED";
119
+ SKIPPED: "SKIPPED";
120
+ }>;
121
+ export type ReminderAlertStatus = z.infer<typeof ReminderAlertStatus>;
@@ -0,0 +1,41 @@
1
+ // =============================================================================
2
+ // SHARED ENUMS - AUTO-GENERATED FROM PRISMA SCHEMA
3
+ // =============================================================================
4
+ // DO NOT EDIT THIS FILE MANUALLY.
5
+ // Run: node shared/scripts/generate-enums.js
6
+ // Or: cd server && npx prisma generate (runs automatically)
7
+ //
8
+ // This file generates Zod schemas (not TypeScript enums) because:
9
+ // - TS enums are nominally typed (Prisma's enums != shared enums even with same values)
10
+ // - Zod schemas produce string unions which are structurally typed
11
+ // - Server can pass Prisma enum values to shared functions without type errors
12
+ //
13
+ // See shared/README.md for full architecture explanation.
14
+ // =============================================================================
15
+ import { z } from "zod";
16
+ export const userTiers = ["FREE", "PREMIUM", "PREMIUM_GRATIS"];
17
+ export const UserTier = z.enum(userTiers);
18
+ export const petRoles = ["OWNER", "COOWNER", "CARETAKER"];
19
+ export const PetRole = z.enum(petRoles);
20
+ export const speciesValues = ["DOG", "CAT", "BIRD", "RABBIT", "FISH", "REPTILE", "OTHER"];
21
+ export const Species = z.enum(speciesValues);
22
+ export const genders = ["MALE", "FEMALE"];
23
+ export const Gender = z.enum(genders);
24
+ export const recurrences = ["DAILY", "WEEKLY", "SPECIFIC_WEEKDAYS", "MONTHLY", "YEARLY", "ONCE"];
25
+ export const Recurrence = z.enum(recurrences);
26
+ export const daysOfWeek = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
27
+ export const DayOfWeek = z.enum(daysOfWeek);
28
+ export const activityTypes = ["WALK", "MEAL", "BATHROOM", "TREAT", "PLAYTIME", "MEDICATION", "VET_APPOINTMENT", "GROOMING", "BATH", "NAIL_TRIM"];
29
+ export const ActivityType = z.enum(activityTypes);
30
+ export const healthLogTypes = ["WEIGHT", "VET_VISIT", "SYMPTOM", "CONDITION", "ALLERGY", "VACCINE"];
31
+ export const HealthLogType = z.enum(healthLogTypes);
32
+ export const achievementTypes = ["FIRST_WALK", "FIRST_PHOTO", "DAYS_TOGETHER_30", "DAYS_TOGETHER_100", "DAYS_TOGETHER_365", "WALKS_STREAK_7", "WALKS_STREAK_30", "MEALS_ON_TIME_7", "WALKS_100", "WALKS_500", "PHOTOS_50"];
33
+ export const AchievementType = z.enum(achievementTypes);
34
+ export const accessTypes = ["PERMANENT", "DATE_RANGE", "ONE_TIME", "RECURRING"];
35
+ export const AccessType = z.enum(accessTypes);
36
+ export const inviteTypes = ["PET_ACCESS", "REFERRAL"];
37
+ export const InviteType = z.enum(inviteTypes);
38
+ export const updateTypes = ["PET_ACCESS_GRANTED", "ACHIEVEMENT_UNLOCKED", "REFERRAL_ACCEPTED"];
39
+ export const UpdateType = z.enum(updateTypes);
40
+ export const reminderAlertStatuses = ["PENDING", "SENT", "FAILED", "SKIPPED"];
41
+ export const ReminderAlertStatus = z.enum(reminderAlertStatuses);
@@ -0,0 +1,35 @@
1
+ /**
2
+ * SERVER-SIDE REMINDER PUSH NOTIFICATIONS
3
+ *
4
+ * When ENABLED (true):
5
+ * - Server runs cron job every minute to check due reminders
6
+ * - Server sends push notifications via Expo Push API
7
+ * - User timezone is synced from mobile to server
8
+ * - ReminderAlert table tracks sent notifications
9
+ *
10
+ * When DISABLED (false):
11
+ * - Server cron jobs for reminders do NOT run
12
+ * - Mobile handles all reminder notifications locally via expo-notifications
13
+ * - Timezone sync still happens (may be useful for other features) but not required
14
+ *
15
+ * IMPORTANT - REMOVAL INSTRUCTIONS:
16
+ * If removing server-side reminder push entirely, delete ALL of the following together:
17
+ *
18
+ * Server:
19
+ * - server/src/services/reminder-scheduler.ts (entire file)
20
+ * - server/src/index.ts: cron.schedule blocks wrapped with this flag
21
+ * - server/src/index.ts: import of reminder-scheduler
22
+ * - server/prisma/schema.prisma: ReminderAlert model (run migration after)
23
+ * - server/prisma/schema.prisma: ReminderAlertStatus enum
24
+ * - server/prisma/schema.prisma: User.timezone field (optional - may want to keep)
25
+ *
26
+ * Mobile:
27
+ * - mobile/lib/sync-timezone.ts (entire file) - only if User.timezone removed
28
+ * - mobile/stores/auth-store.ts: syncTimezone/clearSyncedTimezone calls - only if above removed
29
+ *
30
+ * Shared:
31
+ * - This flag can be removed once all above code is removed
32
+ *
33
+ * DO NOT partially remove - either keep all or remove all to avoid dead code.
34
+ */
35
+ export declare const SERVER_REMINDER_PUSH_ENABLED = false;
@@ -0,0 +1,41 @@
1
+ // =============================================================================
2
+ // FEATURE FLAGS
3
+ // =============================================================================
4
+ // Toggles for features that may not be fully implemented or need coordination
5
+ // between client and server.
6
+ // =============================================================================
7
+ /**
8
+ * SERVER-SIDE REMINDER PUSH NOTIFICATIONS
9
+ *
10
+ * When ENABLED (true):
11
+ * - Server runs cron job every minute to check due reminders
12
+ * - Server sends push notifications via Expo Push API
13
+ * - User timezone is synced from mobile to server
14
+ * - ReminderAlert table tracks sent notifications
15
+ *
16
+ * When DISABLED (false):
17
+ * - Server cron jobs for reminders do NOT run
18
+ * - Mobile handles all reminder notifications locally via expo-notifications
19
+ * - Timezone sync still happens (may be useful for other features) but not required
20
+ *
21
+ * IMPORTANT - REMOVAL INSTRUCTIONS:
22
+ * If removing server-side reminder push entirely, delete ALL of the following together:
23
+ *
24
+ * Server:
25
+ * - server/src/services/reminder-scheduler.ts (entire file)
26
+ * - server/src/index.ts: cron.schedule blocks wrapped with this flag
27
+ * - server/src/index.ts: import of reminder-scheduler
28
+ * - server/prisma/schema.prisma: ReminderAlert model (run migration after)
29
+ * - server/prisma/schema.prisma: ReminderAlertStatus enum
30
+ * - server/prisma/schema.prisma: User.timezone field (optional - may want to keep)
31
+ *
32
+ * Mobile:
33
+ * - mobile/lib/sync-timezone.ts (entire file) - only if User.timezone removed
34
+ * - mobile/stores/auth-store.ts: syncTimezone/clearSyncedTimezone calls - only if above removed
35
+ *
36
+ * Shared:
37
+ * - This flag can be removed once all above code is removed
38
+ *
39
+ * DO NOT partially remove - either keep all or remove all to avoid dead code.
40
+ */
41
+ export const SERVER_REMINDER_PUSH_ENABLED = false;
@@ -0,0 +1,70 @@
1
+ import { z } from "zod";
2
+ declare const __brand: unique symbol;
3
+ type Brand<T, B> = T & {
4
+ readonly [__brand]: B;
5
+ };
6
+ export type UserId = Brand<string, "UserId">;
7
+ export declare const UserId: z.ZodPipe<z.ZodString, z.ZodCustom<UserId, UserId>>;
8
+ export type PetId = Brand<string, "PetId">;
9
+ export declare const PetId: z.ZodPipe<z.ZodString, z.ZodCustom<PetId, PetId>>;
10
+ export type ReminderId = Brand<string, "ReminderId">;
11
+ export declare const ReminderId: z.ZodPipe<z.ZodString, z.ZodCustom<ReminderId, ReminderId>>;
12
+ export type ActivityId = Brand<string, "ActivityId">;
13
+ export declare const ActivityId: z.ZodPipe<z.ZodString, z.ZodCustom<ActivityId, ActivityId>>;
14
+ export type HealthLogId = Brand<string, "HealthLogId">;
15
+ export declare const HealthLogId: z.ZodPipe<z.ZodString, z.ZodCustom<HealthLogId, HealthLogId>>;
16
+ export type PhotoId = Brand<string, "PhotoId">;
17
+ export declare const PhotoId: z.ZodPipe<z.ZodString, z.ZodCustom<PhotoId, PhotoId>>;
18
+ export type PetPhotoFileId = Brand<string, "PetPhotoFileId">;
19
+ export declare const PetPhotoFileId: z.ZodPipe<z.ZodString, z.ZodCustom<PetPhotoFileId, PetPhotoFileId>>;
20
+ export type AchievementId = Brand<string, "AchievementId">;
21
+ export declare const AchievementId: z.ZodPipe<z.ZodString, z.ZodCustom<AchievementId, AchievementId>>;
22
+ export type PetAccessId = Brand<string, "PetAccessId">;
23
+ export declare const PetAccessId: z.ZodPipe<z.ZodString, z.ZodCustom<PetAccessId, PetAccessId>>;
24
+ export type UpdateId = Brand<string, "UpdateId">;
25
+ export declare const UpdateId: z.ZodPipe<z.ZodString, z.ZodCustom<UpdateId, UpdateId>>;
26
+ export type UserSettingsId = Brand<string, "UserSettingsId">;
27
+ export declare const UserSettingsId: z.ZodPipe<z.ZodString, z.ZodCustom<UserSettingsId, UserSettingsId>>;
28
+ export type ReminderAlertId = Brand<string, "ReminderAlertId">;
29
+ export declare const ReminderAlertId: z.ZodPipe<z.ZodString, z.ZodCustom<ReminderAlertId, ReminderAlertId>>;
30
+ export type InviteId = Brand<string, "InviteId">;
31
+ export declare const InviteId: z.ZodPipe<z.ZodString, z.ZodCustom<InviteId, InviteId>>;
32
+ export type PushTokenId = Brand<string, "PushTokenId">;
33
+ export declare const PushTokenId: z.ZodPipe<z.ZodString, z.ZodCustom<PushTokenId, PushTokenId>>;
34
+ /**
35
+ * The actual Expo push token value (e.g., "ExponentPushToken[abc123...]").
36
+ * This is the token used to send push notifications to a device.
37
+ * Distinct from PushTokenId which is the database row ID.
38
+ */
39
+ export type ExpoPushToken = Brand<string, "ExpoPushToken">;
40
+ export declare const ExpoPushToken: z.ZodPipe<z.ZodString, z.ZodCustom<ExpoPushToken, ExpoPushToken>>;
41
+ export type JwtToken = Brand<string, "JwtToken">;
42
+ export declare const JwtToken: z.ZodPipe<z.ZodString, z.ZodCustom<JwtToken, JwtToken>>;
43
+ /**
44
+ * Cast a plain string to a branded ID type at trust boundaries.
45
+ *
46
+ * Trust boundaries include:
47
+ * - Database query results (Prisma returns plain strings)
48
+ * - API request params (user input)
49
+ * - External services (Descope returns user ID as string)
50
+ *
51
+ * Don't scatter casts everywhere - do it once at the boundary, then pass
52
+ * the branded type through your code.
53
+ */
54
+ export declare const asUserId: (id: string) => UserId;
55
+ export declare const asPetId: (id: string) => PetId;
56
+ export declare const asReminderId: (id: string) => ReminderId;
57
+ export declare const asActivityId: (id: string) => ActivityId;
58
+ export declare const asHealthLogId: (id: string) => HealthLogId;
59
+ export declare const asPhotoId: (id: string) => PhotoId;
60
+ export declare const asPetPhotoFileId: (id: string) => PetPhotoFileId;
61
+ export declare const asAchievementId: (id: string) => AchievementId;
62
+ export declare const asPetAccessId: (id: string) => PetAccessId;
63
+ export declare const asUpdateId: (id: string) => UpdateId;
64
+ export declare const asUserSettingsId: (id: string) => UserSettingsId;
65
+ export declare const asReminderAlertId: (id: string) => ReminderAlertId;
66
+ export declare const asInviteId: (id: string) => InviteId;
67
+ export declare const asPushTokenId: (id: string) => PushTokenId;
68
+ export declare const asExpoPushToken: (token: string) => ExpoPushToken;
69
+ export declare const asJwtToken: (token: string) => JwtToken;
70
+ export {};
@@ -0,0 +1,47 @@
1
+ import { z } from "zod";
2
+ export const UserId = z.string().pipe(z.custom());
3
+ export const PetId = z.string().pipe(z.custom());
4
+ export const ReminderId = z.string().pipe(z.custom());
5
+ export const ActivityId = z.string().pipe(z.custom());
6
+ export const HealthLogId = z.string().pipe(z.custom());
7
+ export const PhotoId = z.string().pipe(z.custom());
8
+ export const PetPhotoFileId = z.string().pipe(z.custom());
9
+ export const AchievementId = z.string().pipe(z.custom());
10
+ export const PetAccessId = z.string().pipe(z.custom());
11
+ export const UpdateId = z.string().pipe(z.custom());
12
+ export const UserSettingsId = z.string().pipe(z.custom());
13
+ export const ReminderAlertId = z.string().pipe(z.custom());
14
+ export const InviteId = z.string().pipe(z.custom());
15
+ export const PushTokenId = z.string().pipe(z.custom());
16
+ export const ExpoPushToken = z.string().pipe(z.custom());
17
+ export const JwtToken = z.string().pipe(z.custom());
18
+ // =============================================================================
19
+ // CASTING HELPERS
20
+ // =============================================================================
21
+ /**
22
+ * Cast a plain string to a branded ID type at trust boundaries.
23
+ *
24
+ * Trust boundaries include:
25
+ * - Database query results (Prisma returns plain strings)
26
+ * - API request params (user input)
27
+ * - External services (Descope returns user ID as string)
28
+ *
29
+ * Don't scatter casts everywhere - do it once at the boundary, then pass
30
+ * the branded type through your code.
31
+ */
32
+ export const asUserId = (id) => id;
33
+ export const asPetId = (id) => id;
34
+ export const asReminderId = (id) => id;
35
+ export const asActivityId = (id) => id;
36
+ export const asHealthLogId = (id) => id;
37
+ export const asPhotoId = (id) => id;
38
+ export const asPetPhotoFileId = (id) => id;
39
+ export const asAchievementId = (id) => id;
40
+ export const asPetAccessId = (id) => id;
41
+ export const asUpdateId = (id) => id;
42
+ export const asUserSettingsId = (id) => id;
43
+ export const asReminderAlertId = (id) => id;
44
+ export const asInviteId = (id) => id;
45
+ export const asPushTokenId = (id) => id;
46
+ export const asExpoPushToken = (token) => token;
47
+ export const asJwtToken = (token) => token;
@@ -0,0 +1,14 @@
1
+ export * from "./activity-details.js";
2
+ export * from "./admin.js";
3
+ export * from "./auth.js";
4
+ export * from "./constants.js";
5
+ export * from "./enums.js";
6
+ export * from "./feature-flags.js";
7
+ export * from "./ids.js";
8
+ export * from "./roles.js";
9
+ export * from "./schemas.js";
10
+ export * from "./theme.js";
11
+ export * from "./updates.js";
12
+ export * from "./uploads.js";
13
+ export * from "./utils.js";
14
+ export * from "./web-routes.js";
@@ -0,0 +1,14 @@
1
+ export * from "./activity-details.js";
2
+ export * from "./admin.js";
3
+ export * from "./auth.js";
4
+ export * from "./constants.js";
5
+ export * from "./enums.js";
6
+ export * from "./feature-flags.js";
7
+ export * from "./ids.js";
8
+ export * from "./roles.js";
9
+ export * from "./schemas.js";
10
+ export * from "./theme.js";
11
+ export * from "./updates.js";
12
+ export * from "./uploads.js";
13
+ export * from "./utils.js";
14
+ export * from "./web-routes.js";
@@ -0,0 +1,6 @@
1
+ import { z } from "zod";
2
+ export declare const userRoles: readonly ["admin"];
3
+ export declare const UserRole: z.ZodEnum<{
4
+ admin: "admin";
5
+ }>;
6
+ export type UserRole = z.infer<typeof UserRole>;
@@ -0,0 +1,17 @@
1
+ // =============================================================================
2
+ // USER ROLES - Constants Only
3
+ // =============================================================================
4
+ // Shared role string constants to avoid typos between mobile and server.
5
+ //
6
+ // IMPORTANT: Role checking logic is NOT shared. Each platform has its own:
7
+ // - Server: adminProcedure in trpc.ts checks ctx.auth (validated by Descope SDK)
8
+ // - Mobile: decodes JWT for UI only (not a security boundary)
9
+ //
10
+ // Descope setup required:
11
+ // 1. Create "admin" role in Descope dashboard
12
+ // 2. Assign role to admin users
13
+ // 3. Role appears in JWT claims under "roles" array
14
+ // =============================================================================
15
+ import { z } from "zod";
16
+ export const userRoles = ["admin"];
17
+ export const UserRole = z.enum(userRoles);
@@ -0,0 +1,112 @@
1
+ import { z } from "zod";
2
+ import { DayOfWeek } from "./enums.js";
3
+ export declare const TINY_TEXT_MAX = 50;
4
+ export declare const SHORT_TEXT_MAX = 100;
5
+ export declare const LONG_TEXT_MAX = 2000;
6
+ export declare const MEALS_PER_DAY_MIN = 1;
7
+ export declare const MEALS_PER_DAY_MAX = 10;
8
+ export declare const ShortText: z.ZodString;
9
+ export declare const TinyText: z.ZodString;
10
+ export declare const LongText: z.ZodString;
11
+ export declare const AmountUnit: z.ZodString;
12
+ export declare const Subtype: z.ZodString;
13
+ export declare const PetColor: z.ZodString;
14
+ export declare const Breed: z.ZodString;
15
+ export declare const MicrochipId: z.ZodString;
16
+ export declare const RabiesTagNumber: z.ZodString;
17
+ export declare const InsuranceProvider: z.ZodString;
18
+ export declare const InsurancePolicyNumber: z.ZodString;
19
+ export declare const DURATION_MIN_MIN = 1;
20
+ export declare const DURATION_MIN_MAX = 1440;
21
+ export declare const DurationMin: z.ZodNumber;
22
+ export declare const Amount: z.ZodNumber;
23
+ export declare const CAPTION_MAX = 250;
24
+ export declare const Caption: z.ZodString;
25
+ export declare const HealthValue: z.ZodNumber;
26
+ export declare const HealthUnit: z.ZodString;
27
+ export declare const Title: z.ZodString;
28
+ export declare const VetName: z.ZodString;
29
+ export declare const Location: z.ZodString;
30
+ /**
31
+ * Name field for pets, users, etc.
32
+ *
33
+ * Restricts to safe characters to prevent XSS in contexts like email templates
34
+ * where names are interpolated. Allows unicode letters (\p{L}) for international
35
+ * names (José, 北京, Мария) while blocking HTML-unsafe characters like < >.
36
+ */
37
+ export declare const Name: z.ZodString;
38
+ export declare const Notes: z.ZodString;
39
+ export declare const bathroomActivitySubtypes: readonly ["pee", "poo"];
40
+ export declare const BathroomActivitySubtype: z.ZodEnum<{
41
+ pee: "pee";
42
+ poo: "poo";
43
+ }>;
44
+ export type BathroomActivitySubtype = z.infer<typeof BathroomActivitySubtype>;
45
+ /**
46
+ * URL for images (pet photos, avatars, etc.)
47
+ *
48
+ * Restricted to HTTPS only to prevent XSS via javascript:, data:, or file:// URLs
49
+ * if the URL is ever rendered in an <img src> or <a href> context.
50
+ */
51
+ export declare const ImageUrl: z.ZodURL;
52
+ export declare const DateField: z.ZodCoercedDate<unknown>;
53
+ export declare const MealsPerDay: z.ZodNumber;
54
+ export declare const PAGINATION_LIMIT_MIN = 1;
55
+ export declare const PAGINATION_LIMIT_MAX = 50;
56
+ export declare const PAGINATION_LIMIT_DEFAULT = 20;
57
+ export declare const PaginationLimit: z.ZodDefault<z.ZodNumber>;
58
+ export declare const MOOD_MIN = 1;
59
+ export declare const MOOD_MAX = 5;
60
+ export declare const Mood: z.ZodNumber;
61
+ /**
62
+ * Time of day in HH:MM format (24-hour).
63
+ *
64
+ * Examples: "08:00", "14:30", "23:59"
65
+ * Used for reminder schedules. The mobile app interprets this in the device's timezone.
66
+ */
67
+ export declare const Time: z.ZodString;
68
+ /**
69
+ * Day of month (1-31).
70
+ *
71
+ * Used for monthly reminders. Values 29-31 may not exist in all months;
72
+ * the scheduler should handle this (e.g., Feb 30 becomes Feb 28).
73
+ */
74
+ export declare const DAY_OF_MONTH_MIN = 1;
75
+ export declare const DAY_OF_MONTH_MAX = 31;
76
+ export declare const DayOfMonth: z.ZodNumber;
77
+ /**
78
+ * Interval count for recurring reminders.
79
+ *
80
+ * "Every N days/weeks/months" - N is the interval count.
81
+ * Minimum 1 (every day/week/month), no maximum because it depends on user needs and allows flexibility like 45 days.
82
+ */
83
+ export declare const INTERVAL_COUNT_MIN = 1;
84
+ export declare const IntervalCount: z.ZodNumber;
85
+ /**
86
+ * Maps DayOfWeek enum values to JavaScript Date.getDay() values (0 = Sunday).
87
+ */
88
+ export declare const DAY_OF_WEEK_TO_NUMBER: Record<DayOfWeek, 0 | 1 | 2 | 3 | 4 | 5 | 6>;
89
+ /**
90
+ * Year for pet dates (birthday, adoption).
91
+ *
92
+ * Range: 1990-2100. Pets born before 1990 are extremely rare (35+ years old).
93
+ * Upper bound leaves room for future dates (adoption planned for next year).
94
+ */
95
+ export declare const YEAR_MIN = 1990;
96
+ export declare const YEAR_MAX = 2100;
97
+ export declare const YearInt: z.ZodNumber;
98
+ /**
99
+ * Month (1-12).
100
+ */
101
+ export declare const MONTH_MIN = 1;
102
+ export declare const MONTH_MAX = 12;
103
+ export declare const MonthInt: z.ZodNumber;
104
+ /**
105
+ * Format a pet date with variable precision.
106
+ *
107
+ * @example
108
+ * formatPetDate(2020) // "2020"
109
+ * formatPetDate(2020, 3) // "March 2020"
110
+ * formatPetDate(2020, 3, 15) // "March 15, 2020"
111
+ */
112
+ export declare function formatPetDate(year: number, month?: number | null, day?: number | null): string;