diff --git a/README.md b/README.md
index 6e6339e..762d761 100644
--- a/README.md
+++ b/README.md
@@ -72,7 +72,7 @@ load `apps/extension/.output/chrome-mv3` unpacked.
```
apps/extension/ Chrome extension: recorder + editor (WXT + React)
-packages/schema/ Demo format: Zod schema + TypeScript types
+packages/schema/ Demo format: Valibot schema + TypeScript types
packages/player-core/ Framework-agnostic playback engine
packages/exporter/ Builds the exported bundle in the browser
adapters/player-react/ for React
diff --git a/packages/exporter/vitest.config.ts b/packages/exporter/vitest.config.ts
index b71cb7e..ca96bc2 100644
--- a/packages/exporter/vitest.config.ts
+++ b/packages/exporter/vitest.config.ts
@@ -5,7 +5,7 @@ import { defineConfig } from "vitest/config";
// (browser-only APIs), and buildDemoBundle calls into it for any non-webp photo step.
export default defineConfig({
// Pre-bundle up front — an on-demand optimize mid-run forces Vite to reload the page,
- // breaking whatever test was in flight (same issue player-core hit with zod).
+ // breaking whatever test was in flight (same issue player-core hit with the schema's validator).
optimizeDeps: { include: ["jszip"] },
test: {
include: ["src/__test__/unit/**/*.test.ts"],
diff --git a/packages/player-core/CLAUDE.md b/packages/player-core/CLAUDE.md
index a4312dc..5bea660 100644
--- a/packages/player-core/CLAUDE.md
+++ b/packages/player-core/CLAUDE.md
@@ -6,7 +6,7 @@ Framework-agnostic playback engine used by both adapters and by the extension's
- `pnpm build`: Vite (CSS inlined into JS, no `.css` output) + `dts-bundle-generator` for `dist/index.d.ts`.
- **Keep `exports["."].default` pointing at `./dist/index.js`** (only `types` points at `src`). Resolving raw source forces every consumer's bundler to compile `.svelte` files, and the extension's build breaks.
-- `@rustrak/openshowcase-schema` stays `external` in `vite.config.ts`. Inlining it bundles a second copy of zod.
+- `@rustrak/openshowcase-schema` stays `external` in `vite.config.ts`. Inlining it bundles a second copy of valibot.
- `pnpm size`: 35 KB brotli budget for `dist/index.js`.
- `pnpm test`: Vitest projects `unit` (Node, for `core/`) and `component` (covers `__test__/component` + `__test__/integration` in real Chromium via Playwright, not jsdom).
diff --git a/packages/player-core/vite.config.ts b/packages/player-core/vite.config.ts
index a7e879d..ccc811d 100644
--- a/packages/player-core/vite.config.ts
+++ b/packages/player-core/vite.config.ts
@@ -23,9 +23,9 @@ export default defineConfig({
formats: ["es"],
fileName: () => "index.js",
},
- // The schema (and zod behind it) is a declared dependency, not something to inline:
+ // The schema (and valibot behind it) is a declared dependency, not something to inline:
// every consumer already has it (the adapters import `parseDemo` from it), so bundling
- // a private copy here only doubled zod in the host app.
+ // a private copy here only doubled valibot in the host app.
rollupOptions: {
external: ["@rustrak/openshowcase-schema"],
},
diff --git a/packages/player-core/vitest.config.ts b/packages/player-core/vitest.config.ts
index 104619c..18ffb2f 100644
--- a/packages/player-core/vitest.config.ts
+++ b/packages/player-core/vitest.config.ts
@@ -5,10 +5,10 @@ import { defineConfig } from "vitest/config";
export default defineConfig({
plugins: [tailwindcss(), svelte()],
- // Pre-bundle zod up front — the integration test is the first to pull in @rustrak/openshowcase-schema's
+ // Pre-bundle valibot up front — the integration test is the first to pull in @rustrak/openshowcase-schema's
// runtime code inside the browser project, and an on-demand optimize mid-run forces Vite
// to reload the page, breaking whatever test was in flight.
- optimizeDeps: { include: ["@rustrak/openshowcase-schema > zod"] },
+ optimizeDeps: { include: ["@rustrak/openshowcase-schema > valibot"] },
test: {
projects: [
{
diff --git a/packages/schema/CLAUDE.md b/packages/schema/CLAUDE.md
index 4251eba..2842ead 100644
--- a/packages/schema/CLAUDE.md
+++ b/packages/schema/CLAUDE.md
@@ -1,7 +1,8 @@
# @rustrak/openshowcase-schema
-Zod schema + TS types for the demo format (`steps.json`). This is the source of truth for every other workspace. Its only runtime dependency is `zod` (3 KB brotli budget, `pnpm size`).
+Valibot schema + TS types for the demo format (`steps.json`). This is the source of truth for every other workspace. Its only runtime dependency is `valibot` (3 KB brotli budget, `pnpm size`). Valibot was picked over Zod for the host app's bundle: see issue #4.
- Bump `SCHEMA_VERSION` in `src/index.ts` on any breaking change to the `Demo`/`Step` shape.
-- `parseDemo()` is the single validation point for untrusted JSON. Consumers rely on it, so don't add ad hoc validation elsewhere.
+- `parseDemo()` is the single validation point for untrusted JSON. Consumers rely on it, so don't add ad hoc validation elsewhere. It throws `DemoParseError`, never the library's own error: that keeps the validator swappable without breaking the public API.
+- `pnpm test`: Vitest in Node. `parse-demo.test.ts` pins the validation rules, so any change to a rule shows up there.
- Changes ripple everywhere. Grep `packages/`, `adapters/` and `apps/` for a field before renaming or removing it.
diff --git a/packages/schema/README.md b/packages/schema/README.md
index 1138dc0..65af9a0 100644
--- a/packages/schema/README.md
+++ b/packages/schema/README.md
@@ -1,6 +1,6 @@
# @rustrak/openshowcase-schema
-The [OpenShowcase](https://github.com/rustrak/openshowcase) demo format (`steps.json`): a Zod schema and TypeScript types.
+The [OpenShowcase](https://github.com/rustrak/openshowcase) demo format (`steps.json`): a [Valibot](https://valibot.dev) schema and TypeScript types.
## Install
@@ -16,7 +16,19 @@ import { parseDemo, type Demo } from "@rustrak/openshowcase-schema";
const demo: Demo = parseDemo(await (await fetch("/demos/my-demo/steps.json")).json());
```
-`parseDemo` throws a `ZodError` if the data doesn't match the schema.
+`parseDemo` throws a `DemoParseError` if the data doesn't match the schema. Its `issues` list each problem with the dot path to the offending value:
+
+```ts
+import { DemoParseError, parseDemo } from "@rustrak/openshowcase-schema";
+
+try {
+ parseDemo(json);
+} catch (error) {
+ if (error instanceof DemoParseError) {
+ console.error(error.issues); // [{ path: "steps.1.hotspot.x", message: "..." }]
+ }
+}
+```
## Format overview
diff --git a/packages/schema/package.json b/packages/schema/package.json
index 9a1dadb..30724d4 100644
--- a/packages/schema/package.json
+++ b/packages/schema/package.json
@@ -1,14 +1,14 @@
{
"name": "@rustrak/openshowcase-schema",
"version": "0.1.0",
- "description": "Zod schema and TypeScript types for the OpenShowcase interactive demo format",
+ "description": "Valibot schema and TypeScript types for the OpenShowcase interactive demo format",
"keywords": [
"openshowcase",
"interactive-demo",
"product-demo",
"product-tour",
"schema",
- "zod"
+ "valibot"
],
"license": "MIT",
"author": "AbianS",
@@ -41,10 +41,16 @@
"build": "rslib build",
"dev": "rslib build --watch",
"check-types": "tsc --noEmit",
+ "test": "vitest run",
+ "test:watch": "vitest",
"size": "size-limit"
},
- "dependencies": {
- "zod": "4.6.5"
+ "devDependencies": {
+ "@rslib/core": "1.0.1",
+ "@size-limit/file": "14.0.0",
+ "size-limit": "14.0.0",
+ "typescript": "6.0.3",
+ "vitest": "5.0.1"
},
"size-limit": [
{
@@ -53,10 +59,7 @@
"limit": "3 KB"
}
],
- "devDependencies": {
- "@rslib/core": "1.0.1",
- "@size-limit/file": "14.0.0",
- "size-limit": "14.0.0",
- "typescript": "6.0.3"
+ "dependencies": {
+ "valibot": "1.5.0"
}
}
diff --git a/packages/schema/rslib.config.ts b/packages/schema/rslib.config.ts
index 0354add..668bdd8 100644
--- a/packages/schema/rslib.config.ts
+++ b/packages/schema/rslib.config.ts
@@ -1,9 +1,12 @@
import { defineConfig } from "@rslib/core";
-// Pure TS+zod library, no framework/CSS to process. autoExternal (default on) keeps `zod`
+// Pure TS+valibot library, no framework/CSS to process. autoExternal (default on) keeps `valibot`
// as a real `import` in the output rather than bundling it in — correct for a package meant
-// to be published standalone, where the consumer brings their own zod.
+// to be published standalone, where the consumer brings their own valibot.
export default defineConfig({
+ // Build-only tsconfig: tests live under src/ (so check-types sees them) but must not get
+ // emitted declarations in dist/.
+ source: { tsconfigPath: "./tsconfig.build.json" },
lib: [
{
format: "esm",
diff --git a/packages/schema/src/__test__/unit/parse-demo.test.ts b/packages/schema/src/__test__/unit/parse-demo.test.ts
new file mode 100644
index 0000000..60c3b98
--- /dev/null
+++ b/packages/schema/src/__test__/unit/parse-demo.test.ts
@@ -0,0 +1,190 @@
+import { describe, expect, it } from "vitest";
+import { type Demo, DemoParseError, parseDemo } from "../../index";
+
+function createDemo(): Demo {
+ return {
+ id: "demo-1",
+ title: "Overview",
+ video: {
+ src: "assets/recording.webm",
+ width: 3000,
+ height: 1790,
+ durationSec: 12.5,
+ },
+ theme: { wrapper: "browser", autoplay: true, appearance: "light" },
+ steps: [
+ { id: "step-1", type: "video", startTime: 1.23, endTime: 3.99 },
+ {
+ id: "step-2",
+ type: "photo",
+ image: { src: "assets/step-1.webp", width: 3000, height: 1790 },
+ hotspot: {
+ x: 0.16,
+ y: 0.32,
+ label: "Click the project",
+ bgColor: "#C5F11E",
+ textColor: "#0C0C0C",
+ position: "auto",
+ },
+ panZoom: { x: 0.28, y: 0.32, scale: 1.8 },
+ },
+ {
+ id: "step-3",
+ type: "video",
+ startTime: 4,
+ endTime: 9,
+ playbackRate: 2,
+ panZoom: { x: 0.5, y: 0.5, scale: 1.5, duration: 800, easing: "fast" },
+ },
+ ],
+ };
+}
+
+type Mutable = Record & { steps: Record[] };
+
+/** A deep copy of the demo, with `edit` applied, as untyped JSON. */
+function demoWith(edit: (demo: Mutable) => void): unknown {
+ const demo = JSON.parse(JSON.stringify(createDemo())) as Mutable;
+ edit(demo);
+ return demo;
+}
+
+const photo = (demo: Mutable) => demo.steps[1] as Record;
+const video = (demo: Mutable) => demo.steps[2] as Record;
+
+describe("parseDemo", () => {
+ it("returns a valid demo unchanged", () => {
+ expect(parseDemo(createDemo())).toEqual(createDemo());
+ });
+
+ it("accepts a demo without a recording and with no steps", () => {
+ const demo = demoWith((d) => {
+ delete d.video;
+ d.steps = [];
+ });
+ expect(parseDemo(demo)).toEqual(demo);
+ });
+
+ it("accepts a hotspot with only its position", () => {
+ const demo = demoWith((d) => {
+ photo(d).hotspot = { x: 0, y: 1 } as never;
+ });
+ expect(parseDemo(demo)).toEqual(demo);
+ });
+
+ it("drops unknown keys", () => {
+ const demo = demoWith((d) => {
+ d.extra = 1;
+ photo(d).junk = true as never;
+ });
+ expect(parseDemo(demo)).toEqual(createDemo());
+ });
+
+ it.each<[string, (d: Mutable) => void]>([
+ ["a missing title", (d) => delete d.title],
+ ["a numeric id", (d) => (d.id = 1)],
+ ["a missing theme", (d) => delete d.theme],
+ [
+ "an unknown wrapper",
+ (d) => (d.theme = { ...(d.theme as object), wrapper: "phone" }),
+ ],
+ [
+ "a non-boolean autoplay",
+ (d) => (d.theme = { ...(d.theme as object), autoplay: "yes" }),
+ ],
+ [
+ "steps that aren't an array",
+ (d) => ((d as Record).steps = {}),
+ ],
+ [
+ "a recording with zero duration",
+ (d) => (d.video = { ...(d.video as object), durationSec: 0 }),
+ ],
+ ["an unknown step type", (d) => (photo(d).type = "gif" as never)],
+ [
+ "an image with zero width",
+ (d) => (photo(d).image = { src: "a.webp", width: 0, height: 1 } as never),
+ ],
+ [
+ "a hotspot x above 1",
+ (d) => (photo(d).hotspot = { x: 1.5, y: 0.5 } as never),
+ ],
+ [
+ "a hotspot y below 0",
+ (d) => (photo(d).hotspot = { x: 0.5, y: -0.1 } as never),
+ ],
+ [
+ "a NaN hotspot coordinate",
+ (d) => (photo(d).hotspot = { x: Number.NaN, y: 0.5 } as never),
+ ],
+ [
+ "an unknown hotspot position",
+ (d) =>
+ (photo(d).hotspot = { x: 0.5, y: 0.5, position: "middle" } as never),
+ ],
+ [
+ "a zoom scale below 1",
+ (d) => (photo(d).panZoom = { x: 0.5, y: 0.5, scale: 0.5 } as never),
+ ],
+ [
+ "a zero zoom duration",
+ (d) =>
+ (photo(d).panZoom = { x: 0.5, y: 0.5, scale: 2, duration: 0 } as never),
+ ],
+ [
+ "an unknown zoom easing",
+ (d) =>
+ (photo(d).panZoom = {
+ x: 0.5,
+ y: 0.5,
+ scale: 2,
+ easing: "bounce",
+ } as never),
+ ],
+ ["a negative clip start", (d) => (video(d).startTime = -1)],
+ ["a zero clip end", (d) => (video(d).endTime = 0)],
+ [
+ "an infinite clip end",
+ (d) => (video(d).endTime = Number.POSITIVE_INFINITY),
+ ],
+ ["a zero playback rate", (d) => (video(d).playbackRate = 0)],
+ ])("rejects %s", (_, edit) => {
+ expect(() => parseDemo(demoWith(edit))).toThrow(DemoParseError);
+ });
+
+ it("rejects null", () => {
+ expect(() => parseDemo(null)).toThrow(DemoParseError);
+ });
+});
+
+describe("DemoParseError", () => {
+ it("is an Error that lists every issue with its path", () => {
+ const data = demoWith((d) => {
+ d.title = 42;
+ photo(d).hotspot = { x: 1.5, y: 0.5 } as never;
+ });
+
+ let error: unknown;
+ try {
+ parseDemo(data);
+ } catch (caught) {
+ error = caught;
+ }
+
+ expect(error).toBeInstanceOf(Error);
+ expect(error).toBeInstanceOf(DemoParseError);
+ const { name, issues } = error as DemoParseError;
+ expect(name).toBe("DemoParseError");
+ expect(issues.map((issue) => issue.path)).toEqual([
+ "title",
+ "steps.1.hotspot.x",
+ ]);
+ for (const issue of issues) expect(issue.message).not.toBe("");
+ });
+
+ it("names the failing path in its message", () => {
+ expect(() => parseDemo(demoWith((d) => (video(d).endTime = 0)))).toThrow(
+ /steps\.2\.endTime/,
+ );
+ });
+});
diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts
index 6cb0838..bd6b7fc 100644
--- a/packages/schema/src/index.ts
+++ b/packages/schema/src/index.ts
@@ -1,44 +1,47 @@
-import { z } from "zod";
+import * as v from "valibot";
-const frac = z.number().min(0).max(1);
+// `v.number()` accepts Infinity; the demo format never has an unbounded number
+const number = () => v.pipe(v.number(), v.finite());
+const positive = () => v.pipe(v.number(), v.finite(), v.gtValue(0));
+const frac = v.pipe(v.number(), v.minValue(0), v.maxValue(1));
-export const ImageAssetSchema = z.object({
- src: z.string(),
- width: z.number().positive(),
- height: z.number().positive(),
+export const ImageAssetSchema = v.object({
+ src: v.string(),
+ width: positive(),
+ height: positive(),
});
-export type ImageAsset = z.infer;
+export type ImageAsset = v.InferOutput;
-export const VideoAssetSchema = z.object({
- src: z.string(),
- width: z.number().positive(),
- height: z.number().positive(),
- durationSec: z.number().positive(),
+export const VideoAssetSchema = v.object({
+ src: v.string(),
+ width: positive(),
+ height: positive(),
+ durationSec: positive(),
});
-export type VideoAsset = z.infer;
+export type VideoAsset = v.InferOutput;
-export const HotspotPositionSchema = z.enum([
+export const HotspotPositionSchema = v.picklist([
"auto",
"top",
"bottom",
"left",
"right",
]);
-export type HotspotPosition = z.infer;
+export type HotspotPosition = v.InferOutput;
/**
* Pulsing hotspot: fractional coordinates (0-1) over the image, with its own tooltip
* (label) and style. The demo pauses until the user clicks it.
*/
-export const HotspotSchema = z.object({
+export const HotspotSchema = v.object({
x: frac,
y: frac,
- label: z.string().optional(),
- bgColor: z.string().optional(),
- textColor: z.string().optional(),
- position: HotspotPositionSchema.optional(),
+ label: v.optional(v.string()),
+ bgColor: v.optional(v.string()),
+ textColor: v.optional(v.string()),
+ position: v.optional(HotspotPositionSchema),
});
-export type Hotspot = z.infer;
+export type Hotspot = v.InferOutput;
export const defaultHotspotStyle = {
// Rustrak lime (#C5F11E, the brand OpenShowcase shares) with near-black text on it
@@ -54,45 +57,45 @@ export const defaultHotspotStyle = {
* the scale, with the origin always fixed at 50%/50%. That way going from one zoom to another
* (or to "no zoom") is a single continuous `transform` transition.
*/
-export const PanZoomEasingSchema = z.enum([
+export const PanZoomEasingSchema = v.picklist([
"smooth",
"cinematic",
"fast",
"linear",
]);
-export type PanZoomEasing = z.infer;
+export type PanZoomEasing = v.InferOutput;
-export const PanZoomSchema = z.object({
+export const PanZoomSchema = v.object({
x: frac,
y: frac,
- scale: z.number().min(1),
+ scale: v.pipe(number(), v.minValue(1)),
/** Transition duration (ms). Defaults to `defaultPanZoomTiming.duration` when unset. */
- duration: z.number().positive().optional(),
+ duration: v.optional(positive()),
/** Named easing preset (mapped to a CSS timing function in player-core). Defaults to
* `defaultPanZoomTiming.easing` when unset. */
- easing: PanZoomEasingSchema.optional(),
+ easing: v.optional(PanZoomEasingSchema),
});
-export type PanZoom = z.infer;
+export type PanZoom = v.InferOutput;
export const defaultPanZoomTiming = {
duration: 1000,
easing: "cinematic",
} as const satisfies Partial;
-export const ThemeWrapperSchema = z.enum(["none", "browser"]);
-export type ThemeWrapper = z.infer;
+export const ThemeWrapperSchema = v.picklist(["none", "browser"]);
+export type ThemeWrapper = v.InferOutput;
-export const ThemeAppearanceSchema = z.enum(["light", "dark"]);
-export type ThemeAppearance = z.infer;
+export const ThemeAppearanceSchema = v.picklist(["light", "dark"]);
+export type ThemeAppearance = v.InferOutput;
-export const ThemeSchema = z.object({
+export const ThemeSchema = v.object({
wrapper: ThemeWrapperSchema,
/** After the last step, restart the demo from the beginning instead of stopping. */
- autoplay: z.boolean(),
+ autoplay: v.boolean(),
/** Colors of the wrapper and the player controls — to match the light/dark mode of the embedding site. */
appearance: ThemeAppearanceSchema,
});
-export type Theme = z.infer;
+export type Theme = v.InferOutput;
export const defaultTheme: Theme = {
wrapper: "browser",
@@ -100,43 +103,68 @@ export const defaultTheme: Theme = {
appearance: "light",
};
-export const PhotoStepSchema = z.object({
- id: z.string(),
- type: z.literal("photo"),
+export const PhotoStepSchema = v.object({
+ id: v.string(),
+ type: v.literal("photo"),
image: ImageAssetSchema,
- hotspot: HotspotSchema.optional(),
- panZoom: PanZoomSchema.optional(),
+ hotspot: v.optional(HotspotSchema),
+ panZoom: v.optional(PanZoomSchema),
});
-export type PhotoStep = z.infer;
+export type PhotoStep = v.InferOutput;
/** A trim (in/out in seconds) of the demo's continuous recording (`Demo.video`). */
-export const VideoStepSchema = z.object({
- id: z.string(),
- type: z.literal("video"),
- startTime: z.number().nonnegative(),
- endTime: z.number().positive(),
- playbackRate: z.number().positive().optional(),
- panZoom: PanZoomSchema.optional(),
+export const VideoStepSchema = v.object({
+ id: v.string(),
+ type: v.literal("video"),
+ startTime: v.pipe(number(), v.minValue(0)),
+ endTime: positive(),
+ playbackRate: v.optional(positive()),
+ panZoom: v.optional(PanZoomSchema),
});
-export type VideoStep = z.infer;
+export type VideoStep = v.InferOutput;
-export const StepSchema = z.discriminatedUnion("type", [
- PhotoStepSchema,
- VideoStepSchema,
-]);
-export type Step = z.infer;
+export const StepSchema = v.variant("type", [PhotoStepSchema, VideoStepSchema]);
+export type Step = v.InferOutput;
-export const DemoSchema = z.object({
- id: z.string(),
- title: z.string(),
- video: VideoAssetSchema.optional(),
+export const DemoSchema = v.object({
+ id: v.string(),
+ title: v.string(),
+ video: v.optional(VideoAssetSchema),
theme: ThemeSchema,
- steps: z.array(StepSchema),
+ steps: v.array(StepSchema),
});
-export type Demo = z.infer;
+export type Demo = v.InferOutput;
+
+export interface DemoIssue {
+ /** Dot path to the offending value, e.g. `steps.1.hotspot.x` (empty for the root). */
+ path: string;
+ message: string;
+}
+
+/** Thrown by `parseDemo` when the data doesn't match the demo format. */
+export class DemoParseError extends Error {
+ override readonly name = "DemoParseError";
+ readonly issues: DemoIssue[];
+
+ constructor(issues: DemoIssue[]) {
+ const lines = issues.map(
+ (issue) => `- ${issue.path || "(root)"}: ${issue.message}`,
+ );
+ super(`Invalid demo:\n${lines.join("\n")}`);
+ this.issues = issues;
+ }
+}
+/** Validates untrusted data (e.g. a fetched `steps.json`) as a `Demo`, dropping unknown keys. */
export function parseDemo(data: unknown): Demo {
- return DemoSchema.parse(data);
+ const result = v.safeParse(DemoSchema, data);
+ if (result.success) return result.output;
+ throw new DemoParseError(
+ result.issues.map((issue) => ({
+ path: v.getDotPath(issue) ?? "",
+ message: issue.message,
+ })),
+ );
}
export const SCHEMA_VERSION = 6 as const;
diff --git a/packages/schema/tsconfig.build.json b/packages/schema/tsconfig.build.json
new file mode 100644
index 0000000..6079c7f
--- /dev/null
+++ b/packages/schema/tsconfig.build.json
@@ -0,0 +1,4 @@
+{
+ "extends": "./tsconfig.json",
+ "exclude": ["src/**/__test__"]
+}
diff --git a/packages/schema/vitest.config.ts b/packages/schema/vitest.config.ts
new file mode 100644
index 0000000..5dc7374
--- /dev/null
+++ b/packages/schema/vitest.config.ts
@@ -0,0 +1,8 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ include: ["src/__test__/unit/**/*.test.ts"],
+ environment: "node",
+ },
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2e169fe..1c0582e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -435,9 +435,9 @@ importers:
packages/schema:
dependencies:
- zod:
- specifier: 4.6.5
- version: 4.6.5
+ valibot:
+ specifier: 1.5.0
+ version: 1.5.0(typescript@6.0.3)
devDependencies:
'@rslib/core':
specifier: 1.0.1
@@ -451,6 +451,9 @@ importers:
typescript:
specifier: 6.0.3
version: 6.0.3
+ vitest:
+ specifier: 5.0.1
+ version: 5.0.1(@types/node@26.6.2)(@vitest/browser-playwright@5.0.1)(@vitest/ui@5.0.1)(vite@8.3.0(@types/node@26.6.2))
packages:
@@ -2348,6 +2351,14 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+ valibot@1.5.0:
+ resolution: {integrity: sha512-nil6AkP2TChWL43Z5uJ6GTxX01CUA+g8LWUM+N/rB9NBbkUMaUsi9PUNzlUPgoKASgmx9f7eGOYpJ04/fSa6FQ==}
+ peerDependencies:
+ typescript: '>=5'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
vite-plugin-css-injected-by-js@5.0.2:
resolution: {integrity: sha512-Hvg48QfYsCL53ju9T5zTC0Ofg90XLa8t5zhhKmPYbayoMQieNcQDkkRX8pv4TAcKPZwVisLFiwDy5uBjOn+3Ow==}
peerDependencies:
@@ -2529,9 +2540,6 @@ packages:
zimmerframe@1.1.5:
resolution: {integrity: sha512-msJxIvYDYcoNL+PJsu+7qmpDWsYmAxTY+2TNYXXF0hzBzBk0BMecOqDOG/EckUoKCuKwObfbugIl8QpqHDXeFA==}
- zod@4.6.5:
- resolution: {integrity: sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==}
-
snapshots:
'@1natsu/wait-element@4.2.0':
@@ -4212,6 +4220,10 @@ snapshots:
util-deprecate@1.0.2: {}
+ valibot@1.5.0(typescript@6.0.3):
+ optionalDependencies:
+ typescript: 6.0.3
+
vite-plugin-css-injected-by-js@5.0.2(vite@8.3.0(@types/node@26.6.2)(jiti@2.7.0)(yaml@2.9.1)):
dependencies:
vite: 8.3.0(@types/node@26.6.2)(jiti@2.7.0)(yaml@2.9.1)
@@ -4359,5 +4371,3 @@ snapshots:
yargs-parser: 21.1.1
zimmerframe@1.1.5: {}
-
- zod@4.6.5: {}