@loaders.gl/core
Version:
The core API for working with loaders.gl loaders and writers
167 lines (143 loc) • 5.5 kB
text/typescript
// loaders.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import type {
Loader,
LoaderContext,
LoaderOptions,
DataType,
LoaderWithParser,
LoaderOptionsType,
LoaderReturnType,
LoaderArrayOptionsType,
LoaderArrayReturnType
} from '@loaders.gl/loader-utils';
import {parseWithWorker, canParseWithWorker, mergeLoaderOptions} from '@loaders.gl/loader-utils';
import {assert, validateWorkerVersion} from '@loaders.gl/worker-utils';
import {isLoaderObject} from '../loader-utils/normalize-loader';
import {isResponse} from '../../javascript-utils/is-type';
import {normalizeOptions} from '../loader-utils/option-utils';
import {getArrayBufferOrStringFromData} from '../loader-utils/get-data';
import {getLoaderContext, getLoadersFromContext} from '../loader-utils/loader-context';
import {getResourceUrl} from '../utils/resource-utils';
import {selectLoader} from './select-loader';
// type LoaderArrayType<T> = T extends (infer Loader)[] ? LoaderOptionsType<Loader> : T
/**
* Parses `data` asynchronously using the supplied loader
*/
export async function parse<
LoaderT extends Loader,
OptionsT extends LoaderOptions = LoaderOptionsType<LoaderT>
>(
data: DataType | Promise<DataType>,
loader: LoaderT,
options?: OptionsT,
context?: LoaderContext
): Promise<LoaderReturnType<LoaderT>>;
/**
* Parses `data` asynchronously by matching one of the supplied loader
*/
export async function parse<
LoaderArrayT extends Loader[],
OptionsT extends LoaderOptions = LoaderArrayOptionsType<LoaderArrayT>
>(
data: DataType | Promise<DataType>,
loaders: LoaderArrayT,
options?: OptionsT,
context?: LoaderContext
): Promise<LoaderArrayReturnType<LoaderArrayT>>;
/**
* Parses data asynchronously by matching a pre-registered loader
* @deprecated Loader registration is deprecated, use parse(data, loaders, options) instead
*/
export async function parse(
data: DataType | Promise<DataType>,
options?: LoaderOptions
): Promise<unknown>;
/**
* Parses `data` using a specified loader
* @param data
* @param loaders
* @param options
* @param context
*/
// implementation signature
export async function parse(
data: DataType | Promise<DataType>,
loaders?: Loader | Loader[] | LoaderOptions,
options?: LoaderOptions,
context?: LoaderContext
): Promise<unknown> {
// Signature: parse(data, options, context | url)
// Uses registered loaders
if (loaders && !Array.isArray(loaders) && !isLoaderObject(loaders)) {
context = undefined; // context not supported in short signature
options = loaders as LoaderOptions;
loaders = undefined;
}
data = await data; // Resolve any promise
options = options || ({} as LoaderOptions); // Could be invalid...
// Extract a url for auto detection
const url = getResourceUrl(data);
// Chooses a loader (and normalizes it)
// Also use any loaders in the context, new loaders take priority
const typedLoaders = loaders as Loader | Loader[] | undefined;
const candidateLoaders = getLoadersFromContext(typedLoaders, context);
// todo hacky type cast
const loader = await selectLoader(data as ArrayBuffer, candidateLoaders, options);
// Note: if no loader was found, if so just return null
if (!loader) {
return null;
}
// Normalize options
// @ts-expect-error
options = normalizeOptions(options, loader, candidateLoaders, url); // Could be invalid...
// Get a context (if already present, will be unchanged)
context = getLoaderContext(
// @ts-expect-error
{url, _parse: parse, loaders: candidateLoaders},
options,
context || null
);
return await parseWithLoader(loader, data, options, context);
}
// TODO: support progress and abort
// TODO - should accept loader.parseAsyncIterator and concatenate.
async function parseWithLoader(
loader: Loader,
data,
options: LoaderOptions,
context: LoaderContext
): Promise<unknown> {
validateWorkerVersion(loader);
options = mergeLoaderOptions(loader.options, options);
if (isResponse(data)) {
// Serialize to support passing the response to web worker
const response = data as Response;
const {ok, redirected, status, statusText, type, url} = response;
const headers = Object.fromEntries(response.headers.entries());
// @ts-expect-error TODO - fix this
context.response = {headers, ok, redirected, status, statusText, type, url};
}
data = await getArrayBufferOrStringFromData(data, loader, options);
const loaderWithParser = loader as LoaderWithParser;
// First check for synchronous text parser, wrap results in promises
if (loaderWithParser.parseTextSync && typeof data === 'string') {
return loaderWithParser.parseTextSync(data, options, context);
}
// If we have a workerUrl and the loader can parse the given options efficiently in a worker
if (canParseWithWorker(loader, options)) {
return await parseWithWorker(loader, data, options, context, parse);
}
// Check for asynchronous parser
if (loaderWithParser.parseText && typeof data === 'string') {
return await loaderWithParser.parseText(data, options, context);
}
if (loaderWithParser.parse) {
return await loaderWithParser.parse(data, options, context);
}
// This should not happen, all sync loaders should also offer `parse` function
assert(!loaderWithParser.parseSync);
// TBD - If asynchronous parser not available, return null
throw new Error(`${loader.id} loader - no parser found and worker is disabled`);
}