keepset 0.11.0 → 0.13.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 CHANGED
@@ -29,13 +29,14 @@ provenance.sha256; // the hash of exactly those bytes
29
29
 
30
30
  ```sh
31
31
  npx keepset search citatio --kind graph
32
+ npx keepset namespaces
32
33
  npx keepset show cora
33
34
  npx keepset fetch cora --out cora.json
34
35
  npx keepset validate cora
35
36
  npx keepset roundtrip cora
36
37
  ```
37
38
 
38
- `fetch` writes the normalised result with its provenance beside it; `validate` checks the entry against the entry schema and its crosswalk against twindex's validation without the network; `roundtrip` fetches the dataset and checks that decoding the encoded native document gives it back. Exit status is 0 for an answer, 1 for a negative one (an invalid entry, a round trip that does not hold) and 2 for no answer (bad usage, an unknown or ambiguous name, a recipe that failed).
39
+ `namespaces [path]` lists the namespaces directly beneath a path (the outermost ones without one) with how many datasets each holds, and the site has a page for each, linked from every entry's namespace. `fetch` writes the normalised result with its provenance beside it; `validate` checks the entry against the entry schema and its crosswalk against twindex's validation without the network; `roundtrip` fetches the dataset and checks that decoding the encoded native document gives it back. Exit status is 0 for an answer, 1 for a negative one (an invalid entry, a round trip that does not hold) and 2 for no answer (bad usage, an unknown or ambiguous name or namespace, a recipe that failed).
39
40
 
40
41
  ## Names, aliases and namespaces
41
42
 
@@ -48,7 +49,11 @@ npx keepset fetch gset/g53
48
49
  npx keepset fetch g53
