diff --git a/.eslintrc.js b/.eslintrc.js index 90e10f1..49558c6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,5 +1,9 @@ -/* @flow */ - module.exports = { - extends: "@krakenjs/eslint-config-grumbler/eslintrc-browser", + extends: + "./node_modules/@krakenjs/eslint-config-grumbler/eslintrc-typescript.js", + + rules: { + "@typescript-eslint/keyword-spacing": "off", + // off for initial ts conversion + }, }; diff --git a/.flowconfig b/.flowconfig deleted file mode 100644 index 522fa48..0000000 --- a/.flowconfig +++ /dev/null @@ -1,14 +0,0 @@ -[ignore] -.*/node_modules/babel-plugin-flow-runtime -.*/node_modules/flow-runtime -.*/node_modules/npm -.*/node_modules/eslint-plugin-compat -.*/node_modules/jsonlint -.*/node_modules/resolve -.*/dist/module -[include] -[libs] -flow-typed -[options] -module.name_mapper='^src\(.*\)$' -> '/src/\1' -experimental.const_params=false diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ca59cc9..6b992e4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,9 +27,6 @@ jobs: - name: 👕 Lint commit messages uses: wagoid/commitlint-github-action@v4 - - name: ▶️ Run flow-typed script - run: npm run flow-typed - - name: ▶️ Run build script run: npm run build diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..dd684c7 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + extends: "@krakenjs/babel-config-grumbler/babelrc-browser", + presets: ["@krakenjs/babel-config-grumbler/flow-ts-babel-preset"], +}; diff --git a/babel.config.json b/babel.config.json deleted file mode 100644 index 5f06501..0000000 --- a/babel.config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "@krakenjs/babel-config-grumbler/babelrc-browser" -} diff --git a/karma.conf.js b/karma.conf.js deleted file mode 100644 index fd591ed..0000000 --- a/karma.conf.js +++ /dev/null @@ -1,15 +0,0 @@ -/* @flow */ -/* eslint import/no-default-export: off */ - -import { getKarmaConfig } from "@krakenjs/karma-config-grumbler"; - -import { WEBPACK_CONFIG_TEST } from "./webpack.config"; - -export default function configKarma(karma: Object) { - const karmaConfig = getKarmaConfig(karma, { - basePath: __dirname, - webpack: WEBPACK_CONFIG_TEST, - }); - - karma.set(karmaConfig); -} diff --git a/package.json b/package.json index 66d921a..bc45ab4 100644 --- a/package.json +++ b/package.json @@ -3,25 +3,27 @@ "version": "2.0.0", "description": "Javascript module template.", "main": "index.js", + "types": "dist/types/index.d.ts", "scripts": { - "setup": "npm install && npm run flow-typed", - "lint": "eslint src/ test/ *.js --ext .js,.jsx", - "flow-typed": "rm -rf ./flow-typed && flow-typed install && flow-typed install mocha@4", - "flow": "flow", + "build": "npm run test && npm run babel && npm run webpack && npm run build:types", + "build:flow": "find ./dist -type f -not -path './node_modules/*' -name '*.d.ts' -exec sh -c 'flowgen --add-flow-header $1 -o ${1%.*.*}.js.flow' _ '{}' \\;", + "build:tsc": "tsc src/**/** --outDir ./dist/esm --declaration --emitDeclarationOnly --strict", + "build:types": "npm run build:tsc && npm run build:flow", + "webpack": "cross-env NODE_ENV=production babel-node --plugins=transform-es2015-modules-commonjs ./node_modules/.bin/webpack --progress", + "babel": "cross-env NODE_ENV=production babel src/ --out-dir ./dist/esm/ --extensions .ts,.tsx", + "tsc": "tsc", "format": "prettier --write --ignore-unknown .", "format:check": "prettier --check .", - "karma": "cross-env NODE_ENV=test babel-node --plugins=transform-es2015-modules-commonjs ./node_modules/.bin/karma start", - "babel": "babel src/ --out-dir dist/module", - "webpack": "babel-node --plugins=transform-es2015-modules-commonjs ./node_modules/.bin/webpack --progress", - "test": "npm run format:check && npm run lint && npm run flow-typed && npm run flow && npm run karma", - "build": "npm run test && npm run babel && npm run webpack", + "test": "npm run format:check && npm run lint && npm run tsc --no-emit && npm run vitest", + "lint": "eslint --ext ts,tsx,js,jsx src/ test/", "clean": "rimraf dist coverage", - "reinstall": "rimraf flow-typed && rimraf node_modules && npm install && flow-typed install", - "debug": "cross-env NODE_ENV=debug", - "prepare": "husky install", "prerelease": "npm run clean && npm run build && git add dist && git commit -m 'ci: check in dist folder' || echo 'Nothing to distribute'", "release": "standard-version", - "postrelease": "git push && git push --follow-tags && npm publish" + "postrelease": "git push && git push --follow-tags && npm publish", + "debug": "cross-env NODE_ENV=debug", + "prepare": "husky install", + "vitest": "vitest run --dom --coverage", + "vitest:watch": "vitest watch --dom --coverage --ui" }, "standard-version": { "types": [ @@ -89,17 +91,25 @@ ], "readmeFilename": "README.md", "devDependencies": { - "@commitlint/cli": "^16.2.1", - "@commitlint/config-conventional": "^16.2.1", - "@krakenjs/grumbler-scripts": "^8.0.4", - "cross-env": "^7.0.3", - "flow-bin": "0.155.0", - "flow-typed": "^3.8.0", - "husky": "^7.0.4", - "jest": "^29.3.1", - "lint-staged": "^12.4.0", - "prettier": "^2.6.2", - "standard-version": "^9.3.2" + "@commitlint/cli": "^17.3.0", + "@commitlint/config-conventional": "^17.3.0", + "@krakenjs/babel-config-grumbler": "^8.0.7", + "@krakenjs/eslint-config-grumbler": "^8.0.7", + "@krakenjs/typescript-config-grumbler": "^8.0.7", + "@krakenjs/webpack-config-grumbler": "^8.1.0-alpha.1", + "@vitest/coverage-c8": "^0.25.3", + "@vitest/ui": "^0.25.3", + "flowgen": "^1.20.1", + "happy-dom": "^7.7.0", + "husky": "^8.0.2", + "lint-staged": "^13.0.3", + "prettier": "^2.8.0", + "standard-version": "^9.5.0", + "ts-node": "^10.9.1", + "typescript": "4.9.3", + "utility-types": "^3.10.0", + "vite": "^3.2.4", + "vitest": "^0.25.3" }, "lint-staged": { "*": "prettier --write --ignore-unknown" diff --git a/src/common.js b/src/common.ts similarity index 65% rename from src/common.js rename to src/common.ts index 73d3bfb..e4a637a 100644 --- a/src/common.js +++ b/src/common.ts @@ -1,17 +1,20 @@ -/* @flow */ +import type { $Values } from "utility-types"; import { TYPE } from "./constants"; import type { CustomSerializedType } from "./types"; -export function isSerializedType(item: mixed): boolean { +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any +export function isSerializedType(item: any): boolean { return ( typeof item === "object" && item !== null && + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access typeof item.__type__ === "string" ); } -export function determineType(val: mixed): $Values | void { +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any +export function determineType(val: any): $Values | undefined { if (typeof val === "undefined") { return TYPE.UNDEFINED; } @@ -33,16 +36,15 @@ export function determineType(val: mixed): $Values | void { return TYPE.ERROR; } + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (typeof val.then === "function") { return TYPE.PROMISE; } - // $FlowFixMe method-unbinding if (Object.prototype.toString.call(val) === "[object RegExp]") { return TYPE.REGEX; } - // $FlowFixMe method-unbinding if (Object.prototype.toString.call(val) === "[object Date]") { return TYPE.DATE; } @@ -63,7 +65,7 @@ export function determineType(val: mixed): $Values | void { } } -export function serializeType( +export function serializeType( type: T, val: V ): CustomSerializedType { diff --git a/src/constants.js b/src/constants.js deleted file mode 100644 index 88ac8be..0000000 --- a/src/constants.js +++ /dev/null @@ -1,16 +0,0 @@ -/* @flow */ - -export const TYPE = { - FUNCTION: ("function": "function"), - ERROR: ("error": "error"), - PROMISE: ("promise": "promise"), - REGEX: ("regex": "regex"), - DATE: ("date": "date"), - ARRAY: ("array": "array"), - OBJECT: ("object": "object"), - STRING: ("string": "string"), - NUMBER: ("number": "number"), - BOOLEAN: ("boolean": "boolean"), - NULL: ("null": "null"), - UNDEFINED: ("undefined": "undefined"), -}; diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..ea551df --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,14 @@ +export const TYPE = { + FUNCTION: "function", + ERROR: "error", + PROMISE: "promise", + REGEX: "regex", + DATE: "date", + ARRAY: "array", + OBJECT: "object", + STRING: "string", + NUMBER: "number", + BOOLEAN: "boolean", + NULL: "null", + UNDEFINED: "undefined", +} as const; diff --git a/src/deserialize.js b/src/deserialize.js deleted file mode 100644 index 4b2e075..0000000 --- a/src/deserialize.js +++ /dev/null @@ -1,102 +0,0 @@ -/* @flow */ - -import type { Thenable } from "./types"; -import { TYPE } from "./constants"; -import { determineType, isSerializedType } from "./common"; -import { - deserializeFunction, - deserializeError, - type SerializedError, - deserializePromise, - deserializeRegex, - type SerializedRegex, - deserializeDate, - type SerializedDate, - deserializeArray, - deserializeObject, - deserializeString, - deserializeNumber, - deserializeBoolean, - deserializeNull, - deserializeUndefined, -} from "./serializers"; - -type Deserializer = (serializedValue: S, key: string) => V; -type PrimitiveDeserializer = (serializedValue: S, key: string) => V; - -type Deserializers = { - function?: Deserializer, - error?: Deserializer, - promise?: Deserializer, - regex?: Deserializer, - date?: Deserializer, - array?: PrimitiveDeserializer<$ReadOnlyArray>, - object?: PrimitiveDeserializer, - string?: PrimitiveDeserializer, - number?: PrimitiveDeserializer, - boolean?: PrimitiveDeserializer, - null?: PrimitiveDeserializer, - [string]: Deserializer, - undefined?: PrimitiveDeserializer, -}; - -// $FlowFixMe -const DESERIALIZER: Deserializers = { - [TYPE.FUNCTION]: deserializeFunction, - [TYPE.ERROR]: deserializeError, - [TYPE.PROMISE]: deserializePromise, - [TYPE.REGEX]: deserializeRegex, - [TYPE.DATE]: deserializeDate, - [TYPE.ARRAY]: deserializeArray, - [TYPE.OBJECT]: deserializeObject, - [TYPE.STRING]: deserializeString, - [TYPE.NUMBER]: deserializeNumber, - [TYPE.BOOLEAN]: deserializeBoolean, - [TYPE.NULL]: deserializeNull, - [TYPE.UNDEFINED]: deserializeUndefined, -}; - -// $FlowFixMe -const defaultDeserializers: Deserializers = {}; - -export function deserialize( - str: string, - deserializers: Deserializers = defaultDeserializers -): T { - if (str === TYPE.UNDEFINED) { - // $FlowFixMe - return; - } - - function replacer(key, val): ?mixed { - if (isSerializedType(this)) { - return val; - } - - let type; - let value; - - if (isSerializedType(val)) { - type = val.__type__; - value = val.__val__; - } else { - type = determineType(val); - value = val; - } - - if (!type) { - return value; - } - - // $FlowFixMe - const deserializer = deserializers[type] || DESERIALIZER[type]; - - if (!deserializer) { - return value; - } - - return deserializer(value, key); - } - - return JSON.parse(str, replacer); -} diff --git a/src/deserialize.ts b/src/deserialize.ts new file mode 100644 index 0000000..8a2ab5b --- /dev/null +++ b/src/deserialize.ts @@ -0,0 +1,108 @@ +import { TYPE } from "./constants"; +import { determineType, isSerializedType } from "./common"; +import type { + SerializedError, + SerializedRegex, + SerializedDate, +} from "./serializers"; +import { + deserializeFunction, + deserializeError, + deserializePromise, + deserializeRegex, + deserializeDate, + deserializeArray, + deserializeObject, + deserializeString, + deserializeNumber, + deserializeBoolean, + deserializeNull, + deserializeUndefined, +} from "./serializers"; +import type { CustomSerializedType } from "./types"; + +type Deserializer = (serializedValue: S, key: string) => V; +type PrimitiveDeserializer = (serializedValue: S, key: string) => V; + +type Deserializers = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: Deserializer; + function: Deserializer; + error: Deserializer; + promise: Deserializer; + regex: Deserializer; + date: Deserializer; + array: PrimitiveDeserializer; + object: PrimitiveDeserializer>; + string: PrimitiveDeserializer; + number: PrimitiveDeserializer; + boolean: PrimitiveDeserializer; + null: PrimitiveDeserializer; + undefined: PrimitiveDeserializer; +}; + +const DESERIALIZER: Deserializers = { + [TYPE.FUNCTION]: deserializeFunction, + [TYPE.ERROR]: deserializeError, + [TYPE.PROMISE]: deserializePromise, + [TYPE.REGEX]: deserializeRegex, + [TYPE.DATE]: deserializeDate, + [TYPE.ARRAY]: deserializeArray, + [TYPE.OBJECT]: deserializeObject, + [TYPE.STRING]: deserializeString, + [TYPE.NUMBER]: deserializeNumber, + [TYPE.BOOLEAN]: deserializeBoolean, + [TYPE.NULL]: deserializeNull, + [TYPE.UNDEFINED]: deserializeUndefined, +}; + +const defaultDeserializers: Partial = {}; + +export function deserialize( + str: string, + deserializers: Partial = defaultDeserializers +): T { + if (str === TYPE.UNDEFINED) { + // the lib allows undefined returns but doesnt expect type assertions for it. need fixing + // @ts-expect-error - we need to update the definitiona of this to allow for undefined because of this but itll take a lot of work + return; + } + + function replacer( + key: string, + val: CustomSerializedType + ): unknown { + // @ts-expect-error - function this has unknown caller + if (isSerializedType(this)) { + return val; + } + + let type; + let value; + + if (isSerializedType(val)) { + type = val.__type__; + value = val.__val__; + } else { + type = determineType(val); + value = val; + } + + if (!type) { + return value; + } + + // dynamic code execution + // eslint-disable-next-line security/detect-object-injection + const deserializer = deserializers[type] ?? DESERIALIZER[type]; + + if (!deserializer) { + return value; + } + + return deserializer(value, key); + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return JSON.parse(str, replacer); +} diff --git a/src/index.js b/src/index.ts similarity index 92% rename from src/index.js rename to src/index.ts index b8594de..bcbad86 100644 --- a/src/index.js +++ b/src/index.ts @@ -1,5 +1,3 @@ -/* @flow */ - export * from "./serialize"; export * from "./deserialize"; export * from "./serializers"; diff --git a/src/serialize.js b/src/serialize.ts similarity index 55% rename from src/serialize.js rename to src/serialize.ts index a054f56..6133a75 100644 --- a/src/serialize.js +++ b/src/serialize.ts @@ -1,4 +1,4 @@ -/* @flow */ +import type { $Values } from "utility-types"; import { TYPE } from "./constants"; import type { @@ -7,15 +7,17 @@ import type { NativeSerializedType, } from "./types"; import { determineType, isSerializedType } from "./common"; +import type { + SerializedError, + SerializedRegex, + SerializedDate, +} from "./serializers"; import { serializeFunction, serializeError, - type SerializedError, serializePromise, serializeRegex, - type SerializedRegex, serializeDate, - type SerializedDate, serializeArray, serializeObject, serializeString, @@ -25,48 +27,55 @@ import { serializeUndefined, } from "./serializers"; -type NativeSerializer> = ( +type NativeSerializer> = ( value: V, key: string ) => NativeSerializedType; -type CustomSerializer = ( +type CustomSerializer = ( value: V, key: string ) => CustomSerializedType; -type PrimitiveSerializer = (value: V, key: string) => S; -type CustomOrPrimitiveSerializer = - | CustomSerializer - | PrimitiveSerializer; -type NativeOrCustomOrPrimitiveSerializer = - | NativeSerializer - | CustomOrPrimitiveSerializer; +type PrimitiveSerializer = (value: V, key: string) => S; +type CustomOrPrimitiveSerializer = + | CustomSerializer + | PrimitiveSerializer; +type NativeOrCustomOrPrimitiveSerializer< + V, + S, + T extends $Values +> = NativeSerializer | CustomOrPrimitiveSerializer; -type Serializers = {| - function?: CustomOrPrimitiveSerializer, +type Serializers = { + // allow Function keyword as we do expect a generic Function type + // eslint-disable-next-line @typescript-eslint/ban-types + function?: CustomOrPrimitiveSerializer; error?: NativeOrCustomOrPrimitiveSerializer< Error, SerializedError, typeof TYPE.ERROR - >, - promise?: CustomOrPrimitiveSerializer, + >; + promise?: CustomOrPrimitiveSerializer; regex?: NativeOrCustomOrPrimitiveSerializer< RegExp, SerializedRegex, typeof TYPE.REGEX - >, + >; date?: NativeOrCustomOrPrimitiveSerializer< Date, SerializedDate, typeof TYPE.DATE - >, - array?: CustomOrPrimitiveSerializer<$ReadOnlyArray, typeof TYPE.ARRAY>, - object?: CustomOrPrimitiveSerializer, - string?: CustomOrPrimitiveSerializer, - number?: CustomOrPrimitiveSerializer, - boolean?: CustomOrPrimitiveSerializer, - null?: CustomOrPrimitiveSerializer, - undefined?: CustomOrPrimitiveSerializer, -|}; + >; + array?: CustomOrPrimitiveSerializer; + object?: CustomOrPrimitiveSerializer< + Record, + typeof TYPE.OBJECT + >; + string?: CustomOrPrimitiveSerializer; + number?: CustomOrPrimitiveSerializer; + boolean?: CustomOrPrimitiveSerializer; + null?: CustomOrPrimitiveSerializer; + undefined?: CustomOrPrimitiveSerializer; +}; const SERIALIZER: Serializers = { [TYPE.FUNCTION]: serializeFunction, @@ -83,16 +92,18 @@ const SERIALIZER: Serializers = { [TYPE.UNDEFINED]: serializeUndefined, }; -// $FlowFixMe const defaultSerializers: Serializers = {}; -export function serialize( +export function serialize( obj: T, serializers: Serializers = defaultSerializers ): string { - function replacer(key): ?mixed { + function replacer(key: string): unknown { + // @ts-expect-error - this has unknown caller + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, security/detect-object-injection const val = this[key]; + // @ts-expect-error = this has unknown caller if (isSerializedType(this)) { return val; } @@ -103,13 +114,16 @@ export function serialize( return val; } - // $FlowFixMe - const serializer = serializers[type] || SERIALIZER[type]; + // dynamic code execution + // eslint-disable-next-line security/detect-object-injection + const serializer = serializers[type] ?? SERIALIZER[type]; if (!serializer) { return val; } + // @ts-expect-error val is of type any + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return serializer(val, key); } diff --git a/src/serializers/array.js b/src/serializers/array.js deleted file mode 100644 index 3451726..0000000 --- a/src/serializers/array.js +++ /dev/null @@ -1,15 +0,0 @@ -/* @flow */ - -export type SerializedArray = $ReadOnlyArray; - -export function serializeArray( - val: $ReadOnlyArray -): SerializedArray { - return val; -} - -export function deserializeArray( - val: SerializedArray -): $ReadOnlyArray { - return val; -} diff --git a/src/serializers/array.ts b/src/serializers/array.ts new file mode 100644 index 0000000..3151e8d --- /dev/null +++ b/src/serializers/array.ts @@ -0,0 +1,9 @@ +export type SerializedArray = readonly T[]; + +export function serializeArray(val: readonly T[]): SerializedArray { + return val; +} + +export function deserializeArray(val: SerializedArray): readonly T[] { + return val; +} diff --git a/src/serializers/boolean.js b/src/serializers/boolean.ts similarity index 94% rename from src/serializers/boolean.js rename to src/serializers/boolean.ts index 98f5256..86ddc52 100644 --- a/src/serializers/boolean.js +++ b/src/serializers/boolean.ts @@ -1,5 +1,3 @@ -/* @flow */ - export type SerializedBoolean = boolean; export function serializeBoolean(val: boolean): SerializedBoolean { diff --git a/src/serializers/date.js b/src/serializers/date.ts similarity index 96% rename from src/serializers/date.js rename to src/serializers/date.ts index 73e8583..0f216f9 100644 --- a/src/serializers/date.js +++ b/src/serializers/date.ts @@ -1,5 +1,3 @@ -/* @flow */ - import { serializeType } from "../common"; import { TYPE } from "../constants"; import type { NativeSerializedType } from "../types"; diff --git a/src/serializers/error.js b/src/serializers/error.js deleted file mode 100644 index c300a33..0000000 --- a/src/serializers/error.js +++ /dev/null @@ -1,44 +0,0 @@ -/* @flow */ - -import { serializeType } from "../common"; -import { TYPE } from "../constants"; -import type { NativeSerializedType } from "../types"; - -import { serializeObject } from "./object"; - -export type SerializedError = {| - message: string, - stack: string, - code: string | number | void, - data: mixed, -|}; - -export function serializeError({ - message, - stack, - // $FlowFixMe - code, - // $FlowFixMe - data, -}: Error): NativeSerializedType { - return serializeType(TYPE.ERROR, { message, stack, code, data }); -} - -export function deserializeError({ - message, - stack, - code, - data, -}: SerializedError): Error { - const error = new Error(message); - // $FlowFixMe - error.code = code; - - if (data) { - // $FlowFixMe - error.data = serializeObject(data); - } - - error.stack = `${stack}\n\n${error.stack}`; - return error; -} diff --git a/src/serializers/error.ts b/src/serializers/error.ts new file mode 100644 index 0000000..bcd094b --- /dev/null +++ b/src/serializers/error.ts @@ -0,0 +1,50 @@ +import { serializeType } from "../common"; +import { TYPE } from "../constants"; +import type { NativeSerializedType } from "../types"; + +import { serializeObject } from "./object"; + +export type SerializedError = { + message: string; + stack: string | undefined; + code: string | undefined; + data?: Record; +}; + +export type ExtendedError = { + data?: Record; + // eslint-disable-next-line no-undef +} & NodeJS.ErrnoException; + +export function serializeError({ + message, + stack, + code, + data, +}: ExtendedError): NativeSerializedType { + return serializeType(TYPE.ERROR, { + message, + stack, + code, + data, + }); +} + +export function deserializeError({ + message, + stack, + code, + data, +}: SerializedError): ExtendedError { + const error: ExtendedError = new Error(message); + error.code = code; + + if (data) { + error.data = serializeObject(data); + } + + // stack and error are both potentially undefined + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + error.stack = `${stack}\n\n${error.stack}`; + return error; +} diff --git a/src/serializers/function.js b/src/serializers/function.js deleted file mode 100644 index 68156b2..0000000 --- a/src/serializers/function.js +++ /dev/null @@ -1,13 +0,0 @@ -/* @flow */ - -export type SerializedFunction = void; - -export function serializeFunction(): SerializedFunction { - // pass -} - -export function deserializeFunction() { - throw new Error( - `Function serialization is not implemented; nothing to deserialize` - ); -} diff --git a/src/serializers/function.ts b/src/serializers/function.ts new file mode 100644 index 0000000..a2eb60d --- /dev/null +++ b/src/serializers/function.ts @@ -0,0 +1,9 @@ +export function serializeFunction(): void { + // pass +} + +export function deserializeFunction(): Error { + throw new Error( + `Function serialization is not implemented; nothing to deserialize` + ); +} diff --git a/src/serializers/index.js b/src/serializers/index.ts similarity index 96% rename from src/serializers/index.js rename to src/serializers/index.ts index 8b8f4d4..8752be1 100644 --- a/src/serializers/index.js +++ b/src/serializers/index.ts @@ -1,5 +1,3 @@ -/* @flow */ - export * from "./array"; export * from "./boolean"; export * from "./date"; diff --git a/src/serializers/null.js b/src/serializers/null.js deleted file mode 100644 index 7e421be..0000000 --- a/src/serializers/null.js +++ /dev/null @@ -1,11 +0,0 @@ -/* @flow */ - -export type SerializedNull = null; - -export function serializeNull(val: null): SerializedNull { - return val; -} - -export function deserializeNull(val: SerializedNull): null { - return val; -} diff --git a/src/serializers/null.ts b/src/serializers/null.ts new file mode 100644 index 0000000..b831452 --- /dev/null +++ b/src/serializers/null.ts @@ -0,0 +1,9 @@ +export type SerializedNull = undefined; + +export function serializeNull(val: undefined): SerializedNull { + return val; +} + +export function deserializeNull(val: SerializedNull): undefined { + return val; +} diff --git a/src/serializers/number.js b/src/serializers/number.ts similarity index 94% rename from src/serializers/number.js rename to src/serializers/number.ts index b909d88..1990574 100644 --- a/src/serializers/number.js +++ b/src/serializers/number.ts @@ -1,5 +1,3 @@ -/* @flow */ - export type SerializedNumber = number; export function serializeNumber(val: number): SerializedNumber { diff --git a/src/serializers/object.js b/src/serializers/object.js deleted file mode 100644 index d83a33d..0000000 --- a/src/serializers/object.js +++ /dev/null @@ -1,11 +0,0 @@ -/* @flow */ - -export type SerializedObject = Object; - -export function serializeObject(val: Object): SerializedObject { - return val; -} - -export function deserializeObject(val: SerializedObject): Object { - return val; -} diff --git a/src/serializers/object.ts b/src/serializers/object.ts new file mode 100644 index 0000000..c956547 --- /dev/null +++ b/src/serializers/object.ts @@ -0,0 +1,13 @@ +export type SerializedObject = Record; + +export function serializeObject( + val: Record +): SerializedObject { + return val; +} + +export function deserializeObject( + val: SerializedObject +): Record { + return val; +} diff --git a/src/serializers/promise.js b/src/serializers/promise.js deleted file mode 100644 index 5228bc5..0000000 --- a/src/serializers/promise.js +++ /dev/null @@ -1,15 +0,0 @@ -/* @flow */ - -import type { Thenable } from "../types"; - -export type SerializedPromise = void; - -export function serializePromise(): SerializedPromise { - // pass -} - -export function deserializePromise(): Thenable { - throw new Error( - `Promise serialization is not implemented; nothing to deserialize` - ); -} diff --git a/src/serializers/promise.ts b/src/serializers/promise.ts new file mode 100644 index 0000000..6bfc7e4 --- /dev/null +++ b/src/serializers/promise.ts @@ -0,0 +1,9 @@ +export function serializePromise(): void { + // pass +} + +export function deserializePromise(): Error { + throw new Error( + `Promise serialization is not implemented; nothing to deserialize` + ); +} diff --git a/src/serializers/regex.js b/src/serializers/regex.ts similarity index 97% rename from src/serializers/regex.js rename to src/serializers/regex.ts index 880002b..47d470e 100644 --- a/src/serializers/regex.js +++ b/src/serializers/regex.ts @@ -1,5 +1,3 @@ -/* @flow */ - import { serializeType } from "../common"; import { TYPE } from "../constants"; import type { NativeSerializedType } from "../types"; diff --git a/src/serializers/string.js b/src/serializers/string.ts similarity index 94% rename from src/serializers/string.js rename to src/serializers/string.ts index eeeb2d8..4306040 100644 --- a/src/serializers/string.js +++ b/src/serializers/string.ts @@ -1,5 +1,3 @@ -/* @flow */ - export type SerializedString = string; export function serializeString(val: string): SerializedString { diff --git a/src/serializers/undefined.js b/src/serializers/undefined.ts similarity index 84% rename from src/serializers/undefined.js rename to src/serializers/undefined.ts index c6ef86e..4bb2e58 100644 --- a/src/serializers/undefined.js +++ b/src/serializers/undefined.ts @@ -1,13 +1,11 @@ -/* @flow */ - import type { NativeSerializedType } from "../types"; import { serializeType } from "../common"; import { TYPE } from "../constants"; -export type SerializedUndefined = void; +export type SerializedUndefined = undefined; export function serializeUndefined( - val: void + val: undefined ): NativeSerializedType { return serializeType(TYPE.UNDEFINED, val); } diff --git a/src/types.js b/src/types.js deleted file mode 100644 index 96d8580..0000000 --- a/src/types.js +++ /dev/null @@ -1,26 +0,0 @@ -/* @flow */ - -import { TYPE } from "./constants"; - -// export something to force webpack to see this as an ES module -export const TYPES = true; - -// eslint-disable-next-line flowtype/require-exact-type -export type Thenable = { - then: ( - onSuccess?: (val?: mixed) => mixed, - onError?: (err?: mixed) => mixed - ) => Thenable, - catch: (onError?: (err?: mixed) => mixed) => Thenable, -}; - -// eslint-disable-next-line flowtype/require-exact-type -export type NativeSerializedType, V: mixed> = { - __type__: T, - __val__: V, -}; - -export type CustomSerializedType = {| - __type__: T, - __val__: V, -|}; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..07579f7 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,26 @@ +import type { $Values } from "utility-types"; + +import type { TYPE } from "./constants"; + +// export something to force webpack to see this as an ES module +export const TYPES = true; + +export type TypeSerializableTypes = $Values; + +export type Thenable = { + then: ( + onSuccess?: (val?: unknown) => unknown, + onError?: (err?: unknown) => unknown + ) => Thenable; + catch: (onError?: (err?: unknown) => unknown) => Thenable; +}; + +export type NativeSerializedType, V> = { + __type__: T; + __val__: V; +}; + +export type CustomSerializedType = { + __type__: T; + __val__: V; +}; diff --git a/test/basic.test.ts b/test/basic.test.ts new file mode 100644 index 0000000..0652fa1 --- /dev/null +++ b/test/basic.test.ts @@ -0,0 +1,120 @@ +import { describe, it, expect } from "vitest"; + +import { serialize, deserialize, type ExtendedError } from "../src"; + +describe("basic type cases", () => { + it("should serialize a date", () => { + const val = new Date(); + const result = deserialize(serialize(val)); + + expect(result.toJSON()).toEqual(val.toJSON()); + }); + + it("should serialize a boolean", () => { + const val = true; + const result = deserialize(serialize(val)); + + expect(result).toEqual(val); + }); + + it("should serialize a string", () => { + const val = "hello world\nsup"; + const result = deserialize(serialize(val)); + + expect(result).toEqual(val); + }); + + it("should serialize a number", () => { + const val = 12345; + const result = deserialize(serialize(val)); + + expect(result).toEqual(val); + }); + + it("should serialize a float", () => { + const val = 123.45; + const result = deserialize(serialize(val)); + + expect(result).toEqual(val); + }); + + it("should serialize an array", () => { + const val = [1, 2, 3, "hello", { "5": 6 }]; + const result = deserialize(serialize(val)); + + expect(JSON.stringify(result)).toEqual(JSON.stringify(val)); + }); + + it("should serialize an object", () => { + const val = { woop: [1, 2, 3, "hello", { "5": 6 }], floop: 5 }; + const result = deserialize(serialize(val)); + + expect(JSON.stringify(result)).toEqual(JSON.stringify(val)); + }); + + it("should serialize a regex", () => { + const val = /hello world[123]/; + const result = deserialize(serialize(val)); + + expect(result.source).toEqual(val.source); + }); + + it("should serialize null", () => { + const val = null; + const result = deserialize(serialize(val)); + + expect(result).toEqual(val); + }); + + it("should serialize undefined", () => { + const val = undefined; + const result = deserialize(serialize(val)); + + expect(result).toEqual(val); + }); + + it("should serialize undefined in an object", () => { + const obj = { foo: undefined }; + const result = deserialize>(serialize(obj)); + + expect(result.foo).toEqual(obj.foo); + }); + + it("should serialize undefined in an array", () => { + const arr = [undefined]; + const result = deserialize(serialize(arr)); + + expect(arr[0]).toEqual(result[0]); + }); + + it("should serialize an error", () => { + const val: ExtendedError = new Error("meep"); + val.code = "ERROR_55"; + val.data = { zerp: "blerp" }; + const result = deserialize(serialize(val)); + + expect(result).toBeInstanceOf(Error); + expect(result.message).toEqual(val.message); + expect(result.code).toEqual(val.code); + expect(result.stack).toContain(val.stack); + expect(result.data).toBeTruthy(); + expect(result.data).toEqual(val.data); + }); + + it("should silently remove promises", () => { + const val = Promise.resolve(1); + const result = deserialize(serialize(val)); + + expect(result).toEqual(undefined); + }); + + it("should silently remove functions", () => { + const val = function foo(bar: string): string { + return bar; + }; + + const result = deserialize(serialize(val)); + + expect(result).toEqual(undefined); + }); +}); diff --git a/test/tests/custom.js b/test/custom.test.ts similarity index 80% rename from test/tests/custom.js rename to test/custom.test.ts index 828839a..989b23d 100644 --- a/test/tests/custom.js +++ b/test/custom.test.ts @@ -1,5 +1,5 @@ -/* @flow */ /* eslint max-lines: off */ +import { describe, it, expect } from "vitest"; import { TYPE, @@ -8,7 +8,7 @@ import { serializeType, serializeObject, type CustomSerializedType, -} from "../../src"; +} from "../src"; describe("custom type cases", () => { const CUSTOM_SERIALIZATION = "CUSTOM_SERIALIZATION"; @@ -21,8 +21,8 @@ describe("custom type cases", () => { const serializers = { [TYPE.DATE]: ( - value, - key + value: Date, + key: string ): CustomSerializedType => { if (value.toJSON() !== val.foo.toJSON()) { throw new Error( @@ -41,7 +41,7 @@ describe("custom type cases", () => { }; const deserializers = { - [CUSTOM_SERIALIZATION]: (value, key) => { + [CUSTOM_SERIALIZATION]: (value: unknown, key: string) => { if (value !== serializedValue) { throw new Error( `Expected ${JSON.stringify(value)} to equal ${JSON.stringify( @@ -58,10 +58,12 @@ describe("custom type cases", () => { }, }; - const result = deserialize(serialize(val, serializers), deserializers); - if (result.foo !== deserializedValue) { - throw new Error(`Expected ${result.foo} to equal ${deserializedValue}`); - } + const result = deserialize>( + serialize(val, serializers), + deserializers + ); + + expect(result.foo).toEqual(deserializedValue); }); it("should serialize a boolean with a custom serializer and deserializer", () => { @@ -72,8 +74,8 @@ describe("custom type cases", () => { const serializers = { [TYPE.BOOLEAN]: ( - value, - key + value: boolean, + key: string ): CustomSerializedType => { if (value !== val.foo) { throw new Error( @@ -92,7 +94,7 @@ describe("custom type cases", () => { }; const deserializers = { - [CUSTOM_SERIALIZATION]: (value, key) => { + [CUSTOM_SERIALIZATION]: (value: unknown, key: string) => { if (value !== serializedValue) { throw new Error( `Expected ${JSON.stringify(value)} to equal ${JSON.stringify( @@ -109,10 +111,12 @@ describe("custom type cases", () => { }, }; - const result = deserialize(serialize(val, serializers), deserializers); - if (result.foo !== deserializedValue) { - throw new Error(`Expected ${result.foo} to equal ${deserializedValue}`); - } + const result = deserialize>( + serialize(val, serializers), + deserializers + ); + + expect(result.foo).toEqual(deserializedValue); }); it("should serialize a string with a custom serializer and deserializer", () => { @@ -123,8 +127,8 @@ describe("custom type cases", () => { const serializers = { [TYPE.STRING]: ( - value, - key + value: string, + key: string ): CustomSerializedType => { if (value !== val.foo) { throw new Error( @@ -143,7 +147,7 @@ describe("custom type cases", () => { }; const deserializers = { - [CUSTOM_SERIALIZATION]: (value, key) => { + [CUSTOM_SERIALIZATION]: (value: unknown, key: string) => { if (value !== serializedValue) { throw new Error( `Expected ${JSON.stringify(value)} to equal ${JSON.stringify( @@ -159,8 +163,10 @@ describe("custom type cases", () => { return deserializedValue; }, }; - - const result = deserialize(serialize(val, serializers), deserializers); + const result = deserialize>( + serialize(val, serializers), + deserializers + ); if (result.foo !== deserializedValue) { throw new Error(`Expected ${result.foo} to equal ${deserializedValue}`); @@ -169,13 +175,14 @@ describe("custom type cases", () => { it("should serialize a number with a custom serializer and deserializer", () => { const val = { foo: 12345 }; + const serializedValue = `serialized::${Math.random().toString()}`; const deserializedValue = `deserialized::${Math.random().toString()}`; const serializers = { [TYPE.NUMBER]: ( - value, - key + value: number, + key: string ): CustomSerializedType => { if (value !== val.foo) { throw new Error( @@ -194,7 +201,7 @@ describe("custom type cases", () => { }; const deserializers = { - [CUSTOM_SERIALIZATION]: (value, key) => { + [CUSTOM_SERIALIZATION]: (value: unknown, key: string) => { if (value !== serializedValue) { throw new Error( `Expected ${JSON.stringify(value)} to equal ${JSON.stringify( @@ -210,8 +217,10 @@ describe("custom type cases", () => { return deserializedValue; }, }; - - const result = deserialize(serialize(val, serializers), deserializers); + const result = deserialize>( + serialize(val, serializers), + deserializers + ); if (result.foo !== deserializedValue) { throw new Error(`Expected ${result.foo} to equal ${val.foo}`); @@ -220,13 +229,14 @@ describe("custom type cases", () => { it("should serialize a float with a custom serializer and deserializer", () => { const val = { foo: 123.45 }; + const serializedValue = `serialized::${Math.random().toString()}`; const deserializedValue = `deserialized::${Math.random().toString()}`; const serializers = { [TYPE.NUMBER]: ( - value, - key + value: number, + key: string ): CustomSerializedType => { if (value !== val.foo) { throw new Error( @@ -245,7 +255,7 @@ describe("custom type cases", () => { }; const deserializers = { - [CUSTOM_SERIALIZATION]: (value, key) => { + [CUSTOM_SERIALIZATION]: (value: unknown, key: string) => { if (value !== serializedValue) { throw new Error( `Expected ${JSON.stringify(value)} to equal ${JSON.stringify( @@ -262,7 +272,10 @@ describe("custom type cases", () => { }, }; - const result = deserialize(serialize(val, serializers), deserializers); + const result = deserialize>( + serialize(val, serializers), + deserializers + ); if (result.foo !== deserializedValue) { throw new Error(`Expected ${result.foo} to equal ${val.foo}`); @@ -271,13 +284,14 @@ describe("custom type cases", () => { it("should serialize an array with a custom serializer and deserializer", () => { const val = { foo: [1, 2, 3, "hello", { "5": 6 }] }; + const serializedValue = `serialized::${Math.random().toString()}`; const deserializedValue = `deserialized::${Math.random().toString()}`; const serializers = { [TYPE.ARRAY]: ( - value, - key + value: unknown, + key: string ): CustomSerializedType => { if (value !== val.foo) { throw new Error( @@ -296,7 +310,7 @@ describe("custom type cases", () => { }; const deserializers = { - [CUSTOM_SERIALIZATION]: (value, key) => { + [CUSTOM_SERIALIZATION]: (value: unknown, key: string) => { if (value !== serializedValue) { throw new Error( `Expected ${JSON.stringify(value)} to equal ${JSON.stringify( @@ -313,7 +327,10 @@ describe("custom type cases", () => { }, }; - const result = deserialize(serialize(val, serializers), deserializers); + const result = deserialize>( + serialize(val, serializers), + deserializers + ); if (result.foo !== deserializedValue) { throw new Error(`Expected ${result.foo} to equal ${deserializedValue}`); @@ -322,15 +339,17 @@ describe("custom type cases", () => { it("should serialize an object with a custom serializer and deserializer", () => { const val = { foo: { woop: [1, 2, 3, "hello", { "5": 6 }], floop: 5 } }; + const serializedValue = `serialized::${Math.random().toString()}`; const deserializedValue = `deserialized::${Math.random().toString()}`; const serializers = { [TYPE.OBJECT]: ( - value, - key - ): CustomSerializedType => { + value: Record, + key: string + ): CustomSerializedType => { if (value !== val.foo) { + // @ts-expect-error missing all the custom fields return serializeObject(value); } @@ -343,7 +362,7 @@ describe("custom type cases", () => { }; const deserializers = { - [CUSTOM_SERIALIZATION]: (value, key) => { + [CUSTOM_SERIALIZATION]: (value: unknown, key: string) => { if (value !== serializedValue) { throw new Error( `Expected ${JSON.stringify(value)} to equal ${JSON.stringify( @@ -360,22 +379,24 @@ describe("custom type cases", () => { }, }; - const result = deserialize(serialize(val, serializers), deserializers); + const result = deserialize>( + serialize(val, serializers), + deserializers + ); - if (result.foo !== deserializedValue) { - throw new Error(`Expected ${result.foo} to equal ${deserializedValue}`); - } + expect(result.foo).toEqual(deserializedValue); }); it("should serialize a regex with a custom serializer and deserializer", () => { const val = { foo: /hello world[123]/ }; + const serializedValue = `serialized::${Math.random().toString()}`; const deserializedValue = `deserialized::${Math.random().toString()}`; const serializers = { [TYPE.REGEX]: ( - value, - key + value: RegExp, + key: string ): CustomSerializedType => { if (value !== val.foo) { throw new Error( @@ -392,9 +413,8 @@ describe("custom type cases", () => { return serializeType(CUSTOM_SERIALIZATION, serializedValue); }, }; - const deserializers = { - [CUSTOM_SERIALIZATION]: (value, key) => { + [CUSTOM_SERIALIZATION]: (value: unknown, key: string) => { if (value !== serializedValue) { throw new Error( `Expected ${JSON.stringify(value)} to equal ${JSON.stringify( @@ -411,22 +431,25 @@ describe("custom type cases", () => { }, }; - const result = deserialize(serialize(val, serializers), deserializers); + const result = deserialize>( + serialize(val, serializers), + deserializers + ); - if (result.foo !== deserializedValue) { - throw new Error(`Expected ${result.foo} to equal ${deserializedValue}`); - } + expect(result.foo).toEqual(deserializedValue); }); it("should serialize null with a custom serializer and deserializer", () => { - const val = { foo: null }; + // eslint-disable-next-line @typescript-eslint/ban-types + const val: Record = { foo: null }; + const serializedValue = `serialized::${Math.random().toString()}`; const deserializedValue = `deserialized::${Math.random().toString()}`; const serializers = { [TYPE.NULL]: ( - value, - key + value: undefined, + key: string ): CustomSerializedType => { if (value !== val.foo) { throw new Error( @@ -443,9 +466,8 @@ describe("custom type cases", () => { return serializeType(CUSTOM_SERIALIZATION, serializedValue); }, }; - const deserializers = { - [CUSTOM_SERIALIZATION]: (value, key) => { + [CUSTOM_SERIALIZATION]: (value: unknown, key: string) => { if (value !== serializedValue) { throw new Error( `Expected ${JSON.stringify(value)} to equal ${JSON.stringify( @@ -462,22 +484,24 @@ describe("custom type cases", () => { }, }; - const result = deserialize(serialize(val, serializers), deserializers); + const result = deserialize>( + serialize(val, serializers), + deserializers + ); - if (result.foo !== deserializedValue) { - throw new Error(`Expected ${result.foo} to equal ${deserializedValue}`); - } + expect(result.foo).toEqual(deserializedValue); }); it("should serialize an error with a custom serializer and deserializer", () => { const val = { foo: new Error("meep") }; + const serializedValue = `serialized::${Math.random().toString()}`; const deserializedValue = `deserialized::${Math.random().toString()}`; const serializers = { [TYPE.ERROR]: ( - value, - key + value: Error, + key: string ): CustomSerializedType => { if (value !== val.foo) { throw new Error( @@ -494,9 +518,8 @@ describe("custom type cases", () => { return serializeType(CUSTOM_SERIALIZATION, serializedValue); }, }; - const deserializers = { - [CUSTOM_SERIALIZATION]: (value, key) => { + [CUSTOM_SERIALIZATION]: (value: unknown, key: string) => { if (value !== serializedValue) { throw new Error( `Expected ${JSON.stringify(value)} to equal ${JSON.stringify( @@ -513,23 +536,24 @@ describe("custom type cases", () => { }, }; - const result = deserialize(serialize(val, serializers), deserializers); + const result = deserialize>( + serialize(val, serializers), + deserializers + ); - if (result.foo !== deserializedValue) { - throw new Error(`Expected ${result.foo} to equal ${deserializedValue}`); - } + expect(result.foo).toEqual(deserializedValue); }); it("should serialize a promise with a custom serializer and deserializer", () => { - // eslint-disable-next-line no-restricted-globals, compat/compat, promise/no-native const val = { foo: Promise.resolve(1) }; + const serializedValue = `serialized::${Math.random().toString()}`; const deserializedValue = `deserialized::${Math.random().toString()}`; const serializers = { [TYPE.PROMISE]: ( - value, - key + value: unknown, + key: string ): CustomSerializedType => { if (value !== val.foo) { throw new Error( @@ -548,7 +572,7 @@ describe("custom type cases", () => { }; const deserializers = { - [CUSTOM_SERIALIZATION]: (value, key) => { + [CUSTOM_SERIALIZATION]: (value: unknown, key: string) => { if (value !== serializedValue) { throw new Error( `Expected ${JSON.stringify(value)} to equal ${JSON.stringify( @@ -565,11 +589,12 @@ describe("custom type cases", () => { }, }; - const result = deserialize(serialize(val, serializers), deserializers); + const result = deserialize>( + serialize(val, serializers), + deserializers + ); - if (result.foo !== deserializedValue) { - throw new Error(`Expected ${result.foo} to equal ${deserializedValue}`); - } + expect(result.foo).toEqual(deserializedValue); }); it("should serialize a function with a custom serializer and deserializer", () => { @@ -578,13 +603,14 @@ describe("custom type cases", () => { return bar; }, }; + const serializedValue = `serialized::${Math.random().toString()}`; const deserializedValue = `deserialized::${Math.random().toString()}`; const serializers = { [TYPE.FUNCTION]: ( - value, - key + value: unknown, + key: string ): CustomSerializedType => { if (value !== val.foo) { throw new Error( @@ -603,7 +629,7 @@ describe("custom type cases", () => { }; const deserializers = { - [CUSTOM_SERIALIZATION]: (value, key) => { + [CUSTOM_SERIALIZATION]: (value: unknown, key: string) => { if (value !== serializedValue) { throw new Error( `Expected ${JSON.stringify(value)} to equal ${JSON.stringify( @@ -620,11 +646,12 @@ describe("custom type cases", () => { }, }; - const result = deserialize(serialize(val, serializers), deserializers); + const result = deserialize>( + serialize(val, serializers), + deserializers + ); - if (result.foo !== deserializedValue) { - throw new Error(`Expected ${result.foo} to equal ${deserializedValue}`); - } + expect(result.foo).toEqual(deserializedValue); }); it("should serialize an array with a function with a custom serializer and deserializer", () => { @@ -635,13 +662,14 @@ describe("custom type cases", () => { }, ], }; + const serializedValue = `serialized::${Math.random().toString()}`; const deserializedValue = `deserialized::${Math.random().toString()}`; const serializers = { [TYPE.FUNCTION]: ( - value, - key + value: unknown, + key: string ): CustomSerializedType => { if (value !== val.blerp[0]) { throw new Error( @@ -660,7 +688,7 @@ describe("custom type cases", () => { }; const deserializers = { - [CUSTOM_SERIALIZATION]: (value, key) => { + [CUSTOM_SERIALIZATION]: (value: unknown, key: string) => { if (value !== serializedValue) { throw new Error( `Expected ${JSON.stringify(value)} to equal ${JSON.stringify( @@ -677,12 +705,11 @@ describe("custom type cases", () => { }, }; - const result = deserialize(serialize(val, serializers), deserializers); + const result = deserialize>( + serialize(val, serializers), + deserializers + ); - if (result.blerp[0] !== deserializedValue) { - throw new Error( - `Expected ${result.blerp[0]} to equal ${deserializedValue}` - ); - } + expect(result.blerp[0]).toEqual(deserializedValue); }); }); diff --git a/test/embedded.test.ts b/test/embedded.test.ts new file mode 100644 index 0000000..13a7718 --- /dev/null +++ b/test/embedded.test.ts @@ -0,0 +1,116 @@ +import { describe, it, expect } from "vitest"; + +import { serialize, deserialize, type ExtendedError } from "../src"; + +describe("basic type cases", () => { + it("should serialize a date embedded in an object", () => { + const val = { + foo: new Date(), + }; + const result = deserialize>(serialize(val)); + + expect(result.foo.toJSON()).toEqual(val.foo.toJSON()); + }); + + it("should serialize a boolean embedded in an object", () => { + const val = { + foo: true, + }; + const result = deserialize>(serialize(val)); + + expect(result.foo).toEqual(val.foo); + }); + + it("should serialize a string embedded in an object", () => { + const val = { + foo: "hello world\nsup", + }; + const result = deserialize>(serialize(val)); + + expect(result.foo).toEqual(val.foo); + }); + + it("should serialize a number embedded in an object", () => { + const val = { + foo: 12345, + }; + const result = deserialize>(serialize(val)); + + expect(result.foo).toEqual(val.foo); + }); + + it("should serialize a float embedded in an object", () => { + const val = { foo: 123.45 }; + const result = deserialize>(serialize(val)); + + expect(result.foo).toEqual(val.foo); + }); + + it("should serialize an array embedded in an object", () => { + const val = { foo: [1, 2, 3, "hello", { "5": 6 }] }; + const result = deserialize>(serialize(val)); + + expect(JSON.stringify(result.foo)).toEqual(JSON.stringify(val.foo)); + }); + + it("should serialize an object embedded in an object", () => { + const val = { foo: { woop: [1, 2, 3, "hello", { "5": 6 }], floop: 5 } }; + const result = deserialize>(serialize(val)); + + expect(JSON.stringify(result.foo)).toEqual(JSON.stringify(val.foo)); + }); + + it("should serialize a regex embedded in an object", () => { + const val = { + foo: /hello world[123]/, + }; + const result = deserialize>(serialize(val)); + + expect(result.foo.source).toEqual(val.foo.source); + }); + + it("should serialize null embedded in an object", () => { + const val = { foo: null }; + const result = deserialize>(serialize(val)); + + expect(result.foo).toEqual(val.foo); + }); + + it("should serialize undefined embedded in an object", () => { + const val = { foo: undefined }; + const result = deserialize>(serialize(val)); + + expect(result.foo).toEqual(val.foo); + }); + + it("should serialize an error embedded in an object", () => { + const val: Record = { foo: new Error("meep") }; + val.foo.code = "ERROR_55"; + const result = deserialize>(serialize(val)); + + expect(result.foo).toBeInstanceOf(Error); + expect(result.foo.message).toEqual(val.foo.message); + expect(result.foo.code).toEqual(val.foo.code); + expect(result.foo.stack).toContain(val.foo.stack); + }); + + it("should silently remove promises embedded in an object", () => { + const val = { foo: Promise.resolve(1) }; + const result = deserialize>>(serialize(val)); + + expect(result.foo).toEqual(undefined); + }); + + it("should silently remove functions embedded in an object", () => { + const val = { + foo: function foo(bar: string): string { + return bar; + }, + }; + + // eslint-disable-next-line @typescript-eslint/ban-types + const result = deserialize>(serialize(val)); + + expect(result.foo).toEqual(undefined); + }); +}); diff --git a/test/tests/error.js b/test/error.test.ts similarity index 52% rename from test/tests/error.js rename to test/error.test.ts index 06fb31f..d744827 100644 --- a/test/tests/error.js +++ b/test/error.test.ts @@ -1,20 +1,18 @@ -/* @flow */ +import { describe, it, expect } from "vitest"; -import { deserialize } from "../../src"; +import { deserialize } from "../src"; describe("error cases", () => { it("should error while trying to deserialize a function", () => { - let error; + let error: Error | undefined; try { deserialize('{ "__type__": "function" }'); - } catch (err) { - error = err; + } catch (err: unknown) { + error = err as Error; } - if (!error) { - throw new Error(`Expected error to be thrown`); - } + expect(error).toBeTruthy(); }); it("should error while trying to deserialize a promise", () => { @@ -22,12 +20,10 @@ describe("error cases", () => { try { deserialize('{ "__type__": "promise" }'); - } catch (err) { - error = err; + } catch (err: unknown) { + error = err as Error; } - if (!error) { - throw new Error(`Expected error to be thrown`); - } + expect(error).toBeTruthy(); }); }); diff --git a/test/index.js b/test/index.js deleted file mode 100644 index d30bfb7..0000000 --- a/test/index.js +++ /dev/null @@ -1,4 +0,0 @@ -/* @flow */ - -import "./util"; -import "./tests"; diff --git a/test/tests/basic.js b/test/tests/basic.js deleted file mode 100644 index 23b40b0..0000000 --- a/test/tests/basic.js +++ /dev/null @@ -1,174 +0,0 @@ -/* @flow */ - -import { serialize, deserialize } from "../../src"; - -describe("basic type cases", () => { - it("should serialize a date", () => { - const val = new Date(); - const result = deserialize(serialize(val)); - if (result.toJSON() !== val.toJSON()) { - throw new Error(`Expected ${result.toJSON()} to equal ${val.toJSON()}`); - } - }); - - it("should serialize a boolean", () => { - const val = true; - const result = deserialize(serialize(val)); - if (result !== val) { - throw new Error( - `Expected ${JSON.stringify(result)} to equal ${JSON.stringify(val)}` - ); - } - }); - - it("should serialize a string", () => { - const val = "hello world\nsup"; - const result = deserialize(serialize(val)); - if (result !== val) { - throw new Error( - `Expected ${JSON.stringify(result)} to equal ${JSON.stringify(val)}` - ); - } - }); - - it("should serialize a number", () => { - const val = 12345; - const result = deserialize(serialize(val)); - if (result !== val) { - throw new Error( - `Expected ${JSON.stringify(result)} to equal ${JSON.stringify(val)}` - ); - } - }); - - it("should serialize a float", () => { - const val = 123.45; - const result = deserialize(serialize(val)); - if (result !== val) { - throw new Error( - `Expected ${JSON.stringify(result)} to equal ${JSON.stringify(val)}` - ); - } - }); - - it("should serialize an array", () => { - const val = [1, 2, 3, "hello", { "5": 6 }]; - const result = deserialize(serialize(val)); - if (JSON.stringify(result) !== JSON.stringify(val)) { - throw new Error( - `Expected ${JSON.stringify(result)} to equal ${JSON.stringify(val)}` - ); - } - }); - - it("should serialize an object", () => { - const val = { woop: [1, 2, 3, "hello", { "5": 6 }], floop: 5 }; - const result = deserialize(serialize(val)); - if (JSON.stringify(result) !== JSON.stringify(val)) { - throw new Error( - `Expected ${JSON.stringify(result)} to equal ${JSON.stringify(val)}` - ); - } - }); - - it("should serialize a regex", () => { - const val = /hello world[123]/; - const result = deserialize(serialize(val)); - if (result.source !== val.source) { - throw new Error(`Expected ${result.source} to equal ${val.source}`); - } - }); - - it("should serialize null", () => { - const val = null; - const result = deserialize(serialize(val)); - if (result !== val) { - throw new Error( - `Expected ${JSON.stringify(result)} to equal ${JSON.stringify(val)}` - ); - } - }); - - it("should serialize undefined", () => { - const val = undefined; - const result = deserialize(serialize(val)); - if (result !== val) { - throw new Error( - `Expected ${JSON.stringify(result)} to equal ${ - JSON.stringify(val) || "undefined" - }` - ); - } - }); - - it("should serialize undefined in an object", () => { - const obj = { foo: undefined }; - const result = deserialize(serialize(obj)); - if (result.foo !== obj.foo) { - throw new Error( - `Expected ${JSON.stringify(result.foo)} to equal ${ - JSON.stringify(obj.foo) || "undefined" - }` - ); - } - }); - - it("should serialize undefined in an array", () => { - const arr = [undefined]; - const result = deserialize(serialize(arr)); - if (arr[0] !== result[0]) { - throw new Error( - `Expected ${JSON.stringify(result[0]) || "undefined"} to equal ${ - JSON.stringify(arr[0]) || "undefined" - }` - ); - } - }); - - it("should serialize an error", () => { - const val = new Error("meep"); - // $FlowFixMe - val.code = "ERROR_55"; - // $FlowFixMe - val.data = { zerp: "blerp" }; - const result = deserialize(serialize(val)); - if (!(result instanceof Error)) { - throw new TypeError(`Expected result to be an instance of error`); - } - if (result.message !== val.message) { - throw new Error( - `Expected message ${result.message} to equal ${val.message}` - ); - } - // $FlowFixMe - if (result.code !== val.code) { - // $FlowFixMe - throw new Error(`Expected message ${result.code} to equal ${val.code}`); - } - if (!result.data || result.data.zerp !== "blerp") { - throw new Error(`Expected err.data to be serialized`); - } - if (result.stack.indexOf(val.stack)) { - throw new Error(`Expected stack ${result.stack} to contain ${val.stack}`); - } - }); - - it("should silently remove promises", () => { - // eslint-disable-next-line no-restricted-globals, compat/compat, promise/no-native - const val = Promise.resolve(1); - const result = deserialize(serialize(val)); - if (result !== undefined) { - throw new Error(`Expected ${result} to equal undefined`); - } - }); - - it("should silently remove functions", () => { - const val = function foo(bar: string): string { - return bar; - }; - const result = deserialize(serialize(val)); - if (result !== undefined) { - throw new Error(`Expected ${result} to equal undefined`); - } - }); -}); diff --git a/test/tests/embedded.js b/test/tests/embedded.js deleted file mode 100644 index a054958..0000000 --- a/test/tests/embedded.js +++ /dev/null @@ -1,171 +0,0 @@ -/* @flow */ - -import { serialize, deserialize } from "../../src"; - -describe("basic type cases", () => { - it("should serialize a date embedded in an object", () => { - const val = { foo: new Date() }; - const result = deserialize(serialize(val)); - if (result.foo.toJSON() !== val.foo.toJSON()) { - throw new Error( - `Expected ${result.foo.toJSON()} to equal ${val.foo.toJSON()}` - ); - } - }); - - it("should serialize a boolean embedded in an object", () => { - const val = { foo: true }; - const result = deserialize(serialize(val)); - if (result.foo !== val.foo) { - throw new Error( - `Expected ${JSON.stringify(result.foo)} to equal ${JSON.stringify( - val.foo - )}` - ); - } - }); - - it("should serialize a string embedded in an object", () => { - const val = { foo: "hello world\nsup" }; - const result = deserialize(serialize(val)); - if (result.foo !== val.foo) { - throw new Error( - `Expected ${JSON.stringify(result.foo)} to equal ${JSON.stringify( - val.foo - )}` - ); - } - }); - - it("should serialize a number embedded in an object", () => { - const val = { foo: 12345 }; - const result = deserialize(serialize(val)); - if (result.foo !== val.foo) { - throw new Error( - `Expected ${JSON.stringify(result.foo)} to equal ${JSON.stringify( - val.foo - )}` - ); - } - }); - - it("should serialize a float embedded in an object", () => { - const val = { foo: 123.45 }; - const result = deserialize(serialize(val)); - if (result.foo !== val.foo) { - throw new Error( - `Expected ${JSON.stringify(result.foo)} to equal ${JSON.stringify( - val.foo - )}` - ); - } - }); - - it("should serialize an array embedded in an object", () => { - const val = { foo: [1, 2, 3, "hello", { "5": 6 }] }; - const result = deserialize(serialize(val)); - if (JSON.stringify(result.foo) !== JSON.stringify(val.foo)) { - throw new Error( - `Expected ${JSON.stringify(result.foo)} to equal ${JSON.stringify( - val.foo - )}` - ); - } - }); - - it("should serialize an object embedded in an object", () => { - const val = { foo: { woop: [1, 2, 3, "hello", { "5": 6 }], floop: 5 } }; - const result = deserialize(serialize(val)); - if (JSON.stringify(result.foo) !== JSON.stringify(val.foo)) { - throw new Error( - `Expected ${JSON.stringify(result.foo)} to equal ${JSON.stringify( - val.foo - )}` - ); - } - }); - - it("should serialize a regex embedded in an object", () => { - const val = { foo: /hello world[123]/ }; - const result = deserialize(serialize(val)); - if (result.foo.source !== val.foo.source) { - throw new Error( - `Expected ${JSON.stringify( - result.foo.source - )} to equal ${JSON.stringify(val.foo.source)}` - ); - } - }); - - it("should serialize null embedded in an object", () => { - const val = { foo: null }; - const result = deserialize(serialize(val)); - if (result.foo !== val.foo) { - throw new Error( - `Expected ${JSON.stringify(result.foo)} to equal ${JSON.stringify( - val.foo - )}` - ); - } - }); - - it("should serialize undefined embedded in an object", () => { - const val = { foo: undefined }; - const result = deserialize(serialize(val)); - if (result.foo !== val.foo) { - throw new Error( - `Expected ${JSON.stringify(result.foo)} to equal ${ - JSON.stringify(val.foo) || "undefined" - }` - ); - } - }); - - it("should serialize an error embedded in an object", () => { - const val = { foo: new Error("meep") }; - // $FlowFixMe - val.foo.code = "ERROR_55"; - const result = deserialize(serialize(val)); - if (!(result.foo instanceof Error)) { - throw new TypeError(`Expected result.foo to be an instance of error`); - } - if (result.foo.message !== val.foo.message) { - throw new Error( - `Expected message ${result.foo.message} to equal ${val.foo.message}` - ); - } - // $FlowFixMe - if (result.foo.code !== val.foo.code) { - throw new Error( - // $FlowFixMe - `Expected message ${result.foo.code} to equal ${val.foo.code}` - ); - } - if (result.foo.stack.indexOf(val.foo.stack) === -1) { - throw new Error( - `Expected stack ${result.foo.stack} to contain ${val.foo.stack}` - ); - } - }); - - it("should silently remove promises embedded in an object", () => { - // eslint-disable-next-line no-restricted-globals, compat/compat, promise/no-native - const val = { foo: Promise.resolve(1) }; - const result = deserialize(serialize(val)); - if (result.foo !== undefined) { - throw new Error(`Expected ${result.foo} to equal undefined`); - } - }); - - it("should silently remove functions embedded in an object", () => { - const val = { - foo: function foo(bar: string): string { - return bar; - }, - }; - const result = deserialize(serialize(val)); - if (result.foo !== undefined) { - throw new Error(`Expected ${result.foo} to equal undefined`); - } - }); -}); diff --git a/test/tests/index.js b/test/tests/index.js deleted file mode 100644 index 906641b..0000000 --- a/test/tests/index.js +++ /dev/null @@ -1,6 +0,0 @@ -/* @flow */ - -import "./basic"; -import "./error"; -import "./embedded"; -import "./custom"; diff --git a/test/util.js b/test/util.js deleted file mode 100644 index f710e08..0000000 --- a/test/util.js +++ /dev/null @@ -1,10 +0,0 @@ -/* @flow */ - -window.console.karma = function consoleKarma() { - const karma = - window.karma || - (window.top && window.top.karma) || - (window.opener && window.opener.karma); - karma.log("debug", arguments); - console.log.apply(console, arguments); // eslint-disable-line no-console -}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..5613994 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "@krakenjs/typescript-config-grumbler/tsconfig.json" +} diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..5883cfd --- /dev/null +++ b/vite.config.js @@ -0,0 +1,31 @@ +/* eslint-disable eslint-comments/disable-enable-pair */ +/* eslint-disable spaced-comment */ +/// + +// Configure Vitest (https://vitest.dev/config/) + +import path from "path"; + +import { defineConfig } from "vite"; + +// eslint-disable-next-line import/no-default-export +export default defineConfig({ + build: { + lib: { + entry: path.resolve(__dirname, "src/index.ts"), + name: "crossDomainUtil", + fileName: (format) => `cross-domain-utils.${format}.js`, + formats: ["es", "umd"], + }, + sourcemap: true, + rollupOptions: { + ouput: { + preserveModules: true, + }, + }, + }, + test: { + /* for example, use global to avoid globals imports (describe, test, expect): */ + // globals: true, + }, +}); diff --git a/webpack.config.js b/webpack.config.ts similarity index 57% rename from webpack.config.js rename to webpack.config.ts index 2ba14c0..a1a6963 100644 --- a/webpack.config.js +++ b/webpack.config.ts @@ -1,18 +1,16 @@ -/* @flow */ -/* eslint import/no-nodejs-modules: off, import/no-default-export: off */ - -import type { WebpackConfig } from "@krakenjs/webpack-config-grumbler/index.flow"; import { getWebpackConfig } from "@krakenjs/webpack-config-grumbler"; const FILE_NAME = "universalSerialize"; const MODULE_NAME = "universal-serialize"; -export const WEBPACK_CONFIG: WebpackConfig = getWebpackConfig({ +export const WEBPACK_CONFIG = getWebpackConfig({ + entry: "./src/index.ts", filename: `${FILE_NAME}.js`, modulename: MODULE_NAME, }); -export const WEBPACK_CONFIG_MIN: WebpackConfig = getWebpackConfig({ +export const WEBPACK_CONFIG_MIN = getWebpackConfig({ + entry: "./src/index.ts", filename: `${FILE_NAME}.min.js`, modulename: MODULE_NAME, minify: true, @@ -21,7 +19,8 @@ export const WEBPACK_CONFIG_MIN: WebpackConfig = getWebpackConfig({ }, }); -export const WEBPACK_CONFIG_TEST: WebpackConfig = getWebpackConfig({ +export const WEBPACK_CONFIG_TEST = getWebpackConfig({ + entry: "./src/index.ts", modulename: MODULE_NAME, options: { devtool: "inline-source-map",