docks66-json-schema
Yanick Champoux 2023-06-06 11:01:13 -04:00
parent 4e75fae835
commit b4293b2736
9 changed files with 163 additions and 22 deletions

View File

@ -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",

13
src/lib/jsc.ts Normal file
View File

@ -0,0 +1,13 @@
export default {
string: (args = {}) => ({ type: "string" as const, ...args }),
object: <P extends {}, A extends {}>(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: <S extends string>(type: S, args = {}) => ({ type, ...args }),
ref: <S extends string>(ref: S, args = {}) => ({ $ref: ref, ...args }),
};

View File

@ -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);

View File

@ -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<string>());
const updateIdentification = createAction("updateIdentification");

View File

@ -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<typeof identificationSchema>;

View File

@ -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<typeof reqsSchema>;

View File

@ -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");
}
});

View File

@ -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<typeof shipSchema>;
export default shipSchema;

View File

@ -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;