@loaders.gl/scan 5.0.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +52 -0
  2. package/README.md +219 -0
  3. package/dist/delta.cjs +24246 -0
  4. package/dist/delta.cjs.map +6 -0
  5. package/dist/delta.d.ts +11 -0
  6. package/dist/delta.d.ts.map +1 -0
  7. package/dist/delta.js +12 -0
  8. package/dist/delta.js.map +1 -0
  9. package/dist/dist.min.js +18475 -0
  10. package/dist/federated-table-scan-source.d.ts +116 -0
  11. package/dist/federated-table-scan-source.d.ts.map +1 -0
  12. package/dist/federated-table-scan-source.js +691 -0
  13. package/dist/federated-table-scan-source.js.map +1 -0
  14. package/dist/hudi.cjs +24065 -0
  15. package/dist/hudi.cjs.map +6 -0
  16. package/dist/hudi.d.ts +54 -0
  17. package/dist/hudi.d.ts.map +1 -0
  18. package/dist/hudi.js +124 -0
  19. package/dist/hudi.js.map +1 -0
  20. package/dist/iceberg.cjs +25862 -0
  21. package/dist/iceberg.cjs.map +6 -0
  22. package/dist/iceberg.d.ts +12 -0
  23. package/dist/iceberg.d.ts.map +1 -0
  24. package/dist/iceberg.js +12 -0
  25. package/dist/iceberg.js.map +1 -0
  26. package/dist/index.cjs +3485 -0
  27. package/dist/index.cjs.map +6 -0
  28. package/dist/index.d.ts +15 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +14 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/scan-engine.d.ts +44 -0
  33. package/dist/scan-engine.d.ts.map +1 -0
  34. package/dist/scan-engine.js +49 -0
  35. package/dist/scan-engine.js.map +1 -0
  36. package/dist/scan-query.d.ts +24 -0
  37. package/dist/scan-query.d.ts.map +1 -0
  38. package/dist/scan-query.js +5 -0
  39. package/dist/scan-query.js.map +1 -0
  40. package/dist/vector-table-scan-source.d.ts +90 -0
  41. package/dist/vector-table-scan-source.d.ts.map +1 -0
  42. package/dist/vector-table-scan-source.js +215 -0
  43. package/dist/vector-table-scan-source.js.map +1 -0
  44. package/package.json +66 -0
  45. package/src/delta.ts +18 -0
  46. package/src/federated-table-scan-source.ts +991 -0
  47. package/src/hudi.ts +200 -0
  48. package/src/iceberg.ts +36 -0
  49. package/src/index.ts +97 -0
  50. package/src/scan-engine.ts +96 -0
  51. package/src/scan-query.ts +27 -0
  52. package/src/vector-table-scan-source.ts +294 -0
