Skip to content

Commit

Permalink
refactor!: generate type maps
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 committed Mar 27, 2024
1 parent bee1e1e commit c2ff49e
Show file tree
Hide file tree
Showing 15 changed files with 329 additions and 187 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
"dist"
],
"scripts": {
"build": "unbuild",
"build": "pnpm gen-maps && unbuild",
"dev": "vitest dev",
"gen-maps": "jiti ./scripts/gen-maps.ts",
"lint": "eslint --cache --ext .ts,.js,.mjs,.cjs . && prettier -c src test",
"lint:fix": "automd && eslint --cache --ext .ts,.js,.mjs,.cjs . --fix && prettier -c src test -w",
"prepack": "pnpm build",
Expand All @@ -45,4 +46,4 @@
"vitest": "^1.4.0"
},
"packageManager": "pnpm@8.12.1"
}
}
53 changes: 53 additions & 0 deletions scripts/gen-maps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { writeFileSync } from "node:fs";

const types = [
"arrayBuffer",
"blob",
"dataView",
"numberArray",
"readableStream",
"response",
"string",
"uint8Array",
];

const importLines: string[] = [];
const code: string[] = [];

for (const to of types) {
const toAssertName = `assert${upperFirst(to)}`;
importLines.push(toAssertName);
code.push(`export const _to${upperFirst(to)} = {`);
for (const from of types) {
if (to === from) {
code.push(
` ${upperFirst(from)}: (input: unknown) => (${toAssertName}(input), input),`,
);
continue;
}
const fnName = `${from}To${upperFirst(to)}`;
importLines.push(fnName);
code.push(` ${upperFirst(from)}: ${fnName},`);
}
code.push("} as const;", "");
}

const genCode = `// Auto generated using gen-maps script
import {
${importLines.map((l) => `${l},`).join("\n ")}
} from "./data-types";
${code.join("\n")}
export const _to = {
${types.map((t) => `${upperFirst(t)}: _to${upperFirst(t)},`).join("\n ")}\n} as const;
`;

writeFileSync(
new URL("../src/convert-maps.ts", import.meta.url),
genCode,
"utf8",
);

function upperFirst(str: string) {
return str[0].toUpperCase() + str.slice(1);
}
166 changes: 166 additions & 0 deletions src/convert-maps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// Auto generated using gen-maps script
import {
assertArrayBuffer,
blobToArrayBuffer,
dataViewToArrayBuffer,
numberArrayToArrayBuffer,
readableStreamToArrayBuffer,
responseToArrayBuffer,
stringToArrayBuffer,
uint8ArrayToArrayBuffer,
assertBlob,
arrayBufferToBlob,
dataViewToBlob,
numberArrayToBlob,
readableStreamToBlob,
responseToBlob,
stringToBlob,
uint8ArrayToBlob,
assertDataView,
arrayBufferToDataView,
blobToDataView,
numberArrayToDataView,
readableStreamToDataView,
responseToDataView,
stringToDataView,
uint8ArrayToDataView,
assertNumberArray,
arrayBufferToNumberArray,
blobToNumberArray,
dataViewToNumberArray,
readableStreamToNumberArray,
responseToNumberArray,
stringToNumberArray,
uint8ArrayToNumberArray,
assertReadableStream,
arrayBufferToReadableStream,
blobToReadableStream,
dataViewToReadableStream,
numberArrayToReadableStream,
responseToReadableStream,
stringToReadableStream,
uint8ArrayToReadableStream,
assertResponse,
arrayBufferToResponse,
blobToResponse,
dataViewToResponse,
numberArrayToResponse,
readableStreamToResponse,
stringToResponse,
uint8ArrayToResponse,
assertString,
arrayBufferToString,
blobToString,
dataViewToString,
numberArrayToString,
readableStreamToString,
responseToString,
uint8ArrayToString,
assertUint8Array,
arrayBufferToUint8Array,
blobToUint8Array,
dataViewToUint8Array,
numberArrayToUint8Array,
readableStreamToUint8Array,
responseToUint8Array,
stringToUint8Array,
} from "./data-types";

export const _toArrayBuffer = {
ArrayBuffer: (input: unknown) => (assertArrayBuffer(input), input),
Blob: blobToArrayBuffer,
DataView: dataViewToArrayBuffer,
NumberArray: numberArrayToArrayBuffer,
ReadableStream: readableStreamToArrayBuffer,
Response: responseToArrayBuffer,
String: stringToArrayBuffer,
Uint8Array: uint8ArrayToArrayBuffer,
} as const;

export const _toBlob = {
ArrayBuffer: arrayBufferToBlob,
Blob: (input: unknown) => (assertBlob(input), input),
DataView: dataViewToBlob,
NumberArray: numberArrayToBlob,
ReadableStream: readableStreamToBlob,
Response: responseToBlob,
String: stringToBlob,
Uint8Array: uint8ArrayToBlob,
} as const;

export const _toDataView = {
ArrayBuffer: arrayBufferToDataView,
Blob: blobToDataView,
DataView: (input: unknown) => (assertDataView(input), input),
NumberArray: numberArrayToDataView,
ReadableStream: readableStreamToDataView,
Response: responseToDataView,
String: stringToDataView,
Uint8Array: uint8ArrayToDataView,
} as const;

export const _toNumberArray = {
ArrayBuffer: arrayBufferToNumberArray,
Blob: blobToNumberArray,
DataView: dataViewToNumberArray,
NumberArray: (input: unknown) => (assertNumberArray(input), input),
ReadableStream: readableStreamToNumberArray,
Response: responseToNumberArray,
String: stringToNumberArray,
Uint8Array: uint8ArrayToNumberArray,
} as const;

