<!-- Markdown mirror of https://justscale.sh/docs/advanced/types -->

# TypeScript Utilities

Advanced TypeScript patterns and utilities in JustScale

JustScale leverages TypeScript's advanced type system to provide compile-time guarantees. Understanding these type utilities helps you build more robust applications and create custom abstractions.

## Dependency Type Utilities

JustScale provides utilities for working with dependencies and their resolved instances.

### InstanceOf and ResolvedDeps

These utilities extract instance types from service tokens and convert dependency maps to resolved instances:

src/example.tsTypeScript

```typescript
import type { InstanceOf, ResolvedDeps } from "@justscale/core";
import { defineService } from "@justscale/core";
import { UserService } from "./services/user-service";

// InstanceOf: Extract instance type from a service token
type UserServiceInstance = InstanceOf<typeof UserService>;
// => { get: (ref: Ref<User>) => Promise<User>, ... }

// Works with classes too
class DatabaseService {
  query(sql: string) { /* ... */ }
}

type DbInstance = InstanceOf<typeof DatabaseService>;
// => DatabaseService

// ResolvedDeps: Convert dependency map to resolved instances
const deps = {
  users: UserService,
  db: DatabaseService,
};

type Resolved = ResolvedDeps<typeof deps>;
// => {
//   users: UserServiceInstance,
//   db: DatabaseService,
// }

// Used internally by defineService
class MyService extends defineService({
  inject: { users: UserService, db: DatabaseService },
  factory: (deps) => {
    // deps has type ResolvedDeps<{ users: ..., db: ... }>
    deps.users.get(User.ref`123`);
    return { /* ... */ };
  },
}) {}
```

### ExtractDeps and ExtractAllDeps

Extract direct and transitive dependencies from services:

deps-example.tsTypeScript

```typescript
import type { ExtractDeps, ExtractAllDeps } from "@justscale/core";
import { defineService } from "@justscale/core";

// Database has no dependencies
class Database extends defineService({ inject: {}, factory: () => ({}) }) {}

// UserService depends on Database
class UserService extends defineService({
  inject: { db: Database },
  factory: ({ db }) => ({}),
}) {}

// AuthService depends on UserService (and transitively on Database)
class AuthService extends defineService({
  inject: { users: UserService },
  factory: ({ users }) => ({}),
}) {}

// ExtractDeps: Get direct dependencies only
type UserServiceDeps = ExtractDeps<typeof UserService>;
// => { db: typeof Database }

// ExtractAllDeps: Get all transitive dependencies
type AllDeps = ExtractAllDeps<typeof AuthService>;
// => typeof UserService | typeof Database
```

## Route Context Types

Route handlers receive a context object with dependencies, parameters, and transport-specific properties. Understanding these types helps with type-safe middleware and handlers.

### HandlerContext

The full context type available in route handlers. Combines dependencies, transport context, and built-in properties.

src/controllers/users.tsTypeScript

```typescript
import type { HandlerContext } from "@justscale/core";
import { createController } from "@justscale/core";
import { Get } from '@justscale/http';
import { UserService } from "../services/user-service";
import { AuthService } from "../services/auth-service";

const UsersController = createController("/users", {
  inject: { users: UserService, auth: AuthService },
  routes: (services) => ({
    list: Get("/").handle((ctx) => {
      // ctx has type:
      // HandlerContext<{ users: UserService, auth: AuthService }>
      //
      // Which expands to:
      // {
      //   users: UserServiceInstance,
      //   auth: AuthServiceInstance,
      //   params: Record<string, string>,
      //   logger: Logger,
      //   res: JsonResponse,  // from HTTP plugin
      //   req: IncomingMessage,
      //   // ... other transport properties
      // }

      ctx.users.findAll();
      ctx.logger.info("Listing users");
      ctx.res.json({ users: [] });
    }),
  }),
});
```

### ExtractParams and Prettify

Type utilities for path parameter extraction and type cleaning:

type-utils.tsTypeScript

```typescript
import type { ExtractParams, Prettify } from "@justscale/core";
import { createController } from "@justscale/core";
import { Get } from '@justscale/http';

// ExtractParams: Extract path parameters from route strings
type Params1 = ExtractParams<"/users/:id">;
// => { id: string }

type Params2 = ExtractParams<"/users/:userId/posts/:postId">;
// => { userId: string, postId: string }

type Params3 = ExtractParams<"/api/v1/items">;
// => {} (no params)

// Used automatically in route handlers
const UsersController = createController('/users', {
  routes: () => ({
    getOne: Get('/:id').handle(({ params }) => {
      params.id; // TypeScript knows this exists!
    }),
  }),
});

// Prettify: Flatten intersection types for better tooltips
type Complex = { a: string } & { b: number } & { c: boolean };
// Tooltip shows: { a: string } & { b: number } & { c: boolean }

type Clean = Prettify<Complex>;
// Tooltip shows: { a: string, b: number, c: boolean }
```

## Middleware Type Utilities

Middleware types help ensure type-safe context accumulation.

### Middleware and MiddlewareDef

Type-safe middleware with and without dependency injection:

Files

srcmiddlewareauth.ts

controllersprofile.ts

srcmiddlewareauth.ts

controllersprofile.ts

src/middleware/auth.tsTypeScript

