elysiajs / eden

Fully type-safe Elysia client
MIT License
169 stars 40 forks source link

Values of Date type are strings in eden treaty #54

Closed lomithrani closed 5 months ago

lomithrani commented 8 months ago

I've noticed that the values of Date type in eden treaty don't match their types (In nested object, not sure about first level depth) Type inference works correctly and the type is Date but the actual value at runtime is a string and not a date (see following screenshots), I've checked server side and the type sent are indeed Dates

image image

Types:

export interface Experience extends Document {
  type: ExperienceType;
  company: Types.ObjectId | Company;
  summary: string;
  title: string;
  projects: Project[]
}

type Project = {
    name: string;
    start?: Date | undefined;
    end?: Date | undefined;
    summary?: string | undefined;
}

I use edenTreaty as such :

import { edenTreaty } from '@elysiajs/eden'
import type { Portfolio } from 'portfolio-api'

import { env } from '$env/dynamic/public'

const api = edenTreaty<Portfolio>(env.API_URL)

export { api }
let { data, error } = await portfolioApi.domain[params.domain].get({
    $fetch: { credentials: 'include' }
  });

Server side :

import Elysia, { t } from 'elysia';
import { Experience, Domain } from '../models/database';

export const domain = new Elysia()
  .get('/domain/:name', async ({ params: { name } }) => {
    const domain = await Domain.findOne({ name: name });
    if (!domain) throw new Error('Domain not found');
    const populatedDomain = await domain.populate<{ experiences: Experience[] }>('experiences');
    return populatedDomain;
  })
ReoHakase commented 7 months ago

I've encountered the exact same problem, so I leave a report here for those who have met it too.

Current Behavior

When receiving a response from an API endpoint, Eden Treaty v2 does not deserialize ISO8601 date strings into Date instances based on the response schema defined using Elysia's t utility. Instead, the current implementation simply returns the result of await response.json().

Consequently, the actual runtime value of the date field is a string, which differs from its expected type definition of Date as specified in the response schema.

image image

Expected Behavior

Eden Treaty v2 should properly deserialize ISO8601 date strings into Date instances, as indicated by the response schema, to maintain consistency between the runtime values and the type definitions.

Possible Solution

To address this issue, consider the following approach:

  1. Modify Elysia to send responses serialized using the superjson library when the Use-Superjson header is present in the request.

  2. Update Eden Treaty v2 to include the Use-Superjson header in its requests to the API endpoints.

  3. Upon receiving the response, Eden Treaty v2 should deserialize the JSON payload using superjson, which will automatically convert ISO8601 date strings into Date instances based on the response schema.

By implementing this solution, Eden Treaty v2 will ensure that the deserialized response data aligns with the expected type definitions, including the proper handling of Date instances.

Related Links

ReoHakase commented 7 months ago

I've written a simple workaround using the superjson library to address the issue of deserializing ISO8601 date strings to Date instances. Let me share the solution with you.

First, install Superjson by running bun add superjson. Then, add the following code snippets to the respective sides:

export const app = new Elysia() .use(swagger()) .use(serverTiming()) .use(log.into()) // ↓ Add this block .mapResponse(({ response, set }) => { const isJson = typeof response === 'object'; if (!isJson) return; const { json, meta } = serialize(response); set.headers['Elysia-Superjson-Meta'] = JSON.stringify(meta); log.info(json); return new Response(JSON.stringify(json)); }) // ...


- Eden Treaty v2 (client)
```ts
import { treaty } from '@elysiajs/eden';
import { deserialize } from 'superjson';
import { describe, expect, it } from 'bun:test';
import { app } from '../src';

const apiClient = treaty(app, {
  // ↓ Add this block
  onResponse: async (response) => {
    const superJsonMeta = response.headers.get('Elysia-Superjson-Meta');
    if (!superJsonMeta) return;
    return deserialize({
      json: await response.json(),
      meta: JSON.parse(superJsonMeta),
    });
  },
});

// ...

According to the tests, seems like Date and bigint get parsed properly now. Would like to get feedbacks👀

mrctrifork commented 6 months ago

Faced the same issue. I believe I have addressed it in this branch of my fork feel free to install it by doing

npm install mrctrifork/eden#fix-serialization