package/src/hudi.ts ADDED
@@ -0,0 +1,200 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {
6
+ CoreAPI,
7
+ ScanFragment,
8
+ ScanFragmentProvider,
9
+ ScanQueryMetadata,
10
+ ScanQueryMetadataOptions,
11
+ TableScanSource
12
+ } from '@loaders.gl/loader-utils';
13
+ import {createScanQueryMetadata, DataSource} from '@loaders.gl/loader-utils';
14
+ import {ParquetDatasetSource} from '@loaders.gl/parquet/parquet-dataset-source';
15
+ import {PARQUET_TABLE_QUERY_CAPABILITIES} from '@loaders.gl/parquet/parquet-source-capabilities';
16
+ import type {
17
+ ParquetDatasetBatch,
18
+ ParquetDatasetExplain,
19
+ ParquetDatasetReadOptions,
20
+ ParquetPredicate,
21
+ ParquetDatasetSourceOptions
22
+ } from '@loaders.gl/parquet/parquet-source-types';
23
+
24
+ /** A physical Parquet base file recorded by a Hudi snapshot descriptor. */
25
+ export type HudiBaseFile = Readonly<{
26
+ path: string;
27
+ size?: number;
28
+ numRecords?: number;
29
+ partitionValues?: Readonly<Record<string, unknown>>;
30
+ }>;
31
+
32
+ /** Minimal, portable descriptor used by the Hudi proof of concept. */
33
+ export type HudiSnapshotDescriptor = Readonly<{
34
+ tableType?: 'COPY_ON_WRITE' | 'MERGE_ON_READ';
35
+ basePath?: string;
36
+ completedInstant?: string;
37
+ files: readonly HudiBaseFile[];
38
+ }>;
39
+
40
+ /** Options for reading a Hudi snapshot descriptor and its Parquet base files. */
41
+ export type HudiSourceOptions = ParquetDatasetSourceOptions & {
42
+ hudi?: {
43
+ baseUrl?: string;
44
+ headers?: HeadersInit;
45
+ };
46
+ };
47
+
48
+ /** Query options accepted by a Hudi snapshot scan. */
49
+ export type HudiScanOptions = ParquetDatasetReadOptions;
50
+
51
+ /**
52
+ * Minimal read-only Hudi Copy-on-Write source.
53
+ *
54
+ * The POC consumes a small JSON snapshot descriptor containing the selected base files. It does
55
+ * not claim to decode Hudi log files, merge-on-read records, or incremental timeline queries.
56
+ */
57
+ export class HudiTableSource
58
+ extends DataSource<string | Blob, HudiSourceOptions>
59
+ implements
60
+ TableScanSource<ParquetDatasetBatch, ParquetPredicate>,
61
+ ScanFragmentProvider<ParquetPredicate>
62
+ {
63
+ private descriptorPromise: Promise<HudiSnapshotDescriptor> | null = null;
64
+
65
+ /** Creates a source from a Hudi snapshot descriptor URL or Blob. */
66
+ constructor(data: string | Blob, options: HudiSourceOptions = {}, coreApi?: CoreAPI) {
67
+ super(data, options, undefined, coreApi);
68
+ }
69
+
70
+ /** Loads and validates the POC snapshot descriptor. */
71
+ async getDescriptor(signal?: AbortSignal): Promise<HudiSnapshotDescriptor> {
72
+ if (!this.descriptorPromise) {
73
+ this.descriptorPromise = this.loadDescriptor(signal).catch(error => {
74
+ this.descriptorPromise = null;
75
+ throw error;
76
+ });
77
+ }
78
+ return this.descriptorPromise;
79
+ }
80
+
81
+ /** Discovers the Copy-on-Write Parquet base files in the selected snapshot. */
82
+ async getScanFragments(options: HudiScanOptions = {}): Promise<readonly ScanFragment[]> {
83
+ const descriptor = await this.getDescriptor(options.signal);
84
+ if (descriptor.tableType === 'MERGE_ON_READ') {
85
+ throw new Error('Hudi Merge-on-Read snapshots are not supported by this POC');
86
+ }
87
+ return Object.freeze(
88
+ descriptor.files.map(file =>
89
+ Object.freeze({
90
+ id: file.path,
91
+ uri: this.resolveDataFile(file.path, descriptor.basePath),
92
+ partitionValues: file.partitionValues,
93
+ byteLength: file.size,
94
+ rowCount: file.numRecords,
95
+ metadata: {completedInstant: descriptor.completedInstant}
96
+ })
97
+ )
98
+ );
99
+ }
100
+
101
+ /** Discovers schema and snapshot statistics without decoding every row. */
102
+ async getQueryMetadata(options: ScanQueryMetadataOptions = {}): Promise<ScanQueryMetadata> {
103
+ const fragments = await this.getScanFragments(options);
104
+ if (!fragments.length) throw new Error('Hudi snapshot contains no Parquet base files');
105
+ const dataset = this.createDataset(fragments);
106
+ const schema = await dataset.getSchema({signal: options.signal});
107
+ return createScanQueryMetadata({
108
+ sourceType: 'hudi',
109
+ queryType: 'table',
110
+ execution: {status: 'supported', method: 'read'},
111
+ name: this.data instanceof Blob ? undefined : this.data,
112
+ schema,
113
+ capabilities: {table: PARQUET_TABLE_QUERY_CAPABILITIES},
114
+ statistics: {
115
+ rowCount: fragments.reduce((total, fragment) => total + Number(fragment.rowCount || 0), 0),
116
+ byteLength: fragments.reduce(
117
+ (total, fragment) => total + Number(fragment.byteLength || 0),
118
+ 0
119
+ )
120
+ }
121
+ });
122
+ }
123
+
124
+ /** Explains the Hudi snapshot and delegated Parquet physical plan. */
125
+ async getScanPlan(options: HudiScanOptions = {}): Promise<ParquetDatasetExplain> {
126
+ return this.createDataset(await this.getScanFragments(options)).getScanPlan(options);
127
+ }
128
+
129
+ /** Reads the selected Hudi snapshot through the shared Parquet executor. */
130
+ read(options: HudiScanOptions = {}): AsyncIterable<ParquetDatasetBatch> {
131
+ return this.createDatasetFromOptions(options).read(options);
132
+ }
133
+
134
+ /** Alias for `read()` used by scan-aware consumers. */
135
+ scan(options: HudiScanOptions = {}): AsyncIterable<ParquetDatasetBatch> {
136
+ return this.read(options);
137
+ }
138
+
139
+ private async loadDescriptor(signal?: AbortSignal): Promise<HudiSnapshotDescriptor> {
140
+ const text =
141
+ typeof this.data === 'string'
142
+ ? await (await this.fetch(this.data, {headers: this.options.hudi?.headers, signal})).text()
143
+ : await this.data.text();
144
+ let descriptor: unknown;
145
+ try {
146
+ descriptor = JSON.parse(text);
147
+ } catch {
148
+ throw new Error('Hudi snapshot descriptor must be valid JSON');
149
+ }
150
+ const files =
151
+ descriptor && typeof descriptor === 'object'
152
+ ? (descriptor as {files?: unknown}).files
153
+ : undefined;
154
+ if (!Array.isArray(files)) {
155
+ throw new Error('Hudi snapshot descriptor must contain a files array');
156
+ }
157
+ const value = descriptor as HudiSnapshotDescriptor;
158
+ if (
159
+ value.tableType &&
160
+ value.tableType !== 'COPY_ON_WRITE' &&
161
+ value.tableType !== 'MERGE_ON_READ'
162
+ ) {
163
+ throw new Error(`Unsupported Hudi table type: ${String(value.tableType)}`);
164
+ }
165
+ return value;
166
+ }
167
+
168
+ private createDataset(fragments: readonly ScanFragment[]): ParquetDatasetSource {
169
+ return new ParquetDatasetSource(
170
+ fragments.map(fragment => ({
171
+ data: fragment.uri!,
172
+ id: fragment.id,
173
+ partitions: fragment.partitionValues as never,
174
+ metadata: fragment.metadata
175
+ })),
176
+ this.options
177
+ );
178
+ }
179
+
180
+ private createDatasetFromOptions(options: HudiScanOptions): ParquetDatasetSource {
181
+ return new ParquetDatasetSource(
182
+ () =>
183
+ this.getScanFragments(options).then(fragments =>
184
+ fragments.map(fragment => ({
185
+ data: fragment.uri!,
186
+ id: fragment.id,
187
+ partitions: fragment.partitionValues as never,
188
+ metadata: fragment.metadata
189
+ }))
190
+ ),
191
+ this.options
192
+ );
193
+ }
194
+
195
+ private resolveDataFile(path: string, basePath?: string): string {
196
+ const baseUrl = this.options.hudi?.baseUrl || basePath;
197
+ if (baseUrl) return new URL(path, baseUrl).toString();
198
+ return path;
199
+ }
200
+ }
package/src/iceberg.ts ADDED
@@ -0,0 +1,36 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ /**
6
+ * Incubating Iceberg table-format adapters.
7
+ *
8
+ * This entry point is intentionally separate from the scan package root. The adapter remains
9
+ * backed by the Parquet implementation while its public API and planning semantics mature.
10
+ */
11
+ export {IcebergTableSource} from '@loaders.gl/parquet/iceberg-table-source';
12
+ export type {
13
+ IcebergScanOptions,
14
+ IcebergSourceOptions
15
+ } from '@loaders.gl/parquet/iceberg-table-source';
16
+ export {IcebergRestCatalog} from '@loaders.gl/parquet/iceberg-rest-catalog';
17
+ export type {
18
+ IcebergRestCatalogOptions,
19
+ IcebergRestTable,
20
+ IcebergRestTableIdentifier
21
+ } from '@loaders.gl/parquet/iceberg-rest-catalog';
22
+ export type {
23
+ IcebergBoundingBox,
24
+ IcebergDataFile,
25
+ IcebergDeleteFile,
26
+ IcebergManifestFile,
27
+ IcebergParquetFile,
28
+ IcebergPartitionSpec,
29
+ IcebergScanPlan,
30
+ IcebergSchema,
31
+ IcebergSnapshot,
32
+ IcebergSnapshotReference,
33
+ IcebergSpatialFilter,
34
+ IcebergTableMetadata,
35
+ IcebergTableSourceOptions
36
+ } from '@loaders.gl/parquet/iceberg-types';
package/src/index.ts ADDED
@@ -0,0 +1,97 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ export {createScanEngine, registerScanBackend} from './scan-engine';
6
+ // The low-level executor lives in loader-utils so format packages can reuse it without importing
7
+ // this optional module. It is re-exported here as the scan package's common batch adapter.
8
+ export {
9
+ executeTableScanBatches,
10
+ filterTableBatch,
11
+ makeTableScanBatch,
12
+ projectTableBatch,
13
+ projectTableSchema,
14
+ truncateTableBatch
15
+ } from '@loaders.gl/loader-utils';
16
+ export type {TableScanBatchReader, TableScanBatchOperators} from '@loaders.gl/loader-utils';
17
+ export type {
18
+ ScanBackend,
19
+ ScanBackendLoader,
20
+ ScanBackendName,
21
+ ScanEngine,
22
+ ScanEngineOptions
23
+ } from './scan-engine';
24
+
25
+ // Re-export the query vocabulary from one optional entry point for applications. Format adapters
26
+ // should continue importing the lightweight contracts from @loaders.gl/loader-utils.
27
+ export {parseSQLPredicate} from '@loaders.gl/sql';
28
+ export type {ArrowQueryOptions, SQLPredicate} from '@loaders.gl/sql';
29
+ export type {
30
+ RelationalAggregate,
31
+ RelationalExpression,
32
+ RelationalOrderKey,
33
+ TableQueryOptions
34
+ } from '@loaders.gl/loader-utils';
35
+
36
+ export {
37
+ createScanQueryMetadata,
38
+ intersectPointCloudBounds,
39
+ selectPointCloudScanTiles,
40
+ validatePointCloudQueryOptions
41
+ } from '@loaders.gl/loader-utils';
42
+ export type {
43
+ CreateScanQueryMetadataOptions,
44
+ PointCloudQueryBounds,
45
+ PointCloudQueryCapabilities,
46
+ PointCloudQueryOptions,
47
+ PointCloudScanChildrenLoader,
48
+ PointCloudScanReadOptions,
49
+ PointCloudScanSource,
50
+ PointCloudScanTile,
51
+ RasterQueryCapabilities,
52
+ RasterQueryOptions,
53
+ ScanBounds,
54
+ ScanColumnMetadata,
55
+ ScanColumnRole,
56
+ ScanExecutionMethod,
57
+ ScanExecutionTelemetry,
58
+ ScanExecutionTelemetryCallback,
59
+ ScanExecutionTelemetryStatus,
60
+ ScanSourceExecutionTelemetry,
61
+ ScanExecutionSupport,
62
+ ScanQueryCapabilities,
63
+ ScanQueryMetadata,
64
+ ScanQueryMetadataOptions,
65
+ ScanQueryMetadataProvider,
66
+ ScanRasterLevel,
67
+ ScanSourceStatistics,
68
+ ScanSpatialMetadata,
69
+ TableScanReadOptions,
70
+ TableScanSource
71
+ } from '@loaders.gl/loader-utils';
72
+ export type {ScanQuery} from './scan-query';
73
+
74
+ export {
75
+ FEDERATED_TABLE_QUERY_CAPABILITIES,
76
+ FederatedTableScanSource
77
+ } from './federated-table-scan-source';
78
+ export type {
79
+ FederatedTableBatch,
80
+ FederatedTableBatchProvenance,
81
+ FederatedTableScanExplain,
82
+ FederatedTableScanSourceOptions,
83
+ FederatedTableSchemaPolicy,
84
+ FederatedTableSourceEntry,
85
+ FederatedTableSourceExplain
86
+ } from './federated-table-scan-source';
87
+
88
+ export {
89
+ AddressedVectorTableScanSource,
90
+ VectorFeatureTableScanSource,
91
+ VectorTileTableScanSource
92
+ } from './vector-table-scan-source';
93
+ export type {
94
+ VectorFeatureTableScanSourceOptions,
95
+ VectorTableScanSourceOptions,
96
+ VectorTileTableScanSourceOptions
97
+ } from './vector-table-scan-source';
@@ -0,0 +1,96 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {ArrowTable} from '@loaders.gl/schema';
6
+ import {
7
+ explainArrowTableQuery,
8
+ queryArrowTable,
9
+ type ArrowQueryOptions,
10
+ type SQLPredicate
11
+ } from '@loaders.gl/sql';
12
+ import type {TableQueryExplain} from '@loaders.gl/loader-utils';
13
+
14
+ /** Names accepted by the optional scan engine factory. */
15
+ export type ScanBackendName = 'arrow' | (string & {});
16
+
17
+ /** A backend implementation for the Arrow table query surface. */
18
+ export type ScanBackend = Readonly<{
19
+ /** Stable backend name reported by the engine. */
20
+ name: ScanBackendName;
21
+ /** Executes a query over one in-memory Arrow table. */
22
+ query(sourceTable: ArrowTable, options?: ArrowQueryOptions): ArrowTable;
23
+ /** Optional asynchronous execution hook for backends that compile or schedule work. */
24
+ queryAsync?: (sourceTable: ArrowTable, options?: ArrowQueryOptions) => Promise<ArrowTable>;
25
+ /** Explains a query without evaluating table rows. */
26
+ explain(sourceTable: ArrowTable, options?: ArrowQueryOptions): TableQueryExplain<SQLPredicate>;
27
+ }>;
28
+
29
+ /** Lazy loader used to register an optional scan backend. */
30
+ export type ScanBackendLoader = () => ScanBackend | Promise<ScanBackend>;
31
+
32
+ /** Options for creating a scan engine. */
33
+ export type ScanEngineOptions = Readonly<{
34
+ /** Backend to use. Arrow is the built-in reference backend. */
35
+ backend?: ScanBackendName;
36
+ }>;
37
+
38
+ /** Stable application-facing scan engine returned by {@link createScanEngine}. */
39
+ export type ScanEngine = ScanBackend & {
40
+ /** Executes a query asynchronously, allowing backends to schedule remote or compiled work. */
41
+ queryAsync: (sourceTable: ArrowTable, options?: ArrowQueryOptions) => Promise<ArrowTable>;
42
+ };
43
+
44
+ const scanBackendLoaders = new Map<ScanBackendName, ScanBackendLoader>();
45
+
46
+ const arrowScanBackend: ScanBackend = Object.freeze({
47
+ name: 'arrow',
48
+ query: queryArrowTable,
49
+ queryAsync: async (sourceTable, options) => queryArrowTable(sourceTable, options),
50
+ explain: explainArrowTableQuery
51
+ });
52
+
53
+ scanBackendLoaders.set('arrow', () => arrowScanBackend);
54
+
55
+ /**
56
+ * Registers a lazy scan backend without adding it to the default bundle.
57
+ *
58
+ * This is the extension point for future DuckDB and GPU implementations. The backend package can
59
+ * register a dynamic loader from application code while the public import remains
60
+ * `@loaders.gl/scan`.
61
+ */
62
+ export function registerScanBackend(name: ScanBackendName, loader: ScanBackendLoader): void {
63
+ if (!name || !loader) {
64
+ throw new Error('A scan backend name and loader are required');
65
+ }
66
+ scanBackendLoaders.set(name, loader);
67
+ }
68
+
69
+ /**
70
+ * Creates the configured scan engine.
71
+ *
72
+ * The factory is asynchronous even for Arrow so optional backends can be loaded lazily later
73
+ * without changing the application-facing API.
74
+ */
75
+ export async function createScanEngine(options: ScanEngineOptions = {}): Promise<ScanEngine> {
76
+ const backendName = options.backend || 'arrow';
77
+ const loader = scanBackendLoaders.get(backendName);
78
+ if (!loader) {
79
+ throw new Error(
80
+ `Scan backend "${backendName}" is not registered. The built-in backend is "arrow".`
81
+ );
82
+ }
83
+ const backend = await loader();
84
+ if (backend.name !== backendName) {
85
+ throw new Error(
86
+ `Scan backend loader returned "${backend.name}" while "${backendName}" was requested.`
87
+ );
88
+ }
89
+ const query = backend.query.bind(backend);
90
+ const explain = backend.explain.bind(backend);
91
+ const queryAsync = backend.queryAsync
92
+ ? backend.queryAsync.bind(backend)
93
+ : async (sourceTable: ArrowTable, queryOptions?: ArrowQueryOptions) =>
94
+ backend.query.call(backend, sourceTable, queryOptions);
95
+ return Object.freeze({...backend, query, explain, queryAsync});
96
+ }
@@ -0,0 +1,27 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ /**
6
+ * Source-neutral values emitted by a scan query editor.
7
+ *
8
+ * This type is deliberately smaller than any one format's executor options. Applications can
9
+ * pass it between metadata-driven controls and adapters, which may normalize or ignore values
10
+ * that are not supported by a particular source family.
11
+ */
12
+ export type ScanQuery = Readonly<{
13
+ /** Output columns; an empty selection means all columns. */
14
+ columns?: readonly string[];
15
+ /** Maximum number of rows, features, or points to return. */
16
+ limit?: number;
17
+ /** Optional source-coordinate bounding box in minX, minY, maxX, maxY order. */
18
+ boundingBox?: readonly [number, number, number, number];
19
+ /** Optional raster overview or point-cloud hierarchy level. */
20
+ level?: number;
21
+ /** Optional point-cloud minimum hierarchy level. */
22
+ minimumLevel?: number;
23
+ /** Optional point-cloud maximum hierarchy level. */
24
+ maximumLevel?: number;
25
+ /** Optional point-cloud target spacing. */
26
+ targetSpacing?: number;
27
+ }>;