diff --git a/docs/table-of-contents.json b/docs/table-of-contents.json index 4b3b8f49..e4604d53 100644 --- a/docs/table-of-contents.json +++ b/docs/table-of-contents.json @@ -43,7 +43,13 @@ { "title": "API Reference", "chapters": [ - { + "title": "@math.gl/types", + "entries": [ + {"entry": "modules/types/docs"}, + {"entry": "modules/types/docs/api-reference/is-array"} + ] + }, + { "title": "@math.gl/core", "entries": [ {"entry": "modules/core/docs"}, diff --git a/docs/whats-new.md b/docs/whats-new.md index e2c3e5bd..844a8733 100644 --- a/docs/whats-new.md +++ b/docs/whats-new.md @@ -25,7 +25,13 @@ -## v3.6 (In Development) +## v3.7 (In Development) + +**`@math.gl/types` + +- Add `isTypedArray()` and `isNumericArray()` utilities that both check values and return properly restricted types to help write strictly typed code (avoids the `DataView` issue with `ArrayBuffer.isView()`). + +## v3.6 Target Release Date: Q4, 2021 diff --git a/modules/core/src/index.ts b/modules/core/src/index.ts index f2ec21b0..69ac9d7d 100644 --- a/modules/core/src/index.ts +++ b/modules/core/src/index.ts @@ -3,6 +3,8 @@ // types export type {TypedArray, NumericArray} from '@math.gl/types'; +export type {isTypedArray, isNumericArray} from '@math.gl/types'; + // classes export {default as Vector2} from './classes/vector2'; export {default as Vector3} from './classes/vector3'; diff --git a/modules/core/src/lib/common.ts b/modules/core/src/lib/common.ts index 5638c6d3..a8f82adb 100644 --- a/modules/core/src/lib/common.ts +++ b/modules/core/src/lib/common.ts @@ -1,6 +1,6 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// MIT License -import {NumericArray} from '@math.gl/types'; +// math.gl, MIT license + +import type {NumericArray} from '@math.gl/types'; import type MathArray from '../classes/base/math-array'; @@ -50,8 +50,8 @@ export function formatValue( /** * Check if value is an "array" * Returns `true` if value is either an array or a typed array - * * Note: returns `false` for `ArrayBuffer` and `DataView` instances + * @note isTypedArray and isNumericArray are often more useful in TypeScript */ export function isArray(value: unknown): boolean { return Array.isArray(value) || (ArrayBuffer.isView(value) && !(value instanceof DataView)); diff --git a/modules/types/docs/README.md b/modules/types/docs/README.md new file mode 100644 index 00000000..1dc1f7b8 --- /dev/null +++ b/modules/types/docs/README.md @@ -0,0 +1,10 @@ +# Overview + +Minimal set of math types, intended to be used with very low cost (bundle size impact) +across other frameworks. + +## Installation + +```bash +npm install @math.gl/types +``` \ No newline at end of file diff --git a/modules/types/docs/api-reference/array-types.md b/modules/types/docs/api-reference/array-types.md new file mode 100644 index 00000000..75592bf0 --- /dev/null +++ b/modules/types/docs/api-reference/array-types.md @@ -0,0 +1,23 @@ +# Array Types + +## Types + +### `TypedArray` + +Any javascript typed array + +### `NumericArray` + +Any javascript typed array, or any javascript array containing numbers + +## Utilities + +### `isTypedArray(value: unknown): TypedArray | null` + +Avoids type problems with the `ArrayBuffer.isView()` check. + +### `isNumericArray(value: unknown): TypedArray | null` + +Avoids type problems with the `ArrayBuffer.isView()` check. + +Note: only the type of the first element in a standard array is checked to be a `number`. diff --git a/modules/types/src/number-array.ts b/modules/types/src/array-types.ts similarity index 87% rename from modules/types/src/number-array.ts rename to modules/types/src/array-types.ts index 476e14b9..0f6ed9b9 100644 --- a/modules/types/src/number-array.ts +++ b/modules/types/src/array-types.ts @@ -23,5 +23,6 @@ export type NumericArray = TypedArray | number[]; /** * TypeScript type covering all typed arrays and classic arrays consisting of numbers + * @note alias for NumericArray */ -export type NumberArray = TypedArray | number[]; +export type NumberArray = NumericArray; diff --git a/modules/types/src/index.ts b/modules/types/src/index.ts index 614f7136..815f0b6d 100644 --- a/modules/types/src/index.ts +++ b/modules/types/src/index.ts @@ -1 +1,2 @@ -export {TypedArray, NumberArray, NumericArray} from './number-array'; +export type {TypedArray, NumericArray, NumberArray} from './array-types'; +export {isTypedArray, isNumericArray} from './is-array'; diff --git a/modules/types/src/is-array.ts b/modules/types/src/is-array.ts new file mode 100644 index 00000000..daee3230 --- /dev/null +++ b/modules/types/src/is-array.ts @@ -0,0 +1,22 @@ +import {TypedArray, NumericArray} from './array-types'; + +/** + * Check is an array is a typed array + * @param value value to be tested + * @returns input as TypedArray, or null + */ +export function isTypedArray(value: unknown): TypedArray | null { + return ArrayBuffer.isView(value) && !(value instanceof DataView) ? (value as TypedArray) : null; +} + +/** + * Check is an array is a numeric array (typed array or array of numbers) + * @param value value to be tested + * @returns input as NumericArray, or null + */ +export function isNumericArray(value: unknown): NumericArray | null { + if (Array.isArray(value)) { + return value.length === 0 || typeof value[0] === 'number' ? (value as number[]) : null; + } + return isTypedArray(value); +} diff --git a/modules/types/test/index.ts b/modules/types/test/index.ts new file mode 100644 index 00000000..d701ccb2 --- /dev/null +++ b/modules/types/test/index.ts @@ -0,0 +1,3 @@ +// math.gl, MIT license + +import './is-array.spec'; diff --git a/modules/types/test/is-array.spec.ts b/modules/types/test/is-array.spec.ts new file mode 100644 index 00000000..b361fdf2 --- /dev/null +++ b/modules/types/test/is-array.spec.ts @@ -0,0 +1,44 @@ +// math.gl, MIT license + +import test, {Test} from 'tape-promise/tape'; +import {isTypedArray, isNumericArray} from '@math.gl/types'; + +const TEST_CASES: {value: unknown; isTypedArray: boolean; isNumericArray: boolean}[] = [ + {value: new Float32Array(1), isTypedArray: true, isNumericArray: true}, + {value: new Uint8Array(2), isTypedArray: true, isNumericArray: true}, + {value: [], isTypedArray: false, isNumericArray: true}, + {value: [100, 100], isTypedArray: false, isNumericArray: true}, + {value: ['a'], isTypedArray: false, isNumericArray: false}, + {value: new ArrayBuffer(4), isTypedArray: false, isNumericArray: false}, + {value: new DataView(new ArrayBuffer(16)), isTypedArray: false, isNumericArray: false}, + {value: undefined, isTypedArray: false, isNumericArray: false}, + {value: null, isTypedArray: false, isNumericArray: false}, + {value: {}, isTypedArray: false, isNumericArray: false}, + {value: {length: 0}, isTypedArray: false, isNumericArray: false}, + {value: 1, isTypedArray: false, isNumericArray: false}, + {value: NaN, isTypedArray: false, isNumericArray: false}, + {value: 'NaN', isTypedArray: false, isNumericArray: false}, + {value: '', isTypedArray: false, isNumericArray: false} +]; + +test('math.gl#isTypedArray', (t) => { + for (const tc of TEST_CASES) { + t.equal( + Boolean(isTypedArray(tc.value)), + tc.isTypedArray, + `isTypedArray(${JSON.stringify(tc.value)}) => ${tc.isTypedArray}` + ); + } + t.end(); +}); + +test('math.gl#isNumericArray', (t) => { + for (const tc of TEST_CASES) { + t.equal( + Boolean(isNumericArray(tc.value)), + tc.isNumericArray, + `isNumericArray(${JSON.stringify(tc.value)}) => ${tc.isNumericArray}` + ); + } + t.end(); +}); diff --git a/test/modules.spec.ts b/test/modules.spec.ts index fdb8ede4..ce121bcb 100644 --- a/test/modules.spec.ts +++ b/test/modules.spec.ts @@ -1,3 +1,4 @@ +import '../modules/types/test'; import '../modules/core/test'; import '../modules/culling/test'; import '../modules/geoid/test'; diff --git a/tsconfig.json b/tsconfig.json index d6f4b61e..c4151041 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -54,6 +54,7 @@ "@math.gl/sun/*": ["modules/sun/src/*"], "@math.gl/sun/test/*": ["modules/sun/test/*"], "@math.gl/types/*": ["modules/types/src/*"], + "@math.gl/types/test/*": ["modules/types/test/*"], "@math.gl/web-mercator/*": ["modules/web-mercator/src/*"], "@math.gl/web-mercator/test/*": ["modules/web-mercator/test/*"], "test/*": ["test/*"]