Shared

This directory contains code shared between frontend (browser) and backend (Deno).

Critical Rule: All code here MUST work in both browser and Deno environments.

Files

types.ts - TypeScript Interfaces

Purpose: Define data structures shared across all layers of the application.

What Goes Here:

  • ✅ Controller response format (ControllerResponse<T>)
  • ✅ Notion data structures (NotionPage, NotionDatabase, etc.)
  • ✅ API request/response types (UpdatePageRequest, PageResponse, etc.)
  • ✅ Common data models used in both frontend and backend
  • ✅ Enum types for shared constants

Naming Conventions:

  • Interfaces/Types: PascalCase (e.g., NotionWebhookPayload, PageResponse)
  • Enum values: SCREAMING_SNAKE_CASE or PascalCase depending on usage

Example:

// Standardized controller response export interface ControllerResponse<T> { success: boolean; data: T | null; error: string | null; details?: string; } // Notion page structure export interface NotionPage { id: string; properties: Record<string, any>; created_time: string; last_edited_time: string; } // API request type export interface UpdatePageRequest { properties: Record<string, any>; }

utils.ts - Browser-Compatible Utility Functions

Purpose: Pure utility functions that work in BOTH browser and Deno environments.

Critical Constraint: NO platform-specific APIs allowed.

What Goes Here:

  • ✅ String manipulation functions
  • ✅ Date formatting (using standard Date API only)
  • ✅ Data transformation functions
  • ✅ Validation helpers (using standard JavaScript)
  • ✅ Array/object utilities
  • ✅ Notion-specific helpers (ID formatting, rich text extraction)

What Does NOT Go Here:

  • ❌ Deno APIs (Deno.env, Deno.readFile, etc.) → use backend/utils/
  • ❌ Browser APIs (window, document, localStorage) → use frontend code
  • ❌ Node APIs (fs, path, etc.)
  • ❌ External API calls → use backend/services/
  • ❌ Business logic → use backend/controllers/

Rules:

  • Pure functions with predictable outputs
  • No side effects (no global state, no I/O)
  • Single responsibility per function
  • Type-safe with TypeScript
  • Well-documented with JSDoc comments

Example Functions:

/** * Check if a string is a valid Notion page ID format */ export function isValidPageId(pageId: string): boolean { if (!pageId || typeof pageId !== 'string') { return false; } const cleanId = pageId.replace(/-/g, ''); return cleanId.length === 32 && /^[a-f0-9]+$/i.test(cleanId); } /** * Format a Notion page ID with hyphens in standard format (8-4-4-4-12) */ export function formatPageId(pageId: string): string { const clean = pageId.replace(/-/g, ''); return `${clean.slice(0, 8)}-${clean.slice(8, 12)}-${clean.slice(12, 16)}-${clean.slice(16, 20)}-${clean.slice(20)}`; } /** * Extract plain text from Notion rich text array */ export function getPlainText(richText: any[]): string { if (!Array.isArray(richText)) { return ''; } return richText.map(text => text.plain_text || '').join(''); } /** * Get a property value from Notion page properties by type */ export function getPropertyValue(properties: any, propertyName: string): any { const property = properties[propertyName]; if (!property) return null; switch (property.type) { case 'title': return getPlainText(property.title); case 'rich_text': return getPlainText(property.rich_text); case 'number': return property.number; case 'select': return property.select?.name || null; case 'date': return property.date?.start || null; // ... more types default: return property; } }

Import Patterns

From Frontend (Browser)

// frontend/components/App.tsx import { getPropertyValue } from "../../shared/utils.ts"; import type { NotionPage } from "../../shared/types.ts";

From Backend (Deno)

// backend/controllers/pageController.ts import { isValidPageId } from '../../shared/utils.ts'; import type { ControllerResponse } from '../../shared/types.ts';

Shared vs Backend Utils

When to Use shared/utils.ts

Use when the function:

  • Works in both browser AND Deno
  • Uses only standard JavaScript/TypeScript
  • Has no platform-specific dependencies

Examples:

  • String formatting: formatPageId(id)
  • Data extraction: getPlainText(richText)
  • Simple validation: isValidEmail(email)
  • Array utilities: chunk(array, size)

When to Use backend/utils/

Use when the function:

  • Only runs in Deno (backend)
  • Needs Deno-specific APIs (Web Crypto, file system, etc.)
  • Is never called from frontend

Examples:

  • Cryptography: sha256(message) using Web Crypto API
  • JWT signing: signToken(payload) using Deno crypto
  • Environment parsing: parseEnvConfig()
  • File hashing: hashFile(path)

Guidelines for Adding Code

Adding New Types

  1. Define interface in types.ts
  2. Use clear, descriptive PascalCase names
  3. Document complex types with JSDoc comments
  4. Include all required fields, mark optional with ?
  5. Use generics where appropriate
/** * Response from page update operations */ export interface PageUpdateResponse { page: NotionPage; updatedAt: string; changes: string[]; }

Adding New Utils

  1. Verify function works in BOTH browser and Deno
  2. Test with standard JavaScript only (no platform APIs)
  3. Write as pure function (no side effects)
  4. Add JSDoc documentation
  5. Use TypeScript types for parameters and return values
  6. Keep functions small and focused
/** * Normalize a Notion page ID by removing hyphens * @param pageId - The page ID with or without hyphens * @returns The normalized ID (32 hex characters) */ export function normalizePageId(pageId: string): string { return pageId.replace(/-/g, ''); }

Testing Compatibility

To ensure code works in both environments:

Browser Test

Open browser console and test:

// Test your function const result = getPlainText([{plain_text: "Hello"}]); console.log(result); // Should work

Deno Test

// Test in Deno REPL or script import { getPlainText } from "./shared/utils.ts"; console.log(getPlainText([{plain_text: "Hello"}])); // Should work

❌ Common Mistakes

// ❌ BAD - Uses Deno API export function getEnv(key: string): string { return Deno.env.get(key) || ''; // Won't work in browser! } // ❌ BAD - Uses browser API export function saveToLocal(key: string, value: string): void { localStorage.setItem(key, value); // Won't work in Deno! } // ❌ BAD - Uses Node API export function readFile(path: string): string { return require('fs').readFileSync(path); // Won't work anywhere in Val Town! } // ✅ GOOD - Pure JavaScript export function toKebabCase(str: string): string { return str.toLowerCase().replace(/\s+/g, '-'); }

Best Practices

  1. Platform Independence: Never use Deno, Node, or browser-specific APIs
  2. Pure Functions: No side effects, predictable outputs
  3. Type Safety: Always use TypeScript types
  4. Documentation: Add JSDoc comments for public functions
  5. Single Responsibility: Each function does one thing well
  6. Test Both Environments: Verify code works in browser and Deno
  7. Standard Library Only: Use only JavaScript standard library features
  8. No External Calls: No API calls, database queries, or file I/O

Common Use Cases

Notion ID Utilities

isValidPageId(id) // Validate format normalizePageId(id) // Remove hyphens formatPageId(id) // Add hyphens (8-4-4-4-12)

Notion Data Extraction

getPlainText(richText) // Extract text from rich text getPropertyValue(properties, name) // Get typed property value

Data Validation

isValidEmail(email) // Email format check isValidUrl(url) // URL format check isEmpty(value) // Check for empty values

String Utilities

slugify(text) // Convert to URL-safe slug truncate(text, length) // Truncate with ellipsis capitalize(text) // Capitalize first letter

See Also

  • /backend/README.md - Backend architecture and backend/utils/
  • /frontend/README.md - Frontend architecture
  • CLAUDE.md - Complete project architecture guidelines