@chartcoach/catalog 0.1.7 → 0.2.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/README.md +324 -38
- package/dist/duckdb-wasm.d.ts +8 -0
- package/dist/duckdb-wasm.js +22 -0
- package/dist/duckdb.d.ts +8 -0
- package/dist/duckdb.js +9 -0
- package/dist/index-cache-Cx1gIK6N.js +280 -0
- package/dist/index.d.ts +40 -8
- package/dist/index.js +22 -7
- package/dist/json-CBWjz9b4.js +29 -0
- package/dist/model-De0MF-zg.d.ts +255 -0
- package/dist/node.d.ts +15 -0
- package/dist/node.js +218 -0
- package/dist/open-Nnzkr_bE.d.ts +15 -0
- package/dist/open-Uu3-r8kp.js +1244 -0
- package/dist/registration-CgQGMUIH.d.ts +6 -0
- package/dist/registration-DXhS7vgr.js +41 -0
- package/dist/tables-DcgKnXwF.js +389 -0
- package/package.json +47 -18
- package/src/catalog/artifacts.ts +309 -100
- package/src/catalog/description.ts +198 -0
- package/src/catalog/errors.ts +26 -1
- package/src/catalog/identity.ts +69 -0
- package/src/catalog/json.ts +32 -0
- package/src/catalog/labels.ts +8 -6
- package/src/catalog/manifest.ts +81 -20
- package/src/catalog/markdown.ts +2 -2
- package/src/catalog/model.ts +222 -37
- package/src/catalog/open.ts +428 -0
- package/src/catalog/parquet.ts +164 -0
- package/src/catalog/profile-layout.ts +86 -0
- package/src/catalog/profile.ts +345 -0
- package/src/catalog/query.ts +125 -0
- package/src/catalog/read.ts +101 -0
- package/src/catalog/references.ts +336 -0
- package/src/catalog/registration.ts +74 -0
- package/src/catalog/tables.ts +250 -0
- package/src/catalog/wire.ts +38 -65
- package/src/duckdb-wasm.ts +33 -0
- package/src/duckdb.ts +18 -0
- package/src/index.ts +51 -20
- package/src/node/index-cache.ts +432 -0
- package/src/node.ts +347 -0
- package/dist/catalog/artifacts.d.ts +0 -28
- package/dist/catalog/artifacts.d.ts.map +0 -1
- package/dist/catalog/artifacts.js +0 -89
- package/dist/catalog/chartcoach-defaults.d.ts +0 -17
- package/dist/catalog/chartcoach-defaults.d.ts.map +0 -1
- package/dist/catalog/chartcoach-defaults.js +0 -8
- package/dist/catalog/errors.d.ts +0 -4
- package/dist/catalog/errors.d.ts.map +0 -1
- package/dist/catalog/errors.js +0 -6
- package/dist/catalog/labels.d.ts +0 -9
- package/dist/catalog/labels.d.ts.map +0 -1
- package/dist/catalog/labels.js +0 -23
- package/dist/catalog/load-parquet-core.d.ts +0 -15
- package/dist/catalog/load-parquet-core.d.ts.map +0 -1
- package/dist/catalog/load-parquet-core.js +0 -45
- package/dist/catalog/manifest.d.ts +0 -15
- package/dist/catalog/manifest.d.ts.map +0 -1
- package/dist/catalog/manifest.js +0 -143
- package/dist/catalog/markdown.d.ts +0 -3
- package/dist/catalog/markdown.d.ts.map +0 -1
- package/dist/catalog/markdown.js +0 -13
- package/dist/catalog/model.d.ts +0 -33
- package/dist/catalog/model.d.ts.map +0 -1
- package/dist/catalog/model.js +0 -67
- package/dist/catalog/wire.d.ts +0 -18
- package/dist/catalog/wire.d.ts.map +0 -1
- package/dist/catalog/wire.js +0 -78
- package/dist/index.d.ts.map +0 -1
- package/src/catalog/chartcoach-defaults.ts +0 -17
- package/src/catalog/load-parquet-core.ts +0 -78
package/dist/node.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { a as CatalogError, i as parseJson } from "./json-CBWjz9b4.js";
|
|
2
|
+
import { c as releaseArtifact, i as loadCatalogData, o as assertReleaseDigest, s as parseCatalogRelease, t as openCatalog$1 } from "./open-Uu3-r8kp.js";
|
|
3
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
+
import { createReadStream } from "node:fs";
|
|
5
|
+
import { mkdir, open, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { dirname, join, resolve } from "node:path";
|
|
8
|
+
import { Readable } from "node:stream";
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
10
|
+
//#region src/node.ts
|
|
11
|
+
const contexts = /* @__PURE__ */ new WeakMap();
|
|
12
|
+
/** Return a verified profile's extracted database directory for native LanceDB clients. */
|
|
13
|
+
async function indexPath(catalog, profile, options = {}) {
|
|
14
|
+
options.signal?.throwIfAborted();
|
|
15
|
+
if (!catalog.release) throw new CatalogError("Index access requires a catalog release.", { code: "unavailable_capability" });
|
|
16
|
+
await catalog.describe({
|
|
17
|
+
profile,
|
|
18
|
+
signal: options.signal
|
|
19
|
+
});
|
|
20
|
+
const path = `profiles/${profile}/index.tar.gz`;
|
|
21
|
+
const artifact = releaseArtifact(catalog.release, path);
|
|
22
|
+
const { extractIndex } = await import("./index-cache-Cx1gIK6N.js");
|
|
23
|
+
return extractIndex({
|
|
24
|
+
archive: () => artifactPath(catalog, path, options),
|
|
25
|
+
digest: artifact.sha256,
|
|
26
|
+
cacheDirectory: contexts.get(catalog)?.cacheDirectory ?? defaultCacheDirectory(),
|
|
27
|
+
directory: options.directory,
|
|
28
|
+
signal: options.signal
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
async function openCatalog(location, options = {}) {
|
|
32
|
+
const cacheDirectory = options.cacheDirectory ?? defaultCacheDirectory();
|
|
33
|
+
const fetch = options.fetch ?? globalThis.fetch;
|
|
34
|
+
const uri = location === void 0 ? void 0 : locationUrl(location);
|
|
35
|
+
if (uri?.protocol === "file:") {
|
|
36
|
+
const local = fileURLToPath(uri);
|
|
37
|
+
if ((await stat(local)).isDirectory()) {
|
|
38
|
+
const selection = await existingFile(join(local, "catalog.json"));
|
|
39
|
+
const release = await existingFile(join(local, "release.json"));
|
|
40
|
+
if (selection && release) throw new CatalogError("Catalog directory contains both descriptors.");
|
|
41
|
+
if (selection || release) return openCatalog(pathToFileURL(join(local, selection ? "catalog.json" : "release.json")), options);
|
|
42
|
+
const [entries, manifest] = await Promise.all([readBytes(join(local, "entries.parquet"), 64 * 1024 ** 2, options.signal), readBytes(join(local, "MANIFEST.md"), 64 * 1024 ** 2, options.signal)]);
|
|
43
|
+
return loadCatalogData({
|
|
44
|
+
entries,
|
|
45
|
+
manifestText: new TextDecoder("utf-8", { fatal: true }).decode(manifest)
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const fetchResource = async (input, init) => {
|
|
50
|
+
const url = new URL(input);
|
|
51
|
+
init?.signal?.throwIfAborted();
|
|
52
|
+
if (url.protocol === "file:") {
|
|
53
|
+
const body = Readable.toWeb(createReadStream(fileURLToPath(url)));
|
|
54
|
+
return new Response(body);
|
|
55
|
+
}
|
|
56
|
+
const parent = url.pathname.split("/").at(-2) ?? "";
|
|
57
|
+
if (url.pathname.endsWith("/release.json") && /^[a-f0-9]{64}$/.test(parent)) {
|
|
58
|
+
const cached = await readCached(join(cacheDirectory, "descriptors", `${parent}.json`), 1024 ** 2);
|
|
59
|
+
if (cached) try {
|
|
60
|
+
const release = parseCatalogRelease(parseJson(new TextDecoder().decode(cached)));
|
|
61
|
+
await assertReleaseDigest(release, parent);
|
|
62
|
+
return new Response(Uint8Array.from(cached));
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (!(error instanceof CatalogError) && !(error instanceof SyntaxError)) throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return fetch(input, init);
|
|
68
|
+
};
|
|
69
|
+
const catalog = await openCatalog$1(uri, {
|
|
70
|
+
signal: options.signal,
|
|
71
|
+
cache: options.cache ?? {
|
|
72
|
+
get: async (digest, bytes) => readCached(join(cacheDirectory, "artifacts", digest), bytes),
|
|
73
|
+
put: async (digest, bytes) => atomicWrite(join(cacheDirectory, "artifacts", digest), bytes)
|
|
74
|
+
},
|
|
75
|
+
fetch: fetchResource
|
|
76
|
+
});
|
|
77
|
+
if (catalog.release) await atomicWrite(join(cacheDirectory, "descriptors", `${catalog.release.digest}.json`), new TextEncoder().encode(JSON.stringify(catalog.release)));
|
|
78
|
+
contexts.set(catalog, {
|
|
79
|
+
cacheDirectory,
|
|
80
|
+
fetch: fetchResource
|
|
81
|
+
});
|
|
82
|
+
return catalog;
|
|
83
|
+
}
|
|
84
|
+
async function artifactPath(catalog, path, options = {}) {
|
|
85
|
+
if (!catalog.release) throw new CatalogError("Artifact access requires a catalog release.", { code: "unavailable_capability" });
|
|
86
|
+
const artifact = releaseArtifact(catalog.release, path);
|
|
87
|
+
if (artifact.bytes > 1024 ** 3) throw new CatalogError("Catalog artifact exceeds 1 GiB.");
|
|
88
|
+
const context = contexts.get(catalog);
|
|
89
|
+
const target = join(context?.cacheDirectory ?? defaultCacheDirectory(), "artifacts", artifact.sha256);
|
|
90
|
+
const timeout = AbortSignal.timeout(9e5);
|
|
91
|
+
const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
|
|
92
|
+
signal.throwIfAborted();
|
|
93
|
+
if (await verifiedFile(target, artifact.sha256, artifact.bytes, signal)) return target;
|
|
94
|
+
if (!context || !catalog.releaseUrl) {
|
|
95
|
+
await atomicWrite(target, await catalog.artifact(path, { signal }));
|
|
96
|
+
signal.throwIfAborted();
|
|
97
|
+
return target;
|
|
98
|
+
}
|
|
99
|
+
const response = await context.fetch(new URL(path, catalog.releaseUrl), { signal });
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
response.body?.cancel().catch(() => {});
|
|
102
|
+
throw new CatalogError("Artifact could not be loaded.");
|
|
103
|
+
}
|
|
104
|
+
if (!response.body) {
|
|
105
|
+
if (artifact.bytes !== 0 || artifact.sha256 !== createHash("sha256").digest("hex")) throw new CatalogError("Artifact integrity check failed.", { code: "integrity" });
|
|
106
|
+
signal.throwIfAborted();
|
|
107
|
+
await atomicWrite(target, /* @__PURE__ */ new Uint8Array());
|
|
108
|
+
return target;
|
|
109
|
+
}
|
|
110
|
+
const temporary = `${target}.${randomUUID()}.tmp`;
|
|
111
|
+
const reader = response.body.getReader();
|
|
112
|
+
const abort = () => {
|
|
113
|
+
reader.cancel(signal.reason).catch(() => {});
|
|
114
|
+
};
|
|
115
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
116
|
+
if (signal.aborted) abort();
|
|
117
|
+
try {
|
|
118
|
+
await mkdir(dirname(target), { recursive: true });
|
|
119
|
+
const file = await open(temporary, "wx");
|
|
120
|
+
const hash = createHash("sha256");
|
|
121
|
+
let size = 0;
|
|
122
|
+
try {
|
|
123
|
+
while (true) {
|
|
124
|
+
const chunk = await reader.read();
|
|
125
|
+
signal.throwIfAborted();
|
|
126
|
+
if (chunk.done) break;
|
|
127
|
+
size += chunk.value.byteLength;
|
|
128
|
+
if (size > artifact.bytes) throw new CatalogError("Artifact byte count mismatch.", { code: "integrity" });
|
|
129
|
+
hash.update(chunk.value);
|
|
130
|
+
await file.writeFile(chunk.value);
|
|
131
|
+
}
|
|
132
|
+
} finally {
|
|
133
|
+
await file.close();
|
|
134
|
+
}
|
|
135
|
+
if (size !== artifact.bytes || hash.digest("hex") !== artifact.sha256) throw new CatalogError("Artifact integrity check failed.", { code: "integrity" });
|
|
136
|
+
signal.throwIfAborted();
|
|
137
|
+
await rename(temporary, target);
|
|
138
|
+
return target;
|
|
139
|
+
} finally {
|
|
140
|
+
signal.removeEventListener("abort", abort);
|
|
141
|
+
reader.cancel().catch(() => {});
|
|
142
|
+
reader.releaseLock();
|
|
143
|
+
await removeTemporary(temporary);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async function verifiedFile(path, digest, bytes, signal) {
|
|
147
|
+
try {
|
|
148
|
+
if ((await stat(path)).size !== bytes) return false;
|
|
149
|
+
const hash = createHash("sha256");
|
|
150
|
+
let size = 0;
|
|
151
|
+
for await (const chunk of createReadStream(path, { signal })) {
|
|
152
|
+
size += chunk.length;
|
|
153
|
+
if (size > bytes) return false;
|
|
154
|
+
hash.update(chunk);
|
|
155
|
+
}
|
|
156
|
+
signal.throwIfAborted();
|
|
157
|
+
return size === bytes && hash.digest("hex") === digest;
|
|
158
|
+
} catch (error) {
|
|
159
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function locationUrl(location) {
|
|
164
|
+
if (location instanceof URL) return location;
|
|
165
|
+
return /^[a-z][a-z0-9+.-]*:\/\//i.test(location) ? new URL(location) : pathToFileURL(resolve(location));
|
|
166
|
+
}
|
|
167
|
+
function defaultCacheDirectory() {
|
|
168
|
+
if (process.platform === "darwin") return join(homedir(), "Library", "Caches", "chartcoach");
|
|
169
|
+
if (process.platform === "win32") return join(process.env.LOCALAPPDATA ?? join(homedir(), "AppData", "Local"), "chartcoach", "Cache");
|
|
170
|
+
return join(process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache"), "chartcoach");
|
|
171
|
+
}
|
|
172
|
+
async function existingFile(path) {
|
|
173
|
+
try {
|
|
174
|
+
return (await stat(path)).isFile();
|
|
175
|
+
} catch (error) {
|
|
176
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async function readBytes(path, maximum, signal) {
|
|
181
|
+
if ((await stat(path)).size > maximum) throw new CatalogError("Catalog file exceeds size limit.");
|
|
182
|
+
const chunks = [];
|
|
183
|
+
let size = 0;
|
|
184
|
+
for await (const chunk of createReadStream(path, { signal })) {
|
|
185
|
+
size += chunk.length;
|
|
186
|
+
if (size > maximum) throw new CatalogError("Catalog file exceeds size limit.");
|
|
187
|
+
chunks.push(chunk);
|
|
188
|
+
}
|
|
189
|
+
signal?.throwIfAborted();
|
|
190
|
+
return Buffer.concat(chunks);
|
|
191
|
+
}
|
|
192
|
+
async function readCached(path, maximum) {
|
|
193
|
+
try {
|
|
194
|
+
return await readBytes(path, maximum);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
if (error instanceof CatalogError || error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async function atomicWrite(path, bytes) {
|
|
201
|
+
await mkdir(dirname(path), { recursive: true });
|
|
202
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
203
|
+
try {
|
|
204
|
+
await writeFile(temporary, bytes, { flag: "wx" });
|
|
205
|
+
await rename(temporary, path);
|
|
206
|
+
} finally {
|
|
207
|
+
await removeTemporary(temporary);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async function removeTemporary(path) {
|
|
211
|
+
try {
|
|
212
|
+
await unlink(path);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
//#endregion
|
|
218
|
+
export { artifactPath, indexPath, openCatalog };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { n as Catalog } from "./model-De0MF-zg.js";
|
|
2
|
+
//#region src/catalog/open.d.ts
|
|
3
|
+
type FetchLike = (input: string | URL, init?: RequestInit) => Promise<Response>;
|
|
4
|
+
interface ArtifactCache {
|
|
5
|
+
get(sha256: string, bytes: number): Promise<Uint8Array | undefined>;
|
|
6
|
+
put(sha256: string, bytes: Uint8Array): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
type OpenCatalogOptions = {
|
|
9
|
+
fetch?: FetchLike;
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
cache?: ArtifactCache;
|
|
12
|
+
};
|
|
13
|
+
declare function openCatalog(location?: string | URL, options?: OpenCatalogOptions): Promise<Catalog>;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { openCatalog as i, FetchLike as n, OpenCatalogOptions as r, ArtifactCache as t };
|