refactor(model): extract dashboard model package
This commit is contained in:
parent
22f50d64f9
commit
7a6ad8ab0e
35 changed files with 164 additions and 57 deletions
|
|
@ -5,13 +5,16 @@ reusable React component library.
|
|||
|
||||
This project is not a Homepage customization and does not depend on Homepage
|
||||
runtime, frontend code, or configuration. The dashboard will be model-driven:
|
||||
the reusable UI package stays content-free, while environment-specific data
|
||||
lives in validated dashboard model state inside the web app.
|
||||
the reusable UI package stays content-free, the reusable dashboard model
|
||||
package owns schema and validation, and environment-specific data lives in
|
||||
validated dashboard model state inside the web app.
|
||||
|
||||
## Workspace Layout
|
||||
|
||||
- `apps/web`: Vite React website, Bun API server, model fixtures, Drizzle
|
||||
persistence, Playwright e2e checks, and container build.
|
||||
- `packages/dashboard-model`: reusable dashboard schema, validation, and
|
||||
generic model fixtures shared by apps and tooling.
|
||||
- `packages/ui`: reusable dashboard React components, design tokens,
|
||||
shadcn/radix primitives, generic fixtures, and Storybook.
|
||||
- `docs/superpowers`: migration specs and execution plans used for this repo.
|
||||
|
|
@ -65,7 +68,7 @@ with an explicit migration error.
|
|||
## Seed Data
|
||||
|
||||
The initial Dimension Lab dashboard lives in
|
||||
`apps/web/src/lib/model/fixtures/dimensionlab.ts` as validated model data. It
|
||||
`apps/web/src/lib/dashboard-seed/dimensionlab.ts` as validated model data. It
|
||||
includes the first-screen telemetry, service groups, status strip, weather
|
||||
module, Iconify icon identifiers, links, and datasource references. Values that
|
||||
are not live yet are labeled as fallback values in the data so later datasource
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
"db:check": "drizzle-kit check"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dimensionlab/dashboard-model": "workspace:*",
|
||||
"@dimensionlab/ui": "workspace:*",
|
||||
"@sinclair/typebox": "^0.34.49",
|
||||
"ajv": "^8.20.0",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import type {
|
|||
DatasourceReference,
|
||||
ServiceEntry,
|
||||
TelemetryCard,
|
||||
} from "../schema";
|
||||
} from "@dimensionlab/dashboard-model";
|
||||
|
||||
describe("Dimension Lab dashboard seed", () => {
|
||||
test("defines the primary first-screen sections as model data", () => {
|
||||
|
|
@ -7,7 +7,7 @@ import {
|
|||
type ServiceGroup,
|
||||
type Severity,
|
||||
type TelemetryCard,
|
||||
} from "../schema";
|
||||
} from "@dimensionlab/dashboard-model";
|
||||
|
||||
type NumericValueKind = "percent" | "bytes" | "temperature" | "latency" | "number";
|
||||
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export { dimensionLabDashboardFixture } from "./dimensionlab";
|
||||
export { genericDashboardFixture } from "./generic";
|
||||
|
|
@ -74,5 +74,7 @@ function findFiles(path: string, extension: string): string[] {
|
|||
}
|
||||
|
||||
function withoutInternalPackageScope(source: string): string {
|
||||
return source.replaceAll("@dimensionlab/ui", "@internal/ui");
|
||||
return source
|
||||
.replaceAll("@dimensionlab/dashboard-model", "@internal/dashboard-model")
|
||||
.replaceAll("@dimensionlab/ui", "@internal/ui");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import { mkdtemp } from "node:fs/promises";
|
|||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, test } from "vitest";
|
||||
import type { DashboardDocument } from "$lib/model";
|
||||
import { genericDashboardFixture } from "$lib/model/fixtures/generic";
|
||||
import type { DashboardDocument } from "@dimensionlab/dashboard-model";
|
||||
import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
|
||||
import { createDashboardStore, type DashboardStore } from "$lib/server/db/dashboard-store";
|
||||
import {
|
||||
AgentConfigAuthorizationError,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import {
|
|||
type ServiceGroup,
|
||||
type StatusItem,
|
||||
type TelemetryCard,
|
||||
} from "$lib/model";
|
||||
} from "@dimensionlab/dashboard-model";
|
||||
import {
|
||||
createDashboardStore,
|
||||
DashboardRevisionNotFoundError,
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import { mkdtemp } from "node:fs/promises";
|
|||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, test } from "vitest";
|
||||
import { dimensionLabDashboardFixture } from "$lib/model/fixtures/dimensionlab";
|
||||
import { genericDashboardFixture } from "$lib/model/fixtures/generic";
|
||||
import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab";
|
||||
import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
|
||||
import { createDashboardStore, type DashboardStore } from "./db/dashboard-store";
|
||||
import { loadDashboardRuntime } from "./dashboard";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { dimensionLabDashboardFixture } from "$lib/model/fixtures/dimensionlab";
|
||||
import type { DashboardDocument } from "$lib/model";
|
||||
import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab";
|
||||
import type { DashboardDocument } from "@dimensionlab/dashboard-model";
|
||||
import {
|
||||
createDashboardStore,
|
||||
DashboardPersistenceValidationError,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { describe, expect, test, vi } from "vitest";
|
|||
import {
|
||||
DASHBOARD_SCHEMA_VERSION,
|
||||
type DashboardDocument,
|
||||
} from "$lib/model";
|
||||
} from "@dimensionlab/dashboard-model";
|
||||
import { resolveDashboardDatasources } from ".";
|
||||
|
||||
describe("dashboard datasource resolution", () => {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import type {
|
|||
StatusItem,
|
||||
StatusStrip,
|
||||
TelemetryCard,
|
||||
} from "$lib/model";
|
||||
} from "@dimensionlab/dashboard-model";
|
||||
|
||||
export interface DatasourceResolutionOptions {
|
||||
fetch?: DatasourceFetch;
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import { mkdtemp } from "node:fs/promises";
|
|||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { genericDashboardFixture } from "$lib/model/fixtures/generic";
|
||||
import type { DashboardDocument } from "$lib/model";
|
||||
import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
|
||||
import type { DashboardDocument } from "@dimensionlab/dashboard-model";
|
||||
import {
|
||||
DashboardPersistenceValidationError,
|
||||
createDashboardStore,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { desc, eq } from "drizzle-orm";
|
|||
import {
|
||||
type DashboardDocument,
|
||||
type DashboardValidationFailure,
|
||||
} from "$lib/model";
|
||||
} from "@dimensionlab/dashboard-model";
|
||||
import {
|
||||
type DashboardDatabaseConnection,
|
||||
openDashboardDatabase,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { describe, expect, test } from "vitest";
|
||||
import { DASHBOARD_SCHEMA_VERSION } from "$lib/model";
|
||||
import { genericDashboardFixture } from "$lib/model/fixtures/generic";
|
||||
import { DASHBOARD_SCHEMA_VERSION } from "@dimensionlab/dashboard-model";
|
||||
import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
|
||||
import {
|
||||
UnsupportedDashboardModelVersionError,
|
||||
migrateDashboardDocumentForPersistence,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import {
|
|||
validateDashboardDocument,
|
||||
type DashboardDocument,
|
||||
type DashboardValidationFailure,
|
||||
} from "$lib/model";
|
||||
} from "@dimensionlab/dashboard-model";
|
||||
|
||||
export interface DashboardModelMigrationSuccess {
|
||||
valid: true;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { DashboardDocument } from "$lib/model";
|
||||
import type { DashboardDocument } from "@dimensionlab/dashboard-model";
|
||||
import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
||||
|
||||
export const dashboardDocuments = sqliteTable("dashboard_documents", {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { type DashboardDocument } from "$lib/model";
|
||||
import { dimensionLabDashboardFixture } from "$lib/model/fixtures/dimensionlab";
|
||||
import { genericDashboardFixture } from "$lib/model/fixtures/generic";
|
||||
import { type DashboardDocument } from "@dimensionlab/dashboard-model";
|
||||
import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab";
|
||||
import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
|
||||
import { dashboardDocumentToUiDashboard } from "./model-renderer";
|
||||
|
||||
const appRoot = process.cwd().endsWith(`${join("apps", "web")}`)
|
||||
|
|
@ -93,6 +93,7 @@ describe("dashboard model renderer", () => {
|
|||
"utf8",
|
||||
)
|
||||
.toLowerCase()
|
||||
.replaceAll("@dimensionlab/dashboard-model", "@internal/dashboard-model")
|
||||
.replaceAll("@dimensionlab/ui", "@internal/ui");
|
||||
|
||||
expect(source).not.toContain("dimension");
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import type {
|
|||
StatusItem,
|
||||
StatusStrip,
|
||||
TelemetryCard,
|
||||
} from "$lib/model";
|
||||
} from "@dimensionlab/dashboard-model";
|
||||
import type {
|
||||
UiDashboardPreview,
|
||||
UiModuleBlock,
|
||||
|
|
|
|||
|
|
@ -88,4 +88,29 @@ describe("workspace boundaries", () => {
|
|||
expect(uiPackage.exports).toHaveProperty(".");
|
||||
expect(uiPackage.exports).toHaveProperty("./styles.css");
|
||||
});
|
||||
|
||||
test("keeps the dashboard model in a reusable internal package", () => {
|
||||
const webPackage = JSON.parse(
|
||||
readFileSync(join(root, "apps/web/package.json"), "utf8"),
|
||||
) as { dependencies?: Record<string, string> };
|
||||
const modelPackage = JSON.parse(
|
||||
readFileSync(join(root, "packages/dashboard-model/package.json"), "utf8"),
|
||||
) as { exports?: Record<string, unknown>; name?: string };
|
||||
const tsconfig = JSON.parse(
|
||||
readFileSync(join(root, "tsconfig.json"), "utf8"),
|
||||
) as { references?: Array<{ path: string }> };
|
||||
|
||||
expect(webPackage.dependencies?.["@dimensionlab/dashboard-model"]).toBe(
|
||||
"workspace:*",
|
||||
);
|
||||
expect(modelPackage.name).toBe("@dimensionlab/dashboard-model");
|
||||
expect(modelPackage.exports).toHaveProperty(".");
|
||||
expect(modelPackage.exports).toHaveProperty("./fixtures");
|
||||
expect(tsconfig.references).toEqual(
|
||||
expect.arrayContaining([{ path: "./packages/dashboard-model" }]),
|
||||
);
|
||||
expect(existsSync(join(root, "apps/web/src/lib/model/index.ts"))).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { renderToString } from "react-dom/server";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { genericDashboardFixture } from "$lib/model/fixtures/generic";
|
||||
import { genericDashboardFixture } from "@dimensionlab/dashboard-model/fixtures";
|
||||
import { AppStateView, resolveDocumentMetadata } from "./App";
|
||||
|
||||
describe("home page model renderer", () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, expect, test } from "vitest";
|
||||
import { dimensionLabDashboardFixture } from "$lib/model/fixtures/dimensionlab";
|
||||
import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab";
|
||||
import { loadDashboardResponse } from "./dashboard";
|
||||
|
||||
describe("dashboard API route", () => {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@
|
|||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@dimensionlab/dashboard-model": ["../../packages/dashboard-model/src/index.ts"],
|
||||
"@dimensionlab/dashboard-model/fixtures": [
|
||||
"../../packages/dashboard-model/src/fixtures/index.ts"
|
||||
],
|
||||
"@dimensionlab/ui": ["../../packages/ui/src/index.ts"],
|
||||
"@dimensionlab/ui/styles.css": ["../../packages/ui/src/styles.css"],
|
||||
"$lib/*": ["src/lib/*"]
|
||||
|
|
|
|||
|
|
@ -30,6 +30,12 @@ export default defineConfig({
|
|||
"@dimensionlab/ui": fileURLToPath(
|
||||
new URL("../../packages/ui/src/index.ts", import.meta.url),
|
||||
),
|
||||
"@dimensionlab/dashboard-model/fixtures": fileURLToPath(
|
||||
new URL("../../packages/dashboard-model/src/fixtures/index.ts", import.meta.url),
|
||||
),
|
||||
"@dimensionlab/dashboard-model": fileURLToPath(
|
||||
new URL("../../packages/dashboard-model/src/index.ts", import.meta.url),
|
||||
),
|
||||
$lib: fileURLToPath(new URL("./src/lib", import.meta.url)),
|
||||
},
|
||||
},
|
||||
|
|
|
|||
57
bun.lock
57
bun.lock
|
|
@ -12,6 +12,7 @@
|
|||
"name": "@dimensionlab/web",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@dimensionlab/dashboard-model": "workspace:*",
|
||||
"@dimensionlab/ui": "workspace:*",
|
||||
"@sinclair/typebox": "^0.34.49",
|
||||
"ajv": "^8.20.0",
|
||||
|
|
@ -40,6 +41,22 @@
|
|||
"vitest": "^4.1.9",
|
||||
},
|
||||
},
|
||||
"packages/dashboard-model": {
|
||||
"name": "@dimensionlab/dashboard-model",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@sinclair/typebox": "^0.34.49",
|
||||
"ajv": "^8.20.0",
|
||||
"ajv-formats": "^3.0.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.3.14",
|
||||
"@types/node": "^25.9.3",
|
||||
"bun-types": "^1.3.14",
|
||||
"typescript": "^6.0.3",
|
||||
"vitest": "^4.1.9",
|
||||
},
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@dimensionlab/ui",
|
||||
"version": "0.0.1",
|
||||
|
|
@ -137,6 +154,8 @@
|
|||
|
||||
"@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
|
||||
|
||||
"@dimensionlab/dashboard-model": ["@dimensionlab/dashboard-model@workspace:packages/dashboard-model"],
|
||||
|
||||
"@dimensionlab/ui": ["@dimensionlab/ui@workspace:packages/ui"],
|
||||
|
||||
"@dimensionlab/web": ["@dimensionlab/web@workspace:apps/web"],
|
||||
|
|
@ -671,7 +690,7 @@
|
|||
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.2", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.0" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg=="],
|
||||
|
||||
"@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="],
|
||||
"@vitest/expect": ["@vitest/expect@4.1.9", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.9", "@vitest/utils": "4.1.9", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA=="],
|
||||
|
||||
"@vitest/mocker": ["@vitest/mocker@4.1.9", "", { "dependencies": { "@vitest/spy": "4.1.9", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw=="],
|
||||
|
||||
|
|
@ -681,7 +700,7 @@
|
|||
|
||||
"@vitest/snapshot": ["@vitest/snapshot@4.1.9", "", { "dependencies": { "@vitest/pretty-format": "4.1.9", "@vitest/utils": "4.1.9", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA=="],
|
||||
|
||||
"@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="],
|
||||
"@vitest/spy": ["@vitest/spy@4.1.9", "", {}, "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA=="],
|
||||
|
||||
"@vitest/utils": ["@vitest/utils@4.1.9", "", { "dependencies": { "@vitest/pretty-format": "4.1.9", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA=="],
|
||||
|
||||
|
|
@ -745,7 +764,7 @@
|
|||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001799", "", {}, "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw=="],
|
||||
|
||||
"chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
|
||||
"chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
|
||||
|
||||
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||
|
||||
|
|
@ -873,7 +892,7 @@
|
|||
|
||||
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
|
||||
|
||||
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
|
||||
|
||||
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||
|
||||
|
|
@ -1479,6 +1498,8 @@
|
|||
|
||||
"@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
|
||||
|
||||
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg=="],
|
||||
|
|
@ -1495,14 +1516,6 @@
|
|||
|
||||
"@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
|
||||
|
||||
"@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
|
||||
|
||||
"@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="],
|
||||
|
||||
"@vitest/mocker/@vitest/spy": ["@vitest/spy@4.1.9", "", {}, "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA=="],
|
||||
|
||||
"@vitest/mocker/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
|
||||
|
||||
"body-parser/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
|
||||
|
||||
"cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
|
@ -1549,14 +1562,14 @@
|
|||
|
||||
"shadcn/open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="],
|
||||
|
||||
"storybook/@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="],
|
||||
|
||||
"storybook/@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="],
|
||||
|
||||
"string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
|
||||
|
||||
"vitest/@vitest/expect": ["@vitest/expect@4.1.9", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.9", "@vitest/utils": "4.1.9", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA=="],
|
||||
|
||||
"vitest/@vitest/spy": ["@vitest/spy@4.1.9", "", {}, "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA=="],
|
||||
|
||||
"wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
|
@ -1625,8 +1638,6 @@
|
|||
|
||||
"@oxc-resolver/binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="],
|
||||
|
||||
"@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
|
||||
|
||||
"cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
|
@ -1689,10 +1700,16 @@
|
|||
|
||||
"shadcn/open/wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="],
|
||||
|
||||
"storybook/@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
|
||||
|
||||
"storybook/@vitest/expect/chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
|
||||
|
||||
"storybook/@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="],
|
||||
|
||||
"string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"vitest/@vitest/expect/chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
|
||||
|
||||
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"storybook/@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
|
||||
}
|
||||
}
|
||||
|
|
|
|||
34
packages/dashboard-model/package.json
Normal file
34
packages/dashboard-model/package.json
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "@dimensionlab/dashboard-model",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
},
|
||||
"./fixtures": {
|
||||
"types": "./dist/fixtures/index.d.ts",
|
||||
"default": "./dist/fixtures/index.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
||||
"check": "tsc --noEmit",
|
||||
"test": "vitest run",
|
||||
"test:unit": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sinclair/typebox": "^0.34.49",
|
||||
"ajv": "^8.20.0",
|
||||
"ajv-formats": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.3.14",
|
||||
"@types/node": "^25.9.3",
|
||||
"bun-types": "^1.3.14",
|
||||
"typescript": "^6.0.3",
|
||||
"vitest": "^4.1.9"
|
||||
}
|
||||
}
|
||||
1
packages/dashboard-model/src/fixtures/index.ts
Normal file
1
packages/dashboard-model/src/fixtures/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { genericDashboardFixture } from "./generic";
|
||||
|
|
@ -5,7 +5,6 @@ import {
|
|||
validateDashboardDocument,
|
||||
} from ".";
|
||||
import {
|
||||
dimensionLabDashboardFixture,
|
||||
genericDashboardFixture,
|
||||
} from "./fixtures";
|
||||
|
||||
|
|
@ -19,12 +18,6 @@ describe("dashboard model validation", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("accepts the Dimension Lab dashboard fixture", () => {
|
||||
const result = validateDashboardDocument(dimensionLabDashboardFixture);
|
||||
|
||||
expect(result.valid).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects documents with an unsupported schema version", () => {
|
||||
const invalid = {
|
||||
...genericDashboardFixture,
|
||||
12
packages/dashboard-model/tsconfig.build.json
Normal file
12
packages/dashboard-model/tsconfig.build.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": false,
|
||||
"noEmit": false,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["dist", "node_modules", "src/**/*.test.ts"]
|
||||
}
|
||||
9
packages/dashboard-model/tsconfig.json
Normal file
9
packages/dashboard-model/tsconfig.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"types": ["node", "bun-types"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["dist", "node_modules"]
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./packages/dashboard-model" },
|
||||
{ "path": "./apps/web" },
|
||||
{ "path": "./packages/ui" }
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue