AbsoluteJS

End-to-End Type Safety

Types flow from your database schema through server handlers to frontend components across all frameworks : React, Svelte, Vue, and Angular.

#How It Works

AbsoluteJS enforces a single type flow across your entire stack: Database Schema → Inferred Types → Server Handler → Page Component. Every step is validated by TypeScript at compile time.

Compile-Time Errors

Missing or incorrectly typed props are caught by TypeScript before your code runs. No runtime surprises.

Refactoring Safety

Rename a field in your database schema and TypeScript shows every place that needs updating across your entire application.

#Schema to Types

Define your database schema with Drizzle and infer TypeScript types directly from your table definitions. These types are the single source of truth for your entire application.

TS
// db/schema.ts
import { pgTable, text, timestamp, boolean } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: text('id').primaryKey(),
  name: text('name').notNull(),
  email: text('email').notNull().unique()
});

export const notifications = pgTable('notifications', {
  id: text('id').primaryKey(),
  userId: text('user_id').notNull().references(() => users.id),
  message: text('message').notNull(),
  read: boolean('read').notNull().default(false),
  createdAt: timestamp('created_at').notNull().defaultNow()
});

// types/databaseTypes.ts
// Infer types directly from your table definitions
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;

export type Notification = typeof notifications.$inferSelect;
export type NewNotification = typeof notifications.$inferInsert;
$inferSelectthe type you get back when querying rows
$inferInsertthe type required when inserting new rows (optional fields with defaults are made optional)

#React

Use handleReactPageRequest with a generic Props parameter to enforce type safety at the server boundary. TypeScript ensures the props you pass match the component's expected types exactly.

TS
// backend/server.ts
import { User, Notification } from '../types/databaseTypes';
import { Dashboard } from '../frontend/pages/Dashboard';

type DashboardProps = {
  user: User;
  notifications: Notification[];
  unreadCount: number;
};

new Elysia()
  .get('/dashboard', async ({ cookie }) => {
    const user = await getAuthenticatedUser(cookie);
    const notifications = await getNotifications(user.id);

    // Type error if props don't match DashboardProps!
    return handleReactPageRequest({
      Page: Dashboard,
      index: asset(manifest, 'DashboardIndex'),
      props: {
        user,
        notifications,
        unreadCount: notifications.filter(n => !n.read).length
      }
    });
  })
1
Schema → Types
Drizzle infers types directly from your table definitions
2
Types → Server
Your inferred types flow into route handlers and props
3
Props → Component
React receives correctly typed props on both server and client

#Svelte

Svelte components enjoy the same end-to-end type safety as React. Define your database schema and infer types directly from your table definitions using Drizzle:

TS
// db/schema.ts
import { pgTable, text, integer } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: text('id').primaryKey(),
  name: text('name').notNull(),
  avatar: text('avatar')
});

export const stats = pgTable('stats', {
  id: text('id').primaryKey(),
  userId: text('user_id').notNull().references(() => users.id),
  views: integer('views').notNull().default(0),
  revenue: integer('revenue').notNull().default(0)
});

// types/databaseTypes.ts
export type User = typeof users.$inferSelect;
export type Stats = typeof stats.$inferSelect;

Use those types in your server handlers to ensure props match your components:

TS
// backend/server.ts
import { User, Stats } from '../types/databaseTypes';

type DashboardProps = {
  user: User;
  stats: Stats;
};

new Elysia()
  .get('/dashboard', async ({ cookie }) => {
    const user = await getUser(cookie);
    const stats = await getStats(user.id);

    // TypeScript error if props don't match DashboardProps!
    return handleSveltePageRequest({
      indexPath: asset(manifest, 'DashboardIndex'),
      pagePath: asset(manifest, 'DashboardPage'),
      props: { user, stats }
    });
  })
1
Schema → Types
Drizzle infers types directly from your table definitions
2
Types → Server
Your inferred types flow into route handlers and props
3
Props → Component
Svelte receives correctly typed props on both server and client

#Vue

Vue 3's defineProps<T> with TypeScript generics provides complete compile-time type checking. The generic parameter is validated against the props passed from the server, so errors are caught before your code runs.

VUE
// Vue 3 with TypeScript provides complete type safety
// defineProps<T>() ensures compile-time type checking

<script setup lang="ts">
// Types are enforced at compile time
type User = {
  id: string;
  name: string;
  role: 'admin' | 'user';
};

type AdminDashboardProps = {
  user: User;
  systemStats: SystemStats;
};

// TypeScript error if server sends wrong types!
const props = defineProps<AdminDashboardProps>();

// Computed properties are also type-safe
const isAdmin = computed(() => props.user.role === 'admin');
</script>
Server to clientProps types are validated end-to-end
Computed propertiesDerived values maintain type safety
Template type checkingVue Language Server validates template bindings

#Angular

Angular components receive typed props via @Input() decorators. The page handler acts as a typed provider, injecting props into the component at render time. TypeScript catches mismatched props at compile time.

TS
// Types flow from your server to Angular components via props
// The page handler enforces type safety at the boundary
import { defineAngularPage } from '@absolutejs/absolute/angular';
import { Component, inject, InjectionToken } from '@angular/core';

// 1. Define your props type
type SettingsProps = {
  user: User;
  preferences: UserPreferences;
};

// 2. Angular component receives typed props via matching InjectionTokens
export const USER = new InjectionToken<SettingsProps['user']>('USER');
export const PREFERENCES = new InjectionToken<SettingsProps['preferences']>(
  'PREFERENCES'
);

@Component({
  selector: 'app-settings',
  standalone: true,
  template: `
    <div>
      <h1>Settings for {{ user.name }}</h1>
      <label>
        Theme:
        <select [value]="preferences.theme">
          <option value="light">Light</option>
          <option value="dark">Dark</option>
        </select>
      </label>
    </div>
  `
})
export class Settings {
  readonly user = inject(USER);
  readonly preferences = inject(PREFERENCES);
}

export const page = defineAngularPage<SettingsProps>({
  component: Settings
});

// 3. Server passes type-safe props
.get('/settings', async ({ cookie }) => {
  const user = await getUser(cookie);
  const preferences = await getPreferences(user.id);

  // TypeScript error if props don't match SettingsProps!
  return handleAngularPageRequest<typeof SettingsPage>({
    pagePath: asset(manifest, 'Settings'),
    indexPath: asset(manifest, 'SettingsIndex'),
    headTag: generateHeadElement({ title: 'Settings' }),
    props: { user, preferences }
  });
})

#PropsOf Utilities

AbsoluteJS provides helper types that extract the prop types from any component. Use these when you need to reference a component's props without importing the type definition directly.

ReactPropsOf<C>extracts props from a React component type. Works with function components and class components.
SveltePropsOf<C>extracts props from a compiled Svelte component. Infers the exported prop types from the component module.
VuePropsOf<C>extracts props from a Vue component defined with defineProps. Works with both runtime and type-only prop declarations.

These utility types are especially useful when building shared layouts or higher-order components that need to pass through props without manually duplicating type definitions.