export const _toReadableStream = {
ArrayBuffer: arrayBufferToReadableStream,
Blob: blobToReadableStream,
DataView: dataViewToReadableStream,
NumberArray: numberArrayToReadableStream,
ReadableStream: (input: unknown) => (assertReadableStream(input), input),
Response: responseToReadableStream,
String: stringToReadableStream,
Uint8Array: uint8ArrayToReadableStream,
} as const;

export const _toResponse = {
ArrayBuffer: arrayBufferToResponse,
Blob: blobToResponse,
DataView: dataViewToResponse,
NumberArray: numberArrayToResponse,
ReadableStream: readableStreamToResponse,
Response: (input: unknown) => (assertResponse(input), input),
String: stringToResponse,
Uint8Array: uint8ArrayToResponse,
} as const;

export const _toString = {
ArrayBuffer: arrayBufferToString,
Blob: blobToString,
DataView: dataViewToString,
NumberArray: numberArrayToString,
ReadableStream: readableStreamToString,
Response: responseToString,
String: (input: unknown) => (assertString(input), input),
Uint8Array: uint8ArrayToString,
} as const;

export const _toUint8Array = {
ArrayBuffer: arrayBufferToUint8Array,
Blob: blobToUint8Array,
DataView: dataViewToUint8Array,
NumberArray: numberArrayToUint8Array,
ReadableStream: readableStreamToUint8Array,
Response: responseToUint8Array,
String: stringToUint8Array,
Uint8Array: (input: unknown) => (assertUint8Array(input), input),
} as const;

export const _to = {
ArrayBuffer: _toArrayBuffer,
Blob: _toBlob,
DataView: _toDataView,
NumberArray: _toNumberArray,
ReadableStream: _toReadableStream,
Response: _toResponse,
String: _toString,
Uint8Array: _toUint8Array,
} as const;
96 changes: 96 additions & 0 deletions src/convert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { DataType, DataTypeName } from "./types";
import { detectType } from "./detect";
import {
_toArrayBuffer,
_toBlob,
_toDataView,
_toNumberArray,
_toReadableStream,
_toResponse,
_toString,
_toUint8Array,
_to,
} from "./convert-maps";

/**
* Convert from any value to any supported data type
*/
export function convertTo<T extends DataTypeName>(
toType: T,
input: DataType,
): any {
const _map = _to[toType];
if (!_map) {
throw new Error(`Conversion to ${toType} is not supported.`);
}
const fromType = detectType(input);
return _convertTo(fromType, input, _map);
}

/**
* Convert from any value to [ArrayBuffer][ArrayBuffer]
* @group ArrayBuffer
*/
export const toArrayBuffer = (input: DataType) =>
_convertTo<ArrayBuffer>("ArrayBuffer", input, _toArrayBuffer);

/**
* Convert from any value to [Blob][Blob]
* @group Blob
*/
export const toBlob = (input: DataType) =>
_convertTo<Blob>("Blob", input, _toBlob);

/**
* Convert from any value to [DataView][DataView]
* @group DataView
*/
export const toDataView = (input: DataType) =>
_convertTo<DataView>("DataView", input, _toDataView);

/**
* Convert from any value to [Number Array][Number Array]
* @group NumberArray
*/
export const toNumberArray = (input: DataType) =>
_convertTo<number[]>("NumberArray", input, _toNumberArray);

/**
* Convert from any value to [ReadableStream][ReadableStream]
* @group ReadableStream
*/
export const toReadableStream = (input: DataType) =>
_convertTo<ReadableStream>("ReadableStream", input, _toReadableStream);

/**
* Convert from any value to [Response][Response]
* @group ReadableStream
*/
export const toResponse = (input: DataType) =>
_convertTo<ReadableStream>("Response", input, _toResponse);

/**
* Convert from any value to [String][String]
* @group String
*/
export const toString = (input: DataType) =>
_convertTo<string>("String", input, _toString);

/**
* Convert from any value to [Uinit8Array][Uinit8Array]
* @group Uinit8Array
*/
export const toUnit8Array = (input: DataType) =>
_convertTo<string>("Uint8Array", input, _toUint8Array);

function _convertTo<T extends DataType>(
name: DataTypeName,
input: DataType,
map: Record<DataTypeName, any /* (input: DataType) => T */>,
) {
const converter = map[name];
if (converter === undefined) {
throw new Error(`Conversion from ${name} is not supported.`);
}
return converter(input);
}
23 changes: 0 additions & 23 deletions src/data-types/_utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
import { detectType } from "../detect";
import { DataTypeName, DataType } from "../types";
import {} from "./array-buffer";

type TestFn<T> = (input: unknown) => input is T;

export function assertType<T>(
Expand All @@ -13,22 +9,3 @@ export function assertType<T>(
throw new TypeError(`Expected ${name} type but got ${typeof test}.`);
}
}

type DataNameToType = Record<DataTypeName, DataType>;
export type ConvertMap<FROM extends DataType> = {
[TO in DataTypeName]: (
input: FROM,
) => DataNameToType[TO] | Promise<DataNameToType[TO]>;
};

export function convertTo<T extends DataType>(
name: string,
input: DataType,
convertMap: ConvertMap<T>,
) {
const typeName = detectType(input);
if (typeName in convertMap) {
return convertMap[typeName](input as T);
}
throw new TypeError(`Cannot convert ${typeName} to ${name}.`);
}

0 comments on commit c2ff49e

Please sign in to comment.