keepset 0.2.0 → 0.4.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/dist/index.js CHANGED
@@ -1,375 +1,30 @@
1
- import { z } from "zod";
2
- import { CrosswalkSchema } from "twindex";
3
- //#region src/errors.ts
4
- /** The root of this package's error taxonomy: every error keepset throws is a `KeepsetError`, so callers can catch the family with one check. */
5
- var KeepsetError = class extends Error {
6
- constructor(message) {
7
- super(message);
8
- this.name = new.target.name;
9
- }
10
- };
11
- /** Thrown when a category id names no category in the registry. Carries the ids that do exist, so the message says what would have been accepted. */
12
- var UnknownCategoryError = class extends KeepsetError {
13
- categoryId;
14
- knownIds;
15
- constructor(categoryId, knownIds) {
16
- super(`unknown category ${JSON.stringify(categoryId)}: known categories are ${knownIds.join(", ")}`);
17
- this.categoryId = categoryId;
18
- this.knownIds = knownIds;
19
- }
20
- };
21
- /** Thrown when an entry or a catalogue manifest fails its schema. Carries every issue found, not just the first, so one failed parse reports the whole document. */
22
- var CatalogueSyntaxError = class extends KeepsetError {
23
- document;
24
- issues;
25
- constructor(document, issues) {
26
- super(`${document} syntax error: ${issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ")}`);
27
- this.document = document;
28
- this.issues = issues;
29
- }
30
- };
31
- //#endregion
32
- //#region src/define.ts
33
- /**
34
- * Attaches a type guard to a Zod schema (the schema-attached guard pattern: validation, inference and narrowing share one definition, so the three cannot drift). This module is isolated because it is the one place in the package that augments an object in place with Object.assign, which the house lint rule that eslint.config.ts turns off for this file alone would otherwise reject: object spread is no alternative, as it copies own properties only and drops the Zod prototype's parse/refine methods.
35
- * @param schema - The schema to augment.
36
- * @returns The same schema object, now also carrying `is`.
37
- */
38
- function define(schema) {
39
- return Object.assign(schema, { is(value) {
40
- return schema.safeParse(value).success;
41
- } });
42
- }
43
- //#endregion
44
- //#region src/record.ts
45
- /**
46
- * A record whose every key must satisfy `key`. Zod's own record silently drops an own `__proto__` key (which `JSON.parse` creates as an ordinary property) before the key schema sees it, so this checks the raw input first and rejects such a key loudly instead of losing the value.
47
- * @param key - The key schema.
48
- * @param value - The value schema.
49
- * @returns The record schema.
50
- */
51
- function recordOf(key, value) {
52
- return z.preprocess((input, ctx) => {
53
- if (typeof input === "object" && input !== null && Object.hasOwn(input, "__proto__")) ctx.issues.push({
54
- code: "custom",
55
- input,
56
- message: "__proto__ is not a valid key"
57
- });
58
- return input;
59
- }, z.record(key, value));
60
- }
61
- //#endregion
62
- //#region src/targets/graph-v1.ts
63
- /** A JSON scalar: the only values graph/v1 metadata may hold, so `meta` stays a flat record any consumer can tabulate. `z.number()` already rejects NaN and the infinities, which JSON cannot carry. */
64
- const JsonScalarSchema = z.union([
65
- z.string(),
66
- z.number(),
67
- z.boolean(),
68
- z.null()
69
- ]);
70
- /** A node: an id, an optional type, and whatever else the dataset carries. The target fixes only what every graph consumer needs, so extra fields pass through untouched. */
71
- const GraphNodeSchema = z.looseObject({
72
- id: z.string().min(1),
73
- type: z.string().min(1).optional()
74
- });
75
- /** An edge: source and target node ids, an optional type, and whatever else the dataset carries. Direction is the dataset's own: graph/v1 records it in `meta`, never by duplicating edges. */
76
- const GraphEdgeSchema = z.looseObject({
77
- source: z.string().min(1),
78
- target: z.string().min(1),
79
- type: z.string().min(1).optional()
80
- });
81
- /**
82
- * The graph/v1 normalisation target: `{ meta, nodes, edges }`, the shape the thesis pipeline's benchmark cache already uses. It describes the result `fetchDataset` resolves a graph entry to, not what a source file may contain. Beyond the field shapes it checks the two properties every consumer relies on: node ids are unique, and every edge endpoint names a node.
83
- */
84
- const GraphV1Schema = define(z.object({
85
- meta: recordOf(z.string().min(1), JsonScalarSchema),
86
- nodes: z.array(GraphNodeSchema),
87
- edges: z.array(GraphEdgeSchema)
88
- }).strict().superRefine((graph, ctx) => {
89
- const ids = /* @__PURE__ */ new Set();
90
- for (const [index, node] of graph.nodes.entries()) {
91
- if (ids.has(node.id)) ctx.addIssue({
92
- code: "custom",
93
- path: [
94
- "nodes",
95
- index,
96
- "id"
97
- ],
98
- message: `duplicate node id ${JSON.stringify(node.id)}`
99
- });
100
- ids.add(node.id);
101
- }
102
- for (const [index, edge] of graph.edges.entries()) for (const end of ["source", "target"]) if (!ids.has(edge[end])) ctx.addIssue({
103
- code: "custom",
104
- path: [
105
- "edges",
106
- index,
107
- end
108
- ],
109
- message: `edge ${end} ${JSON.stringify(edge[end])} names no node`
110
- });
111
- }));
112
- //#endregion
113
- //#region src/categories.ts
114
- /** Every category id the registry knows. A category id is `<kind>/v<version>`: the kind of data, and the version of its normalisation target, so a breaking change to a target is a new category rather than an edit to an old one. */
115
- const CategoryIdSchema = z.enum(["graph/v1"]);
116
- /** The registry. Adding a category is adding its id to `CategoryIdSchema` and its entry here, with its target schema beside graph/v1's in src/targets/: the mapped type makes the compiler reject a registry that misses an id or has one too many. */
117
- const registry = { "graph/v1": {
118
- id: "graph/v1",
119
- description: "A graph as { meta, nodes, edges }: a flat record of scalar metadata, nodes carrying at least an id, and edges carrying at least source and target node ids, with optional node and edge types.",
120
- target: GraphV1Schema
121
- } };
122
- function getCategory(id) {
123
- const parsed = CategoryIdSchema.safeParse(id);
124
- if (!parsed.success) throw new UnknownCategoryError(id, CategoryIdSchema.options);
125
- return registry[parsed.data];
126
- }
127
- function getTarget(id) {
128
- return getCategory(id).target;
129
- }
130
- /**
131
- * The kind of data a category holds: the part of its id before the version.
132
- * @param id - A category id.
133
- * @returns The kind, such as `graph`.
134
- */
135
- function kindOf(id) {
136
- return id.slice(0, id.indexOf("/"));
137
- }
138
- /** @returns Every registered category, in id order. */
139
- function listCategories() {
140
- return CategoryIdSchema.options.map((id) => getCategory(id));
141
- }
142
- //#endregion
143
- //#region src/entry.ts
144
- /** A stable identifier: an entry id, a catalogue id. It starts with a letter or digit, so no id can be `__proto__` or any other name that begins with an underscore. Entry ids stay the same across forks, which is what lets two catalogues be compared entry by entry. */
145
- const IdSchema = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, { message: "must start with a letter or digit and contain only letters, digits, '.', '_' and '-'" });
146
- /** A key of an entry's `properties` or `expectedCounts` record: an identifier that starts with a letter, so it can never be `__proto__` or another name Object.prototype reacts to on assignment. */
147
- const RecordKeySchema = z.string().regex(/^[A-Za-z][A-Za-z0-9]*$/, { message: "must be a letter followed by letters or digits" });
148
- /** A calendar date, `YYYY-MM-DD`. */
149
- const IsoDateSchema = z.iso.date();
150
- /** An http or https URL: the only schemes a recipe may download from. */
151
- const HttpUrlSchema = z.httpUrl();
152
- /** A Wayback Machine timestamp, `YYYYMMDD` or `YYYYMMDDhhmmss`: the moment the snapshot is pinned to. */
153
- const WaybackTimestampSchema = z.string().regex(/^\d{8}(?:\d{6})?$/, { message: "must be a Wayback timestamp, YYYYMMDD or YYYYMMDDhhmmss" });
154
- /**
155
- * The Wayback Machine URL of a snapshot of `url` at `timestamp`: the page form a person follows. The raw bytes a recipe downloads come from the `id_` form, which `fetchDataset` derives from this one.
156
- * @param timestamp - A Wayback timestamp.
157
- * @param url - The original URL.
158
- * @returns The snapshot URL.
159
- */
160
- function waybackUrl(timestamp, url) {
161
- return `https://web.archive.org/web/${timestamp}/${url}`;
162
- }
163
- /** A pinned Wayback Machine snapshot of a source. The timestamp is part of the recipe's reproducibility, not incidental metadata, so it travels with the URL and the two are checked against each other. */
164
- const SnapshotSchema = z.object({
165
- url: HttpUrlSchema,
166
- timestamp: WaybackTimestampSchema
167
- }).strict();
168
- /**
169
- * Where a dataset's bytes come from. A `dead` source no longer serves the bytes the recipe was written against and must carry the snapshot the recipe fetches instead; a `live` source may also carry one, as the pinned record of what it served. The snapshot URL must be exactly `waybackUrl(timestamp, url)`.
170
- */
171
- const SourceSchema = z.object({
172
- url: HttpUrlSchema,
173
- retrieved: IsoDateSchema,
174
- status: z.enum(["live", "dead"]),
175
- snapshot: SnapshotSchema.optional()
176
- }).strict().superRefine((source, ctx) => {
177
- if (source.status === "dead" && source.snapshot === void 0) ctx.addIssue({
178
- code: "custom",
179
- path: ["snapshot"],
180
- message: "a dead source must pin the Wayback snapshot its recipe fetches"
181
- });
182
- if (source.snapshot !== void 0 && source.snapshot.url !== waybackUrl(source.snapshot.timestamp, source.url)) ctx.addIssue({
183
- code: "custom",
184
- path: ["snapshot", "url"],
185
- message: `must be ${waybackUrl(source.snapshot.timestamp, source.url)}, the snapshot of this source's url at this timestamp`
186
- });
187
- });
188
- /** The licence a dataset is published under: an SPDX identifier, a named licence with the URL of its terms, or a statement that the source records none. A licence the source does not state is recorded as unrecorded rather than guessed. */
189
- const LicenceSchema = z.discriminatedUnion("kind", [
190
- z.object({
191
- kind: z.literal("spdx"),
192
- id: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9.+-]*$/, { message: "must be an SPDX licence identifier" })
193
- }).strict(),
194
- z.object({
195
- kind: z.literal("custom"),
196
- name: z.string().min(1),
197
- url: HttpUrlSchema
198
- }).strict(),
199
- z.object({ kind: z.literal("unrecorded") }).strict()
200
- ]);
201
- /** A work to cite for the dataset: a formatted reference, with its DOI or URL where the source records one. */
202
- const CitationSchema = z.object({
203
- text: z.string().min(1),
204
- doi: z.string().regex(/^10\.\d{4,9}\/\S+$/, { message: "must be a bare DOI, 10.<registrant>/<suffix>" }).optional(),
205
- url: HttpUrlSchema.optional()
206
- }).strict();
207
- /** A relative path inside an archive or a catalogue: `/`-separated segments, none empty, `.` or `..`, so it can never climb out of the directory it is resolved against. */
208
- const RelativePathSchema = z.string().refine((path) => path.split("/").every((segment) => segment !== "" && segment !== "." && segment !== ".."), { message: "must be a relative path of non-empty segments with no '.' or '..'" });
209
- /** Which file of an archive to read: a member path, matched against a member's whole path or its trailing segments (so `net.gml` finds `net/net.gml`), or the member whose name ends with an extension. Either must match exactly one member when the recipe runs; macOS resource-fork members (`__MACOSX/` and `._` files) never match. */
210
- const ArchiveMemberSchema = z.union([z.object({ path: RelativePathSchema }).strict(), z.object({ extension: z.string().regex(/^\.[A-Za-z0-9][A-Za-z0-9.]*$/, { message: "must be a file extension such as .mtx" }) }).strict()]);
211
- /** How the downloaded bytes unpack into the one file the parser reads. */
212
- const ArchiveSchema = z.discriminatedUnion("kind", [
213
- z.object({ kind: z.literal("none") }).strict(),
214
- z.object({ kind: z.literal("gzip") }).strict(),
215
- z.object({
216
- kind: z.literal("zip"),
217
- member: ArchiveMemberSchema
218
- }).strict(),
219
- z.object({
220
- kind: z.literal("tar"),
221
- member: ArchiveMemberSchema
222
- }).strict(),
223
- z.object({
224
- kind: z.literal("tar.gz"),
225
- member: ArchiveMemberSchema
226
- }).strict()
227
- ]);
228
- const ColumnSchema = z.int().min(0);
229
- /** The text encoding of the unpacked file. Decoding is strict: bytes that are not valid in the stated encoding fail the recipe rather than turning into replacement characters. */
230
- const EncodingSchema = z.enum(["utf-8", "latin1"]);
231
- /** The parser that reads the unpacked file into the dataset's native JSON shape, with the options that format needs. Every option is stated in the entry, so a recipe reads the same way wherever it runs. */
232
- const ParserSchema = z.discriminatedUnion("format", [
233
- z.object({
234
- format: z.literal("edge-list"),
235
- encoding: EncodingSchema,
236
- delimiter: z.enum([
237
- "whitespace",
238
- "tab",
239
- "comma",
240
- "space"
241
- ]),
242
- skipLines: z.int().min(0),
243
- commentPrefixes: z.array(z.string().min(1)),
244
- sourceColumn: ColumnSchema,
245
- targetColumn: ColumnSchema
246
- }).strict().refine((parser) => parser.sourceColumn !== parser.targetColumn, {
247
- message: "sourceColumn and targetColumn must differ",
248
- path: ["targetColumn"]
249
- }),
250
- z.object({
251
- format: z.literal("matrix-market"),
252
- encoding: EncodingSchema
253
- }).strict(),
254
- z.object({
255
- format: z.literal("gml"),
256
- encoding: EncodingSchema
257
- }).strict(),
258
- z.object({
259
- format: z.literal("pajek"),
260
- encoding: EncodingSchema
261
- }).strict()
262
- ]);
263
- /** How to obtain the dataset: a fetchable recipe naming the source (by index into `sources`), the archive layout and the parser, or a statement that no declarative recipe exists yet and why. */
264
- const RecipeSchema = z.discriminatedUnion("status", [z.object({
265
- status: z.literal("fetchable"),
266
- source: z.int().min(0),
267
- archive: ArchiveSchema,
268
- parser: ParserSchema
269
- }).strict(), z.object({
270
- status: z.literal("not-fetchable"),
271
- reason: z.string().min(1)
272
- }).strict()]);
273
- /** The twindex crosswalk from the native shape to the category target: the document inline, or a `.json` file path relative to the catalogue root, so entries that share a native shape share one crosswalk. */
274
- const CrosswalkReferenceSchema = z.union([CrosswalkSchema, z.object({ file: RelativePathSchema.refine((path) => path.endsWith(".json"), { message: "must name a .json file" }) }).strict()]);
275
- /**
276
- * One catalogue entry: the descriptor (id, name, description, domain, tags), the category whose target the entry resolves to, where the bytes come from, the licence and citations, the recipe, the crosswalk, the counts a fetched result is expected to have and the dataset's recorded properties. The kind of data is the category's (`kindOf(category)`), never a second field that could disagree with it.
277
- */
278
- const EntrySchema = define(z.object({
279
- id: IdSchema,
280
- name: z.string().min(1),
281
- description: z.string().min(1).optional(),
282
- domain: z.string().min(1).optional(),
283
- tags: z.array(z.string().min(1)).refine((tags) => new Set(tags).size === tags.length, { message: "tags must be unique" }),
284
- category: CategoryIdSchema,
285
- sources: z.array(SourceSchema).min(1),
286
- licence: LicenceSchema,
287
- citations: z.array(CitationSchema),
288
- recipe: RecipeSchema,
289
- crosswalk: CrosswalkReferenceSchema,
290
- expectedCounts: recordOf(RecordKeySchema, z.int().min(0)),
291
- properties: recordOf(RecordKeySchema, JsonScalarSchema)
292
- }).strict().superRefine((entry, ctx) => {
293
- if (entry.recipe.status === "fetchable" && entry.recipe.source >= entry.sources.length) ctx.addIssue({
294
- code: "custom",
295
- path: ["recipe", "source"],
296
- message: `names source ${String(entry.recipe.source)} but the entry has ${String(entry.sources.length)}`
297
- });
298
- if ("target" in entry.crosswalk && entry.crosswalk.target !== entry.category) ctx.addIssue({
299
- code: "custom",
300
- path: ["crosswalk", "target"],
301
- message: `must be the entry's category, ${entry.category}`
302
- });
303
- }));
304
- /**
305
- * Collapses Zod issues into the package's own issue shape, so the public error surface stays stable across Zod versions.
306
- * @param error - A Zod error.
307
- * @returns One issue per Zod issue, with its path written as dotted text.
308
- */
309
- function toIssues(error) {
310
- return error.issues.map((issue) => ({
311
- path: issue.path.map(String).join(".") || "(root)",
312
- message: issue.message
313
- }));
314
- }
1
+ import { A as getCategory, B as DatasetNotFoundError, C as SnapshotSchema, D as waybackUrl, E as parseEntry, F as GraphNodeSchema, H as NativeParseError, I as GraphV1Schema, L as JsonScalarSchema, M as kindOf, N as listCategories, O as CategoryIdSchema, P as GraphEdgeSchema, R as ArchiveError, S as RelativePathSchema, T as WaybackTimestampSchema, U as RecipeError, V as KeepsetError, W as UnknownCategoryError, _ as IdSchema, a as createCatalogue, b as RESERVED_META_KEYS, c as ManifestSchema, d as ArchiveMemberSchema, f as ArchiveSchema, g as HttpUrlSchema, h as EntrySchema, i as CatalogueIndexSchema, j as getTarget, k as countTarget, l as SemVerSchema, m as CrosswalkReferenceSchema, n as getManifest, p as CitationSchema, r as listDatasets, s as LineageSchema, t as getDataset, u as parseManifest, v as LicenceSchema, w as SourceSchema, x as RecipeSchema, y as ParserSchema, z as CatalogueSyntaxError } from "./bundled-DN3sSHfu.js";
2
+ //#region src/serialise.ts
315
3
  /**
316
- * Parses an unknown value as a catalogue entry.
317
- * @param doc - The candidate entry document.
318
- * @returns The validated entry.
319
- * @throws CatalogueSyntaxError carrying every issue found.
4
+ * Copies a JSON value with every object's keys in ascending code-unit order, the order the house JSON lint (`json/sort-keys`) requires of committed JSON. `Object.fromEntries` defines each key as an own data property, so a key such as `__proto__` stays a key.
5
+ * @param value - A JSON value.
6
+ * @returns The same value with sorted keys.
320
7
  */