```typescript
import type { Middleware, MiddlewareDef } from "@justscale/core";
import { createMiddleware } from "@justscale/core";
import { TokenService } from "../services/token-service";

// Simple middleware without dependencies
const parseAuth: Middleware<
  { req: IncomingMessage },
  { user: User }
> = async (ctx) => {
  const token = ctx.req.headers.authorization;
  const user = await validateToken(token);
  return { user };
};

// Middleware with dependency injection
export const AuthMiddleware: MiddlewareDef<
  { user: User },
  { tokens: TokenService }
> = createMiddleware({
  inject: { tokens: TokenService },
  handler: ({ tokens }) => async (ctx) => {
    const user = await tokens.validate(ctx.req.headers.authorization);
    return { user };
  },
});
```

## Feature Type Utilities

Features use advanced types to handle dependency graphs and configuration.

### PendingFeature and ResolvedFeatureDeps

Type utilities for working with features and their dependencies:

features.tsTypeScript

```typescript
import { createFeatureBuilder } from "@justscale/core";
import { ModelRepository } from "@justscale/core/models";

// FeatureToken: result of createFeatureBuilder()...provides()
// Tracks required tokens and provided tokens at the type level

const AuthFeature = createFeatureBuilder()
  .name('auth')
  .requires(ModelRepository.of(User))
  .requires(ModelRepository.of(Session))
  .requires(AbstractEmailSender)
  .provides((b) => b
    .add(PasswordService)
    .add(UserService)
    .add(SessionService)
    .add(AuthController)
  );

// AuthFeature is a FeatureToken<TRequires, TProvides>
// TypeScript tracks what it needs and what it provides:
//   TRequires = [ModelRepository<User>, ModelRepository<Session>, AbstractEmailSender]
//   TProvides = [PasswordService, UserService, SessionService, AuthController]

// Features that require other features get their provides available
const AdminFeature = createFeatureBuilder()
  .name('admin')
  .requires(AuthFeature)  // UserService, SessionService, etc. now available
  .provides((b) => b
    .add(AdminService)    // Can depend on AuthFeature's services
    .add(AdminController)
  );

// In the app, dependencies are checked at compile time
import JustScale from "@justscale/core";

const app = JustScale()
  .add(AuthFeature)
  .add(AdminFeature)  // Type error if AuthFeature is missing
  .build();
```

## Validation Types

JustScale uses type-level validation to catch errors at compile time.

### ValidateDeps and ValidateDepsNoConflict

Compile-time validation ensures dependencies are provided and don't conflict:

validation-example.tsTypeScript

```typescript
import JustScale, { defineService, createController } from "@justscale/core";
import { Get } from '@justscale/http';

class Database extends defineService({
  inject: {},
  factory: () => ({ query: async (sql: string) => [] }),
}) {}

class UserService extends defineService({
  inject: { db: Database },
  factory: ({ db }) => ({ findAll: async () => [] }),
}) {}

const UsersController = createController("/users", {
  inject: { users: UserService },
  routes: (services) => ({
    list: Get('/').handle(({ res }) => res.json({ users: [] })),
  }),
});

// RequiresSatisfied (compile-time): builder rejects incomplete graphs
const badApp = JustScale()
  .add(UsersController)   // type error: UserService (and Database) not provided yet
  .build();

// Correct: add dependencies before the dependents
const goodApp = JustScale()
  .add(Database)
  .add(UserService)
  .add(UsersController)
  .build();

// ValidateDepsNoConflict: This will error at compile time
const BadController = createController("/bad", {
  inject: {
    users: UserService,
    res: ResponseService, // Error! 'res' is reserved
  },
  routes: (services) => ({ /* ... */ }),
});
// Error: Property '__context_conflict__' exists with conflicting: 'res'
```

## Type Utilities Reference

| Utility | Purpose | Package |
| - | - | - |
| InstanceOf | Extract instance type from service token | @justscale/core |
| ResolvedDeps | Map dependency tokens to instances | @justscale/core |
| ExtractDeps | Get dependencies from service/controller | @justscale/core |
| ExtractAllDeps | Get transitive dependencies | @justscale/core |
| HandlerContext | Full route handler context type | @justscale/core |
| ExtractParams | Extract params from path string | @justscale/core |
| Prettify | Flatten intersection types | @justscale/core |

## Building Type-Safe Abstractions

Use these utilities to build your own type-safe wrappers and helpers.

cached-service.tsTypeScript

```typescript
import type { InstanceOf, ResolvedDeps } from "@justscale/core";
import { defineService } from "@justscale/core";

// Type-safe service factory wrapper
function createCachedService<
  TDeps extends Record<string, ServiceToken>,
  TInstance
>(config: {
  inject: TDeps;
  factory: (deps: ResolvedDeps<TDeps>) => TInstance;
  ttl?: number;
}) {
  const cache = new Map<string, { value: TInstance; expires: number }>();

  return defineService({
    inject: config.inject,
    factory: (deps) => {
      const instance = config.factory(deps);
      // Wrap methods with caching logic
      return new Proxy(instance, { /* ... */ });
    },
  });
}

// Use it
class Database extends defineService({
  inject: {},
  factory: () => ({
    query: async (sql: string, params: any[]) => {
      return [{ id: "1", name: "Alice" }];
    },
  }),
}) {}

const CachedUserService = createCachedService({
  inject: { db: Database },
  factory: ({ db }) => ({
    get: (ref: Ref<User>) => db.query(`SELECT * FROM users WHERE id = ?`, [ref.id]),
  }),
  ttl: 60000,
});
// Fully typed, with caching!
```

ℹ️Info

**Type Safety First:** JustScale's type utilities enable compile-time dependency validation, eliminating entire classes of runtime errors. The framework fails fast during development, not in production.

## Next Steps

- Plugins
- Debugging
- Services
