@@ -1,5 +1,5 @@ | |||
{ | |||
"name": "@modal-sh/oatmeal", | |||
"name": "@modal-sh/oatmeal-core", | |||
"version": "0.0.0", | |||
"files": [ | |||
"dist", | |||
@@ -48,5 +48,22 @@ | |||
}, | |||
"dependencies": { | |||
"xml-js": "^1.6.11" | |||
}, | |||
"types": "./dist/types/index.d.ts", | |||
"main": "./dist/cjs/production/index.js", | |||
"module": "./dist/esm/production/index.js", | |||
"exports": { | |||
".": { | |||
"development": { | |||
"require": "./dist/cjs/development/index.js", | |||
"import": "./dist/esm/development/index.js" | |||
}, | |||
"require": "./dist/cjs/production/index.js", | |||
"import": "./dist/esm/production/index.js", | |||
"types": "./dist/types/index.d.ts" | |||
} | |||
}, | |||
"typesVersions": { | |||
"*": {} | |||
} | |||
} |
@@ -3,6 +3,6 @@ export interface TypeOptions { | |||
} | |||
export interface SerializeType<Options extends TypeOptions = TypeOptions> { | |||
serialize: <T>(data: T, options: Options) => Promise<string>; | |||
deserialize: <T>(serialized: string, options: Options) => Promise<T>; | |||
serialize: <T>(data: T, options: Options) => string; | |||
deserialize: <T>(serialized: string, options: Options) => T; | |||
} |
@@ -1,40 +1,38 @@ | |||
import { TypeOptions, SerializeType } from 'src/common'; | |||
import * as applicationJson from 'src/types/application/json'; | |||
import * as applicationXml from 'src/types/application/xml'; | |||
import * as applicationXWwwFormUrlencoded from 'src/types/application/x-www-form-urlencoded'; | |||
interface LoadSerializeModule<T extends SerializeType> { | |||
(): Promise<T>; | |||
} | |||
const TYPES: Record<string, LoadSerializeModule<SerializeType>> = { | |||
'application/json': () => import('src/types/application/json'), | |||
'text/json': () => import('src/types/application/json'), | |||
'application/xml': () => import('src/types/application/xml'), | |||
'text/xml': () => import('src/types/application/xml'), | |||
const TYPES: Record<string, SerializeType> = { | |||
'application/json': applicationJson, | |||
'text/json': applicationJson, | |||
'application/xml': applicationXml, | |||
'text/xml': applicationXml, | |||
'application/x-www-form-urlencoded': applicationXWwwFormUrlencoded, | |||
}; | |||
export const AVAILABLE_TYPES: (keyof typeof TYPES)[] = Object.keys(TYPES); | |||
export const serialize = async <T, O extends TypeOptions = TypeOptions>( | |||
export const serialize = <T, O extends TypeOptions = TypeOptions>( | |||
data: T, | |||
options: O, | |||
): Promise<string> => { | |||
): string => { | |||
const { type: optionsType } = options; | |||
const { [optionsType]: loadType } = TYPES; | |||
if (!loadType) { | |||
const { [optionsType]: serializeType } = TYPES; | |||
if (!serializeType) { | |||
throw new Error(`Unsupported type: ${optionsType}`); | |||
} | |||
const serializeType = await loadType(); | |||
return serializeType.serialize(data, options); | |||
}; | |||
export const deserialize = async <T, O extends TypeOptions = TypeOptions>( | |||
export const deserialize = <T, O extends TypeOptions = TypeOptions>( | |||
serialized: string, | |||
options: O, | |||
): Promise<T> => { | |||
): T => { | |||
const { type: optionsType } = options; | |||
const { [optionsType]: loadType } = TYPES; | |||
if (!loadType) { | |||
const { [optionsType]: serializeType } = TYPES; | |||
if (!serializeType) { | |||
throw new Error(`Unsupported type: ${optionsType}`); | |||
} | |||
const serializeType = await loadType(); | |||
return serializeType.deserialize<T>(serialized, options); | |||
}; |
@@ -9,22 +9,22 @@ interface SerializeApplicationJsonOptions extends CommonApplicationJsonOptions { | |||
replacer?: (key: string, value: unknown) => unknown; | |||
} | |||
export const serialize = async <T, O extends TypeOptions = SerializeApplicationJsonOptions>( | |||
export const serialize = <T, O extends TypeOptions = SerializeApplicationJsonOptions>( | |||
data: T, | |||
options = {} as O, | |||
): Promise<string> => { | |||
): string => { | |||
const theOptions = options as SerializeApplicationJsonOptions; | |||
return Promise.resolve(JSON.stringify(data, theOptions.replacer, theOptions.indent)); | |||
return JSON.stringify(data, theOptions.replacer, theOptions.indent); | |||
}; | |||
interface DeserializeApplicationJsonOptions extends CommonApplicationJsonOptions { | |||
reviver?: (key: string, value: unknown) => unknown; | |||
} | |||
export const deserialize = async <T, O extends TypeOptions = DeserializeApplicationJsonOptions>( | |||
export const deserialize = <T, O extends TypeOptions = DeserializeApplicationJsonOptions>( | |||
serialized: string, | |||
options = {} as O, | |||
): Promise<T> => { | |||
): T => { | |||
const theOptions = options as DeserializeApplicationJsonOptions; | |||
return Promise.resolve(JSON.parse(serialized, theOptions.reviver) as T); | |||
return JSON.parse(serialized, theOptions.reviver) as T; | |||
}; |
@@ -0,0 +1,16 @@ | |||
export const serialize = <T>( | |||
data: T, | |||
): string => { | |||
if (typeof data !== 'object') { | |||
throw new TypeError('Data must be an object'); | |||
} | |||
return new URLSearchParams(data as Record<string, string>).toString(); | |||
} | |||
export const deserialize = <T>( | |||
serialized: string, | |||
): T => { | |||
return Object.fromEntries( | |||
new URLSearchParams(serialized).entries() | |||
) as unknown as T; | |||
} |
@@ -1,4 +1,4 @@ | |||
import type { Element } from 'xml-js'; | |||
import { js2xml, xml2js, type Element } from 'xml-js'; | |||
import { TypeOptions } from 'src/common'; | |||
interface CommonApplicationXmlOptions extends TypeOptions { | |||
@@ -119,11 +119,10 @@ parseElement = (element: Element, allowValueOverride = false): unknown => { | |||
); | |||
}; | |||
export const serialize = async <T, O extends TypeOptions = SerializeApplicationXmlOptions>( | |||
export const serialize = <T, O extends TypeOptions = SerializeApplicationXmlOptions>( | |||
data: T, | |||
options = {} as O, | |||
): Promise<string> => { | |||
const { js2xml } = await import('xml-js'); | |||
): string => { | |||
const { indent, rootElementName = 'root' } = options as SerializeApplicationXmlOptions; | |||
return js2xml( | |||
@@ -139,11 +138,10 @@ interface DeserializeApplicationXmlOptions extends CommonApplicationXmlOptions { | |||
allowValueOverride?: boolean; | |||
} | |||
export const deserialize = async <T, O extends TypeOptions = DeserializeApplicationXmlOptions>( | |||
export const deserialize = <T, O extends TypeOptions = DeserializeApplicationXmlOptions>( | |||
serialized: string, | |||
options = {} as O, | |||
): Promise<T> => { | |||
const { xml2js } = await import('xml-js'); | |||
): T => { | |||
const { allowValueOverride } = options as DeserializeApplicationXmlOptions; | |||
const rawElements = xml2js(serialized, { | |||
compact: false, | |||
@@ -3,65 +3,65 @@ import { deserialize, serialize } from '../../src/types/application/json'; | |||
describe('application/json', () => { | |||
describe('serialize', () => { | |||
it('should serialize a string', async () => { | |||
const result = await serialize('Hello, World!', { type: 'application/json' }); | |||
it('should serialize a string', () => { | |||
const result = serialize('Hello, World!', { type: 'application/json' }); | |||
expect(result).toBe('"Hello, World!"'); | |||
}); | |||
it('should serialize a number', async () => { | |||
const result = await serialize(123, { type: 'application/json' }); | |||
it('should serialize a number', () => { | |||
const result = serialize(123, { type: 'application/json' }); | |||
expect(result).toBe('123'); | |||
}); | |||
it('should serialize a boolean', async () => { | |||
const result = await serialize(true, { type: 'application/json' }); | |||
it('should serialize a boolean', () => { | |||
const result = serialize(true, { type: 'application/json' }); | |||
expect(result).toBe('true'); | |||
}); | |||
it('should serialize an array', async () => { | |||
const result = await serialize([1, 2, 3], { type: 'application/json' }); | |||
it('should serialize an array', () => { | |||
const result = serialize([1, 2, 3], { type: 'application/json' }); | |||
expect(result).toBe('[1,2,3]'); | |||
}); | |||
it('should serialize an object', async () => { | |||
const result = await serialize({ a: 1, b: 2, c: 3 }, { type: 'application/json' }); | |||
it('should serialize an object', () => { | |||
const result = serialize({ a: 1, b: 2, c: 3 }, { type: 'application/json' }); | |||
expect(result).toBe('{"a":1,"b":2,"c":3}'); | |||
}); | |||
it('should serialize an object with indent', async () => { | |||
const result = await serialize({ a: 1, b: 2, c: 3 }, { type: 'application/json', indent: 2 }); | |||
it('should serialize an object with indent', () => { | |||
const result = serialize({ a: 1, b: 2, c: 3 }, { type: 'application/json', indent: 2 }); | |||
expect(result).toBe('{\n "a": 1,\n "b": 2,\n "c": 3\n}'); | |||
}); | |||
}); | |||
describe('deserialize', () => { | |||
it('should deserialize a string', async () => { | |||
const result = await deserialize<string>('"Hello, World!"', { type: 'application/json' }); | |||
it('should deserialize a string', () => { | |||
const result = deserialize<string>('"Hello, World!"', { type: 'application/json' }); | |||
expect(result).toBe('Hello, World!'); | |||
}); | |||
it('should deserialize a number', async () => { | |||
const result = await deserialize<number>('123', { type: 'application/json' }); | |||
it('should deserialize a number', () => { | |||
const result = deserialize<number>('123', { type: 'application/json' }); | |||
expect(result).toBe(123); | |||
}); | |||
it('should deserialize a boolean', async () => { | |||
const result = await deserialize<boolean>('true', { type: 'application/json' }); | |||
it('should deserialize a boolean', () => { | |||
const result = deserialize<boolean>('true', { type: 'application/json' }); | |||
expect(result).toBe(true); | |||
}); | |||
it('should deserialize an array', async () => { | |||
const result = await deserialize<number[]>('[1,2,3]', { type: 'application/json' }); | |||
it('should deserialize an array', () => { | |||
const result = deserialize<number[]>('[1,2,3]', { type: 'application/json' }); | |||
expect(result).toEqual([1, 2, 3]); | |||
}); | |||
it('should deserialize an object', async () => { | |||
const result = await deserialize<{ a: number, b: number, c: number }>('{"a":1,"b":2,"c":3}', { type: 'application/json' }); | |||
it('should deserialize an object', () => { | |||
const result = deserialize<{ a: number, b: number, c: number }>('{"a":1,"b":2,"c":3}', { type: 'application/json' }); | |||
expect(result).toEqual({ a: 1, b: 2, c: 3 }); | |||
}); | |||
it('should deserialize an object with indent', async () => { | |||
const result = await deserialize<{ a: number, b: number, c: number }>( | |||
it('should deserialize an object with indent', () => { | |||
const result = deserialize<{ a: number, b: number, c: number }>( | |||
'{\n "a": 1,\n "b": 2,\n "c": 3\n}', | |||
{ type: 'application/json' }, | |||
); | |||
@@ -0,0 +1,34 @@ | |||
import { describe, expect, it } from 'vitest'; | |||
import { deserialize, serialize } from '../../src/types/application/x-www-form-urlencoded'; | |||
describe('application/x-www-form-urlencoded', () => { | |||
describe('serialize', () => { | |||
it('should throw when serializing a string', () => { | |||
expect(() => serialize('Hello, World!')).toThrow(); | |||
}); | |||
it('should throw when serializing a number', () => { | |||
expect(() => serialize(123)).toThrow(); | |||
}); | |||
it('should throw when serializing a boolean', () => { | |||
expect(() => serialize(true)).toThrow(); | |||
}); | |||
it('should throw when serializing an array', () => { | |||
expect(() => serialize([1, 2, 3])).toThrow(); | |||
}); | |||
it('should serialize an object', () => { | |||
const result = serialize({ a: 1, b: 2, c: 3 }); | |||
expect(result).toBe('a=1&b=2&c=3'); | |||
}); | |||
}); | |||
describe('deserialize', () => { | |||
it('should deserialize an object', () => { | |||
const result = deserialize<{ a: string, b: string, c: string }>('a=1&b=2&c=3'); | |||
expect(result).toEqual({ a: '1', b: '2', c: '3' }); | |||
}); | |||
}); | |||
}); |
@@ -3,38 +3,38 @@ import { serialize, deserialize } from '../../src/types/application/xml'; | |||
describe('application/xml', () => { | |||
describe('serialize', () => { | |||
it('should serialize a string', async () => { | |||
const result = await serialize('Hello, World!', { type: 'application/xml' }); | |||
it('should serialize a string', () => { | |||
const result = serialize('Hello, World!', { type: 'application/xml' }); | |||
expect(result).toBe('<root type="string">Hello, World!</root>'); | |||
}); | |||
it('should serialize a number', async () => { | |||
const result = await serialize(123, { type: 'application/xml' }); | |||
it('should serialize a number', () => { | |||
const result = serialize(123, { type: 'application/xml' }); | |||
expect(result).toBe('<root type="number">123</root>'); | |||
}); | |||
it('should serialize a boolean', async () => { | |||
const result = await serialize(true, { type: 'application/xml' }); | |||
it('should serialize a boolean', () => { | |||
const result = serialize(true, { type: 'application/xml' }); | |||
expect(result).toBe('<root type="boolean">true</root>'); | |||
}); | |||
it('should serialize an array', async () => { | |||
const result = await serialize([1, 2, 3], { type: 'application/xml' }); | |||
it('should serialize an array', () => { | |||
const result = serialize([1, 2, 3], { type: 'application/xml' }); | |||
expect(result).toBe('<root array="array"><_0 type="number">1</_0><_1 type="number">2</_1><_2 type="number">3</_2></root>'); | |||
}); | |||
it('should serialize an object', async () => { | |||
const result = await serialize({ a: 1, b: 2, c: 3 }, { type: 'application/xml' }); | |||
it('should serialize an object', () => { | |||
const result = serialize({ a: 1, b: 2, c: 3 }, { type: 'application/xml' }); | |||
expect(result).toBe('<root type="object"><a type="number">1</a><b type="number">2</b><c type="number">3</c></root>'); | |||
}); | |||
it('should serialize an object with a custom root element name', async () => { | |||
const result = await serialize({ a: 1, b: 2, c: 3 }, { type: 'application/xml', rootElementName: 'fsh', }); | |||
it('should serialize an object with a custom root element name', () => { | |||
const result = serialize({ a: 1, b: 2, c: 3 }, { type: 'application/xml', rootElementName: 'fsh', }); | |||
expect(result).toBe('<fsh type="object"><a type="number">1</a><b type="number">2</b><c type="number">3</c></fsh>'); | |||
}); | |||
it('should serialize an object with indent', async () => { | |||
const result = await serialize({ a: 1, b: 2, c: 3 }, { type: 'application/xml', indent: 2 }); | |||
it('should serialize an object with indent', () => { | |||
const result = serialize({ a: 1, b: 2, c: 3 }, { type: 'application/xml', indent: 2 }); | |||
expect(result).toBe(`<root type="object"> | |||
<a type="number">1</a> | |||
<b type="number">2</b> | |||
@@ -44,28 +44,28 @@ describe('application/xml', () => { | |||
}); | |||
describe('deserialize', () => { | |||
it('should deserialize a string', async () => { | |||
const result = await deserialize<string>('<root type="string">Hello, World!</root>', { type: 'application/xml' }); | |||
it('should deserialize a string', () => { | |||
const result = deserialize<string>('<root type="string">Hello, World!</root>', { type: 'application/xml' }); | |||
expect(result).toBe('Hello, World!'); | |||
}); | |||
it('should deserialize a number', async () => { | |||
const result = await deserialize<number>('<root type="number">123</root>', { type: 'application/xml' }); | |||
it('should deserialize a number', () => { | |||
const result = deserialize<number>('<root type="number">123</root>', { type: 'application/xml' }); | |||
expect(result).toBe(123); | |||
}); | |||
it('should deserialize a boolean', async () => { | |||
const result = await deserialize<boolean>('<root type="boolean">true</root>', { type: 'application/xml' }); | |||
it('should deserialize a boolean', () => { | |||
const result = deserialize<boolean>('<root type="boolean">true</root>', { type: 'application/xml' }); | |||
expect(result).toBe(true); | |||
}); | |||
it('should deserialize an array', async () => { | |||
const result = await deserialize<number[]>('<root array="array"><_0 type="number">1</_0><_1 type="number">2</_1><_2 type="number">3</_2></root>', { type: 'application/xml' }); | |||
it('should deserialize an array', () => { | |||
const result = deserialize<number[]>('<root array="array"><_0 type="number">1</_0><_1 type="number">2</_1><_2 type="number">3</_2></root>', { type: 'application/xml' }); | |||
expect(result).toEqual([1, 2, 3]); | |||
}); | |||
it('should deserialize an object', async () => { | |||
const result = await deserialize<{ a: number, b: number, c: number }>( | |||
it('should deserialize an object', () => { | |||
const result = deserialize<{ a: number, b: number, c: number }>( | |||
'<root type="object"><a type="number">1</a><b type="number">2</b><c type="number">3</c></root>', | |||
{ type: 'application/xml' }, | |||
); | |||