321
- function parseEntry(doc) {
322
- const result = EntrySchema.safeParse(doc);
323
- if (!result.success) throw new CatalogueSyntaxError("entry", toIssues(result.error));
324
- return result.data;
8
+ function sortKeys(value) {
9
+ if (Array.isArray(value)) return value.map(sortKeys);
10
+ if (typeof value === "object" && value !== null) return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left < right ? -1 : 1).map(([key, item]) => [key, sortKeys(item)]));
11
+ return value;
325
12
  }
326
- //#endregion
327
- //#region src/manifest.ts
328
- /** A semantic version, `MAJOR.MINOR.PATCH` with optional pre-release and build parts (https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string). */
329
- const SemVerSchema = z.string().regex(/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/, { message: "must be a semantic version, MAJOR.MINOR.PATCH" });
330
- /** Where a forked catalogue came from: the catalogue it was forked from, the version it forked at and the repository that holds it, which together are what lets a fork's entries be compared with, and brought back to, their origin. */
331
- const LineageSchema = z.object({
332
- catalogue: IdSchema,
333
- version: SemVerSchema,
334
- repository: z.httpUrl()
335
- }).strict();
336
13
  /**
337
- * A catalogue manifest: the catalogue's own id and version, where it lives, and, for a fork, its lineage. A catalogue is a self-contained set of entry documents beside its manifest, and a fork is its own catalogue with its own id, so lineage may never name the catalogue itself.
338
- */
339
- const ManifestSchema = define(z.object({
340
- id: IdSchema,
341
- name: z.string().min(1),
342
- description: z.string().min(1),
343
- version: SemVerSchema,
344
- repository: z.httpUrl(),
345
- lineage: LineageSchema.optional()
346
- }).strict().superRefine((manifest, ctx) => {
347
- if (manifest.lineage?.catalogue === manifest.id) ctx.addIssue({
348
- code: "custom",
349
- path: ["lineage", "catalogue"],
350
- message: "a fork is its own catalogue: lineage must name the catalogue it was forked from, not itself"
351
- });
352
- }));
353
- /**
354
- * Parses an unknown value as a catalogue manifest.
355
- * @param doc - The candidate manifest document.
356
- * @returns The validated manifest.
357
- * @throws CatalogueSyntaxError carrying every issue found.
14
+ * Serialises any JSON value in the committed form: two-space indented, keys in ascending order, with a trailing newline.
15
+ * @param value - A JSON value.
16
+ * @returns The file text.
358
17
  */
