Schemas
API Schemas
Section titled “API Schemas”BitMarks uses Zod for runtime validation. All schemas are available in the @bitmarks.sh/core package.
Core Schemas
Section titled “Core Schemas”Bookmark
Section titled “Bookmark”The main bookmark schema (decrypted form):
import { z } from 'zod';
export const BookmarkSchema = z.object({ id: z.string(), title: z.string(), url: z.string().url().optional(), parentId: z.string().optional(), dateAdded: z.string().datetime().optional(), dateModified: z.string().datetime().optional(), folder: z.string().optional(), path: z.string().optional(), tags: z.array(z.string()).optional(), favicon: z.string().optional(), domain: z.string().optional(), source: SourceTypeSchema,
// GitHub star fields name: z.string().optional(), full_name: z.string().optional(), stargazers_count: z.number().optional(), description: z.string().nullable().optional(), owner: GitHubOwnerSchema.optional(),});
export type Bookmark = z.infer<typeof BookmarkSchema>;Source Type
Section titled “Source Type”export const SourceTypeSchema = z.enum([ 'bookmarks', // Browser bookmarks 'history', // Browsing history 'groups', // Tab groups 'stars' // GitHub stars]);
export type SourceType = z.infer<typeof SourceTypeSchema>;Encrypted Data
Section titled “Encrypted Data”The encrypted envelope sent to/from the API:
export const EncryptedDataSchema = z.object({ encrypted_data: z.string(), // Base64-encoded ciphertext data_hash: z.string() // SHA-256 of plaintext});
export type EncryptedData = z.infer<typeof EncryptedDataSchema>;Sync Schemas
Section titled “Sync Schemas”Sync Event
Section titled “Sync Event”Events in the sync log:
export const SyncEventSchema = z.object({ id: z.string(), user_id: z.string(), entity_type: z.enum(['bookmark', 'history', 'group']), entity_id: z.string(), operation: z.enum(['create', 'update', 'delete']), timestamp: z.number().int().positive(), device_id: z.string(), encrypted_data: z.string().optional(), data_hash: z.string(), conflict_resolved: z.boolean(), resolution_strategy: z.string().optional()});
export type SyncEvent = z.infer<typeof SyncEventSchema>;Sync Config
Section titled “Sync Config”User sync settings:
export const SyncConfigSchema = z.object({ enabled: z.boolean().default(true), autoSync: z.boolean().default(true), syncInterval: z.number().default(300000), // 5 minutes conflictStrategy: z.enum(['last-write-wins', 'manual']).default('last-write-wins'), sources: z.array(SourceTypeSchema).default(['bookmarks'])});
export type SyncConfig = z.infer<typeof SyncConfigSchema>;WebSocket Schemas
Section titled “WebSocket Schemas”Inbound Messages
Section titled “Inbound Messages”Messages from client to server:
export const WebSocketInboundSchema = z.discriminatedUnion('type', [ z.object({ type: z.literal('sync'), event: SyncEventSchema }), z.object({ type: z.literal('ping') })]);
export type WebSocketInbound = z.infer<typeof WebSocketInboundSchema>;Outbound Messages
Section titled “Outbound Messages”Messages from server to client:
export const WebSocketOutboundSchema = z.discriminatedUnion('type', [ z.object({ type: z.literal('connected'), deviceId: z.string(), timestamp: z.number() }), z.object({ type: z.literal('pong') }), z.object({ type: z.literal('sync'), event: SyncEventSchema }), z.object({ type: z.literal('error'), error: z.string() })]);
export type WebSocketOutbound = z.infer<typeof WebSocketOutboundSchema>;Database Schemas
Section titled “Database Schemas”D1 Bookmark Row
Section titled “D1 Bookmark Row”export const D1BookmarkRowSchema = z.object({ id: z.string(), user_id: z.string(), encrypted_data: z.string(), data_hash: z.string(), created_at: z.number(), updated_at: z.number(), deleted_at: z.number().nullable()});
export type D1BookmarkRow = z.infer<typeof D1BookmarkRowSchema>;D1 Sync Log Row
Section titled “D1 Sync Log Row”export const D1SyncLogRowSchema = z.object({ id: z.string(), user_id: z.string(), entity_type: z.string(), entity_id: z.string(), operation: z.string(), timestamp: z.number(), device_id: z.string(), encrypted_data: z.string().nullable(), data_hash: z.string(), conflict_resolved: z.number(), // 0 or 1 resolution_strategy: z.string().nullable()});
export type D1SyncLogRow = z.infer<typeof D1SyncLogRowSchema>;User Schemas
Section titled “User Schemas”User Session
Section titled “User Session”export const UserSessionSchema = z.object({ user_id: z.string(), email: z.string().email(), name: z.string().optional(), avatar_url: z.string().url().optional(), email_verified: z.boolean()});
export type UserSession = z.infer<typeof UserSessionSchema>;View Schemas
Section titled “View Schemas”View Mode
Section titled “View Mode”export const ViewModeSchema = z.enum([ 'table', // Spreadsheet-like table view 'cards', // Card grid view 'reader', // Reader/article view 'finder' // Finder/explorer view]);
export type ViewMode = z.infer<typeof ViewModeSchema>;Sort Order
Section titled “Sort Order”export const SortOrderSchema = z.enum(['asc', 'desc']);
export type SortOrder = z.infer<typeof SortOrderSchema>;GitHub Schemas
Section titled “GitHub Schemas”GitHub Owner
Section titled “GitHub Owner”export const GitHubOwnerSchema = z.object({ login: z.string(), avatar_url: z.string().url()});
export type GitHubOwner = z.infer<typeof GitHubOwnerSchema>;Using Schemas
Section titled “Using Schemas”Validation
Section titled “Validation”import { BookmarkSchema, SyncEventSchema } from '@bitmarks.sh/core';
// Validate incoming dataconst result = BookmarkSchema.safeParse(unknownData);
if (result.success) { const bookmark = result.data; // Typed as Bookmark} else { console.error('Validation failed:', result.error);}Type Inference
Section titled “Type Inference”import { z } from 'zod';import { BookmarkSchema } from '@bitmarks.sh/core';
// Get TypeScript type from schematype Bookmark = z.infer<typeof BookmarkSchema>;
// Use in function signaturesfunction processBookmark(bookmark: Bookmark) { // ...}Partial Schemas
Section titled “Partial Schemas”// For updates where not all fields are requiredconst BookmarkUpdateSchema = BookmarkSchema.partial();
// Only certain fields optionalconst BookmarkCreateSchema = BookmarkSchema.omit({ id: true });API Request Validation
Section titled “API Request Validation”The API validates all incoming requests using these schemas:
// In route handlerapp.post('/bookmarks', async (c) => { const body = await c.req.json();
const result = EncryptedDataSchema.safeParse(body);
if (!result.success) { return c.json({ error: 'Invalid request body' }, 400); }
const { encrypted_data, data_hash } = result.data; // ...});