Skip to content

elysians-org/resource

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Elysion Resource

A lightweight, type-safe resource transformation library for TypeScript, inspired by Laravel's API Resources. Easily transform your data structures into JSON:API-compatible responses with support for collections, pagination, and conditional attributes.

Features

  • πŸš€ Type-safe transformations with TypeScript
  • πŸ”„ Collections & Pagination out of the box
  • 🎭 Conditional attributes based on runtime conditions
  • 🧩 Composable resources with merge and when methods
  • πŸ›‘οΈ Null-safe handling throughout
  • πŸ“Š Flexible metadata for collections and pagination

Installation

bun add @elysion/resource
# or
npm install @elysion/resource
# or
yarn add @elysion/resource

Basic Usage

Defining a Resource

import { defineResource } from '@elysion/resource';

interface User {
  id: number;
  username: string;
  email: string;
  isAdmin: boolean;
  posts: Array<{ id: number; title: string }>;
}

const UserResource = defineResource<User, {
  id: number;
  name: string;
  email: string;
  is_admin: boolean;
  post_count: number;
  latest_post?: { id: number; title: string } | null;
}>({
  id: (user) => user.id,
  name: (user) => user.username,
  email: (user) => user.email,
  is_admin: (user) => user.isAdmin,
  post_count: (user) => user.posts?.length ?? 0,
  latest_post: (user) => user.posts[0] ?? null,
});

Using the Resource

Transform a Single Resource

const user = {
  id: 1,
  username: 'johndoe',
  email: 'john@example.com',
  isAdmin: true,
  posts: [
    { id: 1, title: 'Hello World' },
    { id: 2, title: 'Getting Started' }
  ]
};

const transformed = UserResource(user);
/*
{
  id: 1,
  name: 'johndoe',
  email: 'john@example.com',
  is_admin: true,
  post_count: 2,
  latest_post: { id: 1, title: 'Hello World' }
}
*/

Transform a Collection

const users = [
  { id: 1, username: 'alice', email: 'alice@example.com', isAdmin: false, posts: [] },
  { id: 2, username: 'bob', email: 'bob@example.com', isAdmin: true, posts: [{ id: 1, title: 'Hi' }] },
  null // null values are handled gracefully
];

const collection = UserResource.collection(users);
/*
{
  data: [
    {
      id: 1,
      name: 'alice',
      email: 'alice@example.com',
      is_admin: false,
      post_count: 0,
      latest_post: null
    },
    {
      id: 2,
      name: 'bob',
      email: 'bob@example.com',
      is_admin: true,
      post_count: 1,
      latest_post: { id: 1, title: 'Hi' }
    },
    null
  ],
  meta: {
    count: 3,
    valid_count: 2
  }
}
*/

Paginated Responses

const items = Array(15).fill(0).map((_, i) => ({
  id: i + 1,
  username: `user${i + 1}`,
  email: `user${i + 1}@example.com`,
  isAdmin: false,
  posts: []
}));

const paginated = UserResource.paginate(
  items,
  45, // total items
  2,  // current page
  15, // per page
  undefined, // context (optional)
  { custom_meta: 'value' } // additional metadata (optional)
);
/*
{
  data: [
    // ... 15 user objects
  ],
  meta: {
    current_page: 2,
    from: 16,
    last_page: 3,
    per_page: 15,
    to: 30,
    total: 45,
    valid_count: 15,
    custom_meta: 'value'
  }
}
*/

Advanced Usage

Conditional Attributes

const AdminResource = defineResource<User, {
  id: number;
  email: string;
  is_admin: boolean;
  admin_only?: string;
}>({
  id: (user) => user.id,
  email: (user) => user.email,
  is_admin: (user) => user.isAdmin,
  admin_only: (user) => 'Sensitive admin data'
});

// Only include admin_only field for admin users
const SecureUserResource = AdminResource
  .when(
    (user) => user.isAdmin,
    { admin_only: (user) => 'Sensitive admin data' }
  );

// For non-admin users, admin_only will be undefined
const regularUser = { id: 1, username: 'user', email: 'user@example.com', isAdmin: false, posts: [] };
SecureUserResource(regularUser); // { id: 1, email: 'user@example.com', is_admin: false }

Merging Resources

const BaseResource = defineResource<User, { id: number; type: string }>({
  id: (user) => user.id,
  type: () => 'user'
});

const ExtendedResource = BaseResource.merge({
  name: (user) => user.username,
  email: (user) => user.email
});

ExtendedResource({ id: 1, username: 'test', email: 'test@example.com', isAdmin: false, posts: [] });
/*
{
  id: 1,
  type: 'user',
  name: 'test',
  email: 'test@example.com'
}
*/

Context

Pass additional context to your transformers:

const UserWithContextResource = defineResource<
  User,
  { id: number; name: string; is_current_user: boolean }
>({
  id: (user) => user.id,
  name: (user) => user.username,
  is_current_user: (user, context) => user.id === context?.currentUserId
});

const user = { id: 1, username: 'test', email: 'test@example.com', isAdmin: false, posts: [] };
UserWithContextResource(user, { currentUserId: 2 }); // { id: 1, name: 'test', is_current_user: false }

API Reference

defineResource<T, R>(schema: ResourceSchema<T, R>): ResourceTransformer<T, R>

Creates a new resource transformer with the given schema.

Parameters:

  • schema: An object where keys are output field names and values are either:
    • A static value
    • A function that takes the model and optional context and returns the value

Returns:

A function that can transform a model of type T into an object of type R.

The returned function has additional methods:

  • .collection(models, context?, meta?): Transform an array of models
  • .paginate(items, total, currentPage, perPage, context?, meta?): Create a paginated response
  • .when(condition, attributes): Conditionally include attributes
  • .merge(attributes): Merge additional attributes into the schema

License

MIT

bun run dev

Open http://localhost:3000/ with your browser to see the result.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published