chore: bootstrap monorepo workspace and shared schemas

This commit is contained in:
Anton
2026-04-23 16:03:34 +03:00
commit 5f6a551b6c
15 changed files with 308 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
{
"name": "@family-wishlist/shared",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"typecheck": "tsc --noEmit",
"build": "echo 'shared is source-only'",
"lint": "echo 'skip'",
"dev": "echo 'shared is source-only'"
},
"dependencies": {
"zod": "^3.23.8"
},
"devDependencies": {
"typescript": "^5.6.2"
}
}

View File

@@ -0,0 +1,17 @@
import { z } from 'zod';
export const loginSchema = z.object({
username: z.string().trim().min(3).max(64),
password: z.string().min(8).max(200),
});
export type LoginInput = z.infer<typeof loginSchema>;
export const authUserSchema = z.object({
id: z.string(),
username: z.string(),
slug: z.string(),
displayName: z.string(),
});
export type AuthUser = z.infer<typeof authUserSchema>;

View File

@@ -0,0 +1,5 @@
export * from './auth.schema.js';
export * from './profile.schema.js';
export * from './wish.schema.js';
export * from './public.schema.js';
export * from './types.js';

View File

@@ -0,0 +1,30 @@
import { z } from 'zod';
export const slugRegex = /^[a-z0-9](?:[a-z0-9-]{1,30}[a-z0-9])?$/;
export const profileSchema = z.object({
id: z.string(),
username: z.string(),
slug: z.string(),
displayName: z.string(),
bio: z.string().nullable(),
avatarUrl: z.string().nullable(),
createdAt: z.string(),
updatedAt: z.string(),
});
export type Profile = z.infer<typeof profileSchema>;
export const updateProfileSchema = z.object({
slug: z
.string()
.trim()
.toLowerCase()
.regex(slugRegex, 'Slug must be 3-32 chars, lowercase letters, digits, hyphens')
.optional(),
displayName: z.string().trim().min(1).max(64).optional(),
bio: z.string().trim().max(500).nullable().optional(),
avatarUrl: z.string().url().nullable().optional(),
});
export type UpdateProfileInput = z.infer<typeof updateProfileSchema>;

View File

@@ -0,0 +1,16 @@
import { z } from 'zod';
export const publicProfileSchema = z.object({
slug: z.string(),
displayName: z.string(),
bio: z.string().nullable(),
avatarUrl: z.string().nullable(),
});
export type PublicProfile = z.infer<typeof publicProfileSchema>;
export const markSeenSchema = z.object({
wishIds: z.array(z.string().cuid2().or(z.string())).min(1).max(500),
});
export type MarkSeenInput = z.infer<typeof markSeenSchema>;

View File

@@ -0,0 +1,9 @@
export interface ApiError {
error: string;
message: string;
details?: unknown;
}
export interface VersionInfo {
backend: string;
}

View File

@@ -0,0 +1,61 @@
import { z } from 'zod';
export const wishStatus = z.enum(['ACTIVE', 'ARCHIVED', 'COMPLETED', 'DELETED']);
export type WishStatus = z.infer<typeof wishStatus>;
export const imageSource = z.enum(['DEFAULT', 'OG', 'UPLOADED']);
export type ImageSource = z.infer<typeof imageSource>;
export const wishSchema = z.object({
id: z.string(),
title: z.string(),
price: z.string().nullable(),
currency: z.string(),
url: z.string().nullable(),
comment: z.string().nullable(),
imageUrl: z.string().nullable(),
imageSource: imageSource,
status: wishStatus,
createdAt: z.string(),
updatedAt: z.string(),
archivedAt: z.string().nullable(),
completedAt: z.string().nullable(),
deletedAt: z.string().nullable(),
sourceWishId: z.string().nullable(),
isNewForGuest: z.boolean().optional(),
});
export type Wish = z.infer<typeof wishSchema>;
export const createWishSchema = z.object({
title: z.string().trim().min(1).max(200),
price: z
.union([
z.string().trim().regex(/^\d+(\.\d{1,2})?$/, 'Price must be a number with up to 2 decimals'),
z.literal(''),
z.null(),
])
.optional()
.transform((v) => (v === '' || v == null ? null : v)),
currency: z.string().trim().length(3).toUpperCase().default('RUB'),
url: z
.union([z.string().trim().url(), z.literal(''), z.null()])
.optional()
.transform((v) => (v === '' || v == null ? null : v)),
comment: z
.union([z.string().trim().max(2000), z.literal(''), z.null()])
.optional()
.transform((v) => (v === '' || v == null ? null : v)),
});
export type CreateWishInput = z.infer<typeof createWishSchema>;
export const updateWishSchema = createWishSchema.partial();
export type UpdateWishInput = z.infer<typeof updateWishSchema>;
export const wishStatusQuery = z
.enum(['active', 'archived', 'completed', 'deleted'])
.default('active');
export const NEW_BADGE_DAYS = 5;
export const TRASH_RETENTION_DAYS = 30;

View File

@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"noEmit": true
},
"include": ["src/**/*"]
}