diff --git a/apps/back-end/src/routes.ts b/apps/back-end/src/routes.ts
index afed75c1..40eaa87d 100644
--- a/apps/back-end/src/routes.ts
+++ b/apps/back-end/src/routes.ts
@@ -137,6 +137,18 @@ export function MykomapRouter(
return reply;
},
+ async getConfig({ params: { datasetId }, request, reply }) {
+ // Validate the parameters some more
+
+ if (!sendJson(request, reply, filePath("datasets", datasetId, "config")))
+ throw new TsRestResponseError(contract.getDataset, {
+ status: 404,
+ body: { message: `unknown datasetId '${datasetId}'` },
+ });
+
+ return reply;
+ },
+
async getVersion(req) {
return {
body: __BUILD_INFO__,
diff --git a/apps/back-end/test/data/config/dataset-A/config.json b/apps/back-end/test/data/config/dataset-A/config.json
deleted file mode 100644
index d12eeadc..00000000
--- a/apps/back-end/test/data/config/dataset-A/config.json
+++ /dev/null
@@ -1,156 +0,0 @@
-{
- "prefixes": {
- "https://dev.lod.coop/essglobal/2.1/standard/activities-ica/": "aci",
- "https://dev.lod.coop/essglobal/2.1/standard/base-membership-type/": "bmt",
- "https://dev.lod.coop/essglobal/2.1/standard/organisational-structure/": "os"
- },
- "languages": ["en", "fr"],
- "fields": {
- "uid": "value",
- "name": "value",
- "description": "value",
- "website": {
- "type": "multi",
- "of": {
- "type": "value"
- }
- },
- "dc_domains": {
- "type": "multi",
- "of": { "type": "value" }
- },
- "country_id": {
- "type": "vocab",
- "uri": "coun"
- },
- "primary_activity": {
- "type": "vocab",
- "uri": "aci",
- "titleUri": "ui:primary_activity"
- },
- "organisational_structure": {
- "type": "vocab",
- "uri": "os",
- "titleUri": "ui:organisational_structure"
- },
- "typology": {
- "type": "vocab",
- "uri": "bmt",
- "titleUri": "ui:typology"
- },
- "latitude": "value",
- "longitude": "value",
- "geocontainer_lat": "value",
- "geocontainer_lon": "value",
- "geocoded_addr": "value",
- "data_sources": {
- "type": "multi",
- "of": { "type": "vocab", "uri": "dso" }
- }
- },
- "vocabs": {
- "aci": {
- "en": {
- "title": "Economic Activity",
- "terms": {
- "ICA210": "Housing",
- "ICA220": "Transport",
- "ICA230": "Utilities"
- }
- },
- "fr": {
- "title": "Secteur économique",
- "terms": {
- "ICA210": "Logement",
- "ICA220": "Transports",
- "ICA230": "Services publics"
- }
- }
- },
- "bmt": {
- "en": {
- "title": "Typology",
- "terms": {
- "BMT10": "Consumer/Users",
- "BMT20": "Producers",
- "BMT30": "Workers"
- }
- },
- "fr": {
- "title": "Typologie",
- "terms": {
- "BMT10": "Consommateurs/usagers",
- "BMT20": "Producteurs",
- "BMT30": "Travailleurs"
- }
- }
- },
- "os": {
- "en": {
- "title": "Structure Type",
- "terms": {
- "OS60": "Workers cooperative",
- "OS80": "Consumer/User coops",
- "OS90": "Producer cooperative"
- }
- },
- "fr": {
- "title": "Type de structure",
- "terms": {
- "OS50": "Entreprise (autre)",
- "OS60": "Coopératives de travail associé",
- "OS80": "Coopératives de consommateurs",
- "OS90": "Coopératives de producteurs"
- }
- }
- },
- "coun": {
- "en": {
- "title": "Country",
- "terms": {
- "GB": "United Kingdom",
- "FR": "France"
- }
- },
- "fr": {
- "title": "Pays",
- "terms": {
- "GB": "Royaume-Uni",
- "FR": "France"
- }
- }
- },
- "dso": {
- "en": {
- "title": "Data Source",
- "terms": {
- "CUK": "Co-operatives UK",
- "DC": "DotCoop",
- "ICA": "International Cooperative Alliance",
- "NCBA": "National Cooperative Business Association (USA)"
- }
- },
- "fr": {
- "title": "Source de données",
- "terms": {
- "CUK": "Co-operatives UK",
- "DC": "DotCoop",
- "ICA": "Alliance coopérative internationale",
- "NCBA": "National Cooperative Business Association (USA)"
- }
- }
- },
- "ui": {
- "en": {
- "primary_activity": "Primary Activity",
- "organisational_structure": "Organisational Structure",
- "typology": "Typology"
- },
- "fr": {
- "primary_activity": "Activité principale",
- "organisational_structure": "Structure organisationnelle",
- "typology": "Typologie"
- }
- }
- }
-}
diff --git a/apps/back-end/test/data/datasets/dataset-A/initiatives/0.json b/apps/back-end/test/data/datasets/dataset-A/initiatives/0.json
deleted file mode 100644
index 37052eeb..00000000
--- a/apps/back-end/test/data/datasets/dataset-A/initiatives/0.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "uid": "test/cuk/R000001",
- "name": "Apples Co-op",
- "description": "We sell apples",
- "website": "https://apples.coop",
- "dc_domains": ["apples.coop", "orchards.coop"],
- "country_id": "coun:GB",
- "primary_activity": "aci:ICA210",
- "organisational_structure": "os:OS60",
- "typology": "bmt:BMT20",
- "latitude": 51.507476,
- "longitude": -0.127825,
- "geocontainer_lat": 51.50747643,
- "geocontainer_lon": -0.12782464,
- "geocoded_addr": "1 West Street, Sheffield, S1 2AB, UK",
- "data_sources": ["dso:DC", "dso:CUK"]
-}
diff --git a/apps/back-end/test/data/datasets/dataset-A/initiatives/1.json b/apps/back-end/test/data/datasets/dataset-A/initiatives/1.json
deleted file mode 100644
index bccecf94..00000000
--- a/apps/back-end/test/data/datasets/dataset-A/initiatives/1.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "uid": "test/cuk/R000002",
- "name": "Pears United",
- "description": "We sell pears",
- "website": "https://pears.coop",
- "dc_domains": ["pears.coop", "pearsunited.coop"],
- "country_id": "coun:FR",
- "primary_activity": "aci:ICA230",
- "organisational_structure": "os:OS90",
- "typology": "bmt:BMT30",
- "latitude": 50.850452,
- "longitude": 0.924728,
- "geocontainer_lat": 50.85045216,
- "geocontainer_lon": 0.92472819,
- "geocoded_addr": "79 rue de la Mare aux Carats, 34090 Montpellier, France",
- "data_sources": ["dso:DC"]
-}
diff --git a/apps/back-end/test/data/datasets/dataset-A/locations.json b/apps/back-end/test/data/datasets/dataset-A/locations.json
deleted file mode 100644
index e8996e0f..00000000
--- a/apps/back-end/test/data/datasets/dataset-A/locations.json
+++ /dev/null
@@ -1,4 +0,0 @@
-[
- [-0.12783, 51.50748],
- [0.92473, 50.85045]
-]
diff --git a/apps/back-end/test/data/datasets/dataset-A/searchable.json b/apps/back-end/test/data/datasets/dataset-A/searchable.json
deleted file mode 100644
index 20ca7235..00000000
--- a/apps/back-end/test/data/datasets/dataset-A/searchable.json
+++ /dev/null
@@ -1,16 +0,0 @@
-[
- {
- "country_id": "coun:GB",
- "primary_activity": "aci:ICA210",
- "organisational_structure": "os:OS60",
- "typology": "bmt:BMT20",
- "searchString": "test co-op 1 west street sheffield s1 2ab uk test.coop"
- },
- {
- "country_id": "coun:FR",
- "primary_activity": "aci:ICA230",
- "organisational_structure": "os:OS90",
- "typology": "bmt:BMT30",
- "searchString": "pears united 79 rue de la mare aux carats 34090 montpellier france pears.coop"
- }
-]
diff --git a/apps/back-end/test/data/datasets/test-A.json b/apps/back-end/test/data/datasets/test-A.json
new file mode 100644
index 00000000..70aaa390
--- /dev/null
+++ b/apps/back-end/test/data/datasets/test-A.json
@@ -0,0 +1 @@
+[[1,2],[3,2],[5,2],["a",3]]
diff --git a/apps/back-end/test/data/datasets/test-A/config.json b/apps/back-end/test/data/datasets/test-A/config.json
new file mode 100644
index 00000000..e116519f
--- /dev/null
+++ b/apps/back-end/test/data/datasets/test-A/config.json
@@ -0,0 +1,30 @@
+{
+ "prefixes": {
+ "https://dev.lod.coop/essglobal/2.1/standard/activities-modified/": "am"
+ },
+ "vocabs": {
+ "am": {
+ "en": {
+ "title": "Activities (Modified)",
+ "terms": {
+ "AM10": "Arts, Media, Culture & Leisure",
+ "AM20": "Campaigning, Activism & Advocacy",
+ "AM30": "Community & Collective Spaces",
+ "AM40": "Education",
+ "AM50": "Energy",
+ "AM60": "Food",
+ "AM70": "Goods & Services",
+ "AM80": "Health, Social Care & Wellbeing",
+ "AM90": "Housing",
+ "AM100": "Money & Finance",
+ "AM110": "Nature, Conservation & Environment",
+ "AM120": "Reduce, Reuse, Repair & Recycle",
+ "AM130": "Agriculture",
+ "AM140": "Industry",
+ "AM150": "Utilities",
+ "AM160": "Transport"
+ }
+ }
+ }
+ }
+}
diff --git a/apps/back-end/test/data/datasets/test-A/items/0.json b/apps/back-end/test/data/datasets/test-A/items/0.json
new file mode 100644
index 00000000..d1f40b48
--- /dev/null
+++ b/apps/back-end/test/data/datasets/test-A/items/0.json
@@ -0,0 +1 @@
+{"id":0, "a":"a:foo", "b":"b:bar", "desc": "humbug"}
diff --git a/apps/back-end/test/data/datasets/test-A/search/a:foo/b:bar/text.json b/apps/back-end/test/data/datasets/test-A/search/a:foo/b:bar/text.json
new file mode 100644
index 00000000..22fd8752
--- /dev/null
+++ b/apps/back-end/test/data/datasets/test-A/search/a:foo/b:bar/text.json
@@ -0,0 +1 @@
+[2]
diff --git a/apps/back-end/test/data/datasets/test-A/search/a:foo/text.json b/apps/back-end/test/data/datasets/test-A/search/a:foo/text.json
new file mode 100644
index 00000000..7660873d
--- /dev/null
+++ b/apps/back-end/test/data/datasets/test-A/search/a:foo/text.json
@@ -0,0 +1 @@
+[1]
diff --git a/apps/back-end/test/validation.test.ts b/apps/back-end/test/validation.test.ts
deleted file mode 100644
index 3fba5dd0..00000000
--- a/apps/back-end/test/validation.test.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-///
-
-import { expect, test } from "vitest";
-import { schemas } from "@mykomap/common";
-
-const { DatasetId, QName } = schemas;
-
-test("testing DatasetId validation", async (t) => {
- const expectTrue = ["0", "A", "z", "_", "-", "01234", "Quick-Brown-Fox_42"];
- const expectFalse = ["", " ", "/", "?", "&", ":", ".", "="];
- expectTrue.forEach((it) =>
- expect(DatasetId.safeParse(it).success, `parsing '${it}'`).toBeTruthy(),
- );
- expectFalse.forEach((it) =>
- expect(DatasetId.safeParse(it).success, `parsing '${it}'`).toBeFalsy(),
- );
-});
-
-test("testing QName validation", async (t) => {
- const expectTrue = ["a:b", "a1:b1", "_:_", "_1-.:_1-."];
- const expectFalse = [
- "",
- ":",
- "a:",
- ":a",
- "_",
- "-",
- ".",
- "&",
- ";",
- "/",
- "1:",
- ":1",
- "a:1",
- "1:a",
- "-:-",
- ];
- expectTrue.forEach((it) =>
- expect(QName.safeParse(it).success, `parsing '${it}'`).toBeTruthy(),
- );
- expectFalse.forEach((it) =>
- expect(QName.safeParse(it).success, `parsing '${it}'`).toBeFalsy(),
- );
-});
diff --git a/libs/common/package.json b/libs/common/package.json
index e46efd64..24a3b04d 100644
--- a/libs/common/package.json
+++ b/libs/common/package.json
@@ -13,11 +13,12 @@
"prebuild": "npm run generate-openapi",
"build": "vite build",
"generate-openapi": "npx vite-node src/api/generate-openapi.ts",
- "test": "echo \"Error: no test specified\" && exit 1"
+ "test": "vitest run"
},
"dependencies": {
"@ts-rest/core": "^3.51.0",
"@ts-rest/open-api": "^3.51.0",
+ "glob": "^11.0.0",
"zod": "^3.23.8"
},
"devDependencies": {
@@ -26,6 +27,7 @@
"typescript": "^5.5.4",
"vite": "^5.4.3",
"vite-node": "^2.0.5",
- "vite-plugin-dts": "^4.1.0"
+ "vite-plugin-dts": "^4.1.0",
+ "vitest": "^2.1.4"
}
}
diff --git a/libs/common/src/api/contract.ts b/libs/common/src/api/contract.ts
index 7ef8b772..3240ebb2 100644
--- a/libs/common/src/api/contract.ts
+++ b/libs/common/src/api/contract.ts
@@ -1,73 +1,55 @@
import { initContract } from "@ts-rest/core";
import { z } from "zod";
import { extendZodWithOpenApi } from "@anatine/zod-openapi";
+import * as Rx from "../rxdefs.js";
+import RxUtils from "../rxutils.js";
+import { Iso639Set1Codes } from "../iso639-1.js";
extendZodWithOpenApi(z);
const c = initContract();
-/** A regex testing for an *URL-safe* base64 string (RFC4648 sect 5) */
-const UrlSafeBase64Rx = /^[A-Z0-9_-]+$/i;
-
-/** A string regular expression matching an XML NCName
- *
- * - An NCName is an XML Name, but with no colons allowed.
- * - An XML Name is a complicated beast, but loosely a Unicode version of \w,
- * or in other words, a unicode alphanumeric symbolic identifier.
- * - It can contain digits, letters, hyphens, periods and underscores and
- * certain unicode equivalents.
- * - But it must not start with digits, a hyphen or a period, nor certain
- * unicode equivalents.
- *
- * The regex for an XML Name is adapted from O'Reilly Regex Cookbook section 8.4,
- * "XML 1.0 names (approximate)" - but the colon is removed.
- *
- * Paraphrasing that book's explanation:
- * - the name start character can be a [:_] or
- * - any of the following Unicode categories:
- * - Lowercase letter (Ll)
- * - Uppercase letter (Lu)
- * - Titlecase letter (Lt)
- * - Letter without case (Lo)
- * - Letter number (Nl)
- * - subsequent characters can also include [.-] or
- * - Mark (M)
- * - Modifier letter (Lm)
- * - Decimal digit (Nd)
+/** Helper function to generate Zod refinements from a RegExp
*
- * This definition is not compiled, as it is intended for composition below.
- * Therefore it is wrapped in a non-capturing group to isolate it without affecting
- * the captures which may be defined around it.
+ * It promotes the RegExp to be a unicode, entire-string match, if it is
+ * not already.
*
- * This regex requires node 10+ to be able to use /u and \p.
+ * It also sets the validation error message attribute from the message parameter.
*
+ * @returns a Zod validator generated by the Zod.string().refine() method
*/
-const NCName =
- "(?:[_\\p{Ll}\\p{Lu}\\p{Lt}\\p{Lo}\\p{Nl}][_.\\p{L}\\p{M}\\p{Nd}\\p{Nl}-]*)";
-
-/** Match a QName
- *
- * Paraphrasing https://en.wikipedia.org/wiki/QName
- * - A QName is an NCName (see above)
- * - Or two of them delimited by a colon.
- *
- * Note: for our purposes, we *require* a colon delimiter - it can't just be a NCName.
- * This is because we need an abbreviation with which to look up the URL prefix.
- *
- * FIXME Perhaps we should also disallow common URI scheme prefixes.
- * FIXME Maybe we don't care about unicode?
- *
- * This regex requires node 10+ to be able to use /u and \p.
- *
- */
-const QNameRx = new RegExp(`^${NCName}[:]${NCName}$`, "gsu");
+function ZodRegex(rx: RegExp, message: string) {
+ return z.string().refine((v: any) => RxUtils.uaon(rx).test(String(v)), {
+ message,
+ });
+}
const Location = z.array(z.number()).min(2).max(2);
-const DatasetId = z.string().regex(UrlSafeBase64Rx);
+const DatasetId = z.string().regex(Rx.UrlSafeBase64);
const DatasetItemId = z.coerce.number().int().nonnegative();
const DatasetItem = z.object({}).passthrough();
const Dataset = z.array(Location);
-const QName = z.string().regex(QNameRx);
+const NCName = ZodRegex(Rx.NCName, "Invalid NCName format");
+const QName = ZodRegex(Rx.QName, "Invalid QName format");
+// Developer note: PrefixUri is regex based, as it attempts to avoid the .url() deficiencies in
+// https://github.com/colinhacks/zod/issues/2236. But also our concept of a URI is narrowed, see
+// documentation for Rx.PrefixUri.
+const PrefixUri = ZodRegex(Rx.PrefixUri, "Invalid prefix URI format");
+const PrefixIndex = z.record(PrefixUri, NCName);
+// Zod.enum needs some hand-holding to be happy with using object keys, as it wants a
+// guaranteed non-zero length list
+const [lang0, ...langs] = Object.keys(Iso639Set1Codes);
+const Iso639Set1Code = z.enum([lang0, ...langs]);
+const VocabDef = z.object({
+ title: z.string(),
+ terms: z.record(NCName, z.string()),
+});
+const I18nVocabDefs = z.record(Iso639Set1Code, VocabDef);
+const VocabIndex = z.record(NCName, I18nVocabDefs);
+const ConfigData = z.object({
+ prefixes: PrefixIndex,
+ vocabs: VocabIndex,
+});
const VersionInfo = z.object({
name: z.string(),
buildTime: z.string().datetime({ offset: false }),
@@ -79,12 +61,19 @@ const ErrorInfo = z.object({ message: z.string() }).passthrough();
export const schemas = {
Location,
+ ConfigData,
DatasetId,
DatasetItemId,
DatasetItem,
Dataset,
+ I18nVocabDefs,
+ Iso639Set1Code,
+ NCName,
+ PrefixUri,
+ PrefixIndex,
QName,
VersionInfo,
+ VocabIndex,
ErrorInfo,
};
@@ -176,6 +165,32 @@ export const contract = c.router({
}),
},
},
+ getConfig: {
+ method: "GET",
+ path: "/dataset/:datasetId/config",
+ summary: "obtain various configured parameters for a map",
+ description:
+ "Obtains configured parameters for a map, which amongst other things, " +
+ "include default values for various options, and definitions of " +
+ "vocabulary terms with their localised labels, that are used to " +
+ "interpret identifers in the data and/or elsewhere.",
+ pathParams: z.object({
+ datasetId: DatasetId.openapi({
+ // description: "uniquely specifies the dataset wanted",
+ }),
+ }),
+ responses: {
+ 200: ConfigData.openapi({
+ // description: "variuos configured parameters for a map",
+ }),
+ 400: ErrorInfo.openapi({
+ // description: "bad input parameter",
+ }),
+ 404: ErrorInfo.openapi({
+ // description: "no such map",
+ }),
+ },
+ },
getVersion: {
method: "GET",
path: "/version",
diff --git a/libs/common/src/api/mykomap-openapi.json b/libs/common/src/api/mykomap-openapi.json
index b5fba42c..7bff9d9b 100644
--- a/libs/common/src/api/mykomap-openapi.json
+++ b/libs/common/src/api/mykomap-openapi.json
@@ -13,7 +13,7 @@
"required": true,
"schema": {
"type": "string",
- "pattern": "^[A-Z0-9_-]+$"
+ "pattern": "^[A-Za-z0-9_-]+$"
}
}
],
@@ -90,7 +90,7 @@
"required": true,
"schema": {
"type": "string",
- "pattern": "^[A-Z0-9_-]+$"
+ "pattern": "^[A-Za-z0-9_-]+$"
}
},
{
@@ -108,13 +108,11 @@
{
"type": "array",
"items": {
- "type": "string",
- "pattern": "^(?:[_\\p{Ll}\\p{Lu}\\p{Lt}\\p{Lo}\\p{Nl}][_.\\p{L}\\p{M}\\p{Nd}\\p{Nl}-]*)[:](?:[_\\p{Ll}\\p{Lu}\\p{Lt}\\p{Lo}\\p{Nl}][_.\\p{L}\\p{M}\\p{Nd}\\p{Nl}-]*)$"
+ "type": "string"
}
},
{
- "type": "string",
- "pattern": "^(?:[_\\p{Ll}\\p{Lu}\\p{Lt}\\p{Lo}\\p{Nl}][_.\\p{L}\\p{M}\\p{Nd}\\p{Nl}-]*)[:](?:[_\\p{Ll}\\p{Lu}\\p{Lt}\\p{Lo}\\p{Nl}][_.\\p{L}\\p{M}\\p{Nd}\\p{Nl}-]*)$"
+ "type": "string"
}
]
}
@@ -190,7 +188,7 @@
"required": true,
"schema": {
"type": "string",
- "pattern": "^[A-Z0-9_-]+$"
+ "pattern": "^[A-Za-z0-9_-]+$"
}
},
{
@@ -259,6 +257,111 @@
}
}
},
+ "/dataset/{datasetId}/config": {
+ "get": {
+ "description": "Obtains configured parameters for a map, which amongst other things, include default values for various options, and definitions of vocabulary terms with their localised labels, that are used to interpret identifers in the data and/or elsewhere.",
+ "summary": "obtain various configured parameters for a map",
+ "tags": [],
+ "parameters": [
+ {
+ "name": "datasetId",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9_-]+$"
+ }
+ }
+ ],
+ "operationId": "getConfig",
+ "responses": {
+ "200": {
+ "description": "200",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "prefixes": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+ "vocabs": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "object",
+ "properties": {
+ "title": {
+ "type": "string"
+ },
+ "terms": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "title",
+ "terms"
+ ]
+ }
+ }
+ }
+ },
+ "required": [
+ "prefixes",
+ "vocabs"
+ ]
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "400",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "message"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "404",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "message"
+ ],
+ "additionalProperties": true
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/version": {
"get": {
"description": "Obtains version information about the backend Mykomap server, in the form of a JSON object",
diff --git a/libs/common/src/iso639-1.ts b/libs/common/src/iso639-1.ts
new file mode 100644
index 00000000..87103bcb
--- /dev/null
+++ b/libs/common/src/iso639-1.ts
@@ -0,0 +1,229 @@
+/** Defines the ISO639 set-1 language codes, mapped to the English name of the language.
+ *
+ * Derived on 14/10/2024 from https://www.loc.gov/standards/iso639-2/php/English_list.php
+ *
+ * Note: sorted alphabetically, and any duplicate codes from the table have been
+ * necessarily commented out, somewhat arbitrarily (I've aimed to leave the most
+ * obvious names, which are here mainly for convenience at the moment.)
+ */
+export const Iso639Set1Codes = {
+ // ISO 639-1 code: "English Name of Language"
+ aa: "Afar",
+ ab: "Abkhazian",
+ ae: "Avestan",
+ af: "Afrikaans",
+ ak: "Akan",
+ am: "Amharic",
+ an: "Aragonese",
+ ar: "Arabic",
+ as: "Assamese",
+ av: "Avaric",
+ ay: "Aymara",
+ az: "Azerbaijani",
+ ba: "Bashkir",
+ be: "Belarusian",
+ bg: "Bulgarian",
+ bi: "Bislama",
+ bm: "Bambara",
+ bn: "Bengali",
+ bo: "Tibetan",
+ br: "Breton",
+ bs: "Bosnian",
+ ca: "Catalan",
+ //ca: "Valencian",
+ ce: "Chechen",
+ ch: "Chamorro",
+ co: "Corsican",
+ cr: "Cree",
+ cs: "Czech",
+ cu: "Church Slavic",
+ //cu: "Church Slavonic",
+ //cu: "Old Bulgarian",
+ //cu: "Old Church Slavonic",
+ //cu: "Old Slavonic",
+ cv: "Chuvash",
+ cy: "Welsh",
+ da: "Danish",
+ de: "German",
+ //dv: "Dhivehi",
+ //dv: "Divehi",
+ dv: "Maldivian",
+ dz: "Dzongkha",
+ ee: "Ewe",
+ el: "Greek, Modern (1453-)",
+ en: "English",
+ eo: "Esperanto",
+ //es: "Castilian",
+ es: "Spanish",
+ et: "Estonian",
+ eu: "Basque",
+ fa: "Persian",
+ ff: "Fulah",
+ fi: "Finnish",
+ fj: "Fijian",
+ fo: "Faroese",
+ fr: "French",
+ fy: "Western Frisian",
+ ga: "Irish",
+ //gd: "Gaelic",
+ gd: "Scottish Gaelic",
+ gl: "Galician",
+ gn: "Guarani",
+ gu: "Gujarati",
+ gv: "Manx",
+ ha: "Hausa",
+ he: "Hebrew",
+ hi: "Hindi",
+ ho: "Hiri Motu",
+ hr: "Croatian",
+ //ht: "Haitian Creole",
+ ht: "Haitian",
+ hu: "Hungarian",
+ hy: "Armenian",
+ hz: "Herero",
+ ia: "Interlingua (International Auxiliary Language Association)",
+ id: "Indonesian",
+ ie: "Interlingue",
+ //ie: "Occidental",
+ ig: "Igbo",
+ //ii: "Nuosu",
+ ii: "Sichuan Yi",
+ ik: "Inupiaq",
+ io: "Ido",
+ is: "Icelandic",
+ it: "Italian",
+ iu: "Inuktitut",
+ ja: "Japanese",
+ jv: "Javanese",
+ ka: "Georgian",
+ kg: "Kongo",
+ //ki: "Gikuyu",
+ ki: "Kikuyu",
+ kj: "Kuanyama",
+ //kj: "Kwanyama",
+ kk: "Kazakh",
+ kl: "Greenlandic",
+ //kl: "Kalaallisut",
+ km: "Central Khmer",
+ kn: "Kannada",
+ ko: "Korean",
+ kr: "Kanuri",
+ ks: "Kashmiri",
+ ku: "Kurdish",
+ kv: "Komi",
+ kw: "Cornish",
+ //ky: "Kirghiz",
+ ky: "Kyrgyz",
+ la: "Latin",
+ //lb: "Letzeburgesch",
+ lb: "Luxembourgish",
+ lg: "Ganda",
+ li: "Limburgan",
+ //li: "Limburger",
+ //li: "Limburgish",
+ ln: "Lingala",
+ lo: "Lao",
+ lt: "Lithuanian",
+ lu: "Luba-Katanga",
+ lv: "Latvian",
+ mg: "Malagasy",
+ mh: "Marshallese",
+ mi: "Maori",
+ mk: "Macedonian",
+ ml: "Malayalam",
+ mn: "Mongolian",
+ mr: "Marathi",
+ ms: "Malay",
+ mt: "Maltese",
+ my: "Burmese",
+ na: "Nauru",
+ //nb: "Bokmål, Norwegian",
+ nb: "Norwegian Bokmål",
+ //nd: "Ndebele, North",
+ nd: "North Ndebele",
+ ne: "Nepali",
+ ng: "Ndonga",
+ nl: "Dutch",
+ //nl: "Flemish",
+ nn: "Norwegian Nynorsk",
+ //nn: "Nynorsk, Norwegian",
+ no: "Norwegian",
+ //nr: "Ndebele, South",
+ nr: "South Ndebele",
+ //nv: "Navaho",
+ nv: "Navajo",
+ ny: "Chewa",
+ //ny: "Chichewa",
+ //ny: "Nyanja",
+ oc: "Occitan (post 1500)",
+ oj: "Ojibwa",
+ om: "Oromo",
+ or: "Oriya",
+ os: "Ossetian",
+ //os: "Ossetic",
+ //pa: "Panjabi",
+ pa: "Punjabi",
+ pi: "Pali",
+ pl: "Polish",
+ ps: "Pashto",
+ //ps: "Pushto",
+ pt: "Portuguese",
+ qu: "Quechua",
+ rm: "Romansh",
+ rn: "Rundi",
+ //ro: "Moldavian",
+ //ro: "Moldovan",
+ ro: "Romanian",
+ ru: "Russian",
+ rw: "Kinyarwanda",
+ sa: "Sanskrit",
+ sc: "Sardinian",
+ sd: "Sindhi",
+ se: "Northern Sami",
+ sg: "Sango",
+ //si: "Sinhala",
+ si: "Sinhalese",
+ sk: "Slovak",
+ sl: "Slovenian",
+ sm: "Samoan",
+ sn: "Shona",
+ so: "Somali",
+ sq: "Albanian",
+ sr: "Serbian",
+ ss: "Swati",
+ st: "Sotho, Southern",
+ su: "Sundanese",
+ sv: "Swedish",
+ sw: "Swahili",
+ ta: "Tamil",
+ te: "Telugu",
+ tg: "Tajik",
+ th: "Thai",
+ ti: "Tigrinya",
+ tk: "Turkmen",
+ tl: "Tagalog",
+ tn: "Tswana",
+ to: "Tonga (Tonga Islands)",
+ tr: "Turkish",
+ ts: "Tsonga",
+ tt: "Tatar",
+ tw: "Twi",
+ ty: "Tahitian",
+ ug: "Uighur",
+ //ug: "Uyghur",
+ uk: "Ukrainian",
+ ur: "Urdu",
+ uz: "Uzbek",
+ ve: "Venda",
+ vi: "Vietnamese",
+ vo: "Volapük",
+ wa: "Walloon",
+ wo: "Wolof",
+ xh: "Xhosa",
+ yi: "Yiddish",
+ yo: "Yoruba",
+ //za: "Chuang",
+ za: "Zhuang",
+ zh: "Chinese",
+ zu: "Zulu",
+} as const;
diff --git a/libs/common/src/rxdefs.ts b/libs/common/src/rxdefs.ts
new file mode 100644
index 00000000..e9eb0582
--- /dev/null
+++ b/libs/common/src/rxdefs.ts
@@ -0,0 +1,133 @@
+/** Regular expression definitions */
+import RxUtils from "./rxutils.js";
+const { min, seq, conc, maybe } = RxUtils;
+
+/** A regex testing for an *URL-safe* base64 string (RFC4648 sect 5) */
+export const UrlSafeBase64 = /^[A-Za-z0-9_-]+$/imsu;
+
+/** A regular expression matching an XML NCName
+ *
+ * - An NCName is an XML Name, but with no colons allowed.
+ * - An XML Name is a complicated beast, but loosely a Unicode version of \w,
+ * or in other words, a unicode alphanumeric symbolic identifier.
+ * - It can contain digits, letters, hyphens, periods and underscores and
+ * certain unicode equivalents.
+ * - But it must not start with digits, a hyphen or a period, nor certain
+ * unicode equivalents.
+ *
+ * The regex for an XML Name is adapted from O'Reilly Regex Cookbook section 8.4,
+ * "XML 1.0 names (approximate)" - but the colon is removed.
+ *
+ * Paraphrasing that book's explanation:
+ * - the name start character can be a [:_] or
+ * - any of the following Unicode categories:
+ * - Lowercase letter (Ll)
+ * - Uppercase letter (Lu)
+ * - Titlecase letter (Lt)
+ * - Letter without case (Lo)
+ * - Letter number (Nl)
+ * - subsequent characters can also include [.-] or
+ * - Mark (M)
+ * - Modifier letter (Lm)
+ * - Decimal digit (Nd)
+ *
+ * This definition is is wrapped in a non-capturing group to isolate it without affecting
+ * captures which may be defined around it in derived regular expressions.
+ *
+ * This regex requires node 10+ to be able to use /u and \p.
+ *
+ */
+export const NCName =
+ /(?:[_\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nl}][_.\p{L}\p{M}\p{Nd}\p{Nl}-]*)/imsu;
+
+/** Match a QName
+ *
+ * Paraphrasing https://en.wikipedia.org/wiki/QName
+ * - A QName is an NCName (see above)
+ * - Or two of them delimited by a colon.
+ *
+ * Note: for our purposes, we *require* a colon delimiter - it can't just be a NCName.
+ * This is because we need an abbreviation with which to look up the URL prefix.
+ *
+ * This definition is is wrapped in a non-capturing group to isolate it without affecting
+ * captures which may be defined around it in derived regular expressions.
+ *
+ * FIXME Perhaps we should also disallow common URI scheme prefixes.
+ * FIXME Maybe we don't care about unicode?
+ *
+ * This regex requires node 10+ to be able to use /u and \p.
+ *
+ */
+export const QName = new RegExp(`${NCName.source}[:]${NCName.source}`, "imsu");
+
+/** A Prefix-URI scheme.
+ *
+ * Note, limited to http and https.
+ */
+const Scheme = /https?:\/\//;
+
+/** A domain name component.
+ *
+ * A letter, optionally followed by letters, digits or hyphens, but ending with a letter or digit.
+ *
+ * Based on https://datatracker.ietf.org/doc/html/rfc1035#section-2.3.1
+ */
+const DomainLabel = /[A-Za-z](?:[A-Za-z0-9-]*[A-Za-z0-9])?/;
+
+/** A domain name.
+ *
+ * A domain label optionally followed by more domain labels, each prefixed with a period
+ *
+ * Based on https://datatracker.ietf.org/doc/html/rfc1035#section-2.3.1
+ */
+const Domain = seq(DomainLabel, min(0, seq("[.]", DomainLabel)));
+
+/** A percent-encoded URI character. */
+const PctEnc = /%[0-9A-Fa-f]{2}/;
+
+// URI reserved characters
+// reserved /[:/?#\[\]@!$&'()*+,;=]/;
+
+/** A valid (non-percent-encoded) URI path character.
+ *
+ * Path characters can be any of:
+ * - unreserved chars /[A-Za-z0-9._~-]/
+ * - percent-encoded chars i./%[A-Za-z0-9]{2}/
+ * - sub-delims chars /[!$&'()*+,;=]/
+ * - colon or at chars /[:@]/
+ *
+ * Based on https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
+ */
+const PathChar = /[A-Za-z0-9._~!$&'()*+,;=:@-]/;
+
+/** A segment of a URI path, without '/' delimiters. */
+const PathSegment = min(1, PctEnc, PathChar);
+
+// This is roughly based on the RFC's "path-abempty" syntax - the other kinds
+// seem not to fit a http URL, or be special cases of path-abempty
+// (path-absolute, path-noscheme, path-rootless, path-empty)
+const Path = seq(min(0, seq("/", PathSegment)), maybe("/"));
+
+/** Matches a Prefix URI.
+ *
+ * A prefix URI is one which is a common prefix to a linked-data SKOS vocab
+ * namespace, i.e. one to which the term IDs can be suffixed to, in order to get
+ * a full URI for the term. It also tends to be a URI for the SKOS vocab as a
+ * whole.
+ *
+ * Constructed based on the RFC3986 spec, narrowed for our purposes.
+ * https://datatracker.ietf.org/doc/html/rfc3986
+ *
+ * This does not match the full specification of an URI as outloned in RFC3986,
+ * as we have more restrictive ideas of a valid URI. Specifically:
+ * - no local URIs, like http://localhost
+ * - no IPv6 ot IPv6 hostnames
+ * - no port address, like http://example.com:1234
+ * - no user or password, like http://joe:secret@example.com
+ * - only http or https schemes (not ftp, gopher, ldap, etc; also we insist on lower case)
+ * - no querystring, like http://example.com/somewhere?querystring
+ * - a trailing anchor character ("#") is allowed, but nothing more (the URI needs to be
+ * ready for anchor IDs to be suffixed)
+ *
+ */
+export const PrefixUri = conc(Scheme, Domain, Path, maybe("#"));
diff --git a/libs/common/src/rxutils.ts b/libs/common/src/rxutils.ts
new file mode 100644
index 00000000..190cdd3a
--- /dev/null
+++ b/libs/common/src/rxutils.ts
@@ -0,0 +1,91 @@
+/** A collection of Regular expression building utilities. */
+const RxUtils = (() => {
+ /** Map a string or RegExp to a string */
+ const _str = (component: string | RegExp) =>
+ component instanceof RegExp ? component.source : component;
+
+ /** Wrap patterns in a non-capturing group, separated by a delimiter
+ *
+ * If there are more than one pattern, each one is also wrapped in a non-capturing group.
+ *
+ * Note, unlike the other functions here, this one is for internal use and
+ * returns a string, not a RegExp.
+ */
+ const _wrap = (
+ delim: "" | "|",
+ ...components: Array
+ ): string => {
+ switch (components.length) {
+ case 0:
+ return "";
+ case 1:
+ return `(?:${_str(components[0])})`;
+ default:
+ return (
+ "(?:" + components.map((c) => `(?:${_str(c)})`).join(delim) + ")"
+ );
+ }
+ };
+
+ /** Wrap patterns in a non-capturing group, separated by a delimiter.
+ *
+ * If there are more than one pattern, each one is also wrapped in a non-capturing group.
+ */
+ const wrap = (
+ delim: "" | "|",
+ ...components: Array
+ ): RegExp => new RegExp(_wrap(delim, ...components));
+
+ /** Simply concatenate the patterns passed, no grouping or delimiter */
+ const conc = (...components: Array): RegExp =>
+ new RegExp(components.map((c) => _str(c)).join(""));
+
+ /** Match the patterns passed as a sequence */
+ const seq = (...components: Array): RegExp =>
+ new RegExp(_wrap("", ...components));
+
+ /** Match just one of the patterns passed */
+ const oneOf = (...components: Array): RegExp =>
+ new RegExp(_wrap("|", ...components));
+
+ /** Match at least n of one the patterns passed */
+ const min = (n: 0 | 1, ...components: Array): RegExp => {
+ const expr = _wrap("|", ...components);
+ switch (n) {
+ case 0:
+ return new RegExp(`${expr}*`);
+ case 1:
+ return new RegExp(`${expr}+`);
+ }
+ };
+
+ /** Matches zero or one of the patterns passed */
+ const maybe = (...components: Array): RegExp => {
+ const expr = _wrap("|", ...components);
+ return new RegExp(`${expr}?`);
+ };
+
+ /** Match unicode, all or nothing.
+ *
+ * Ensures the regular expression has msu flags and is anchored to match all
+ * of a string or nothing.
+ */
+ const uaon = (component: string | RegExp): RegExp => {
+ let [expr, flags] =
+ component instanceof RegExp
+ ? [component.source, component.flags]
+ : [component, ""];
+
+ expr = expr.replace(/^\^?/ms, "^"); // ensure anchor at start
+ expr = expr.replace(/\$?$/ms, "$"); // ensure anchor at end
+
+ // Extend and deduplicate flags
+ flags = [...new Set((flags + "msu").split(""))].join("");
+
+ return new RegExp(expr, flags);
+ };
+
+ return { conc, maybe, min, seq, oneOf, uaon, wrap };
+})();
+
+export default RxUtils;
diff --git a/libs/common/test/data/validation/ConfigData/case-1.good.json b/libs/common/test/data/validation/ConfigData/case-1.good.json
new file mode 100644
index 00000000..e116519f
--- /dev/null
+++ b/libs/common/test/data/validation/ConfigData/case-1.good.json
@@ -0,0 +1,30 @@
+{
+ "prefixes": {
+ "https://dev.lod.coop/essglobal/2.1/standard/activities-modified/": "am"
+ },
+ "vocabs": {
+ "am": {
+ "en": {
+ "title": "Activities (Modified)",
+ "terms": {
+ "AM10": "Arts, Media, Culture & Leisure",
+ "AM20": "Campaigning, Activism & Advocacy",
+ "AM30": "Community & Collective Spaces",
+ "AM40": "Education",
+ "AM50": "Energy",
+ "AM60": "Food",
+ "AM70": "Goods & Services",
+ "AM80": "Health, Social Care & Wellbeing",
+ "AM90": "Housing",
+ "AM100": "Money & Finance",
+ "AM110": "Nature, Conservation & Environment",
+ "AM120": "Reduce, Reuse, Repair & Recycle",
+ "AM130": "Agriculture",
+ "AM140": "Industry",
+ "AM150": "Utilities",
+ "AM160": "Transport"
+ }
+ }
+ }
+ }
+}
diff --git a/libs/common/test/file-utils.ts b/libs/common/test/file-utils.ts
new file mode 100644
index 00000000..63b1d48d
--- /dev/null
+++ b/libs/common/test/file-utils.ts
@@ -0,0 +1,42 @@
+import { readFileSync } from "node:fs";
+import { readFile } from "node:fs/promises";
+
+/** Read a file asynchronously, given its path
+ */
+async function slurp(
+ path: string,
+ opts: {
+ enc?: BufferEncoding;
+ } = {},
+): Promise {
+ return readFile(path, opts.enc ?? "utf8").then((it) => it.toString());
+}
+
+/** Read a file synchronously, given its path
+ */
+function slurpSync(
+ path: string,
+ opts: {
+ enc?: BufferEncoding;
+ } = {},
+): string {
+ return readFileSync(path, opts.enc ?? "utf8").toString();
+}
+
+/** Read in a JSON file asynchronously, given its path. */
+async function slurpJson(
+ path: string,
+ reviver?: (this: any, key: any, value: any) => any,
+): Promise {
+ return slurp(path).then((x) => JSON.parse(x, reviver));
+}
+
+/** Read in a JSON file synchronously, given its path. */
+function slurpJsonSync(
+ path: string,
+ reviver?: (this: any, key: any, value: any) => any,
+): Promise {
+ return JSON.parse(slurpSync(path), reviver);
+}
+
+export { slurp, slurpSync, slurpJson, slurpJsonSync };
diff --git a/libs/common/test/validation.test.ts b/libs/common/test/validation.test.ts
new file mode 100644
index 00000000..f5feb9d1
--- /dev/null
+++ b/libs/common/test/validation.test.ts
@@ -0,0 +1,183 @@
+///
+
+import { expect, test, Assertion } from "vitest";
+import { schemas } from "../src/index.js";
+import { ZodType } from "zod";
+import { slurpJsonSync } from "./file-utils.js";
+import { globSync } from "glob";
+import { join } from "node:path";
+
+const { DatasetId, QName, PrefixUri, Iso639Set1Code, ConfigData } = schemas;
+
+/** Creates expectations on validating each of an array of cases
+ *
+ * Mainly here to provide the common part of expectValid and expectInvalid.
+ */
+function expectSafeParse(
+ validation: Z,
+ cases: unknown[],
+ expectTest: (assert: Assertion) => void,
+) {
+ cases.forEach((it) => {
+ const result = validation.safeParse(it);
+ expectTest(expect(result.success, `parsing '${it}'...\n${result.error}`));
+ });
+}
+
+/** Expect Zod validations of each of the cases given to be truthy */
+function expectValid(validation: Z, cases: unknown[]) {
+ expectSafeParse(validation, cases, (a) => a.toBeTruthy());
+}
+
+/** Expect Zod validations of each of the cases given to be falsy */
+function expectInvalid(validation: Z, cases: unknown[]) {
+ expectSafeParse(validation, cases, (a) => a.toBeFalsy());
+}
+
+/** Find files given by a glob and parse as JSON */
+function glorpJson(pathGlob: string) {
+ const paths = globSync(pathGlob).map((path) =>
+ join(import.meta.dirname, path),
+ );
+ return paths.map((path) => slurpJsonSync(path));
+}
+
+test("testing DatasetId validation", async (t) => {
+ expectValid(DatasetId, [
+ "0",
+ "A",
+ "z",
+ "_",
+ "-",
+ "01234",
+ "Quick-Brown-Fox_42",
+ ]);
+ expectInvalid(DatasetId, ["", " ", "/", "?", "&", ":", ".", "="]);
+});
+
+test("testing QName validation", async (t) => {
+ expectValid(QName, ["a:b", "a1:b1", "_:_", "_1-.:_1-."]);
+ expectInvalid(QName, [
+ "",
+ ":",
+ "a:",
+ ":a",
+ "_",
+ "-",
+ ".",
+ "&",
+ ";",
+ "/",
+ "1:",
+ ":1",
+ "a:1",
+ "1:a",
+ "-:-",
+ ]);
+});
+
+test("testing PrefixUri validation", async (t) => {
+ expectValid(PrefixUri, [
+ "http://a",
+ "http://a/",
+ "http://e.a",
+ "http://e.a/",
+ "http://example.com",
+ "http://EXAMPLE.COM",
+ "http://example.Com",
+ "https://example.com",
+ "http://www.example.com",
+ "http://www.example.com/",
+ "https://w3-example/",
+ "https://w3-example1.com/",
+ "http://example.com#",
+ "http://example.com/#",
+ "http://example.com/foo",
+ "http://example.com/foo/",
+ "http://example.com/foo/bar",
+ "http://example.com/foo/bar/",
+ "http://example.com/foo#",
+ "http://example.com/foo/#",
+ "http://example.com/foo/bar#",
+ "http://example.com/foo/bar/#",
+ "http://example.com/%2e%4F",
+ "http://example.com/A-Za-z0-9._~!$&'()*+,;=:@-%20/",
+ ]);
+ expectInvalid(PrefixUri, [
+ "http://",
+ "http://-",
+ "http://.",
+ "http://.a",
+ "http://-a",
+ "http://a-",
+ "http://a.",
+ "http://3a.com",
+ "http://a.3com",
+ "http://-a.com",
+ "http://a-.com",
+ "http://a.-com",
+ "http://a.-com",
+ "http://a.com-",
+ "http://a_b",
+ "http://a_b.c",
+ "http://a@b",
+ "http://a@b.c",
+ "http://a:b.c",
+ "http://b.c:8000",
+ "HTTP://example.com",
+ "Http://example.com",
+ "htt://example.com",
+ "httpss://example.com",
+ "http//example.com",
+ "http:/example.com",
+ "http:///example.com",
+ "http//:/example.com",
+ "http/example.com",
+ "http/:example.com",
+ "http//:example.com",
+ "http://example.com?",
+ "http://example.com/?",
+ "http://example.com?q",
+ "http://example.com/?q",
+ "http://example.com/foo?q",
+ "http://example.com/foo/?q",
+ "http://example.com/#?",
+ "http://example.com#?",
+ "http://example.com#q",
+ "http://example.com/#q",
+ 'http://example.com/foo"bar',
+ "http://example.com//foobar",
+ "http://example.com/foo//bar",
+ "http://example.com/foobar//",
+ ]);
+});
+
+test("testing Iso639Set1Code validation", async (t) => {
+ expectValid(Iso639Set1Code, ["en", "fr", "ko", "es"]);
+ expectInvalid(Iso639Set1Code, [
+ "xe",
+ "En",
+ "eN",
+ "EN",
+ "enn",
+ "en ",
+ " en",
+ "e ",
+ "e n",
+ " ",
+ "e_",
+ "_e",
+ "e-",
+ "-e",
+ "__",
+ "--",
+ "'en'",
+ "en:",
+ "e:",
+ ]);
+});
+
+test("testing ConfigData validation", async (t) => {
+ expectValid(ConfigData, glorpJson("data/validation/vocabIndex/*.good.json"));
+ expectInvalid(ConfigData, glorpJson("data/validation/vocabIndex/*.bad.json"));
+});
diff --git a/package-lock.json b/package-lock.json
index dd961ecf..53bf883a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4,6 +4,7 @@
"requires": true,
"packages": {
"": {
+ "name": "mykomap-monolith",
"workspaces": [
"apps/*",
"libs/*"
@@ -115,6 +116,7 @@
"dependencies": {
"@ts-rest/core": "^3.51.0",
"@ts-rest/open-api": "^3.51.0",
+ "glob": "^11.0.0",
"zod": "^3.23.8"
},
"devDependencies": {
@@ -123,7 +125,142 @@
"typescript": "^5.5.4",
"vite": "^5.4.3",
"vite-node": "^2.0.5",
- "vite-plugin-dts": "^4.1.0"
+ "vite-plugin-dts": "^4.1.0",
+ "vitest": "^2.1.4"
+ }
+ },
+ "libs/common/node_modules/@vitest/expect": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.4.tgz",
+ "integrity": "sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "2.1.4",
+ "@vitest/utils": "2.1.4",
+ "chai": "^5.1.2",
+ "tinyrainbow": "^1.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "libs/common/node_modules/@vitest/runner": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.4.tgz",
+ "integrity": "sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "2.1.4",
+ "pathe": "^1.1.2"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "libs/common/node_modules/@vitest/snapshot": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.4.tgz",
+ "integrity": "sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "2.1.4",
+ "magic-string": "^0.30.12",
+ "pathe": "^1.1.2"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "libs/common/node_modules/@vitest/spy": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.4.tgz",
+ "integrity": "sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyspy": "^3.0.2"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "libs/common/node_modules/tinypool": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz",
+ "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
+ "libs/common/node_modules/vitest": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.4.tgz",
+ "integrity": "sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "2.1.4",
+ "@vitest/mocker": "2.1.4",
+ "@vitest/pretty-format": "^2.1.4",
+ "@vitest/runner": "2.1.4",
+ "@vitest/snapshot": "2.1.4",
+ "@vitest/spy": "2.1.4",
+ "@vitest/utils": "2.1.4",
+ "chai": "^5.1.2",
+ "debug": "^4.3.7",
+ "expect-type": "^1.1.0",
+ "magic-string": "^0.30.12",
+ "pathe": "^1.1.2",
+ "std-env": "^3.7.0",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^0.3.1",
+ "tinypool": "^1.0.1",
+ "tinyrainbow": "^1.2.0",
+ "vite": "^5.0.0",
+ "vite-node": "2.1.4",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "@vitest/browser": "2.1.4",
+ "@vitest/ui": "2.1.4",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
}
},
"node_modules/@adobe/css-tools": {
@@ -3626,7 +3763,6 @@
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
- "dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
@@ -6849,10 +6985,67 @@
"@types/estree": "^1.0.0"
}
},
+ "node_modules/@vitest/mocker": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.4.tgz",
+ "integrity": "sha512-Ky/O1Lc0QBbutJdW0rqLeFNbuLEyS+mIPiNdlVlp2/yhJ0SbyYqObS5IHdhferJud8MbbwMnexg4jordE5cCoQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "2.1.4",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.12"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/mocker/node_modules/@types/estree": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
+ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@vitest/mocker/node_modules/@vitest/spy": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.4.tgz",
+ "integrity": "sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyspy": "^3.0.2"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker/node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
"node_modules/@vitest/pretty-format": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.2.tgz",
- "integrity": "sha512-FIoglbHrSUlOJPDGIrh2bjX1sNars5HbxlcsFKCtKzu4+5lpsRhOCVcuzp0fEhAGHkPZRIXVNzPcpSlkoZ3LuA==",
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.4.tgz",
+ "integrity": "sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -7034,14 +7227,14 @@
}
},
"node_modules/@vitest/utils": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.2.tgz",
- "integrity": "sha512-zMO2KdYy6mx56btx9JvAqAZ6EyS3g49krMPPrgOp1yxGZiA93HumGk+bZ5jIZtOg5/VBYl5eBmGRQHqq4FG6uQ==",
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.4.tgz",
+ "integrity": "sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "2.1.2",
- "loupe": "^3.1.1",
+ "@vitest/pretty-format": "2.1.4",
+ "loupe": "^3.1.2",
"tinyrainbow": "^1.2.0"
},
"funding": {
@@ -7290,7 +7483,6 @@
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -7303,7 +7495,6 @@
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -7782,7 +7973,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true,
"license": "MIT"
},
"node_modules/base64-js": {
@@ -7887,7 +8077,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
@@ -8123,9 +8312,9 @@
"license": "CC-BY-4.0"
},
"node_modules/chai": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz",
- "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==",
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz",
+ "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8520,9 +8709,9 @@
"license": "MIT"
},
"node_modules/cookie": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
- "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
+ "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"dev": true,
"license": "MIT",
"engines": {
@@ -8607,7 +8796,6 @@
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
- "dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
@@ -9021,7 +9209,6 @@
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
- "dev": true,
"license": "MIT"
},
"node_modules/editorconfig": {
@@ -9061,7 +9248,6 @@
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true,
"license": "MIT"
},
"node_modules/encodeurl": {
@@ -10323,10 +10509,20 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/expect-type": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz",
+ "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/express": {
- "version": "4.21.0",
- "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
- "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
+ "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10335,7 +10531,7 @@
"body-parser": "1.20.3",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
- "cookie": "0.6.0",
+ "cookie": "0.7.1",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
@@ -10914,7 +11110,6 @@
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
"integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
- "dev": true,
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.0",
@@ -11191,7 +11386,6 @@
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz",
"integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==",
- "dev": true,
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
@@ -11228,7 +11422,6 @@
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
"integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
- "dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
@@ -12042,7 +12235,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -12343,7 +12535,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "dev": true,
"license": "ISC"
},
"node_modules/isobject": {
@@ -12454,7 +12645,6 @@
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz",
"integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==",
- "dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
@@ -12846,7 +13036,6 @@
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz",
"integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==",
- "dev": true,
"license": "ISC",
"engines": {
"node": "20 || >=22"
@@ -12863,9 +13052,9 @@
}
},
"node_modules/magic-string": {
- "version": "0.30.11",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz",
- "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==",
+ "version": "0.30.12",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz",
+ "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -13140,7 +13329,6 @@
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
- "dev": true,
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
@@ -13674,7 +13862,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
- "dev": true,
"license": "BlueOak-1.0.0"
},
"node_modules/parent-module": {
@@ -13774,7 +13961,6 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -13790,7 +13976,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz",
"integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==",
- "dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^11.0.0",
@@ -15627,7 +15812,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
@@ -15640,7 +15824,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -15676,7 +15859,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
"license": "ISC",
"engines": {
"node": ">=14"
@@ -15943,7 +16125,6 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
@@ -15962,7 +16143,6 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
@@ -15977,7 +16157,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -15987,14 +16166,12 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
"license": "MIT"
},
"node_modules/string-width-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
@@ -16108,7 +16285,6 @@
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
@@ -16125,7 +16301,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
@@ -16138,7 +16313,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -16600,6 +16774,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/tinyexec": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz",
+ "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/tinypool": {
"version": "0.8.4",
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz",
@@ -17389,14 +17570,14 @@
}
},
"node_modules/vite-node": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.2.tgz",
- "integrity": "sha512-HPcGNN5g/7I2OtPjLqgOtCRu/qhVvBxTUD3qzitmL0SrG1cWFzxzhMDWussxSbrRYWqnKf8P2jiNhPMSN+ymsQ==",
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.4.tgz",
+ "integrity": "sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg==",
"dev": true,
"license": "MIT",
"dependencies": {
"cac": "^6.7.14",
- "debug": "^4.3.6",
+ "debug": "^4.3.7",
"pathe": "^1.1.2",
"vite": "^5.0.0"
},
@@ -18271,7 +18452,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
@@ -18397,7 +18577,6 @@
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
@@ -18416,7 +18595,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
@@ -18434,7 +18612,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -18444,7 +18621,6 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
@@ -18460,14 +18636,12 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
"license": "MIT"
},
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
@@ -18482,7 +18656,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"