49
50
  ```
50
51
 
51
- A qualified name joins the namespace to a name with `/`, and any leading part of the namespace may be dropped while the name still means one dataset. An entry's own id always wins; otherwise a name that more than one entry answers to is refused with the ids it could mean (`DatasetAmbiguousError` in the library, exit status 2 in the CLI), and the way out is a longer qualified name or the id. `search --namespace suitesparse` (or `listDatasets({ namespace })`) lists a namespace and everything beneath it. Everything a command writes or prints uses the entry's own id, never the name typed.
52
+ A qualified name joins the namespace to a name with `/`, and any leading part of the namespace may be dropped while the name still means one dataset (a middle part may not be). An entry's own id always wins; otherwise a name that more than one entry answers to is refused with the ids it could mean (`DatasetAmbiguousError` in the library, exit status 2 in the CLI), and the way out is a longer qualified name or the id. Everything a command writes or prints uses the entry's own id, never the name typed.
53
+
54
+ An alias that is a path, such as `foo/graphs/bar`, files the entry in a second group as well: it is a name in its own right, not prefixed by the entry's namespace, and answers to `bar`, `graphs/bar` and `foo/graphs/bar`. The namespace is where an entry comes from and path aliases are cross-listing, so an entry can sit in several groups. The catalogue manifest can also give a namespace segment aliases (`namespaceAliases`, such as `ss` for `suitesparse`), and a name then answers with the alias in the segment's place.
55
+
56
+ `search --namespace suitesparse` (or `listDatasets({ namespace })`) lists a group and every group beneath it, an entry once however many of those it is in. `listNamespaces(path)` lists the namespaces directly beneath a path, or the outermost ones, each with the number of entries at or beneath it.
52
57
 
53
58
  ## The entry format
54
59
 
@@ -191,10 +191,87 @@ function countTarget(id, target) {
191
191
  return new Map(category.counted.map((key) => [key, target[key].length]));
192
192
  }
193
193
  //#endregion
194
+ //#region src/own.ts
195
+ /**
196
+ * Reads an own property of a record keyed by strings that come from data. A plain `record[key]` read follows the prototype chain, so a key such as `constructor` would find an inherited function instead of reporting absence.
197
+ * @param record - The record to read.
198
+ * @param key - Any string.
199
+ * @returns The own property's value, or `undefined` when the record has no such own property.
200
+ */
201
+ function getOwn(record, key) {
202
+ return Object.hasOwn(record, key) ? record[key] : void 0;
203
+ }
204
+ //#endregion
205
+ //#region src/names.ts
206
+ /** The character that joins a namespace's segments to each other and to a name. It is not in an id's alphabet, so a name containing it is always a path and never a literal id. */
207
+ const NAME_SEPARATOR = "/";
208
+ /**
209
+ * Splits a path of ids on the separator.
210
+ * @param path - A path such as `a/b`.
211
+ * @returns Its segments.
212
+ */
213
+ function segmentsOf(path) {
214
+ return path.split("/");
215
+ }
216
+ /**
217
+ * The addresses of an entry, each as its path of segments with the name last: the id and every bare alias sit in the entry's own namespace, and an alias that is a path is an address of its own, in the group its leading segments name.
218
+ * @param entry - The entry.
219
+ * @returns One path per id or alias, the id first.
220
+ */
221
+ function addressesOf(entry) {
222
+ const { namespace = [], aliases = [] } = entry;
223
+ return [entry.id, ...aliases].map((name) => name.includes("/") ? segmentsOf(name) : [...namespace, name]);
224
+ }
225
+ /**
226
+ * The groups an entry belongs to: the namespace its id and bare aliases sit in, and the group each alias that is a path names, each once, in order of first appearance. A group is a path of segments; an entry belongs to every group above it as well, which the caller reads off the path.
227
+ * @param entry - The entry.
228
+ * @returns The groups, empty for an entry with no namespace and no path aliases.
229
+ */
230
+ function groupsOf(entry) {
231
+ const seen = /* @__PURE__ */ new Set();
232
+ return addressesOf(entry).map((address) => address.slice(0, -1)).filter((group) => {
233
+ const key = group.join("/");
234
+ if (group.length === 0 || seen.has(key)) return false;
235
+ seen.add(key);
236
+ return true;
237
+ });
238
+ }
239
+ /**
240
+ * Every spelling of a path of segments when each segment may be replaced by one of its aliases.
241
+ * @param segments - The segments.
242
+ * @param aliases - The segment aliases.
243
+ * @returns Each combination, the unaliased path first.
244
+ */
245
+ function spellings(segments, aliases) {
246
+ return segments.reduce((paths, segment) => paths.flatMap((path) => [segment, ...getOwn(aliases, segment) ?? []].map((spelling) => [...path, spelling])), [[]]);
247
+ }
248
+ /**
249
+ * Works out the names an entry answers to. Each address is a path of segments with the name last, and an address answers to every trailing run of its path, so id `c` in namespace `a/b` answers to `c`, `b/c` and `a/b/c`, and the alias `x/y/d` answers to `d`, `y/d` and `x/y/d`. Where the manifest gives a segment aliases, an address also answers with the alias in the segment's place.
250
+ * @param entry - The entry: only its id, namespace and aliases are read.
251
+ * @param segmentAliases - The manifest's aliases for namespace segments, if any.
252
+ * @returns The fully qualified names and every name that resolves to the entry.
253
+ */
254
+ function namesOf(entry, segmentAliases = {}) {
255
+ const addresses = addressesOf(entry);
256
+ const names = addresses.flatMap((address) => {
257
+ const name = address.slice(-1);
258
+ return spellings(address.slice(0, -1), segmentAliases).flatMap((group) => {
259
+ const path = [...group, ...name];
260
+ return Array.from({ length: path.length }, (_unused, depth) => path.slice(path.length - 1 - depth).join("/"));
261
+ });
262
+ });
263
+ return {
264
+ full: addresses.map((address) => address.join("/")),
265
+ all: [...new Set(names)]
266
+ };
267
+ }
268
+ //#endregion
194
269
  //#region src/entry.ts
195
270
  /** 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. */
196
271
  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 '-'" });
197
272
  /** 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. */
273
+ /** An alias: an id, or a path of ids joined by `/` such as `foo/graphs/bar`. A bare id is a name inside the entry's own namespace; a path is a name of its own, in the group its leading segments name. The segment pattern repeats the one in `IdSchema` so that each part of a path is an id. */
274
+ const AliasSchema = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]*(?:\/[A-Za-z0-9][A-Za-z0-9._-]*)*$/, { message: "must be an id, or ids joined by '/', each starting with a letter or digit and containing only letters, digits, '.', '_' and '-'" });
198
275
  /** The path a dataset sits under in a collection, outermost segment first, such as `["suitesparse", "gset"]`. Each segment is an id, so a segment can never contain the `/` that joins a namespace to a name. */
199
276
  const NamespaceSchema = z.array(IdSchema).min(1);
200
277
  const RecordKeySchema = z.string().regex(/^[A-Za-z][A-Za-z0-9]*$/, { message: "must be a letter followed by letters or digits" });
@@ -334,7 +411,7 @@ const RESERVED_META_KEYS = ["id", "name"];
334
411
  const EntrySchema = define(z.object({
335
412
  id: IdSchema,
336
413
  namespace: NamespaceSchema.optional(),
337
- aliases: z.array(IdSchema).min(1).optional(),
414
+ aliases: z.array(AliasSchema).min(1).optional(),
338
415
  name: z.string().min(1),
339
416
  description: z.string().min(1).optional(),
340
417
  domain: z.string().min(1).optional(),
@@ -354,6 +431,12 @@ const EntrySchema = define(z.object({
354
431
  path: ["aliases"],
355
432
  message: "aliases must be unique and differ from the entry's own id"
356
433
  });
434
+ const { full } = namesOf(entry);
435
+ if (new Set(full).size !== full.length) ctx.addIssue({
436
+ code: "custom",
437
+ path: ["aliases"],
438
+ message: "the entry's id and aliases must not give the same fully qualified name twice"
439
+ });
357
440
  if (entry.recipe.status === "fetchable" && entry.recipe.source >= entry.sources.length) ctx.addIssue({
358
441
  code: "custom",
359
442
  path: ["recipe", "source"],
@@ -409,7 +492,7 @@ const LineageSchema = z.object({
409
492
  repository: HttpUrlSchema
410
493
  }).strict();
411
494
  /**
412
- * 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.
495
+ * A catalogue manifest: the catalogue's own id and version, where it lives, the other names its namespace segments go by, 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. A segment alias is a name for a whole segment wherever it appears in a namespace, so it may not repeat, name itself, or be the segment of another alias.
413
496
  */
414
497
  const ManifestSchema = define(z.object({
415
498
  id: IdSchema,
@@ -417,13 +500,35 @@ const ManifestSchema = define(z.object({
417
500
  description: z.string().min(1),
418
501
  version: SemVerSchema,
419
502
  repository: HttpUrlSchema,
420
- lineage: LineageSchema.optional()
503
+ lineage: LineageSchema.optional(),
504
+ namespaceAliases: recordOf(IdSchema, z.array(IdSchema).min(1)).optional()
421
505
  }).strict().superRefine((manifest, ctx) => {
422
506
  if (manifest.lineage?.catalogue === manifest.id) ctx.addIssue({
423
507
  code: "custom",
424
508
  path: ["lineage", "catalogue"],
425
509
  message: "a fork is its own catalogue: lineage must name the catalogue it was forked from, not itself"
426
510
  });
511
+ const aliasOf = /* @__PURE__ */ new Map();
512
+ const aliases = manifest.namespaceAliases ?? {};
513
+ for (const [segment, names] of Object.entries(aliases)) for (const alias of names) {
514
+ const earlier = aliasOf.get(alias);
515
+ if (alias === segment) ctx.addIssue({
516
+ code: "custom",
517
+ path: ["namespaceAliases", segment],
518
+ message: `${alias} cannot be an alias of itself`
519
+ });
520
+ else if (earlier !== void 0) ctx.addIssue({
521
+ code: "custom",
522
+ path: ["namespaceAliases", segment],
523
+ message: `${alias} is already an alias of ${earlier}`
524
+ });
525
+ else if (Object.hasOwn(aliases, alias)) ctx.addIssue({
526
+ code: "custom",
527
+ path: ["namespaceAliases", segment],
528
+ message: `${alias} is itself a segment with aliases`
529
+ });
530
+ aliasOf.set(alias, segment);
531
+ }
427
532
  }));
428
533
  /**
429
534
  * Parses an unknown value as a catalogue manifest.
@@ -437,37 +542,9 @@ function parseManifest(doc) {
437
542
  return result.data;
438
543
  }
439
544
  //#endregion
440
- //#region src/names.ts
441
- /** The character that joins a namespace's segments to each other and to a name. It is not in an id's alphabet, so a name containing it is always a path and never a literal id. */
442
- const NAME_SEPARATOR = "/";
443
- /**
444
- * Works out the names an entry answers to. A leaf is the entry's id or one of its aliases; a name is a leaf with any trailing run of the entry's namespace segments before it, so an entry in namespace `a/b` with id `c` answers to `c`, `b/c` and `a/b/c`.
445
- * @param entry - The entry: only its id, namespace and aliases are read.
446
- * @returns The fully qualified names and every name that resolves to the entry.
447
- */
448
- function namesOf(entry) {
449
- const { namespace = [], aliases = [] } = entry;
450
- const leaves = [entry.id, ...aliases];
451
- return {
452
- full: leaves.map((leaf) => [...namespace, leaf].join("/")),
453
- all: leaves.flatMap((leaf) => Array.from({ length: namespace.length + 1 }, (_unused, depth) => [...namespace.slice(namespace.length - depth), leaf].join("/")))
454
- };
455
- }
456
- //#endregion
457
- //#region src/own.ts
458
- /**
459
- * Reads an own property of a record keyed by strings that come from data. A plain `record[key]` read follows the prototype chain, so a key such as `constructor` would find an inherited function instead of reporting absence.
460
- * @param record - The record to read.
461
- * @param key - Any string.
462
- * @returns The own property's value, or `undefined` when the record has no such own property.
463
- */
464
- function getOwn(record, key) {
465
- return Object.hasOwn(record, key) ? record[key] : void 0;
466
- }
467
- //#endregion
468
545
  //#region src/catalogue.ts
469
546
  /**
470
- * A catalogue index: the manifest, every entry, and every crosswalk file of the catalogue, keyed by its catalogue-relative path. The build assembles it from a catalogue directory; a consumer holding its own catalogue (a fork, say) can pass one to `createCatalogue`. Entry ids are unique, every fully qualified name (the whole namespace and an id or alias) identifies one entry and is not another entry's id, every crosswalk passes twindex's structural validation, every crosswalk file reference resolves, and every crosswalk's target is the category of each entry that uses it.
547
+ * A catalogue index: the manifest, every entry, and every crosswalk file of the catalogue, keyed by its catalogue-relative path. The build assembles it from a catalogue directory; a consumer holding its own catalogue (a fork, say) can pass one to `createCatalogue`. Entry ids are unique, every fully qualified name (the whole path of an id or alias) identifies one entry and is not another entry's id, every namespace segment the manifest gives aliases to is some entry's segment and no alias is itself one, every crosswalk passes twindex's structural validation, every crosswalk file reference resolves, and every crosswalk's target is the category of each entry that uses it.
471
548
  */
472
549
  const CatalogueIndexSchema = define(z.object({
473
550
  manifest: ManifestSchema,
@@ -479,6 +556,27 @@ const CatalogueIndexSchema = define(z.object({
479
556
  path: ["crosswalks", file],
480
557
  message: `rule ${String(issue.ruleIndex)}: ${issue.message}`
481
558
  });
559
+ const segments = new Set(index.entries.flatMap((entry) => groupsOf(entry).flat()));
560
+ for (const [segment, aliases] of Object.entries(index.manifest.namespaceAliases ?? {})) {
561
+ if (!segments.has(segment)) ctx.addIssue({
562
+ code: "custom",
563
+ path: [
564
+ "manifest",
565
+ "namespaceAliases",
566
+ segment
567
+ ],
568
+ message: `no entry has a namespace segment ${segment}, so it has nothing to be an alias of`
569
+ });
570
+ for (const alias of aliases.filter((name) => segments.has(name))) ctx.addIssue({
571
+ code: "custom",
572
+ path: [
573
+ "manifest",
574
+ "namespaceAliases",
575
+ segment
576
+ ],
577
+ message: `${alias} is already a namespace segment of an entry, so it cannot also be an alias`
578
+ });
579
+ }
482
580
  const seen = /* @__PURE__ */ new Set();
483
581
  const ids = new Set(index.entries.map((entry) => entry.id));
484
582
  const owners = /* @__PURE__ */ new Map();
@@ -553,32 +651,57 @@ const CatalogueIndexSchema = define(z.object({
553
651
  * Whether a dataset matches a query.
554
652
  * @param dataset - The dataset.
555
653
  * @param query - The filters.
654
+ * @param groups - The dataset's groups, each as a path joined by the separator.
655
+ * @param namespace - The query's namespace with every segment alias replaced by its segment, when it has one.
556
656
  * @returns True when every given filter matches.
557
657
  */
558
- function matches(dataset, query) {
658
+ function matches(dataset, query, groups, namespace) {
559
659
  const { entry } = dataset;
560
660
  if (query.kind !== void 0 && dataset.kind !== query.kind) return false;
561
661
  if (query.category !== void 0 && entry.category !== query.category) return false;
562
662
  if (query.domain !== void 0 && entry.domain !== query.domain) return false;
563
- if (query.namespace !== void 0) {
564
- const path = (entry.namespace ?? []).join("/");
565
- if (path !== query.namespace && !path.startsWith(`${query.namespace}/`)) return false;
566
- }
663
+ if (namespace !== void 0 && !groups.some((group) => group === namespace || group.startsWith(`${namespace}/`))) return false;
567
664
  if (query.text !== void 0) {
568
665
  const needle = query.text.toLowerCase();
569
666
  return [
570
667
  entry.id,
571
668
  ...entry.aliases ?? [],
572
- (entry.namespace ?? []).join("/"),
669
+ ...groups,
573
670
  entry.name,
574
671
  entry.description,
575
672
  entry.domain,
576
673
  ...entry.tags
577
- ].filter((field) => field !== void 0 && field !== "").some((field) => field.toLowerCase().includes(needle));
674
+ ].filter((field) => field !== void 0).some((field) => field.toLowerCase().includes(needle));
578
675
  }
579
676
  return true;
580
677
  }
581
678
  /**
679
+ * Builds the tree of namespaces: every group of every entry and each path above it, with the ids of the entries at or beneath each.
680
+ * @param groupsById - Each entry's groups, each as a path joined by the separator.
681
+ * @returns The child namespaces of each path, the outermost ones under the empty path, each list in path order.
682
+ */
683
+ function namespaceTree(groupsById) {
684
+ const members = /* @__PURE__ */ new Map();
685
+ for (const [id, groups] of groupsById) for (const group of groups) {
686
+ const segments = group.split("/");
687
+ for (let length = 1; length <= segments.length; length += 1) {
688
+ const path = segments.slice(0, length).join("/");
689
+ members.set(path, (members.get(path) ?? /* @__PURE__ */ new Set()).add(id));
690
+ }
691
+ }
692
+ const children = /* @__PURE__ */ new Map();
693
+ for (const [path, ids] of [...members].sort(([left], [right]) => left < right ? -1 : 1)) {
694
+ const cut = path.lastIndexOf("/");
695
+ const parent = cut === -1 ? "" : path.slice(0, cut);
696
+ children.set(parent, [...children.get(parent) ?? [], {
697
+ path,
698
+ name: path.slice(cut + 1),
699
+ count: ids.size
700
+ }]);
701
+ }
702
+ return children;
703
+ }
704
+ /**
582
705
  * Builds a queryable catalogue from an index. Pure and synchronous: no filesystem, no network, so it runs the same in a browser as in Node.
583
706
  * @param index - A candidate catalogue index.
584
707
  * @returns The catalogue.
@@ -592,7 +715,11 @@ function createCatalogue(index) {
592
715
  id: manifest.id,
593
716
  version: manifest.version
594
717
  };
718
+ const segmentAliases = manifest.namespaceAliases ?? {};
719
+ const segmentOf = new Map(Object.entries(segmentAliases).flatMap(([segment, aliases]) => aliases.map((alias) => [alias, segment])));
720
+ const canonical = (path) => path.split("/").map((segment) => segmentOf.get(segment) ?? segment).join("/");
595
721
  const datasets = /* @__PURE__ */ new Map();
722
+ const groupsById = /* @__PURE__ */ new Map();
596
723
  const names = /* @__PURE__ */ new Map();
597
724
  for (const entry of [...entries].sort((left, right) => left.id < right.id ? -1 : 1)) {
598
725
  const crosswalk = "file" in entry.crosswalk ? getOwn(crosswalks, entry.crosswalk.file) : entry.crosswalk;
@@ -606,11 +733,16 @@ function createCatalogue(index) {
606
733
  crosswalk,
607
734
  catalogue
608
735
  });
609
- for (const name of new Set(namesOf(entry).all)) names.set(name, [...names.get(name) ?? [], entry.id]);
736
+ groupsById.set(entry.id, groupsOf(entry).map((group) => group.join("/")));
737
+ for (const name of namesOf(entry, segmentAliases).all) names.set(name, [...names.get(name) ?? [], entry.id]);
610
738
  }
739
+ const tree = namespaceTree(groupsById);
611
740
  return {
612
741
  manifest,
613
- listDatasets: (query = {}) => [...datasets.values()].filter((dataset) => matches(dataset, query)),
742
+ listDatasets: (query = {}) => {
743
+ const namespace = query.namespace === void 0 ? void 0 : canonical(query.namespace);
744
+ return [...datasets.values()].filter((dataset) => matches(dataset, query, groupsById.get(dataset.entry.id) ?? [], namespace));
745
+ },
614
746
  getDataset: (name) => {
615
747
  const exact = datasets.get(name);
616
748
  if (exact !== void 0) return exact;
@@ -621,7 +753,8 @@ function createCatalogue(index) {
621
753
  const dataset = datasets.get(only);
622
754
  if (dataset === void 0) throw new DatasetNotFoundError(name);
623
755
  return dataset;
624
- }
756
+ },
757
+ listNamespaces: (path) => tree.get(path === void 0 ? "" : canonical(path)) ?? []
625
758
  };
626
759
  }
627
760
  //#endregion
@@ -631,8 +764,12 @@ var catalogue_default = {
631
764
  "id": "keepset",
632
765
  "name": "keepset",
633
766
  "description": "The keepset catalogue: datasets with machine-readable recipes that fetch each one and normalise it into its category's target shape.",
634
- "version": "1.1.0",
635
- "repository": "https://github.com/Mearman/keepset"
767
+ "version": "1.2.0",
768
+ "repository": "https://github.com/Mearman/keepset",
769
+ "namespaceAliases": {
770
+ "networkrepository": ["nr"],
771
+ "suitesparse": ["ss"]
772
+ }
636
773
  },
637
774
  crosswalks: { "crosswalks/graph-v1/topology.json": {
638
775
  "source": "keepset/topology",
@@ -47667,9 +47804,17 @@ function listDatasets(query) {
47667
47804
  function getDataset(id) {
47668
47805
  return catalogue().getDataset(id);
47669
47806
  }
47807
+ /**
47808
+ * Lists the namespaces directly beneath a path of the bundled catalogue, or the outermost ones, with how many entries each holds. Browser-safe.
47809
+ * @param path - A group path whose children to list; absent for the outermost namespaces.
47810
+ * @returns The child namespaces in path order.
47811
+ */
47812
+ function listNamespaces(path) {
47813
+ return catalogue().listNamespaces(path);
47814
+ }
47670
47815
  /** @returns The bundled catalogue's manifest: its id, version and repository. */
47671
47816
  function getManifest() {
47672
47817
  return catalogue().manifest;
47673
47818
  }
47674
47819
  //#endregion
47675
- export { toIssues as A, GraphV1Schema as B, RESERVED_META_KEYS as C, SourceSchema as D, SnapshotSchema as E, getTarget as F, DatasetNotFoundError as G, ArchiveError as H, kindOf as I, RecipeError as J, KeepsetError as K, listCategories as L, CategoryIdSchema as M, countTarget as N, WaybackTimestampSchema as O, getCategory as P, GraphEdgeSchema as R, ParserSchema as S, RelativePathSchema as T, CatalogueSyntaxError as U, JsonScalarSchema as V, DatasetAmbiguousError as W, UnknownCategoryError as Y, EntrySchema as _, createCatalogue as a, LicenceSchema as b, namesOf as c, SemVerSchema as d, parseManifest as f, CrosswalkReferenceSchema as g, CitationSchema as h, CatalogueIndexSchema as i, waybackUrl as j, parseEntry as k, LineageSchema as l, ArchiveSchema as m, getManifest as n, getOwn as o, ArchiveMemberSchema as p, NativeParseError as q, listDatasets as r, NAME_SEPARATOR as s, getDataset as t, ManifestSchema as u, HttpUrlSchema as v, RecipeSchema as w, NamespaceSchema as x, IdSchema as y, GraphNodeSchema as z };
47820
+ export { waybackUrl as A, listCategories as B, RecipeSchema as C, WaybackTimestampSchema as D, SourceSchema as E, CategoryIdSchema as F, ArchiveError as G, GraphNodeSchema as H, countTarget as I, DatasetNotFoundError as J, CatalogueSyntaxError as K, getCategory as L, groupsOf as M, namesOf as N, parseEntry as O, getOwn as P, UnknownCategoryError as Q, getTarget as R, RESERVED_META_KEYS as S, SnapshotSchema as T, GraphV1Schema as U, GraphEdgeSchema as V, JsonScalarSchema as W, NativeParseError as X, KeepsetError as Y, RecipeError as Z, HttpUrlSchema as _, CatalogueIndexSchema as a, NamespaceSchema as b, ManifestSchema as c, AliasSchema as d, ArchiveMemberSchema as f, EntrySchema as g, CrosswalkReferenceSchema as h, listNamespaces as i, NAME_SEPARATOR as j, toIssues as k, SemVerSchema as l, CitationSchema as m, getManifest as n, createCatalogue as o, ArchiveSchema as p, DatasetAmbiguousError as q, listDatasets as r, LineageSchema as s, getDataset as t, parseManifest as u, IdSchema as v, RelativePathSchema as w, ParserSchema as x, LicenceSchema as y, kindOf as z };
@@ -4,6 +4,8 @@ import { z } from "zod";
4
4
  /** 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. */
5
5
  declare const IdSchema: z.ZodString;
6
6
  /** 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. */
7
+ /** An alias: an id, or a path of ids joined by `/` such as `foo/graphs/bar`. A bare id is a name inside the entry's own namespace; a path is a name of its own, in the group its leading segments name. The segment pattern repeats the one in `IdSchema` so that each part of a path is an id. */
8
+ declare const AliasSchema: z.ZodString;
7
9
  /** The path a dataset sits under in a collection, outermost segment first, such as `["suitesparse", "gset"]`. Each segment is an id, so a segment can never contain the `/` that joins a namespace to a name. */
8
10
  declare const NamespaceSchema: z.ZodArray<z.ZodString>;
9
11
  type Namespace = z.infer<typeof NamespaceSchema>;
@@ -443,7 +445,7 @@ declare const LineageSchema: z.ZodObject<{
443
445
  }, z.core.$strict>;
444
446
  type Lineage = z.infer<typeof LineageSchema>;
445
447
  /**
446
- * 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.
448
+ * A catalogue manifest: the catalogue's own id and version, where it lives, the other names its namespace segments go by, 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. A segment alias is a name for a whole segment wherever it appears in a namespace, so it may not repeat, name itself, or be the segment of another alias.
447
449
  */
448
450
  declare const ManifestSchema: z.ZodObject<{
449
451
  id: z.ZodString;
@@ -456,6 +458,7 @@ declare const ManifestSchema: z.ZodObject<{
456
458
  version: z.ZodString;
457
459
  repository: z.ZodURL;
458
460
  }, z.core.$strict>>;
461
+ namespaceAliases: z.ZodOptional<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>, unknown>>;
459
462
  }, z.core.$strict> & {
460
463
  is: (value: unknown) => value is {
461
464
  id: string;
@@ -468,6 +471,7 @@ declare const ManifestSchema: z.ZodObject<{
468
471
  version: string;
469
472
  repository: string;
470
473
  } | undefined;
474
+ namespaceAliases?: Record<string, string[]> | undefined;
471
475
  };
472
476
  };
473
477
  type Manifest = z.infer<typeof ManifestSchema>;
@@ -481,7 +485,7 @@ declare function parseManifest(doc: unknown): Manifest;
481
485
  //#endregion
482
486
  //#region src/catalogue.d.ts
483
487
  /**
484
- * A catalogue index: the manifest, every entry, and every crosswalk file of the catalogue, keyed by its catalogue-relative path. The build assembles it from a catalogue directory; a consumer holding its own catalogue (a fork, say) can pass one to `createCatalogue`. Entry ids are unique, every fully qualified name (the whole namespace and an id or alias) identifies one entry and is not another entry's id, every crosswalk passes twindex's structural validation, every crosswalk file reference resolves, and every crosswalk's target is the category of each entry that uses it.
488
+ * A catalogue index: the manifest, every entry, and every crosswalk file of the catalogue, keyed by its catalogue-relative path. The build assembles it from a catalogue directory; a consumer holding its own catalogue (a fork, say) can pass one to `createCatalogue`. Entry ids are unique, every fully qualified name (the whole path of an id or alias) identifies one entry and is not another entry's id, every namespace segment the manifest gives aliases to is some entry's segment and no alias is itself one, every crosswalk passes twindex's structural validation, every crosswalk file reference resolves, and every crosswalk's target is the category of each entry that uses it.
485
489
  */
486
490
  declare const CatalogueIndexSchema: z.ZodObject<{
487
491
  manifest: z.ZodObject<{
@@ -495,6 +499,7 @@ declare const CatalogueIndexSchema: z.ZodObject<{
495
499
  version: z.ZodString;
496
500
  repository: z.ZodURL;
497
501
  }, z.core.$strict>>;
502
+ namespaceAliases: z.ZodOptional<z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>, unknown>>;
498
503
  }, z.core.$strict> & {
499
504
  is: (value: unknown) => value is {
500
505
  id: string;
@@ -507,6 +512,7 @@ declare const CatalogueIndexSchema: z.ZodObject<{
507
512
  version: string;
508
513
  repository: string;
509
514
  } | undefined;
515
+ namespaceAliases?: Record<string, string[]> | undefined;
510
516
  };
511
517
  };
512
518
  crosswalks: z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodType<Crosswalk, unknown, z.core.$ZodTypeInternals<Crosswalk, unknown>> & {
@@ -731,6 +737,7 @@ declare const CatalogueIndexSchema: z.ZodObject<{
731
737
  version: string;
732
738
  repository: string;
733
739
  } | undefined;
740
+ namespaceAliases?: Record<string, string[]> | undefined;
734
741
  };
735
742
  crosswalks: Record<string, Crosswalk>;
736
743
  entries: {
@@ -841,12 +848,18 @@ interface DatasetQuery {
841
848
  readonly category?: string;
842
849
  /** The entry's domain, matched exactly. */
843
850
  readonly domain?: string;
844
- /** A namespace path such as `suitesparse` or `suitesparse/gset`: matches the entries in that namespace and every namespace beneath it. */
851
+ /** A group path such as `suitesparse` or `suitesparse/gset`, in which any segment may be an alias the manifest gives it: matches the entries in that group, in any group beneath it, and the entries a path alias files there. */
845
852
  readonly namespace?: string;
846
- /** Text that must appear, ignoring case, in the entry's id, an alias, its namespace, name, description, domain or one of its tags. */
853
+ /** Text that must appear, ignoring case, in the entry's id, an alias, one of its groups, name, description, domain or one of its tags. */
847
854
  readonly text?: string;
848
855
  }
849
- /** A queryable catalogue: the surface `listDatasets` and `getDataset` are bound to. */
856
+ /** One namespace in the tree of groups: its path, its last segment, and how many distinct entries sit in it or in any namespace beneath it. */
857
+ interface NamespaceNode {
858
+ readonly path: string;
859
+ readonly name: string;
860
+ readonly count: number;
861
+ }
862
+ /** A queryable catalogue: the surface `listDatasets`, `getDataset` and `listNamespaces` are bound to. */
850
863
  interface Catalogue {
851
864
  readonly manifest: Manifest;
852
865
  /**
@@ -863,6 +876,12 @@ interface Catalogue {
863
876
  * @throws DatasetNotFoundError when no entry answers to the name.
864
877
  */
865
878
  readonly getDataset: (name: string) => Dataset;
879
+ /**
880
+ * Lists the namespaces directly beneath a path, or the outermost ones, with how many entries each holds. The tree is every group of every entry and each path above it, so an entry filed in `a/b` counts toward `a/b` and `a`.
881
+ * @param path - A group path whose children to list, in which any segment may be an alias the manifest gives it; absent for the outermost namespaces.
882
+ * @returns The child namespaces in path order; empty when the path has none, whether it is unknown or a leaf.
883
+ */
884
+ readonly listNamespaces: (path?: string) => NamespaceNode[];
866
885
  }
867
886
  /**
868
887
  * Builds a queryable catalogue from an index. Pure and synchronous: no filesystem, no network, so it runs the same in a browser as in Node.
@@ -974,4 +993,4 @@ declare function listCategories(): readonly Category[];
974
993
  */
975
994
  declare function countTarget(id: CategoryId, target: CategoryTarget): ReadonlyMap<string, number>;
976
995
  //#endregion
977
- export { RelativePathSchema as $, SemVerSchema as A, Entry as B, Dataset as C, LineageSchema as D, Lineage as E, ArchiveSchema as F, LicenceSchema as G, HttpUrlSchema as H, Citation as I, Parser as J, Namespace as K, CitationSchema as L, Archive as M, ArchiveMember as N, Manifest as O, ArchiveMemberSchema as P, RecipeSchema as Q, CrosswalkReference as R, CatalogueIndexSchema as S, createCatalogue as T, IdSchema as U, EntrySchema as V, Licence as W, RESERVED_META_KEYS as X, ParserSchema as Y, Recipe as Z, GraphV1Schema as _, CategoryTargets as a, parseEntry as at, Catalogue as b, getCategory as c, listCategories as d, Snapshot as et, GraphEdge as f, GraphV1 as g, GraphNodeSchema as h, CategoryTarget as i, WaybackTimestampSchema as it, parseManifest as j, ManifestSchema as k, getTarget as l, GraphNode as m, CategoryId as n, Source as nt, CountedKey as o, waybackUrl as ot, GraphEdgeSchema as p, NamespaceSchema as q, CategoryIdSchema as r, SourceSchema as rt, countTarget as s, Category as t, SnapshotSchema as tt, kindOf as u, JsonScalar as v, DatasetQuery as w, CatalogueIndex as x, JsonScalarSchema as y, CrosswalkReferenceSchema as z };
996
+ export { Recipe as $, ManifestSchema as A, CrosswalkReference as B, Dataset as C, Lineage as D, createCatalogue as E, ArchiveMember as F, IdSchema as G, Entry as H, ArchiveMemberSchema as I, Namespace as J, Licence as K, ArchiveSchema as L, parseManifest as M, AliasSchema as N, LineageSchema as O, Archive as P, RESERVED_META_KEYS as Q, Citation as R, CatalogueIndexSchema as S, NamespaceNode as T, EntrySchema as U, CrosswalkReferenceSchema as V, HttpUrlSchema as W, Parser as X, NamespaceSchema as Y, ParserSchema as Z, GraphV1Schema as _, CategoryTargets as a, SourceSchema as at, Catalogue as b, getCategory as c, waybackUrl as ct, listCategories as d, RecipeSchema as et, GraphEdge as f, GraphV1 as g, GraphNodeSchema as h, CategoryTarget as i, Source as it, SemVerSchema as j, Manifest as k, getTarget as l, GraphNode as m, CategoryId as n, Snapshot as nt, CountedKey as o, WaybackTimestampSchema as ot, GraphEdgeSchema as p, LicenceSchema as q, CategoryIdSchema as r, SnapshotSchema as rt, countTarget as s, parseEntry as st, Category as t, RelativePathSchema as tt, kindOf as u, JsonScalar as v, DatasetQuery as w, CatalogueIndex as x, JsonScalarSchema as y, CitationSchema as z };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as RelativePathSchema, A as SemVerSchema, B as Entry, C as Dataset, D as LineageSchema, E as Lineage, F as ArchiveSchema, G as LicenceSchema, H as HttpUrlSchema, I as Citation, J as Parser, K as Namespace, L as CitationSchema, M as Archive, N as ArchiveMember, O as Manifest, P as ArchiveMemberSchema, Q as RecipeSchema, R as CrosswalkReference, S as CatalogueIndexSchema, T as createCatalogue, U as IdSchema, V as EntrySchema, W as Licence, X as RESERVED_META_KEYS, Y as ParserSchema, Z as Recipe, _ as GraphV1Schema, a as CategoryTargets, at as parseEntry, b as Catalogue, c as getCategory, d as listCategories, et as Snapshot, f as GraphEdge, g as GraphV1, h as GraphNodeSchema, i as CategoryTarget, it as WaybackTimestampSchema, j as parseManifest, k as ManifestSchema, l as getTarget, m as GraphNode, n as CategoryId, nt as Source, o as CountedKey, ot as waybackUrl, p as GraphEdgeSchema, q as NamespaceSchema, r as CategoryIdSchema, rt as SourceSchema, s as countTarget, t as Category, tt as SnapshotSchema, u as kindOf, v as JsonScalar, w as DatasetQuery, x as CatalogueIndex, y as JsonScalarSchema, z as CrosswalkReferenceSchema } from "./categories-BSJj6C-y.js";
1
+ import { $ as Recipe, A as ManifestSchema, B as CrosswalkReference, C as Dataset, D as Lineage, E as createCatalogue, F as ArchiveMember, G as IdSchema, H as Entry, I as ArchiveMemberSchema, J as Namespace, K as Licence, L as ArchiveSchema, M as parseManifest, N as AliasSchema, O as LineageSchema, P as Archive, Q as RESERVED_META_KEYS, R as Citation, S as CatalogueIndexSchema, T as NamespaceNode, U as EntrySchema, V as CrosswalkReferenceSchema, W as HttpUrlSchema, X as Parser, Y as NamespaceSchema, Z as ParserSchema, _ as GraphV1Schema, a as CategoryTargets, at as SourceSchema, b as Catalogue, c as getCategory, ct as waybackUrl, d as listCategories, et as RecipeSchema, f as GraphEdge, g as GraphV1, h as GraphNodeSchema, i as CategoryTarget, it as Source, j as SemVerSchema, k as Manifest, l as getTarget, m as GraphNode, n as CategoryId, nt as Snapshot, o as CountedKey, ot as WaybackTimestampSchema, p as GraphEdgeSchema, q as LicenceSchema, r as CategoryIdSchema, rt as SnapshotSchema, s as countTarget, st as parseEntry, t as Category, tt as RelativePathSchema, u as kindOf, v as JsonScalar, w as DatasetQuery, x as CatalogueIndex, y as JsonScalarSchema, z as CitationSchema } from "./categories-DzjLQ_fp.js";
2
2
  import { Crosswalk } from "twindex";
3
3
  //#region src/errors.d.ts
4
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. */
@@ -64,25 +64,42 @@ export declare function listDatasets(query?: DatasetQuery): Dataset[];
64
64
  * @throws DatasetNotFoundError when no entry has that id.
65
65
  */
66
66
  export declare function getDataset(id: string): Dataset;
67
+ /**
68
+ * Lists the namespaces directly beneath a path of the bundled catalogue, or the outermost ones, with how many entries each holds. Browser-safe.
69
+ * @param path - A group path whose children to list; absent for the outermost namespaces.
70
+ * @returns The child namespaces in path order.
71
+ */
72
+ export declare function listNamespaces(path?: string): NamespaceNode[];
67
73
  /** @returns The bundled catalogue's manifest: its id, version and repository. */
68
74
  export declare function getManifest(): Manifest;
69
75
  //#endregion
70
76
  //#region src/names.d.ts
71
77
  /** The character that joins a namespace's segments to each other and to a name. It is not in an id's alphabet, so a name containing it is always a path and never a literal id. */
72
78
  export declare const NAME_SEPARATOR = "/";
79
+ /** The other names namespace segments go by, as a catalogue manifest declares them: a segment mapped to its aliases. */
80
+ type SegmentAliases = Readonly<Record<string, readonly string[]>>;
73
81
  /** The names an entry answers to: its fully qualified names, which must each identify it alone, and every name that resolves to it, which also includes the shorter forms. */
74
82
  interface EntryNames {
75
- /** The whole namespace joined to each leaf, or the bare leaf for an entry with no namespace. */
83
+ /** Each address written out in full: the whole path of an address, with no segment alias substituted. */
76
84
  readonly full: readonly string[];
77
- /** Every suffix of the namespace joined to each leaf, the fully qualified name and the bare leaf among them. */
85
+ /** Every trailing run of each address's path, the whole path and the bare name among them, with every combination of segment aliases for the namespace part. */
78
86
  readonly all: readonly string[];
79
87
  }
88
+ /** The entry fields that give it addresses and groups. */
89
+ type Addressed = Pick<Entry, "id" | "namespace" | "aliases">;
90
+ /**
91
+ * The groups an entry belongs to: the namespace its id and bare aliases sit in, and the group each alias that is a path names, each once, in order of first appearance. A group is a path of segments; an entry belongs to every group above it as well, which the caller reads off the path.
92
+ * @param entry - The entry.
93
+ * @returns The groups, empty for an entry with no namespace and no path aliases.
94
+ */
95
+ export declare function groupsOf(entry: Addressed): string[][];
80
96
  /**
81
- * Works out the names an entry answers to. A leaf is the entry's id or one of its aliases; a name is a leaf with any trailing run of the entry's namespace segments before it, so an entry in namespace `a/b` with id `c` answers to `c`, `b/c` and `a/b/c`.
97
+ * Works out the names an entry answers to. Each address is a path of segments with the name last, and an address answers to every trailing run of its path, so id `c` in namespace `a/b` answers to `c`, `b/c` and `a/b/c`, and the alias `x/y/d` answers to `d`, `y/d` and `x/y/d`. Where the manifest gives a segment aliases, an address also answers with the alias in the segment's place.
82
98
  * @param entry - The entry: only its id, namespace and aliases are read.
99
+ * @param segmentAliases - The manifest's aliases for namespace segments, if any.
83
100
  * @returns The fully qualified names and every name that resolves to the entry.
84
101
  */
85
- export declare function namesOf(entry: Pick<Entry, "id" | "namespace" | "aliases">): EntryNames;
102
+ export declare function namesOf(entry: Addressed, segmentAliases?: SegmentAliases): EntryNames;
86
103
  //#endregion
87
104
  //#region src/serialise.d.ts
88
105
  /**
@@ -100,4 +117,4 @@ export declare function serialise(doc: Entry | Manifest | Crosswalk): string;
100
117
  */
101
118
  export declare function validateDataset(dataset: Dataset): SyntaxIssue[];
102
119
  //#endregion
103
- export { type Archive, type ArchiveMember, ArchiveMemberSchema, ArchiveSchema, type Catalogue, type CatalogueIndex, CatalogueIndexSchema, type Category, type CategoryId, CategoryIdSchema, type CategoryTarget, type CategoryTargets, type Citation, CitationSchema, type CountedKey, type CrosswalkReference, CrosswalkReferenceSchema, type Dataset, type DatasetQuery, type Entry, type EntryNames, EntrySchema, type GraphEdge, GraphEdgeSchema, type GraphNode, GraphNodeSchema, type GraphV1, GraphV1Schema, HttpUrlSchema, IdSchema, type JsonScalar, JsonScalarSchema, type Licence, LicenceSchema, type Lineage, LineageSchema, type Manifest, ManifestSchema, type Namespace, NamespaceSchema, type Parser, ParserSchema, RESERVED_META_KEYS, type Recipe, RecipeSchema, type RecipeStep, RelativePathSchema, SemVerSchema, type Snapshot, SnapshotSchema, type Source, SourceSchema, type SyntaxIssue, WaybackTimestampSchema, countTarget, createCatalogue, getCategory, getTarget, kindOf, listCategories, parseEntry, parseManifest, waybackUrl };
120
+ export { AliasSchema, type Archive, type ArchiveMember, ArchiveMemberSchema, ArchiveSchema, type Catalogue, type CatalogueIndex, CatalogueIndexSchema, type Category, type CategoryId, CategoryIdSchema, type CategoryTarget, type CategoryTargets, type Citation, CitationSchema, type CountedKey, type CrosswalkReference, CrosswalkReferenceSchema, type Dataset, type DatasetQuery, type Entry, type EntryNames, EntrySchema, type GraphEdge, GraphEdgeSchema, type GraphNode, GraphNodeSchema, type GraphV1, GraphV1Schema, HttpUrlSchema, IdSchema, type JsonScalar, JsonScalarSchema, type Licence, LicenceSchema, type Lineage, LineageSchema, type Manifest, ManifestSchema, type Namespace, type NamespaceNode, NamespaceSchema, type Parser, ParserSchema, RESERVED_META_KEYS, type Recipe, RecipeSchema, type RecipeStep, RelativePathSchema, type SegmentAliases, SemVerSchema, type Snapshot, SnapshotSchema, type Source, SourceSchema, type SyntaxIssue, WaybackTimestampSchema, countTarget, createCatalogue, getCategory, getTarget, kindOf, listCategories, parseEntry, parseManifest, waybackUrl };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as toIssues, B as GraphV1Schema, C as RESERVED_META_KEYS, D as SourceSchema, E as SnapshotSchema, F as getTarget, G as DatasetNotFoundError, H as ArchiveError, I as kindOf, J as RecipeError, K as KeepsetError, L as listCategories, M as CategoryIdSchema, N as countTarget, O as WaybackTimestampSchema, P as getCategory, R as GraphEdgeSchema, S as ParserSchema, T as RelativePathSchema, U as CatalogueSyntaxError, V as JsonScalarSchema, W as DatasetAmbiguousError, Y as UnknownCategoryError, _ as EntrySchema, a as createCatalogue, b as LicenceSchema, c as namesOf, d as SemVerSchema, f as parseManifest, g as CrosswalkReferenceSchema, h as CitationSchema, i as CatalogueIndexSchema, j as waybackUrl, k as parseEntry, l as LineageSchema, m as ArchiveSchema, n as getManifest, p as ArchiveMemberSchema, q as NativeParseError, r as listDatasets, s as NAME_SEPARATOR, t as getDataset, u as ManifestSchema, v as HttpUrlSchema, w as RecipeSchema, x as NamespaceSchema, y as IdSchema, z as GraphNodeSchema } from "./bundled-DaXJpk0C.js";
1
+ import { A as waybackUrl, B as listCategories, C as RecipeSchema, D as WaybackTimestampSchema, E as SourceSchema, F as CategoryIdSchema, G as ArchiveError, H as GraphNodeSchema, I as countTarget, J as DatasetNotFoundError, K as CatalogueSyntaxError, L as getCategory, M as groupsOf, N as namesOf, O as parseEntry, Q as UnknownCategoryError, R as getTarget, S as RESERVED_META_KEYS, T as SnapshotSchema, U as GraphV1Schema, V as GraphEdgeSchema, W as JsonScalarSchema, X as NativeParseError, Y as KeepsetError, Z as RecipeError, _ as HttpUrlSchema, a as CatalogueIndexSchema, b as NamespaceSchema, c as ManifestSchema, d as AliasSchema, f as ArchiveMemberSchema, g as EntrySchema, h as CrosswalkReferenceSchema, i as listNamespaces, j as NAME_SEPARATOR, k as toIssues, l as SemVerSchema, m as CitationSchema, n as getManifest, o as createCatalogue, p as ArchiveSchema, q as DatasetAmbiguousError, r as listDatasets, s as LineageSchema, t as getDataset, u as parseManifest, v as IdSchema, w as RelativePathSchema, x as ParserSchema, y as LicenceSchema, z as kindOf } from "./bundled-DNLiwEcd.js";
2
2
  import { validateCrosswalk } from "twindex";
3
3
  //#region src/serialise.ts
4
4
  /**
@@ -57,4 +57,4 @@ function validateDataset(dataset) {
57
57
  return issues;
58
58
  }
59
59
  //#endregion
60
- export { ArchiveError, ArchiveMemberSchema, ArchiveSchema, CatalogueIndexSchema, CatalogueSyntaxError, CategoryIdSchema, CitationSchema, CrosswalkReferenceSchema, DatasetAmbiguousError, DatasetNotFoundError, EntrySchema, GraphEdgeSchema, GraphNodeSchema, GraphV1Schema, HttpUrlSchema, IdSchema, JsonScalarSchema, KeepsetError, LicenceSchema, LineageSchema, ManifestSchema, NAME_SEPARATOR, NamespaceSchema, NativeParseError, ParserSchema, RESERVED_META_KEYS, RecipeError, RecipeSchema, RelativePathSchema, SemVerSchema, SnapshotSchema, SourceSchema, UnknownCategoryError, WaybackTimestampSchema, countTarget, createCatalogue, getCategory, getDataset, getManifest, getTarget, kindOf, listCategories, listDatasets, namesOf, parseEntry, parseManifest, serialise, validateDataset, waybackUrl };
60
+ export { AliasSchema, ArchiveError, ArchiveMemberSchema, ArchiveSchema, CatalogueIndexSchema, CatalogueSyntaxError, CategoryIdSchema, CitationSchema, CrosswalkReferenceSchema, DatasetAmbiguousError, DatasetNotFoundError, EntrySchema, GraphEdgeSchema, GraphNodeSchema, GraphV1Schema, HttpUrlSchema, IdSchema, JsonScalarSchema, KeepsetError, LicenceSchema, LineageSchema, ManifestSchema, NAME_SEPARATOR, NamespaceSchema, NativeParseError, ParserSchema, RESERVED_META_KEYS, RecipeError, RecipeSchema, RelativePathSchema, SemVerSchema, SnapshotSchema, SourceSchema, UnknownCategoryError, WaybackTimestampSchema, countTarget, createCatalogue, getCategory, getDataset, getManifest, getTarget, groupsOf, kindOf, listCategories, listDatasets, listNamespaces, namesOf, parseEntry, parseManifest, serialise, validateDataset, waybackUrl };
@@ -1,4 +1,4 @@
1
- import { B as Entry, C as Dataset, et as Snapshot, i as CategoryTarget, nt as Source } from "../categories-BSJj6C-y.js";
1
+ import { C as Dataset, H as Entry, i as CategoryTarget, it as Source, nt as Snapshot } from "../categories-DzjLQ_fp.js";
2
2
  //#region src/node/fetch-dataset.d.ts
3
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
4
  export type FetchPort = (url: string, init: {
@@ -1,4 +1,4 @@
1
- import { F as getTarget, H as ArchiveError, J as RecipeError, K as KeepsetError, N as countTarget, o as getOwn, q as NativeParseError, t as getDataset } from "../bundled-DaXJpk0C.js";
1
+ import { G as ArchiveError, I as countTarget, P as getOwn, R as getTarget, X as NativeParseError, Y as KeepsetError, Z as RecipeError, t as getDataset } from "../bundled-DNLiwEcd.js";
2
2
  import { RoundTripMismatchError, assertRoundTrip, encode } from "twindex";
3
3
  import { createHash } from "node:crypto";
4
4
  import { gunzipSync } from "node:zlib";
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { writeFileSync } from "node:fs";
3
- import { KeepsetError, NAME_SEPARATOR, countTarget, getDataset, listDatasets, validateDataset } from "keepset";
3
+ import { KeepsetError, NAME_SEPARATOR, countTarget, getDataset, listDatasets, listNamespaces, validateDataset } from "keepset";
4
4
  import { fetchDataset, roundTripDataset } from "keepset/node";
5
5
  import { parseArgs } from "node:util";
6
6
  //#region package.json
7
- var version = "0.11.0";
7
+ var version = "0.13.0";
8
8
  const usage = `Usage: keepset <command> [options]
