diff --git a/src/fetch/index.ts b/src/fetch/index.ts index b8b8484..a7c01c7 100644 --- a/src/fetch/index.ts +++ b/src/fetch/index.ts @@ -1,10 +1,8 @@ import type { Elysia } from 'elysia' -import { isFormalDate, isISO8601, isShortenDate } from '../treaty2' - import { EdenFetchError } from '../errors' import type { EdenFetch } from './types' -import { isNumericString } from '../treaty/utils' +import { parseStringifiedValue } from '../utils/parsingUtils' export type { EdenFetch } from './types' @@ -75,27 +73,7 @@ export const edenFetch = break default: - data = await response.text().then((data) => { - if (isNumericString(data)) return +data - if (data === 'true') return true - if (data === 'false') return false - - if (!data) return data - - // Remove quote from stringified date - const temp = data.replace(/"/g, '') - - if ( - isISO8601.test(temp) || - isFormalDate.test(temp) || - isShortenDate.test(temp) - ) { - const date = new Date(temp) - if (!Number.isNaN(date.getTime())) return date - } - - return data - }) + data = await response.text().then(parseStringifiedValue) } if (response.status > 300) diff --git a/src/treaty/index.ts b/src/treaty/index.ts index 1dd6f20..64dd912 100644 --- a/src/treaty/index.ts +++ b/src/treaty/index.ts @@ -2,8 +2,9 @@ import type { Elysia, InputSchema } from 'elysia' import { EdenFetchError } from '../errors' -import { composePath, isNumericString } from './utils' +import { composePath } from './utils' import type { EdenTreaty } from './types' +import { isNumericString } from '../utils/parsingUtils' export type { EdenTreaty } from './types' diff --git a/src/treaty/utils.ts b/src/treaty/utils.ts index 40d0a4c..3307ca8 100644 --- a/src/treaty/utils.ts +++ b/src/treaty/utils.ts @@ -13,6 +13,3 @@ export const composePath = ( return `${domain}${path}?${q.slice(0, -1)}` } - -export const isNumericString = (message: string) => - message.trim().length !== 0 && !Number.isNaN(Number(message)) diff --git a/src/treaty2/index.ts b/src/treaty2/index.ts index fe54d80..35d30a8 100644 --- a/src/treaty2/index.ts +++ b/src/treaty2/index.ts @@ -2,9 +2,9 @@ import type { Elysia } from 'elysia' import type { Treaty } from './types' -import { composePath, isNumericString } from '../treaty/utils' import { EdenFetchError } from '../errors' import { EdenWS } from './ws' +import { parseStringifiedValue } from '../utils/parsingUtils' const method = [ 'get', @@ -22,13 +22,6 @@ const locals = ['localhost', '127.0.0.1', '0.0.0.0'] const isServer = typeof FileList === 'undefined' -export const isISO8601 = - /(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/ -export const isFormalDate = - /(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)\s(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s\d{2}\s\d{4}\s\d{2}:\d{2}:\d{2}\sGMT(?:\+|-)\d{4}\s\([^)]+\)/ -export const isShortenDate = - /^(?:(?:(?:(?:0?[1-9]|[12][0-9]|3[01])[/\s-](?:0?[1-9]|1[0-2])[/\s-](?:19|20)\d{2})|(?:(?:19|20)\d{2}[/\s-](?:0?[1-9]|1[0-2])[/\s-](?:0?[1-9]|[12][0-9]|3[01]))))(?:\s(?:1[012]|0?[1-9]):[0-5][0-9](?::[0-5][0-9])?(?:\s[AP]M)?)?$/ - const isFile = (v: any) => { if (isServer) return v instanceof Blob @@ -130,46 +123,7 @@ export async function* streamResponse(response: Response) { const data = decoder.decode(value) - if (isNumericString(data)) { - yield +data - continue - } - - if (data === 'true') { - yield true - continue - } - - if (data === 'false') { - yield false - continue - } - - const start = data.charCodeAt(0) - const end = data.charCodeAt(data.length - 1) - - if ((start === 123 && end === 125) || (start === 91 && end === 93)) - try { - yield JSON.parse(data) - - continue - } catch {} - - // Remove quote from stringified date - const temp = data.replace(/"/g, '') - - if ( - isISO8601.test(temp) || - isFormalDate.test(temp) || - isShortenDate.test(temp) - ) { - const date = new Date(temp) - if (!Number.isNaN(date.getTime())) yield date - - continue - } - - yield data + yield parseStringifiedValue(data) } } finally { reader.releaseLock() @@ -431,62 +385,50 @@ const createProxy = ( } } - if (data === null) { - switch ( - response.headers.get('Content-Type')?.split(';')[0] - ) { - case 'text/event-stream': - data = streamResponse(response) - break - - case 'application/json': - data = await response.json() - break - - case 'application/octet-stream': - data = await response.arrayBuffer() - break - - case 'multipart/form-data': - const temp = await response.formData() - - data = {} - temp.forEach((value, key) => { - // @ts-ignore - data[key] = value - }) - - break - - default: - data = await response.text().then((data) => { - if (isNumericString(data)) return +data - if (data === 'true') return true - if (data === 'false') return false - - if (!data) return data - - // Remove quote from stringified date - const temp = data.replace(/"/g, '') - - if ( - isISO8601.test(temp) || - isFormalDate.test(temp) || - isShortenDate.test(temp) - ) { - const date = new Date(temp) - if (!Number.isNaN(date.getTime())) - return date - } - - return data - }) + if (data !== null) { + return { + data, + error, + response, + status: response.status, + headers: response.headers } + } - if (response.status >= 300 || response.status < 200) { - error = new EdenFetchError(response.status, data) - data = null - } + switch ( + response.headers.get('Content-Type')?.split(';')[0] + ) { + case 'text/event-stream': + data = streamResponse(response) + break + + case 'application/json': + data = await response.json() + break + case 'application/octet-stream': + data = await response.arrayBuffer() + break + + case 'multipart/form-data': + const temp = await response.formData() + + data = {} + temp.forEach((value, key) => { + // @ts-ignore + data[key] = value + }) + + break + + default: + data = await response + .text() + .then(parseStringifiedValue) // Since this change, if the string contains a stringified JSOn, it will be parsed as such. Is it OK? + } + + if (response.status >= 300 || response.status < 200) { + error = new EdenFetchError(response.status, data) + data = null } return { diff --git a/src/treaty2/ws.ts b/src/treaty2/ws.ts index edc9a52..e3abfbe 100644 --- a/src/treaty2/ws.ts +++ b/src/treaty2/ws.ts @@ -1,6 +1,6 @@ import type { InputSchema } from 'elysia' import type { Treaty } from './types' -import { isNumericString } from '../treaty/utils' +import { isNumericString } from '../utils/parsingUtils' const ISO8601DateString = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/ @@ -67,23 +67,28 @@ export class EdenWS = {}> { if (start === 91 || start === 123) try { data = JSON.parse(data, (key, value) => { - if(typeof value === 'string' && ISO8601DateString.test(value)) { + if ( + typeof value === 'string' && + ISO8601DateString.test(value) + ) { const d = new Date(value) - if(!Number.isNaN(d.getTime())) - return d + if (!Number.isNaN(d.getTime())) return d } return value }) } catch { // Not Empty } - else if (isNumericString(data)) data = +data else if (data === 'true') data = true else if (data === 'false') data = false else if (data === 'null') data = null // Remove " before parsing - else if (start === 34 && end === 34 && ISO8601DateString.test(data)) + else if ( + start === 34 && + end === 34 && + ISO8601DateString.test(data) + ) data = new Date(data.substring(1, data.length - 1)) listener({ diff --git a/src/utils/parsingUtils.ts b/src/utils/parsingUtils.ts new file mode 100644 index 0000000..d138d5b --- /dev/null +++ b/src/utils/parsingUtils.ts @@ -0,0 +1,82 @@ +const isISO8601 = + /(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/ +const isFormalDate = + /(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)\s(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s\d{2}\s\d{4}\s\d{2}:\d{2}:\d{2}\sGMT(?:\+|-)\d{4}\s\([^)]+\)/ +const isShortenDate = + /^(?:(?:(?:(?:0?[1-9]|[12][0-9]|3[01])[/\s-](?:0?[1-9]|1[0-2])[/\s-](?:19|20)\d{2})|(?:(?:19|20)\d{2}[/\s-](?:0?[1-9]|1[0-2])[/\s-](?:0?[1-9]|[12][0-9]|3[01]))))(?:\s(?:1[012]|0?[1-9]):[0-5][0-9](?::[0-5][0-9])?(?:\s[AP]M)?)?$/ + +export const isNumericString = (message: string) => + message.trim().length !== 0 && !Number.isNaN(Number(message)) + +export const parseStringifiedDate = (value: any) => { + if (typeof value !== 'string') { + return null + } + + // Remove quote from stringified date + const temp = value.replace(/"/g, '') + + if ( + isISO8601.test(temp) || + isFormalDate.test(temp) || + isShortenDate.test(temp) + ) { + const date = new Date(temp) + + if (!Number.isNaN(date.getTime())) { + return date + } + } + + return null +} + +export const isStringifiedObject = (value: string) => { + const start = value.charCodeAt(0) + const end = value.charCodeAt(value.length - 1) + + return (start === 123 && end === 125) || (start === 91 && end === 93) +} + +export const parseStringifiedObject = (data: string) => + JSON.parse(data, (_, value) => { + const date = parseStringifiedDate(value) + + if (date) { + return date + } + + return value + }) + +export const parseStringifiedValue = (value: any) => { + if (!value) { + return value + } + + if (isNumericString(value)) { + return +value + } + + if (value === 'true') { + return true + } + + if (value === 'false') { + return false + } + + const date = parseStringifiedDate(value) + + if (date) { + return date + } + + if (isStringifiedObject(value)) { + try { + return parseStringifiedObject(value) + } catch {} + } + + return value +} diff --git a/test/treaty2.test.ts b/test/treaty2.test.ts index 2b96873..0153038 100644 --- a/test/treaty2.test.ts +++ b/test/treaty2.test.ts @@ -130,6 +130,7 @@ const app = new Elysia() date: t.Date() }) }) + .get('/dateObject', () => ({ date: new Date() })) .get( '/redirect', ({ set }) => (set.redirect = 'http://localhost:8083/true') @@ -190,6 +191,12 @@ describe('Treaty2', () => { expect(data).toEqual(false) }) + it.todo('parse object with date', async () => { + const { data } = await client.dateObject.get() + + expect(data?.date).toBeInstanceOf(Date) + }) + it('post array', async () => { const { data } = await client.array.post(['a', 'b'])