diff --git a/biome.json b/biome.json index 03b130ac..4b981501 100644 --- a/biome.json +++ b/biome.json @@ -27,20 +27,37 @@ }, "overrides": [ { - "include": ["imageData.d.ts"], + "include": [ + "core.ts", + "full/index.ts", + "reader/index.ts", + "writer/index.ts" + ], "linter": { "rules": { - "complexity": { - "noBannedTypes": "off" + "suspicious": { + "noConfusingVoidType": "off" } } } }, { - "include": ["*.json"], - "json": { - "parser": { - "allowTrailingCommas": true + "include": ["tests/**/*.ts"], + "linter": { + "rules": { + "performance": { + "noDelete": "off" + } + } + } + }, + { + "include": ["imageData.d.ts"], + "linter": { + "rules": { + "complexity": { + "noBannedTypes": "off" + } } } }, diff --git a/package.json b/package.json index fa54c6a9..1b1e4d03 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,8 @@ "vitest": "^2.1.8" }, "dependencies": { - "@types/emscripten": "^1.39.13" + "@types/emscripten": "^1.39.13", + "type-fest": "^4.30.1" }, "overrides": { "typedoc": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 839b32de..32b2eb9b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@types/emscripten': specifier: ^1.39.13 version: 1.39.13 + type-fest: + specifier: ^4.30.1 + version: 4.30.1 devDependencies: '@babel/core': specifier: ^7.26.0 @@ -2127,6 +2130,10 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + type-fest@4.30.1: + resolution: {integrity: sha512-ojFL7eDMX2NF0xMbDwPZJ8sb7ckqtlAi1GsmgsFXvErT9kFTk1r0DuQKvrCh73M6D4nngeHJmvogF9OluXs7Hw==} + engines: {node: '>=16'} + typedoc@0.27.5: resolution: {integrity: sha512-x+fhKJtTg4ozXwKayh/ek4wxZQI/+2hmZUdO2i2NGDBRUflDble70z+ewHod3d4gRpXSO6fnlnjbDTnJk7HlkQ==} engines: {node: '>= 18'} @@ -4327,6 +4334,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + type-fest@4.30.1: {} + typedoc@0.27.5(typescript@5.7.2): dependencies: '@gerrit0/mini-shiki': 1.24.1 diff --git a/src/bindings/barcodeFormat.ts b/src/bindings/barcodeFormat.ts index 7e282653..90c0af6d 100644 --- a/src/bindings/barcodeFormat.ts +++ b/src/bindings/barcodeFormat.ts @@ -74,7 +74,7 @@ export type WriteInputBarcodeFormat = TakeFirst< >; /** - * Barcode formats that may be returned in {@link ReadResult.format} in read functions. + * Barcode formats that may be returned in {@link ReadResult.format | `ReadResult.format`} in read functions. */ export type ReadOutputBarcodeFormat = BarcodeFormat | "None"; diff --git a/src/core.ts b/src/core.ts index ab097f47..f9a121d8 100644 --- a/src/core.ts +++ b/src/core.ts @@ -1,3 +1,4 @@ +import type { Merge } from "type-fest"; import { type ReadResult, type ReaderOptions, @@ -56,38 +57,31 @@ export interface ZXingWriterModule extends EmscriptenModule { */ export interface ZXingFullModule extends ZXingReaderModule, ZXingWriterModule {} -export type ZXingModule = - T extends "reader" - ? ZXingReaderModule - : T extends "writer" - ? ZXingWriterModule - : T extends "full" - ? ZXingFullModule - : ZXingReaderModule | ZXingWriterModule | ZXingFullModule; - export type ZXingReaderModuleFactory = EmscriptenModuleFactory; + export type ZXingWriterModuleFactory = EmscriptenModuleFactory; + export type ZXingFullModuleFactory = EmscriptenModuleFactory; +interface TypeModuleMap { + reader: [ZXingReaderModule, ZXingReaderModuleFactory]; + writer: [ZXingWriterModule, ZXingWriterModuleFactory]; + full: [ZXingFullModule, ZXingFullModuleFactory]; +} + +export type ZXingModule = + TypeModuleMap[T][0]; + export type ZXingModuleFactory = - T extends "reader" - ? ZXingReaderModuleFactory - : T extends "writer" - ? ZXingWriterModuleFactory - : T extends "full" - ? ZXingFullModuleFactory - : - | ZXingReaderModuleFactory - | ZXingWriterModuleFactory - | ZXingFullModuleFactory; + TypeModuleMap[T][1]; export type ZXingModuleOverrides = Partial; -export const VERSION = NPM_PACKAGE_VERSION; +export const ZXING_WASM_VERSION = NPM_PACKAGE_VERSION; -const defaultModuleOverrides: ZXingModuleOverrides = import.meta.env.PROD +const DEFAULT_MODULE_OVERRIDES: ZXingModuleOverrides = import.meta.env.PROD ? { locateFile: (path, prefix) => { const match = path.match(/_(.+?)\.wasm$/); @@ -107,72 +101,85 @@ const defaultModuleOverrides: ZXingModuleOverrides = import.meta.env.PROD }, }; -interface ZXingWeakMapValue { - moduleOverrides: ZXingModuleOverrides; - modulePromise?: Promise>; +type CachedValue = + | [ZXingModuleOverrides] + | [ZXingModuleOverrides, Promise>]; + +const __CACHE__ = new WeakMap(); + +export interface PrepareZXingModuleOptions { + overrides?: ZXingModuleOverrides; + equalityFn?: ( + cachedOverrides: ZXingModuleOverrides, + overrides: ZXingModuleOverrides, + ) => boolean; + fireImmediately?: boolean; } -type ZXingWeakMap = WeakMap; +export function prepareZXingModuleWithFactory( + zxingModuleFactory: ZXingModuleFactory, + options?: Merge, +): void; -let zxingWeakMap: ZXingWeakMap = new WeakMap(); +export function prepareZXingModuleWithFactory( + zxingModuleFactory: ZXingModuleFactory, + options: Merge, +): Promise>; -export function getZXingModuleWithFactory( +export function prepareZXingModuleWithFactory( zxingModuleFactory: ZXingModuleFactory, - zxingModuleOverrides?: ZXingModuleOverrides, -): Promise> { - const zxingWeakMapValue = zxingWeakMap.get(zxingModuleFactory) as - | ZXingWeakMapValue - | undefined; - - if ( - zxingWeakMapValue?.modulePromise && - (zxingModuleOverrides === undefined || - Object.is(zxingModuleOverrides, zxingWeakMapValue.moduleOverrides)) - ) { - return zxingWeakMapValue.modulePromise; - } + options?: PrepareZXingModuleOptions, +): void | Promise>; - const resolvedModuleOverrides = - zxingModuleOverrides ?? - zxingWeakMapValue?.moduleOverrides ?? - defaultModuleOverrides; +export function prepareZXingModuleWithFactory( + zxingModuleFactory: ZXingModuleFactory, + { + overrides, + equalityFn = Object.is, + fireImmediately = false, + }: PrepareZXingModuleOptions = {}, +) { + // look up the cached overrides and module promise + const [cachedOverrides, cachedPromise] = (__CACHE__.get(zxingModuleFactory) as + | CachedValue + | undefined) ?? [DEFAULT_MODULE_OVERRIDES]; - const modulePromise = zxingModuleFactory({ - ...resolvedModuleOverrides, - }) as Promise>; + // resolve the input overrides + const resolvedOverrides = overrides ?? cachedOverrides; - zxingWeakMap.set(zxingModuleFactory, { - moduleOverrides: resolvedModuleOverrides, - modulePromise, - }); + let cacheHit: boolean | undefined; - return modulePromise; -} + // if the module is to be instantiated immediately + if (fireImmediately) { + // if cache is hit and a cached promise is available, + // return the cached promise directly + if ( + cachedPromise && + (cacheHit = equalityFn(cachedOverrides, resolvedOverrides)) + ) { + return cachedPromise; + } + // otherwise, instantiate the module + const modulePromise = zxingModuleFactory({ + ...resolvedOverrides, + }) as Promise>; + // cache the overrides and the promise + __CACHE__.set(zxingModuleFactory, [resolvedOverrides, modulePromise]); + // and return the promise + return modulePromise; + } -export function purgeZXingModule() { - zxingWeakMap = new WeakMap(); + // otherwise only update the cache if the overrides have changed + if (!(cacheHit ?? equalityFn(cachedOverrides, resolvedOverrides))) { + __CACHE__.set(zxingModuleFactory, [resolvedOverrides]); + } } -// export function setZXingModuleOverridesWithFactory( -// zxingModuleFactory: ZXingModuleFactory, -// zxingModuleOverrides: ZXingModuleOverrides, -// ) { -// zxingWeakMap.set(zxingModuleFactory, { -// moduleOverrides: zxingModuleOverrides, -// }); -// } - -export function setZXingModuleOverridesWithFactory( +export function purgeZXingModuleWithFactory( zxingModuleFactory: ZXingModuleFactory, - zxingModuleOverrides: ZXingModuleOverrides, - options?: { - fireImmediately?: boolean; - equalityFn?: ( - prevOverrides: ZXingModuleOverrides, - currOverrides: ZXingModuleOverrides, - ) => boolean; - }, -) {} +) { + __CACHE__.delete(zxingModuleFactory); +} export async function readBarcodesWithFactory( zxingModuleFactory: ZXingModuleFactory, @@ -183,7 +190,9 @@ export async function readBarcodesWithFactory( ...defaultReaderOptions, ...readerOptions, }; - const zxingModule = await getZXingModuleWithFactory(zxingModuleFactory); + const zxingModule = await prepareZXingModuleWithFactory(zxingModuleFactory, { + fireImmediately: true, + }); let zxingReadResultVector: ZXingVector; let bufferPtr: number; if ("size" in input) { @@ -231,7 +240,9 @@ export async function writeBarcodeWithFactory( const zxingWriterOptions = writerOptionsToZXingWriterOptions( requiredWriterOptions, ); - const zxingModule = await getZXingModuleWithFactory(zxingModuleFactory); + const zxingModule = await prepareZXingModuleWithFactory(zxingModuleFactory, { + fireImmediately: true, + }); if (typeof input === "string") { return zxingWriteResultToWriteResult( zxingModule.writeBarcodeFromText(input, zxingWriterOptions), diff --git a/src/full/index.ts b/src/full/index.ts index d4bb29e2..b4cb5205 100644 --- a/src/full/index.ts +++ b/src/full/index.ts @@ -1,28 +1,75 @@ +import type { Merge } from "type-fest"; import type { ReaderOptions, WriterOptions } from "../bindings/index.js"; import { + type PrepareZXingModuleOptions, type ZXingFullModule, type ZXingModuleOverrides, - getZXingModuleWithFactory, + prepareZXingModuleWithFactory, + purgeZXingModuleWithFactory, readBarcodesWithFactory, - setZXingModuleOverridesWithFactory, writeBarcodeWithFactory, } from "../core.js"; import zxingModuleFactory from "./zxing_full.js"; +export function prepareZXingModule( + options?: Merge, +): void; + +export function prepareZXingModule( + options: Merge, +): Promise; + +export function prepareZXingModule( + options?: PrepareZXingModuleOptions, +): void | Promise; + +export function prepareZXingModule(options?: PrepareZXingModuleOptions) { + return prepareZXingModuleWithFactory(zxingModuleFactory, options); +} + +export function purgeZXingModule() { + return purgeZXingModuleWithFactory(zxingModuleFactory); +} + +/** + * @deprecated Use {@link prepareZXingModule | `prepareZXingModule`} instead. + * This function is equivalent to the following: + * + * ```ts + * prepareZXingModule({ + * overrides: zxingModuleOverrides, + * equalityFn: Object.is, + * fireImmediately: true, + * }); + */ export function getZXingModule(zxingModuleOverrides?: ZXingModuleOverrides) { - return getZXingModuleWithFactory( - zxingModuleFactory, - zxingModuleOverrides, - ) as Promise; + return prepareZXingModule({ + overrides: zxingModuleOverrides, + equalityFn: Object.is, + fireImmediately: true, + }); } +/** + * @deprecated Use {@link prepareZXingModule | `prepareZXingModule`} instead. + * This function is equivalent to the following: + * + * ```ts + * prepareZXingModule({ + * overrides: zxingModuleOverrides, + * equalityFn: Object.is, + * fireImmediately: false, + * }); + * ``` + */ export function setZXingModuleOverrides( zxingModuleOverrides: ZXingModuleOverrides, ) { - return setZXingModuleOverridesWithFactory( - zxingModuleFactory, - zxingModuleOverrides, - ); + prepareZXingModule({ + overrides: zxingModuleOverrides, + equalityFn: Object.is, + fireImmediately: false, + }); } export async function readBarcodes( @@ -33,7 +80,7 @@ export async function readBarcodes( } /** - * @deprecated Use {@link readBarcodes} instead. + * @deprecated Use {@link readBarcodes | `readBarcodes`} instead. */ export async function readBarcodesFromImageFile( imageFile: Blob, @@ -43,7 +90,7 @@ export async function readBarcodesFromImageFile( } /** - * @deprecated Use {@link readBarcodes} instead. + * @deprecated Use {@link readBarcodes | `readBarcodes`} instead. */ export async function readBarcodesFromImageData( imageData: ImageData, @@ -62,8 +109,8 @@ export async function writeBarcode( export * from "../bindings/exposedReaderBindings.js"; export * from "../bindings/exposedWriterBindings.js"; export { - VERSION, - purgeZXingModule, + ZXING_WASM_VERSION, + type PrepareZXingModuleOptions, type ZXingFullModule, type ZXingModuleOverrides, } from "../core.js"; diff --git a/src/reader/index.ts b/src/reader/index.ts index 3658591b..15f19b09 100644 --- a/src/reader/index.ts +++ b/src/reader/index.ts @@ -1,27 +1,74 @@ +import type { Merge } from "type-fest"; import type { ReaderOptions } from "../bindings/index.js"; import { + type PrepareZXingModuleOptions, type ZXingModuleOverrides, type ZXingReaderModule, - getZXingModuleWithFactory, + prepareZXingModuleWithFactory, + purgeZXingModuleWithFactory, readBarcodesWithFactory, - setZXingModuleOverridesWithFactory, } from "../core.js"; import zxingModuleFactory from "./zxing_reader.js"; +export function prepareZXingModule( + options?: Merge, +): void; + +export function prepareZXingModule( + options: Merge, +): Promise; + +export function prepareZXingModule( + options?: PrepareZXingModuleOptions, +): void | Promise; + +export function prepareZXingModule(options?: PrepareZXingModuleOptions) { + return prepareZXingModuleWithFactory(zxingModuleFactory, options); +} + +export function purgeZXingModule() { + return purgeZXingModuleWithFactory(zxingModuleFactory); +} + +/** + * @deprecated Use {@link prepareZXingModule | `prepareZXingModule`} instead. + * This function is equivalent to the following: + * + * ```ts + * prepareZXingModule({ + * overrides: zxingModuleOverrides, + * equalityFn: Object.is, + * fireImmediately: true, + * }); + */ export function getZXingModule(zxingModuleOverrides?: ZXingModuleOverrides) { - return getZXingModuleWithFactory( - zxingModuleFactory, - zxingModuleOverrides, - ) as Promise; + return prepareZXingModule({ + overrides: zxingModuleOverrides, + equalityFn: Object.is, + fireImmediately: true, + }); } +/** + * @deprecated Use {@link prepareZXingModule | `prepareZXingModule`} instead. + * This function is equivalent to the following: + * + * ```ts + * prepareZXingModule({ + * overrides: zxingModuleOverrides, + * equalityFn: Object.is, + * fireImmediately: false, + * }); + * ``` + */ export function setZXingModuleOverrides( zxingModuleOverrides: ZXingModuleOverrides, ) { - return setZXingModuleOverridesWithFactory( - zxingModuleFactory, - zxingModuleOverrides, - ); + prepareZXingModule({ + overrides: zxingModuleOverrides, + equalityFn: Object.is, + fireImmediately: false, + }); } export async function readBarcodes( @@ -32,7 +79,7 @@ export async function readBarcodes( } /** - * @deprecated Use {@link readBarcodes} instead. + * @deprecated Use {@link readBarcodes | `readBarcodes`} instead. */ export async function readBarcodesFromImageFile( imageFile: Blob, @@ -42,7 +89,7 @@ export async function readBarcodesFromImageFile( } /** - * @deprecated Use {@link readBarcodes} instead. + * @deprecated Use {@link readBarcodes | `readBarcodes`} instead. */ export async function readBarcodesFromImageData( imageData: ImageData, @@ -53,8 +100,8 @@ export async function readBarcodesFromImageData( export * from "../bindings/exposedReaderBindings.js"; export { - VERSION, - purgeZXingModule, + ZXING_WASM_VERSION, + type PrepareZXingModuleOptions, type ZXingReaderModule, type ZXingModuleOverrides, } from "../core.js"; diff --git a/src/writer/index.ts b/src/writer/index.ts index 5fe8c3b9..4393c4b2 100644 --- a/src/writer/index.ts +++ b/src/writer/index.ts @@ -5,30 +5,77 @@ * @packageDocumentation */ +import type { Merge } from "type-fest"; import type { WriterOptions } from "../bindings/index.js"; import { + type PrepareZXingModuleOptions, type ZXingModuleOverrides, type ZXingWriterModule, - getZXingModuleWithFactory, - setZXingModuleOverridesWithFactory, + prepareZXingModuleWithFactory, + purgeZXingModuleWithFactory, writeBarcodeWithFactory, } from "../core.js"; import zxingModuleFactory from "./zxing_writer.js"; +export function prepareZXingModule( + options?: Merge, +): void; + +export function prepareZXingModule( + options: Merge, +): Promise; + +export function prepareZXingModule( + options?: PrepareZXingModuleOptions, +): void | Promise; + +export function prepareZXingModule(options?: PrepareZXingModuleOptions) { + return prepareZXingModuleWithFactory(zxingModuleFactory, options); +} + +export function purgeZXingModule() { + return purgeZXingModuleWithFactory(zxingModuleFactory); +} + +/** + * @deprecated Use {@link prepareZXingModule | `prepareZXingModule`} instead. + * This function is equivalent to the following: + * + * ```ts + * prepareZXingModule({ + * overrides: zxingModuleOverrides, + * equalityFn: Object.is, + * fireImmediately: true, + * }); + */ export function getZXingModule(zxingModuleOverrides?: ZXingModuleOverrides) { - return getZXingModuleWithFactory( - zxingModuleFactory, - zxingModuleOverrides, - ) as Promise; + return prepareZXingModule({ + overrides: zxingModuleOverrides, + equalityFn: Object.is, + fireImmediately: true, + }); } +/** + * @deprecated Use {@link prepareZXingModule | `prepareZXingModule`} instead. + * This function is equivalent to the following: + * + * ```ts + * prepareZXingModule({ + * overrides: zxingModuleOverrides, + * equalityFn: Object.is, + * fireImmediately: false, + * }); + * ``` + */ export function setZXingModuleOverrides( zxingModuleOverrides: ZXingModuleOverrides, ) { - return setZXingModuleOverridesWithFactory( - zxingModuleFactory, - zxingModuleOverrides, - ); + prepareZXingModule({ + overrides: zxingModuleOverrides, + equalityFn: Object.is, + fireImmediately: false, + }); } export async function writeBarcode( @@ -40,8 +87,8 @@ export async function writeBarcode( export * from "../bindings/exposedWriterBindings.js"; export { - VERSION, - purgeZXingModule, + ZXING_WASM_VERSION, + type PrepareZXingModuleOptions, type ZXingWriterModule, type ZXingModuleOverrides, } from "../core.js"; diff --git a/tests/blackbox.test.ts b/tests/blackbox.test.ts index fa5b715d..368b1b30 100644 --- a/tests/blackbox.test.ts +++ b/tests/blackbox.test.ts @@ -253,15 +253,12 @@ for (const { ..._summary!.undetected!.images, ]).size; if (_summary!.misreads!.total === 0) { - // biome-ignore lint/performance/noDelete: remove misreads if there is no misread delete _summary!.misreads; } if (_summary!.undetected!.total === 0) { - // biome-ignore lint/performance/noDelete: remove undetected if there is no undetected delete _summary!.undetected; } if (_summary!.failures === 0) { - // biome-ignore lint/performance/noDelete: remove failures if there is no failure delete _summary!.failures; } }