From b4293b273606a533c8f477468a3aa5951106c729 Mon Sep 17 00:00:00 2001 From: Yanick Champoux Date: Tue, 6 Jun 2023 11:01:13 -0400 Subject: [PATCH] wip --- package.json | 3 ++ src/lib/jsc.ts | 13 ++++++++ src/lib/store/ship.ts | 27 +++++++++++++++++ src/lib/store/ship/identification.ts | 18 +++++------ src/lib/store/ship/identification/schema.ts | 33 +++++++++++++++++++++ src/lib/store/ship/reqs.ts | 11 +++++++ src/lib/store/ship/schema.test.ts | 28 +++++++++++++++++ src/lib/store/ship/schema.ts | 29 ++++++++++++++++++ vite.config.js | 23 +++++++------- 9 files changed, 163 insertions(+), 22 deletions(-) create mode 100644 src/lib/jsc.ts create mode 100644 src/lib/store/ship/identification/schema.ts create mode 100644 src/lib/store/ship/reqs.ts create mode 100644 src/lib/store/ship/schema.test.ts create mode 100644 src/lib/store/ship/schema.ts diff --git a/package.json b/package.json index e7cb3ec..57f730f 100644 --- a/package.json +++ b/package.json @@ -44,11 +44,14 @@ "dependencies": { "@reduxjs/toolkit": "==2.0.0-alpha.5 ", "@yanick/updeep-remeda": "^2.2.0", + "ajv": "^8.12.0", "beercss": "^3.1.3", "git-describe": "^4.1.1", "git-repo-version": "^1.0.2", "histoire": "^0.16.1", "jsdom": "^21.1.1", + "json-schema-shorthand": "^3.0.0", + "json-schema-to-ts": "^2.9.1", "lodash": "^4.17.21", "mdsvex": "^0.10.6", "memoize-one": "^6.0.0", diff --git a/src/lib/jsc.ts b/src/lib/jsc.ts new file mode 100644 index 0000000..4e7be41 --- /dev/null +++ b/src/lib/jsc.ts @@ -0,0 +1,13 @@ +export default { + string: (args = {}) => ({ type: "string" as const, ...args }), + object:

(properties: P = {}, args: A = {}) => ({ + type: "object" as const, + properties, + ...args, + }), + boolean: (args = {}) => ({ type: "boolean" as const, ...args }), + number: (args = {}) => ({ type: "number" as const, ...args }), + integer: (args = {}) => ({ type: "integer" as const, ...args }), + type: (type: S, args = {}) => ({ type, ...args }), + ref: (ref: S, args = {}) => ({ $ref: ref, ...args }), +}; diff --git a/src/lib/store/ship.ts b/src/lib/store/ship.ts index 70d9980..bec0c03 100644 --- a/src/lib/store/ship.ts +++ b/src/lib/store/ship.ts @@ -2,6 +2,7 @@ import { createSelector } from "@reduxjs/toolkit"; import Updux, { createPayloadAction } from "updux"; import * as R from "remeda"; import memoize from "memoize-one"; +import Ajv from "ajv"; import identification from "./ship/identification"; import ftl, { calcFtlReqs } from "./ship/propulsion/ftl"; @@ -18,6 +19,7 @@ import { fireconsDux } from "./ship/weaponry/firecons"; import { adfcDux } from "./ship/weaponry/adfc"; import { weaponsDux } from "./ship/weaponry/weapons"; import { weaponryDux } from "./ship/weaponry"; +import schema from "./ship/schema"; if (typeof process !== "undefined") { process.env.UPDEEP_MODE = "dangerously_never_freeze"; @@ -64,6 +66,31 @@ const shipDux = new Updux({ }, }); +shipDux.addEffect((api) => { + const ajv = new Ajv(); + + const validate = ajv.compile(schema); + + return (next) => (action) => { + next(action); + + if (!validate(api.getState())) { + console.error( + JSON.stringify( + { + errors: validate.errors, + state: api.getState(), + action, + }, + undefined, + 2 + ) + ); + throw new Error("validation failed"); + } + }; +}); + shipDux.addMutation(restore, (state) => () => state); shipDux.addMutation(importShip, (state) => () => state); diff --git a/src/lib/store/ship/identification.ts b/src/lib/store/ship/identification.ts index bdb65f0..c726c68 100644 --- a/src/lib/store/ship/identification.ts +++ b/src/lib/store/ship/identification.ts @@ -1,17 +1,15 @@ import Updux, { createAction, withPayload } from "updux"; import u from "@yanick/updeep-remeda"; import { carrierDux } from "./carrier"; +import type { FromSchema } from "json-schema-to-ts"; +import * as j from "json-schema-shorthand"; -const initialState = { - shipType: "", - shipClass: "", - isCarrier: false, - reqs: { - mass: 10, - cost: 0, - usedMass: 0, - }, -}; +import { + identificationSchema, + type Identification, +} from "./identification/schema.js"; + +const initialState: Identification = identificationSchema.default; const setShipClass = createAction("setShipClass", withPayload()); const updateIdentification = createAction("updateIdentification"); diff --git a/src/lib/store/ship/identification/schema.ts b/src/lib/store/ship/identification/schema.ts new file mode 100644 index 0000000..4c99cd9 --- /dev/null +++ b/src/lib/store/ship/identification/schema.ts @@ -0,0 +1,33 @@ +import type { FromSchema } from "json-schema-to-ts"; +import * as j from "json-schema-shorthand"; + +import { reqsSchema } from "../reqs"; + +export const identificationSchema = j.object( + { + shipType: "string", + shipClass: "string", + isCarrier: { type: "boolean", default: false }, + reqs: { + allOf: ["#/$defs/reqs", { object: { usedMass: "number" } }], + }, + } as const, + { + additionalProperties: false, + $defs: { + reqs: reqsSchema, + }, + default: { + shipType: "", + shipClass: "", + isCarrier: false, + reqs: { + mass: 10, + cost: 0, + usedMass: 0, + }, + }, + } as const +); + +export type Identification = FromSchema; diff --git a/src/lib/store/ship/reqs.ts b/src/lib/store/ship/reqs.ts new file mode 100644 index 0000000..bbb0ce7 --- /dev/null +++ b/src/lib/store/ship/reqs.ts @@ -0,0 +1,11 @@ +import type { FromSchema, JSONSchema } from "json-schema-to-ts"; +import * as j from "json-schema-shorthand"; + +import jsc from "$lib/jsc"; + +export const reqsSchema = j.object({ + cost: "integer", + mass: "integer", +} as const); + +export type Reqs = FromSchema; diff --git a/src/lib/store/ship/schema.test.ts b/src/lib/store/ship/schema.test.ts new file mode 100644 index 0000000..2ced854 --- /dev/null +++ b/src/lib/store/ship/schema.test.ts @@ -0,0 +1,28 @@ +import schema from "./schema"; +import shipDux from "../ship"; +import Ajv from "ajv"; + +const ajv = new Ajv(); + +test("initial value is valid", () => { + const validate = ajv.compile(schema); + console.log(shipDux.initialState); + + try { + expect(validate(shipDux.initialState)).toBeTruthy(); + } catch (e) { + if (validate.errors) { + console.warn( + JSON.stringify( + { + state: shipDux.initialState, + error: validate.errors, + }, + undefined, + 2 + ) + ); + } + throw new Error("validation failed"); + } +}); diff --git a/src/lib/store/ship/schema.ts b/src/lib/store/ship/schema.ts new file mode 100644 index 0000000..1aeebb0 --- /dev/null +++ b/src/lib/store/ship/schema.ts @@ -0,0 +1,29 @@ +const todo = { + type: "object", + additionalProperties: true, +}; + +const shipSchema = { + type: "object", + properties: { + schemaVersion: { const: "1" }, + identification: { $ref: "#/$defs/identification" }, + structure: { $ref: "#/$defs/todo" }, + propulsion: { $ref: "#/$defs/todo" }, + carrier: { $ref: "#/$defs/todo" }, + weaponry: { $ref: "#/$defs/todo" }, + }, + additionalProperties: false, + $defs: { + identification: identificationSchema, + todo, + ...identificationSchema.$defs, + }, +} as const; + +import type { FromSchema } from "json-schema-to-ts"; +import { identificationSchema } from "./identification/schema"; + +type Ship = FromSchema; + +export default shipSchema; diff --git a/vite.config.js b/vite.config.js index e2e67f3..3e5d975 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,4 +1,3 @@ -// vite.config.js import { sveltekit } from "@sveltejs/kit/vite"; import packageJson from "./package.json"; import getVersion from "git-repo-version"; @@ -6,17 +5,17 @@ import git from "git-describe"; /** @type {import('vite').UserConfig} */ const config = { - plugins: [sveltekit()], - publicDir: "./static", - ssr: {}, - optimizeDeps: {}, - define: { - "import.meta.env.PACKAGE_VERSION": JSON.stringify( - git.gitDescribeSync().semverString - ), - "import.meta.env.HOMEPAGE": JSON.stringify(packageJson.homepage), - "import.meta.env.BUGS": JSON.stringify(packageJson.bugs.url), - }, + plugins: [sveltekit()], + publicDir: "./static", + ssr: {}, + optimizeDeps: {}, + define: { + "import.meta.env.PACKAGE_VERSION": JSON.stringify( + git.gitDescribeSync().semverString + ), + "import.meta.env.HOMEPAGE": JSON.stringify(packageJson.homepage), + "import.meta.env.BUGS": JSON.stringify(packageJson.bugs.url), + }, }; export default config;