• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

visgl / loaders.gl / 24285926069

11 Apr 2026 03:46PM UTC coverage: 54.859% (-0.2%) from 55.094%
24285926069

push

github

web-flow
feat(tiles) Add Tile2DSource (#3366)

9084 of 17900 branches covered (50.75%)

Branch coverage included in aggregate %.

390 of 824 new or added lines in 10 files covered. (47.33%)

5 existing lines in 3 files now uncovered.

18943 of 33189 relevant lines covered (57.08%)

5038.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

61.86
/modules/loader-utils/src/lib/sources/data-source.ts
1
// loaders.gl
2
// SPDX-License-Identifier: MIT
3
// Copyright (c) vis.gl contributors
4

5
import type {Loader, StrictLoaderOptions} from '../../loader-types';
6
import type {RequiredOptions} from '../option-utils/merge-options';
7
import {mergeOptions} from '../option-utils/merge-options';
8
import {resolvePath} from '../path-utils/file-aliases';
9
import {log} from '../log-utils/log';
10

11
/** Common properties for all data sources */
12
export type DataSourceOptions = Partial<{
13
  core: {
14
    /** Allows application to specify which source should be selected. Matches `Source.type`. Defaults to 'auto' */
15
    type?: string;
16
    /** Any dataset attributions (in case underlying metadata does not include attributions) */
17
    attributions?: string[];
18
    /** LoaderOptions provide an option to override `fetch`. Will also be passed to any sub loaders */
19
    loadOptions?: StrictLoaderOptions;
20
    /** Make additional loaders available to the data source */
21
    loaders?: Loader[];
22
    /** Called when source-level initialization or metadata loading fails. */
23
    onError?: (error: Error, source: DataSource<any, any>) => void;
24
  };
25
  [key: string]: Record<string, unknown>;
26
}>;
27

28
/** base class of all data sources */
29
export abstract class DataSource<DataT, OptionsT extends DataSourceOptions> {
30
  static defaultOptions: Required<DataSourceOptions> = {
351✔
31
    core: {
32
      type: 'auto',
33
      attributions: [],
34
      loadOptions: {},
35
      loaders: [],
36
      onError: undefined!
37
    }
38
  };
39

40
  optionsType?: OptionsT & DataSourceOptions;
41
  options: Required<OptionsT & DataSourceOptions>;
42
  readonly data: DataT;
43
  readonly url: string;
44

45
  /** The actual load options, if calling a loaders.gl loader */
46
  loadOptions: StrictLoaderOptions;
47
  /** A resolved fetch function extracted from loadOptions prop */
48
  fetch: (url: string, options?: RequestInit) => Promise<Response>;
49
  _needsRefresh: boolean = true;
34✔
50

51
  constructor(
52
    data: DataT,
53
    options: OptionsT,
54
    defaultOptions?: Omit<RequiredOptions<OptionsT>, 'core'>
55
  ) {
56
    if (defaultOptions) {
34✔
57
      // @ts-expect-error Typescript gets confused
58
      this.options = mergeOptions({...defaultOptions, core: DataSource.defaultOptions}, options);
31✔
59
    } else {
60
      // @ts-expect-error
61
      this.options = {...options};
3✔
62
    }
63
    this.data = data;
34✔
64
    this.url = typeof data === 'string' ? resolvePath(data) : '';
34✔
65
    this.loadOptions = normalizeDirectLoaderOptions(this.options.core?.loadOptions);
34✔
66
    this.fetch = getFetchFunction(this.loadOptions);
34✔
67
  }
68

69
  setProps(options: OptionsT) {
70
    this.options = Object.assign(this.options, options);
×
71
    // TODO - add a shallow compare to avoid setting refresh if no change?
72
    this.setNeedsRefresh();
×
73
  }
74

75
  /** Mark this data source as needing a refresh (redraw) */
76
  setNeedsRefresh(): void {
77
    this._needsRefresh = true;
×
78
  }
79

80
  /**
81
   * Does this data source need refreshing?
82
   * @note The specifics of the refresh mechanism depends on type of data source
83
   */
84
  getNeedsRefresh(clear: boolean = true) {
×
85
    const needsRefresh = this._needsRefresh;
×
86
    if (clear) {
×
87
      this._needsRefresh = false;
×
88
    }
89
    return needsRefresh;
×
90
  }
91

92
  /** Reports a source-level failure through the configured callback or the shared logger. */
93
  protected reportError(error: unknown, message: string): Error {
NEW
94
    const normalizedError = normalizeError(error, message);
×
NEW
95
    const callback = this.options.core?.onError;
×
NEW
96
    if (callback) {
×
NEW
97
      callback(normalizedError, this);
×
98
    } else {
NEW
99
      log.warn(`${this.constructor.name}: ${normalizedError.message}`)();
×
100
    }
NEW
101
    return normalizedError;
×
102
  }
103
}
104

105
/**
106
 * Gets the current fetch function from options
107
 * @todo - move to loader-utils module
108
 * @todo - use in core module counterpart
109
 * @param options
110
 * @param context
111
 */
112
export function getFetchFunction(options?: StrictLoaderOptions) {
113
  const fetchFunction = options?.core?.fetch;
34✔
114

115
  // options.fetch can be a function
116
  if (fetchFunction && typeof fetchFunction === 'function') {
34!
117
    return (url: string, fetchOptions?: RequestInit) => fetchFunction(url, fetchOptions);
×
118
  }
119

120
  // options.fetch can be an options object, use global fetch with those options
121
  const fetchOptions = options?.fetch;
34✔
122
  if (fetchOptions && typeof fetchOptions !== 'function') {
34✔
123
    return (url, requestOptions) => fetch(url, mergeFetchOptions(fetchOptions, requestOptions));
1✔
124
  }
125

126
  // else return the global fetch function
127
  return (url, requestOptions) => fetch(url, requestOptions);
33✔
128
}
129

130
function mergeFetchOptions(fetchOptions: RequestInit, requestOptions?: RequestInit): RequestInit {
131
  const mergedOptions: RequestInit = {...fetchOptions, ...requestOptions};
1✔
132
  if (fetchOptions.headers || requestOptions?.headers) {
1!
133
    mergedOptions.headers = mergeHeaders(fetchOptions.headers, requestOptions?.headers);
1✔
134
  }
135
  return mergedOptions;
1✔
136
}
137

138
function mergeHeaders(defaultHeaders?: HeadersInit, requestHeaders?: HeadersInit): Headers {
139
  const headers = new Headers(defaultHeaders);
1✔
140
  if (requestHeaders) {
1!
141
    new Headers(requestHeaders).forEach((value, key) => headers.set(key, value));
×
142
  }
143
  return headers;
1✔
144
}
145

146
function normalizeDirectLoaderOptions(options?: StrictLoaderOptions): StrictLoaderOptions {
147
  const loadOptions = {...options};
34✔
148
  if (options?.core) {
34!
149
    loadOptions.core = {...options.core};
×
150
  }
151

152
  const topLevelBaseUri = typeof loadOptions.baseUri === 'string' ? loadOptions.baseUri : undefined;
34✔
153
  const topLevelBaseUrl = typeof loadOptions.baseUrl === 'string' ? loadOptions.baseUrl : undefined;
34✔
154

155
  if (topLevelBaseUri !== undefined || topLevelBaseUrl !== undefined) {
34✔
156
    loadOptions.core ||= {};
2✔
157
    if (loadOptions.core.baseUrl === undefined) {
2!
158
      loadOptions.core.baseUrl = topLevelBaseUrl ?? topLevelBaseUri;
2✔
159
    }
160
    delete loadOptions.baseUri;
2✔
161
    delete loadOptions.baseUrl;
2✔
162
  }
163

164
  return loadOptions;
34✔
165
}
166

167
/** Normalizes arbitrary thrown values to `Error` instances for source-level reporting. */
168
function normalizeError(error: unknown, message: string): Error {
NEW
169
  if (error instanceof Error) {
×
NEW
170
    return error;
×
171
  }
NEW
172
  if (typeof error === 'string') {
×
NEW
173
    return new Error(error);
×
174
  }
NEW
175
  return new Error(message);
×
176
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc