lakeql 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +25 -0
- package/dist/chunk-VRJ72DGA.js +6824 -0
- package/dist/cloudflare.d.ts +44 -0
- package/dist/cloudflare.js +79 -0
- package/dist/index.d.ts +1386 -0
- package/dist/index.js +1 -0
- package/dist/node.d.ts +27 -0
- package/dist/node.js +422 -0
- package/package.json +72 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AggregationBuilder, CacheApiCache, ERROR_CODES, IcebergRestCatalog, IcebergTable, IcebergUnsupportedCatalog, LaQLError, Lake, MemoryCache, MemoryCheckpointAdapter, MemoryObjectStore, MemorySpillAdapter, ObjectStoreIcebergCommitCatalog, PACKAGE, ParquetScanAdapter, QueryBuilder, QueryResult, ResumedQuery, advanceTaskCheckpoint, and, applyIcebergDeletes, assertBookmarkMatches, asyncBufferFromStore, bboxMayIntersect, between, broadcastJoin, buildBBoxIndex, buildMinMaxIndex, cacheApiCache, classifyPredicate, col, createBookmark, createParquetLake as createLake, createOutputManifest, createOutputManifestFromCheckpoints, createParquetLake, createParquetTableAs, createTaskManifest, deserializeAggregateOperatorState, deserializeSortOperatorState, deserializeTopKOperatorState, encodeJsonLine, eq, evaluate, fingerprint, fn, gt, gte, icebergGlueCatalog, icebergNessieCatalog, icebergRestCatalog, ilike, isIn, isLaQLError, isNotNull, isNull, jsonSafeValue, like, lit, loadIcebergTable, loadIcebergTableFromObjectStore, loadIcebergTableFromRest, loadTable, lookupJoin, lt, lte, matches, memoryCache, memoryCheckpointAdapter, memorySpillAdapter, memoryStore, ne, not, notIn, or, parquetScanner, parseHivePartitions, parseJsonQuery, partitionedParquetOutputEntries, planFiles, planRowGroups, planRowGroupsFromMetadata, pruneFilesWithIndex, readControlSignal, readIcebergParquetDeletes, readOutputManifest, readParquetMetadata, readParquetObjectBatches, readParquetObjects, rejectUnsupportedParquetSchema, rowGroupMayMatch, scanBatches, scanPlannedIcebergRows, scanRows, serializeAggregateOperatorState, serializeSortOperatorState, serializeTopKOperatorState, signPaginationToken, stableStringify, throwIfAborted, transitionTaskCheckpoint, verifyPaginationToken, withObjectStoreReadControls, writeOutputManifest, writeParquet, writePartitionedParquet, writePartitionedParquetTask } from './chunk-VRJ72DGA.js';
|
package/dist/node.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ObjectInfo, ObjectStore, ConditionalObjectStore } from './index.js';
|
|
2
|
+
export { AggregateExpr, AggregateGroupSnapshot, AggregateOp, AggregateOperatorState, AggregateOptions, AggregateResult, AggregateSnapshotValue, AggregateSpec, AggregateStateSnapshot, AggregationBuilder, ApplyIcebergDeletesOptions, BBoxIndex, BetweenExpr, Bookmark, BookmarkInit, BookmarkPosition, BookmarkQuery, BroadcastJoinOptions, CacheAdapter, CacheApiCache, CacheApiCacheOptions, CacheEntry, CallExpr, CheckpointAdapter, CheckpointStore, Clock, ColumnExpr, ColumnInput, CompareExpr, CompareOp, ConditionalPutOptions, CreateParquetTableAsOptions, CreateParquetTableAsQuery, CreateParquetTableAsResult, CsvStreamOptions, DecodedIcebergDeletes, DecodedIcebergParquetDeletes, ERROR_CODES, EngineFilePlan, EngineTable, ErrorDetails, ExplainJson, ExplainResult, Expr, IcebergAppendFile, IcebergAppendOptions, IcebergAppendOutputManifestOptions, IcebergAppendResult, IcebergCatalog, IcebergCommitCatalog, IcebergCommitInput, IcebergCommitResult, IcebergDeleteFile, IcebergDeletionVector, IcebergEnginePlan, IcebergEngineTable, IcebergEqualityDelete, IcebergField, IcebergGlueCatalogOptions, IcebergNessieCatalogOptions, IcebergParquetDeleteFile, IcebergParquetDeleteFileContent, IcebergPartitionField, IcebergPartitionSpec, IcebergPlan, IcebergPositionDelete, IcebergReadMode, IcebergRestCatalog, IcebergRestCatalogOptions, IcebergRowBatch, IcebergSortOrder, IcebergTable, IcebergTableIdentifier, IcebergUnsupportedCatalog, IdGenerator, InExpr, IndexPruneResult, IndexValue, InsertValidationRules, JoinType, JsonExpr, JsonOrderByTerm, JsonQueryV1, LaQLError, LaQLErrorCode, Lake, LakeConfig, LikeExpr, ListOptions, LiteralExpr, LoadIcebergEngineTableOptions, LoadIcebergTableFromObjectStoreOptions, LoadIcebergTableFromRestOptions, LoadIcebergTableOptions, LoadParquetEngineTableOptions, LoadTableOptions, LockAdapter, LogHook, LogicalExpr, LookupJoinFunction, LookupJoinOptions, Manifest, ManifestDeleteFile, ManifestFile, MemoryCache, MemoryCheckpointAdapter, MemoryObjectStore, MemorySpillAdapter, MemorySpillAdapterOptions, MetadataFile, MetricsHook, MinMaxColumnIndex, NotExpr, NullCheckExpr, ObjectHead, ObjectStoreIcebergCommitCatalog, ObjectStoreReadControls, OperatorSnapshotValue, OrderByTerm, OutputManifest, OutputManifestEntry, PACKAGE, ParquetEnginePlan, ParquetEngineTable, ParquetLakeConfig, ParquetMetadata, ParquetRowBatch, ParquetRowGroupPlan, ParquetScanAdapter, PartitionedParquetOutputEntryOptions, PathQueryInit, PlanIcebergFilesOptions, PlanParquetRowGroupsOptions, PlannedIcebergFile, PlannedParquetRowGroup, PredicatePlan, PredicatePlanOptions, ProjectIcebergRowOptions, PutOptions, QueryBudget, QueryBuilder, QueryPolicy, QueryPolicyContext, QueryResult, QueryRunOptions, QueryStats, QueueAdapter, ReadParquetBatchOptions, ReadParquetOptions, ResumableBatchOptions, ResumedQuery, Row, RuntimeSubstrate, Scalar, ScanAdapter, ScanBatch, ScanEngineOptions, ScanOptions, ScanPlannedIcebergRowsOptions, ScanTaskPlan, ScanTaskPlanOptions, SidecarFileIndex, SliceOptions, SliceResult, Snapshot, SortOperatorState, SortOptions, SortResult, SortRunState, SpillAdapter, SpillRef, SpillUsage, SqlBoolean, TaskCheckpoint, TaskInput, TaskManifest, TaskManifestTask, TaskState, TaskTransitionInput, TopKOperatorState, TopKOptions, TopKResult, ValueInput, WriteParquetOptions, WriteParquetRowsOptions, WritePartitionedParquetFile, WritePartitionedParquetResult, WritePartitionedParquetTaskOptions, WritePartitionedParquetTaskResult, advanceTaskCheckpoint, and, applyIcebergDeletes, assertBookmarkMatches, asyncBufferFromStore, bboxMayIntersect, between, broadcastJoin, buildBBoxIndex, buildMinMaxIndex, cacheApiCache, classifyPredicate, col, createBookmark, createLake, createOutputManifest, createOutputManifestFromCheckpoints, createLake as createParquetLake, createParquetTableAs, createTaskManifest, deserializeAggregateOperatorState, deserializeSortOperatorState, deserializeTopKOperatorState, encodeJsonLine, eq, evaluate, fingerprint, fn, gt, gte, icebergGlueCatalog, icebergNessieCatalog, icebergRestCatalog, ilike, isIn, isLaQLError, isNotNull, isNull, jsonSafeValue, like, lit, loadIcebergTable, loadIcebergTableFromObjectStore, loadIcebergTableFromRest, loadTable, lookupJoin, lt, lte, matches, memoryCache, memoryCheckpointAdapter, memorySpillAdapter, memoryStore, ne, not, notIn, or, parquetScanner, parseHivePartitions, parseJsonQuery, partitionedParquetOutputEntries, planFiles, planRowGroups, planRowGroupsFromMetadata, pruneFilesWithIndex, readControlSignal, readIcebergParquetDeletes, readOutputManifest, readParquetMetadata, readParquetObjectBatches, readParquetObjects, rejectUnsupportedParquetSchema, rowGroupMayMatch, scanBatches, scanPlannedIcebergRows, scanRows, serializeAggregateOperatorState, serializeSortOperatorState, serializeTopKOperatorState, signPaginationToken, stableStringify, throwIfAborted, transitionTaskCheckpoint, verifyPaginationToken, withObjectStoreReadControls, writeOutputManifest, writeParquet, writePartitionedParquet, writePartitionedParquetTask } from './index.js';
|
|
3
|
+
import 'hyparquet/src/types.js';
|
|
4
|
+
import 'hyparquet';
|
|
5
|
+
import 'hyparquet-writer';
|
|
6
|
+
|
|
7
|
+
interface HttpStoreOptions {
|
|
8
|
+
baseUrl: string;
|
|
9
|
+
fetch?: typeof fetch;
|
|
10
|
+
headers?: HeadersInit;
|
|
11
|
+
objects?: ObjectInfo[];
|
|
12
|
+
}
|
|
13
|
+
declare function httpStore(options: HttpStoreOptions): ObjectStore;
|
|
14
|
+
|
|
15
|
+
interface S3StoreOptions {
|
|
16
|
+
endpoint: string;
|
|
17
|
+
bucket: string;
|
|
18
|
+
region: string;
|
|
19
|
+
accessKeyId: string;
|
|
20
|
+
secretAccessKey: string;
|
|
21
|
+
sessionToken?: string;
|
|
22
|
+
fetch?: typeof fetch;
|
|
23
|
+
now?: () => Date;
|
|
24
|
+
}
|
|
25
|
+
declare function s3Store(options: S3StoreOptions): ConditionalObjectStore;
|
|
26
|
+
|
|
27
|
+
export { ConditionalObjectStore, ObjectInfo, ObjectStore, httpStore, s3Store };
|
package/dist/node.js
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import { LaQLError } from './chunk-VRJ72DGA.js';
|
|
2
|
+
export { AggregationBuilder, CacheApiCache, ERROR_CODES, IcebergRestCatalog, IcebergTable, IcebergUnsupportedCatalog, LaQLError, Lake, MemoryCache, MemoryCheckpointAdapter, MemoryObjectStore, MemorySpillAdapter, ObjectStoreIcebergCommitCatalog, PACKAGE, ParquetScanAdapter, QueryBuilder, QueryResult, ResumedQuery, advanceTaskCheckpoint, and, applyIcebergDeletes, assertBookmarkMatches, asyncBufferFromStore, bboxMayIntersect, between, broadcastJoin, buildBBoxIndex, buildMinMaxIndex, cacheApiCache, classifyPredicate, col, createBookmark, createParquetLake as createLake, createOutputManifest, createOutputManifestFromCheckpoints, createParquetLake, createParquetTableAs, createTaskManifest, deserializeAggregateOperatorState, deserializeSortOperatorState, deserializeTopKOperatorState, encodeJsonLine, eq, evaluate, fingerprint, fn, gt, gte, icebergGlueCatalog, icebergNessieCatalog, icebergRestCatalog, ilike, isIn, isLaQLError, isNotNull, isNull, jsonSafeValue, like, lit, loadIcebergTable, loadIcebergTableFromObjectStore, loadIcebergTableFromRest, loadTable, lookupJoin, lt, lte, matches, memoryCache, memoryCheckpointAdapter, memorySpillAdapter, memoryStore, ne, not, notIn, or, parquetScanner, parseHivePartitions, parseJsonQuery, partitionedParquetOutputEntries, planFiles, planRowGroups, planRowGroupsFromMetadata, pruneFilesWithIndex, readControlSignal, readIcebergParquetDeletes, readOutputManifest, readParquetMetadata, readParquetObjectBatches, readParquetObjects, rejectUnsupportedParquetSchema, rowGroupMayMatch, scanBatches, scanPlannedIcebergRows, scanRows, serializeAggregateOperatorState, serializeSortOperatorState, serializeTopKOperatorState, signPaginationToken, stableStringify, throwIfAborted, transitionTaskCheckpoint, verifyPaginationToken, withObjectStoreReadControls, writeOutputManifest, writeParquet, writePartitionedParquet, writePartitionedParquetTask } from './chunk-VRJ72DGA.js';
|
|
3
|
+
import { AwsClient } from 'aws4fetch';
|
|
4
|
+
import { XMLParser } from 'fast-xml-parser';
|
|
5
|
+
|
|
6
|
+
// ../http/dist/index.js
|
|
7
|
+
function httpStore(options) {
|
|
8
|
+
return new HttpObjectStore(options);
|
|
9
|
+
}
|
|
10
|
+
var HttpObjectStore = class {
|
|
11
|
+
baseUrl;
|
|
12
|
+
fetchImpl;
|
|
13
|
+
headers;
|
|
14
|
+
objects;
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.baseUrl = options.baseUrl.endsWith("/") ? options.baseUrl : `${options.baseUrl}/`;
|
|
17
|
+
this.fetchImpl = options.fetch ?? fetch.bind(globalThis);
|
|
18
|
+
this.headers = options.headers;
|
|
19
|
+
this.objects = options.objects;
|
|
20
|
+
}
|
|
21
|
+
async get(path) {
|
|
22
|
+
const response = await this.fetchPath(path, { method: "GET" });
|
|
23
|
+
if (response.status === 404)
|
|
24
|
+
return null;
|
|
25
|
+
assertOk(response, path);
|
|
26
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
27
|
+
}
|
|
28
|
+
async getRange(path, range) {
|
|
29
|
+
if (range.offset < 0 || range.length < 0) {
|
|
30
|
+
throw new LaQLError("LAQL_OBJECT_NOT_FOUND", `Invalid range for ${path}`, { path, range });
|
|
31
|
+
}
|
|
32
|
+
const response = await this.fetchPath(path, {
|
|
33
|
+
method: "GET",
|
|
34
|
+
headers: { Range: `bytes=${range.offset}-${range.offset + range.length - 1}` }
|
|
35
|
+
});
|
|
36
|
+
assertOk(response, path);
|
|
37
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
38
|
+
if (response.status !== 206 && bytes.length > range.length) {
|
|
39
|
+
return bytes.subarray(range.offset, range.offset + range.length);
|
|
40
|
+
}
|
|
41
|
+
return bytes;
|
|
42
|
+
}
|
|
43
|
+
async put(path, body, options) {
|
|
44
|
+
const headers = new Headers(this.headers);
|
|
45
|
+
if (options?.contentType)
|
|
46
|
+
headers.set("content-type", options.contentType);
|
|
47
|
+
const response = await this.fetchPath(path, { method: "PUT", headers, body: bodyInit(body) });
|
|
48
|
+
assertOk(response, path);
|
|
49
|
+
}
|
|
50
|
+
async delete(path) {
|
|
51
|
+
const response = await this.fetchPath(path, { method: "DELETE" });
|
|
52
|
+
assertOk(response, path);
|
|
53
|
+
}
|
|
54
|
+
async *list(prefix, options) {
|
|
55
|
+
if (!this.objects) {
|
|
56
|
+
throw new LaQLError("LAQL_UNSUPPORTED_PUSHDOWN", "HTTP store list requires an object index", {
|
|
57
|
+
prefix
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
let emitted = 0;
|
|
61
|
+
for (const object of this.objects.filter((candidate) => candidate.path.startsWith(prefix)).sort((a, b) => a.path.localeCompare(b.path))) {
|
|
62
|
+
if (options?.limit !== void 0 && emitted >= options.limit)
|
|
63
|
+
return;
|
|
64
|
+
yield object;
|
|
65
|
+
emitted += 1;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async head(path) {
|
|
69
|
+
const response = await this.fetchPath(path, {
|
|
70
|
+
method: "GET",
|
|
71
|
+
headers: { Range: "bytes=0-0" }
|
|
72
|
+
});
|
|
73
|
+
if (response.status === 404)
|
|
74
|
+
return null;
|
|
75
|
+
assertOk(response, path);
|
|
76
|
+
await response.arrayBuffer().catch(() => void 0);
|
|
77
|
+
const total = parseContentRangeTotal(response.headers.get("content-range"));
|
|
78
|
+
const contentLength = response.headers.get("content-length");
|
|
79
|
+
const size = total ?? (contentLength ? Number(contentLength) : Number.NaN);
|
|
80
|
+
if (!Number.isFinite(size)) {
|
|
81
|
+
throw new LaQLError("LAQL_OBJECT_NOT_FOUND", `Missing size for ${path}`, { path });
|
|
82
|
+
}
|
|
83
|
+
const head = { size };
|
|
84
|
+
const etag = response.headers.get("etag");
|
|
85
|
+
const lastModified = response.headers.get("last-modified");
|
|
86
|
+
const contentType = response.headers.get("content-type");
|
|
87
|
+
if (etag !== null)
|
|
88
|
+
head.etag = etag;
|
|
89
|
+
if (lastModified !== null)
|
|
90
|
+
head.lastModified = new Date(lastModified);
|
|
91
|
+
if (contentType !== null)
|
|
92
|
+
head.contentType = contentType;
|
|
93
|
+
return head;
|
|
94
|
+
}
|
|
95
|
+
fetchPath(path, init) {
|
|
96
|
+
const headers = new Headers(this.headers);
|
|
97
|
+
if (init.headers) {
|
|
98
|
+
for (const [key, value] of new Headers(init.headers))
|
|
99
|
+
headers.set(key, value);
|
|
100
|
+
}
|
|
101
|
+
return this.fetchImpl(this.urlForPath(path), { ...init, headers });
|
|
102
|
+
}
|
|
103
|
+
urlForPath(path) {
|
|
104
|
+
const base = new URL(this.baseUrl);
|
|
105
|
+
const url = new URL(encodeObjectPath(path), base);
|
|
106
|
+
if (url.origin !== base.origin || !url.pathname.startsWith(base.pathname)) {
|
|
107
|
+
throw new LaQLError("LAQL_VALIDATION_ERROR", `Object path escapes HTTP base URL: ${path}`, {
|
|
108
|
+
path,
|
|
109
|
+
baseUrl: this.baseUrl
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return url;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
function encodeObjectPath(path) {
|
|
116
|
+
if (/^(?:[a-z][a-z0-9+.-]*:)?\/\//iu.test(path) || path.startsWith("/")) {
|
|
117
|
+
throw new LaQLError("LAQL_VALIDATION_ERROR", `Object path must be relative: ${path}`, {
|
|
118
|
+
path
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (path === "")
|
|
122
|
+
return "";
|
|
123
|
+
return path.split("/").map((segment) => {
|
|
124
|
+
let decoded;
|
|
125
|
+
try {
|
|
126
|
+
decoded = decodeURIComponent(segment);
|
|
127
|
+
} catch {
|
|
128
|
+
throw new LaQLError("LAQL_VALIDATION_ERROR", `Object path has invalid encoding: ${path}`, {
|
|
129
|
+
path
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
if (decoded === "." || decoded === "..") {
|
|
133
|
+
throw new LaQLError("LAQL_VALIDATION_ERROR", `Object path contains traversal: ${path}`, {
|
|
134
|
+
path
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return encodeURIComponent(segment);
|
|
138
|
+
}).join("/");
|
|
139
|
+
}
|
|
140
|
+
function parseContentRangeTotal(header) {
|
|
141
|
+
if (!header)
|
|
142
|
+
return void 0;
|
|
143
|
+
const match = /\/(\d+)\s*$/.exec(header);
|
|
144
|
+
if (!match)
|
|
145
|
+
return void 0;
|
|
146
|
+
const total = Number(match[1]);
|
|
147
|
+
return Number.isFinite(total) ? total : void 0;
|
|
148
|
+
}
|
|
149
|
+
function assertOk(response, path) {
|
|
150
|
+
if (response.ok || response.status === 206)
|
|
151
|
+
return;
|
|
152
|
+
if (response.status === 404) {
|
|
153
|
+
throw new LaQLError("LAQL_OBJECT_NOT_FOUND", `No object at ${path}`, { path });
|
|
154
|
+
}
|
|
155
|
+
throw new LaQLError("LAQL_OBJECT_NOT_FOUND", `HTTP object request failed for ${path}`, {
|
|
156
|
+
path,
|
|
157
|
+
status: response.status
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function bodyInit(body) {
|
|
161
|
+
if (body instanceof Uint8Array) {
|
|
162
|
+
const copy = new Uint8Array(body.byteLength);
|
|
163
|
+
copy.set(body);
|
|
164
|
+
return copy.buffer;
|
|
165
|
+
}
|
|
166
|
+
return body;
|
|
167
|
+
}
|
|
168
|
+
function s3Store(options) {
|
|
169
|
+
return new S3ObjectStore(options);
|
|
170
|
+
}
|
|
171
|
+
var S3ObjectStore = class {
|
|
172
|
+
options;
|
|
173
|
+
fetchImpl;
|
|
174
|
+
now;
|
|
175
|
+
aws;
|
|
176
|
+
constructor(options) {
|
|
177
|
+
this.options = options;
|
|
178
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
179
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
180
|
+
const awsOptions = {
|
|
181
|
+
accessKeyId: options.accessKeyId,
|
|
182
|
+
secretAccessKey: options.secretAccessKey,
|
|
183
|
+
region: options.region,
|
|
184
|
+
service: "s3",
|
|
185
|
+
retries: 0
|
|
186
|
+
};
|
|
187
|
+
if (options.sessionToken !== void 0)
|
|
188
|
+
awsOptions.sessionToken = options.sessionToken;
|
|
189
|
+
this.aws = new AwsClient(awsOptions);
|
|
190
|
+
}
|
|
191
|
+
async get(path) {
|
|
192
|
+
const response = await this.request("GET", path);
|
|
193
|
+
if (response.status === 404)
|
|
194
|
+
return null;
|
|
195
|
+
assertOk2(response, path);
|
|
196
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
197
|
+
}
|
|
198
|
+
async getRange(path, range) {
|
|
199
|
+
const response = await this.request("GET", path, {
|
|
200
|
+
Range: `bytes=${range.offset}-${range.offset + range.length - 1}`
|
|
201
|
+
});
|
|
202
|
+
assertOk2(response, path);
|
|
203
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
204
|
+
}
|
|
205
|
+
async put(path, body, options) {
|
|
206
|
+
const headers = {};
|
|
207
|
+
if (options?.contentType)
|
|
208
|
+
headers["content-type"] = options.contentType;
|
|
209
|
+
const response = await this.request("PUT", path, headers, body);
|
|
210
|
+
assertOk2(response, path);
|
|
211
|
+
}
|
|
212
|
+
async conditionalPut(path, body, options) {
|
|
213
|
+
const headers = {};
|
|
214
|
+
if (options.contentType)
|
|
215
|
+
headers["content-type"] = options.contentType;
|
|
216
|
+
if (options.expectedEtag === null)
|
|
217
|
+
headers["if-none-match"] = "*";
|
|
218
|
+
else
|
|
219
|
+
headers["if-match"] = options.expectedEtag;
|
|
220
|
+
const response = await this.request("PUT", path, headers, body);
|
|
221
|
+
if (response.status === 409 || response.status === 412)
|
|
222
|
+
return false;
|
|
223
|
+
assertOk2(response, path);
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
async delete(path) {
|
|
227
|
+
const response = await this.request("DELETE", path);
|
|
228
|
+
assertOk2(response, path);
|
|
229
|
+
}
|
|
230
|
+
async *list(prefix, options) {
|
|
231
|
+
let continuationToken;
|
|
232
|
+
let emitted = 0;
|
|
233
|
+
do {
|
|
234
|
+
const query = new URLSearchParams({ "list-type": "2", prefix });
|
|
235
|
+
if (options?.limit !== void 0)
|
|
236
|
+
query.set("max-keys", String(options.limit - emitted));
|
|
237
|
+
if (options?.delimiter !== void 0)
|
|
238
|
+
query.set("delimiter", options.delimiter);
|
|
239
|
+
if (continuationToken !== void 0)
|
|
240
|
+
query.set("continuation-token", continuationToken);
|
|
241
|
+
const response = await this.request("GET", "", {}, void 0, query);
|
|
242
|
+
assertOk2(response, prefix);
|
|
243
|
+
const result = parseListObjectsV2(await response.text());
|
|
244
|
+
for (const object of result.objects) {
|
|
245
|
+
if (options?.limit !== void 0 && emitted >= options.limit)
|
|
246
|
+
return;
|
|
247
|
+
yield object;
|
|
248
|
+
emitted += 1;
|
|
249
|
+
}
|
|
250
|
+
continuationToken = result.nextContinuationToken;
|
|
251
|
+
} while (continuationToken !== void 0 && (options?.limit === void 0 || emitted < options.limit));
|
|
252
|
+
}
|
|
253
|
+
async head(path) {
|
|
254
|
+
const response = await this.request("HEAD", path);
|
|
255
|
+
if (response.status === 404)
|
|
256
|
+
return null;
|
|
257
|
+
assertOk2(response, path);
|
|
258
|
+
const contentLength = response.headers.get("content-length");
|
|
259
|
+
if (contentLength === null) {
|
|
260
|
+
throw new LaQLError("LAQL_OBJECT_NOT_FOUND", `Missing S3 content-length for ${path}`, {
|
|
261
|
+
path
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
const head = { size: Number(contentLength) };
|
|
265
|
+
const etag = response.headers.get("etag");
|
|
266
|
+
const contentType = response.headers.get("content-type");
|
|
267
|
+
const lastModified = response.headers.get("last-modified");
|
|
268
|
+
if (etag !== null)
|
|
269
|
+
head.etag = etag;
|
|
270
|
+
if (contentType !== null)
|
|
271
|
+
head.contentType = contentType;
|
|
272
|
+
if (lastModified !== null)
|
|
273
|
+
head.lastModified = new Date(lastModified);
|
|
274
|
+
return head;
|
|
275
|
+
}
|
|
276
|
+
async request(method, path, headers = {}, body, query = new URLSearchParams()) {
|
|
277
|
+
const url = new URL(`${encodeURIComponent(this.options.bucket)}/${encodeObjectPath2(path)}`, ensureSlash(this.options.endpoint));
|
|
278
|
+
url.search = query.toString();
|
|
279
|
+
const signRequest = {
|
|
280
|
+
method,
|
|
281
|
+
url,
|
|
282
|
+
headers,
|
|
283
|
+
region: this.options.region,
|
|
284
|
+
accessKeyId: this.options.accessKeyId,
|
|
285
|
+
secretAccessKey: this.options.secretAccessKey,
|
|
286
|
+
now: this.now()
|
|
287
|
+
};
|
|
288
|
+
if (this.options.sessionToken !== void 0)
|
|
289
|
+
signRequest.sessionToken = this.options.sessionToken;
|
|
290
|
+
const requestBody = body === void 0 ? void 0 : bodyInit2(body);
|
|
291
|
+
const signed = await signS3Request(signRequest, this.aws, requestBody);
|
|
292
|
+
const init = { method, headers: signed };
|
|
293
|
+
if (requestBody !== void 0)
|
|
294
|
+
init.body = requestBody;
|
|
295
|
+
return this.fetchImpl(url, init);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
async function signS3Request(request, client = s3SignerClient(request), body) {
|
|
299
|
+
const headers = new Headers(request.headers);
|
|
300
|
+
if (!headers.has("x-amz-content-sha256")) {
|
|
301
|
+
headers.set("x-amz-content-sha256", "UNSIGNED-PAYLOAD");
|
|
302
|
+
}
|
|
303
|
+
const init = {
|
|
304
|
+
method: request.method,
|
|
305
|
+
headers,
|
|
306
|
+
aws: {
|
|
307
|
+
datetime: timestamp(request.now)
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
if (body !== void 0)
|
|
311
|
+
init.body = body;
|
|
312
|
+
const signed = await client.sign(request.url, init);
|
|
313
|
+
return signed.headers;
|
|
314
|
+
}
|
|
315
|
+
function s3SignerClient(request) {
|
|
316
|
+
const options = {
|
|
317
|
+
accessKeyId: request.accessKeyId,
|
|
318
|
+
secretAccessKey: request.secretAccessKey,
|
|
319
|
+
region: request.region,
|
|
320
|
+
service: "s3",
|
|
321
|
+
retries: 0
|
|
322
|
+
};
|
|
323
|
+
if (request.sessionToken !== void 0)
|
|
324
|
+
options.sessionToken = request.sessionToken;
|
|
325
|
+
return new AwsClient(options);
|
|
326
|
+
}
|
|
327
|
+
var listObjectsParser = new XMLParser({
|
|
328
|
+
ignoreAttributes: true,
|
|
329
|
+
parseTagValue: false,
|
|
330
|
+
trimValues: true
|
|
331
|
+
});
|
|
332
|
+
function parseListObjectsV2(xml) {
|
|
333
|
+
const parsed = listObjectsParser.parse(xml);
|
|
334
|
+
const result = parsed.ListBucketResult ?? {};
|
|
335
|
+
const objects = [];
|
|
336
|
+
for (const content of arrayOf(result.Contents)) {
|
|
337
|
+
const key = stringValue(content.Key);
|
|
338
|
+
const size = Number(stringValue(content.Size));
|
|
339
|
+
if (!key || !Number.isFinite(size))
|
|
340
|
+
continue;
|
|
341
|
+
const info = { path: key, size };
|
|
342
|
+
const etag = stringValue(content.ETag);
|
|
343
|
+
const lastModified = stringValue(content.LastModified);
|
|
344
|
+
if (etag)
|
|
345
|
+
info.etag = etag;
|
|
346
|
+
if (lastModified)
|
|
347
|
+
info.lastModified = new Date(lastModified);
|
|
348
|
+
objects.push(info);
|
|
349
|
+
}
|
|
350
|
+
const nextContinuationToken = isTrue(result.IsTruncated) ? stringValue(result.NextContinuationToken) : "";
|
|
351
|
+
return {
|
|
352
|
+
objects,
|
|
353
|
+
...nextContinuationToken ? { nextContinuationToken } : {}
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
function arrayOf(value) {
|
|
357
|
+
if (value === void 0 || value === null)
|
|
358
|
+
return [];
|
|
359
|
+
return Array.isArray(value) ? value : [value];
|
|
360
|
+
}
|
|
361
|
+
function stringValue(value) {
|
|
362
|
+
if (value === void 0 || value === null)
|
|
363
|
+
return "";
|
|
364
|
+
return String(value);
|
|
365
|
+
}
|
|
366
|
+
function isTrue(value) {
|
|
367
|
+
return value === true || String(value).toLowerCase() === "true";
|
|
368
|
+
}
|
|
369
|
+
function ensureSlash(value) {
|
|
370
|
+
return value.endsWith("/") ? value : `${value}/`;
|
|
371
|
+
}
|
|
372
|
+
function encodeObjectPath2(path) {
|
|
373
|
+
if (/^(?:[a-z][a-z0-9+.-]*:)?\/\//iu.test(path) || path.startsWith("/")) {
|
|
374
|
+
throw new LaQLError("LAQL_VALIDATION_ERROR", `Object path must be relative: ${path}`, {
|
|
375
|
+
path
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
if (path === "")
|
|
379
|
+
return "";
|
|
380
|
+
return path.split("/").map((segment) => {
|
|
381
|
+
let decoded;
|
|
382
|
+
try {
|
|
383
|
+
decoded = decodeURIComponent(segment);
|
|
384
|
+
} catch {
|
|
385
|
+
throw new LaQLError("LAQL_VALIDATION_ERROR", `Object path has invalid encoding: ${path}`, {
|
|
386
|
+
path
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
if (decoded === "." || decoded === "..") {
|
|
390
|
+
throw new LaQLError("LAQL_VALIDATION_ERROR", `Object path contains traversal: ${path}`, {
|
|
391
|
+
path
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
return encodeURIComponent(segment);
|
|
395
|
+
}).join("/");
|
|
396
|
+
}
|
|
397
|
+
function assertOk2(response, path) {
|
|
398
|
+
if (response.ok || response.status === 206)
|
|
399
|
+
return;
|
|
400
|
+
if (response.status === 404) {
|
|
401
|
+
throw new LaQLError("LAQL_OBJECT_NOT_FOUND", `No object at ${path}`, { path });
|
|
402
|
+
}
|
|
403
|
+
throw new LaQLError("LAQL_OBJECT_NOT_FOUND", `S3 request failed for ${path}`, {
|
|
404
|
+
path,
|
|
405
|
+
status: response.status
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
function timestamp(date) {
|
|
409
|
+
return date.toISOString().replace(/[:-]|\.\d{3}/gu, "");
|
|
410
|
+
}
|
|
411
|
+
function bodyInit2(body) {
|
|
412
|
+
if (body instanceof Uint8Array)
|
|
413
|
+
return arrayBuffer(body);
|
|
414
|
+
return body;
|
|
415
|
+
}
|
|
416
|
+
function arrayBuffer(bytes) {
|
|
417
|
+
const copy = new Uint8Array(bytes.byteLength);
|
|
418
|
+
copy.set(bytes);
|
|
419
|
+
return copy.buffer;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export { httpStore, s3Store };
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "lakeql",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "LaQL: lightweight TypeScript query engine for Iceberg + Parquet on object storage",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"lakehouse",
|
|
7
|
+
"parquet",
|
|
8
|
+
"iceberg",
|
|
9
|
+
"query-engine",
|
|
10
|
+
"object-storage"
|
|
11
|
+
],
|
|
12
|
+
"author": "Erik Aronesty",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./cloudflare": {
|
|
23
|
+
"types": "./dist/cloudflare.d.ts",
|
|
24
|
+
"default": "./dist/cloudflare.js"
|
|
25
|
+
},
|
|
26
|
+
"./node": {
|
|
27
|
+
"types": "./dist/node.d.ts",
|
|
28
|
+
"default": "./dist/node.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsup",
|
|
38
|
+
"prepack": "pnpm -w build && pnpm --filter lakeql build"
|
|
39
|
+
},
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "https://github.com/earonesty/laql.git",
|
|
43
|
+
"directory": "packages/laql"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://laql.dev",
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=22"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@turf/boolean-contains": "^7.2.0",
|
|
54
|
+
"@turf/boolean-intersects": "^7.2.0",
|
|
55
|
+
"avsc": "^5.7.9",
|
|
56
|
+
"aws4fetch": "^1.0.20",
|
|
57
|
+
"fast-xml-parser": "^5.9.0",
|
|
58
|
+
"h3-js": "^4.4.0",
|
|
59
|
+
"hyparquet": "^1.26.0",
|
|
60
|
+
"hyparquet-writer": "^0.15.6"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@laql/core": "workspace:*",
|
|
64
|
+
"@laql/fixtures": "workspace:*",
|
|
65
|
+
"@laql/http": "workspace:*",
|
|
66
|
+
"@laql/iceberg": "workspace:*",
|
|
67
|
+
"@laql/parquet": "workspace:*",
|
|
68
|
+
"@laql/r2": "workspace:*",
|
|
69
|
+
"@laql/s3": "workspace:*",
|
|
70
|
+
"tsup": "^8.5.0"
|
|
71
|
+
}
|
|
72
|
+
}
|