9
9
 
10
10
  Commands:
@@ -12,8 +12,12 @@ Commands:
12
12
  [--category <id>] [--json]
13
13
  list the datasets whose id, alias, namespace, name,
14
14
  description, domain or tags contain the text, filtered by
15
- kind, domain, namespace (and everything beneath it) or
16
- category
15
+ kind, domain, namespace (and every namespace beneath it,
16
+ segment aliases allowed) or category
17
+ namespaces [path] [--json]
18
+ list the namespaces directly beneath a path, or the
19
+ outermost ones, each with how many datasets it holds,
20
+ tab-separated
17
21
  show <dataset> [--json]
18
22
  print a dataset's metadata, recipe and crosswalk
19
23
  fetch <dataset> [--out <file>]
@@ -36,7 +40,7 @@ Options:
36
40
 
37
41
  Exit status: 0 for a result, a valid entry or a round trip that holds; 1 for an
38
42
  invalid entry or a round trip that does not hold; 2 when there is no answer:
39
- bad usage, an unknown or ambiguous dataset or a recipe that failed.
43
+ bad usage, an unknown or ambiguous dataset or namespace or a recipe that failed.
40
44
  `;
41
45
  function parseCommandLine(args) {
42
46
  return parseArgs({
@@ -78,6 +82,14 @@ function formatSearch(datasets) {
78
82
  return datasets.map(({ entry }) => `${entry.id}\t${entry.name}\t${entry.category}\t${entry.recipe.status}\n`).join("");
79
83
  }
80
84
  /**
85
+ * One line per namespace for `namespaces`: path and the number of datasets it holds, tab-separated so the output pipes into other tools.
86
+ * @param nodes - The namespaces.
87
+ * @returns The text.
88
+ */
89
+ function formatNamespaces(nodes) {
90
+ return nodes.map((node) => `${node.path}\t${String(node.count)}\n`).join("");
91
+ }
92
+ /**
81
93
  * The human-readable form of `show`: the descriptor, sources, licence, citations, recipe and crosswalk rules, one labelled section after another.
82
94
  * @param dataset - The dataset.
83
95
  * @returns The text.
@@ -183,6 +195,17 @@ async function run(args, library, io) {
183
195
  io.writeStdout(values.json === true ? json(datasets.map((dataset) => dataset.entry)) : formatSearch(datasets));
184
196
  return 0;
185
197
  }
198
+ if (command === "namespaces") {
199
+ if (rest.length > 1) return reject("namespaces takes at most one path");
200
+ const [path] = rest;
201
+ if (path !== void 0 && library.listDatasets({ namespace: path }).length === 0) {
202
+ io.writeStderr(`no namespace ${JSON.stringify(path)} in the catalogue\n`);
203
+ return 2;
204
+ }
205
+ const nodes = library.listNamespaces(path);
206
+ io.writeStdout(values.json === true ? json(nodes) : formatNamespaces(nodes));
207
+ return 0;
208
+ }
186
209
  const [name, ...extra] = rest;
187
210
  if (command === void 0) return reject("expected a command");
188
211
  if (![
@@ -240,6 +263,7 @@ process.stdout.on("error", (error) => {
240
263
  process.exitCode = await run(process.argv.slice(2), {
241
264
  listDatasets,
242
265
  getDataset,
266
+ listNamespaces,
243
267
  validateDataset,
244
268
  fetchDataset: async (id) => fetchDataset(id),
245
269
  roundTripDataset: async (id) => roundTripDataset(id),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "keepset",
3
3
  "description": "Library, CLI and statically generated catalogue for dataset fetch-and-normalise recipes",
4
- "version": "0.11.0",
4
+ "version": "0.13.0",
5
5
  "bin": {
6
6
  "keepset": "./dist-cli/keepset.mjs"
7
7
  },
@@ -1 +1 @@
1
- {"$defs":{"Crosswalk":{"$ref":"https://cdn.jsdelivr.net/npm/twindex@0.1.1/schemas/twindex.schema.json"},"Entry":{"additionalProperties":false,"properties":{"aliases":{"items":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"minItems":1,"type":"array"},"category":{"enum":["graph/v1"],"type":"string"},"citations":{"items":{"additionalProperties":false,"properties":{"doi":{"pattern":"^10\\.\\d{4,9}\\/\\S+$","type":"string"},"text":{"minLength":1,"type":"string"},"url":{"format":"uri","type":"string"}},"required":["text"],"type":"object"},"type":"array"},"crosswalk":{"anyOf":[{"$ref":"#/$defs/Crosswalk"},{"additionalProperties":false,"properties":{"file":{"type":"string"}},"required":["file"],"type":"object"}]},"description":{"minLength":1,"type":"string"},"domain":{"minLength":1,"type":"string"},"expectedCounts":{"additionalProperties":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"propertyNames":{"pattern":"^[A-Za-z][A-Za-z0-9]*$","type":"string"},"type":"object"},"id":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"licence":{"oneOf":[{"additionalProperties":false,"properties":{"id":{"pattern":"^[A-Za-z0-9][A-Za-z0-9.+-]*$","type":"string"},"kind":{"const":"spdx","type":"string"}},"required":["kind","id"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"custom","type":"string"},"name":{"minLength":1,"type":"string"},"url":{"format":"uri","type":"string"}},"required":["kind","name","url"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"unrecorded","type":"string"}},"required":["kind"],"type":"object"}]},"name":{"minLength":1,"type":"string"},"namespace":{"items":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"minItems":1,"type":"array"},"properties":{"additionalProperties":{"type":["string","number","boolean","null"]},"propertyNames":{"pattern":"^[A-Za-z][A-Za-z0-9]*$","type":"string"},"type":"object"},"recipe":{"oneOf":[{"additionalProperties":false,"properties":{"archive":{"oneOf":[{"additionalProperties":false,"properties":{"kind":{"const":"none","type":"string"}},"required":["kind"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"gzip","type":"string"}},"required":["kind"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"zip","type":"string"},"member":{"anyOf":[{"additionalProperties":false,"properties":{"path":{"type":"string"}},"required":["path"],"type":"object"},{"additionalProperties":false,"properties":{"extensions":{"items":{"pattern":"^\\.[A-Za-z0-9][A-Za-z0-9.]*$","type":"string"},"minItems":1,"type":"array"}},"required":["extensions"],"type":"object"}]}},"required":["kind","member"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"tar","type":"string"},"member":{"anyOf":[{"additionalProperties":false,"properties":{"path":{"type":"string"}},"required":["path"],"type":"object"},{"additionalProperties":false,"properties":{"extensions":{"items":{"pattern":"^\\.[A-Za-z0-9][A-Za-z0-9.]*$","type":"string"},"minItems":1,"type":"array"}},"required":["extensions"],"type":"object"}]}},"required":["kind","member"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"tar.gz","type":"string"},"member":{"anyOf":[{"additionalProperties":false,"properties":{"path":{"type":"string"}},"required":["path"],"type":"object"},{"additionalProperties":false,"properties":{"extensions":{"items":{"pattern":"^\\.[A-Za-z0-9][A-Za-z0-9.]*$","type":"string"},"minItems":1,"type":"array"}},"required":["extensions"],"type":"object"}]}},"required":["kind","member"],"type":"object"}]},"parser":{"oneOf":[{"additionalProperties":false,"properties":{"commentPrefixes":{"items":{"minLength":1,"type":"string"},"type":"array"},"delimiter":{"enum":["detect","whitespace","tab","comma","space"],"type":"string"},"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"edge-list","type":"string"},"skipLines":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"sourceColumn":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"targetColumn":{"maximum":9007199254740991,"minimum":0,"type":"integer"}},"required":["format","encoding","delimiter","skipLines","commentPrefixes","sourceColumn","targetColumn"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"matrix-market","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"gml","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"pajek","type":"string"}},"required":["format","encoding"],"type":"object"}]},"source":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"status":{"const":"fetchable","type":"string"}},"required":["status","source","archive","parser"],"type":"object"},{"additionalProperties":false,"properties":{"reason":{"minLength":1,"type":"string"},"status":{"const":"not-fetchable","type":"string"}},"required":["status","reason"],"type":"object"}]},"sources":{"items":{"additionalProperties":false,"properties":{"retrieved":{"format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$","type":"string"},"snapshot":{"additionalProperties":false,"properties":{"timestamp":{"pattern":"^\\d{8}(?:\\d{6})?$","type":"string"},"url":{"format":"uri","type":"string"}},"required":["url","timestamp"],"type":"object"},"status":{"enum":["live","dead"],"type":"string"},"url":{"format":"uri","type":"string"}},"required":["url","retrieved","status"],"type":"object"},"minItems":1,"type":"array"},"tags":{"items":{"minLength":1,"type":"string"},"type":"array"}},"required":["id","name","tags","category","sources","licence","citations","recipe","crosswalk","expectedCounts","properties"],"type":"object"}},"$id":"https://cdn.jsdelivr.net/npm/keepset@0.11.0/schemas/entry.schema.json","$ref":"#/$defs/Entry","$schema":"https://json-schema.org/draft/2020-12/schema"}
1
+ {"$defs":{"Crosswalk":{"$ref":"https://cdn.jsdelivr.net/npm/twindex@0.1.1/schemas/twindex.schema.json"},"Entry":{"additionalProperties":false,"properties":{"aliases":{"items":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*(?:\\/[A-Za-z0-9][A-Za-z0-9._-]*)*$","type":"string"},"minItems":1,"type":"array"},"category":{"enum":["graph/v1"],"type":"string"},"citations":{"items":{"additionalProperties":false,"properties":{"doi":{"pattern":"^10\\.\\d{4,9}\\/\\S+$","type":"string"},"text":{"minLength":1,"type":"string"},"url":{"format":"uri","type":"string"}},"required":["text"],"type":"object"},"type":"array"},"crosswalk":{"anyOf":[{"$ref":"#/$defs/Crosswalk"},{"additionalProperties":false,"properties":{"file":{"type":"string"}},"required":["file"],"type":"object"}]},"description":{"minLength":1,"type":"string"},"domain":{"minLength":1,"type":"string"},"expectedCounts":{"additionalProperties":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"propertyNames":{"pattern":"^[A-Za-z][A-Za-z0-9]*$","type":"string"},"type":"object"},"id":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"licence":{"oneOf":[{"additionalProperties":false,"properties":{"id":{"pattern":"^[A-Za-z0-9][A-Za-z0-9.+-]*$","type":"string"},"kind":{"const":"spdx","type":"string"}},"required":["kind","id"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"custom","type":"string"},"name":{"minLength":1,"type":"string"},"url":{"format":"uri","type":"string"}},"required":["kind","name","url"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"unrecorded","type":"string"}},"required":["kind"],"type":"object"}]},"name":{"minLength":1,"type":"string"},"namespace":{"items":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"minItems":1,"type":"array"},"properties":{"additionalProperties":{"type":["string","number","boolean","null"]},"propertyNames":{"pattern":"^[A-Za-z][A-Za-z0-9]*$","type":"string"},"type":"object"},"recipe":{"oneOf":[{"additionalProperties":false,"properties":{"archive":{"oneOf":[{"additionalProperties":false,"properties":{"kind":{"const":"none","type":"string"}},"required":["kind"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"gzip","type":"string"}},"required":["kind"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"zip","type":"string"},"member":{"anyOf":[{"additionalProperties":false,"properties":{"path":{"type":"string"}},"required":["path"],"type":"object"},{"additionalProperties":false,"properties":{"extensions":{"items":{"pattern":"^\\.[A-Za-z0-9][A-Za-z0-9.]*$","type":"string"},"minItems":1,"type":"array"}},"required":["extensions"],"type":"object"}]}},"required":["kind","member"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"tar","type":"string"},"member":{"anyOf":[{"additionalProperties":false,"properties":{"path":{"type":"string"}},"required":["path"],"type":"object"},{"additionalProperties":false,"properties":{"extensions":{"items":{"pattern":"^\\.[A-Za-z0-9][A-Za-z0-9.]*$","type":"string"},"minItems":1,"type":"array"}},"required":["extensions"],"type":"object"}]}},"required":["kind","member"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"tar.gz","type":"string"},"member":{"anyOf":[{"additionalProperties":false,"properties":{"path":{"type":"string"}},"required":["path"],"type":"object"},{"additionalProperties":false,"properties":{"extensions":{"items":{"pattern":"^\\.[A-Za-z0-9][A-Za-z0-9.]*$","type":"string"},"minItems":1,"type":"array"}},"required":["extensions"],"type":"object"}]}},"required":["kind","member"],"type":"object"}]},"parser":{"oneOf":[{"additionalProperties":false,"properties":{"commentPrefixes":{"items":{"minLength":1,"type":"string"},"type":"array"},"delimiter":{"enum":["detect","whitespace","tab","comma","space"],"type":"string"},"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"edge-list","type":"string"},"skipLines":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"sourceColumn":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"targetColumn":{"maximum":9007199254740991,"minimum":0,"type":"integer"}},"required":["format","encoding","delimiter","skipLines","commentPrefixes","sourceColumn","targetColumn"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"matrix-market","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"gml","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"pajek","type":"string"}},"required":["format","encoding"],"type":"object"}]},"source":{"maximum":9007199254740991,"minimum":0,"type":"integer"},"status":{"const":"fetchable","type":"string"}},"required":["status","source","archive","parser"],"type":"object"},{"additionalProperties":false,"properties":{"reason":{"minLength":1,"type":"string"},"status":{"const":"not-fetchable","type":"string"}},"required":["status","reason"],"type":"object"}]},"sources":{"items":{"additionalProperties":false,"properties":{"retrieved":{"format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$","type":"string"},"snapshot":{"additionalProperties":false,"properties":{"timestamp":{"pattern":"^\\d{8}(?:\\d{6})?$","type":"string"},"url":{"format":"uri","type":"string"}},"required":["url","timestamp"],"type":"object"},"status":{"enum":["live","dead"],"type":"string"},"url":{"format":"uri","type":"string"}},"required":["url","retrieved","status"],"type":"object"},"minItems":1,"type":"array"},"tags":{"items":{"minLength":1,"type":"string"},"type":"array"}},"required":["id","name","tags","category","sources","licence","citations","recipe","crosswalk","expectedCounts","properties"],"type":"object"}},"$id":"https://cdn.jsdelivr.net/npm/keepset@0.13.0/schemas/entry.schema.json","$ref":"#/$defs/Entry","$schema":"https://json-schema.org/draft/2020-12/schema"}
@@ -1 +1 @@
1
- {"$defs":{"Manifest":{"additionalProperties":false,"properties":{"description":{"minLength":1,"type":"string"},"id":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"lineage":{"additionalProperties":false,"properties":{"catalogue":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"repository":{"format":"uri","type":"string"},"version":{"pattern":"^(?: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-]+)*)?$","type":"string"}},"required":["catalogue","version","repository"],"type":"object"},"name":{"minLength":1,"type":"string"},"repository":{"format":"uri","type":"string"},"version":{"pattern":"^(?: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-]+)*)?$","type":"string"}},"required":["id","name","description","version","repository"],"type":"object"}},"$id":"https://cdn.jsdelivr.net/npm/keepset@0.11.0/schemas/manifest.schema.json","$ref":"#/$defs/Manifest","$schema":"https://json-schema.org/draft/2020-12/schema"}
1
+ {"$defs":{"Manifest":{"additionalProperties":false,"properties":{"description":{"minLength":1,"type":"string"},"id":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"lineage":{"additionalProperties":false,"properties":{"catalogue":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"repository":{"format":"uri","type":"string"},"version":{"pattern":"^(?: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-]+)*)?$","type":"string"}},"required":["catalogue","version","repository"],"type":"object"},"name":{"minLength":1,"type":"string"},"namespaceAliases":{"additionalProperties":{"items":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"minItems":1,"type":"array"},"propertyNames":{"pattern":"^[A-Za-z0-9][A-Za-z0-9._-]*$","type":"string"},"type":"object"},"repository":{"format":"uri","type":"string"},"version":{"pattern":"^(?: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-]+)*)?$","type":"string"}},"required":["id","name","description","version","repository"],"type":"object"}},"$id":"https://cdn.jsdelivr.net/npm/keepset@0.13.0/schemas/manifest.schema.json","$ref":"#/$defs/Manifest","$schema":"https://json-schema.org/draft/2020-12/schema"}