keepset 0.23.0 → 0.24.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
@@ -63,7 +63,7 @@ The catalogue itself is a manifest (`catalogue/manifest.json`: id, version, repo
63
63
 
64
64
  ## Categories and normalisation targets
65
65
 
66
- A category names a normalisation target: the shape every entry in that category resolves to, whatever its native shape. `graph/v1` is `{ meta, nodes, edges }` with unique node ids and edges whose endpoints name nodes; its entries are graph datasets taken from upstream collections such as SuiteSparse, Netzschleuder, SNAP, KONECT and OGB. `or/v1` is `{ meta, nodes }`, an operations-research instance as the nodes it offers to visit, each flat with optional coordinates, prize, prize probability, duration, travel time, skill, territory, lifecycle status and times, and deadline, read from tabular sources by the `csv-table` parser (a delimited file, a member of an archive, a sheet of an Excel workbook, or an open data portal read a page at a time) and from ArcGIS feature layers by the `geojson` parser. Its entries come from a survey of open operations-research datasets, imported with a curation that records, for each source, what its download gives and how its columns map onto the node; a dataset whose source has no usable table is imported without a recipe and says why. A new category starts by defining its target schema in `src/targets/` and registering it in the category registry; an entry cannot name a category that does not exist. The statically generated site renders every entry with schema.org Dataset JSON-LD, so the catalogue surfaces in the dataset discovery layers.
66
+ A category names a normalisation target: the shape every entry in that category resolves to, whatever its native shape. `graph/v1` is `{ meta, nodes, edges }` with unique node ids and edges whose endpoints name nodes; its entries are graph datasets taken from upstream collections such as SuiteSparse, Netzschleuder, SNAP, KONECT and OGB. `or/v1` is `{ meta, nodes }`, an operations-research instance as the nodes it offers to visit, each flat with optional coordinates, prize, prize probability, duration, travel time, skill, territory, lifecycle status and times, and deadline, read from tabular sources by the `csv-table` parser (a delimited file, a member of an archive, a sheet of an Excel workbook, or an open data portal read a page at a time) and from ArcGIS feature layers by the `geojson` parser. Its entries come from a survey of open operations-research datasets, imported with a curation that records, for each source, what its download gives and how its columns map onto the node; a dataset whose source has no usable table is imported without a recipe and says why. `or-set/v1` is `{ meta, instances }`, a collection of operations-research instances such as a benchmark suite, each instance an `or/v1` document under an id of its own (for a collection read from an archive, the path of the member it came from): a recipe selects every matching archive member with `members`, the parser reads each one, and a member that does not parse fails the fetch and is named. A new category starts by defining its target schema in `src/targets/` and registering it in the category registry; an entry cannot name a category that does not exist. The statically generated site renders every entry with schema.org Dataset JSON-LD, so the catalogue surfaces in the dataset discovery layers.
67
67
 
68
68
  ## Licensing
69
69
 
@@ -113,14 +113,16 @@ const JsonScalarSchema = z.union([
113
113
  * Reports each node whose id an earlier node already has, as an issue on that node's id.
114
114
  * @param nodes - The nodes of a target document, in order.
115
115
  * @param ctx - The refinement context to report to.
116
+ * @param prefix - The path of the object holding the `nodes` array, for a document that nests nodes inside another.
116
117
  * @returns The set of ids the nodes carry.
117
118
  */
118
- function addDuplicateIdIssues(nodes, ctx) {
119
+ function addDuplicateIdIssues(nodes, ctx, prefix = []) {
119
120
  const ids = /* @__PURE__ */ new Set();
120
121
  for (const [index, node] of nodes.entries()) {
121
122
  if (ids.has(node.id)) ctx.addIssue({
122
123
  code: "custom",
123
124
  path: [
125
+ ...prefix,
124
126
  "nodes",
125
127
  index,
126
128
  "id"
@@ -202,22 +204,65 @@ const OrV1Schema = define(z.object({
202
204
  addDuplicateIdIssues(instance.nodes, ctx);
203
205
  }));
204
206
  //#endregion
207
+ //#region src/targets/or-set-v1.ts
208
+ /** One instance of a set: an or/v1 instance, `{ meta, nodes }`, with the id that names it within the set (for a collection read from an archive, the member's path). */
209
+ const OrInstanceSchema = z.object({
210
+ id: z.string().min(1),
211
+ meta: recordOf(z.string().min(1), JsonScalarSchema),
212
+ nodes: z.array(OrNodeSchema)
213
+ }).strict();
214
+ /**
215
+ * The or-set/v1 normalisation target: `{ meta, instances }`, a collection of operations-research instances such as a benchmark suite, each instance an or/v1 document (`{ meta, nodes }`) under an id of its own. Beyond the shapes it checks that instance ids are unique in the set and node ids unique within each instance. A node id need not be unique across instances, because every instance numbers its own nodes.
216
+ */
217
+ const OrSetV1Schema = define(z.object({
218
+ meta: recordOf(z.string().min(1), JsonScalarSchema),
219
+ instances: z.array(OrInstanceSchema)
220
+ }).strict().superRefine((set, ctx) => {
221
+ const ids = /* @__PURE__ */ new Set();
222
+ for (const [index, instance] of set.instances.entries()) {
223
+ if (ids.has(instance.id)) ctx.addIssue({
224
+ code: "custom",
225
+ path: [
226
+ "instances",
227
+ index,
228
+ "id"
229
+ ],
230
+ message: `duplicate instance id ${JSON.stringify(instance.id)}`
231
+ });
232
+ ids.add(instance.id);
233
+ addDuplicateIdIssues(instance.nodes, ctx, ["instances", index]);
234
+ }
235
+ }));
236
+ //#endregion
205
237
  //#region src/categories.ts
206
238
  /** 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. */
207
- const CategoryIdSchema = z.enum(["graph/v1", "or/v1"]);
239
+ const CategoryIdSchema = z.enum([
240
+ "graph/v1",
241
+ "or/v1",
242
+ "or-set/v1"
243
+ ]);
208
244
  /** 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. */
209
245
  const registry = {
210
246
  "graph/v1": {
211
247
  id: "graph/v1",
212
248
  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.",
249
+ shape: "document",
213
250
  target: GraphV1Schema,
214
251
  counted: ["nodes", "edges"]
215
252
  },
216
253
  "or/v1": {
217
254
  id: "or/v1",
218
255
  description: "An operations-research instance as { meta, nodes }: a flat record of scalar metadata and nodes carrying at least an id, with optional coordinates, prize, prize probability, duration, travel time, skill, territory, lifecycle status and times, and deadline.",
256
+ shape: "document",
219
257
  target: OrV1Schema,
220
258
  counted: ["nodes"]
259
+ },
260
+ "or-set/v1": {
261
+ id: "or-set/v1",
262
+ description: "A collection of operations-research instances as { meta, instances }: a flat record of scalar metadata and instances, each an or/v1 document ({ meta, nodes }) under an id of its own, read from every matching member of an archive.",
263
+ shape: "collection",
264
+ target: OrSetV1Schema,
265
+ counted: ["instances"]
221
266
  }
222
267
  };
223
268
  function getCategory(id) {
@@ -410,22 +455,26 @@ const CitationSchema = z.object({
410
455
  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 '..'" });
411
456
  /** 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 one of a list of extensions (a format can have several, such as Pajek's `.net` and `.paj`). Either must match exactly one member when the recipe runs; macOS resource-fork members (`__MACOSX/` and `._` files) never match. */
412
457
  const ArchiveMemberSchema = z.union([z.object({ path: RelativePathSchema }).strict(), z.object({ extensions: z.array(z.string().regex(/^\.[A-Za-z0-9][A-Za-z0-9.]*$/, { message: "must be a file extension such as .mtx" })).min(1) }).strict()]);
413
- /** How the downloaded bytes unpack into the one file the parser reads. A workbook (`xlsx`) is read as the CSV of its named sheet, with dates as ISO 8601 text, for the `csv-table` parser. */
414
- const ArchiveSchema = z.discriminatedUnion("kind", [
458
+ /** The archive kinds that hold several files, from which a recipe selects one (`member`) or every one a selector matches (`members`). */
459
+ const FileArchiveKindSchema = z.enum([
460
+ "zip",
461
+ "tar",
462
+ "tar.gz"
463
+ ]);
464
+ /** An archive a collection is read from: every file a selector matches, at least one. */
465
+ const CollectionArchiveSchema = z.object({
466
+ kind: FileArchiveKindSchema,
467
+ members: ArchiveMemberSchema
468
+ }).strict();
469
+ /** How the downloaded bytes unpack into the file the parser reads, or for a collection the files it reads one by one. A `members` selector gives every member it matches, at least one, in path order. A workbook (`xlsx`) is read as the CSV of its named sheet, with dates as ISO 8601 text, for the `csv-table` parser. */
470
+ const ArchiveSchema = z.union([
415
471
  z.object({ kind: z.literal("none") }).strict(),
416
472
  z.object({ kind: z.literal("gzip") }).strict(),
417
473
  z.object({
418
- kind: z.literal("zip"),
419
- member: ArchiveMemberSchema
420
- }).strict(),
421
- z.object({
422
- kind: z.literal("tar"),
423
- member: ArchiveMemberSchema
424
- }).strict(),
425
- z.object({
426
- kind: z.literal("tar.gz"),
474
+ kind: FileArchiveKindSchema,
427
475
  member: ArchiveMemberSchema
428
476
  }).strict(),
477
+ CollectionArchiveSchema,
429
478
  z.object({
430
479
  kind: z.literal("xlsx"),
431
480
  sheet: z.string().min(1)
@@ -468,6 +517,10 @@ const ParserSchema = z.discriminatedUnion("format", [
468
517
  format: z.literal("pajek"),
469
518
  encoding: EncodingSchema
470
519
  }).strict(),
520
+ z.object({
521
+ format: z.literal("solomon"),
522
+ encoding: EncodingSchema
523
+ }).strict(),
471
524
  z.object({
472
525
  format: z.literal("csv-table"),
473
526
  encoding: EncodingSchema,
@@ -587,6 +640,24 @@ const EntrySchema = define(z.object({
587
640
  path: ["recipe", "source"],
588
641
  message: `names source ${String(entry.recipe.source)} but the entry has ${String(entry.sources.length)}`
589
642
  });
643
+ if (entry.recipe.status === "fetchable") {
644
+ const selectsMembers = "members" in entry.recipe.archive;
645
+ const { shape } = getCategory(entry.category);
646
+ if (shape === "collection" && !selectsMembers) ctx.addIssue({
647
+ code: "custom",
648
+ path: ["recipe", "archive"],
649
+ message: `${entry.category} is a collection, so the archive must select its members with \`members\``
650
+ });
651
+ if (shape === "document" && selectsMembers) ctx.addIssue({
652
+ code: "custom",
653
+ path: [
654
+ "recipe",
655
+ "archive",
656
+ "members"
657
+ ],
658
+ message: `${entry.category} is one document, so the archive selects one file with \`member\``
659
+ });
660
+ }
590
661
  if ("target" in entry.crosswalk && entry.crosswalk.target !== entry.category) ctx.addIssue({
591
662
  code: "custom",
592
663
  path: ["crosswalk", "target"],
@@ -909,7 +980,7 @@ var catalogue_default = {
909
980
  "id": "keepset",
910
981
  "name": "keepset",
911
982
  "description": "The keepset catalogue: datasets with machine-readable recipes that fetch each one and normalise it into its category's target shape.",
912
- "version": "1.11.0",
983
+ "version": "1.12.0",
913
984
  "repository": "https://github.com/Mearman/keepset",
914
985
  "namespaceAliases": {
915
986
  "networkrepository": ["nr"],
@@ -7105,7 +7176,7 @@ var catalogue_default = {
7105
7176
  "duration",
7106
7177
  "travelTime"
7107
7178
  ],
7108
- "category": "or/v1",
7179
+ "category": "or-set/v1",
7109
7180
  "sources": [{
7110
7181
  "url": "https://github.com/psxjpc/MOVRPTW-Generator",
7111
7182
  "retrieved": "2026-10-19",
@@ -7121,12 +7192,95 @@ var catalogue_default = {
7121
7192
  },
7122
7193
  "citations": [],
7123
7194
  "recipe": {
7124
- "status": "not-fetchable",
7125
- "reason": "The download is a collection of Solomon style instance files and distance matrices, and a collection is more than one or/v1 document, so no recipe reads it."
7195
+ "status": "fetchable",
7196
+ "source": 1,
7197
+ "archive": {
7198
+ "kind": "zip",
7199
+ "members": { "extensions": [
7200
+ ".tw0Specs.dat",
7201
+ ".tw1Specs.dat",
7202
+ ".tw2Specs.dat",
7203
+ ".tw3Specs.dat",
7204
+ ".tw4Specs.dat"
7205
+ ] }
7206
+ },
7207
+ "parser": {
7208
+ "format": "solomon",
7209
+ "encoding": "utf-8"
7210
+ }
7126
7211
  },
7127
- "crosswalk": { "file": "crosswalks/or-v1/nodes.json" },
7128
- "expectedCounts": {},
7129
- "properties": { "surveyId": "or-9" }
7212
+ "crosswalk": {
7213
+ "source": "solomon",
7214
+ "target": "or-set/v1",
7215
+ "rules": [
7216
+ {
7217
+ "op": "move",
7218
+ "from": "meta",
7219
+ "to": "meta"
7220
+ },
7221
+ {
7222
+ "op": "move",
7223
+ "from": "members[].id",
7224
+ "to": "instances[].id"
7225
+ },
7226
+ {
7227
+ "op": "move",
7228
+ "from": "members[].meta.name",
7229
+ "to": "instances[].meta.name"
7230
+ },
7231
+ {
7232
+ "op": "move",
7233
+ "from": "members[].meta.vehicles",
7234
+ "to": "instances[].meta.vehicles"
7235
+ },
7236
+ {
7237
+ "op": "move",
7238
+ "from": "members[].meta.capacity",
7239
+ "to": "instances[].meta.capacity"
7240
+ },
7241
+ {
7242
+ "op": "move",
7243
+ "from": "members[].nodes[].id",
7244
+ "to": "instances[].nodes[].id"
7245
+ },
7246
+ {
7247
+ "op": "move",
7248
+ "from": "members[].nodes[].due",
7249
+ "to": "instances[].nodes[].deadline"
7250
+ },
7251
+ {
7252
+ "op": "move",
7253
+ "from": "members[].nodes[].service",
7254
+ "to": "instances[].nodes[].duration"
7255
+ },
7256
+ {
7257
+ "op": "move",
7258
+ "from": "members[].nodes[].x",
7259
+ "to": "instances[].nodes[].lat"
7260
+ },
7261
+ {
7262
+ "op": "move",
7263
+ "from": "members[].nodes[].y",
7264
+ "to": "instances[].nodes[].lon"
7265
+ },
7266
+ {
7267
+ "op": "move",
7268
+ "from": "members[].nodes[].demand",
7269
+ "to": "instances[].nodes[].demand"
7270
+ },
7271
+ {
7272
+ "op": "move",
7273
+ "from": "members[].nodes[].ready",
7274
+ "to": "instances[].nodes[].ready"
7275
+ }
7276
+ ],
7277
+ "oneWay": []
7278
+ },
7279
+ "expectedCounts": { "instances": 45 },
7280
+ "properties": {
7281
+ "surveyId": "or-9",
7282
+ "timeUnit": "seconds"
7283
+ }
7130
7284
  },
7131
7285
  {
7132
7286
  "id": "github-zhangchenhaoseu-jd-cold-chain-nodes",
@@ -8586,21 +8740,102 @@ var catalogue_default = {
8586
8740
  "duration",
8587
8741
  "travelTime"
8588
8742
  ],
8589
- "category": "or/v1",
8743
+ "category": "or-set/v1",
8590
8744
  "sources": [{
8591
8745
  "url": "https://neo.lcc.uma.es/vrp/vrp-instances/capacitated-vrp-with-time-windows-instances/",
8592
8746
  "retrieved": "2026-10-19",
8593
8747
  "status": "live"
8748
+ }, {
8749
+ "url": "https://neo.lcc.uma.es/vrp/wp-content/data/instances/solomon/solomon_100.zip",
8750
+ "retrieved": "2026-10-19",
8751
+ "status": "live"
8594
8752
  }],
8595
8753
  "licence": { "kind": "unrecorded" },
8596
8754
  "citations": [],
8597
8755
  "recipe": {
8598
- "status": "not-fetchable",
8599
- "reason": "The download is a collection of Solomon instance files (planar coordinates, demands, service times and windows in abstract units, not seconds), and a collection is more than one or/v1 document, so no recipe reads it."
8756
+ "status": "fetchable",
8757
+ "source": 1,
8758
+ "archive": {
8759
+ "kind": "zip",
8760
+ "members": { "extensions": [".txt"] }
8761
+ },
8762
+ "parser": {
8763
+ "format": "solomon",
8764
+ "encoding": "utf-8"
8765
+ }
8600
8766
  },
8601
- "crosswalk": { "file": "crosswalks/or-v1/nodes.json" },
8602
- "expectedCounts": {},
8603
- "properties": { "surveyId": "or-5" }
8767
+ "crosswalk": {
8768
+ "source": "solomon",
8769
+ "target": "or-set/v1",
8770
+ "rules": [
8771
+ {
8772
+ "op": "move",
8773
+ "from": "meta",
8774
+ "to": "meta"
8775
+ },
8776
+ {
8777
+ "op": "move",
8778
+ "from": "members[].id",
8779
+ "to": "instances[].id"
8780
+ },
8781
+ {
8782
+ "op": "move",
8783
+ "from": "members[].meta.name",
8784
+ "to": "instances[].meta.name"
8785
+ },
8786
+ {
8787
+ "op": "move",
8788
+ "from": "members[].meta.vehicles",
8789
+ "to": "instances[].meta.vehicles"
8790
+ },
8791
+ {
8792
+ "op": "move",
8793
+ "from": "members[].meta.capacity",
8794
+ "to": "instances[].meta.capacity"
8795
+ },
8796
+ {
8797
+ "op": "move",
8798
+ "from": "members[].nodes[].id",
8799
+ "to": "instances[].nodes[].id"
8800
+ },
8801
+ {
8802
+ "op": "move",
8803
+ "from": "members[].nodes[].due",
8804
+ "to": "instances[].nodes[].deadline"
8805
+ },
8806
+ {
8807
+ "op": "move",
8808
+ "from": "members[].nodes[].x",
8809
+ "to": "instances[].nodes[].x"
8810
+ },
8811
+ {
8812
+ "op": "move",
8813
+ "from": "members[].nodes[].y",
8814
+ "to": "instances[].nodes[].y"
8815
+ },
8816
+ {
8817
+ "op": "move",
8818
+ "from": "members[].nodes[].demand",
8819
+ "to": "instances[].nodes[].demand"
8820
+ },
8821
+ {
8822
+ "op": "move",
8823
+ "from": "members[].nodes[].ready",
8824
+ "to": "instances[].nodes[].ready"
8825
+ },
8826
+ {
8827
+ "op": "move",
8828
+ "from": "members[].nodes[].service",
8829
+ "to": "instances[].nodes[].service"
8830
+ }
8831
+ ],
8832
+ "oneWay": []
8833
+ },
8834
+ "expectedCounts": { "instances": 56 },
8835
+ "properties": {
8836
+ "surveyId": "or-5",
8837
+ "timeUnit": "abstract units"
8838
+ }
8604
8839
  },
8605
8840
  {
8606
8841
  "id": "leeds-fixed-penalty-notices",
@@ -70,28 +70,29 @@ declare const ArchiveMemberSchema: z.ZodUnion<readonly [z.ZodObject<{
70
70
  extensions: z.ZodArray<z.ZodString>;
71
71
  }, z.core.$strict>]>;
72
72
  type ArchiveMember = z.infer<typeof ArchiveMemberSchema>;
73
- /** How the downloaded bytes unpack into the one file the parser reads. A workbook (`xlsx`) is read as the CSV of its named sheet, with dates as ISO 8601 text, for the `csv-table` parser. */
74
- declare const ArchiveSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
73
+ /** How the downloaded bytes unpack into the file the parser reads, or for a collection the files it reads one by one. A `members` selector gives every member it matches, at least one, in path order. A workbook (`xlsx`) is read as the CSV of its named sheet, with dates as ISO 8601 text, for the `csv-table` parser. */
74
+ declare const ArchiveSchema: z.ZodUnion<readonly [z.ZodObject<{
75
75
  kind: z.ZodLiteral<"none">;
76
76
  }, z.core.$strict>, z.ZodObject<{
77
77
  kind: z.ZodLiteral<"gzip">;
78
78
  }, z.core.$strict>, z.ZodObject<{
79
- kind: z.ZodLiteral<"zip">;
80
- member: z.ZodUnion<readonly [z.ZodObject<{
81
- path: z.ZodString;
82
- }, z.core.$strict>, z.ZodObject<{
83
- extensions: z.ZodArray<z.ZodString>;
84
- }, z.core.$strict>]>;
85
- }, z.core.$strict>, z.ZodObject<{
86
- kind: z.ZodLiteral<"tar">;
79
+ kind: z.ZodEnum<{
80
+ zip: "zip";
81
+ tar: "tar";
82
+ "tar.gz": "tar.gz";
83
+ }>;
87
84
  member: z.ZodUnion<readonly [z.ZodObject<{
88
85
  path: z.ZodString;
89
86
  }, z.core.$strict>, z.ZodObject<{
90
87
  extensions: z.ZodArray<z.ZodString>;
91
88
  }, z.core.$strict>]>;
92
89
  }, z.core.$strict>, z.ZodObject<{
93
- kind: z.ZodLiteral<"tar.gz">;
94
- member: z.ZodUnion<readonly [z.ZodObject<{
90
+ kind: z.ZodEnum<{
91
+ zip: "zip";
92
+ tar: "tar";
93
+ "tar.gz": "tar.gz";
94
+ }>;
95
+ members: z.ZodUnion<readonly [z.ZodObject<{
95
96
  path: z.ZodString;
96
97
  }, z.core.$strict>, z.ZodObject<{
97
98
  extensions: z.ZodArray<z.ZodString>;
@@ -99,7 +100,7 @@ declare const ArchiveSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
99
100
  }, z.core.$strict>, z.ZodObject<{
100
101
  kind: z.ZodLiteral<"xlsx">;
101
102
  sheet: z.ZodString;
102
- }, z.core.$strict>], "kind">;
103
+ }, z.core.$strict>]>;
103
104
  type Archive = z.infer<typeof ArchiveSchema>;
104
105
  /** A column of a table as the table parser names it: the header with every run of characters outside letters, digits and underscore replaced by one underscore, so it is a valid segment of a twindex path. */
105
106
  declare const ColumnNameSchema: z.ZodString;
@@ -139,6 +140,12 @@ declare const ParserSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
139
140
  "utf-8": "utf-8";
140
141
  latin1: "latin1";
141
142
  }>;
143
+ }, z.core.$strict>, z.ZodObject<{
144
+ format: z.ZodLiteral<"solomon">;
145
+ encoding: z.ZodEnum<{
146
+ "utf-8": "utf-8";
147
+ latin1: "latin1";
148
+ }>;
142
149
  }, z.core.$strict>, z.ZodObject<{
143
150
  format: z.ZodLiteral<"csv-table">;
144
151
  encoding: z.ZodEnum<{
@@ -187,27 +194,28 @@ declare const RecipeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
187
194
  kind: z.ZodLiteral<"openfema">;
188
195
  pageSize: z.ZodInt;
189
196
  }, z.core.$strict>], "kind">>;
190
- archive: z.ZodDiscriminatedUnion<[z.ZodObject<{
197
+ archive: z.ZodUnion<readonly [z.ZodObject<{
191
198
  kind: z.ZodLiteral<"none">;
192
199
  }, z.core.$strict>, z.ZodObject<{
193
200
  kind: z.ZodLiteral<"gzip">;
194
201
  }, z.core.$strict>, z.ZodObject<{
195
- kind: z.ZodLiteral<"zip">;
196
- member: z.ZodUnion<readonly [z.ZodObject<{
197
- path: z.ZodString;
198
- }, z.core.$strict>, z.ZodObject<{
199
- extensions: z.ZodArray<z.ZodString>;
200
- }, z.core.$strict>]>;
201
- }, z.core.$strict>, z.ZodObject<{
202
- kind: z.ZodLiteral<"tar">;
202
+ kind: z.ZodEnum<{
203
+ zip: "zip";
204
+ tar: "tar";
205
+ "tar.gz": "tar.gz";
206
+ }>;
203
207
  member: z.ZodUnion<readonly [z.ZodObject<{
204
208
  path: z.ZodString;
205
209
  }, z.core.$strict>, z.ZodObject<{
206
210
  extensions: z.ZodArray<z.ZodString>;
207
211
  }, z.core.$strict>]>;
208
212
  }, z.core.$strict>, z.ZodObject<{
209
- kind: z.ZodLiteral<"tar.gz">;
210
- member: z.ZodUnion<readonly [z.ZodObject<{
213
+ kind: z.ZodEnum<{
214
+ zip: "zip";
215
+ tar: "tar";
216
+ "tar.gz": "tar.gz";
217
+ }>;
218
+ members: z.ZodUnion<readonly [z.ZodObject<{
211
219
  path: z.ZodString;
212
220
  }, z.core.$strict>, z.ZodObject<{
213
221
  extensions: z.ZodArray<z.ZodString>;
@@ -215,7 +223,7 @@ declare const RecipeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
215
223
  }, z.core.$strict>, z.ZodObject<{
216
224
  kind: z.ZodLiteral<"xlsx">;
217
225
  sheet: z.ZodString;
218
- }, z.core.$strict>], "kind">;
226
+ }, z.core.$strict>]>;
219
227
  parser: z.ZodDiscriminatedUnion<[z.ZodObject<{
220
228
  format: z.ZodLiteral<"edge-list">;
221
229
  encoding: z.ZodEnum<{
@@ -251,6 +259,12 @@ declare const RecipeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
251
259
  "utf-8": "utf-8";
252
260
  latin1: "latin1";
253
261
  }>;
262
+ }, z.core.$strict>, z.ZodObject<{
263
+ format: z.ZodLiteral<"solomon">;
264
+ encoding: z.ZodEnum<{
265
+ "utf-8": "utf-8";
266
+ latin1: "latin1";
267
+ }>;
254
268
  }, z.core.$strict>, z.ZodObject<{
255
269
  format: z.ZodLiteral<"csv-table">;
256
270
  encoding: z.ZodEnum<{
@@ -309,6 +323,7 @@ declare const EntrySchema: z.ZodObject<{
309
323
  category: z.ZodEnum<{
310
324
  "graph/v1": "graph/v1";
311
325
  "or/v1": "or/v1";
326
+ "or-set/v1": "or-set/v1";
312
327
  }>;
313
328
  sources: z.ZodArray<z.ZodObject<{
314
329
  url: z.ZodURL;
@@ -353,27 +368,28 @@ declare const EntrySchema: z.ZodObject<{
353
368
  kind: z.ZodLiteral<"openfema">;
354
369
  pageSize: z.ZodInt;
355
370
  }, z.core.$strict>], "kind">>;
356
- archive: z.ZodDiscriminatedUnion<[z.ZodObject<{
371
+ archive: z.ZodUnion<readonly [z.ZodObject<{
357
372
  kind: z.ZodLiteral<"none">;
358
373
  }, z.core.$strict>, z.ZodObject<{
359
374
  kind: z.ZodLiteral<"gzip">;
360
375
  }, z.core.$strict>, z.ZodObject<{
361
- kind: z.ZodLiteral<"zip">;
362
- member: z.ZodUnion<readonly [z.ZodObject<{
363
- path: z.ZodString;
364
- }, z.core.$strict>, z.ZodObject<{
365
- extensions: z.ZodArray<z.ZodString>;
366
- }, z.core.$strict>]>;
367
- }, z.core.$strict>, z.ZodObject<{
368
- kind: z.ZodLiteral<"tar">;
376
+ kind: z.ZodEnum<{
377
+ zip: "zip";
378
+ tar: "tar";
379
+ "tar.gz": "tar.gz";
380
+ }>;
369
381
  member: z.ZodUnion<readonly [z.ZodObject<{
370
382
  path: z.ZodString;
371
383
  }, z.core.$strict>, z.ZodObject<{
372
384
  extensions: z.ZodArray<z.ZodString>;
373
385
  }, z.core.$strict>]>;
374
386
  }, z.core.$strict>, z.ZodObject<{
375
- kind: z.ZodLiteral<"tar.gz">;
376
- member: z.ZodUnion<readonly [z.ZodObject<{
387
+ kind: z.ZodEnum<{
388
+ zip: "zip";
389
+ tar: "tar";
390
+ "tar.gz": "tar.gz";
391
+ }>;
392
+ members: z.ZodUnion<readonly [z.ZodObject<{
377
393
  path: z.ZodString;
378
394
  }, z.core.$strict>, z.ZodObject<{
379
395
  extensions: z.ZodArray<z.ZodString>;
@@ -381,7 +397,7 @@ declare const EntrySchema: z.ZodObject<{
381
397
  }, z.core.$strict>, z.ZodObject<{
382
398
  kind: z.ZodLiteral<"xlsx">;
383
399
  sheet: z.ZodString;
384
- }, z.core.$strict>], "kind">;
400
+ }, z.core.$strict>]>;
385
401
  parser: z.ZodDiscriminatedUnion<[z.ZodObject<{
386
402
  format: z.ZodLiteral<"edge-list">;
387
403
  encoding: z.ZodEnum<{
@@ -417,6 +433,12 @@ declare const EntrySchema: z.ZodObject<{
417
433
  "utf-8": "utf-8";
418
434
  latin1: "latin1";
419
435
  }>;
436
+ }, z.core.$strict>, z.ZodObject<{
437
+ format: z.ZodLiteral<"solomon">;
438
+ encoding: z.ZodEnum<{
439
+ "utf-8": "utf-8";
440
+ latin1: "latin1";
441
+ }>;
420
442
  }, z.core.$strict>, z.ZodObject<{
421
443
  format: z.ZodLiteral<"csv-table">;
422
444
  encoding: z.ZodEnum<{
@@ -463,7 +485,7 @@ declare const EntrySchema: z.ZodObject<{
463
485
  id: string;
464
486
  name: string;
465
487
  tags: string[];
466
- category: "graph/v1" | "or/v1";
488
+ category: "graph/v1" | "or/v1" | "or-set/v1";
467
489
  sources: {
468
490
  url: string;
469
491
  retrieved: string;
@@ -496,22 +518,15 @@ declare const EntrySchema: z.ZodObject<{
496
518
  } | {
497
519
  kind: "gzip";
498
520
  } | {
499
- kind: "zip";
500
- member: {
501
- path: string;
502
- } | {
503
- extensions: string[];
504
- };
505
- } | {
506
- kind: "tar";
521
+ kind: "zip" | "tar" | "tar.gz";
507
522
  member: {
508
523
  path: string;
509
524
  } | {
510
525
  extensions: string[];
511
526
  };
512
527
  } | {
513
- kind: "tar.gz";
514
- member: {
528
+ kind: "zip" | "tar" | "tar.gz";
529
+ members: {
515
530
  path: string;
516
531
  } | {
517
532
  extensions: string[];
@@ -559,6 +574,9 @@ declare const EntrySchema: z.ZodObject<{
559
574
  } | {
560
575
  format: "pajek";
561
576
  encoding: "utf-8" | "latin1";
577
+ } | {
578
+ format: "solomon";
579
+ encoding: "utf-8" | "latin1";
562
580
  };
563
581
  paging?: {
564
582
  kind: "arcgis-query";
@@ -692,6 +710,7 @@ declare const CatalogueIndexSchema: z.ZodObject<{
692
710
  category: z.ZodEnum<{
693
711
  "graph/v1": "graph/v1";
694
712
  "or/v1": "or/v1";
713
+ "or-set/v1": "or-set/v1";
695
714
  }>;
696
715
  sources: z.ZodArray<z.ZodObject<{
697
716
  url: z.ZodURL;
@@ -736,27 +755,28 @@ declare const CatalogueIndexSchema: z.ZodObject<{
736
755
  kind: z.ZodLiteral<"openfema">;
737
756
  pageSize: z.ZodInt;
738
757
  }, z.core.$strict>], "kind">>;
739
- archive: z.ZodDiscriminatedUnion<[z.ZodObject<{
758
+ archive: z.ZodUnion<readonly [z.ZodObject<{
740
759
  kind: z.ZodLiteral<"none">;
741
760
  }, z.core.$strict>, z.ZodObject<{
742
761
  kind: z.ZodLiteral<"gzip">;
743
762
  }, z.core.$strict>, z.ZodObject<{
744
- kind: z.ZodLiteral<"zip">;
745
- member: z.ZodUnion<readonly [z.ZodObject<{
746
- path: z.ZodString;
747
- }, z.core.$strict>, z.ZodObject<{
748
- extensions: z.ZodArray<z.ZodString>;
749
- }, z.core.$strict>]>;
750
- }, z.core.$strict>, z.ZodObject<{
751
- kind: z.ZodLiteral<"tar">;
763
+ kind: z.ZodEnum<{
764
+ zip: "zip";
765
+ tar: "tar";
766
+ "tar.gz": "tar.gz";
767
+ }>;
752
768
  member: z.ZodUnion<readonly [z.ZodObject<{
753
769
  path: z.ZodString;
754
770
  }, z.core.$strict>, z.ZodObject<{
755
771
  extensions: z.ZodArray<z.ZodString>;
756
772
  }, z.core.$strict>]>;
757
773
  }, z.core.$strict>, z.ZodObject<{
758
- kind: z.ZodLiteral<"tar.gz">;
759
- member: z.ZodUnion<readonly [z.ZodObject<{
774
+ kind: z.ZodEnum<{
775
+ zip: "zip";
776
+ tar: "tar";
777
+ "tar.gz": "tar.gz";
778
+ }>;
779
+ members: z.ZodUnion<readonly [z.ZodObject<{
760
780
  path: z.ZodString;
761
781
  }, z.core.$strict>, z.ZodObject<{
762
782
  extensions: z.ZodArray<z.ZodString>;
@@ -764,7 +784,7 @@ declare const CatalogueIndexSchema: z.ZodObject<{
764
784
  }, z.core.$strict>, z.ZodObject<{
765
785
  kind: z.ZodLiteral<"xlsx">;
766
786
  sheet: z.ZodString;
767
- }, z.core.$strict>], "kind">;
787
+ }, z.core.$strict>]>;
768
788
  parser: z.ZodDiscriminatedUnion<[z.ZodObject<{
769
789
  format: z.ZodLiteral<"edge-list">;
770
790
  encoding: z.ZodEnum<{
@@ -800,6 +820,12 @@ declare const CatalogueIndexSchema: z.ZodObject<{
800
820
  "utf-8": "utf-8";
801
821
  latin1: "latin1";
802
822
  }>;
823
+ }, z.core.$strict>, z.ZodObject<{
824
+ format: z.ZodLiteral<"solomon">;
825
+ encoding: z.ZodEnum<{
826
+ "utf-8": "utf-8";
827
+ latin1: "latin1";
828
+ }>;
803
829
  }, z.core.$strict>, z.ZodObject<{
804
830
  format: z.ZodLiteral<"csv-table">;
805
831
  encoding: z.ZodEnum<{
@@ -846,7 +872,7 @@ declare const CatalogueIndexSchema: z.ZodObject<{
846
872
  id: string;
847
873
  name: string;
848
874
  tags: string[];
849
- category: "graph/v1" | "or/v1";
875
+ category: "graph/v1" | "or/v1" | "or-set/v1";
850
876
  sources: {
851
877
  url: string;
852
878
  retrieved: string;
@@ -879,22 +905,15 @@ declare const CatalogueIndexSchema: z.ZodObject<{
879
905
  } | {
880
906
  kind: "gzip";
881
907
  } | {
882
- kind: "zip";
908
+ kind: "zip" | "tar" | "tar.gz";
883
909
  member: {
884
910
  path: string;
885
911
  } | {
886
912
  extensions: string[];
887
913
  };
888
914
  } | {
889
- kind: "tar";
890
- member: {
891
- path: string;
892
- } | {
893
- extensions: string[];
894
- };
895
- } | {
896
- kind: "tar.gz";
897
- member: {
915
+ kind: "zip" | "tar" | "tar.gz";
916
+ members: {
898
917
  path: string;
899
918
  } | {
900
919
  extensions: string[];
@@ -942,6 +961,9 @@ declare const CatalogueIndexSchema: z.ZodObject<{
942
961
  } | {
943
962
  format: "pajek";
944
963
  encoding: "utf-8" | "latin1";
964
+ } | {
965
+ format: "solomon";
966
+ encoding: "utf-8" | "latin1";
945
967
  };
946
968
  paging?: {
947
969
  kind: "arcgis-query";
@@ -991,7 +1013,7 @@ declare const CatalogueIndexSchema: z.ZodObject<{
991
1013
  id: string;
992
1014
  name: string;
993
1015
  tags: string[];
994
- category: "graph/v1" | "or/v1";
1016
+ category: "graph/v1" | "or/v1" | "or-set/v1";
995
1017
  sources: {
996
1018
  url: string;
997
1019
  retrieved: string;
@@ -1024,22 +1046,15 @@ declare const CatalogueIndexSchema: z.ZodObject<{
1024
1046
  } | {
1025
1047
  kind: "gzip";
1026
1048
  } | {
1027
- kind: "zip";
1049
+ kind: "zip" | "tar" | "tar.gz";
1028
1050
  member: {
1029
1051
  path: string;
1030
1052
  } | {
1031
1053
  extensions: string[];
1032
1054
  };
1033
1055
  } | {
1034
- kind: "tar";
1035
- member: {
1036
- path: string;
1037
- } | {
1038
- extensions: string[];
1039
- };
1040
- } | {
1041
- kind: "tar.gz";
1042
- member: {
1056
+ kind: "zip" | "tar" | "tar.gz";
1057
+ members: {
1043
1058
  path: string;
1044
1059
  } | {
1045
1060
  extensions: string[];
@@ -1087,6 +1102,9 @@ declare const CatalogueIndexSchema: z.ZodObject<{
1087
1102
  } | {
1088
1103
  format: "pajek";
1089
1104
  encoding: "utf-8" | "latin1";
1105
+ } | {
1106
+ format: "solomon";
1107
+ encoding: "utf-8" | "latin1";
1090
1108
  };
1091
1109
  paging?: {
1092
1110
  kind: "arcgis-query";
@@ -1222,6 +1240,62 @@ declare const GraphV1Schema: z.ZodObject<{
1222
1240
  };
1223
1241
  type GraphV1 = z.infer<typeof GraphV1Schema>;
1224
1242
  //#endregion
1243
+ //#region src/targets/or-set-v1.d.ts
1244
+ /**
1245
+ * The or-set/v1 normalisation target: `{ meta, instances }`, a collection of operations-research instances such as a benchmark suite, each instance an or/v1 document (`{ meta, nodes }`) under an id of its own. Beyond the shapes it checks that instance ids are unique in the set and node ids unique within each instance. A node id need not be unique across instances, because every instance numbers its own nodes.
1246
+ */
1247
+ declare const OrSetV1Schema: z.ZodObject<{
1248
+ meta: z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>, unknown>;
1249
+ instances: z.ZodArray<z.ZodObject<{
1250
+ id: z.ZodString;
1251
+ meta: z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>, unknown>;
1252
+ nodes: z.ZodArray<z.ZodObject<{
1253
+ id: z.ZodString;
1254
+ lat: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
1255
+ lon: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
1256
+ x: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
1257
+ y: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
1258
+ prize: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
1259
+ prizeProbability: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
1260
+ duration: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
1261
+ travelTime: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
1262
+ skill: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1263
+ territory: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1264
+ status: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1265
+ opened: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1266
+ closed: z.ZodOptional<z.ZodNullable<z.ZodString>>;
1267
+ deadline: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
1268
+ }, z.core.$loose>>;
1269
+ }, z.core.$strict>>;
1270
+ }, z.core.$strict> & {
1271
+ is: (value: unknown) => value is {
1272
+ meta: Record<string, string | number | boolean | null>;
1273
+ instances: {
1274
+ id: string;
1275
+ meta: Record<string, string | number | boolean | null>;
1276
+ nodes: {
1277
+ [x: string]: unknown;
1278
+ id: string;
1279
+ lat?: number | null | undefined;
1280
+ lon?: number | null | undefined;
1281
+ x?: number | null | undefined;
1282
+ y?: number | null | undefined;
1283
+ prize?: number | null | undefined;
1284
+ prizeProbability?: number | null | undefined;
1285
+ duration?: number | null | undefined;
1286
+ travelTime?: number | null | undefined;
1287
+ skill?: string | null | undefined;
1288
+ territory?: string | null | undefined;
1289
+ status?: string | null | undefined;
1290
+ opened?: string | null | undefined;
1291
+ closed?: string | null | undefined;
1292
+ deadline?: string | number | null | undefined;
1293
+ }[];
1294
+ }[];
1295
+ };
1296
+ };
1297
+ type OrSetV1 = z.infer<typeof OrSetV1Schema>;
1298
+ //#endregion
1225
1299
  //#region src/targets/or-v1.d.ts
1226
1300
  /**
1227
1301
  * A node of an operations-research instance: a place or case a route may visit, flat so a twindex crosswalk can map a table's columns onto it (a wildcard path must keep the same shape on both sides). The target fixes only what the datasets share and lets a crosswalk map any further column through under its own name, as graph/v1 does. `lat` and `lon` are WGS84 degrees and `x` and `y` are planar coordinates in the dataset's own system; `duration` and `travelTime` are seconds, `prize` is the dataset's own reward measure, and `prizeProbability` is the chance the reward is collected. `skill` and `territory` name what is needed to serve the node and where it sits, `status` is where it is in its lifecycle, and `opened`, `closed` and `deadline` are its lifecycle times as the source writes them, a deadline being a number where the source gives a time of day.
@@ -1296,21 +1370,26 @@ type OrV1 = z.infer<typeof OrV1Schema>;
1296
1370
  declare const CategoryIdSchema: z.ZodEnum<{
1297
1371
  "graph/v1": "graph/v1";
1298
1372
  "or/v1": "or/v1";
1373
+ "or-set/v1": "or-set/v1";
1299
1374
  }>;
1300
1375
  type CategoryId = z.infer<typeof CategoryIdSchema>;
1301
1376
  /** The normalisation target each category resolves to, keyed by category id. */
1302
1377
  interface CategoryTargets {
1303
1378
  readonly "graph/v1": GraphV1;
1304
1379
  readonly "or/v1": OrV1;
1380
+ readonly "or-set/v1": OrSetV1;
1305
1381
  }
1306
1382
  /** The normalised shape of any category: what `fetchDataset` can resolve to. */
1307
1383
  type CategoryTarget = CategoryTargets[CategoryId];
1308
1384
  /** The keys of a target whose values are arrays: the collections an entry's `expectedCounts` may name. */
1309
1385
  type CountedKey<Target> = { readonly [Key in keyof Target]: Target[Key] extends readonly unknown[] ? Key : never; }[keyof Target] & string;
1310
- /** One category: its id, what it holds, the schema of its normalisation target, and which of the target's arrays an entry may record expected counts for. */
1386
+ /** What one fetch of an entry in a category reads: a single document, or a set of documents, one per member of the archive the source unpacks to. */
1387
+ type CategoryShape = "document" | "collection";
1388
+ /** One category: its id, what it holds, whether an entry is one document or a collection of them, the schema of its normalisation target, and which of the target's arrays an entry may record expected counts for. */
1311
1389
  interface CategoryOf<Id extends CategoryId> {
1312
1390
  readonly id: Id;
1313
1391
  readonly description: string;
1392
+ readonly shape: CategoryShape;
1314
1393
  readonly target: z.ZodType<CategoryTargets[Id]>;
1315
1394
  readonly counted: readonly CountedKey<CategoryTargets[Id]>[];
1316
1395
  }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as Parser, A as LineageSchema, B as Citation, C as CatalogueIndex, D as NamespaceNode, E as DatasetQuery, F as AliasSchema, G as Entry, H as ColumnNameSchema, I as Archive, J as IdSchema, K as EntrySchema, L as ArchiveMember, M as ManifestSchema, N as SemVerSchema, O as createCatalogue, P as parseManifest, Q as NamespaceSchema, R as ArchiveMemberSchema, S as Catalogue, T as Dataset, U as CrosswalkReference, V as CitationSchema, W as CrosswalkReferenceSchema, X as LicenceSchema, Y as Licence, Z as Namespace, _ as GraphEdgeSchema, a as CategoryTargets, at as Snapshot, b as GraphV1, c as getCategory, ct as SourceSchema, d as listCategories, dt as waybackUrl, et as ParserSchema, f as OrNode, g as GraphEdge, h as OrV1Schema, i as CategoryTarget, it as RelativePathSchema, j as Manifest, k as Lineage, l as getTarget, lt as WaybackTimestampSchema, m as OrV1, n as CategoryId, nt as Recipe, o as CountedKey, ot as SnapshotSchema, p as OrNodeSchema, q as HttpUrlSchema, r as CategoryIdSchema, rt as RecipeSchema, s as countTarget, st as Source, t as Category, tt as RESERVED_META_KEYS, u as kindOf, ut as parseEntry, v as GraphNode, w as CatalogueIndexSchema, x as GraphV1Schema, y as GraphNodeSchema, z as ArchiveSchema } from "./categories-BGjPWXpS.js";
1
+ import { $ as Parser, A as LineageSchema, B as Citation, C as CatalogueIndex, D as NamespaceNode, E as DatasetQuery, F as AliasSchema, G as Entry, H as ColumnNameSchema, I as Archive, J as IdSchema, K as EntrySchema, L as ArchiveMember, M as ManifestSchema, N as SemVerSchema, O as createCatalogue, P as parseManifest, Q as NamespaceSchema, R as ArchiveMemberSchema, S as Catalogue, T as Dataset, U as CrosswalkReference, V as CitationSchema, W as CrosswalkReferenceSchema, X as LicenceSchema, Y as Licence, Z as Namespace, _ as GraphEdgeSchema, a as CategoryTargets, at as Snapshot, b as GraphV1, c as getCategory, ct as SourceSchema, d as listCategories, dt as waybackUrl, et as ParserSchema, f as OrNode, g as GraphEdge, h as OrV1Schema, i as CategoryTarget, it as RelativePathSchema, j as Manifest, k as Lineage, l as getTarget, lt as WaybackTimestampSchema, m as OrV1, n as CategoryId, nt as Recipe, o as CountedKey, ot as SnapshotSchema, p as OrNodeSchema, q as HttpUrlSchema, r as CategoryIdSchema, rt as RecipeSchema, s as countTarget, st as Source, t as Category, tt as RESERVED_META_KEYS, u as kindOf, ut as parseEntry, v as GraphNode, w as CatalogueIndexSchema, x as GraphV1Schema, y as GraphNodeSchema, z as ArchiveSchema } from "./categories-BfqmrAvf.js";
2
2
  import { Crosswalk } from "twindex";
3
3
  import { z } from "zod";
4
4
  //#region src/errors.d.ts
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as NativeParseError, A as toIssues, B as kindOf, C as RESERVED_META_KEYS, D as SourceSchema, E as SnapshotSchema, G as GraphNodeSchema, H as OrNodeSchema, I as CategoryIdSchema, J as ArchiveError, K as GraphV1Schema, L as countTarget, M as NAME_SEPARATOR, N as groupsOf, O as WaybackTimestampSchema, P as namesOf, Q as KeepsetError, R as getCategory, S as ParserSchema, T as RelativePathSchema, U as OrV1Schema, V as listCategories, W as GraphEdgeSchema, X as DatasetAmbiguousError, Y as CatalogueSyntaxError, Z as DatasetNotFoundError, _ as EntrySchema, a as CatalogueIndexSchema, b as LicenceSchema, c as ManifestSchema, d as AliasSchema, et as RecipeError, f as ArchiveMemberSchema, g as CrosswalkReferenceSchema, h as ColumnNameSchema, i as listNamespaces, j as waybackUrl, k as parseEntry, l as SemVerSchema, m as CitationSchema, n as getManifest, o as createCatalogue, p as ArchiveSchema, q as JsonScalarSchema, r as listDatasets, s as LineageSchema, t as getDataset, tt as UnknownCategoryError, u as parseManifest, v as HttpUrlSchema, w as RecipeSchema, x as NamespaceSchema, y as IdSchema, z as getTarget } from "./bundled-BKl0C7kF.js";
1
+ import { $ as NativeParseError, A as toIssues, B as kindOf, C as RESERVED_META_KEYS, D as SourceSchema, E as SnapshotSchema, G as GraphNodeSchema, H as OrNodeSchema, I as CategoryIdSchema, J as ArchiveError, K as GraphV1Schema, L as countTarget, M as NAME_SEPARATOR, N as groupsOf, O as WaybackTimestampSchema, P as namesOf, Q as KeepsetError, R as getCategory, S as ParserSchema, T as RelativePathSchema, U as OrV1Schema, V as listCategories, W as GraphEdgeSchema, X as DatasetAmbiguousError, Y as CatalogueSyntaxError, Z as DatasetNotFoundError, _ as EntrySchema, a as CatalogueIndexSchema, b as LicenceSchema, c as ManifestSchema, d as AliasSchema, et as RecipeError, f as ArchiveMemberSchema, g as CrosswalkReferenceSchema, h as ColumnNameSchema, i as listNamespaces, j as waybackUrl, k as parseEntry, l as SemVerSchema, m as CitationSchema, n as getManifest, o as createCatalogue, p as ArchiveSchema, q as JsonScalarSchema, r as listDatasets, s as LineageSchema, t as getDataset, tt as UnknownCategoryError, u as parseManifest, v as HttpUrlSchema, w as RecipeSchema, x as NamespaceSchema, y as IdSchema, z as getTarget } from "./bundled-BWINQKLQ.js";
2
2
  import { validateCrosswalk } from "twindex";
3
3
  //#region src/serialise.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { G as Entry, T as Dataset, at as Snapshot, i as CategoryTarget, st as Source } from "../categories-BGjPWXpS.js";
1
+ import { G as Entry, T as Dataset, at as Snapshot, i as CategoryTarget, st as Source } from "../categories-BfqmrAvf.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: {
@@ -24,7 +24,10 @@ export interface Provenance {
24
24
  readonly retrievedAt: string;
25
25
  readonly sha256: string;
26
26
  readonly byteLength: number;
27
+ /** The archive member a document was read from. */
27
28
  readonly member?: string;
29
+ /** The archive members a collection was read from, in the order they were read. */
30
+ readonly members?: readonly string[];
28
31
  }
29
32
  /** 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
33
  export interface FetchedDataset {
@@ -1,4 +1,4 @@
1
- import { $ as NativeParseError, F as getOwn, J as ArchiveError, L as countTarget, Q as KeepsetError, et as RecipeError, t as getDataset, z as getTarget } from "../bundled-BKl0C7kF.js";
1
+ import { $ as NativeParseError, F as getOwn, J as ArchiveError, L as countTarget, Q as KeepsetError, et as RecipeError, t as getDataset, z as getTarget } from "../bundled-BWINQKLQ.js";
2
2
  import { RoundTripMismatchError, assertRoundTrip, encode } from "twindex";
3
3
  import { z } from "zod";
4
4
  import { createHash } from "node:crypto";
@@ -104,7 +104,7 @@ function parseEdgeList(text, options) {
104
104
  //#endregion
105
105
  //#region src/parsers/gml.ts
106
106
  const KEY = /[A-Za-z_][A-Za-z0-9_]*/y;
107
- const NUMBER = /[+-]?(?:\d+\.?\d*|\.\d+)(?:[Ee][+-]?\d+)?/y;
107
+ const NUMBER$1 = /[+-]?(?:\d+\.?\d*|\.\d+)(?:[Ee][+-]?\d+)?/y;
108
108
  /**
109
109
  * Matches a sticky pattern at a position.
110
110
  * @param pattern - A sticky regular expression.
@@ -155,7 +155,7 @@ function tokenise(text) {
155
155
  position = end + 1;
156
156
  } else {
157
157
  const key = matchAt(KEY, text, position);
158
- const number = key === void 0 ? matchAt(NUMBER, text, position) : void 0;
158
+ const number = key === void 0 ? matchAt(NUMBER$1, text, position) : void 0;
159
159
  const match = key ?? number;
160
160
  if (match === void 0) throw new NativeParseError("gml", line, `unexpected character ${JSON.stringify(char)}`);
161
161
  tokens.push({
@@ -832,6 +832,89 @@ function parsePajek(text) {
832
832
  };
833
833
  }
834
834
  //#endregion
835
+ //#region src/parsers/solomon.ts
836
+ const FORMAT = "solomon";
837
+ /** A line of integers or decimals separated by any run of spaces and tabs, as the files write rows (Solomon's with padded spaces, the Tenerife set with tabs). */
838
+ const NUMBER = /^-?\d+(?:\.\d+)?$/;
839
+ /**
840
+ * Reads a token as a finite number.
841
+ * @param token - The token.
842
+ * @param line - The 1-based line, for the error.
843
+ * @param what - What the token should be, for the error.
844
+ * @returns The number.
845
+ * @throws NativeParseError when the token is not a decimal number.
846
+ */
847
+ function numberOf(token, line, what) {
848
+ if (!NUMBER.test(token)) throw new NativeParseError(FORMAT, line, `expected ${what}, found ${JSON.stringify(token)}`);
849
+ return Number(token);
850
+ }
851
+ /**
852
+ * Parses a Solomon style vehicle routing instance (R. M. Solomon, 1987): a name on the first line, a `VEHICLE` section holding the number of vehicles and their capacity, and a `CUSTOMER` section of one row per node, the depot first, with its id, position, demand, ready time, due date and service time. Columns are separated by any run of spaces or tabs, so the original files and the tab separated Tenerife set read alike, and blank or whitespace only lines are skipped. The header lines (`NUMBER CAPACITY` and the column titles) are not read: a row is a line of exactly seven numbers after the `CUSTOMER` keyword, and anything else after it is an error. Node ids are kept as the file writes them, so a file whose ids are not consecutive keeps its own. The time quantities are in the unit the source uses, which the format does not record.
853
+ * @param text - The file text.
854
+ * @returns The instance: its name, vehicle count and capacity as `meta`, and its nodes in file order.
855
+ * @throws NativeParseError naming the line for a missing name, `VEHICLE` or `CUSTOMER` section, a vehicle line that is not two numbers, a customer row that is not seven numbers, a repeated node id, or a file with no customer.
856
+ */
857
+ function parseSolomon(text) {
858
+ const all = lines(text);
859
+ const present = (index) => (all[index] ?? "").trim();
860
+ const name = present(0);
861
+ if (name === "") throw new NativeParseError(FORMAT, 1, "expected the instance name on the first line");
862
+ const meta = { name };
863
+ const nodes = [];
864
+ const seen = /* @__PURE__ */ new Set();
865
+ let section = "preamble";
866
+ let awaitingHeader = false;
867
+ for (const [index, raw] of all.entries()) {
868
+ const line = index + 1;
869
+ const trimmed = raw.trim();
870
+ if (index === 0 || trimmed === "") continue;
871
+ const keyword = trimmed.toUpperCase();
872
+ if (keyword === "VEHICLE" || keyword === "CUSTOMER") {
873
+ section = keyword === "VEHICLE" ? "vehicle" : "customer";
874
+ awaitingHeader = true;
875
+ continue;
876
+ }
877
+ if (section === "preamble") throw new NativeParseError(FORMAT, line, `expected VEHICLE or CUSTOMER, found ${JSON.stringify(trimmed)}`);
878
+ if (awaitingHeader) {
879
+ awaitingHeader = false;
880
+ continue;
881
+ }
882
+ const tokens = trimmed.split(/\s+/);
883
+ if (section === "vehicle") {
884
+ const [vehicles, capacity, ...extra] = tokens;
885
+ if (vehicles === void 0 || capacity === void 0 || extra.length > 0) throw new NativeParseError(FORMAT, line, `expected the vehicle count and capacity, found ${JSON.stringify(trimmed)}`);
886
+ meta.vehicles = numberOf(vehicles, line, "the vehicle count");
887
+ meta.capacity = numberOf(capacity, line, "the vehicle capacity");
888
+ continue;
889
+ }
890
+ const [id, x, y, demand, ready, due, service, ...extra] = tokens;
891
+ if (id === void 0 || x === void 0 || y === void 0 || demand === void 0 || ready === void 0 || due === void 0 || service === void 0 || extra.length > 0) throw new NativeParseError(FORMAT, line, `expected seven numbers (id, x, y, demand, ready, due, service), found ${JSON.stringify(trimmed)}`);
892
+ if (!/^\d+$/.test(id)) throw new NativeParseError(FORMAT, line, `expected a whole number node id, found ${JSON.stringify(id)}`);
893
+ if (seen.has(id)) throw new NativeParseError(FORMAT, line, `node ${id} appears twice`);
894
+ seen.add(id);
895
+ nodes.push({
896
+ id,
897
+ x: numberOf(x, line, "x"),
898
+ y: numberOf(y, line, "y"),
899
+ demand: numberOf(demand, line, "the demand"),
900
+ ready: numberOf(ready, line, "the ready time"),
901
+ due: numberOf(due, line, "the due date"),
902
+ service: numberOf(service, line, "the service time")
903
+ });
904
+ }
905
+ if (section !== "customer") throw new NativeParseError(FORMAT, all.length, "expected a CUSTOMER section");
906
+ if (nodes.length === 0) throw new NativeParseError(FORMAT, all.length, "the CUSTOMER section has no rows");
907
+ if (meta.vehicles === void 0 || meta.capacity === void 0) throw new NativeParseError(FORMAT, all.length, "expected a VEHICLE section with the vehicle count and capacity");
908
+ return {
909
+ meta: {
910
+ name: meta.name,
911
+ vehicles: meta.vehicles,
912
+ capacity: meta.capacity
913
+ },
914
+ nodes
915
+ };
916
+ }
917
+ //#endregion
835
918
  //#region src/parsers/parse-native.ts
836
919
  /** Bytes converted per String.fromCharCode call: spreading a whole file would exceed the engine's argument-count limit, and this many stays well inside every engine's. */
837
920
  const LATIN1_CHUNK = 32768;
@@ -887,6 +970,7 @@ function parseNative(bytes, parser) {
887
970
  if (parser.format === "edge-list") return parseEdgeList(text, parser);
888
971
  if (parser.format === "matrix-market") return parseMatrixMarket(text);
889
972
  if (parser.format === "gml") return parseGml(text);
973
+ if (parser.format === "solomon") return parseSolomon(text);
890
974
  return parsePajek(text);
891
975
  }
892
976
  /** The ustar header layout (POSIX.1-1988, pax(1) "ustar Interchange Format"): each field's byte offset and length. */
@@ -1285,6 +1369,18 @@ function selects(path, member) {
1285
1369
  return member.extensions.some((extension) => path.toLowerCase().endsWith(extension.toLowerCase()));
1286
1370
  }
1287
1371
  /**
1372
+ * Picks every member a selector matches, in path order, so a collection reads in the same order wherever it is unpacked.
1373
+ * @param paths - Every member path.
1374
+ * @param member - The recipe's selector.
1375
+ * @returns The matching paths, sorted, at least one.
1376
+ * @throws ArchiveError when no member matches, listing the members.
1377
+ */
1378
+ function pickAll(paths, member) {
1379
+ const matched = paths.filter((path) => selects(path, member)).sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
1380
+ if (matched.length === 0) throw new ArchiveError(`expected at least one ${"path" in member ? `member ${member.path}` : `member ending ${member.extensions.join(" or ")}`}, found none among: ${paths.join(", ")}`);
1381
+ return matched;
1382
+ }
1383
+ /**
1288
1384
  * Picks the one member a selector matches.
1289
1385
  * @param paths - Every member path.
1290
1386
  * @param member - The recipe's selector.
@@ -1338,6 +1434,42 @@ function unpack(bytes, archive) {
1338
1434
  };
1339
1435
  }
1340
1436
  /**
1437
+ * Unpacks downloaded bytes into every file a collection's selector matches.
1438
+ * @param bytes - The downloaded bytes.
1439
+ * @param archive - The recipe's archive layout, which selects members with `members`.
1440
+ * @returns Each matching file, in path order.
1441
+ * @throws ArchiveError when the bytes are not a well-formed archive of the stated kind or the selector matches no member.
1442
+ */
1443
+ function unpackMembers(bytes, archive) {
1444
+ if (archive.kind === "zip") {
1445
+ const names = [];
1446
+ unzip(bytes, (file) => {
1447
+ names.push(file.name);
1448
+ return false;
1449
+ });
1450
+ const paths = pickAll(names, archive.members);
1451
+ const chosen = new Set(paths);
1452
+ const files = unzip(bytes, (candidate) => chosen.has(candidate.name));
1453
+ return paths.map((path) => {
1454
+ const file = getOwn(files, path);
1455
+ if (file === void 0) throw new ArchiveError(`zip member ${path} could not be read`);
1456
+ return {
1457
+ path,
1458
+ bytes: file
1459
+ };
1460
+ });
1461
+ }
1462
+ const members = readTar(archive.kind === "tar" ? bytes : gunzip(bytes));
1463
+ return pickAll(members.map((member) => member.path), archive.members).map((path) => {
1464
+ const found = members.find((member) => member.path === path);
1465
+ if (found === void 0) throw new ArchiveError(`tar member ${path} could not be read`);
1466
+ return {
1467
+ path,
1468
+ bytes: found.data
1469
+ };
1470
+ });
1471
+ }
1472
+ /**
1341
1473
  * Gunzips bytes, rethrowing zlib's failure in the package's taxonomy.
1342
1474
  * @param bytes - Gzip data.
1343
1475
  * @returns The decompressed bytes.
@@ -1808,28 +1940,54 @@ async function runToNative(dataset, options) {
1808
1940
  if (!response.ok) throw new KeepsetError(`${fetchedUrl} answered ${String(response.status)} ${response.statusText}`);
1809
1941
  return new Uint8Array(await response.arrayBuffer());
1810
1942
  });
1811
- const unpacked = await step(entry.id, "extract", () => unpack(bytes, recipe.archive));
1943
+ const provenanceOf = (extra) => ({
1944
+ entryId: entry.id,
1945
+ catalogue: dataset.catalogue,
1946
+ category: entry.category,
1947
+ source: {
1948
+ url: source.url,
1949
+ status: source.status,
1950
+ ...source.snapshot === void 0 ? {} : { snapshot: source.snapshot }
1951
+ },
1952
+ fetchedUrl,
1953
+ retrievedAt,
1954
+ sha256: createHash("sha256").update(bytes).digest("hex"),
1955
+ byteLength: bytes.byteLength,
1956
+ ...extra
1957
+ });
1958
+ const { archive } = recipe;
1959
+ if ("members" in archive) {
1960
+ const files = await step(entry.id, "extract", () => unpackMembers(bytes, archive));
1961
+ const members = await step(entry.id, "parse", () => files.map((file) => {
1962
+ try {
1963
+ return {
1964
+ id: file.path,
1965
+ meta: {},
1966
+ ...parseNative(file.bytes, recipe.parser)
1967
+ };
1968
+ } catch (error) {
1969
+ throw new KeepsetError(`member ${file.path}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
1970
+ }
1971
+ }));
1972
+ return {
1973
+ native: {
1974
+ meta: metaOf(entry),
1975
+ members
1976
+ },
1977
+ provenance: provenanceOf({ members: files.map((file) => file.path) })
1978
+ };
1979
+ }
1980
+ const unpacked = await step(entry.id, "extract", () => unpack(bytes, archive));
1812
1981
  const parsed = await step(entry.id, "parse", () => parseNative(unpacked.bytes, recipe.parser));
1813
1982
  return {
1814
1983
  native: {
1815
- meta: metaOf(entry),
1816
- ...parsed
1984
+ ...parsed,
1985
+ meta: {
1986
+ ..."meta" in parsed ? parsed.meta : {},
1987
+ ...metaOf(entry)
1988
+ }
1817
1989
  },
1818
- provenance: {
1819
- entryId: entry.id,
1820
- catalogue: dataset.catalogue,
1821
- category: entry.category,
1822
- source: {
1823
- url: source.url,
1824
- status: source.status,
1825
- ...source.snapshot === void 0 ? {} : { snapshot: source.snapshot }
1826
- },
1827
- fetchedUrl,
1828
- retrievedAt,
1829
- sha256: createHash("sha256").update(bytes).digest("hex"),
1830
- byteLength: bytes.byteLength,
1831
- ...unpacked.member === void 0 ? {} : { member: unpacked.member }
1832
- }
1990
+ provenance: provenanceOf(unpacked.member === void 0 ? {} : { member: unpacked.member })
1833
1991
  };
1834
1992
  }
1835
1993
  /**
@@ -4,7 +4,7 @@ import { KeepsetError, NAME_SEPARATOR, countTarget, getDataset, listDatasets, li
4
4
  import { fetchDataset, roundTripDataset } from "keepset/node";
5
5
  import { parseArgs } from "node:util";
6
6
  //#region package.json
7
- var version = "0.23.0";
7
+ var version = "0.24.0";
8
8
  const usage = `Usage: keepset <command> [options]
9
9
 
10
10
  Commands:
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.23.0",
4
+ "version": "0.24.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.4/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","or/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"},{"additionalProperties":false,"properties":{"kind":{"const":"xlsx","type":"string"},"sheet":{"minLength":1,"type":"string"}},"required":["kind","sheet"],"type":"object"}]},"paging":{"oneOf":[{"additionalProperties":false,"properties":{"kind":{"const":"arcgis-query","type":"string"},"orderBy":{"pattern":"^[A-Za-z_][A-Za-z0-9_]*$","type":"string"}},"required":["kind","orderBy"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"socrata-resource","type":"string"},"pageSize":{"maximum":9007199254740991,"minimum":1,"type":"integer"}},"required":["kind","pageSize"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"ckan-datastore","type":"string"},"pageSize":{"maximum":9007199254740991,"minimum":1,"type":"integer"}},"required":["kind","pageSize"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"openfema","type":"string"},"pageSize":{"maximum":9007199254740991,"minimum":1,"type":"integer"}},"required":["kind","pageSize"],"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"},{"additionalProperties":false,"properties":{"columns":{"items":{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},"type":"array"},"delimiter":{"enum":["comma","tab","semicolon"],"type":"string"},"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"csv-table","type":"string"},"idColumn":{"anyOf":[{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},{"type":"null"}]},"nullValues":{"items":{"minLength":1,"type":"string"},"type":"array"},"numericColumns":{"items":{"additionalProperties":false,"properties":{"column":{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},"factor":{"exclusiveMinimum":0,"type":"number"}},"required":["column","factor"],"type":"object"},"type":"array"},"skipLines":{"maximum":9007199254740991,"minimum":0,"type":"integer"}},"required":["format","encoding","delimiter","skipLines","idColumn","numericColumns"],"type":"object"},{"additionalProperties":false,"properties":{"columns":{"items":{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},"minItems":1,"type":"array"},"dateColumns":{"items":{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},"type":"array"},"encoding":{"const":"utf-8","type":"string"},"format":{"const":"geojson","type":"string"},"idColumn":{"anyOf":[{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},{"type":"null"}]},"numericColumns":{"items":{"additionalProperties":false,"properties":{"column":{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},"factor":{"exclusiveMinimum":0,"type":"number"}},"required":["column","factor"],"type":"object"},"type":"array"}},"required":["format","encoding","idColumn","numericColumns","dateColumns","columns"],"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.23.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.4/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","or/v1","or-set/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":{"anyOf":[{"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":{"enum":["zip","tar","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"},{"additionalProperties":false,"properties":{"kind":{"enum":["zip","tar","tar.gz"],"type":"string"},"members":{"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","members"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"xlsx","type":"string"},"sheet":{"minLength":1,"type":"string"}},"required":["kind","sheet"],"type":"object"}]},"paging":{"oneOf":[{"additionalProperties":false,"properties":{"kind":{"const":"arcgis-query","type":"string"},"orderBy":{"pattern":"^[A-Za-z_][A-Za-z0-9_]*$","type":"string"}},"required":["kind","orderBy"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"socrata-resource","type":"string"},"pageSize":{"maximum":9007199254740991,"minimum":1,"type":"integer"}},"required":["kind","pageSize"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"ckan-datastore","type":"string"},"pageSize":{"maximum":9007199254740991,"minimum":1,"type":"integer"}},"required":["kind","pageSize"],"type":"object"},{"additionalProperties":false,"properties":{"kind":{"const":"openfema","type":"string"},"pageSize":{"maximum":9007199254740991,"minimum":1,"type":"integer"}},"required":["kind","pageSize"],"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"},{"additionalProperties":false,"properties":{"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"solomon","type":"string"}},"required":["format","encoding"],"type":"object"},{"additionalProperties":false,"properties":{"columns":{"items":{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},"type":"array"},"delimiter":{"enum":["comma","tab","semicolon"],"type":"string"},"encoding":{"enum":["utf-8","latin1"],"type":"string"},"format":{"const":"csv-table","type":"string"},"idColumn":{"anyOf":[{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},{"type":"null"}]},"nullValues":{"items":{"minLength":1,"type":"string"},"type":"array"},"numericColumns":{"items":{"additionalProperties":false,"properties":{"column":{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},"factor":{"exclusiveMinimum":0,"type":"number"}},"required":["column","factor"],"type":"object"},"type":"array"},"skipLines":{"maximum":9007199254740991,"minimum":0,"type":"integer"}},"required":["format","encoding","delimiter","skipLines","idColumn","numericColumns"],"type":"object"},{"additionalProperties":false,"properties":{"columns":{"items":{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},"minItems":1,"type":"array"},"dateColumns":{"items":{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},"type":"array"},"encoding":{"const":"utf-8","type":"string"},"format":{"const":"geojson","type":"string"},"idColumn":{"anyOf":[{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},{"type":"null"}]},"numericColumns":{"items":{"additionalProperties":false,"properties":{"column":{"pattern":"^[A-Za-z](?:[A-Za-z0-9_]*[A-Za-z0-9])?$","type":"string"},"factor":{"exclusiveMinimum":0,"type":"number"}},"required":["column","factor"],"type":"object"},"type":"array"}},"required":["format","encoding","idColumn","numericColumns","dateColumns","columns"],"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.24.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"},"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.23.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.24.0/schemas/manifest.schema.json","$ref":"#/$defs/Manifest","$schema":"https://json-schema.org/draft/2020-12/schema"}