359
- function parseManifest(doc) {
360
- const result = ManifestSchema.safeParse(doc);
361
- if (!result.success) throw new CatalogueSyntaxError("manifest", toIssues(result.error));
362
- return result.data;
18
+ function canonicalJson(value) {
19
+ return `${JSON.stringify(sortKeys(value), void 0, 2)}\n`;
363
20
  }
364
- //#endregion
365
- //#region src/serialise.ts
366
21
  /**
367
- * Serialises a catalogue document as its file holds it: two-space indented JSON in the schema's key order with a trailing newline. A parsed document keeps the schema's key order, so parsing a file written this way and serialising the result reproduces the file byte for byte.
368
- * @param doc - A parsed entry or manifest.
22
+ * Serialises a catalogue document (an entry, a manifest or a crosswalk file) as its file holds it: two-space indented JSON with keys in ascending order and a trailing newline. The form depends only on the document's content, so parsing a file written this way and serialising the result reproduces the file byte for byte.
23
+ * @param doc - A parsed entry, manifest or crosswalk.
369
24
  * @returns The file text.
370
25
  */
371
26
  function serialise(doc) {
372
- return `${JSON.stringify(doc, void 0, 2)}\n`;
27
+ return canonicalJson(doc);
373
28
  }
374
29
  //#endregion
375
- export { ArchiveMemberSchema, ArchiveSchema, CatalogueSyntaxError, CategoryIdSchema, CitationSchema, CrosswalkReferenceSchema, EntrySchema, GraphEdgeSchema, GraphNodeSchema, GraphV1Schema, IdSchema, JsonScalarSchema, KeepsetError, LicenceSchema, LineageSchema, ManifestSchema, ParserSchema, RecipeSchema, RelativePathSchema, SemVerSchema, SnapshotSchema, SourceSchema, UnknownCategoryError, WaybackTimestampSchema, getCategory, getTarget, kindOf, listCategories, parseEntry, parseManifest, serialise, waybackUrl };
30
+ export { ArchiveError, ArchiveMemberSchema, ArchiveSchema, CatalogueIndexSchema, CatalogueSyntaxError, CategoryIdSchema, CitationSchema, CrosswalkReferenceSchema, DatasetNotFoundError, EntrySchema, GraphEdgeSchema, GraphNodeSchema, GraphV1Schema, HttpUrlSchema, IdSchema, JsonScalarSchema, KeepsetError, LicenceSchema, LineageSchema, ManifestSchema, NativeParseError, ParserSchema, RESERVED_META_KEYS, RecipeError, RecipeSchema, RelativePathSchema, SemVerSchema, SnapshotSchema, SourceSchema, UnknownCategoryError, WaybackTimestampSchema, countTarget, createCatalogue, getCategory, getDataset, getManifest, getTarget, kindOf, listCategories, listDatasets, parseEntry, parseManifest, serialise, waybackUrl };
@@ -0,0 +1,58 @@
1
+ import { B as Entry, C as Dataset, Q as Snapshot, et as Source, i as CategoryTarget } from "../categories-CUf1VecC.js";
2
+ //#region src/node/fetch-dataset.d.ts
3
+ /** The network port a recipe downloads through: `downloadRaw` by default, which returns the bytes exactly as served, or a stand-in under test. */
4
+ export type FetchPort = (url: string, init: {
5
+ readonly signal?: AbortSignal;
6
+ }) => Promise<Response>;
7
+ /** What a fetch may be given beyond the dataset: the network port, an abort signal and the clock that dates the provenance record. */
8
+ export interface FetchOptions {
9
+ readonly fetch?: FetchPort;
10
+ readonly signal?: AbortSignal;
11
+ readonly now?: () => Date;
12
+ }
13
+ /** Where a fetched result's bytes came from: enough to say which entry, catalogue and source produced it, which URL was actually requested, when, and exactly which bytes arrived. */
14
+ export interface Provenance {
15
+ readonly entryId: string;
16
+ readonly catalogue: Dataset["catalogue"];
17
+ readonly category: Entry["category"];
18
+ readonly source: {
19
+ readonly url: string;
20
+ readonly status: Source["status"];
21
+ readonly snapshot?: Snapshot;
22
+ };
23
+ readonly fetchedUrl: string;
24
+ readonly retrievedAt: string;
25
+ readonly sha256: string;
26
+ readonly byteLength: number;
27
+ readonly member?: string;
28
+ }
29
+ /** A fetched dataset: the resolved category target, the provenance of the bytes it came from, and any warnings, such as a count that differs from the one the entry records. */
30
+ export interface FetchedDataset {
31
+ readonly dataset: Dataset;
32
+ readonly data: CategoryTarget;
33
+ readonly provenance: Provenance;
34
+ readonly warnings: readonly string[];
35
+ }
36
+ /**
37
+ * The URL a recipe downloads a source from. A dead source is fetched from its pinned snapshot in the Wayback Machine's `id_` form, which serves the archived bytes as they were captured rather than wrapped in the archive's page chrome.
38
+ * @param source - The source.
39
+ * @returns The URL to request.
40
+ */
41
+ export declare function downloadUrl(source: Source): string;
42
+ /**
43
+ * Runs a dataset's recipe: downloads the source (from its pinned snapshot when the source is dead), unpacks the archive, parses the native shape, applies the twindex crosswalk and validates the result against the category target. The native document the crosswalk receives is the parser's output with `meta` built from the entry.
44
+ * @param dataset - A dataset from any catalogue.
45
+ * @param options - The network port, abort signal and clock.
46
+ * @returns The resolved target with its provenance and any count warnings.
47
+ * @throws RecipeError naming the entry and the failing step; nothing partial or default-shaped is ever returned.
48
+ */
49
+ export declare function fetchEntry(dataset: Dataset, options?: FetchOptions): Promise<FetchedDataset>;
50
+ /**
51
+ * Fetches a dataset of the bundled catalogue by id and resolves it to its category's target shape, so `fetchDataset("cora")` yields graph/v1 `{ meta, nodes, edges }` directly. Node-only: it downloads with Node's http and https clients and unpacks with Node's zlib.
52
+ * @param id - The entry id.
53
+ * @param options - The network port, abort signal and clock.
54
+ * @returns The resolved target with its provenance and any count warnings.
55
+ * @throws DatasetNotFoundError when no entry has that id, and RecipeError naming the entry and the failing step when the recipe fails.
56
+ */
57
+ export declare function fetchDataset(id: string, options?: FetchOptions): Promise<FetchedDataset>;
58
+ //#endregion